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.
- package/README.md +97 -2
- package/dist/agents/councilApi.js +7 -7
- package/dist/agents/councilApi.js.map +1 -1
- package/dist/agents/councilDirectives.js +3 -3
- package/dist/agents/promoteMember.js +2 -2
- package/dist/agents/roles.js +32 -32
- package/dist/agents/roles.js.map +1 -1
- package/dist/agents/skills/builtin/debugging.js +4 -4
- package/dist/agents/skills/builtin/debugging.js.map +1 -1
- package/dist/agents/skills/builtin/docs.js +13 -23
- package/dist/agents/skills/builtin/docs.js.map +1 -1
- package/dist/agents/skills/builtin/git-ops.js +3 -3
- package/dist/agents/skills/builtin/git-ops.js.map +1 -1
- package/dist/agents/skills/builtin/planning.js +4 -4
- package/dist/agents/skills/builtin/planning.js.map +1 -1
- package/dist/agents/skills/builtin/refactoring.js +3 -3
- package/dist/agents/skills/builtin/refactoring.js.map +1 -1
- package/dist/agents/skills/builtin/review.js +9 -9
- package/dist/agents/skills/builtin/review.js.map +1 -1
- package/dist/agents/skills/builtin/testing.js +3 -3
- package/dist/agents/skills/builtin/testing.js.map +1 -1
- package/dist/agents/tools.js +9 -2
- package/dist/agents/tools.js.map +1 -1
- package/dist/cli/app.js +94 -1861
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/compaction.js.map +1 -1
- package/dist/cli/components/ChatStream.js +81 -14
- package/dist/cli/components/ChatStream.js.map +1 -1
- package/dist/cli/components/CollapsibleToolOutput.js +26 -3
- package/dist/cli/components/CollapsibleToolOutput.js.map +1 -1
- package/dist/cli/components/Header.js +20 -1
- package/dist/cli/components/Header.js.map +1 -1
- package/dist/cli/components/InputBar.js +34 -2
- package/dist/cli/components/InputBar.js.map +1 -1
- package/dist/cli/components/Sidebar.js +30 -10
- package/dist/cli/components/Sidebar.js.map +1 -1
- package/dist/cli/councilDispatcher.js +10 -2
- package/dist/cli/councilDispatcher.js.map +1 -1
- package/dist/cli/headless.js +137 -0
- package/dist/cli/headless.js.map +1 -0
- package/dist/cli/hooks/chatStats.js +28 -0
- package/dist/cli/hooks/chatStats.js.map +1 -0
- package/dist/cli/hooks/eventsToMessages.js +111 -0
- package/dist/cli/hooks/eventsToMessages.js.map +1 -0
- package/dist/cli/hooks/messageHelpers.js +125 -0
- package/dist/cli/hooks/messageHelpers.js.map +1 -0
- package/dist/cli/hooks/skillCompare.js +69 -0
- package/dist/cli/hooks/skillCompare.js.map +1 -0
- package/dist/cli/hooks/steer.js +28 -0
- package/dist/cli/hooks/steer.js.map +1 -0
- package/dist/cli/hooks/useBatchedMessages.js +67 -0
- package/dist/cli/hooks/useBatchedMessages.js.map +1 -0
- package/dist/cli/hooks/useChatTurn.js +363 -0
- package/dist/cli/hooks/useChatTurn.js.map +1 -0
- package/dist/cli/hooks/useSession.js +116 -0
- package/dist/cli/hooks/useSession.js.map +1 -0
- package/dist/cli/hooks/useSlashDispatch.js +303 -0
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -0
- package/dist/cli/hooks/useTerminalSize.js +60 -0
- package/dist/cli/hooks/useTerminalSize.js.map +1 -0
- package/dist/cli/main.bundled.js +25212 -21722
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +86 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/provider/openai-compatible.js +9 -2
- package/dist/cli/provider/openai-compatible.js.map +1 -1
- package/dist/cli/runHeadless.js +160 -0
- package/dist/cli/runHeadless.js.map +1 -0
- package/dist/cli/sessionManager.js +47 -1
- package/dist/cli/sessionManager.js.map +1 -1
- package/dist/cli/skillSuggest.js +1 -1
- package/dist/cli/slashCommands.js +72 -3
- package/dist/cli/slashCommands.js.map +1 -1
- package/dist/cli/slashHandlers/branch.js +57 -0
- package/dist/cli/slashHandlers/branch.js.map +1 -0
- package/dist/cli/slashHandlers/git.js +40 -0
- package/dist/cli/slashHandlers/git.js.map +1 -0
- package/dist/cli/slashHandlers/promoteMember.js +25 -0
- package/dist/cli/slashHandlers/promoteMember.js.map +1 -0
- package/dist/cli/slashHandlers/provider.js +199 -0
- package/dist/cli/slashHandlers/provider.js.map +1 -0
- package/dist/cli/slashHandlers/skills.js +82 -0
- package/dist/cli/slashHandlers/skills.js.map +1 -0
- package/dist/cli/slashHandlers/transcript.js +13 -0
- package/dist/cli/slashHandlers/transcript.js.map +1 -0
- package/dist/cli/slashHandlers/updater.js +40 -0
- package/dist/cli/slashHandlers/updater.js.map +1 -0
- package/dist/cli/slashHandlers/workspace.js +118 -0
- package/dist/cli/slashHandlers/workspace.js.map +1 -0
- package/dist/cli/toolRegistry.js +17 -8
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/utils/duration.js +34 -0
- package/dist/cli/utils/duration.js.map +1 -0
- package/dist/cli/wizard/firstRun.js +63 -0
- package/dist/cli/wizard/firstRun.js.map +1 -0
- package/dist/cli/wizard/index.js +150 -0
- package/dist/cli/wizard/index.js.map +1 -0
- package/dist/cli/wizard/runWizard.js +137 -0
- package/dist/cli/wizard/runWizard.js.map +1 -0
- package/dist/cli/wizard/useWizardState.js +151 -0
- package/dist/cli/wizard/useWizardState.js.map +1 -0
- package/dist/cli/workspace/agentsMd.js +314 -0
- package/dist/cli/workspace/agentsMd.js.map +1 -0
- package/dist/cli/workspace/index.js +24 -0
- package/dist/cli/workspace/index.js.map +1 -0
- package/dist/cli/workspace/paths.js +88 -0
- package/dist/cli/workspace/paths.js.map +1 -0
- package/dist/cli/workspace/postCouncilHook.js +38 -0
- package/dist/cli/workspace/postCouncilHook.js.map +1 -0
- package/dist/cli/workspace/storage.js +392 -0
- package/dist/cli/workspace/storage.js.map +1 -0
- package/dist/cli/workspace/stubs.js +528 -0
- package/dist/cli/workspace/stubs.js.map +1 -0
- package/dist/cli/workspace/toolRegistry.js +61 -0
- package/dist/cli/workspace/toolRegistry.js.map +1 -0
- package/dist/cli/workspace/types.js +10 -0
- package/dist/cli/workspace/types.js.map +1 -0
- package/dist/main/core/tools/builtin/_walk.js +129 -0
- package/dist/main/core/tools/builtin/_walk.js.map +1 -0
- package/dist/main/core/tools/builtin/diff.js +477 -0
- package/dist/main/core/tools/builtin/diff.js.map +1 -0
- package/dist/main/core/tools/builtin/listFiles.js +5 -43
- package/dist/main/core/tools/builtin/listFiles.js.map +1 -1
- package/dist/main/core/tools/builtin/search.js +120 -28
- package/dist/main/core/tools/builtin/search.js.map +1 -1
- 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"}
|