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.
Files changed (65) hide show
  1. package/dist/cli/app.js +4 -1
  2. package/dist/cli/app.js.map +1 -1
  3. package/dist/cli/ast/engine.js +86 -23
  4. package/dist/cli/ast/engine.js.map +1 -1
  5. package/dist/cli/ast/engine.test.js +101 -0
  6. package/dist/cli/ast/engine.test.js.map +1 -0
  7. package/dist/cli/ast/tools.js +77 -15
  8. package/dist/cli/ast/tools.js.map +1 -1
  9. package/dist/cli/ast/tools.test.js +124 -0
  10. package/dist/cli/ast/tools.test.js.map +1 -0
  11. package/dist/cli/budget/tokenBudget.js +14 -12
  12. package/dist/cli/budget/tokenBudget.js.map +1 -1
  13. package/dist/cli/hooks/conversationContext.js +7 -4
  14. package/dist/cli/hooks/conversationContext.js.map +1 -1
  15. package/dist/cli/hooks/observationStore.js +219 -0
  16. package/dist/cli/hooks/observationStore.js.map +1 -0
  17. package/dist/cli/hooks/observationStore.test.js +135 -0
  18. package/dist/cli/hooks/observationStore.test.js.map +1 -0
  19. package/dist/cli/hooks/sessionSurface.js +171 -0
  20. package/dist/cli/hooks/sessionSurface.js.map +1 -0
  21. package/dist/cli/hooks/sessionSurface.test.js +112 -0
  22. package/dist/cli/hooks/sessionSurface.test.js.map +1 -0
  23. package/dist/cli/hooks/useChatTurn.js +27 -0
  24. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  25. package/dist/cli/hooks/useSession.js +5 -3
  26. package/dist/cli/hooks/useSession.js.map +1 -1
  27. package/dist/cli/lsp/manager.js +10 -0
  28. package/dist/cli/lsp/manager.js.map +1 -1
  29. package/dist/cli/lsp/tools.js +37 -2
  30. package/dist/cli/lsp/tools.js.map +1 -1
  31. package/dist/cli/lsp/tools.test.js +90 -0
  32. package/dist/cli/lsp/tools.test.js.map +1 -0
  33. package/dist/cli/main.bundled.js +2339 -952
  34. package/dist/cli/main.bundled.js.map +4 -4
  35. package/dist/cli/metrics.js +4 -0
  36. package/dist/cli/metrics.js.map +1 -1
  37. package/dist/cli/provider/capabilities.js +53 -0
  38. package/dist/cli/provider/capabilities.js.map +1 -0
  39. package/dist/cli/provider/capabilities.test.js +46 -0
  40. package/dist/cli/provider/capabilities.test.js.map +1 -0
  41. package/dist/cli/provider/openai-compatible.js +2 -1
  42. package/dist/cli/provider/openai-compatible.js.map +1 -1
  43. package/dist/cli/toolRegistry.js +50 -5
  44. package/dist/cli/toolRegistry.js.map +1 -1
  45. package/dist/cli/tools/inspectCommand.js +346 -0
  46. package/dist/cli/tools/inspectCommand.js.map +1 -0
  47. package/dist/cli/tools/inspectCommand.test.js +311 -0
  48. package/dist/cli/tools/inspectCommand.test.js.map +1 -0
  49. package/dist/cli/tools/inspectTypecheckSafety.js +104 -0
  50. package/dist/cli/tools/inspectTypecheckSafety.js.map +1 -0
  51. package/dist/cli/tools/observeBatch.js +254 -0
  52. package/dist/cli/tools/observeBatch.js.map +1 -0
  53. package/dist/cli/tools/observeBatch.test.js +329 -0
  54. package/dist/cli/tools/observeBatch.test.js.map +1 -0
  55. package/dist/cli/tools/retrieveObservation.js +82 -0
  56. package/dist/cli/tools/retrieveObservation.js.map +1 -0
  57. package/dist/cli/tools/taskTool.js +4 -0
  58. package/dist/cli/tools/taskTool.js.map +1 -1
  59. package/dist/cli/utils/contextGrowthSummary.js +55 -0
  60. package/dist/cli/utils/contextGrowthSummary.js.map +1 -0
  61. package/dist/cli/utils/contextGrowthSummary.test.js +74 -0
  62. package/dist/cli/utils/contextGrowthSummary.test.js.map +1 -0
  63. package/dist/cli/utils/doctor.js +26 -0
  64. package/dist/cli/utils/doctor.js.map +1 -1
  65. 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());