@crouton-kit/crouter 0.3.70 → 0.3.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +1 -1
  2. package/dist/build-root.d.ts +12 -4
  3. package/dist/build-root.js +25 -6
  4. package/dist/builtin-memory/00-runtime-base.md +3 -10
  5. package/dist/builtin-memory/04-base-worker.md +18 -0
  6. package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
  7. package/dist/builtin-memory/crouter-development/plugins.md +82 -5
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1115 -1
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +234 -71
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -516
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
  14. package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
  15. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
  16. package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
  17. package/dist/builtin-views/canvas/tui.mjs +8 -9
  18. package/dist/builtin-views/chat/tui.mjs +12 -12
  19. package/dist/builtin-views/git-pr/tui.mjs +7 -8
  20. package/dist/builtin-views/inbox/tui.mjs +27 -41
  21. package/dist/builtin-views/linkedin/tui.mjs +23 -37
  22. package/dist/builtin-views/prompt-review/tui.mjs +11 -11
  23. package/dist/builtin-views/settings/tui.mjs +11 -11
  24. package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
  25. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -6
  26. package/dist/clients/attach/__tests__/attach-keybindings.test.d.ts +1 -0
  27. package/dist/clients/attach/__tests__/attach-keybindings.test.js +113 -0
  28. package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
  29. package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
  30. package/dist/clients/attach/__tests__/mermaid-render.test.js +28 -0
  31. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
  32. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
  33. package/dist/clients/attach/attach-cmd.d.ts +9 -1
  34. package/dist/clients/attach/attach-cmd.js +841 -792
  35. package/dist/clients/attach/auth-pickers.d.ts +0 -12
  36. package/dist/clients/attach/auth-pickers.js +64 -15
  37. package/dist/clients/attach/canvas-panels.js +2 -3
  38. package/dist/clients/attach/chat-view.d.ts +7 -8
  39. package/dist/clients/attach/chat-view.js +119 -79
  40. package/dist/clients/attach/edit-diff-render.d.ts +6 -0
  41. package/dist/clients/attach/edit-diff-render.js +263 -0
  42. package/dist/clients/attach/graph-overlay.d.ts +12 -2
  43. package/dist/clients/attach/graph-overlay.js +83 -33
  44. package/dist/clients/attach/input-controller.d.ts +28 -2
  45. package/dist/clients/attach/input-controller.js +38 -5
  46. package/dist/clients/attach/mermaid-render.js +5 -2
  47. package/dist/clients/attach/pickers.d.ts +8 -7
  48. package/dist/clients/attach/pickers.js +11 -17
  49. package/dist/clients/attach/slash-commands.d.ts +9 -0
  50. package/dist/clients/attach/slash-commands.js +127 -7
  51. package/dist/clients/attach/titled-editor.d.ts +12 -1
  52. package/dist/clients/attach/titled-editor.js +103 -8
  53. package/dist/commands/canvas-browse.js +2 -2
  54. package/dist/commands/human/queue.js +3 -4
  55. package/dist/commands/memory/lint.js +39 -5
  56. package/dist/commands/memory/write.js +1 -0
  57. package/dist/commands/node.js +9 -2
  58. package/dist/commands/pkg/plugin-inspect.js +22 -1
  59. package/dist/commands/pkg/plugin-manage.js +31 -9
  60. package/dist/commands/surface-tmux-spread.js +1 -3
  61. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
  62. package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
  63. package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
  64. package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
  65. package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
  66. package/dist/commands/sys/config.js +18 -21
  67. package/dist/commands/sys/doctor.js +42 -4
  68. package/dist/commands/sys/setup-core.d.ts +49 -1
  69. package/dist/commands/sys/setup-core.js +161 -4
  70. package/dist/commands/sys/setup.d.ts +88 -0
  71. package/dist/commands/sys/setup.js +943 -169
  72. package/dist/commands/view-pick.d.ts +4 -0
  73. package/dist/commands/view-pick.js +17 -7
  74. package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
  75. package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
  76. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
  77. package/dist/core/__tests__/canvas-inbox-watcher.test.js +34 -9
  78. package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +1 -0
  79. package/dist/core/__tests__/command-plugins-surfaces.test.js +298 -0
  80. package/dist/core/__tests__/command-plugins.test.d.ts +1 -0
  81. package/dist/core/__tests__/command-plugins.test.js +444 -0
  82. package/dist/core/__tests__/fault-classifier.test.js +15 -0
  83. package/dist/core/__tests__/fixtures/fake-engine.d.ts +6 -0
  84. package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
  85. package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
  86. package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
  87. package/dist/core/__tests__/preview-registry-sync.test.js +30 -1
  88. package/dist/core/__tests__/scope-crouter-home-fence.test.d.ts +1 -0
  89. package/dist/core/__tests__/scope-crouter-home-fence.test.js +55 -0
  90. package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
  91. package/dist/core/__tests__/stream-watchdog.test.js +70 -0
  92. package/dist/core/__tests__/tmux-surface.test.js +72 -0
  93. package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
  94. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  95. package/dist/core/canvas/browse/app.d.ts +6 -0
  96. package/dist/core/canvas/browse/app.js +230 -41
  97. package/dist/core/canvas/browse/model.d.ts +10 -6
  98. package/dist/core/canvas/browse/model.js +20 -12
  99. package/dist/core/canvas/browse/pins.d.ts +4 -0
  100. package/dist/core/canvas/browse/pins.js +29 -0
  101. package/dist/core/canvas/browse/render.d.ts +13 -1
  102. package/dist/core/canvas/browse/render.js +72 -15
  103. package/dist/core/canvas/nav-model.js +5 -11
  104. package/dist/core/canvas/paths.d.ts +5 -0
  105. package/dist/core/canvas/paths.js +7 -0
  106. package/dist/core/command-plugins/adapter.d.ts +15 -0
  107. package/dist/core/command-plugins/adapter.js +145 -0
  108. package/dist/core/command-plugins/compose.d.ts +5 -0
  109. package/dist/core/command-plugins/compose.js +56 -0
  110. package/dist/core/command-plugins/discovery.d.ts +104 -0
  111. package/dist/core/command-plugins/discovery.js +565 -0
  112. package/dist/core/config.d.ts +48 -0
  113. package/dist/core/config.js +228 -110
  114. package/dist/core/fault-classifier.js +1 -1
  115. package/dist/core/fs-utils.d.ts +1 -0
  116. package/dist/core/fs-utils.js +15 -1
  117. package/dist/core/keybindings/__tests__/bespoke-consumers.test.d.ts +1 -0
  118. package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +40 -0
  119. package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
  120. package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
  121. package/dist/core/keybindings/catalog.d.ts +14 -0
  122. package/dist/core/keybindings/catalog.js +257 -0
  123. package/dist/core/keybindings/index.d.ts +5 -0
  124. package/dist/core/keybindings/index.js +4 -0
  125. package/dist/core/keybindings/match.d.ts +29 -0
  126. package/dist/core/keybindings/match.js +99 -0
  127. package/dist/core/keybindings/persistence.d.ts +14 -0
  128. package/dist/core/keybindings/persistence.js +31 -0
  129. package/dist/core/keybindings/resolve.d.ts +14 -0
  130. package/dist/core/keybindings/resolve.js +305 -0
  131. package/dist/core/keybindings/types.d.ts +37 -0
  132. package/dist/core/keybindings/types.js +1 -0
  133. package/dist/core/memory-resolver.d.ts +1 -1
  134. package/dist/core/memory-resolver.js +10 -3
  135. package/dist/core/predicate.d.ts +5 -3
  136. package/dist/core/predicate.js +5 -3
  137. package/dist/core/profiles/select.d.ts +6 -0
  138. package/dist/core/profiles/select.js +86 -52
  139. package/dist/core/provider-management.d.ts +12 -0
  140. package/dist/core/provider-management.js +24 -0
  141. package/dist/core/runtime/broker-protocol.d.ts +33 -5
  142. package/dist/core/runtime/broker.js +164 -14
  143. package/dist/core/runtime/launch.d.ts +39 -6
  144. package/dist/core/runtime/launch.js +78 -20
  145. package/dist/core/runtime/naming.js +3 -3
  146. package/dist/core/runtime/pi-cli.d.ts +6 -0
  147. package/dist/core/runtime/pi-cli.js +16 -2
  148. package/dist/core/runtime/pi-vendored.d.ts +8 -0
  149. package/dist/core/runtime/pi-vendored.js +14 -0
  150. package/dist/core/runtime/placement.d.ts +2 -2
  151. package/dist/core/runtime/placement.js +4 -1
  152. package/dist/core/runtime/promote.js +4 -0
  153. package/dist/core/runtime/recap.d.ts +3 -3
  154. package/dist/core/runtime/recap.js +75 -47
  155. package/dist/core/runtime/recycle.js +6 -1
  156. package/dist/core/runtime/reset.js +5 -0
  157. package/dist/core/runtime/session-list-cache.d.ts +33 -0
  158. package/dist/core/runtime/session-list-cache.js +338 -0
  159. package/dist/core/runtime/spawn.js +52 -45
  160. package/dist/core/runtime/stream-watchdog.d.ts +26 -0
  161. package/dist/core/runtime/stream-watchdog.js +75 -0
  162. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  163. package/dist/core/runtime/tmux-chrome.js +2 -2
  164. package/dist/core/runtime/tmux.d.ts +18 -15
  165. package/dist/core/runtime/tmux.js +217 -110
  166. package/dist/core/scope.js +27 -4
  167. package/dist/core/subscription-state.d.ts +90 -0
  168. package/dist/core/subscription-state.js +762 -0
  169. package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
  170. package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
  171. package/dist/core/tui/host.d.ts +30 -1
  172. package/dist/core/tui/host.js +148 -34
  173. package/dist/core/view/contract.d.ts +17 -3
  174. package/dist/daemon/crtrd.js +253 -12
  175. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
  176. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
  177. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
  178. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
  179. package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
  180. package/dist/pi-extensions/canvas-recap.d.ts +4 -0
  181. package/dist/pi-extensions/canvas-recap.js +73 -46
  182. package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
  183. package/dist/pi-extensions/canvas-stophook.js +33 -4
  184. package/dist/types.d.ts +9 -26
  185. package/dist/types.js +1 -28
  186. package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
  187. package/dist/web-client/index.html +1 -1
  188. package/dist/web-client/sw.js +1 -1
  189. package/package.json +8 -4
  190. package/dist/pi-extensions/canvas-nav.d.ts +0 -43
  191. package/dist/pi-extensions/canvas-nav.js +0 -640
  192. package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
  193. package/dist/pi-extensions/widget-order-bus.js +0 -34
@@ -10,6 +10,20 @@
10
10
  // `node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js`.
11
11
  // If the upstream list changes (entries added/removed/reworded), update the copy
12
12
  // below to match and adjust the count note.
13
+ import { homedir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ /** pi's sessions root, VENDORED from pi `config.getSessionsDir()` (= `<agentDir>/
16
+ * sessions`). pi's package `exports` map is `.`-only, so config.js can't be
17
+ * deep-imported, and a ROOT import of `getAgentDir` would eager-load the entire
18
+ * heavy pi SDK index on crtr's front-door hot path (the reason broker-sdk.ts
19
+ * dynamic-imports the engine). Mirrors pi: `PI_CODING_AGENT_DIR` env, else
20
+ * `~/.pi/agent` (APP_NAME defaults to 'pi'). Re-sync on a pi SDK bump that moves
21
+ * the sessions dir — same vendoring rationale as the slash-command list below. */
22
+ export function piSessionsRoot() {
23
+ const env = process.env['PI_CODING_AGENT_DIR'];
24
+ const agentDir = env !== undefined && env !== '' ? env : join(homedir(), '.pi', 'agent');
25
+ return join(agentDir, 'sessions');
26
+ }
13
27
  /**
14
28
  * pi's builtin slash commands — vendored verbatim from pi `core/slash-commands.js`
15
29
  * `BUILTIN_SLASH_COMMANDS` (review C1). **21 entries at 0.78.1** (review n1 — NOT 23).
@@ -1,7 +1,7 @@
1
1
  import { type FocusRow } from '../canvas/index.js';
2
2
  export type { FocusRow };
3
- export { splitWindow, breakPane, selectLayout, selectPane, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installMenuBinding, setPaneOption, getPaneOption, piCommand, shellQuote, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
4
- export type { RespawnPaneOpts } from './tmux.js';
3
+ export { splitWindow, breakPane, selectLayout, selectPane, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installTmuxBindings, installTmuxBindings as installMenuBinding, setPaneOption, getPaneOption, piCommand, shellQuote, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
4
+ export type { RespawnPaneOpts, TmuxInstalledPair, TmuxInstallDiagnostic, TmuxInstallResult } from './tmux.js';
5
5
  /** The viewer a node has on screen, or null. UNIQUE(node_id) ⇒ at most one. */
6
6
  export declare function focusOf(nodeId: string): FocusRow | null;
7
7
  /** Does this node have a live viewer on screen? */
@@ -48,7 +48,10 @@ import { isPidAlive } from '../canvas/pid.js';
48
48
  // re-exported for the same reason — callers building the seam's expected
49
49
  // bundled-pi command string (e.g. the M1 seam test) need the identical quoting
50
50
  // piCommand uses internally, without importing tmux.ts directly.
51
- export { splitWindow, breakPane, selectLayout, selectPane, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installMenuBinding, setPaneOption, getPaneOption, piCommand, shellQuote, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
51
+ export { splitWindow, breakPane, selectLayout, selectPane, focusWindow, selectWindow, switchClient, openNodeWindow, ensureSession, paneLocation, paneExists, paneRunning, paneCurrentPath, currentTmux, inTmux, installTmuxBindings,
52
+ // Compile seam for attach-cmd.ts, which still calls installMenuBinding();
53
+ // deleted when the attach unit imports installTmuxBindings directly.
54
+ installTmuxBindings as installMenuBinding, setPaneOption, getPaneOption, piCommand, shellQuote, respawnPaneSync, closePane, windowOfPane, renameWindow, paneOfWindow, windowAlive, listLivePanes, } from './tmux.js';
52
55
  // ---------------------------------------------------------------------------
53
56
  // Viewer registry reads — COMPOSE over the canvas focuses table (§2.3/§4).
54
57
  //
@@ -55,6 +55,10 @@ export function promote(nodeId, opts = {}) {
55
55
  // across the polymorph (the persona default is recomputed fresh for
56
56
  // targetKind).
57
57
  model: targetModel,
58
+ // Resolve kind/ladder config against the NODE's own cwd + profile, not
59
+ // this process's ambient scope.
60
+ cwd: node.cwd,
61
+ profileId: node.profile_id,
58
62
  });
59
63
  // Seed a barebones roadmap scaffold if absent so the file exists for a
60
64
  // refresh. Pre-fill its Goal from the node's goal doc when present (set at
@@ -1,8 +1,8 @@
1
- /** Ask pi headlessly for a three-fragment recap of `conversation`, async. Invokes
2
- * `onRecap` with the parsed fragment lines on success, or never (silent) on any
1
+ /** Ask pi headlessly for a concise briefing of `conversation`, async. Invokes
2
+ * `onRecap` with the parsed string on success, or never (silent) on any
3
3
  * failure — non-zero exit, timeout, empty/garbled output. Owns the subprocess
4
4
  * mechanics — crucially it hands pi an immediate stdin EOF: `pi -p` reads stdin,
5
5
  * and execFile's default stdin is an OPEN pipe that never closes, so without
6
6
  * this pi blocks waiting for EOF and the call exits non-zero (the same gotcha
7
7
  * naming.ts documents). */
8
- export declare function generateRecap(conversation: string, onRecap: (lines: string[]) => void): void;
8
+ export declare function generateRecap(conversation: string, onRecap: (briefing: string) => void, liveModel?: string, roadmap?: string): void;
@@ -1,9 +1,10 @@
1
- // Inactivity recap — turn a dormant node's conversation into a three-fragment
2
- // "where it left off" card (goal / doing-now / next) for the recap widget.
1
+ // Inactivity recap — turn a dormant node's conversation into one concise
2
+ // returning-developer briefing for the recap widget.
3
3
  //
4
4
  // One entry point: generateRecap — async (execFile, non-blocking), mirroring
5
- // naming.ts's headless-`pi -p` pattern exactly. It runs Haiku over the literal
6
- // back-and-forth (the caller concatenates user+assistant text) with a stripped-
5
+ // naming.ts's headless-`pi -p` pattern exactly. It runs the live conversation's
6
+ // working model over the literal back-and-forth (the caller concatenates user and
7
+ // assistant text, plus the node's roadmap when one exists) with a stripped-
7
8
  // down pi invocation (no tools/session/context/extensions/skills), so it's fast
8
9
  // and side-effect free and can never recurse into another spawn.
9
10
  //
@@ -15,37 +16,67 @@
15
16
  // Plain TS-with-types — no imports from @earendil-works/* so it compiles inside
16
17
  // crouter's own tsc build without a dep on the pi packages.
17
18
  import { execFile } from 'node:child_process';
18
- import { resolveBundledPiCliPath } from './pi-cli.js';
19
+ import { bundledPiSubprocessEnv, resolveBundledPiCliPath } from './pi-cli.js';
19
20
  /** Wall-clock budget for the headless pi call before we give up (no recap). */
20
21
  const RECAP_TIMEOUT_MS = 25_000;
21
- /** Default recap model Haiku. A recap is a one-shot summarization, so we pin
22
- * the small/fast model rather than the node's heavyweight default. Override
23
- * with CRTR_RECAP_MODEL (mirrors CRTR_NAME_MODEL). */
22
+ /** Fallback model when the live session has not resolved one yet. Ordinarily the
23
+ * caller passes the provider/model that just completed the conversation's last
24
+ * turn, avoiding a dead hard-pinned provider. CRTR_RECAP_MODEL overrides both. */
24
25
  const DEFAULT_RECAP_MODEL = 'anthropic/claude-haiku-4-5';
25
- const RECAP_SYSTEM_PROMPT = 'You write a terse "where we left off" recap of a coding-agent work session, ' +
26
- 'read at a glance by someone returning to a paused conversation. You are given ' +
27
- 'the literal back-and-forth between the user and the agent. Output EXACTLY three ' +
28
- 'lines, nothing elseno labels, no numbering, no bullets, no punctuation at the ' +
29
- 'end of a line, no prose:\n' +
30
- ' Line 1: the GOAL what this session is ultimately trying to achieve.\n' +
31
- ' Line 2: DOING NOW what was just happening / the active piece of work.\n' +
32
- ' Line 3: NEXT — the immediate next step.\n' +
33
- 'Each line is a short FRAGMENT, not a sentence (e.g. `wire up recap widget above editor`, ' +
34
- 'not `We are wiring up the recap widget.`). Keep each under ~60 characters. ' +
35
- 'If the session has barely started or you cannot tell, still give your best three ' +
36
- 'fragments from whatever text exists. Output JUST the three lines.';
37
- /** Put the conversation FIRST in a delimited block, then the instruction, so the
38
- * model reads the content before being told what to do. */
39
- function recapUserPrompt(conversation) {
40
- return `<conversation>\n${conversation}\n</conversation>\n\nWrite the three-line recap of the session above (goal / doing-now / next). Output JUST the three lines.`;
26
+ const RECAP_SYSTEM_PROMPT = 'You brief a developer returning to a paused coding-agent conversation. They run ' +
27
+ 'many agent sessions in parallel and have lost this one\'s thread: they no longer ' +
28
+ 'know what this conversation is about or what they originally asked for. The ' +
29
+ 'brief exists to solve exactly that pinned above their input box, it must tell ' +
30
+ 'them at a glance what this conversation is FOR and where it actually stands. ' +
31
+ 'You are given the first and most recent messages of the literal user/agent ' +
32
+ 'back-and-forth (tool output omitted), and the agent\'s roadmap file when one ' +
33
+ 'exists.\n' +
34
+ '\n' +
35
+ 'Output EXACTLY two plain-text lines, nothing else:\n' +
36
+ 'Goal: <what was requested the original ask / what this conversation is for>\n' +
37
+ 'State: <catch-them-up: what is ACTUALLY going on right now>\n' +
38
+ '\n' +
39
+ 'Both lines are extremely short telegraphic fragments, roughly 8-15 words, no ' +
40
+ 'filler. The State line must NOT restate the most recent message — the last ' +
41
+ 'message is often mid-progress, a question, or noise; instead synthesize the ' +
42
+ 'real position: what has landed, what is in flight, and the live blocker or ' +
43
+ 'needed answer. If the agent is waiting on the developer, say exactly what for. ' +
44
+ 'Be concrete (real file names, branches, SHAs, error messages — one specific ' +
45
+ 'anchor beats abstractions). Never narrate turn by turn, hedge, or pad. If the ' +
46
+ 'conversation has barely started, still emit your best two lines.\n' +
47
+ '\n' +
48
+ 'Examples of the shape (from other conversations):\n' +
49
+ 'Goal: Validate + commit the Unit 3 attach-keybinding migration in this worktree.\n' +
50
+ 'State: Done — 17/17 tests green, committed and rebased onto main.\n' +
51
+ '\n' +
52
+ 'Goal: Build the trimmed-v1 command-plugin seam (orchestrating phases).\n' +
53
+ 'State: Phase 1 landed; waiting on the core-seam worker\'s final.\n' +
54
+ '\n' +
55
+ 'Goal: Standing personal assistant — iMessage wakes, Reddit scans, small tasks.\n' +
56
+ 'State: Idle — waiting on you: hook pick, map export, Omi timing.';
57
+ /** Put the content FIRST in delimited blocks (roadmap, then conversation), then
58
+ * the instruction, so the model reads the content before being told what to do. */
59
+ function recapUserPrompt(conversation, roadmap) {
60
+ const rm = roadmap !== undefined && roadmap.trim() !== ''
61
+ ? `<roadmap>\n${roadmap.trim()}\n</roadmap>\n\n`
62
+ : '';
63
+ return `${rm}<conversation>\n${conversation}\n</conversation>\n\nWrite the two-line Goal:/State: briefing for the conversation above. Output JUST the two lines.`;
41
64
  }
42
65
  /** The pi argv for a headless recap request. Stripped down (no tools, session,
43
66
  * context files, extensions, skills, templates, themes) so it's fast and
44
- * side-effect free. Pinned to Haiku with thinking off. Override the model with
45
- * CRTR_RECAP_MODEL. */
46
- function recapArgs(conversation) {
67
+ * side-effect free. Thinking stays off; model precedence is explicit env
68
+ * override → the conversation's live working model → the small fallback. */
69
+ function recapArgs(conversation, liveModel, roadmap) {
47
70
  const override = process.env['CRTR_RECAP_MODEL'];
48
- const model = override !== undefined && override.trim() !== '' ? override.trim() : DEFAULT_RECAP_MODEL;
71
+ // An explicit operator override wins. Otherwise prefer the live conversation's
72
+ // current model: it just completed a successful turn, while a hard-pinned
73
+ // provider (the old Haiku-only path) may be rate-limited and silently produce
74
+ // no brief. The small default remains for sessions with no resolved model yet.
75
+ const model = override !== undefined && override.trim() !== ''
76
+ ? override.trim()
77
+ : liveModel !== undefined && liveModel.trim() !== ''
78
+ ? liveModel.trim()
79
+ : DEFAULT_RECAP_MODEL;
49
80
  const argv = [
50
81
  '-p',
51
82
  '--no-session',
@@ -59,44 +90,41 @@ function recapArgs(conversation) {
59
90
  '--thinking', 'off',
60
91
  '--model', model,
61
92
  '--system-prompt', RECAP_SYSTEM_PROMPT,
62
- recapUserPrompt(conversation),
93
+ recapUserPrompt(conversation, roadmap),
63
94
  ];
64
95
  return argv;
65
96
  }
66
- /** Parse the model's raw stdout into up to three short fragment lines. Strips any
67
- * stray leading labels/bullets the model may have added despite instructions,
68
- * drops empties, and hard-caps line length so a runaway line can't blow up the
69
- * widget. Returns [] when nothing usable survives (→ no recap). */
97
+ /** Normalize model stdout to the two-line `Goal:`/`State:` briefing. Collapses
98
+ * intra-line whitespace and drops blank lines; the viewer paints each line
99
+ * (wrapping to its width). A garbled shape still returns whatever lines came
100
+ * back the widget is best-effort chrome, not a contract. */
70
101
  function parseRecap(stdout) {
71
- const LINE_CAP = 72;
72
- const lines = stdout
102
+ return stdout
73
103
  .split('\n')
74
- .map((l) => l.trim())
75
- // Strip a leading `goal:` / `now:` / `next:` / `- ` / `1.` style prefix.
76
- .map((l) => l.replace(/^(?:[-*•]\s*)?(?:\d+[.)]\s*)?(?:goal|now|doing(?:[\s-]now)?|next)\s*[:\-]\s*/i, '').trim())
77
- .filter((l) => l !== '');
78
- return lines.slice(0, 3).map((l) => (l.length > LINE_CAP ? l.slice(0, LINE_CAP) : l));
104
+ .map((l) => l.replace(/\s+/g, ' ').trim())
105
+ .filter((l) => l !== '')
106
+ .join('\n');
79
107
  }
80
- /** Ask pi headlessly for a three-fragment recap of `conversation`, async. Invokes
81
- * `onRecap` with the parsed fragment lines on success, or never (silent) on any
108
+ /** Ask pi headlessly for a concise briefing of `conversation`, async. Invokes
109
+ * `onRecap` with the parsed string on success, or never (silent) on any
82
110
  * failure — non-zero exit, timeout, empty/garbled output. Owns the subprocess
83
111
  * mechanics — crucially it hands pi an immediate stdin EOF: `pi -p` reads stdin,
84
112
  * and execFile's default stdin is an OPEN pipe that never closes, so without
85
113
  * this pi blocks waiting for EOF and the call exits non-zero (the same gotcha
86
114
  * naming.ts documents). */
87
- export function generateRecap(conversation, onRecap) {
115
+ export function generateRecap(conversation, onRecap, liveModel, roadmap) {
88
116
  const body = (conversation ?? '').trim();
89
117
  if (body === '')
90
118
  return;
91
119
  try {
92
- const child = execFile(process.execPath, [resolveBundledPiCliPath(), ...recapArgs(body)], { encoding: 'utf8', timeout: RECAP_TIMEOUT_MS }, (err, stdout) => {
120
+ const child = execFile(process.execPath, [resolveBundledPiCliPath(), ...recapArgs(body, liveModel, roadmap)], { encoding: 'utf8', timeout: RECAP_TIMEOUT_MS, env: bundledPiSubprocessEnv() }, (err, stdout) => {
93
121
  if (err || typeof stdout !== 'string')
94
122
  return; // silent — no recap
95
- const lines = parseRecap(stdout);
96
- if (lines.length === 0)
123
+ const briefing = parseRecap(stdout);
124
+ if (briefing === '')
97
125
  return;
98
126
  try {
99
- onRecap(lines);
127
+ onRecap(briefing);
100
128
  }
101
129
  catch { /* best-effort */ }
102
130
  });
@@ -97,7 +97,11 @@ export async function recycleNode(nodeId, callerPane) {
97
97
  ensureDaemon();
98
98
  }
99
99
  catch { /* daemon is best-effort */ }
100
- const { launch } = buildLaunchSpec('general', 'base', { lifecycle: 'resident', hasManager: false });
100
+ // Resolve against the RECYCLED node's cwd + profile (not this process's
101
+ // ambient scope) and carry the profile onto the fresh root — it is the same
102
+ // person's seat, so profile-scope defaults (e.g. a persisted /model default)
103
+ // must keep applying.
104
+ const { launch } = buildLaunchSpec('general', 'base', { lifecycle: 'resident', hasManager: false, cwd: meta.cwd, profileId: meta.profile_id });
101
105
  const root = spawnNode({
102
106
  kind: 'general',
103
107
  mode: 'base',
@@ -105,6 +109,7 @@ export async function recycleNode(nodeId, callerPane) {
105
109
  cwd: meta.cwd,
106
110
  name: 'general',
107
111
  parent: null,
112
+ profile_id: meta.profile_id,
108
113
  launch,
109
114
  });
110
115
  // Hand the viewport to the fresh root: reuse M's focus row over the SAME pane
@@ -86,6 +86,10 @@ export async function relaunchRoot(oldId, deps = {}) {
86
86
  lifecycle: 'resident',
87
87
  hasManager: false,
88
88
  model: old.model_override ?? undefined,
89
+ // The OLD root's cwd + profile, not this process's ambient scope — the
90
+ // relaunch is the same seat, so profile-scope kind defaults keep applying.
91
+ cwd: old.cwd,
92
+ profileId: old.profile_id,
89
93
  });
90
94
  const newMeta = spawnNode({
91
95
  kind: old.kind,
@@ -94,6 +98,7 @@ export async function relaunchRoot(oldId, deps = {}) {
94
98
  cwd: old.cwd,
95
99
  name: old.kind,
96
100
  parent: null,
101
+ profile_id: old.profile_id,
97
102
  launch,
98
103
  modelOverride: old.model_override ?? undefined,
99
104
  });
@@ -0,0 +1,33 @@
1
+ import type { WireSessionInfo } from './broker-protocol.js';
2
+ export declare function compactSessionSearchText(text: string): string;
3
+ /** In-memory + file-backed cache keyed by absolute `.jsonl` path. One instance
4
+ * per broker; the file is shared across brokers (last-writer-wins via atomic
5
+ * rename — a lost update just costs one file's rebuild next open, never
6
+ * corruption). */
7
+ export declare class SessionListCache {
8
+ private readonly cacheFile;
9
+ private cache;
10
+ private loaded;
11
+ private persisting;
12
+ private persistAgain;
13
+ private persistTimer;
14
+ constructor(cacheFile: string);
15
+ /** Fire-and-forget warm of the cwd scope at broker start, so the first
16
+ * `/resume` open after this broker launches is already warm. The cold scan
17
+ * streams each file via readline (yields per chunk), so it never blocks the
18
+ * broker loop — pre-warming beats worker threads here. Errors are swallowed
19
+ * by the same paths that guard a normal open. */
20
+ prewarm(sessionDir: string): void;
21
+ /** Sessions for one cwd's session dir (the picker's default `cwd` scope). */
22
+ listDir(sessionDir: string): Promise<WireSessionInfo[]>;
23
+ /** Sessions across every cwd's dir under the sessions root (`all` scope). */
24
+ listAll(sessionsRoot: string): Promise<WireSessionInfo[]>;
25
+ private build;
26
+ private ensureLoaded;
27
+ /** Coalesce persists onto a single trailing timer so the tens-of-MB
28
+ * `JSON.stringify` never runs on the reply path and rapid opens write once. */
29
+ private schedulePersist;
30
+ /** Atomic write (temp + rename) so a concurrent reader never sees a partial
31
+ * file. Coalesces overlapping writes into one trailing flush. */
32
+ private persist;
33
+ }