@pellux/goodvibes-tui 1.1.0 → 1.9.0

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 (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -0,0 +1,148 @@
1
+ /**
2
+ * summarizeToolResult — one-line human summaries for tool RESULTS, so the
3
+ * transcript (and the fleet attach view, which shares the same rendering seam
4
+ * in conversation-rendering.ts) shows "wrote haiku.txt (532 B)" instead of a
5
+ * raw `{"files_written":1,"bytes_written":532,...}` JSON blob. The full payload
6
+ * stays reachable behind the existing collapse/expand toggle — this only
7
+ * changes the collapsed preview line, never destroys data. (UX-B item 3.)
8
+ *
9
+ * Returning `null` means "no honest summary for this shape" — the caller then
10
+ * falls back to the previous raw-first-line preview, so an unrecognised tool or
11
+ * an unexpected payload is never mis-summarised.
12
+ */
13
+
14
+ /** Compact byte count: "87 B", "1.2 KB", "3.4 MB". */
15
+ function formatBytes(n: number): string {
16
+ if (!Number.isFinite(n) || n < 0) return '? B';
17
+ if (n < 1024) return `${n} B`;
18
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
19
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
20
+ }
21
+
22
+ /** Compact duration: "820ms", "1.2s", "1m3s". */
23
+ function formatDuration(ms: number): string {
24
+ if (!Number.isFinite(ms) || ms < 0) return '?';
25
+ if (ms < 1000) return `${Math.round(ms)}ms`;
26
+ if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
27
+ const s = Math.round(ms / 1000);
28
+ return `${Math.floor(s / 60)}m${s % 60}s`;
29
+ }
30
+
31
+ /** Trailing filename for compact display (paths can be long/absolute). */
32
+ function baseName(p: string): string {
33
+ const cleaned = p.replace(/[/\\]+$/, '');
34
+ const idx = Math.max(cleaned.lastIndexOf('/'), cleaned.lastIndexOf('\\'));
35
+ return idx >= 0 ? cleaned.slice(idx + 1) : cleaned;
36
+ }
37
+
38
+ function asNumber(v: unknown): number | undefined {
39
+ return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
40
+ }
41
+
42
+ function firstPath(files: unknown): string | undefined {
43
+ if (!Array.isArray(files) || files.length === 0) return undefined;
44
+ const first = files[0];
45
+ if (typeof first === 'string') return first;
46
+ if (first && typeof first === 'object') {
47
+ const rec = first as Record<string, unknown>;
48
+ if (typeof rec.path === 'string') return rec.path;
49
+ if (typeof rec.resolved_path === 'string') return rec.resolved_path;
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ /** write: { files_written, bytes_written, files?: [{path, bytes_written}], dry_run? } */
55
+ function summarizeWrite(obj: Record<string, unknown>): string | null {
56
+ const count = asNumber(obj.files_written);
57
+ const bytes = asNumber(obj.bytes_written);
58
+ if (count === undefined) return null;
59
+ const verb = obj.dry_run === true ? 'would write' : 'wrote';
60
+ const size = bytes !== undefined ? ` (${formatBytes(bytes)})` : '';
61
+ if (count === 1) {
62
+ const path = firstPath(obj.files);
63
+ return path ? `${verb} ${baseName(path)}${size}` : `${verb} 1 file${size}`;
64
+ }
65
+ return `${verb} ${count} files${size}`;
66
+ }
67
+
68
+ /** read: { summary: { files_read, total_lines }, files?: [{path, lineCount}] } */
69
+ function summarizeRead(obj: Record<string, unknown>): string | null {
70
+ const summary = (obj.summary && typeof obj.summary === 'object')
71
+ ? obj.summary as Record<string, unknown>
72
+ : obj;
73
+ const count = asNumber(summary.files_read);
74
+ const lines = asNumber(summary.total_lines);
75
+ if (count === undefined && lines === undefined) return null;
76
+ const lineText = lines !== undefined ? ` (${lines} line${lines === 1 ? '' : 's'})` : '';
77
+ if (count === 1) {
78
+ const path = firstPath(obj.files);
79
+ return path ? `read ${baseName(path)}${lineText}` : `read 1 file${lineText}`;
80
+ }
81
+ return `read ${count ?? '?'} files${lineText}`;
82
+ }
83
+
84
+ /** edit: { applied, failed, dry_run } or richer with a path. */
85
+ function summarizeEdit(obj: Record<string, unknown>): string | null {
86
+ const applied = asNumber(obj.applied);
87
+ if (applied === undefined) return null;
88
+ const failed = asNumber(obj.failed) ?? 0;
89
+ const verb = obj.dry_run === true ? 'would apply' : 'applied';
90
+ const path = typeof obj.path === 'string' ? obj.path : firstPath(obj.files);
91
+ const target = path ? ` to ${baseName(path)}` : '';
92
+ const failText = failed > 0 ? `, ${failed} failed` : '';
93
+ return `${verb} ${applied} edit${applied === 1 ? '' : 's'}${target}${failText}`;
94
+ }
95
+
96
+ /** A single exec command result. */
97
+ function summarizeExecOne(obj: Record<string, unknown>): string | null {
98
+ const exit = obj.exit_code;
99
+ const dur = asNumber(obj.duration_ms);
100
+ const durText = dur !== undefined ? ` · ${formatDuration(dur)}` : '';
101
+ if (obj.timed_out === true) return `timed out${durText}`;
102
+ if (obj.cancelled === true) return `cancelled${durText}`;
103
+ const stdout = typeof obj.stdout === 'string' ? obj.stdout : '';
104
+ const lineCount = stdout.length === 0
105
+ ? 0
106
+ : stdout.replace(/\n$/, '').split('\n').length;
107
+ const lineText = lineCount > 0 ? ` · ${lineCount} line${lineCount === 1 ? '' : 's'}` : '';
108
+ const code = typeof exit === 'number' ? String(exit) : '?';
109
+ return `exit ${code}${durText}${lineText}`;
110
+ }
111
+
112
+ /** exec: single { cmd, exit_code, ... } or multi { commands: [...], total }. */
113
+ function summarizeExec(obj: Record<string, unknown>): string | null {
114
+ if (Array.isArray(obj.commands)) {
115
+ const total = asNumber(obj.total) ?? obj.commands.length;
116
+ const failed = obj.commands.filter(
117
+ (c) => c && typeof c === 'object' && (c as Record<string, unknown>).success === false,
118
+ ).length;
119
+ return failed > 0
120
+ ? `${total} commands · ${failed} failed`
121
+ : `${total} commands · all ok`;
122
+ }
123
+ if ('exit_code' in obj || 'timed_out' in obj || 'cancelled' in obj) {
124
+ return summarizeExecOne(obj);
125
+ }
126
+ return null;
127
+ }
128
+
129
+ export function summarizeToolResult(toolName: string | undefined, content: string): string | null {
130
+ if (!toolName) return null;
131
+ const family = toolName.includes('__') ? toolName.split('__').pop()! : toolName;
132
+ let data: unknown;
133
+ try {
134
+ data = JSON.parse(content);
135
+ } catch {
136
+ return null;
137
+ }
138
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) return null;
139
+ const obj = data as Record<string, unknown>;
140
+
141
+ switch (family) {
142
+ case 'write': return summarizeWrite(obj);
143
+ case 'read': return summarizeRead(obj);
144
+ case 'edit': return summarizeEdit(obj);
145
+ case 'exec': return summarizeExec(obj);
146
+ default: return null;
147
+ }
148
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Per-turn knowledge injection record rendering (Wave-5 W5.2, wo803).
3
+ *
4
+ * The SDK's passive per-turn retrieval engine (wo801, W5.1 —
5
+ * packages/sdk/src/platform/agents/turn-knowledge-injection.ts) stores one
6
+ * `TurnInjectionRecord` per turn on `AgentRecord.turnInjections` (a bounded
7
+ * ring, default 20 entries) and appends the same record to the agent's
8
+ * session transcript as `{type:'knowledge_injection', turn, ...record}`.
9
+ *
10
+ * `TurnInjectionRecord` itself is not re-exported from the SDK's
11
+ * `platform/agents` barrel (only `AgentRecord.turnInjections` carries its
12
+ * structural shape through the already-public `platform/tools` barrel), so
13
+ * the entry type here is DERIVED from `AgentRecord` rather than imported by
14
+ * name — this needs no SDK export change.
15
+ *
16
+ * Reality check (updated wo805): as of Wave-5 wo805 the TUI's main interactive
17
+ * session DOES route through this engine. The SDK `Orchestrator` runs per-turn
18
+ * passive injection on the evolving primary conversation and records each turn
19
+ * on its own bounded ring, exposed via `Orchestrator.getTurnInjections()` — the
20
+ * main-session counterpart to `AgentRecord.turnInjections` (there is no
21
+ * AgentRecord for the primary conversation). `buildMainSessionTurnInjectionsText`
22
+ * renders that ring for `/recall injections` with no agent id. Spawned agents
23
+ * (Task-tool runs, automation-triggered runs, session continuations) still
24
+ * record onto their own `AgentRecord.turnInjections`, rendered per-agent by
25
+ * `buildTurnInjectionsText` when an explicit agent id is given.
26
+ */
27
+ import type { AgentRecord } from '@pellux/goodvibes-sdk/platform/tools';
28
+
29
+ /** One TurnInjectionRecord entry, derived from AgentRecord (see module doc). */
30
+ export type TurnInjectionEntry = NonNullable<AgentRecord['turnInjections']>[number];
31
+
32
+ function fmtN(n: number): string {
33
+ return n.toLocaleString();
34
+ }
35
+
36
+ /**
37
+ * Annotate each injected id with its source (Stage B). Code-index hits get a
38
+ * ` [code]` tag; memory records are left bare so a memory-only line renders
39
+ * byte-identically to the pre-Stage-B output. `injectedSources` is parallel to
40
+ * `injectedIds`; a missing/short entry defaults to memory (no tag).
41
+ */
42
+ function labelInjectedIds(entry: TurnInjectionEntry): string {
43
+ const sources = entry.injectedSources ?? [];
44
+ return entry.injectedIds
45
+ .map((id, i) => (sources[i] === 'code-index' ? `${id} [code]` : id))
46
+ .join(', ');
47
+ }
48
+
49
+ /** Honest one-clause note when a wired code index was queried but injected nothing this turn. */
50
+ function codeSkipNote(entry: TurnInjectionEntry): string {
51
+ return entry.codeInjectionSkipped ? `, code skipped: ${entry.codeInjectionSkipped}` : '';
52
+ }
53
+
54
+ /** Render a single TurnInjectionRecord as one readable line. */
55
+ export function formatTurnInjectionEntry(entry: TurnInjectionEntry): string {
56
+ const backendTag = entry.embeddingBackend === 'fallback-lexical' ? ' [lexical fallback]' : '';
57
+ if (entry.injectedIds.length === 0) {
58
+ // Honest empty state: the engine ran this turn but injected nothing —
59
+ // distinct from "the engine never ran" (see buildTurnInjectionsText).
60
+ const reasonText = entry.reason === 'no records cleared relevance floor'
61
+ ? 'nothing injected this turn — nothing cleared the relevance floor'
62
+ : `nothing injected this turn — ${entry.reason ?? 'unknown reason'}`;
63
+ const codeConsidered = entry.codeCandidatesConsidered ? `, code considered ${entry.codeCandidatesConsidered}` : '';
64
+ return ` turn ${entry.turn}: ${reasonText}${backendTag} (considered ${entry.candidatesConsidered}${codeConsidered}, floor ${entry.relevanceFloor})${codeSkipNote(entry)}`;
65
+ }
66
+ const droppedStr = entry.droppedForBudget.length > 0
67
+ ? `, dropped for budget: ${entry.droppedForBudget.join(', ')}`
68
+ : '';
69
+ // The retrieval query is part of the record's honesty contract — without it
70
+ // an injected line can't be traced back to WHY those ids were retrieved
71
+ // (Wave-5 replay flagged the omission). Truncated to keep the line scannable.
72
+ const queryStr = entry.query ? ` for ${JSON.stringify(truncateQuery(entry.query))}` : '';
73
+ return (
74
+ ` turn ${entry.turn}: injected ${labelInjectedIds(entry)}${queryStr} ` +
75
+ `(~${fmtN(entry.tokenCost)}/${fmtN(entry.budgetTokens)} tok, floor ${entry.relevanceFloor})${droppedStr}${backendTag}${codeSkipNote(entry)}`
76
+ );
77
+ }
78
+
79
+ function truncateQuery(query: string): string {
80
+ const flat = query.replace(/\s+/g, ' ').trim();
81
+ return flat.length > 48 ? `${flat.slice(0, 47)}…` : flat;
82
+ }
83
+
84
+ /**
85
+ * Render the full per-turn injection history for one agent, most-recent-turn
86
+ * first, with an honest empty state when nothing has been recorded yet.
87
+ *
88
+ * An empty `entries` array is deliberately ambiguous about WHY it's empty
89
+ * (flag disabled, no turn with new input has run yet, or every turn's token
90
+ * budget had no headroom) — the SDK does not distinguish these cases in the
91
+ * ring itself, so this renders all three possibilities rather than guessing.
92
+ */
93
+ export function buildTurnInjectionsText(agentId: string, entries: readonly TurnInjectionEntry[]): string {
94
+ if (entries.length === 0) {
95
+ return (
96
+ `[recall] No per-turn injection records for agent ${agentId} yet. This means one of: ` +
97
+ 'passive knowledge injection is disabled, no turn with new input has run yet, or the ' +
98
+ 'token budget had no headroom on every turn so far — there is no record either way.'
99
+ );
100
+ }
101
+ const lines = [`[recall] Per-turn knowledge injections for agent ${agentId} (${entries.length}, most recent first):`];
102
+ for (const entry of [...entries].reverse()) {
103
+ lines.push(formatTurnInjectionEntry(entry));
104
+ }
105
+ return lines.join('\n');
106
+ }
107
+
108
+ /**
109
+ * Render the MAIN interactive session's per-turn injection ring
110
+ * (`Orchestrator.getTurnInjections()`, wo805), most-recent-turn first, with the
111
+ * same honest empty state as the per-agent path. The main-session counterpart
112
+ * to {@link buildTurnInjectionsText}: no agent id, main-session-appropriate
113
+ * wording, but the identical per-entry rendering (`formatTurnInjectionEntry`).
114
+ *
115
+ * As with the agent path, an empty `entries` array is deliberately ambiguous
116
+ * about WHY it's empty (flag disabled, no turn with new input has run yet, or
117
+ * every turn's token budget had no headroom) — the ring does not distinguish
118
+ * these, so this renders all three possibilities rather than guessing.
119
+ */
120
+ export function buildMainSessionTurnInjectionsText(entries: readonly TurnInjectionEntry[]): string {
121
+ if (entries.length === 0) {
122
+ return (
123
+ '[recall] No per-turn injection records for the main session yet. This means one of: ' +
124
+ 'passive knowledge injection is disabled, no turn with new input has run yet, or the ' +
125
+ 'token budget had no headroom on every turn so far — there is no record either way.'
126
+ );
127
+ }
128
+ const lines = [`[recall] Per-turn knowledge injections for the main session (${entries.length}, most recent first):`];
129
+ for (const entry of [...entries].reverse()) {
130
+ lines.push(formatTurnInjectionEntry(entry));
131
+ }
132
+ return lines.join('\n');
133
+ }