zelari-code 1.45.0 → 1.47.1
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/dist/cli/app.js +4 -1
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/ast/engine.js +86 -23
- package/dist/cli/ast/engine.js.map +1 -1
- package/dist/cli/ast/engine.test.js +101 -0
- package/dist/cli/ast/engine.test.js.map +1 -0
- package/dist/cli/ast/tools.js +77 -15
- package/dist/cli/ast/tools.js.map +1 -1
- package/dist/cli/ast/tools.test.js +124 -0
- package/dist/cli/ast/tools.test.js.map +1 -0
- package/dist/cli/budget/tokenBudget.js +14 -12
- package/dist/cli/budget/tokenBudget.js.map +1 -1
- package/dist/cli/hooks/conversationContext.js +7 -4
- package/dist/cli/hooks/conversationContext.js.map +1 -1
- package/dist/cli/hooks/observationStore.js +219 -0
- package/dist/cli/hooks/observationStore.js.map +1 -0
- package/dist/cli/hooks/observationStore.test.js +135 -0
- package/dist/cli/hooks/observationStore.test.js.map +1 -0
- package/dist/cli/hooks/sessionSurface.js +171 -0
- package/dist/cli/hooks/sessionSurface.js.map +1 -0
- package/dist/cli/hooks/sessionSurface.test.js +112 -0
- package/dist/cli/hooks/sessionSurface.test.js.map +1 -0
- package/dist/cli/hooks/useChatTurn.js +27 -0
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useSession.js +5 -3
- package/dist/cli/hooks/useSession.js.map +1 -1
- package/dist/cli/lsp/manager.js +10 -0
- package/dist/cli/lsp/manager.js.map +1 -1
- package/dist/cli/lsp/tools.js +37 -2
- package/dist/cli/lsp/tools.js.map +1 -1
- package/dist/cli/lsp/tools.test.js +90 -0
- package/dist/cli/lsp/tools.test.js.map +1 -0
- package/dist/cli/main.bundled.js +2339 -952
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/metrics.js +4 -0
- package/dist/cli/metrics.js.map +1 -1
- package/dist/cli/provider/capabilities.js +53 -0
- package/dist/cli/provider/capabilities.js.map +1 -0
- package/dist/cli/provider/capabilities.test.js +46 -0
- package/dist/cli/provider/capabilities.test.js.map +1 -0
- package/dist/cli/provider/openai-compatible.js +2 -1
- package/dist/cli/provider/openai-compatible.js.map +1 -1
- package/dist/cli/toolRegistry.js +50 -5
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/inspectCommand.js +346 -0
- package/dist/cli/tools/inspectCommand.js.map +1 -0
- package/dist/cli/tools/inspectCommand.test.js +311 -0
- package/dist/cli/tools/inspectCommand.test.js.map +1 -0
- package/dist/cli/tools/inspectTypecheckSafety.js +104 -0
- package/dist/cli/tools/inspectTypecheckSafety.js.map +1 -0
- package/dist/cli/tools/observeBatch.js +254 -0
- package/dist/cli/tools/observeBatch.js.map +1 -0
- package/dist/cli/tools/observeBatch.test.js +329 -0
- package/dist/cli/tools/observeBatch.test.js.map +1 -0
- package/dist/cli/tools/retrieveObservation.js +82 -0
- package/dist/cli/tools/retrieveObservation.js.map +1 -0
- package/dist/cli/tools/taskTool.js +4 -0
- package/dist/cli/tools/taskTool.js.map +1 -1
- package/dist/cli/utils/contextGrowthSummary.js +55 -0
- package/dist/cli/utils/contextGrowthSummary.js.map +1 -0
- package/dist/cli/utils/contextGrowthSummary.test.js +74 -0
- package/dist/cli/utils/contextGrowthSummary.test.js.map +1 -0
- package/dist/cli/utils/doctor.js +26 -0
- package/dist/cli/utils/doctor.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { envNumber } from '../utils/envNumber.js';
|
|
2
|
+
/** Marker prefix — also used by retrieve_observation to refuse rematerializing a stub. */
|
|
3
|
+
export const OBSERVATION_STUB_PREFIX = 'OBSERVATION ref=#';
|
|
4
|
+
export const DEFAULT_HOT_TAIL = 4;
|
|
5
|
+
export const DEFAULT_MAX_BODY_CHARS = 4_000;
|
|
6
|
+
/** Kill switch: ZELARI_SESSION_SURFACE=0 restores the pre-Fase-2 pruner. */
|
|
7
|
+
export function sessionSurfaceEnabled() {
|
|
8
|
+
return process.env.ZELARI_SESSION_SURFACE !== '0';
|
|
9
|
+
}
|
|
10
|
+
/** True when a tool-result body is already a Fase-2 stub (never un-prune). */
|
|
11
|
+
export function isObservationStub(content) {
|
|
12
|
+
return content.startsWith(OBSERVATION_STUB_PREFIX);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse `OBSERVATION ref=#N …` into fields. Returns null if the body is not a stub.
|
|
16
|
+
* Extra key=value pairs after the known ones are preserved as `extra`.
|
|
17
|
+
*/
|
|
18
|
+
export function parseObservationStub(content) {
|
|
19
|
+
if (!isObservationStub(content))
|
|
20
|
+
return null;
|
|
21
|
+
const first = content.split('\n', 1)[0] ?? content;
|
|
22
|
+
const seqMatch = first.match(/^OBSERVATION ref=#(\d+)\b/);
|
|
23
|
+
if (!seqMatch)
|
|
24
|
+
return null;
|
|
25
|
+
const fields = { seq: Number(seqMatch[1]) };
|
|
26
|
+
const tool = first.match(/\btool=([A-Za-z0-9._-]+)/);
|
|
27
|
+
if (tool)
|
|
28
|
+
fields.tool = tool[1];
|
|
29
|
+
const status = first.match(/\bstatus=([A-Za-z0-9._-]+)/);
|
|
30
|
+
if (status)
|
|
31
|
+
fields.status = status[1];
|
|
32
|
+
const bytes = first.match(/\bbytes=(\d+)/);
|
|
33
|
+
if (bytes)
|
|
34
|
+
fields.bytes = Number(bytes[1]);
|
|
35
|
+
const known = /^(ref|#\d+|tool|status|bytes)$/;
|
|
36
|
+
const extras = [];
|
|
37
|
+
for (const m of first.matchAll(/\b([A-Za-z_][A-Za-z0-9._-]*)=([^\s]+)/g)) {
|
|
38
|
+
if (!known.test(m[1]))
|
|
39
|
+
extras.push(`${m[1]}=${m[2]}`);
|
|
40
|
+
}
|
|
41
|
+
if (extras.length)
|
|
42
|
+
fields.extra = extras.join(' ');
|
|
43
|
+
return fields;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Deterministic stub. Same fields → byte-identical string (cache-safe).
|
|
47
|
+
* `seq` is the 1-based index of this tool_execution_end in the session log.
|
|
48
|
+
*/
|
|
49
|
+
export function formatObservationStub(fields) {
|
|
50
|
+
const parts = [`${OBSERVATION_STUB_PREFIX}${fields.seq}`];
|
|
51
|
+
if (fields.tool)
|
|
52
|
+
parts.push(`tool=${fields.tool}`);
|
|
53
|
+
if (fields.status)
|
|
54
|
+
parts.push(`status=${fields.status}`);
|
|
55
|
+
if (fields.bytes !== undefined)
|
|
56
|
+
parts.push(`bytes=${fields.bytes}`);
|
|
57
|
+
if (fields.extra)
|
|
58
|
+
parts.push(fields.extra);
|
|
59
|
+
return `${parts.join(' ')}\n[retrieve_observation #${fields.seq}]`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract Ground-Truth status from a tool-result body (Fase 0 footer), if any.
|
|
63
|
+
* Footer shape: `\n[observation status=complete filesWalked=12 …]`
|
|
64
|
+
*/
|
|
65
|
+
export function statusFromToolBody(body) {
|
|
66
|
+
const m = body.match(/\[observation\s+([^\]]+)\]\s*$/);
|
|
67
|
+
if (!m)
|
|
68
|
+
return undefined;
|
|
69
|
+
const status = m[1].match(/\bstatus=([A-Za-z0-9._-]+)/);
|
|
70
|
+
return status?.[1];
|
|
71
|
+
}
|
|
72
|
+
function resolveSurfaceLimits(opts) {
|
|
73
|
+
return {
|
|
74
|
+
hotTail: opts?.hotTail ??
|
|
75
|
+
envNumber(process.env.ZELARI_SURFACE_HOT_TAIL, { default: DEFAULT_HOT_TAIL, min: 0 }),
|
|
76
|
+
maxBodyChars: opts?.maxBodyChars ??
|
|
77
|
+
envNumber(process.env.ZELARI_SURFACE_MAX_BODY_CHARS, {
|
|
78
|
+
default: DEFAULT_MAX_BODY_CHARS,
|
|
79
|
+
min: 64,
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the stable observation seq for a tool message.
|
|
85
|
+
*
|
|
86
|
+
* Preference order (all deterministic):
|
|
87
|
+
* 1. already a stub → keep its seq (monotone — never rewrite the number)
|
|
88
|
+
* 2. `lookup(toolCallId)` from the session log
|
|
89
|
+
* 3. 1-based index among role:'tool' messages in THIS history (offline / tests)
|
|
90
|
+
*/
|
|
91
|
+
export function resolveObservationSeq(message, indexAmongTools, lookup) {
|
|
92
|
+
const existing = parseObservationStub(message.content ?? '');
|
|
93
|
+
if (existing)
|
|
94
|
+
return existing.seq;
|
|
95
|
+
const id = message.toolCallId;
|
|
96
|
+
if (id && lookup) {
|
|
97
|
+
const fromLog = lookup(id);
|
|
98
|
+
if (typeof fromLog === 'number' && fromLog > 0)
|
|
99
|
+
return fromLog;
|
|
100
|
+
}
|
|
101
|
+
return indexAmongTools + 1;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Infer a tool name from the preceding assistant(tool_calls) that declared
|
|
105
|
+
* this toolCallId. Deterministic walk — no side tables.
|
|
106
|
+
*/
|
|
107
|
+
export function inferToolName(messages, toolIndex, toolCallId) {
|
|
108
|
+
if (!toolCallId)
|
|
109
|
+
return undefined;
|
|
110
|
+
for (let i = toolIndex - 1; i >= 0; i--) {
|
|
111
|
+
const m = messages[i];
|
|
112
|
+
if (m.role !== 'assistant' || !m.toolCalls)
|
|
113
|
+
continue;
|
|
114
|
+
const hit = m.toolCalls.find((c) => c.id === toolCallId);
|
|
115
|
+
if (hit)
|
|
116
|
+
return hit.name;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Project oversized / cold tool-result bodies onto addressable stubs.
|
|
122
|
+
*
|
|
123
|
+
* Invariants:
|
|
124
|
+
* - never mutates the input array or its messages
|
|
125
|
+
* - already-stubbed bodies are left byte-identical
|
|
126
|
+
* - same (messages, opts, lookup) → byte-identical output (cache-safe)
|
|
127
|
+
* - returns the SAME array reference when nothing changed
|
|
128
|
+
*/
|
|
129
|
+
export function projectSessionSurface(messages, opts, lookup) {
|
|
130
|
+
const { hotTail, maxBodyChars } = resolveSurfaceLimits(opts);
|
|
131
|
+
const stats = { projected: 0, charsOmitted: 0, alreadyStubbed: 0 };
|
|
132
|
+
const toolPositions = [];
|
|
133
|
+
for (let i = 0; i < messages.length; i++) {
|
|
134
|
+
if (messages[i].role === 'tool')
|
|
135
|
+
toolPositions.push(i);
|
|
136
|
+
}
|
|
137
|
+
if (toolPositions.length === 0) {
|
|
138
|
+
return { messages: messages, stats };
|
|
139
|
+
}
|
|
140
|
+
const hotStart = Math.max(0, toolPositions.length - hotTail);
|
|
141
|
+
const protect = new Set(toolPositions.slice(hotStart));
|
|
142
|
+
let changed = false;
|
|
143
|
+
const out = messages.map((m, i) => {
|
|
144
|
+
if (m.role !== 'tool')
|
|
145
|
+
return m;
|
|
146
|
+
const body = m.content ?? '';
|
|
147
|
+
if (isObservationStub(body)) {
|
|
148
|
+
stats.alreadyStubbed += 1;
|
|
149
|
+
return m;
|
|
150
|
+
}
|
|
151
|
+
if (protect.has(i) || body.length <= maxBodyChars)
|
|
152
|
+
return m;
|
|
153
|
+
const toolOrdinal = toolPositions.indexOf(i);
|
|
154
|
+
const seq = resolveObservationSeq(m, toolOrdinal, lookup);
|
|
155
|
+
const stub = formatObservationStub({
|
|
156
|
+
seq,
|
|
157
|
+
tool: inferToolName(messages, i, m.toolCallId),
|
|
158
|
+
status: statusFromToolBody(body),
|
|
159
|
+
bytes: body.length,
|
|
160
|
+
});
|
|
161
|
+
changed = true;
|
|
162
|
+
stats.projected += 1;
|
|
163
|
+
stats.charsOmitted += Math.max(0, body.length - stub.length);
|
|
164
|
+
return { ...m, content: stub };
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
messages: changed ? out : messages,
|
|
168
|
+
stats,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=sessionSurface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionSurface.js","sourceRoot":"","sources":["../../../src/cli/hooks/sessionSurface.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sessionSurface — Fase 2 acceptance: deterministic projection, monotone
|
|
3
|
+
* stubs, byte-identical replay, no un-pruning.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { formatObservationStub, isObservationStub, parseObservationStub, projectSessionSurface, resolveObservationSeq, statusFromToolBody, } from './sessionSurface.js';
|
|
7
|
+
function tool(id, content) {
|
|
8
|
+
return { role: 'tool', toolCallId: id, content };
|
|
9
|
+
}
|
|
10
|
+
function assistantCall(id, name) {
|
|
11
|
+
return { role: 'assistant', content: '', toolCalls: [{ id, name, args: {} }] };
|
|
12
|
+
}
|
|
13
|
+
describe('observation stub codec', () => {
|
|
14
|
+
it('round-trips fields byte-identically', () => {
|
|
15
|
+
const stub = formatObservationStub({
|
|
16
|
+
seq: 12,
|
|
17
|
+
tool: 'grep_content',
|
|
18
|
+
status: 'complete',
|
|
19
|
+
bytes: 4096,
|
|
20
|
+
});
|
|
21
|
+
expect(stub).toBe('OBSERVATION ref=#12 tool=grep_content status=complete bytes=4096\n[retrieve_observation #12]');
|
|
22
|
+
expect(isObservationStub(stub)).toBe(true);
|
|
23
|
+
expect(parseObservationStub(stub)).toEqual({
|
|
24
|
+
seq: 12,
|
|
25
|
+
tool: 'grep_content',
|
|
26
|
+
status: 'complete',
|
|
27
|
+
bytes: 4096,
|
|
28
|
+
});
|
|
29
|
+
expect(formatObservationStub(parseObservationStub(stub))).toBe(stub);
|
|
30
|
+
});
|
|
31
|
+
it('rejects non-stubs', () => {
|
|
32
|
+
expect(isObservationStub('hello')).toBe(false);
|
|
33
|
+
expect(parseObservationStub('hello')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
it('reads Fase-0 footer status', () => {
|
|
36
|
+
expect(statusFromToolBody('body\n[observation status=partial filesWalked=3]')).toBe('partial');
|
|
37
|
+
expect(statusFromToolBody('plain')).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('projectSessionSurface', () => {
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
delete process.env.ZELARI_SURFACE_HOT_TAIL;
|
|
43
|
+
delete process.env.ZELARI_SURFACE_MAX_BODY_CHARS;
|
|
44
|
+
delete process.env.ZELARI_SESSION_SURFACE;
|
|
45
|
+
});
|
|
46
|
+
it('returns the same reference when nothing needs projecting', () => {
|
|
47
|
+
const msgs = [
|
|
48
|
+
{ role: 'user', content: 'hi' },
|
|
49
|
+
tool('c1', 'tiny'),
|
|
50
|
+
];
|
|
51
|
+
const r = projectSessionSurface(msgs, { hotTail: 4, maxBodyChars: 4000 });
|
|
52
|
+
expect(r.messages).toBe(msgs);
|
|
53
|
+
expect(r.stats.projected).toBe(0);
|
|
54
|
+
});
|
|
55
|
+
it('keeps the hot tail full-fidelity and stubs older oversized bodies', () => {
|
|
56
|
+
const big = 'B'.repeat(500);
|
|
57
|
+
const msgs = [];
|
|
58
|
+
for (let i = 1; i <= 6; i++) {
|
|
59
|
+
msgs.push(assistantCall(`c${i}`, 'read_file'));
|
|
60
|
+
msgs.push(tool(`c${i}`, big + `\n[observation status=complete bytes=${big.length}]`));
|
|
61
|
+
}
|
|
62
|
+
const r = projectSessionSurface(msgs, { hotTail: 2, maxBodyChars: 100 });
|
|
63
|
+
expect(r.stats.projected).toBe(4);
|
|
64
|
+
const tools = r.messages.filter((m) => m.role === 'tool');
|
|
65
|
+
expect(tools.slice(0, 4).every((m) => isObservationStub(m.content))).toBe(true);
|
|
66
|
+
expect(tools.slice(4).every((m) => !isObservationStub(m.content))).toBe(true);
|
|
67
|
+
expect(tools[0].content).toContain('tool=read_file');
|
|
68
|
+
expect(tools[0].content).toContain('status=complete');
|
|
69
|
+
expect(tools[0].content).toContain('[retrieve_observation #1]');
|
|
70
|
+
});
|
|
71
|
+
it('is deterministic: same input → byte-identical surface', () => {
|
|
72
|
+
const big = 'X'.repeat(800);
|
|
73
|
+
const msgs = [
|
|
74
|
+
assistantCall('a', 'grep_content'),
|
|
75
|
+
tool('a', big),
|
|
76
|
+
assistantCall('b', 'grep_content'),
|
|
77
|
+
tool('b', big),
|
|
78
|
+
assistantCall('c', 'list_files'),
|
|
79
|
+
tool('c', big),
|
|
80
|
+
];
|
|
81
|
+
const a = projectSessionSurface(msgs, { hotTail: 1, maxBodyChars: 50 });
|
|
82
|
+
const b = projectSessionSurface(msgs, { hotTail: 1, maxBodyChars: 50 });
|
|
83
|
+
expect(JSON.stringify(a.messages)).toBe(JSON.stringify(b.messages));
|
|
84
|
+
expect(a.stats).toEqual(b.stats);
|
|
85
|
+
});
|
|
86
|
+
it('never un-prunes an existing stub (monotone / cache-safe)', () => {
|
|
87
|
+
const stub = formatObservationStub({ seq: 7, tool: 'read_file', bytes: 9999 });
|
|
88
|
+
const msgs = [tool('old', stub), tool('hot', 'H'.repeat(50))];
|
|
89
|
+
const r = projectSessionSurface(msgs, { hotTail: 4, maxBodyChars: 10 });
|
|
90
|
+
expect(r.messages[0].content).toBe(stub);
|
|
91
|
+
expect(r.stats.alreadyStubbed).toBe(1);
|
|
92
|
+
expect(r.stats.projected).toBe(0);
|
|
93
|
+
});
|
|
94
|
+
it('uses lookup(toolCallId) for stable seq when provided', () => {
|
|
95
|
+
const big = 'Z'.repeat(200);
|
|
96
|
+
const msgs = [
|
|
97
|
+
assistantCall('call-z', 'bash'),
|
|
98
|
+
tool('call-z', big),
|
|
99
|
+
assistantCall('keep', 'bash'),
|
|
100
|
+
tool('keep', big),
|
|
101
|
+
];
|
|
102
|
+
const lookup = (id) => (id === 'call-z' ? 42 : undefined);
|
|
103
|
+
const r = projectSessionSurface(msgs, { hotTail: 1, maxBodyChars: 10 }, lookup);
|
|
104
|
+
expect(r.messages[1].content).toContain('ref=#42');
|
|
105
|
+
expect(r.messages[1].content).toContain('[retrieve_observation #42]');
|
|
106
|
+
});
|
|
107
|
+
it('falls back to 1-based ordinal when lookup misses', () => {
|
|
108
|
+
const seq = resolveObservationSeq(tool('x', 'body'), 3);
|
|
109
|
+
expect(seq).toBe(4);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=sessionSurface.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionSurface.test.js","sourceRoot":"","sources":["../../../src/cli/hooks/sessionSurface.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// from app.tsx. Runtime is correct; tighten signatures in a follow-up.
|
|
3
3
|
import { useState, useRef, useCallback } from "react";
|
|
4
4
|
import { AgentHarness } from "@zelari/core/harness";
|
|
5
|
+
import { ingestLiveEvent } from "./observationStore.js";
|
|
5
6
|
import { getMetricsLogger } from "../metrics.js";
|
|
6
7
|
import { calculateCost } from "../modelPricing.js";
|
|
7
8
|
import { providerFromEnv, providerConfigFor, resolveActiveProvider, } from "../provider/openai-compatible.js";
|
|
@@ -351,6 +352,8 @@ export function useChatTurn(params) {
|
|
|
351
352
|
"You are in PLAN mode. Explore and design only.",
|
|
352
353
|
"- Do NOT implement production code or run destructive shell commands.",
|
|
353
354
|
"- write_file / edit_file / bash / apply_diff are unavailable.",
|
|
355
|
+
"- inspect_command IS available: allowlisted read-only inspector (no shell). Use it for git_status/git_log/git_diff/git_show/git_branch_current/git_ls_files/typecheck/node_version/npm_ls/npm_outdated/npm_view - it turns claims into execution-verified observations.",
|
|
356
|
+
"- OBSERVATION INTEGRITY: negative evidence is valid only from a completed observation. Never conclude that code/symbols/files do not exist from degraded results, zero files examined, or unavailable backends (grep_content SEARCH_EMPTY_SCOPE, ast/LSP degraded status, inspect_command unsupported shapes).",
|
|
354
357
|
"- Produce a clear plan, ask clarifying questions (---QUESTION---), use workspace plan tools when relevant.",
|
|
355
358
|
"- When the plan is ready, tell the user to run /build to implement.",
|
|
356
359
|
].join("\n")
|
|
@@ -536,6 +539,8 @@ export function useChatTurn(params) {
|
|
|
536
539
|
// matching start event (keyed by toolCallId) for metrics.
|
|
537
540
|
const toolNameById = new Map();
|
|
538
541
|
const metrics = getMetricsLogger();
|
|
542
|
+
// Fase M: last log-only context_metrics event (arrives before agent_end).
|
|
543
|
+
let lastGrowth = null;
|
|
539
544
|
let realUsage = null;
|
|
540
545
|
try {
|
|
541
546
|
for await (const event of harness.run()) {
|
|
@@ -578,6 +583,11 @@ export function useChatTurn(params) {
|
|
|
578
583
|
// to serialize the render loop with mkdir+appendFile per delta.
|
|
579
584
|
// Turn boundaries below call flush() so the tail is durable.
|
|
580
585
|
void writerRef.current.append(event);
|
|
586
|
+
if (sessionId)
|
|
587
|
+
ingestLiveEvent(sessionId, event);
|
|
588
|
+
}
|
|
589
|
+
if (event.type === "context_metrics") {
|
|
590
|
+
lastGrowth = event;
|
|
581
591
|
}
|
|
582
592
|
if (event.type === "agent_end") {
|
|
583
593
|
metrics.record({
|
|
@@ -587,6 +597,17 @@ export function useChatTurn(params) {
|
|
|
587
597
|
model: envConfig.model,
|
|
588
598
|
latencyMs: event.durationMs,
|
|
589
599
|
ok: event.reason === "stop",
|
|
600
|
+
// Fase M: context-growth counters (log-only event, folded here).
|
|
601
|
+
...(lastGrowth
|
|
602
|
+
? {
|
|
603
|
+
toolRoundTrips: lastGrowth.toolRoundTrips,
|
|
604
|
+
intermediateToolBytes: lastGrowth.intermediateToolBytes,
|
|
605
|
+
requests: lastGrowth.requests,
|
|
606
|
+
historyBytesAtRequest: lastGrowth.historyBytesLast,
|
|
607
|
+
historyBytesPeak: lastGrowth.historyBytesPeak,
|
|
608
|
+
cacheHitTokens: lastGrowth.cacheHitTokens,
|
|
609
|
+
}
|
|
610
|
+
: {}),
|
|
590
611
|
// v1.35: real usage landed on message_end (which precedes
|
|
591
612
|
// agent_end), so historical spend can be aggregated from
|
|
592
613
|
// metrics.jsonl — previously these fields were always absent.
|
|
@@ -1171,12 +1192,16 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
1171
1192
|
// Drain remaining events silently after the abort decision.
|
|
1172
1193
|
if (writerRef.current)
|
|
1173
1194
|
void writerRef.current.append(event);
|
|
1195
|
+
if (sessionId)
|
|
1196
|
+
ingestLiveEvent(sessionId, event);
|
|
1174
1197
|
continue;
|
|
1175
1198
|
}
|
|
1176
1199
|
if (writerRef.current) {
|
|
1177
1200
|
// Same batching as the single-agent path: fire-and-forget per event,
|
|
1178
1201
|
// flush at the turn boundary in `finally`.
|
|
1179
1202
|
void writerRef.current.append(event);
|
|
1203
|
+
if (sessionId)
|
|
1204
|
+
ingestLiveEvent(sessionId, event);
|
|
1180
1205
|
}
|
|
1181
1206
|
if (event.type === "council_mode") {
|
|
1182
1207
|
councilRunMode = event.runMode;
|
|
@@ -1680,6 +1705,8 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
|
|
|
1680
1705
|
onEvent: async (event) => {
|
|
1681
1706
|
if (writerRef.current)
|
|
1682
1707
|
await writerRef.current.append(event);
|
|
1708
|
+
if (sessionId)
|
|
1709
|
+
ingestLiveEvent(sessionId, event);
|
|
1683
1710
|
if (event.type === "tool_execution_start") {
|
|
1684
1711
|
const name = event.toolName ?? "tool";
|
|
1685
1712
|
appendSystem(setMessages, `[build@kraken] → ${name}`, Date.now());
|