zelari-code 2.16.5 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/app.js +3 -0
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/companion/serve.js +29 -0
- package/dist/cli/companion/serve.js.map +1 -1
- package/dist/cli/harnessState.js +240 -0
- package/dist/cli/harnessState.js.map +1 -0
- package/dist/cli/headless/harnessStateEmit.js +38 -0
- package/dist/cli/headless/harnessStateEmit.js.map +1 -0
- package/dist/cli/headless/policyGate.js +6 -3
- package/dist/cli/headless/policyGate.js.map +1 -1
- package/dist/cli/headless/runOneTurn.js +29 -5
- package/dist/cli/headless/runOneTurn.js.map +1 -1
- package/dist/cli/headless.js +20 -9
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +25 -1
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useSlashDispatch.js +1 -1
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
- package/dist/cli/kraken/verificationBridge.js +27 -4
- package/dist/cli/kraken/verificationBridge.js.map +1 -1
- package/dist/cli/lsp/manager.js +31 -11
- package/dist/cli/lsp/manager.js.map +1 -1
- package/dist/cli/main.bundled.js +848 -471
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/memory/fileBackend.js +7 -3
- package/dist/cli/memory/fileBackend.js.map +1 -1
- package/dist/cli/memory/spineTelemetry.js +49 -0
- package/dist/cli/memory/spineTelemetry.js.map +1 -0
- package/dist/cli/runHeadless.js +118 -30
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/safety/jails/win32.js.map +1 -1
- package/dist/cli/serve/harnessServer.js +20 -1
- package/dist/cli/serve/harnessServer.js.map +1 -1
- package/dist/cli/sessionSpine.js +7 -13
- package/dist/cli/sessionSpine.js.map +1 -1
- package/dist/cli/slashHandlers/krakenGraph.js +11 -0
- package/dist/cli/slashHandlers/krakenGraph.js.map +1 -1
- package/package.json +2 -2
|
@@ -118,13 +118,17 @@ export function isMemoryEnabled(env = process.env) {
|
|
|
118
118
|
/**
|
|
119
119
|
* Resolve and initialise the memory backend for a project. Returns a no-op
|
|
120
120
|
* backend (never throws) when disabled or when initialisation fails, so callers
|
|
121
|
-
* can always `await memory.search(...)` without guarding.
|
|
121
|
+
* can always `await memory.search(...)` without guarding. `onEvent` (optional)
|
|
122
|
+
* receives V2 memory telemetry — hosts pass a spine sink (`memorySinkFor`) or
|
|
123
|
+
* `spineMemoryEventNote` to project it onto the session spine (W2).
|
|
122
124
|
*/
|
|
123
|
-
export async function getMemoryBackend(projectRoot, env = process.env) {
|
|
125
|
+
export async function getMemoryBackend(projectRoot, env = process.env, onEvent) {
|
|
124
126
|
if (!isMemoryEnabled(env))
|
|
125
127
|
return new NoopMemoryBackend();
|
|
126
128
|
if (isMemoryV2Enabled(env)) {
|
|
127
|
-
const service = await getMemoryService(projectRoot, env
|
|
129
|
+
const service = await getMemoryService(projectRoot, env, {
|
|
130
|
+
...(onEvent ? { onEvent } : {}),
|
|
131
|
+
});
|
|
128
132
|
if (service instanceof NoopMemoryService)
|
|
129
133
|
return new NoopMemoryBackend();
|
|
130
134
|
return new LegacyMemoryBackendAdapter(service);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileBackend.js","sourceRoot":"","sources":["../../../src/cli/memory/fileBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,OAAO,EAAE,0BAA0B,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"fileBackend.js","sourceRoot":"","sources":["../../../src/cli/memory/fileBackend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write one memory telemetry event as a spine `note`. Per-turn context
|
|
3
|
+
* projection (`memory_recall_end` with reason `context-built`) gets the
|
|
4
|
+
* dedicated `context.projection` subject; every other event lands under
|
|
5
|
+
* `memory_event` with its type. Never throws — a degraded/disabled spine
|
|
6
|
+
* must not break the run over telemetry.
|
|
7
|
+
*/
|
|
8
|
+
export function spineMemoryEventNote(handle, event) {
|
|
9
|
+
try {
|
|
10
|
+
if (event.type === 'memory_recall_end' && event.reason === 'context-built') {
|
|
11
|
+
handle.note('context.projection', {
|
|
12
|
+
subject: 'context.projection',
|
|
13
|
+
...(event.contextChars !== undefined ? { contextChars: event.contextChars } : {}),
|
|
14
|
+
...(event.returnedCount !== undefined ? { returnedCount: event.returnedCount } : {}),
|
|
15
|
+
...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
|
|
16
|
+
...(event.backend !== undefined ? { backend: event.backend } : {}),
|
|
17
|
+
});
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
handle.note(`memory_${event.type}`, {
|
|
21
|
+
subject: 'memory_event',
|
|
22
|
+
type: event.type,
|
|
23
|
+
...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
|
|
24
|
+
...(event.candidateCount !== undefined ? { candidateCount: event.candidateCount } : {}),
|
|
25
|
+
...(event.returnedCount !== undefined ? { returnedCount: event.returnedCount } : {}),
|
|
26
|
+
...(event.backend !== undefined ? { backend: event.backend } : {}),
|
|
27
|
+
...(event.reason !== undefined ? { reason: event.reason } : {}),
|
|
28
|
+
...(event.memoryId !== undefined ? { memoryId: event.memoryId } : {}),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Degraded spine must NEVER break the run over telemetry.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Late-binding seam for hosts where the memory service is constructed BEFORE
|
|
37
|
+
* the spine opens (headless single-turn / council): events are dropped while
|
|
38
|
+
* `holder.current` is unset and flow once the spine handle is assigned.
|
|
39
|
+
* TUI hosts pass a getter-backed holder (`{ get current() { return … } }`) —
|
|
40
|
+
* a getter on `current` satisfies this shape.
|
|
41
|
+
*/
|
|
42
|
+
export function memorySinkFor(holder) {
|
|
43
|
+
return (event) => {
|
|
44
|
+
const handle = holder.current;
|
|
45
|
+
if (handle)
|
|
46
|
+
spineMemoryEventNote(handle, event);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=spineTelemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spineTelemetry.js","sourceRoot":"","sources":["../../../src/cli/memory/spineTelemetry.ts"],"names":[],"mappings":"AAeA;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAuB,EAAE,KAAkB;IAC9E,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,IAAI,KAAK,mBAAmB,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;YAC3E,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;gBAChC,OAAO,EAAE,oBAAoB;gBAC7B,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjF,GAAG,CAAC,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpF,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3E,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnE,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,IAAI,EAAE,EAAE;YAClC,OAAO,EAAE,cAAc;YACvB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,CAAC,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,GAAG,CAAC,KAAK,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvF,GAAG,CAAC,KAAK,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACtE,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAqC;IACjE,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,IAAI,MAAM;YAAE,oBAAoB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/cli/runHeadless.js
CHANGED
|
@@ -3,6 +3,8 @@ import { buildCouncilTaskWithHistory, } from './hooks/conversationContext.js';
|
|
|
3
3
|
import { createBuiltinToolRegistry } from './toolRegistry.js';
|
|
4
4
|
import { chooseOrchestration } from './orchestration/policy.js';
|
|
5
5
|
import { collectOrchestrationFacts, spineOrchestrationNote } from './orchestration/facts.js';
|
|
6
|
+
// W2: memory telemetry projected onto the session spine as state-only notes.
|
|
7
|
+
import { memorySinkFor, spineMemoryEventNote } from './memory/spineTelemetry.js';
|
|
6
8
|
import { COUNCIL_TIER_SIZES } from './councilConfig.js';
|
|
7
9
|
import { emitEvent, resolveHeadlessCwd, resolveHeadlessKey, resolveHeadlessProvider, } from './headless.js';
|
|
8
10
|
import { createLocalCliProvider } from './provider/localCli/claudeProvider.js';
|
|
@@ -16,19 +18,21 @@ import { writeSessionTodos } from './sessionTodos.js';
|
|
|
16
18
|
import { promises as fs } from 'node:fs';
|
|
17
19
|
import path from 'node:path';
|
|
18
20
|
import { randomUUID } from 'node:crypto';
|
|
19
|
-
import { evaluateStrictBuildGate, strictGateEventPayload, strictGateExitCode } from './kraken/verificationBridge.js';
|
|
21
|
+
import { evaluateStrictBuildGate, strictEnvOverlay, strictGateEventPayload, strictGateExitCode } from './kraken/verificationBridge.js';
|
|
20
22
|
import { setActiveProofPersistenceSurface, } from './kraken/completionProofPersist.js';
|
|
21
23
|
import { buildModelContext } from './budget/modelContextBuilder.js';
|
|
22
24
|
import { recordCompactionMetrics } from './metrics.js';
|
|
23
25
|
import { openHeadlessSpine, resolveHeadlessProfileId, seedHeadlessModelHistory, sessionStartedEvent, } from './headlessSpine.js';
|
|
26
|
+
import { emitHarnessStateEvent } from './headless/harnessStateEmit.js';
|
|
24
27
|
import { checkStrictPolicyLoad, recordPolicyLoadBlockedOnSpine, reportPolicyLoadBlocked, } from './headless/policyGate.js';
|
|
25
28
|
import { activePolicyLoadMode, setActivePolicyLoadSurface, } from './safety/policyLoadMode.js';
|
|
26
29
|
import { planModeFromOpts, registerHeadlessMcp, runOneTurn, writeProofSafe } from './headless/runOneTurn.js';
|
|
27
30
|
export async function runHeadless(opts) {
|
|
28
31
|
resetTaskSpawnCount();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
// H10-fix1: strictDone/missionStrict NEVER touch process.env (not even
|
|
33
|
+
// once-per-process) — each gate site builds a per-invocation overlay via
|
|
34
|
+
// strictEnvOverlay(opts), so `--no-strict-done` (false→'0') is honored
|
|
35
|
+
// and a concurrent sidecar can never race on the real env.
|
|
32
36
|
// === Global crash handlers (headless-only) ===
|
|
33
37
|
// Without these, an uncaught exception during a tool call (e.g. write_file
|
|
34
38
|
// failing deep in the harness) kills the process silently: no agent_end,
|
|
@@ -121,7 +125,13 @@ export async function runHeadless(opts) {
|
|
|
121
125
|
model,
|
|
122
126
|
});
|
|
123
127
|
}
|
|
124
|
-
return dispatchHeadlessTurn(opts, provider, model, providerStream
|
|
128
|
+
return dispatchHeadlessTurn(opts, provider, model, providerStream, {
|
|
129
|
+
// H10-fix3: the gate already ran above on the SAME input (no chdir in
|
|
130
|
+
// between) — the one-shot marker keeps dispatchHeadlessTurn from
|
|
131
|
+
// re-running it (duplicate `[policy]` stderr warning + double policy
|
|
132
|
+
// load). Per-invocation flag only; never process-global.
|
|
133
|
+
policyGateDone: true,
|
|
134
|
+
});
|
|
125
135
|
}
|
|
126
136
|
/** Strict policy-load + phase/proof surfaces. Returns the block exit code, or undefined. */
|
|
127
137
|
async function applyHeadlessPolicyGate(opts) {
|
|
@@ -134,6 +144,9 @@ async function applyHeadlessPolicyGate(opts) {
|
|
|
134
144
|
reportPolicyLoadBlocked(policyLoad.block, opts.output);
|
|
135
145
|
await recordPolicyLoadBlockedOnSpine(policyLoad.block, {
|
|
136
146
|
mode: opts.mode,
|
|
147
|
+
// H10-fix2: the spine must land in the RESOLVED workspace, not the
|
|
148
|
+
// process cwd — a sidecar hosts N workspaces without `chdir`.
|
|
149
|
+
workspace: cwd,
|
|
137
150
|
...(opts.profile ? { profile: opts.profile } : {}),
|
|
138
151
|
...(opts.resumeSessionId ? { resumeSessionId: opts.resumeSessionId } : {}),
|
|
139
152
|
});
|
|
@@ -147,9 +160,13 @@ async function applyHeadlessPolicyGate(opts) {
|
|
|
147
160
|
/**
|
|
148
161
|
* Per-turn dispatch shared by `--headless` (one-shot process) and
|
|
149
162
|
* `--serve-harness` (long-lived sidecar). Does NOT install process-fatal
|
|
150
|
-
* handlers or
|
|
151
|
-
*
|
|
152
|
-
*
|
|
163
|
+
* handlers or touch `ZELARI_STRICT_DONE` — the strict knobs ride
|
|
164
|
+
* HeadlessOptions into a per-invocation env overlay (strictEnvOverlay)
|
|
165
|
+
* consumed at each gate site (runOneTurn / runHeadlessZelari). Honors
|
|
166
|
+
* `opts.cwd` so N sidecar sessions can sit on
|
|
167
|
+
* different folders without `chdir`. `oneShot.policyGateDone` is set by
|
|
168
|
+
* the one-shot wrapper ONLY (its gate already ran); the sidecar never
|
|
169
|
+
* passes it, so the gate keeps running every turn here.
|
|
153
170
|
*/
|
|
154
171
|
const KRAKEN_TURN_ENV = [
|
|
155
172
|
['krakenExploreModel', 'ZELARI_KRAKEN_EXPLORE_MODEL'],
|
|
@@ -166,7 +183,11 @@ function applyKrakenTurnEnv(opts) {
|
|
|
166
183
|
}
|
|
167
184
|
}
|
|
168
185
|
}
|
|
169
|
-
export async function dispatchHeadlessTurn(opts, provider, model, providerStream
|
|
186
|
+
export async function dispatchHeadlessTurn(opts, provider, model, providerStream, oneShot,
|
|
187
|
+
// t37: serve hosts pass the kernel-owned workspace LspManager here so
|
|
188
|
+
// every dispatch path (kraken single / council / zelari) registers the
|
|
189
|
+
// LSP tools against THAT server instead of the shared per-root manager.
|
|
190
|
+
extras) {
|
|
170
191
|
const cwd = resolveHeadlessCwd(opts);
|
|
171
192
|
if (typeof opts.mode === 'string') {
|
|
172
193
|
const parsed = parseMode(opts.mode);
|
|
@@ -200,9 +221,17 @@ export async function dispatchHeadlessTurn(opts, provider, model, providerStream
|
|
|
200
221
|
catch {
|
|
201
222
|
/* non-fatal */
|
|
202
223
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
224
|
+
// H10-fix3: the sidecar (serve/harnessServer) needs this gate EVERY turn —
|
|
225
|
+
// it also re-pins setPhase/setActivePolicyLoadSurface/setActiveProof-
|
|
226
|
+
// PersistenceSurface per turn. The one-shot wrapper passes
|
|
227
|
+
// `policyGateDone` because runHeadless() already ran the identical gate
|
|
228
|
+
// moments ago; re-running it would emit the same `[policy]` warning twice
|
|
229
|
+
// and reload the policy set for nothing.
|
|
230
|
+
if (!oneShot?.policyGateDone) {
|
|
231
|
+
const policyBlock = await applyHeadlessPolicyGate(opts);
|
|
232
|
+
if (policyBlock !== undefined)
|
|
233
|
+
return policyBlock;
|
|
234
|
+
}
|
|
206
235
|
// P1.1 (t12) -> t23 (P1.E): `--mode auto` resolves ONCE here, before any
|
|
207
236
|
// dispatch check. Pure classifier (./orchestration/policy.js); the only
|
|
208
237
|
// I/O is the CHEAP FACTS collection (bounded repo walk + active-contract
|
|
@@ -261,12 +290,12 @@ export async function dispatchHeadlessTurn(opts, provider, model, providerStream
|
|
|
261
290
|
process.stderr.write(`[zelari-code --headless] ${line}\n`);
|
|
262
291
|
}
|
|
263
292
|
if (mode === 'zelari') {
|
|
264
|
-
return runHeadlessZelari(opts, provider, model, providerStream);
|
|
293
|
+
return runHeadlessZelari(opts, provider, model, providerStream, extras);
|
|
265
294
|
}
|
|
266
295
|
if (mode === 'council' || opts.useCouncil) {
|
|
267
|
-
return runHeadlessCouncil(opts, provider, model, providerStream);
|
|
296
|
+
return runHeadlessCouncil(opts, provider, model, providerStream, extras);
|
|
268
297
|
}
|
|
269
|
-
return runOneTurn(opts, provider, model, providerStream);
|
|
298
|
+
return runOneTurn(opts, provider, model, providerStream, extras);
|
|
270
299
|
}
|
|
271
300
|
/**
|
|
272
301
|
* `--kraken-graph <goal>`: plan (F4) + execute (F3) a Kraken task graph,
|
|
@@ -291,9 +320,23 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
291
320
|
const { createKrakenSubAgentContextFactory } = await import('./toolRegistry.js');
|
|
292
321
|
const cwd = resolveHeadlessCwd(opts);
|
|
293
322
|
const sessionId = crypto.randomUUID();
|
|
323
|
+
// W1: kraken-graph was the only headless dispatch path with no ADR-0016
|
|
324
|
+
// session spine — hosts had no event log to resume or replay. Mirror the
|
|
325
|
+
// council/zelari hosts: same open shape, same degrade-and-stop discipline.
|
|
326
|
+
const spine = await openHeadlessSpine({ sessionId, mode: 'kraken', workspace: cwd });
|
|
327
|
+
// E1.4: advertise the spine session id so hosts (Desktop) resume the same
|
|
328
|
+
// event log next turn. Gated to json output — plain stdout must stay
|
|
329
|
+
// byte-for-byte identical to the pre-spine behavior.
|
|
330
|
+
if (opts.output === 'json')
|
|
331
|
+
emitEvent(sessionStartedEvent(spine));
|
|
332
|
+
spine.userMessage(prompt);
|
|
294
333
|
const { getMemoryService, isMemoryAutoWriteEnabled, isMemoryV2Enabled } = await import('./memory/serviceFactory.js');
|
|
295
334
|
const graphMemory = isMemoryV2Enabled()
|
|
296
|
-
? await getMemoryService(cwd, process.env
|
|
335
|
+
? await getMemoryService(cwd, process.env, {
|
|
336
|
+
// W2: the spine is already open here — memory events are noted
|
|
337
|
+
// directly (context.projection / memory_event state-only payloads).
|
|
338
|
+
onEvent: (event) => spineMemoryEventNote(spine, event),
|
|
339
|
+
})
|
|
297
340
|
: undefined;
|
|
298
341
|
const log = (message) => {
|
|
299
342
|
if (opts.output === 'json') {
|
|
@@ -314,6 +357,9 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
314
357
|
abort.abort();
|
|
315
358
|
};
|
|
316
359
|
process.once('SIGINT', onSigint);
|
|
360
|
+
// W1: every return below records its code first so the finally can close
|
|
361
|
+
// the spine with the matching status (completed / error / cancelled).
|
|
362
|
+
let exitCode = 0;
|
|
317
363
|
try {
|
|
318
364
|
// ---- Pre-flight: run a pre-built plan from disk, skipping the planner.
|
|
319
365
|
let preflightGraph;
|
|
@@ -326,7 +372,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
326
372
|
}
|
|
327
373
|
catch (e) {
|
|
328
374
|
log(`plan file not found: ${planPath} (${e.message})`);
|
|
329
|
-
|
|
375
|
+
exitCode = 1;
|
|
376
|
+
return exitCode;
|
|
330
377
|
}
|
|
331
378
|
let planJson;
|
|
332
379
|
try {
|
|
@@ -334,11 +381,13 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
334
381
|
}
|
|
335
382
|
catch (e) {
|
|
336
383
|
log(`plan file is malformed JSON: ${e.message}`);
|
|
337
|
-
|
|
384
|
+
exitCode = 1;
|
|
385
|
+
return exitCode;
|
|
338
386
|
}
|
|
339
387
|
if (!planJson || !Array.isArray(planJson.nodes)) {
|
|
340
388
|
log(`plan file is malformed: missing "nodes" array`);
|
|
341
|
-
|
|
389
|
+
exitCode = 1;
|
|
390
|
+
return exitCode;
|
|
342
391
|
}
|
|
343
392
|
const { createGraph, validateGraph } = await import('@zelari/core');
|
|
344
393
|
const validated = createGraph(planJson.graphId ?? opts.runPlan, planJson.nodes);
|
|
@@ -377,7 +426,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
377
426
|
emitEvent({ type: 'log', message: `plan_only_id=${planId}` });
|
|
378
427
|
emitEvent({ type: 'log', message: `plan_only_path=${planPath}` });
|
|
379
428
|
}
|
|
380
|
-
|
|
429
|
+
exitCode = 0;
|
|
430
|
+
return exitCode;
|
|
381
431
|
}
|
|
382
432
|
const audit = new AuditLogger();
|
|
383
433
|
const executor = new KrakenGraphExecutor({
|
|
@@ -442,7 +492,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
442
492
|
else {
|
|
443
493
|
process.stdout.write(`${finalAscii}\n`);
|
|
444
494
|
}
|
|
445
|
-
|
|
495
|
+
exitCode = summary.converged ? 0 : 3;
|
|
496
|
+
return exitCode;
|
|
446
497
|
}
|
|
447
498
|
catch (err) {
|
|
448
499
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -452,18 +503,39 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
452
503
|
else {
|
|
453
504
|
process.stderr.write(`[zelari-code --headless] kraken graph failed: ${message}\n`);
|
|
454
505
|
}
|
|
455
|
-
|
|
506
|
+
exitCode = 2;
|
|
507
|
+
return exitCode;
|
|
456
508
|
}
|
|
457
509
|
finally {
|
|
458
510
|
process.off('SIGINT', onSigint);
|
|
459
511
|
await graphMemory?.close().catch(() => undefined);
|
|
512
|
+
// W1: the spine closes on EVERY exit (completed / error / cancelled) and
|
|
513
|
+
// must never change the exit code.
|
|
514
|
+
try {
|
|
515
|
+
const closeReason = abort.signal.aborted
|
|
516
|
+
? 'cancelled'
|
|
517
|
+
: exitCode === 0
|
|
518
|
+
? 'completed'
|
|
519
|
+
: 'error';
|
|
520
|
+
await spine.close(closeReason);
|
|
521
|
+
}
|
|
522
|
+
catch { /* spine never fails the run */ }
|
|
523
|
+
// HarnessState inc.3: final read-model event for JSON hosts (best-effort).
|
|
524
|
+
await emitHarnessStateEvent({ spine, workspaceRoot: cwd, output: opts.output, emitEvent });
|
|
460
525
|
}
|
|
461
526
|
}
|
|
462
|
-
async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAutoWrite = false
|
|
527
|
+
async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAutoWrite = false,
|
|
528
|
+
// t37: the council/zelari parent registry honors the same TurnExtras as
|
|
529
|
+
// the single-agent path. TODO(t37): the deeper council mission-slice
|
|
530
|
+
// per-agent registries (~runAgentMissionSlice below) still resolve via
|
|
531
|
+
// the shared per-root map — same anti-thrash behavior (one manager per
|
|
532
|
+
// root, no cross-workspace kills), just not the kernel-owned instance.
|
|
533
|
+
extras) {
|
|
463
534
|
const cwd = opts ? resolveHeadlessCwd(opts) : process.cwd();
|
|
464
535
|
const { registry: toolRegistry } = createBuiltinToolRegistry({
|
|
465
536
|
root: cwd,
|
|
466
537
|
planMode,
|
|
538
|
+
...(extras?.lspProvider ? { lspProvider: extras.lspProvider } : {}),
|
|
467
539
|
permissionPolicy: {
|
|
468
540
|
read: 'allow',
|
|
469
541
|
write: 'allow',
|
|
@@ -491,13 +563,18 @@ async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAut
|
|
|
491
563
|
}
|
|
492
564
|
return { toolRegistry, workspaceCtx: realCtx };
|
|
493
565
|
}
|
|
494
|
-
async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
566
|
+
async function runHeadlessCouncil(opts, provider, model, providerStream, extras) {
|
|
495
567
|
const { dispatchCouncil } = await import('./councilDispatcher.js');
|
|
496
568
|
const sessionId = crypto.randomUUID();
|
|
497
569
|
const cwd = resolveHeadlessCwd(opts);
|
|
498
570
|
const memoryFactory = await import('./memory/serviceFactory.js');
|
|
571
|
+
// W2: memory events project onto the session spine (late-binding holder —
|
|
572
|
+
// the spine opens below; events before assignment are dropped, advisory).
|
|
573
|
+
const spineHolder = {};
|
|
499
574
|
const nativeMemory = memoryFactory.isMemoryV2Enabled()
|
|
500
|
-
? await memoryFactory.getMemoryService(cwd, process.env
|
|
575
|
+
? await memoryFactory.getMemoryService(cwd, process.env, {
|
|
576
|
+
onEvent: memorySinkFor(spineHolder),
|
|
577
|
+
})
|
|
501
578
|
: undefined;
|
|
502
579
|
const memoryAutoWrite = memoryFactory.isMemoryAutoWriteEnabled();
|
|
503
580
|
const spine = await openHeadlessSpine({
|
|
@@ -506,6 +583,8 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
506
583
|
profile: opts.profile,
|
|
507
584
|
workspace: cwd,
|
|
508
585
|
});
|
|
586
|
+
// W2: bind the memory telemetry sink to the now-open spine.
|
|
587
|
+
spineHolder.current = spine;
|
|
509
588
|
// Exit-1/E1.2: the session spine is the model-context source of truth.
|
|
510
589
|
// Legacy `--history` is imported one-shot into a fresh log; prior turns
|
|
511
590
|
// are then derived from events. The 1.x rolling history no longer feeds
|
|
@@ -532,7 +611,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
532
611
|
process.stderr.write('[zelari-code --headless] council build soft-gate: forced design-phase ' +
|
|
533
612
|
'(set ZELARI_COUNCIL_CAN_BUILD=1 to allow Lucifero implement)\n');
|
|
534
613
|
}
|
|
535
|
-
const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts) || softGated, opts, nativeMemory, memoryAutoWrite);
|
|
614
|
+
const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts) || softGated, opts, nativeMemory, memoryAutoWrite, extras);
|
|
536
615
|
const { FeedbackStore } = await import('./councilFeedback.js');
|
|
537
616
|
const feedbackStore = new FeedbackStore();
|
|
538
617
|
// Multi-turn: Desktop passes --history, but council used to ignore it →
|
|
@@ -682,6 +761,8 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
682
761
|
await spine.close(exitCode === 0 ? 'completed' : 'error');
|
|
683
762
|
}
|
|
684
763
|
catch { /* spine never fails the run */ }
|
|
764
|
+
// HarnessState inc.3: final read-model event for JSON hosts (best-effort).
|
|
765
|
+
await emitHarnessStateEvent({ spine, workspaceRoot: cwd, output: opts.output, emitEvent });
|
|
685
766
|
if (opts.exportSessionPath) {
|
|
686
767
|
try {
|
|
687
768
|
const json = await spine.exportJson();
|
|
@@ -727,7 +808,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
727
808
|
/**
|
|
728
809
|
* Zelari mission loop (headless). Streams progress as `log` events + council BrainEvents.
|
|
729
810
|
*/
|
|
730
|
-
async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
811
|
+
async function runHeadlessZelari(opts, provider, model, providerStream, extras) {
|
|
731
812
|
const projectRoot = resolveHeadlessCwd(opts);
|
|
732
813
|
const sessionId = opts.resumeSessionId ?? crypto.randomUUID();
|
|
733
814
|
const spine = await openHeadlessSpine({
|
|
@@ -756,9 +837,11 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
756
837
|
userMessage: opts.task,
|
|
757
838
|
hasPlan: hasWorkspacePlan(projectRoot),
|
|
758
839
|
});
|
|
759
|
-
|
|
840
|
+
// W2: the mission spine is already open here — mission memory events are
|
|
841
|
+
// noted directly (context.projection / memory_event state-only payloads).
|
|
842
|
+
const memory = await getMemoryBackend(projectRoot, process.env, (event) => spineMemoryEventNote(spine, event));
|
|
760
843
|
const nativeMissionMemory = memory.service;
|
|
761
|
-
const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(planModeFromOpts(opts), opts, nativeMissionMemory, Boolean(nativeMissionMemory) && process.env.ZELARI_MEMORY_AUTO_WRITE !== '0');
|
|
844
|
+
const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(planModeFromOpts(opts), opts, nativeMissionMemory, Boolean(nativeMissionMemory) && process.env.ZELARI_MEMORY_AUTO_WRITE !== '0', extras);
|
|
762
845
|
const feedbackStore = new FeedbackStore();
|
|
763
846
|
const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
|
|
764
847
|
default: 30,
|
|
@@ -1083,12 +1166,15 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1083
1166
|
exitCode = exitCode || 3;
|
|
1084
1167
|
else if (state.status === 'success') {
|
|
1085
1168
|
// ADR-0025: missions close under the strict evidence gate by default
|
|
1086
|
-
// (opt-out: ZELARI_MISSION_STRICT=0
|
|
1169
|
+
// (opt-out: ZELARI_MISSION_STRICT=0, or the per-run --no-mission-strict /
|
|
1170
|
+
// --no-strict-done overlay). A blocked gate never exits 0 —
|
|
1087
1171
|
// the mission "success" becomes the strict exit code and the spine
|
|
1088
1172
|
// records mission-strict-blocked instead of mission-success.
|
|
1089
1173
|
const missionGate = await evaluateStrictBuildGate('build', {
|
|
1090
1174
|
emit: (input) => spine.appendEvent(input),
|
|
1091
1175
|
surface: 'mission',
|
|
1176
|
+
// H10-fix1: per-invocation env overlay — never process.env.
|
|
1177
|
+
env: strictEnvOverlay(opts),
|
|
1092
1178
|
cwd: projectRoot,
|
|
1093
1179
|
});
|
|
1094
1180
|
const missionVerificationPayload = strictGateEventPayload(missionGate);
|
|
@@ -1130,6 +1216,8 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1130
1216
|
await spine.close(exitCode === 2 ? 'error' : 'stopped');
|
|
1131
1217
|
}
|
|
1132
1218
|
catch { /* spine never fails the run */ }
|
|
1219
|
+
// HarnessState inc.3: final read-model event for JSON hosts (best-effort).
|
|
1220
|
+
await emitHarnessStateEvent({ spine, workspaceRoot: projectRoot, output: opts.output, emitEvent });
|
|
1133
1221
|
if (opts.exportSessionPath) {
|
|
1134
1222
|
try {
|
|
1135
1223
|
const json = await spine.exportJson();
|