zelari-code 2.16.4 → 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 +849 -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 +122 -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);
|
|
@@ -176,6 +197,10 @@ export async function dispatchHeadlessTurn(opts, provider, model, providerStream
|
|
|
176
197
|
opts = { ...opts, cwd };
|
|
177
198
|
}
|
|
178
199
|
applyKrakenTurnEnv(opts);
|
|
200
|
+
// Each parent user turn gets a fresh tentacle budget (GUIDA: default 6).
|
|
201
|
+
// Desktop sidecar never enters runHeadless(), so without this the counter
|
|
202
|
+
// lives for the whole Node process — 6 tentacles total, then spawn cap.
|
|
203
|
+
resetTaskSpawnCount();
|
|
179
204
|
if (opts.todos && opts.todos.length > 0) {
|
|
180
205
|
writeSessionTodos(opts.todos, { merge: false });
|
|
181
206
|
}
|
|
@@ -196,9 +221,17 @@ export async function dispatchHeadlessTurn(opts, provider, model, providerStream
|
|
|
196
221
|
catch {
|
|
197
222
|
/* non-fatal */
|
|
198
223
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
+
}
|
|
202
235
|
// P1.1 (t12) -> t23 (P1.E): `--mode auto` resolves ONCE here, before any
|
|
203
236
|
// dispatch check. Pure classifier (./orchestration/policy.js); the only
|
|
204
237
|
// I/O is the CHEAP FACTS collection (bounded repo walk + active-contract
|
|
@@ -257,12 +290,12 @@ export async function dispatchHeadlessTurn(opts, provider, model, providerStream
|
|
|
257
290
|
process.stderr.write(`[zelari-code --headless] ${line}\n`);
|
|
258
291
|
}
|
|
259
292
|
if (mode === 'zelari') {
|
|
260
|
-
return runHeadlessZelari(opts, provider, model, providerStream);
|
|
293
|
+
return runHeadlessZelari(opts, provider, model, providerStream, extras);
|
|
261
294
|
}
|
|
262
295
|
if (mode === 'council' || opts.useCouncil) {
|
|
263
|
-
return runHeadlessCouncil(opts, provider, model, providerStream);
|
|
296
|
+
return runHeadlessCouncil(opts, provider, model, providerStream, extras);
|
|
264
297
|
}
|
|
265
|
-
return runOneTurn(opts, provider, model, providerStream);
|
|
298
|
+
return runOneTurn(opts, provider, model, providerStream, extras);
|
|
266
299
|
}
|
|
267
300
|
/**
|
|
268
301
|
* `--kraken-graph <goal>`: plan (F4) + execute (F3) a Kraken task graph,
|
|
@@ -287,9 +320,23 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
287
320
|
const { createKrakenSubAgentContextFactory } = await import('./toolRegistry.js');
|
|
288
321
|
const cwd = resolveHeadlessCwd(opts);
|
|
289
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);
|
|
290
333
|
const { getMemoryService, isMemoryAutoWriteEnabled, isMemoryV2Enabled } = await import('./memory/serviceFactory.js');
|
|
291
334
|
const graphMemory = isMemoryV2Enabled()
|
|
292
|
-
? 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
|
+
})
|
|
293
340
|
: undefined;
|
|
294
341
|
const log = (message) => {
|
|
295
342
|
if (opts.output === 'json') {
|
|
@@ -310,6 +357,9 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
310
357
|
abort.abort();
|
|
311
358
|
};
|
|
312
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;
|
|
313
363
|
try {
|
|
314
364
|
// ---- Pre-flight: run a pre-built plan from disk, skipping the planner.
|
|
315
365
|
let preflightGraph;
|
|
@@ -322,7 +372,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
322
372
|
}
|
|
323
373
|
catch (e) {
|
|
324
374
|
log(`plan file not found: ${planPath} (${e.message})`);
|
|
325
|
-
|
|
375
|
+
exitCode = 1;
|
|
376
|
+
return exitCode;
|
|
326
377
|
}
|
|
327
378
|
let planJson;
|
|
328
379
|
try {
|
|
@@ -330,11 +381,13 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
330
381
|
}
|
|
331
382
|
catch (e) {
|
|
332
383
|
log(`plan file is malformed JSON: ${e.message}`);
|
|
333
|
-
|
|
384
|
+
exitCode = 1;
|
|
385
|
+
return exitCode;
|
|
334
386
|
}
|
|
335
387
|
if (!planJson || !Array.isArray(planJson.nodes)) {
|
|
336
388
|
log(`plan file is malformed: missing "nodes" array`);
|
|
337
|
-
|
|
389
|
+
exitCode = 1;
|
|
390
|
+
return exitCode;
|
|
338
391
|
}
|
|
339
392
|
const { createGraph, validateGraph } = await import('@zelari/core');
|
|
340
393
|
const validated = createGraph(planJson.graphId ?? opts.runPlan, planJson.nodes);
|
|
@@ -373,7 +426,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
373
426
|
emitEvent({ type: 'log', message: `plan_only_id=${planId}` });
|
|
374
427
|
emitEvent({ type: 'log', message: `plan_only_path=${planPath}` });
|
|
375
428
|
}
|
|
376
|
-
|
|
429
|
+
exitCode = 0;
|
|
430
|
+
return exitCode;
|
|
377
431
|
}
|
|
378
432
|
const audit = new AuditLogger();
|
|
379
433
|
const executor = new KrakenGraphExecutor({
|
|
@@ -438,7 +492,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
438
492
|
else {
|
|
439
493
|
process.stdout.write(`${finalAscii}\n`);
|
|
440
494
|
}
|
|
441
|
-
|
|
495
|
+
exitCode = summary.converged ? 0 : 3;
|
|
496
|
+
return exitCode;
|
|
442
497
|
}
|
|
443
498
|
catch (err) {
|
|
444
499
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -448,18 +503,39 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
448
503
|
else {
|
|
449
504
|
process.stderr.write(`[zelari-code --headless] kraken graph failed: ${message}\n`);
|
|
450
505
|
}
|
|
451
|
-
|
|
506
|
+
exitCode = 2;
|
|
507
|
+
return exitCode;
|
|
452
508
|
}
|
|
453
509
|
finally {
|
|
454
510
|
process.off('SIGINT', onSigint);
|
|
455
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 });
|
|
456
525
|
}
|
|
457
526
|
}
|
|
458
|
-
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) {
|
|
459
534
|
const cwd = opts ? resolveHeadlessCwd(opts) : process.cwd();
|
|
460
535
|
const { registry: toolRegistry } = createBuiltinToolRegistry({
|
|
461
536
|
root: cwd,
|
|
462
537
|
planMode,
|
|
538
|
+
...(extras?.lspProvider ? { lspProvider: extras.lspProvider } : {}),
|
|
463
539
|
permissionPolicy: {
|
|
464
540
|
read: 'allow',
|
|
465
541
|
write: 'allow',
|
|
@@ -487,13 +563,18 @@ async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAut
|
|
|
487
563
|
}
|
|
488
564
|
return { toolRegistry, workspaceCtx: realCtx };
|
|
489
565
|
}
|
|
490
|
-
async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
566
|
+
async function runHeadlessCouncil(opts, provider, model, providerStream, extras) {
|
|
491
567
|
const { dispatchCouncil } = await import('./councilDispatcher.js');
|
|
492
568
|
const sessionId = crypto.randomUUID();
|
|
493
569
|
const cwd = resolveHeadlessCwd(opts);
|
|
494
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 = {};
|
|
495
574
|
const nativeMemory = memoryFactory.isMemoryV2Enabled()
|
|
496
|
-
? await memoryFactory.getMemoryService(cwd, process.env
|
|
575
|
+
? await memoryFactory.getMemoryService(cwd, process.env, {
|
|
576
|
+
onEvent: memorySinkFor(spineHolder),
|
|
577
|
+
})
|
|
497
578
|
: undefined;
|
|
498
579
|
const memoryAutoWrite = memoryFactory.isMemoryAutoWriteEnabled();
|
|
499
580
|
const spine = await openHeadlessSpine({
|
|
@@ -502,6 +583,8 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
502
583
|
profile: opts.profile,
|
|
503
584
|
workspace: cwd,
|
|
504
585
|
});
|
|
586
|
+
// W2: bind the memory telemetry sink to the now-open spine.
|
|
587
|
+
spineHolder.current = spine;
|
|
505
588
|
// Exit-1/E1.2: the session spine is the model-context source of truth.
|
|
506
589
|
// Legacy `--history` is imported one-shot into a fresh log; prior turns
|
|
507
590
|
// are then derived from events. The 1.x rolling history no longer feeds
|
|
@@ -528,7 +611,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
528
611
|
process.stderr.write('[zelari-code --headless] council build soft-gate: forced design-phase ' +
|
|
529
612
|
'(set ZELARI_COUNCIL_CAN_BUILD=1 to allow Lucifero implement)\n');
|
|
530
613
|
}
|
|
531
|
-
const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts) || softGated, opts, nativeMemory, memoryAutoWrite);
|
|
614
|
+
const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts) || softGated, opts, nativeMemory, memoryAutoWrite, extras);
|
|
532
615
|
const { FeedbackStore } = await import('./councilFeedback.js');
|
|
533
616
|
const feedbackStore = new FeedbackStore();
|
|
534
617
|
// Multi-turn: Desktop passes --history, but council used to ignore it →
|
|
@@ -678,6 +761,8 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
678
761
|
await spine.close(exitCode === 0 ? 'completed' : 'error');
|
|
679
762
|
}
|
|
680
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 });
|
|
681
766
|
if (opts.exportSessionPath) {
|
|
682
767
|
try {
|
|
683
768
|
const json = await spine.exportJson();
|
|
@@ -723,7 +808,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
723
808
|
/**
|
|
724
809
|
* Zelari mission loop (headless). Streams progress as `log` events + council BrainEvents.
|
|
725
810
|
*/
|
|
726
|
-
async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
811
|
+
async function runHeadlessZelari(opts, provider, model, providerStream, extras) {
|
|
727
812
|
const projectRoot = resolveHeadlessCwd(opts);
|
|
728
813
|
const sessionId = opts.resumeSessionId ?? crypto.randomUUID();
|
|
729
814
|
const spine = await openHeadlessSpine({
|
|
@@ -752,9 +837,11 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
752
837
|
userMessage: opts.task,
|
|
753
838
|
hasPlan: hasWorkspacePlan(projectRoot),
|
|
754
839
|
});
|
|
755
|
-
|
|
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));
|
|
756
843
|
const nativeMissionMemory = memory.service;
|
|
757
|
-
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);
|
|
758
845
|
const feedbackStore = new FeedbackStore();
|
|
759
846
|
const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
|
|
760
847
|
default: 30,
|
|
@@ -1079,12 +1166,15 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1079
1166
|
exitCode = exitCode || 3;
|
|
1080
1167
|
else if (state.status === 'success') {
|
|
1081
1168
|
// ADR-0025: missions close under the strict evidence gate by default
|
|
1082
|
-
// (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 —
|
|
1083
1171
|
// the mission "success" becomes the strict exit code and the spine
|
|
1084
1172
|
// records mission-strict-blocked instead of mission-success.
|
|
1085
1173
|
const missionGate = await evaluateStrictBuildGate('build', {
|
|
1086
1174
|
emit: (input) => spine.appendEvent(input),
|
|
1087
1175
|
surface: 'mission',
|
|
1176
|
+
// H10-fix1: per-invocation env overlay — never process.env.
|
|
1177
|
+
env: strictEnvOverlay(opts),
|
|
1088
1178
|
cwd: projectRoot,
|
|
1089
1179
|
});
|
|
1090
1180
|
const missionVerificationPayload = strictGateEventPayload(missionGate);
|
|
@@ -1126,6 +1216,8 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1126
1216
|
await spine.close(exitCode === 2 ? 'error' : 'stopped');
|
|
1127
1217
|
}
|
|
1128
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 });
|
|
1129
1221
|
if (opts.exportSessionPath) {
|
|
1130
1222
|
try {
|
|
1131
1223
|
const json = await spine.exportJson();
|