Skip to content

feat(dictation): every app's sessions in the list, grouped by app (core 0.44.0) - #248

Merged
Maxaubert merged 22 commits into
mainfrom
feat/dictation-all-apps
Oct 17, 2026
Merged

Maxaubert merged 22 commits into
mainfrom
feat/dictation-all-apps

Conversation

@Maxaubert

Copy link
Copy Markdown
Owner

Stacked on #246 (dictate from any app, the one dictation): merges after it; retarget to main before #246 merges with its branch deleted.

Owner, 2026-10-17: "I just tried opening a cloud session in Prism and tried dictating, but the session from Prism didn't show up. So that should be fixed. I think the sessions should be divided. Like for example, if it's a session from Prism, it should have the header Prism above it ... If it's another Prism terminal session or instance ... it should be called Prism terminal, and then maybe parentheses two". Then "Yes, plan and build it". Spec and plan: docs/superpowers/specs/2026-10-17-dictation-all-apps.md.

What the owner gets

  • The list after dictating is grouped under a header per app ("Prism Terminal", "Prism Terminal (Stable)", "Prism Terminal (Dev)", a second of one name numbered "(2)", and "Prism" once Prism joins), only when more than one app has sessions. The numbers 1 to 9 run on across the groups. Screenshot: .e2e-shots/dictation-widget-groups.png.
  • A window of ANY app on the link counts as in front: the hook names the front window's process for every key down (a process id only, PRIVACY.md says so).

Core 0.44.0: what Prism needs to join (Prism's wiring is its own PR, after it pins this core; its session has the plan)

  • core/main/dictationLink.ts (moved from the app): the authenticated pipe, linkKey, linkPipe, copyName.
  • core/main/orchestratorSend.ts (moved): the send rules.
  • core/main/dictationClient.ts (new): typeChecked (the rule 2 exception's checks: Windows' process check, decided again before typing and before Enter), publishTabs, dictationAnswers, startDictationClient. Prism Terminal uses the same pieces.

Tests: unit 2314 pass (dictationClient: pastes then Enters, refuses where Windows sees no Claude, refuses a tab that changed, holds the Enter for a question; grouping, numbering, no headers for one app). e2e: dictateTwoCopies 10/10 (both copies' Claude under two headers, a pick typed by the other copy, a paste at its cursor with no Enter, the hand-over), dictateAnywhere 9/9, dictation 21/21, dictationPage 36/36, dictationParakeet 11/11, launchSkeleton 10/10, updateWindow 78/78. "The real clipboard: untouched by the run".

Core 0.43.0 -> 0.44.0, app 0.57.0 -> 0.58.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

Maxaubert and others added 19 commits October 16, 2026 13:43
…ve not seen (core 0.42.0)

Owner, 2026-10-16: a report of what the Claude sessions did while away and
what needs them next, based on whether they have SEEN each session, with a
tab left in front while away not counted as seen. Spec and plan:
docs/superpowers/specs/2026-10-16-catch-up-report.md (reviewed before
building; 25 findings folded in).

Core: the turn's own hooks (UserPromptSubmit, Stop) add session=<id>
(hook.cmd reads CLAUDE_CODE_SESSION_ID with delayed expansion, only a
UUID-shaped value); the parser carries it; setSessionsFile + an always
replaced PRISM_SESSIONS_FILE in ptyEnv; the plugin's catch-up skill and its
Windows PowerShell 5.1 script.

App: sessionLedger (pure): sessions kept apart from tabs; seen = in front
10 s while present (focused, shown, not locked or asleep, input within
120 s) or typed into, capped at last input + 10 s, restarted after any time
away. sessionsWire writes sessions.json, flushed with the tabs in close,
session-end and will-quit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
The digest quotes other sessions (and whatever web pages or files they
read), so a blanket powershell grant would let injected text run any
command without a prompt. The two script calls now ask like any other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
…role

Owner, 2026-10-16: others who download the terminal should be able to
use it "if they want ... if they don't want it, it's like already in
there"; an orchestrator tab "promoted in the tab bar ... first in the list
and there's a divider line"; "right click and choose make orchestrator it
already knows".

- Settings > Agents > Orchestrator (beta), off by default. Off: no session
  list, no file, no PRISM_SESSIONS_FILE, no plugin, no menu entry.
- core/claude-plugin-orchestrator: a second Claude plugin given to new
  tabs only when it is on, with the catch-up and orchestrator skills and
  role.cmd, which adds a fixed additionalContext in the promoted tab only
  (MEASURED: a plugin hook's additionalContext reaches the model).
- Make orchestrator in a tab's menu: one tab, kept first, an accent
  outline and rule; saved in tabs.json; orchestrator-tab.txt names its
  PRISM_TAB_ID for the role hook.
- The script gains -List and -Only for the orchestrator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
Owner, 2026-10-16: "the terminal needs to be able to send it, and it needs
to be able to send it to any session, not just whichever is in focus.
That is very important." An owner exception to rule 2, recorded in
regression-rules.md and CLAUDE.md.

- orchestratorSend.ts (pure): only from the promoted tab, into the tab
  named by label, a word of its label or folder, its session or its
  PRISM_TAB_ID; never into a bare shell (it would run), never while that
  tab asks a question (it would answer it); one sanitised bracketed paste,
  then Enter after 300 ms (MEASURED on Claude Code 2.1.301: one prompt,
  multi-line kept).
- The orchestrator's send.ps1 drops a request in the app's outbox and
  waits for its answer: SENT to <label>, or NOT SENT: <why>.
- An agent arriving in a tab is now news to the ledger (the e2e caught the
  list lagging).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
Security review of the sends: a tab's Claude mark rests on in-band
signals any program in the tab can print (or a stale one), so it was
enough to make the app type into a shell. Now, at the moment of typing,
agentUnder (core/main/agentPoll.ts) asks the process tree itself; no
Claude under that tab's shell, no send. The e2e proves both: a shell that
only claims to be Claude is refused, a stand-in claude.exe in the tree
gets the message.

A name now resolves to a tab's own ids first, then an exact label, a word
only when nothing else matched and only when it names one tab; -List
prints each tab's id and the skill sends by it.

The process-spawning unit tests get 60 s: 5.8 s on a busy PC failed the
5 s default in a full run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
Security review: the process-tree check takes about a second, in which
the switch, the tab or its state can change. The send is decided again,
synchronously, right before the paste (the same tab and session must
win), and once more before the Enter: a question that opened in between
would take it as its answer, so the text then waits unsent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
…re 0.43.0)

Owner, 2026-10-16: start dictation with the app not in focus, a widget
on screen while dictating, then the words go to the orchestrator when it
is ACTIVE, or to a session picked from the widget (a click or its
number); one dictation key, Ctrl+Win. Spec and plan:
docs/superpowers/specs/2026-10-16-dictate-anywhere.md.

Core: DEFAULT_HOTKEY Ctrl+Win; a chord of modifiers follows the solo
rules (either key first, Ctrl+Win+Left never dictates); a saved key is
kept; dictationOutside / setDictationRoute; DictationPage takes `after`.

App: a WH_KEYBOARD_LL helper (MEASURED: 0.3 to 1.2 ms, no Defender
detection; reads only the binding's keys, "another key", and digits/Esc
only while picking; masks Start after Ctrl+Win), the reducer fed from it
(globalDictKey), the widget window (unfocusable, on top, sized for 225%),
routing through the checked send path, "Dictate from any app" in
Settings. Never runs under --e2e: the suite feeds lines through a seam.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
The default is Ctrl+Win now; these hold Right Alt to keep the AltGr-era
rules proven on a real key. dictation 21/21, dictationParakeet 11/11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
…alls back to the default

Owner, 2026-10-17: "very buggy and unresponsive, it says now it cant find
the mic ... the widget doesnt always show ... i have to press multiple
times". MEASURED in both diag logs: Stable and the installed app both ran
the keyboard hook, so every press went to both; the installed copy failed
in 8 ms ("No microphone found", a stale device id) and its widget covered
Stable's.

- The hook helper holds a lock file while it hooks: the first copy owns the
  key, a later one waits and takes over within 500 ms of the first exiting
  (a named Mutex hung the waiting helper on PowerShell's STA thread).
- A key down says when a Prism Terminal window is in front; another copy
  in front reads the key itself, so this one stays out.
- A saved mic that no longer matches records from the system default.
- Diag crumbs for the hook (ready/waiting/failed) and the key's actions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
…nd again

MEASURED 2026-10-17: a closing copy started a fresh network service child
after the first force; it outlived its parent, the wait ran out and the
refresh changed nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
… copy for every copy

Owner, 2026-10-17: "the dictation in the app and outside seem like two
different things ... they should be the same thing"; "there should be one
dictation engine for both ... it should show all sessions open in one ui
from both instances"; "It needs to be ready at all times". Spec and plan
(approved): docs/superpowers/specs/2026-10-17-one-dictation.md.

- One key path: the hook reads the key with the app in front too; the page
  stops reading it whenever a service is in reach. One widget everywhere.
- The copy holding the hook's lock is the service; every other copy is a
  client on a local pipe (dictationLink): it answers with its tabs, and
  types or pastes into them through its own checked path.
- The words go to the copy whose window was in front at the press (pasted
  at its front tab's cursor, never Enter), else to the active orchestrator
  or the list, which holds every copy's Claude sessions, named by copy.
- The engine never stands down in Prism Terminal (core: idleMs), and only
  the service's copy warms one.
- Core (Prism unchanged): setDictationPageKeys, setDictationAutoWarm,
  keepDictationWarm, pasteIntoFront, registerDictationIpc idleMs.
- The mic opens on each press (owner's choice).

Tests: dictationLink (messages, a real pipe), the engine's Infinity idle,
the key with this copy in front, mergeTabs; e2e dictateTwoCopies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
Owner, 2026-10-17: thin blue horizontal lines over the skeleton while the
sessions load. They were the link decorations (termLinkPaint, xterm's
z-index 6) of the paths Claude prints as it resumes, in the same stacking
context as the skeleton (z-index 1). The terminal's region is now a
stacking context of its own (`isolate`). e2e launchSkeleton checks it,
and fails without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
Owner, 2026-10-17: "look into the performance part ... it's mainly startup
performance"; chose the resident Parakeet process. MEASURED on the owner's
RTX 5090: parakeet-cli took 0.72 to 0.86 s a pass whatever the clip's
length (nearly all model load and CUDA start); a resident host answers in
24 ms (1 s clip) to 170 ms (60 s clip), the text identical, ready 0.8 s
after spawn, paid once in the background after launch. Report:
research/prism-terminal/2026-10-17-dictation-performance.md.

- core/dictation-host/parakeetHost.ps1: a long-lived Windows PowerShell
  (the app's helper pattern, no new binary) calling the engine's own
  parakeet.dll; its compiled C# is cached by hash (0.86 -> 0.78 s start).
- The engine uses it when the host passes `parakeetHost`; one that never
  gets ready falls back to parakeet-cli for the session. stop(), the idle
  stand-down and a Whisper model end it. Prism passes none: unchanged.
- Shipped as resources/dictation-host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
…e key hook

Owner, 2026-10-17: merge the helpers; "it's mainly startup performance".
The DWM helper and the dictation key's hook each started their own Windows
PowerShell (about 65 MB each, MEASURED). Now one, src/main/hook/appHelper.ps1
(was globalKeyHook.ps1), started with the app: it answers `dwm` at once and
runs the hook on its own thread between `hook 1` and `hook 0`, letting the
lock go when it is turned off. Its compiled C# is cached by hash in the
profile: spawn to up 320 -> 220 ms; spawn() on main 3 to 4 ms (-File, never
-EncodedCommand, #180). A helper that dies is started and armed again.
Media pause stays its own, lazy process: it costs nothing at launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
… key

Security review 2026-10-17: the pipe between copies had no authentication,
so a process that took its name first could pose as the service and have a
copy type into its Claude. Now the user's link key (32 random bytes in
PrismDictation\link.key under %LOCALAPPDATA%, made once with an exclusive
create) names the pipe by its hash, and each side answers the other's fresh
nonce with an HMAC before anything else is said; a peer that fails is
dropped, and a client answers, types and pastes for a proven service only.
Tests: a client and a service without the key are never trusted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
…re 0.44.0)

Owner, 2026-10-17: "I just tried opening a cloud session in Prism and tried
dictating, but the session from Prism didn't show up ... if it's a session
from Prism, it should have the header Prism above it ... another Prism
terminal instance ... called Prism terminal, and then maybe parentheses
two"; then "Yes, plan and build it". Spec and plan:
docs/superpowers/specs/2026-10-17-dictation-all-apps.md.

- Core: the link (dictationLink), the send rules (orchestratorSend) and a
  ready client (dictationClient: typeChecked, publishTabs,
  startDictationClient), so Prism joins with its tabs, its checked send and
  its front-tab paste. Prism Terminal uses the same core pieces.
- The hook names the front window's process for every key down, so a
  window of any app on the link counts as in front.
- Names: "Prism Terminal", "Prism Terminal (Stable)", "Prism Terminal
  (Dev)", "Prism"; a second of one name is numbered "(2)".
- The widget groups the list under a header per app when more than one has
  sessions; the numbers run on across the groups.

Prism's own wiring follows in its repo after it pins this core.
Tests: dictationClient (the checks), grouping, numbering; e2e
dictateTwoCopies with both copies' Claude under two headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
…ks first

Owner, 2026-10-17: "The dictation only opens inside of Prism Terminal, not
outside it." MEASURED in both installed 0.58.0 copies: no hook crumb at all,
and the key's lock free. The page says its dictation key before the window
is shown, and the helper's path was set only then, so `hook 1` went nowhere
and was never sent again. Lines sent before the helper is configured are now
kept and sent once it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA
@Maxaubert
Maxaubert changed the base branch from feat/dictate-anywhere to main October 17, 2026 09:22
@Maxaubert
Maxaubert merged commit 7cfd21d into main Oct 17, 2026
2 checks passed
@Maxaubert
Maxaubert deleted the feat/dictation-all-apps branch October 17, 2026 09:27
github-actions Bot pushed a commit that referenced this pull request Oct 17, 2026
…re 0.44.0) (#248)

* feat: catch-up report: which session is in which tab, and what you have not seen (core 0.42.0)

Owner, 2026-10-16: a report of what the Claude sessions did while away and
what needs them next, based on whether they have SEEN each session, with a
tab left in front while away not counted as seen. Spec and plan:
docs/superpowers/specs/2026-10-16-catch-up-report.md (reviewed before
building; 25 findings folded in).

Core: the turn's own hooks (UserPromptSubmit, Stop) add session=<id>
(hook.cmd reads CLAUDE_CODE_SESSION_ID with delayed expansion, only a
UUID-shaped value); the parser carries it; setSessionsFile + an always
replaced PRISM_SESSIONS_FILE in ptyEnv; the plugin's catch-up skill and its
Windows PowerShell 5.1 script.

App: sessionLedger (pure): sessions kept apart from tabs; seen = in front
10 s while present (focused, shown, not locked or asleep, input within
120 s) or typed into, capped at last input + 10 s, restarted after any time
away. sessionsWire writes sessions.json, flushed with the tabs in close,
session-end and will-quit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(catch-up): no tool allowlist in the skill

The digest quotes other sessions (and whatever web pages or files they
read), so a blanket powershell grant would let injected text run any
command without a prompt. The two script calls now ask like any other.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* feat: the orchestrator is opt-in, with a promoted tab that knows its role

Owner, 2026-10-16: others who download the terminal should be able to
use it "if they want ... if they don't want it, it's like already in
there"; an orchestrator tab "promoted in the tab bar ... first in the list
and there's a divider line"; "right click and choose make orchestrator it
already knows".

- Settings > Agents > Orchestrator (beta), off by default. Off: no session
  list, no file, no PRISM_SESSIONS_FILE, no plugin, no menu entry.
- core/claude-plugin-orchestrator: a second Claude plugin given to new
  tabs only when it is on, with the catch-up and orchestrator skills and
  role.cmd, which adds a fixed additionalContext in the promoted tab only
  (MEASURED: a plugin hook's additionalContext reaches the model).
- Make orchestrator in a tab's menu: one tab, kept first, an accent
  outline and rule; saved in tabs.json; orchestrator-tab.txt names its
  PRISM_TAB_ID for the role hook.
- The script gains -List and -Only for the orchestrator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* feat: the orchestrator sends a message into any tab's Claude

Owner, 2026-10-16: "the terminal needs to be able to send it, and it needs
to be able to send it to any session, not just whichever is in focus.
That is very important." An owner exception to rule 2, recorded in
regression-rules.md and CLAUDE.md.

- orchestratorSend.ts (pure): only from the promoted tab, into the tab
  named by label, a word of its label or folder, its session or its
  PRISM_TAB_ID; never into a bare shell (it would run), never while that
  tab asks a question (it would answer it); one sanitised bracketed paste,
  then Enter after 300 ms (MEASURED on Claude Code 2.1.301: one prompt,
  multi-line kept).
- The orchestrator's send.ps1 drops a request in the app's outbox and
  waits for its answer: SENT to <label>, or NOT SENT: <why>.
- An agent arriving in a tab is now news to the ledger (the e2e caught the
  list lagging).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(orchestrator): ask Windows before typing into a tab; send by tab id

Security review of the sends: a tab's Claude mark rests on in-band
signals any program in the tab can print (or a stale one), so it was
enough to make the app type into a shell. Now, at the moment of typing,
agentUnder (core/main/agentPoll.ts) asks the process tree itself; no
Claude under that tab's shell, no send. The e2e proves both: a shell that
only claims to be Claude is refused, a stand-in claude.exe in the tree
gets the message.

A name now resolves to a tab's own ids first, then an exact label, a word
only when nothing else matched and only when it names one tab; -List
prints each tab's id and the skill sends by it.

The process-spawning unit tests get 60 s: 5.8 s on a busy PC failed the
5 s default in a full run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(orchestrator): decide again right before typing, and before Enter

Security review: the process-tree check takes about a second, in which
the switch, the tab or its state can change. The send is decided again,
synchronously, right before the paste (the same tab and session must
win), and once more before the Enter: a question that opened in between
would take it as its answer, so the text then waits unsent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* docs: spec and plan for dictating from any app

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* feat: dictate from any app, with a widget that sends to a session (core 0.43.0)

Owner, 2026-10-16: start dictation with the app not in focus, a widget
on screen while dictating, then the words go to the orchestrator when it
is ACTIVE, or to a session picked from the widget (a click or its
number); one dictation key, Ctrl+Win. Spec and plan:
docs/superpowers/specs/2026-10-16-dictate-anywhere.md.

Core: DEFAULT_HOTKEY Ctrl+Win; a chord of modifiers follows the solo
rules (either key first, Ctrl+Win+Left never dictates); a saved key is
kept; dictationOutside / setDictationRoute; DictationPage takes `after`.

App: a WH_KEYBOARD_LL helper (MEASURED: 0.3 to 1.2 ms, no Defender
detection; reads only the binding's keys, "another key", and digits/Esc
only while picking; masks Start after Ctrl+Win), the reducer fed from it
(globalDictKey), the widget window (unfocusable, on top, sized for 225%),
routing through the checked send path, "Dictate from any app" in
Settings. Never runs under --e2e: the suite feeds lines through a seam.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* test(e2e): the Right Alt dictation scenarios set their key

The default is Ctrl+Win now; these hold Right Alt to keep the AltGr-era
rules proven on a real key. dictation 21/21, dictationParakeet 11/11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(dictation): one copy of the app owns the global key; a gone mic falls back to the default

Owner, 2026-10-17: "very buggy and unresponsive, it says now it cant find
the mic ... the widget doesnt always show ... i have to press multiple
times". MEASURED in both diag logs: Stable and the installed app both ran
the keyboard hook, so every press went to both; the installed copy failed
in 8 ms ("No microphone found", a stale device id) and its widget covered
Stable's.

- The hook helper holds a lock file while it hooks: the first copy owns the
  key, a later one waits and takes over within 500 ms of the first exiting
  (a named Mutex hung the waiting helper on PowerShell's STA thread).
- A key down says when a Prism Terminal window is in front; another copy
  in front reads the key itself, so this one stays out.
- A saved mic that no longer matches records from the system default.
- Diag crumbs for the hook (ready/waiting/failed) and the key's actions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(tools): the stable refresh forces a leftover child closed again and again

MEASURED 2026-10-17: a closing copy started a fresh network service child
after the first force; it outlived its parent, the wait ran out and the
refresh changed nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* feat(dictation): one dictation for the app and outside it, run by one copy for every copy

Owner, 2026-10-17: "the dictation in the app and outside seem like two
different things ... they should be the same thing"; "there should be one
dictation engine for both ... it should show all sessions open in one ui
from both instances"; "It needs to be ready at all times". Spec and plan
(approved): docs/superpowers/specs/2026-10-17-one-dictation.md.

- One key path: the hook reads the key with the app in front too; the page
  stops reading it whenever a service is in reach. One widget everywhere.
- The copy holding the hook's lock is the service; every other copy is a
  client on a local pipe (dictationLink): it answers with its tabs, and
  types or pastes into them through its own checked path.
- The words go to the copy whose window was in front at the press (pasted
  at its front tab's cursor, never Enter), else to the active orchestrator
  or the list, which holds every copy's Claude sessions, named by copy.
- The engine never stands down in Prism Terminal (core: idleMs), and only
  the service's copy warms one.
- Core (Prism unchanged): setDictationPageKeys, setDictationAutoWarm,
  keepDictationWarm, pasteIntoFront, registerDictationIpc idleMs.
- The mic opens on each press (owner's choice).

Tests: dictationLink (messages, a real pipe), the engine's Infinity idle,
the key with this copy in front, mergeTabs; e2e dictateTwoCopies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(terminal): nothing of the terminal paints over the resume skeleton

Owner, 2026-10-17: thin blue horizontal lines over the skeleton while the
sessions load. They were the link decorations (termLinkPaint, xterm's
z-index 6) of the paths Claude prints as it resumes, in the same stacking
context as the skeleton (z-index 1). The terminal's region is now a
stacking context of its own (`isolate`). e2e launchSkeleton checks it,
and fails without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* perf(dictation): Parakeet stays loaded in a resident host

Owner, 2026-10-17: "look into the performance part ... it's mainly startup
performance"; chose the resident Parakeet process. MEASURED on the owner's
RTX 5090: parakeet-cli took 0.72 to 0.86 s a pass whatever the clip's
length (nearly all model load and CUDA start); a resident host answers in
24 ms (1 s clip) to 170 ms (60 s clip), the text identical, ready 0.8 s
after spawn, paid once in the background after launch. Report:
research/prism-terminal/2026-10-17-dictation-performance.md.

- core/dictation-host/parakeetHost.ps1: a long-lived Windows PowerShell
  (the app's helper pattern, no new binary) calling the engine's own
  parakeet.dll; its compiled C# is cached by hash (0.86 -> 0.78 s start).
- The engine uses it when the host passes `parakeetHost`; one that never
  gets ready falls back to parakeet-cli for the session. stop(), the idle
  stand-down and a Whisper model end it. Prism passes none: unchanged.
- Shipped as resources/dictation-host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* perf(app): one helper process at launch for the DWM attributes and the key hook

Owner, 2026-10-17: merge the helpers; "it's mainly startup performance".
The DWM helper and the dictation key's hook each started their own Windows
PowerShell (about 65 MB each, MEASURED). Now one, src/main/hook/appHelper.ps1
(was globalKeyHook.ps1), started with the app: it answers `dwm` at once and
runs the hook on its own thread between `hook 1` and `hook 0`, letting the
lock go when it is turned off. Its compiled C# is cached by hash in the
profile: spawn to up 320 -> 220 ms; spawn() on main 3 to 4 ms (-File, never
-EncodedCommand, #180). A helper that dies is started and armed again.
Media pause stays its own, lazy process: it costs nothing at launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(dictation): both ends of the link prove they hold the user's link key

Security review 2026-10-17: the pipe between copies had no authentication,
so a process that took its name first could pose as the service and have a
copy type into its Claude. Now the user's link key (32 random bytes in
PrismDictation\link.key under %LOCALAPPDATA%, made once with an exclusive
create) names the pipe by its hash, and each side answers the other's fresh
nonce with an HMAC before anything else is said; a peer that fails is
dropped, and a client answers, types and pastes for a proven service only.
Tests: a client and a service without the key are never trusted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* feat(dictation): every app's sessions in the list, grouped by app (core 0.44.0)

Owner, 2026-10-17: "I just tried opening a cloud session in Prism and tried
dictating, but the session from Prism didn't show up ... if it's a session
from Prism, it should have the header Prism above it ... another Prism
terminal instance ... called Prism terminal, and then maybe parentheses
two"; then "Yes, plan and build it". Spec and plan:
docs/superpowers/specs/2026-10-17-dictation-all-apps.md.

- Core: the link (dictationLink), the send rules (orchestratorSend) and a
  ready client (dictationClient: typeChecked, publishTabs,
  startDictationClient), so Prism joins with its tabs, its checked send and
  its front-tab paste. Prism Terminal uses the same core pieces.
- The hook names the front window's process for every key down, so a
  window of any app on the link counts as in front.
- Names: "Prism Terminal", "Prism Terminal (Stable)", "Prism Terminal
  (Dev)", "Prism"; a second of one name is numbered "(2)".
- The widget groups the list under a header per app when more than one has
  sessions; the numbers run on across the groups.

Prism's own wiring follows in its repo after it pins this core.
Tests: dictationClient (the checks), grouping, numbering; e2e
dictateTwoCopies with both copies' Claude under two headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

* fix(dictation): the key from any app is armed even when the page speaks first

Owner, 2026-10-17: "The dictation only opens inside of Prism Terminal, not
outside it." MEASURED in both installed 0.58.0 copies: no hook crumb at all,
and the key's lock free. The page says its dictation key before the window
is shown, and the helper's path was set only then, so `hook 1` went nowhere
and was never sent again. Lines sent before the helper is configured are now
kept and sent once it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant