zelari-code 0.2.2 → 0.6.2

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 (126) hide show
  1. package/README.md +97 -2
  2. package/dist/agents/councilApi.js +7 -7
  3. package/dist/agents/councilApi.js.map +1 -1
  4. package/dist/agents/councilDirectives.js +3 -3
  5. package/dist/agents/promoteMember.js +2 -2
  6. package/dist/agents/roles.js +32 -32
  7. package/dist/agents/roles.js.map +1 -1
  8. package/dist/agents/skills/builtin/debugging.js +4 -4
  9. package/dist/agents/skills/builtin/debugging.js.map +1 -1
  10. package/dist/agents/skills/builtin/docs.js +13 -23
  11. package/dist/agents/skills/builtin/docs.js.map +1 -1
  12. package/dist/agents/skills/builtin/git-ops.js +3 -3
  13. package/dist/agents/skills/builtin/git-ops.js.map +1 -1
  14. package/dist/agents/skills/builtin/planning.js +4 -4
  15. package/dist/agents/skills/builtin/planning.js.map +1 -1
  16. package/dist/agents/skills/builtin/refactoring.js +3 -3
  17. package/dist/agents/skills/builtin/refactoring.js.map +1 -1
  18. package/dist/agents/skills/builtin/review.js +9 -9
  19. package/dist/agents/skills/builtin/review.js.map +1 -1
  20. package/dist/agents/skills/builtin/testing.js +3 -3
  21. package/dist/agents/skills/builtin/testing.js.map +1 -1
  22. package/dist/agents/tools.js +9 -2
  23. package/dist/agents/tools.js.map +1 -1
  24. package/dist/cli/app.js +94 -1861
  25. package/dist/cli/app.js.map +1 -1
  26. package/dist/cli/compaction.js.map +1 -1
  27. package/dist/cli/components/ChatStream.js +81 -14
  28. package/dist/cli/components/ChatStream.js.map +1 -1
  29. package/dist/cli/components/CollapsibleToolOutput.js +26 -3
  30. package/dist/cli/components/CollapsibleToolOutput.js.map +1 -1
  31. package/dist/cli/components/Header.js +20 -1
  32. package/dist/cli/components/Header.js.map +1 -1
  33. package/dist/cli/components/InputBar.js +34 -2
  34. package/dist/cli/components/InputBar.js.map +1 -1
  35. package/dist/cli/components/Sidebar.js +30 -10
  36. package/dist/cli/components/Sidebar.js.map +1 -1
  37. package/dist/cli/councilDispatcher.js +10 -2
  38. package/dist/cli/councilDispatcher.js.map +1 -1
  39. package/dist/cli/headless.js +137 -0
  40. package/dist/cli/headless.js.map +1 -0
  41. package/dist/cli/hooks/chatStats.js +28 -0
  42. package/dist/cli/hooks/chatStats.js.map +1 -0
  43. package/dist/cli/hooks/eventsToMessages.js +111 -0
  44. package/dist/cli/hooks/eventsToMessages.js.map +1 -0
  45. package/dist/cli/hooks/messageHelpers.js +125 -0
  46. package/dist/cli/hooks/messageHelpers.js.map +1 -0
  47. package/dist/cli/hooks/skillCompare.js +69 -0
  48. package/dist/cli/hooks/skillCompare.js.map +1 -0
  49. package/dist/cli/hooks/steer.js +28 -0
  50. package/dist/cli/hooks/steer.js.map +1 -0
  51. package/dist/cli/hooks/useBatchedMessages.js +67 -0
  52. package/dist/cli/hooks/useBatchedMessages.js.map +1 -0
  53. package/dist/cli/hooks/useChatTurn.js +363 -0
  54. package/dist/cli/hooks/useChatTurn.js.map +1 -0
  55. package/dist/cli/hooks/useSession.js +116 -0
  56. package/dist/cli/hooks/useSession.js.map +1 -0
  57. package/dist/cli/hooks/useSlashDispatch.js +303 -0
  58. package/dist/cli/hooks/useSlashDispatch.js.map +1 -0
  59. package/dist/cli/hooks/useTerminalSize.js +60 -0
  60. package/dist/cli/hooks/useTerminalSize.js.map +1 -0
  61. package/dist/cli/main.bundled.js +25212 -21722
  62. package/dist/cli/main.bundled.js.map +4 -4
  63. package/dist/cli/main.js +86 -2
  64. package/dist/cli/main.js.map +1 -1
  65. package/dist/cli/provider/openai-compatible.js +9 -2
  66. package/dist/cli/provider/openai-compatible.js.map +1 -1
  67. package/dist/cli/runHeadless.js +160 -0
  68. package/dist/cli/runHeadless.js.map +1 -0
  69. package/dist/cli/sessionManager.js +47 -1
  70. package/dist/cli/sessionManager.js.map +1 -1
  71. package/dist/cli/skillSuggest.js +1 -1
  72. package/dist/cli/slashCommands.js +72 -3
  73. package/dist/cli/slashCommands.js.map +1 -1
  74. package/dist/cli/slashHandlers/branch.js +57 -0
  75. package/dist/cli/slashHandlers/branch.js.map +1 -0
  76. package/dist/cli/slashHandlers/git.js +40 -0
  77. package/dist/cli/slashHandlers/git.js.map +1 -0
  78. package/dist/cli/slashHandlers/promoteMember.js +25 -0
  79. package/dist/cli/slashHandlers/promoteMember.js.map +1 -0
  80. package/dist/cli/slashHandlers/provider.js +199 -0
  81. package/dist/cli/slashHandlers/provider.js.map +1 -0
  82. package/dist/cli/slashHandlers/skills.js +82 -0
  83. package/dist/cli/slashHandlers/skills.js.map +1 -0
  84. package/dist/cli/slashHandlers/transcript.js +13 -0
  85. package/dist/cli/slashHandlers/transcript.js.map +1 -0
  86. package/dist/cli/slashHandlers/updater.js +40 -0
  87. package/dist/cli/slashHandlers/updater.js.map +1 -0
  88. package/dist/cli/slashHandlers/workspace.js +118 -0
  89. package/dist/cli/slashHandlers/workspace.js.map +1 -0
  90. package/dist/cli/toolRegistry.js +17 -8
  91. package/dist/cli/toolRegistry.js.map +1 -1
  92. package/dist/cli/utils/duration.js +34 -0
  93. package/dist/cli/utils/duration.js.map +1 -0
  94. package/dist/cli/wizard/firstRun.js +63 -0
  95. package/dist/cli/wizard/firstRun.js.map +1 -0
  96. package/dist/cli/wizard/index.js +150 -0
  97. package/dist/cli/wizard/index.js.map +1 -0
  98. package/dist/cli/wizard/runWizard.js +137 -0
  99. package/dist/cli/wizard/runWizard.js.map +1 -0
  100. package/dist/cli/wizard/useWizardState.js +151 -0
  101. package/dist/cli/wizard/useWizardState.js.map +1 -0
  102. package/dist/cli/workspace/agentsMd.js +314 -0
  103. package/dist/cli/workspace/agentsMd.js.map +1 -0
  104. package/dist/cli/workspace/index.js +24 -0
  105. package/dist/cli/workspace/index.js.map +1 -0
  106. package/dist/cli/workspace/paths.js +88 -0
  107. package/dist/cli/workspace/paths.js.map +1 -0
  108. package/dist/cli/workspace/postCouncilHook.js +38 -0
  109. package/dist/cli/workspace/postCouncilHook.js.map +1 -0
  110. package/dist/cli/workspace/storage.js +392 -0
  111. package/dist/cli/workspace/storage.js.map +1 -0
  112. package/dist/cli/workspace/stubs.js +528 -0
  113. package/dist/cli/workspace/stubs.js.map +1 -0
  114. package/dist/cli/workspace/toolRegistry.js +61 -0
  115. package/dist/cli/workspace/toolRegistry.js.map +1 -0
  116. package/dist/cli/workspace/types.js +10 -0
  117. package/dist/cli/workspace/types.js.map +1 -0
  118. package/dist/main/core/tools/builtin/_walk.js +129 -0
  119. package/dist/main/core/tools/builtin/_walk.js.map +1 -0
  120. package/dist/main/core/tools/builtin/diff.js +477 -0
  121. package/dist/main/core/tools/builtin/diff.js.map +1 -0
  122. package/dist/main/core/tools/builtin/listFiles.js +5 -43
  123. package/dist/main/core/tools/builtin/listFiles.js.map +1 -1
  124. package/dist/main/core/tools/builtin/search.js +120 -28
  125. package/dist/main/core/tools/builtin/search.js.map +1 -1
  126. package/package.json +10 -2
@@ -0,0 +1,137 @@
1
+ /**
2
+ * headless — non-interactive CLI mode for CI/CD and scripting.
3
+ *
4
+ * Bypasses Ink entirely. Reads `--task <prompt>`, runs the prompt
5
+ * through the same dispatch path the TUI uses, and serializes the
6
+ * resulting events as one JSON object per line (NDJSON) on stdout.
7
+ *
8
+ * Exit codes:
9
+ * 0 — task completed successfully (last `agent_end.reason === 'completed'`)
10
+ * 1 — user error (missing task, missing API key, invalid flag)
11
+ * 2 — runtime error (provider failure, council exception)
12
+ * 3 — task errored (the agent run itself emitted `agent_end.reason === 'error'`)
13
+ *
14
+ * @public
15
+ * @since 0.5.0
16
+ */
17
+ import { PROVIDERS, resolveApiKeyWithMeta } from './keyStore.js';
18
+ import { getActiveProvider, getModelForProvider } from './providerConfig.js';
19
+ import { openaiCompatibleProvider } from './provider/openai-compatible.js';
20
+ const HELP_TEXT = `zelari-code --headless --task <prompt> [--output json|plain] [--council] [--provider <id>] [--model <name>]
21
+
22
+ Non-interactive mode. Streams BrainEvents as NDJSON to stdout (one JSON
23
+ object per line) or as plain text (just the assistant message text).
24
+
25
+ Exit codes:
26
+ 0 completed
27
+ 1 user error (bad flags, missing API key, ...)
28
+ 2 runtime error (provider failure, council exception)
29
+ 3 agent run errored
30
+ `;
31
+ /**
32
+ * Parse argv for --headless options. Returns null options when
33
+ * --headless is not present (caller should fall through to TUI mode).
34
+ */
35
+ export function parseHeadlessFlags(argv) {
36
+ if (!argv.includes('--headless')) {
37
+ return { options: null };
38
+ }
39
+ let task;
40
+ let output = 'json';
41
+ let useCouncil = false;
42
+ let provider;
43
+ let model;
44
+ for (let i = 0; i < argv.length; i++) {
45
+ const arg = argv[i];
46
+ if (arg === '--headless')
47
+ continue;
48
+ if (arg === '--output') {
49
+ const next = argv[i + 1];
50
+ if (next === 'json' || next === 'plain') {
51
+ output = next;
52
+ i++;
53
+ }
54
+ else {
55
+ return {
56
+ options: null,
57
+ error: `--output requires 'json' or 'plain', got '${next ?? '(missing)'}'`,
58
+ };
59
+ }
60
+ }
61
+ else if (arg === '--task') {
62
+ task = argv[i + 1];
63
+ i++;
64
+ }
65
+ else if (arg === '--council') {
66
+ useCouncil = true;
67
+ }
68
+ else if (arg === '--provider') {
69
+ provider = argv[i + 1];
70
+ i++;
71
+ }
72
+ else if (arg === '--model') {
73
+ model = argv[i + 1];
74
+ i++;
75
+ }
76
+ }
77
+ if (!task || task.trim().length === 0) {
78
+ return { options: null, error: '--headless requires --task <prompt>' };
79
+ }
80
+ return {
81
+ options: { task, output, useCouncil, provider, model },
82
+ };
83
+ }
84
+ /**
85
+ * Print the headless help text to stdout (caller prints to stderr for errors).
86
+ */
87
+ export function printHeadlessHelp() {
88
+ // eslint-disable-next-line no-console
89
+ console.log(HELP_TEXT);
90
+ }
91
+ /**
92
+ * Resolve the API key for a provider; returns null with a reason
93
+ * when the key is missing. Used to fail fast with a clear error
94
+ * instead of failing mid-stream.
95
+ */
96
+ export async function resolveHeadlessKey(providerId) {
97
+ const spec = PROVIDERS.find((p) => p.id === providerId);
98
+ if (!spec) {
99
+ return { error: `unknown provider: '${providerId}'` };
100
+ }
101
+ const resolved = await resolveApiKeyWithMeta(providerId);
102
+ if (!resolved || !resolved.apiKey) {
103
+ return {
104
+ error: `no API key for provider '${providerId}'.\n` +
105
+ `Set the env var ${spec.envVar} or save a key via /login.`,
106
+ };
107
+ }
108
+ // baseUrl lives in providerConfig (customEndpoints) not on StoredKey.
109
+ // Imported lazily to avoid a circular dep at module load.
110
+ const { getCustomEndpoint } = await import('./providerConfig.js');
111
+ const customBase = getCustomEndpoint(providerId);
112
+ return {
113
+ apiKey: resolved.apiKey,
114
+ ...(customBase ? { baseUrl: customBase } : {}),
115
+ };
116
+ }
117
+ /**
118
+ * Determine the effective provider + model for a headless run.
119
+ * Prefers explicit --provider/--model flags, then the active
120
+ * provider from provider.json, then 'openai-compatible' as a
121
+ * last resort.
122
+ */
123
+ export function resolveHeadlessProvider(opts) {
124
+ const provider = opts.provider ?? getActiveProvider().id;
125
+ const model = opts.model ?? getModelForProvider(provider);
126
+ return { provider, model };
127
+ }
128
+ /**
129
+ * Emit one NDJSON line to stdout. Use process.stdout.write directly
130
+ * to avoid the console.log trailing newline (NDJSON convention is
131
+ * one JSON object per line, no extra whitespace).
132
+ */
133
+ export function emitEvent(event) {
134
+ process.stdout.write(JSON.stringify(event) + '\n');
135
+ }
136
+ export { openaiCompatibleProvider };
137
+ //# sourceMappingURL=headless.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headless.js","sourceRoot":"","sources":["../../src/cli/headless.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { calculateCost } from '../modelPricing.js';
2
+ /**
3
+ * Compute the next session-stats snapshot after a chat turn (Task G.4.5).
4
+ * Pure helper extracted from app.tsx so the "real usage vs ~4-char fallback"
5
+ * branch is testable without React/Ink.
6
+ *
7
+ * Behavior:
8
+ * - When `realUsage` is present (provider honored `stream_options.include_usage`),
9
+ * use those numbers exactly.
10
+ * - When `realUsage` is null, fall back to the v3-B approximation:
11
+ * `Math.ceil(text.length / 4)` for both prompt and completion tokens.
12
+ *
13
+ * Cost is computed via `calculateCost(model, prompt, completion)` from
14
+ * `modelPricing.ts`. The result is the new stats object; the caller is
15
+ * responsible for merging it into state via `setSessionStats(prev => ...)`.
16
+ */
17
+ export function computeSessionStatsDelta(realUsage, userText, assistantContent, model, prev) {
18
+ const promptTokens = realUsage ? realUsage.promptTokens : Math.ceil(userText.length / 4);
19
+ const completionTokens = realUsage
20
+ ? realUsage.completionTokens
21
+ : Math.ceil(assistantContent.length / 4);
22
+ const turnCost = calculateCost(model, promptTokens, completionTokens);
23
+ return {
24
+ totalTokens: prev.totalTokens + promptTokens + completionTokens,
25
+ totalCostUsd: prev.totalCostUsd + turnCost,
26
+ };
27
+ }
28
+ //# sourceMappingURL=chatStats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatStats.js","sourceRoot":"","sources":["../../../src/cli/hooks/chatStats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CACtC,SAAyF,EACzF,QAAgB,EAChB,gBAAwB,EACxB,KAAa,EACb,IAAmD;IAEnD,MAAM,YAAY,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzF,MAAM,gBAAgB,GAAG,SAAS;QAChC,CAAC,CAAC,SAAS,CAAC,gBAAgB;QAC5B,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;IACtE,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,WAAW,GAAG,YAAY,GAAG,gBAAgB;QAC/D,YAAY,EAAE,IAAI,CAAC,YAAY,GAAG,QAAQ;KAC3C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,111 @@
1
+ import { newSessionId } from '../sessionManager.js';
2
+ /**
3
+ * eventsToMessages — pure helper: replay a BrainEvent log into ChatMessage[].
4
+ *
5
+ * Streaming message_delta events are coalesced into a single growing
6
+ * assistant message (keyed by assistantId) so the chat transcript doesn't
7
+ * show N messages per LLM response.
8
+ *
9
+ * Extracted from app.tsx (Task v0.4.2 audit split) so it can be unit-tested
10
+ * without booting React/Ink.
11
+ */
12
+ export function eventsToMessages(events) {
13
+ const out = [];
14
+ let assistantBuffer = '';
15
+ let assistantId = '';
16
+ for (const e of events) {
17
+ if (e.type === 'message_delta') {
18
+ if (assistantId === '')
19
+ assistantId = `resumed-${newSessionId()}`;
20
+ assistantBuffer += e.delta;
21
+ const last = out[out.length - 1];
22
+ if (last && last.role === 'assistant' && last.id === assistantId) {
23
+ out[out.length - 1] = { ...last, content: assistantBuffer };
24
+ }
25
+ else {
26
+ out.push({ id: assistantId, role: 'assistant', content: assistantBuffer, ts: e.ts });
27
+ }
28
+ }
29
+ else if (e.type === 'message_end') {
30
+ // Message boundary: the next streamed message gets its own bubble,
31
+ // matching the live UI (which finalizes on message_end).
32
+ assistantBuffer = '';
33
+ assistantId = '';
34
+ }
35
+ else if (e.type === 'tool_execution_start') {
36
+ // v0.4.3 audit fix: the v3-W event schema renamed `tool_call` →
37
+ // `tool_execution_start` and added the `args` field. The old branches
38
+ // here silently dropped every tool invocation during session resume.
39
+ // v0.6.2: restored as a `role: 'tool'` message so the resumed
40
+ // transcript renders CollapsibleToolOutput, same as the live UI.
41
+ assistantBuffer = '';
42
+ assistantId = '';
43
+ const argsPreview = JSON.stringify(e.args)?.slice(0, 120) ?? '';
44
+ out.push({
45
+ id: crypto.randomUUID(),
46
+ role: 'tool',
47
+ content: argsPreview,
48
+ ts: e.ts,
49
+ toolName: e.toolName,
50
+ toolCallId: e.toolCallId,
51
+ });
52
+ }
53
+ else if (e.type === 'tool_execution_end') {
54
+ // v0.6.2: update the matching start message in place (the event does
55
+ // NOT carry toolName — the old code rendered "[tool_result] undefined").
56
+ const end = e;
57
+ let matched = false;
58
+ for (let i = out.length - 1; i >= 0; i--) {
59
+ const m = out[i];
60
+ if (m.role === 'tool' && m.toolCallId === end.toolCallId && m.toolDurationMs === undefined) {
61
+ // v0.6.2 audit LOW-5: append `…` on truncation to match the
62
+ // live path (messageHelpers.appendToolEnd behavior).
63
+ const raw = typeof end.result === 'string' ? end.result : '';
64
+ const truncated = raw.length > 600 ? `${raw.slice(0, 600)}…` : raw;
65
+ out[i] = {
66
+ ...m,
67
+ toolOk: !end.isError,
68
+ toolDurationMs: end.durationMs,
69
+ ...(raw.length > 0 ? { toolResult: truncated } : {}),
70
+ };
71
+ matched = true;
72
+ break;
73
+ }
74
+ }
75
+ if (!matched) {
76
+ out.push({
77
+ id: crypto.randomUUID(),
78
+ role: 'system',
79
+ content: `[tool_result] ${end.toolCallId} → ${end.isError ? 'error' : 'ok'}`,
80
+ ts: e.ts,
81
+ });
82
+ }
83
+ }
84
+ else if (e.type === 'agent_start') {
85
+ out.push({
86
+ id: crypto.randomUUID(),
87
+ role: 'system',
88
+ content: `[agent_start] model=${e.model} provider=${e.provider}`,
89
+ ts: e.ts,
90
+ });
91
+ }
92
+ else if (e.type === 'agent_end') {
93
+ out.push({
94
+ id: crypto.randomUUID(),
95
+ role: 'system',
96
+ content: `[agent_end] reason=${e.reason} duration=${e.durationMs}ms`,
97
+ ts: e.ts,
98
+ });
99
+ }
100
+ else if (e.type === 'error') {
101
+ out.push({
102
+ id: crypto.randomUUID(),
103
+ role: 'system',
104
+ content: `[error] ${e.message}`,
105
+ ts: e.ts,
106
+ });
107
+ }
108
+ }
109
+ return out;
110
+ }
111
+ //# sourceMappingURL=eventsToMessages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventsToMessages.js","sourceRoot":"","sources":["../../../src/cli/hooks/eventsToMessages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Helpers for building ChatMessage instances in the most common shapes.
3
+ *
4
+ * Extracted from app.tsx (Task v0.4.2 audit split). The previous code
5
+ * constructed these inline 50+ times with the same shape:
6
+ * setMessages((prev) => [
7
+ * ...prev,
8
+ * { id: crypto.randomUUID(), role: 'system', content: '...', ts: Date.now() },
9
+ * ]);
10
+ *
11
+ * The `appendX(setMessages, content)` wrappers are kept functional so they
12
+ * play nicely with the React state setter signature.
13
+ */
14
+ /** Append a system message at the end of the chat. */
15
+ export function appendSystem(setMessages, content, ts = Date.now()) {
16
+ setMessages((prev) => [
17
+ ...prev,
18
+ { id: crypto.randomUUID(), role: 'system', content, ts },
19
+ ]);
20
+ }
21
+ /** Append a user message (the user's submitted prompt). */
22
+ export function appendUser(setMessages, content, ts = Date.now()) {
23
+ setMessages((prev) => [
24
+ ...prev,
25
+ { id: crypto.randomUUID(), role: 'user', content, ts },
26
+ ]);
27
+ }
28
+ /**
29
+ * Append or extend the streaming assistant message. If the last message is
30
+ * already a streaming assistant message, append `delta` to its content;
31
+ * otherwise create a new one with a stable streaming id.
32
+ *
33
+ * When `memberName` is set (council-sourced messages), it's stamped on
34
+ * the message so the visible-reasoning UI can render `🜂 Caronte: …`
35
+ * headers above the streamed text.
36
+ */
37
+ export function appendOrExtendStreamingAssistant(setMessages, fullContent, ts, memberContext) {
38
+ setMessages((prev) => {
39
+ const last = prev[prev.length - 1];
40
+ if (last && last.role === 'assistant' && last.id.startsWith('streaming-')) {
41
+ return [...prev.slice(0, -1), { ...last, content: fullContent }];
42
+ }
43
+ return [
44
+ ...prev,
45
+ {
46
+ id: `streaming-${crypto.randomUUID()}`,
47
+ role: 'assistant',
48
+ content: fullContent,
49
+ ts,
50
+ ...(memberContext?.memberId ? { memberId: memberContext.memberId } : {}),
51
+ ...(memberContext?.memberName ? { memberName: memberContext.memberName } : {}),
52
+ },
53
+ ];
54
+ });
55
+ }
56
+ /** Max chars of tool result kept for the expandable body. */
57
+ export const TOOL_RESULT_PREVIEW_CHARS = 600;
58
+ /** Max chars of the JSON args preview shown on the tool summary line. */
59
+ export const TOOL_ARGS_PREVIEW_CHARS = 120;
60
+ /**
61
+ * Append a tool invocation as a `role: 'tool'` message so ChatStream renders
62
+ * it via CollapsibleToolOutput (one status line per invocation, updated in
63
+ * place by {@link updateToolMessageEnd}) instead of loose system lines.
64
+ */
65
+ export function appendToolStart(setMessages, toolName, toolCallId, args, ts) {
66
+ const argsPreview = JSON.stringify(args)?.slice(0, TOOL_ARGS_PREVIEW_CHARS) ?? '';
67
+ setMessages((prev) => [
68
+ ...prev,
69
+ {
70
+ id: crypto.randomUUID(),
71
+ role: 'tool',
72
+ content: argsPreview,
73
+ ts,
74
+ toolName,
75
+ toolCallId,
76
+ toolOk: undefined,
77
+ toolDurationMs: undefined,
78
+ },
79
+ ]);
80
+ }
81
+ /**
82
+ * Update the matching `tool_execution_start` message with its end status.
83
+ * The summary (args preview) is kept; the result is stored separately as the
84
+ * expandable body. Failed invocations auto-expand in ChatStream.
85
+ */
86
+ export function updateToolMessageEnd(setMessages, toolCallId, isError, durationMs, result) {
87
+ setMessages((prev) => {
88
+ for (let i = prev.length - 1; i >= 0; i--) {
89
+ const m = prev[i];
90
+ if (m && m.role === 'tool' && m.toolCallId === toolCallId && m.toolDurationMs === undefined) {
91
+ const updated = [...prev];
92
+ updated[i] = {
93
+ ...m,
94
+ toolOk: !isError,
95
+ toolDurationMs: durationMs,
96
+ ...(result !== undefined
97
+ ? {
98
+ toolResult: result.length > TOOL_RESULT_PREVIEW_CHARS
99
+ ? `${result.slice(0, TOOL_RESULT_PREVIEW_CHARS)}…`
100
+ : result,
101
+ }
102
+ : {}),
103
+ };
104
+ return updated;
105
+ }
106
+ }
107
+ return prev;
108
+ });
109
+ }
110
+ /**
111
+ * Seal the trailing streaming assistant message (if any) by dropping its
112
+ * `streaming-` id prefix. Called on `message_end` so the NEXT assistant
113
+ * message in the same turn (e.g. after a tool call) starts a fresh bubble
114
+ * instead of being merged into — or duplicated over — the previous one.
115
+ */
116
+ export function finalizeStreamingAssistant(setMessages) {
117
+ setMessages((prev) => {
118
+ const last = prev[prev.length - 1];
119
+ if (last && last.role === 'assistant' && last.id.startsWith('streaming-')) {
120
+ return [...prev.slice(0, -1), { ...last, id: last.id.slice('streaming-'.length) }];
121
+ }
122
+ return prev;
123
+ });
124
+ }
125
+ //# sourceMappingURL=messageHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messageHelpers.js","sourceRoot":"","sources":["../../../src/cli/hooks/messageHelpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { readSkillHistory, getSkillStats } from '../skillHistory.js';
2
+ /**
3
+ * Format a side-by-side comparison of two skills' history stats (Task H.3).
4
+ * Pure helper extracted from app.tsx dispatch so it's testable without
5
+ * React/Ink render.
6
+ *
7
+ * Returns a multi-line string with:
8
+ * - Two lines summarizing each skill (id, count, success, avg duration, total tokens)
9
+ * - A "winner" line if there's a clear winner (higher successRate wins;
10
+ * ties broken by lower avgDurationMs; ties beyond that → no winner)
11
+ *
12
+ * When a skill ID has no recorded invocations, the line shows
13
+ * "no invocations yet" so the user can still see the comparison.
14
+ */
15
+ export function formatSkillCompare(id1, id2, records) {
16
+ const stats1 = getSkillStats(records, id1);
17
+ const stats2 = getSkillStats(records, id2);
18
+ const lines = ['[skill-compare]'];
19
+ lines.push(formatSkillCompareLine(id1, stats1));
20
+ lines.push(formatSkillCompareLine(id2, stats2));
21
+ if (stats1.count > 0 && stats2.count > 0) {
22
+ const winner = pickCompareWinner(stats1, stats2);
23
+ if (winner === null) {
24
+ lines.push('Winner: tie (same success rate + same avg duration)');
25
+ }
26
+ else if (winner === 'a') {
27
+ lines.push(`Winner: ${id1} (better success rate${stats1.successRate === stats2.successRate ? ' — tied, lower avg duration' : ''})`);
28
+ }
29
+ else {
30
+ lines.push(`Winner: ${id2} (better success rate${stats1.successRate === stats2.successRate ? ' — tied, lower avg duration' : ''})`);
31
+ }
32
+ }
33
+ else {
34
+ lines.push('Winner: (insufficient data — both skills need ≥1 invocation to compare)');
35
+ }
36
+ return lines.join('\n');
37
+ }
38
+ /** Format a single skill's stats line for the compare output. */
39
+ export function formatSkillCompareLine(id, stats) {
40
+ if (stats.count === 0) {
41
+ return ` ${id} — no invocations recorded yet`;
42
+ }
43
+ return ` ${id} — ${stats.count} invocations, ${(stats.successRate * 100).toFixed(1)}% success, avg ${stats.avgDurationMs.toFixed(0)}ms, ${stats.totalTokens} tokens total`;
44
+ }
45
+ /**
46
+ * Pick the winner between two SkillStats. Returns 'a' / 'b' / null on tie.
47
+ * Order: successRate first (higher wins), then avgDurationMs (lower wins).
48
+ * On perfect tie (same successRate AND same avgDurationMs), returns null.
49
+ */
50
+ export function pickCompareWinner(a, b) {
51
+ if (a.successRate > b.successRate)
52
+ return 'a';
53
+ if (b.successRate > a.successRate)
54
+ return 'b';
55
+ if (a.avgDurationMs < b.avgDurationMs)
56
+ return 'a';
57
+ if (b.avgDurationMs < a.avgDurationMs)
58
+ return 'b';
59
+ return null;
60
+ }
61
+ /**
62
+ * Convenience wrapper: read history from disk + format compare. Caller
63
+ * passes the file path so the test can inject a temp file.
64
+ */
65
+ export async function compareSkillsFromFile(id1, id2, historyFile) {
66
+ const records = await readSkillHistory(historyFile);
67
+ return formatSkillCompare(id1, id2, records);
68
+ }
69
+ //# sourceMappingURL=skillCompare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skillCompare.js","sourceRoot":"","sources":["../../../src/cli/hooks/skillCompare.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAA4C,MAAM,oBAAoB,CAAC;AAE/G;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAW,EACX,GAAW,EACX,OAA6B;IAE7B,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAa,CAAC,iBAAiB,CAAC,CAAC;IAC5C,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;QACpE,CAAC;aAAM,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,wBAAwB,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACtI,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,wBAAwB,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACtI,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,sBAAsB,CAAC,EAAU,EAAE,KAAiB;IAClE,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,EAAE,gCAAgC,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,EAAE,MAAM,KAAK,CAAC,KAAK,iBAAiB,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,WAAW,eAAe,CAAC;AAC9K,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,CAAa,EACb,CAAa;IAEb,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;QAAE,OAAO,GAAG,CAAC;IAC9C,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;QAAE,OAAO,GAAG,CAAC;IAC9C,IAAI,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa;QAAE,OAAO,GAAG,CAAC;IAClD,IAAI,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,aAAa;QAAE,OAAO,GAAG,CAAC;IAClD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,GAAW,EACX,GAAW,EACX,WAAmB;IAEnB,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACpD,OAAO,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * applySteerInterrupt — pure routing for the /steer --interrupt flow (Task C.3.2).
3
+ *
4
+ * Extracted from app.tsx so it can be unit-tested without React/Ink.
5
+ * Side effects are injected via the options:
6
+ * - `harness`: the currently-running AgentHarness, or null if no run is active
7
+ * - `appendMessage(content)`: append a system message to the chat
8
+ * - `setQueueCount(n)`: update the sidebar queue counter
9
+ * - `dispatchPrompt(text)`: dispatch a fresh prompt as if the user hit Enter
10
+ *
11
+ * Semantics:
12
+ * - With active harness: enqueue(text) + cancel() → user does NOT need to press
13
+ * Enter again (queue drain re-enters provider stream with the queued prompt).
14
+ * - Without active harness: fallback to dispatchPrompt(text) as a fresh prompt.
15
+ */
16
+ export async function applySteerInterrupt(options) {
17
+ const { text, harness, appendMessage, setQueueCount, dispatchPrompt } = options;
18
+ if (!harness) {
19
+ appendMessage('[steer --interrupt] no active run — dispatching as fresh prompt.');
20
+ await dispatchPrompt(text);
21
+ return;
22
+ }
23
+ harness.enqueue(text);
24
+ harness.cancel();
25
+ setQueueCount(harness.queueLength);
26
+ appendMessage(`[steer --interrupt] cancelled current run + enqueued: "${text}" (queue: ${harness.queueLength})`);
27
+ }
28
+ //# sourceMappingURL=steer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steer.js","sourceRoot":"","sources":["../../../src/cli/hooks/steer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAMzC;IACC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;IAChF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,aAAa,CAAC,kEAAkE,CAAC,CAAC;QAClF,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,CAAC,MAAM,EAAE,CAAC;IACjB,aAAa,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACnC,aAAa,CAAC,0DAA0D,IAAI,aAAa,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC;AACnH,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ export function useBatchedMessages(messages, setMessages, cadenceMs = 16) {
3
+ // Composed pending updater, or `null` when nothing is buffered.
4
+ const pendingRef = useRef(null);
5
+ // Active throttle-window timer handle, or `null` when not throttling.
6
+ const timerRef = useRef(null);
7
+ /**
8
+ * Apply the current pending updater to state, ignoring/clearing the timer.
9
+ * Used as the synchronous public drain (stream end) and internally.
10
+ */
11
+ const flush = useCallback(() => {
12
+ if (timerRef.current !== null) {
13
+ clearTimeout(timerRef.current);
14
+ timerRef.current = null;
15
+ }
16
+ const pending = pendingRef.current;
17
+ pendingRef.current = null;
18
+ if (pending !== null) {
19
+ setMessages(pending);
20
+ }
21
+ }, [setMessages]);
22
+ const commit = useCallback((update) => {
23
+ // Compose this update onto any pending one, preserving arrival order.
24
+ const head = pendingRef.current;
25
+ pendingRef.current =
26
+ head === null
27
+ ? update
28
+ : (state) => {
29
+ const intermediate = typeof head === 'function' ? head(state) : head;
30
+ return typeof update === 'function' ? update(intermediate) : update;
31
+ };
32
+ if (timerRef.current === null) {
33
+ // Leading edge: not currently throttling. Flush immediately so the
34
+ // first token renders with no latency, then open a throttle window.
35
+ const leading = pendingRef.current;
36
+ pendingRef.current = null;
37
+ if (leading !== null) {
38
+ setMessages(leading);
39
+ }
40
+ timerRef.current = setTimeout(() => {
41
+ timerRef.current = null;
42
+ // Trailing edge: flush anything that accumulated during the window
43
+ // as a single render. Do NOT re-arm — the next commit starts a new
44
+ // leading edge.
45
+ const tail = pendingRef.current;
46
+ pendingRef.current = null;
47
+ if (tail !== null) {
48
+ setMessages(tail);
49
+ }
50
+ }, cadenceMs);
51
+ }
52
+ // else: inside a throttle window — the trailing handler above will
53
+ // catch the buffered update when the window closes.
54
+ }, [cadenceMs, setMessages]);
55
+ // On unmount: cancel any pending timer. Do NOT flush — teardown means the
56
+ // display is going away, and persisted state lives in the JSONL writer.
57
+ useEffect(() => {
58
+ return () => {
59
+ if (timerRef.current !== null) {
60
+ clearTimeout(timerRef.current);
61
+ timerRef.current = null;
62
+ }
63
+ };
64
+ }, []);
65
+ return { messages, commit, flush, setMessages };
66
+ }
67
+ //# sourceMappingURL=useBatchedMessages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBatchedMessages.js","sourceRoot":"","sources":["../../../src/cli/hooks/useBatchedMessages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAkDvD,MAAM,UAAU,kBAAkB,CAChC,QAAuB,EACvB,WAAgE,EAChE,YAAoB,EAAE;IAEtB,gEAAgE;IAChE,MAAM,UAAU,GAAG,MAAM,CAA6C,IAAI,CAAC,CAAC;IAC5E,sEAAsE;IACtE,MAAM,QAAQ,GAAG,MAAM,CAAuC,IAAI,CAAC,CAAC;IAEpE;;;OAGG;IACH,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC9B,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAC/B,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,CAAC;QACD,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;QACnC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,WAAW,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,MAAM,MAAM,GAAG,WAAW,CACxB,CAAC,MAA2C,EAAE,EAAE;QAC9C,sEAAsE;QACtE,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC;QAChC,UAAU,CAAC,OAAO;YAChB,IAAI,KAAK,IAAI;gBACX,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,CAAC,KAAoB,EAAE,EAAE;oBACvB,MAAM,YAAY,GAAG,OAAO,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;oBACrE,OAAO,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBACtE,CAAC,CAAC;QAER,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC9B,mEAAmE;YACnE,oEAAoE;YACpE,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;YACnC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;YAC1B,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrB,WAAW,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;YACD,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;gBACjC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;gBACxB,mEAAmE;gBACnE,mEAAmE;gBACnE,gBAAgB;gBAChB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC;gBAChC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;gBAC1B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAClB,WAAW,CAAC,IAAI,CAAC,CAAC;gBACpB,CAAC;YACH,CAAC,EAAE,SAAS,CAAC,CAAC;QAChB,CAAC;QACD,mEAAmE;QACnE,oDAAoD;IACtD,CAAC,EACD,CAAC,SAAS,EAAE,WAAW,CAAC,CACzB,CAAC;IAEF,0EAA0E;IAC1E,wEAAwE;IACxE,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC9B,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAC/B,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AAClD,CAAC"}