zelari-code 2.0.0-alpha.5 → 2.0.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/headless.js +9 -0
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/headlessE2eSession.test.js +189 -0
- package/dist/cli/headlessE2eSession.test.js.map +1 -0
- package/dist/cli/headlessSessionEvent.test.js +83 -0
- package/dist/cli/headlessSessionEvent.test.js.map +1 -0
- package/dist/cli/headlessSpine.js +25 -0
- package/dist/cli/headlessSpine.js.map +1 -1
- package/dist/cli/hooks/chatState.js.map +1 -1
- package/dist/cli/hooks/eventsToMessages.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +40 -7
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useSession.js +0 -2
- package/dist/cli/hooks/useSession.js.map +1 -1
- package/dist/cli/kraken/evidenceAnchoring.test.js +92 -0
- package/dist/cli/kraken/evidenceAnchoring.test.js.map +1 -0
- package/dist/cli/kraken/nativeVerification.js +113 -0
- package/dist/cli/kraken/nativeVerification.js.map +1 -0
- package/dist/cli/kraken/nativeVerification.test.js +207 -0
- package/dist/cli/kraken/nativeVerification.test.js.map +1 -0
- package/dist/cli/kraken/strictDefaults.test.js +149 -0
- package/dist/cli/kraken/strictDefaults.test.js.map +1 -0
- package/dist/cli/kraken/verificationBridge.js +121 -8
- package/dist/cli/kraken/verificationBridge.js.map +1 -1
- package/dist/cli/kraken/verificationBridge.session.test.js +104 -0
- package/dist/cli/kraken/verificationBridge.session.test.js.map +1 -0
- package/dist/cli/kraken/verificationBridge.test.js +63 -17
- package/dist/cli/kraken/verificationBridge.test.js.map +1 -1
- package/dist/cli/kraken/verifierAdvisoryLock.test.js +154 -0
- package/dist/cli/kraken/verifierAdvisoryLock.test.js.map +1 -0
- package/dist/cli/kraken/verifierResolution.js +28 -0
- package/dist/cli/kraken/verifierResolution.js.map +1 -0
- package/dist/cli/kraken/verifierRoundTrip.test.js +166 -0
- package/dist/cli/kraken/verifierRoundTrip.test.js.map +1 -0
- package/dist/cli/legacyContextIsolation.test.js +65 -0
- package/dist/cli/legacyContextIsolation.test.js.map +1 -0
- package/dist/cli/main.bundled.js +893 -317
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/missionContinuation.test.js +117 -0
- package/dist/cli/missionContinuation.test.js.map +1 -0
- package/dist/cli/profileMatrix.test.js +130 -0
- package/dist/cli/profileMatrix.test.js.map +1 -0
- package/dist/cli/runHeadless.js +68 -8
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/sessionManager.js +7 -0
- package/dist/cli/sessionManager.js.map +1 -1
- package/dist/cli/sessionSpine.js +43 -3
- package/dist/cli/sessionSpine.js.map +1 -1
- package/dist/cli/zelariMission.js +16 -0
- package/dist/cli/zelariMission.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F4 integration — the mission loop records advisory continuation without
|
|
3
|
+
* obeying it: advice on every implementation slice, deterministic stop rules
|
|
4
|
+
* untouched, spine round-trip of mission.progress state events.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { runZelariMission, } from './zelariMission.js';
|
|
8
|
+
import { SessionStore, readSessionLog, buildProjection, } from '@zelari/core/session';
|
|
9
|
+
import { deriveMissionState } from '@zelari/core/mission';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import { promises as fs } from 'node:fs';
|
|
13
|
+
const brief = {
|
|
14
|
+
goal: 'vertical slice',
|
|
15
|
+
deliverableThisMission: 'one module',
|
|
16
|
+
phases: [{ mode: 'implementation', focus: 'impl' }],
|
|
17
|
+
sliceMvp: { id: 'slice-1', summary: 'mvp', acceptance: [] },
|
|
18
|
+
};
|
|
19
|
+
function fakeMemory() {
|
|
20
|
+
return {
|
|
21
|
+
init: vi.fn(async () => { }),
|
|
22
|
+
add: vi.fn(async () => { }),
|
|
23
|
+
search: vi.fn(async () => []),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function deps(overrides = {}) {
|
|
27
|
+
return {
|
|
28
|
+
projectRoot: os.tmpdir(),
|
|
29
|
+
memory: fakeMemory(),
|
|
30
|
+
runSlice: vi.fn(async () => ({ completionOk: true, ran: true, writeCount: 2 })),
|
|
31
|
+
emit: vi.fn(),
|
|
32
|
+
env: { ZELARI_CHECKPOINT: '0', ZELARI_MISSION_MAX_ITER: '3' },
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
describe('runZelariMission — advisory continuation (F4)', () => {
|
|
37
|
+
it('records advice on each implementation slice but never obeys it for done-by-score', async () => {
|
|
38
|
+
const onMissionProgress = vi.fn();
|
|
39
|
+
// Slice 1: claims completion but wrote 0 files → NOT done.
|
|
40
|
+
// Slice 2: real green → done. The loop must follow its own deterministic
|
|
41
|
+
// rules; the advice is only recorded.
|
|
42
|
+
const results = [
|
|
43
|
+
{ completionOk: true, ran: true, writeCount: 0 },
|
|
44
|
+
{ completionOk: true, ran: true, writeCount: 3 },
|
|
45
|
+
];
|
|
46
|
+
let i = 0;
|
|
47
|
+
const d = deps({
|
|
48
|
+
runSlice: vi.fn(async () => results[i++]),
|
|
49
|
+
onMissionProgress,
|
|
50
|
+
});
|
|
51
|
+
const state = await runZelariMission('do it', brief, d);
|
|
52
|
+
expect(state.status).toBe('success');
|
|
53
|
+
expect(state.iteration).toBeGreaterThanOrEqual(2);
|
|
54
|
+
expect(onMissionProgress).toHaveBeenCalled();
|
|
55
|
+
const first = onMissionProgress.mock.calls[0][0];
|
|
56
|
+
expect(first.doneByScore).toBe(false);
|
|
57
|
+
expect(first.goalRewrite).toBe(false);
|
|
58
|
+
expect(['continue', 'wind-down', 'hold-for-user']).toContain(first.recommendation);
|
|
59
|
+
});
|
|
60
|
+
it('emits a wind-down advice only when the deterministic gate is green', async () => {
|
|
61
|
+
const seen = [];
|
|
62
|
+
const d = deps({
|
|
63
|
+
runSlice: vi.fn(async () => ({ completionOk: true, ran: true, writeCount: 1 })),
|
|
64
|
+
onMissionProgress: (advice) => seen.push(advice.recommendation),
|
|
65
|
+
});
|
|
66
|
+
const state = await runZelariMission('go', brief, d);
|
|
67
|
+
expect(state.status).toBe('success');
|
|
68
|
+
expect(seen[seen.length - 1]).toBe('wind-down');
|
|
69
|
+
});
|
|
70
|
+
it('budget-exhausted run holds for the user instead of claiming done', async () => {
|
|
71
|
+
const onMissionProgress = vi.fn();
|
|
72
|
+
const d = deps({
|
|
73
|
+
runSlice: vi.fn(async () => ({ completionOk: false, ran: true, writeCount: 1 })),
|
|
74
|
+
onMissionProgress,
|
|
75
|
+
});
|
|
76
|
+
const state = await runZelariMission('go', brief, d);
|
|
77
|
+
expect(state.status).toBe('stopped');
|
|
78
|
+
const last = onMissionProgress.mock.calls.at(-1)[0];
|
|
79
|
+
expect(['hold-for-user', 'continue']).toContain(last.recommendation);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('mission.progress spine round-trip', () => {
|
|
83
|
+
it('advice events replay as state (lastAdvice) and never join the model surface', async () => {
|
|
84
|
+
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'f4-'));
|
|
85
|
+
const store = new SessionStore(tmp);
|
|
86
|
+
const { sessionId, writer } = await store.create({ profile: 'mission/v1' });
|
|
87
|
+
await writer.append({
|
|
88
|
+
kind: 'mission.phase',
|
|
89
|
+
actor: { type: 'system' },
|
|
90
|
+
data: { phase: 'build' },
|
|
91
|
+
});
|
|
92
|
+
await writer.append({
|
|
93
|
+
kind: 'mission.progress',
|
|
94
|
+
actor: { type: 'system' },
|
|
95
|
+
data: {
|
|
96
|
+
recommendation: 'continue',
|
|
97
|
+
rationale: 'required criteria incomplete → no early-stop',
|
|
98
|
+
blockers: ['1/1 required criteria not passing'],
|
|
99
|
+
iteration: 2,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
await writer.close();
|
|
103
|
+
const report = await readSessionLog(path.join(tmp, sessionId, 'events.jsonl'));
|
|
104
|
+
const events = report.events;
|
|
105
|
+
const projection = buildProjection(events, report.issues);
|
|
106
|
+
expect(projection.missionAdvice).toHaveLength(1);
|
|
107
|
+
expect(projection.missionAdvice[0].recommendation).toBe('continue');
|
|
108
|
+
const state = deriveMissionState(projection);
|
|
109
|
+
expect(state.phase).toBe('build');
|
|
110
|
+
expect(state.lastAdvice?.rationale).toContain('no early-stop');
|
|
111
|
+
const { isModelSurfaceEvent } = await import('@zelari/core/session');
|
|
112
|
+
const adviceEvent = events.find((e) => e.kind === 'mission.progress');
|
|
113
|
+
expect(adviceEvent).toBeDefined();
|
|
114
|
+
expect(isModelSurfaceEvent(adviceEvent)).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=missionContinuation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"missionContinuation.test.js","sourceRoot":"","sources":["../../src/cli/missionContinuation.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* profileMatrix — F7 (Exit-3.2, doc §10) profile × phase smoke matrix.
|
|
3
|
+
*
|
|
4
|
+
* Cells (§10 minimum): minimal+plan/build, kraken+plan/build, council+plan,
|
|
5
|
+
* mission+build. For each cell the smoke walks the three real wiring legs:
|
|
6
|
+
*
|
|
7
|
+
* 1. profile loader — mode/--profile resolves to the expected versioned
|
|
8
|
+
* profile (ADR-0022); explicit ids win over mode defaults
|
|
9
|
+
* 2. session metadata — the spine `session.started` header records profile +
|
|
10
|
+
* toolManifestHash of the declared profile, so same-task/same-profile
|
|
11
|
+
* runs stay comparable
|
|
12
|
+
* 3. capability gate — the REAL registry built for that phase: plan strips
|
|
13
|
+
* every PLAN_BLOCKED_TOOLS mutator while keeping the plan-domain tools
|
|
14
|
+
* (task_create/update/list + explore-only `task`); build registers the
|
|
15
|
+
* full mutator set; declared profile tools are a subset of the build
|
|
16
|
+
* registry (declarative 2.0 manifest ⊆ real 1.x registry)
|
|
17
|
+
*
|
|
18
|
+
* Wiring invariants that live inside runHeadless (mission slices never run in
|
|
19
|
+
* plan; kraken/council registries derive planMode from the phase) are locked
|
|
20
|
+
* as source assertions — same pattern as legacyContextIsolation.test.ts.
|
|
21
|
+
*/
|
|
22
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
23
|
+
import { promises as fs } from 'node:fs';
|
|
24
|
+
import { readFileSync } from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { readSessionLog } from '@zelari/core/session';
|
|
29
|
+
import { MINIMAL_V1, KRAKEN_V1, MISSION_V1, toolManifestHash } from '@zelari/core/runtime';
|
|
30
|
+
import { openHeadlessSpine, resolveHeadlessProfileId } from './headlessSpine.js';
|
|
31
|
+
import { createBuiltinToolRegistry } from './toolRegistry.js';
|
|
32
|
+
import { PLAN_BLOCKED_TOOLS } from './phase.js';
|
|
33
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
34
|
+
function readCli(rel) {
|
|
35
|
+
return readFileSync(path.join(REPO_ROOT, 'src', 'cli', rel), 'utf8');
|
|
36
|
+
}
|
|
37
|
+
const MATRIX = [
|
|
38
|
+
{ mode: 'kraken', profile: 'minimal/v1', explicitProfile: 'minimal/v1', phase: 'plan', declaredTools: MINIMAL_V1.tools },
|
|
39
|
+
{ mode: 'kraken', profile: 'minimal/v1', explicitProfile: 'minimal/v1', phase: 'build', declaredTools: MINIMAL_V1.tools },
|
|
40
|
+
{ mode: 'kraken', profile: 'kraken/v1', phase: 'plan', declaredTools: KRAKEN_V1.tools },
|
|
41
|
+
{ mode: 'kraken', profile: 'kraken/v1', phase: 'build', declaredTools: KRAKEN_V1.tools },
|
|
42
|
+
{ mode: 'council', profile: 'council/v1', phase: 'plan', declaredTools: [...MINIMAL_V1.tools, 'write_file'] },
|
|
43
|
+
// §10 + mission contract: mission runs BUILD only (slices implement on disk).
|
|
44
|
+
{ mode: 'zelari', profile: 'mission/v1', phase: 'build', declaredTools: MISSION_V1.tools },
|
|
45
|
+
];
|
|
46
|
+
let tmp;
|
|
47
|
+
beforeEach(async () => {
|
|
48
|
+
tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'zelari-profile-matrix-'));
|
|
49
|
+
});
|
|
50
|
+
afterEach(async () => {
|
|
51
|
+
await fs.rm(tmp, { recursive: true, force: true });
|
|
52
|
+
});
|
|
53
|
+
describe('F7 profile × phase smoke matrix (Exit-3.2)', () => {
|
|
54
|
+
it.each(MATRIX)('cell $mode/$phase → $profile: loader, session metadata and phase capability gate', async (cell) => {
|
|
55
|
+
// Leg 1 — profile loader: explicit id wins, otherwise the mode default.
|
|
56
|
+
expect(resolveHeadlessProfileId(cell.mode, cell.explicitProfile)).toBe(cell.profile);
|
|
57
|
+
// Leg 2 — session metadata on the spine header.
|
|
58
|
+
const sessionId = `mx-${cell.mode}-${cell.phase}`;
|
|
59
|
+
const handle = await openHeadlessSpine({ sessionId, mode: cell.mode, profile: cell.explicitProfile, baseDir: tmp, quiet: true });
|
|
60
|
+
expect(handle.profileId).toBe(cell.profile);
|
|
61
|
+
const report = await readSessionLog(path.join(tmp, sessionId, 'events.jsonl'));
|
|
62
|
+
expect(report.ok).toBe(true);
|
|
63
|
+
const started = report.events.find((e) => e.kind === 'session.started');
|
|
64
|
+
expect(started).toBeDefined();
|
|
65
|
+
const startedData = (started?.data ?? {});
|
|
66
|
+
expect(startedData.profile).toBe(cell.profile);
|
|
67
|
+
// The manifest hash is the DECLARED profile tool set (ADR-0022) — the
|
|
68
|
+
// comparability contract, distinct from the phase-gated live registry.
|
|
69
|
+
expect(startedData.toolManifestHash).toBe(toolManifestHash(cell.declaredTools));
|
|
70
|
+
// Leg 3 — real registry for the phase (no tool is invoked: zero side effects).
|
|
71
|
+
const { registry } = createBuiltinToolRegistry({
|
|
72
|
+
root: tmp,
|
|
73
|
+
sessionId,
|
|
74
|
+
planMode: cell.phase === 'plan',
|
|
75
|
+
lifecycleHooks: null,
|
|
76
|
+
lspProvider: null,
|
|
77
|
+
});
|
|
78
|
+
const names = new Set(registry.list());
|
|
79
|
+
// Observe builtins exist in BOTH phases.
|
|
80
|
+
for (const observe of ['read_file', 'grep_content', 'list_files']) {
|
|
81
|
+
expect(names.has(observe), `${observe} must exist in ${cell.phase}`).toBe(true);
|
|
82
|
+
}
|
|
83
|
+
if (cell.phase === 'plan') {
|
|
84
|
+
// No mutating builtin survives plan (no unauthorized side effects).
|
|
85
|
+
for (const blocked of PLAN_BLOCKED_TOOLS) {
|
|
86
|
+
expect(names.has(blocked), `${blocked} must be stripped in plan`).toBe(false);
|
|
87
|
+
}
|
|
88
|
+
// Plan-domain tools stay: durable plan tasks + explore-only delegation.
|
|
89
|
+
for (const planTool of ['task_create', 'task_update', 'task_list', 'task']) {
|
|
90
|
+
expect(names.has(planTool), `${planTool} must exist in plan`).toBe(true);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Build: the full mutator set is available.
|
|
95
|
+
for (const mutator of ['write_file', 'edit_file', 'apply_diff', 'bash', 'task']) {
|
|
96
|
+
expect(names.has(mutator), `${mutator} must exist in build`).toBe(true);
|
|
97
|
+
}
|
|
98
|
+
// Declared 2.0 profile tools are a subset of the real build registry.
|
|
99
|
+
for (const declared of cell.declaredTools) {
|
|
100
|
+
expect(names.has(declared), `declared tool ${declared} missing from build registry`).toBe(true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}, 30_000);
|
|
104
|
+
});
|
|
105
|
+
describe('F7 wiring invariants (source-level, legacyContextIsolation pattern)', () => {
|
|
106
|
+
it('kraken and council registries derive planMode from the resolved phase', () => {
|
|
107
|
+
const src = readCli('runHeadless.ts');
|
|
108
|
+
// Kraken parent registry (headless hot path).
|
|
109
|
+
expect(src).toContain('planMode: planModeFromOpts(opts),');
|
|
110
|
+
// Council pipelines: explicit phase (design leg) and soft-gated variant.
|
|
111
|
+
expect(src).toContain('planModeFromOpts(opts) || softGated,');
|
|
112
|
+
expect(src).toContain('planModeFromOpts(opts),');
|
|
113
|
+
// planModeFromOpts is the single phase→planMode derivation.
|
|
114
|
+
expect(src).toContain("return (opts.phase ?? 'build') === 'plan';");
|
|
115
|
+
});
|
|
116
|
+
it('mission implementation slices are hard-wired to BUILD (never planMode)', () => {
|
|
117
|
+
const src = readCli('runHeadless.ts');
|
|
118
|
+
const lines = src.split(/\r?\n/);
|
|
119
|
+
const idx = lines.findIndex((l) => l.includes('const { registry: agentRegistry } = createBuiltinToolRegistry({'));
|
|
120
|
+
expect(idx).toBeGreaterThan(-1);
|
|
121
|
+
const window = lines.slice(idx, idx + 12).join('\n');
|
|
122
|
+
expect(window).toContain('planMode: false');
|
|
123
|
+
expect(window).not.toContain('planMode: true');
|
|
124
|
+
});
|
|
125
|
+
it('PLAN_BLOCKED_TOOLS stays exactly the mutating builtins', () => {
|
|
126
|
+
// If a new mutator lands without a plan gate, this reminds the author.
|
|
127
|
+
expect([...PLAN_BLOCKED_TOOLS].sort()).toEqual(['apply_diff', 'bash', 'edit_file', 'write_file']);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
//# sourceMappingURL=profileMatrix.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profileMatrix.test.js","sourceRoot":"","sources":["../../src/cli/profileMatrix.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/runHeadless.js
CHANGED
|
@@ -37,8 +37,8 @@ import { writeSessionTodos } from './sessionTodos.js';
|
|
|
37
37
|
import { promises as fs } from 'node:fs';
|
|
38
38
|
import path from 'node:path';
|
|
39
39
|
import { randomUUID } from 'node:crypto';
|
|
40
|
-
import { evaluateStrictBuildGate, strictGateEventPayload } from './kraken/verificationBridge.js';
|
|
41
|
-
import { openHeadlessSpine, resolveHeadlessProfileId, seedHeadlessModelHistory, } from './headlessSpine.js';
|
|
40
|
+
import { evaluateStrictBuildGate, strictGateEventPayload, strictGateExitCode } from './kraken/verificationBridge.js';
|
|
41
|
+
import { openHeadlessSpine, resolveHeadlessProfileId, seedHeadlessModelHistory, sessionStartedEvent, } from './headlessSpine.js';
|
|
42
42
|
export async function runHeadless(opts) {
|
|
43
43
|
resetTaskSpawnCount();
|
|
44
44
|
// Desktop multi-turn todo persistence: each message spawns a fresh process,
|
|
@@ -332,6 +332,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
332
332
|
emitEvent({ type: 'message_delta', delta: finalAscii });
|
|
333
333
|
emitEvent({ type: 'message_end' });
|
|
334
334
|
emitEvent({ type: 'agent_end', reason: summary.converged ? 'completed' : 'error' });
|
|
335
|
+
// COMPAT MIRROR (ADR-0024): the session spine is the canonical context;
|
|
336
|
+
// history_snapshot is one-shot legacy-host replay only (F13 cleanup, doc §8).
|
|
335
337
|
emitEvent({
|
|
336
338
|
type: 'history_snapshot',
|
|
337
339
|
messages: [
|
|
@@ -417,6 +419,9 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
417
419
|
// are then derived from events. The 1.x rolling history no longer feeds
|
|
418
420
|
// the harness messages directly (degraded spine falls back to it).
|
|
419
421
|
const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
|
|
422
|
+
// E1.4: advertise the spine session id so hosts (Desktop) resume the
|
|
423
|
+
// same event log next turn instead of replaying 1.x history JSON.
|
|
424
|
+
emitEvent(sessionStartedEvent(spine));
|
|
420
425
|
if (opts.task)
|
|
421
426
|
spine.userMessage(opts.task);
|
|
422
427
|
// Fase 3 (ADR-0020): fresh per-run candidate registry (each headless run
|
|
@@ -785,7 +790,10 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
785
790
|
emittedWrites: pass.emittedWrites + retry.emittedWrites,
|
|
786
791
|
};
|
|
787
792
|
}
|
|
788
|
-
//
|
|
793
|
+
// E2.2: when strict mode is on and the gate stays blocked after the repair
|
|
794
|
+
// pass, the run closes non-success (dedicated exit code + session status).
|
|
795
|
+
let strictExit = 0;
|
|
796
|
+
// Fase 8 (ADR-0020): completion gate — a BUILD turn that used selection // Fase 8 (ADR-0020): completion gate — a BUILD turn that used selection
|
|
789
797
|
// cannot cleanly finish while required checks are unresolved (fail OR
|
|
790
798
|
// unknown — a degraded observation is never proof). One automatic
|
|
791
799
|
// repair pass (budget = 1, structural), reusing the same recovery
|
|
@@ -795,7 +803,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
795
803
|
opts.mode === 'kraken' &&
|
|
796
804
|
isKrakenSelectionEnabled() &&
|
|
797
805
|
!planModeFromOpts(opts)) {
|
|
798
|
-
const strictGate = evaluateStrictBuildGate('build');
|
|
806
|
+
const strictGate = await evaluateStrictBuildGate('build', { emit: (input) => spine.appendEvent(input) });
|
|
799
807
|
const gate = strictGate.gate;
|
|
800
808
|
const verificationPayload = strictGateEventPayload(strictGate);
|
|
801
809
|
spine.verificationRun(verificationPayload);
|
|
@@ -829,7 +837,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
829
837
|
successfulWrites: pass.successfulWrites + repair.successfulWrites,
|
|
830
838
|
emittedWrites: pass.emittedWrites + repair.emittedWrites,
|
|
831
839
|
};
|
|
832
|
-
const after = evaluateStrictBuildGate('build');
|
|
840
|
+
const after = await evaluateStrictBuildGate('build', { emit: (input) => spine.appendEvent(input) });
|
|
833
841
|
const afterPayload = strictGateEventPayload(after);
|
|
834
842
|
spine.verificationRun(afterPayload);
|
|
835
843
|
if (opts.output === 'json') {
|
|
@@ -837,6 +845,15 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
837
845
|
}
|
|
838
846
|
if (!after.blocked)
|
|
839
847
|
markRepairSucceeded();
|
|
848
|
+
else {
|
|
849
|
+
strictExit = strictGateExitCode(after);
|
|
850
|
+
const gateMsg = `[headless] Kraken BUILD: strict completion gate still blocked after repair pass — ` +
|
|
851
|
+
`closing non-success (exit ${strictExit}): ${after.summary}`;
|
|
852
|
+
if (opts.output === 'json')
|
|
853
|
+
emitEvent({ type: 'log', message: gateMsg });
|
|
854
|
+
else
|
|
855
|
+
process.stderr.write(`[zelari-code --headless] ${gateMsg}\n`);
|
|
856
|
+
}
|
|
840
857
|
}
|
|
841
858
|
}
|
|
842
859
|
progressRuntime.finish(pass.finalReason);
|
|
@@ -851,6 +868,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
851
868
|
}
|
|
852
869
|
process.stdout.write('');
|
|
853
870
|
// v1.10.0: emit a history_snapshot so the desktop can replay this turn.
|
|
871
|
+
// COMPAT MIRROR (ADR-0024): session spine is canonical — see emit sites below.
|
|
854
872
|
// Include the user turn + final assistant text (user/assistant only).
|
|
855
873
|
if (pass.finalReason !== 'error' && opts.output === 'json') {
|
|
856
874
|
try {
|
|
@@ -888,6 +906,8 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
888
906
|
: []),
|
|
889
907
|
];
|
|
890
908
|
if (snapshot.length > 0) {
|
|
909
|
+
// COMPAT MIRROR (ADR-0024): the session spine is the canonical context;
|
|
910
|
+
// history_snapshot is one-shot legacy-host replay only (F13 cleanup, doc §8).
|
|
891
911
|
emitEvent({ type: 'history_snapshot', messages: snapshot });
|
|
892
912
|
}
|
|
893
913
|
}
|
|
@@ -896,7 +916,8 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
896
916
|
}
|
|
897
917
|
}
|
|
898
918
|
try {
|
|
899
|
-
|
|
919
|
+
const closeStatus = pass.finalReason === 'error' ? 'error' : strictExit !== 0 ? 'stopped' : 'completed';
|
|
920
|
+
await spine.close(closeStatus);
|
|
900
921
|
}
|
|
901
922
|
catch { /* spine never fails the run */ }
|
|
902
923
|
if (opts.exportSessionPath) {
|
|
@@ -915,6 +936,9 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
915
936
|
}
|
|
916
937
|
if (pass.finalReason === 'error')
|
|
917
938
|
return 3;
|
|
939
|
+
// E2.2: strict done gate — a blocked verdict overrides a clean pass exit.
|
|
940
|
+
if (strictExit !== 0)
|
|
941
|
+
return strictExit;
|
|
918
942
|
return pass.exitCode;
|
|
919
943
|
}
|
|
920
944
|
async function buildCouncilToolRegistry(planMode, opts) {
|
|
@@ -959,6 +983,9 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
959
983
|
// are then derived from events. The 1.x rolling history no longer feeds
|
|
960
984
|
// the harness messages directly (degraded spine falls back to it).
|
|
961
985
|
const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
|
|
986
|
+
// E1.4: advertise the spine session id so hosts (Desktop) resume the
|
|
987
|
+
// same event log next turn instead of replaying 1.x history JSON.
|
|
988
|
+
emitEvent(sessionStartedEvent(spine));
|
|
962
989
|
if (opts.task)
|
|
963
990
|
spine.userMessage(opts.task);
|
|
964
991
|
// Experiment: free-form council+build soft-gated to design-phase unless
|
|
@@ -1078,6 +1105,8 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
1078
1105
|
? [{ role: 'assistant', content: lastAssistantText }]
|
|
1079
1106
|
: []),
|
|
1080
1107
|
];
|
|
1108
|
+
// COMPAT MIRROR (ADR-0024): the session spine is the canonical context;
|
|
1109
|
+
// history_snapshot is one-shot legacy-host replay only (F13 cleanup, doc §8).
|
|
1081
1110
|
emitEvent({ type: 'history_snapshot', messages: snapshot });
|
|
1082
1111
|
}
|
|
1083
1112
|
catch {
|
|
@@ -1121,6 +1150,9 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1121
1150
|
// are then derived from events. The 1.x rolling history no longer feeds
|
|
1122
1151
|
// the harness messages directly (degraded spine falls back to it).
|
|
1123
1152
|
const seededHistory = await seedHeadlessModelHistory(spine, opts.history);
|
|
1153
|
+
// E1.4: advertise the spine session id so hosts (Desktop) resume the
|
|
1154
|
+
// same event log next turn instead of replaying 1.x history JSON.
|
|
1155
|
+
emitEvent(sessionStartedEvent(spine));
|
|
1124
1156
|
if (opts.task)
|
|
1125
1157
|
spine.userMessage(opts.task);
|
|
1126
1158
|
spine.missionPhase('design', 'mission-start');
|
|
@@ -1184,6 +1216,13 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1184
1216
|
emit,
|
|
1185
1217
|
buildViaAgent,
|
|
1186
1218
|
onMissionPhase: (phase, note) => spine.missionPhase(phase, note),
|
|
1219
|
+
onMissionProgress: (advice, iteration) => spine.missionProgress({
|
|
1220
|
+
recommendation: advice.recommendation,
|
|
1221
|
+
rationale: advice.rationale,
|
|
1222
|
+
blockers: advice.blockers,
|
|
1223
|
+
...(advice.trend ? { trend: advice.trend } : {}),
|
|
1224
|
+
iteration,
|
|
1225
|
+
}),
|
|
1187
1226
|
runSlice: async ({ userMessage: slicePrompt, runMode, ragContext, implementerRetry, }) => {
|
|
1188
1227
|
const effectiveRunMode = planModeFromOpts(opts) ? 'design-phase' : runMode;
|
|
1189
1228
|
// design-phase always council; implementation uses agent when policy ON
|
|
@@ -1397,8 +1436,27 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1397
1436
|
if (state.status === 'error')
|
|
1398
1437
|
exitCode = exitCode || 3;
|
|
1399
1438
|
else if (state.status === 'success') {
|
|
1400
|
-
|
|
1401
|
-
|
|
1439
|
+
// ADR-0025: missions close under the strict evidence gate by default
|
|
1440
|
+
// (opt-out: ZELARI_MISSION_STRICT=0). A blocked gate never exits 0 —
|
|
1441
|
+
// the mission "success" becomes the strict exit code and the spine
|
|
1442
|
+
// records mission-strict-blocked instead of mission-success.
|
|
1443
|
+
const missionGate = await evaluateStrictBuildGate('build', {
|
|
1444
|
+
emit: (input) => spine.appendEvent(input),
|
|
1445
|
+
surface: 'mission',
|
|
1446
|
+
});
|
|
1447
|
+
const missionVerificationPayload = strictGateEventPayload(missionGate);
|
|
1448
|
+
spine.verificationRun(missionVerificationPayload);
|
|
1449
|
+
if (opts.output === 'json') {
|
|
1450
|
+
emitEvent({ type: 'verification_run', ...missionVerificationPayload });
|
|
1451
|
+
}
|
|
1452
|
+
if (missionGate.blocked) {
|
|
1453
|
+
exitCode = strictGateExitCode(missionGate);
|
|
1454
|
+
spine.missionPhase('verification', 'mission-strict-blocked');
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
exitCode = 0;
|
|
1458
|
+
spine.missionPhase('done', 'mission-success');
|
|
1459
|
+
}
|
|
1402
1460
|
}
|
|
1403
1461
|
else if (state.status === 'stalled' || state.status === 'stopped') {
|
|
1404
1462
|
exitCode = exitCode || 0;
|
|
@@ -1439,6 +1497,8 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1439
1497
|
}
|
|
1440
1498
|
if (opts.output === 'json') {
|
|
1441
1499
|
try {
|
|
1500
|
+
// COMPAT MIRROR (ADR-0024): the session spine is the canonical context;
|
|
1501
|
+
// history_snapshot is one-shot legacy-host replay only (F13 cleanup, doc §8).
|
|
1442
1502
|
emitEvent({
|
|
1443
1503
|
type: 'history_snapshot',
|
|
1444
1504
|
messages: [
|