zelari-code 1.48.1 → 2.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/app.js +4 -1
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/desktopConfig.js +51 -4
- package/dist/cli/desktopConfig.js.map +1 -1
- package/dist/cli/headless.js +65 -0
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/headlessSpine.js +102 -0
- package/dist/cli/headlessSpine.js.map +1 -0
- package/dist/cli/headlessSpine.test.js +153 -0
- package/dist/cli/headlessSpine.test.js.map +1 -0
- package/dist/cli/hooks/useChatTurn.js +85 -0
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useSession.js +16 -2
- package/dist/cli/hooks/useSession.js.map +1 -1
- package/dist/cli/kraken/candidateRegistry.js +195 -0
- package/dist/cli/kraken/candidateRegistry.js.map +1 -0
- package/dist/cli/kraken/candidateRegistry.test.js +211 -0
- package/dist/cli/kraken/candidateRegistry.test.js.map +1 -0
- package/dist/cli/kraken/completionGate.js +116 -0
- package/dist/cli/kraken/completionGate.js.map +1 -0
- package/dist/cli/kraken/completionGate.test.js +172 -0
- package/dist/cli/kraken/completionGate.test.js.map +1 -0
- package/dist/cli/kraken/metrics.js +96 -0
- package/dist/cli/kraken/metrics.js.map +1 -0
- package/dist/cli/kraken/metrics.test.js +145 -0
- package/dist/cli/kraken/metrics.test.js.map +1 -0
- package/dist/cli/kraken/selectionPlaybook.js +23 -0
- package/dist/cli/kraken/selectionPlaybook.js.map +1 -0
- package/dist/cli/kraken/selectionPlaybook.test.js +55 -0
- package/dist/cli/kraken/selectionPlaybook.test.js.map +1 -0
- package/dist/cli/kraken/turnRuntime.js +207 -0
- package/dist/cli/kraken/turnRuntime.js.map +1 -0
- package/dist/cli/kraken/turnRuntime.test.js +377 -0
- package/dist/cli/kraken/turnRuntime.test.js.map +1 -0
- package/dist/cli/kraken/verificationBridge.js +153 -0
- package/dist/cli/kraken/verificationBridge.js.map +1 -0
- package/dist/cli/kraken/verificationBridge.test.js +131 -0
- package/dist/cli/kraken/verificationBridge.test.js.map +1 -0
- package/dist/cli/kraken/verifier.js +233 -0
- package/dist/cli/kraken/verifier.js.map +1 -0
- package/dist/cli/kraken/verifier.test.js +232 -0
- package/dist/cli/kraken/verifier.test.js.map +1 -0
- package/dist/cli/kraken/verifierSettings.test.js +237 -0
- package/dist/cli/kraken/verifierSettings.test.js.map +1 -0
- package/dist/cli/kraken/verifyReport.js +115 -0
- package/dist/cli/kraken/verifyReport.js.map +1 -0
- package/dist/cli/kraken/verifyReport.test.js +111 -0
- package/dist/cli/kraken/verifyReport.test.js.map +1 -0
- package/dist/cli/main.bundled.js +4676 -943
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +29 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/providerConfig.js +46 -0
- package/dist/cli/providerConfig.js.map +1 -1
- package/dist/cli/runHeadless.js +198 -4
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/sessionSpine.js +272 -0
- package/dist/cli/sessionSpine.js.map +1 -0
- package/dist/cli/sessionSpine.test.js +171 -0
- package/dist/cli/sessionSpine.test.js.map +1 -0
- package/dist/cli/toolRegistry.js +50 -6
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/krakenSelectTool.js +165 -0
- package/dist/cli/tools/krakenSelectTool.js.map +1 -0
- package/dist/cli/tools/krakenSelectTool.test.js +246 -0
- package/dist/cli/tools/krakenSelectTool.test.js.map +1 -0
- package/dist/cli/tools/taskTool.dynamicChecks.test.js +156 -0
- package/dist/cli/tools/taskTool.dynamicChecks.test.js.map +1 -0
- package/dist/cli/tools/taskTool.js +174 -6
- package/dist/cli/tools/taskTool.js.map +1 -1
- package/dist/cli/tools/taskTool.planSafety.test.js +280 -0
- package/dist/cli/tools/taskTool.planSafety.test.js.map +1 -0
- package/dist/cli/tools/taskTool.verifyReport.test.js +140 -0
- package/dist/cli/tools/taskTool.verifyReport.test.js.map +1 -0
- package/dist/cli/zelariMission.js +18 -8
- package/dist/cli/zelariMission.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { SessionLogWriter, SessionLogLockedError, resolveSessionsDir, readSessionLog, buildProjection, deriveMessages, } from '@zelari/core/session';
|
|
2
|
+
import { ACTOR_AGENT, ACTOR_SYSTEM, ACTOR_USER } from '@zelari/core/session';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/** Kill switch — default ON in the 2.0 alpha. */
|
|
5
|
+
export function spineEnabled() {
|
|
6
|
+
return process.env.ZELARI_SESSION_SPINE !== '0';
|
|
7
|
+
}
|
|
8
|
+
/** Map one BrainEvent onto a spine event; null when not spine-relevant. */
|
|
9
|
+
export function mapBrainEventToSpine(ev) {
|
|
10
|
+
switch (ev.type) {
|
|
11
|
+
case 'tool_execution_start':
|
|
12
|
+
return {
|
|
13
|
+
kind: 'tool.call',
|
|
14
|
+
actor: ACTOR_AGENT,
|
|
15
|
+
data: { tool: ev.toolName, args: ev.args ?? {}, callId: ev.toolCallId },
|
|
16
|
+
};
|
|
17
|
+
case 'tool_execution_end':
|
|
18
|
+
return {
|
|
19
|
+
kind: 'tool.result',
|
|
20
|
+
actor: { type: 'tool' },
|
|
21
|
+
data: {
|
|
22
|
+
callId: ev.toolCallId,
|
|
23
|
+
output: ev.result,
|
|
24
|
+
ok: !ev.isError,
|
|
25
|
+
durationMs: ev.durationMs,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
case 'session_compacted':
|
|
29
|
+
return {
|
|
30
|
+
kind: 'session.compacted',
|
|
31
|
+
actor: ACTOR_SYSTEM,
|
|
32
|
+
data: { summary: ev.summary ?? '' },
|
|
33
|
+
};
|
|
34
|
+
case 'agent_start':
|
|
35
|
+
return {
|
|
36
|
+
kind: 'note',
|
|
37
|
+
actor: ACTOR_SYSTEM,
|
|
38
|
+
data: {
|
|
39
|
+
note: 'agent_start',
|
|
40
|
+
model: ev.model,
|
|
41
|
+
provider: ev.provider,
|
|
42
|
+
...(ev.memberName ? { member: ev.memberName } : {}),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
default:
|
|
46
|
+
// message deltas are coalesced at message_end by the mirror;
|
|
47
|
+
// ui/progress/metrics events are not part of the spine vocabulary.
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const MAX_STREAM_BUFFERS = 32;
|
|
52
|
+
/**
|
|
53
|
+
* Owns the spine writer for one session. All appends are serialized; every
|
|
54
|
+
* public method swallows errors (degrade-and-stop) — the spine must never
|
|
55
|
+
* take the interactive loop down with it.
|
|
56
|
+
*/
|
|
57
|
+
export class SessionSpineMirror {
|
|
58
|
+
sessionId;
|
|
59
|
+
options;
|
|
60
|
+
writer = null;
|
|
61
|
+
chain = Promise.resolve();
|
|
62
|
+
streamBuffers = new Map();
|
|
63
|
+
warned = false;
|
|
64
|
+
status = 'disabled';
|
|
65
|
+
/** Seq the log continued from when adopting an existing session. */
|
|
66
|
+
resumedFromSeq;
|
|
67
|
+
sessionsDir;
|
|
68
|
+
constructor(sessionId, options) {
|
|
69
|
+
this.sessionId = sessionId;
|
|
70
|
+
this.options = options;
|
|
71
|
+
this.sessionsDir = resolveSessionsDir({ baseDir: options.baseDir });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Adopt a 1.x sessionId into the spine: continue the seq when the log
|
|
75
|
+
* already exists (resume), otherwise start fresh with `session.started`.
|
|
76
|
+
* Returns the mirror (never throws — check `.status`).
|
|
77
|
+
*/
|
|
78
|
+
static async adopt(sessionId, options = {}) {
|
|
79
|
+
const mirror = new SessionSpineMirror(sessionId, options);
|
|
80
|
+
if (!spineEnabled())
|
|
81
|
+
return mirror;
|
|
82
|
+
try {
|
|
83
|
+
const sessionDir = path.join(mirror.sessionsDir, sessionId);
|
|
84
|
+
const report = await readSessionLog(path.join(sessionDir, 'events.jsonl'));
|
|
85
|
+
const existed = report.events.length > 0 || report.issues.length > 0;
|
|
86
|
+
const lastSeq = report.events[report.events.length - 1]?.seq ?? 0;
|
|
87
|
+
mirror.writer = await SessionLogWriter.open(sessionDir, sessionId, lastSeq + 1, {
|
|
88
|
+
now: options.now,
|
|
89
|
+
});
|
|
90
|
+
mirror.resumedFromSeq = existed ? lastSeq : undefined;
|
|
91
|
+
await mirror.append({
|
|
92
|
+
kind: existed ? 'session.resumed' : 'session.started',
|
|
93
|
+
actor: ACTOR_SYSTEM,
|
|
94
|
+
data: {
|
|
95
|
+
reason: existed ? 'host-resume' : 'host-bootstrap',
|
|
96
|
+
bridge: 'cli-dual-write',
|
|
97
|
+
continuedFromSeq: existed ? lastSeq : undefined,
|
|
98
|
+
replayIssues: report.issues.length || undefined,
|
|
99
|
+
...options.extraStarted,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
mirror.status = 'active';
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
if (err instanceof SessionLogLockedError) {
|
|
106
|
+
mirror.status = 'locked';
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
mirror.status = 'degraded';
|
|
110
|
+
mirror.warnOnce(err);
|
|
111
|
+
}
|
|
112
|
+
mirror.writer = null;
|
|
113
|
+
}
|
|
114
|
+
return mirror;
|
|
115
|
+
}
|
|
116
|
+
/** Log the user prompt — the P1 gap the 1.x log never closed. */
|
|
117
|
+
userMessage(text) {
|
|
118
|
+
void this.append({ kind: 'user.message', actor: ACTOR_USER, data: { text } });
|
|
119
|
+
}
|
|
120
|
+
/** Mirror one BrainEvent (coalescing message deltas until message_end). */
|
|
121
|
+
mirrorBrainEvent(ev) {
|
|
122
|
+
if (this.status !== 'active' || !this.writer)
|
|
123
|
+
return;
|
|
124
|
+
if (ev.type === 'message_delta') {
|
|
125
|
+
const key = ev.messageId;
|
|
126
|
+
const prev = this.streamBuffers.get(key) ?? '';
|
|
127
|
+
this.streamBuffers.set(key, prev + ev.delta);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (ev.type === 'message_end') {
|
|
131
|
+
const text = this.streamBuffers.get(ev.messageId);
|
|
132
|
+
this.streamBuffers.delete(ev.messageId);
|
|
133
|
+
if (this.streamBuffers.size > MAX_STREAM_BUFFERS) {
|
|
134
|
+
this.streamBuffers.clear(); // defensive — bounded memory
|
|
135
|
+
}
|
|
136
|
+
// The buffered text is the full body; when the buffer is missing
|
|
137
|
+
// (e.g. mirror started mid-stream) fall back to nothing rather than
|
|
138
|
+
// logging a truncated claim.
|
|
139
|
+
if (text !== undefined && text.length > 0) {
|
|
140
|
+
void this.append({
|
|
141
|
+
kind: 'assistant.message',
|
|
142
|
+
actor: ACTOR_AGENT,
|
|
143
|
+
data: {
|
|
144
|
+
text,
|
|
145
|
+
messageId: ev.messageId,
|
|
146
|
+
finishReason: ev.finishReason,
|
|
147
|
+
...(ev.memberName ? { member: ev.memberName } : {}),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const mapped = mapBrainEventToSpine(ev);
|
|
154
|
+
if (mapped)
|
|
155
|
+
void this.append(mapped);
|
|
156
|
+
}
|
|
157
|
+
/** Append a `verification.run` event (machine-readable completion evidence). */
|
|
158
|
+
verificationRun(payload) {
|
|
159
|
+
void this.append({ kind: 'verification.run', actor: ACTOR_SYSTEM, data: payload });
|
|
160
|
+
}
|
|
161
|
+
/** Append a `mission.phase` transition (Fase 4 mission reliability). */
|
|
162
|
+
missionPhase(phase, note) {
|
|
163
|
+
void this.append({
|
|
164
|
+
kind: 'mission.phase',
|
|
165
|
+
actor: ACTOR_SYSTEM,
|
|
166
|
+
data: { phase, ...(note ? { note } : {}) },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
note(text, data) {
|
|
170
|
+
void this.append({ kind: 'note', actor: ACTOR_SYSTEM, data: { note: text, ...data } });
|
|
171
|
+
}
|
|
172
|
+
append(input) {
|
|
173
|
+
if (!this.writer || this.status === 'closed')
|
|
174
|
+
return Promise.resolve();
|
|
175
|
+
this.chain = this.chain
|
|
176
|
+
.then(() => this.writer.append(input))
|
|
177
|
+
.then(() => undefined)
|
|
178
|
+
.catch((err) => {
|
|
179
|
+
this.status = 'degraded';
|
|
180
|
+
this.writer = null;
|
|
181
|
+
this.warnOnce(err);
|
|
182
|
+
});
|
|
183
|
+
return this.chain;
|
|
184
|
+
}
|
|
185
|
+
warnOnce(err) {
|
|
186
|
+
if (this.warned || this.options.quiet)
|
|
187
|
+
return;
|
|
188
|
+
this.warned = true;
|
|
189
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
190
|
+
try {
|
|
191
|
+
process.stderr.write(`[zelari] session spine degraded for ${this.sessionId.slice(0, 8)}…: ${msg} (transcript continues on the 1.x log)\n`);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* stderr gone — nothing more to do */
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** Flush pending appends; append `session.ended` and release the lock. */
|
|
198
|
+
async close(reason = 'host-exit') {
|
|
199
|
+
if (this.status === 'active' && this.writer) {
|
|
200
|
+
await this.append({ kind: 'session.ended', actor: ACTOR_SYSTEM, data: { reason } });
|
|
201
|
+
}
|
|
202
|
+
await this.chain;
|
|
203
|
+
const writer = this.writer;
|
|
204
|
+
this.writer = null;
|
|
205
|
+
if (this.status !== 'closed')
|
|
206
|
+
this.status = 'closed';
|
|
207
|
+
if (writer)
|
|
208
|
+
await writer.close().catch(() => undefined);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Release the lock WITHOUT appending `session.ended`.
|
|
212
|
+
* Used on SIGINT / abort so deriveMissionState can project
|
|
213
|
+
* `interrupted: true` and a later adopt continues the seq.
|
|
214
|
+
*/
|
|
215
|
+
async release() {
|
|
216
|
+
await this.chain;
|
|
217
|
+
const writer = this.writer;
|
|
218
|
+
this.writer = null;
|
|
219
|
+
if (this.status === 'active')
|
|
220
|
+
this.status = 'closed';
|
|
221
|
+
if (writer)
|
|
222
|
+
await writer.close().catch(() => undefined);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Writer that dual-writes: 1.x sidecar + spine mirror. */
|
|
226
|
+
export class SpineMirroringWriter {
|
|
227
|
+
inner;
|
|
228
|
+
spine;
|
|
229
|
+
constructor(inner, spine) {
|
|
230
|
+
this.inner = inner;
|
|
231
|
+
this.spine = spine;
|
|
232
|
+
}
|
|
233
|
+
get path() {
|
|
234
|
+
return this.inner.path;
|
|
235
|
+
}
|
|
236
|
+
append(ev) {
|
|
237
|
+
const result = this.inner.append(ev);
|
|
238
|
+
this.spine?.mirrorBrainEvent(ev);
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
async flush() {
|
|
242
|
+
await this.inner.flush?.();
|
|
243
|
+
}
|
|
244
|
+
async close() {
|
|
245
|
+
await this.inner.close();
|
|
246
|
+
await this.spine?.close();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Bootstrap the dual-write writer for a TUI session: opens the spine mirror
|
|
251
|
+
* (best-effort) and wraps the 1.x writer so the chat loop needs no changes.
|
|
252
|
+
*/
|
|
253
|
+
export async function wrapSessionWriter(inner, sessionId, options = {}) {
|
|
254
|
+
const spine = await SessionSpineMirror.adopt(sessionId, options);
|
|
255
|
+
return new SpineMirroringWriter(inner, spine.status === 'active' ? spine : null);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Replay the spine for resume UX (headless + TUI status line). Returns null
|
|
259
|
+
* when the session has no spine log yet.
|
|
260
|
+
*/
|
|
261
|
+
export async function resumeSpineContext(sessionId, baseDir) {
|
|
262
|
+
const sessionsDir = resolveSessionsDir({ baseDir });
|
|
263
|
+
const eventsPath = path.join(sessionsDir, sessionId, 'events.jsonl');
|
|
264
|
+
const report = await readSessionLog(eventsPath).catch(() => null);
|
|
265
|
+
if (!report || (report.events.length === 0 && report.issues.length === 0))
|
|
266
|
+
return null;
|
|
267
|
+
return {
|
|
268
|
+
projection: buildProjection(report.events, report.issues),
|
|
269
|
+
derived: deriveMessages(report.events),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=sessionSpine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionSpine.js","sourceRoot":"","sources":["../../src/cli/sessionSpine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sessionSpine tests — dual-write bridge onto the 2.0 session spine.
|
|
3
|
+
*
|
|
4
|
+
* Covers: BrainEvent→spine mapping, adopt/resume seq continuation, ownership
|
|
5
|
+
* lock, message-delta coalescing, user.message logging (the P1 gap), the
|
|
6
|
+
* declared-discrete-fallback degradation, and the kill switch.
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
9
|
+
import { promises as fs } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import { SessionSpineMirror, mapBrainEventToSpine, resumeSpineContext, spineEnabled, } from './sessionSpine.js';
|
|
13
|
+
import { readSessionLog } from '@zelari/core/session';
|
|
14
|
+
let tmp;
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'zelari-spine-'));
|
|
17
|
+
});
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await fs.rm(tmp, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
function ev(type, rest = {}) {
|
|
22
|
+
return { type, id: `id-${type}`, ts: 1_755_000_000_000, sessionId: 's1', ...rest };
|
|
23
|
+
}
|
|
24
|
+
describe('mapBrainEventToSpine', () => {
|
|
25
|
+
it('maps tool_execution_start → tool.call with tool/args/callId', () => {
|
|
26
|
+
const mapped = mapBrainEventToSpine(ev('tool_execution_start', { toolCallId: 'c1', toolName: 'bash', args: { command: 'ls' } }));
|
|
27
|
+
expect(mapped).not.toBeNull();
|
|
28
|
+
expect(mapped.kind).toBe('tool.call');
|
|
29
|
+
expect(mapped.data).toMatchObject({ tool: 'bash', callId: 'c1' });
|
|
30
|
+
});
|
|
31
|
+
it('maps tool_execution_end → tool.result with ok inverted from isError', () => {
|
|
32
|
+
const mapped = mapBrainEventToSpine(ev('tool_execution_end', { toolCallId: 'c1', result: 'err', isError: true, durationMs: 5 }));
|
|
33
|
+
expect(mapped.kind).toBe('tool.result');
|
|
34
|
+
expect(mapped.data).toMatchObject({ callId: 'c1', ok: false });
|
|
35
|
+
});
|
|
36
|
+
it('drops ui/progress events (queue_update, message_start, thinking_delta)', () => {
|
|
37
|
+
expect(mapBrainEventToSpine(ev('queue_update', { queuedCount: 1 }))).toBeNull();
|
|
38
|
+
expect(mapBrainEventToSpine(ev('message_start', { messageId: 'm', role: 'assistant' }))).toBeNull();
|
|
39
|
+
expect(mapBrainEventToSpine(ev('thinking_delta', { messageId: 'm', delta: 'x' }))).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('SessionSpineMirror.adopt', () => {
|
|
43
|
+
it('bootstraps a fresh session with session.started at seq 1', async () => {
|
|
44
|
+
const mirror = await SessionSpineMirror.adopt('sess-a', { baseDir: tmp, quiet: true });
|
|
45
|
+
expect(mirror.status).toBe('active');
|
|
46
|
+
await mirror.close();
|
|
47
|
+
const report = await readSessionLog(path.join(tmp, 'sess-a', 'events.jsonl'));
|
|
48
|
+
expect(report.events.map((e) => e.kind)).toEqual(['session.started', 'session.ended']);
|
|
49
|
+
expect(report.events[0].seq).toBe(1);
|
|
50
|
+
});
|
|
51
|
+
it('resumes an existing session: session.resumed + seq continues after lastSeq', async () => {
|
|
52
|
+
const first = await SessionSpineMirror.adopt('sess-b', { baseDir: tmp, quiet: true });
|
|
53
|
+
first.userMessage('hello');
|
|
54
|
+
await first.close();
|
|
55
|
+
const second = await SessionSpineMirror.adopt('sess-b', { baseDir: tmp, quiet: true });
|
|
56
|
+
second.userMessage('again');
|
|
57
|
+
await second.close('done');
|
|
58
|
+
const report = await readSessionLog(path.join(tmp, 'sess-b', 'events.jsonl'));
|
|
59
|
+
const kinds = report.events.map((e) => e.kind);
|
|
60
|
+
expect(kinds).toEqual([
|
|
61
|
+
'session.started',
|
|
62
|
+
'user.message',
|
|
63
|
+
'session.ended',
|
|
64
|
+
'session.resumed',
|
|
65
|
+
'user.message',
|
|
66
|
+
'session.ended',
|
|
67
|
+
]);
|
|
68
|
+
// seq must be monotonic and gap-free across the reopen
|
|
69
|
+
report.events.forEach((e, i) => expect(e.seq).toBe(i + 1));
|
|
70
|
+
});
|
|
71
|
+
it('a second live writer is rejected as locked (ownership)', async () => {
|
|
72
|
+
const first = await SessionSpineMirror.adopt('sess-c', { baseDir: tmp, quiet: true });
|
|
73
|
+
const second = await SessionSpineMirror.adopt('sess-c', { baseDir: tmp, quiet: true });
|
|
74
|
+
expect(second.status).toBe('locked');
|
|
75
|
+
// the locked mirror is inert, the first keeps writing
|
|
76
|
+
first.userMessage('still mine');
|
|
77
|
+
await first.close();
|
|
78
|
+
const report = await readSessionLog(path.join(tmp, 'sess-c', 'events.jsonl'));
|
|
79
|
+
expect(report.events.some((e) => e.kind === 'user.message')).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
it('degrades silently when the sessions dir cannot be created', async () => {
|
|
82
|
+
// a FILE at the path where the directory should go → mkdir fails
|
|
83
|
+
const blocker = path.join(tmp, 'blocker');
|
|
84
|
+
await fs.writeFile(blocker, 'x', 'utf-8');
|
|
85
|
+
const mirror = await SessionSpineMirror.adopt('sess-d', { baseDir: blocker, quiet: true });
|
|
86
|
+
expect(mirror.status).toBe('degraded');
|
|
87
|
+
// never throws, stays inert
|
|
88
|
+
expect(() => mirror.userMessage('nope')).not.toThrow();
|
|
89
|
+
await mirror.close();
|
|
90
|
+
});
|
|
91
|
+
it('ZELARI_SESSION_SPINE=0 disables the mirror entirely', async () => {
|
|
92
|
+
const prev = process.env.ZELARI_SESSION_SPINE;
|
|
93
|
+
process.env.ZELARI_SESSION_SPINE = '0';
|
|
94
|
+
try {
|
|
95
|
+
expect(spineEnabled()).toBe(false);
|
|
96
|
+
const mirror = await SessionSpineMirror.adopt('sess-e', { baseDir: tmp, quiet: true });
|
|
97
|
+
expect(mirror.status).toBe('disabled');
|
|
98
|
+
await mirror.close();
|
|
99
|
+
await expect(fs.access(path.join(tmp, 'sess-e'))).rejects.toThrow();
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (prev === undefined)
|
|
103
|
+
delete process.env.ZELARI_SESSION_SPINE;
|
|
104
|
+
else
|
|
105
|
+
process.env.ZELARI_SESSION_SPINE = prev;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('SessionSpineMirror.mirrorBrainEvent', () => {
|
|
110
|
+
it('coalesces message_delta into ONE assistant.message at message_end', async () => {
|
|
111
|
+
const mirror = await SessionSpineMirror.adopt('sess-f', { baseDir: tmp, quiet: true });
|
|
112
|
+
mirror.mirrorBrainEvent(ev('message_start', { messageId: 'm1', role: 'assistant' }));
|
|
113
|
+
mirror.mirrorBrainEvent(ev('message_delta', { messageId: 'm1', delta: 'Hello ' }));
|
|
114
|
+
mirror.mirrorBrainEvent(ev('message_delta', { messageId: 'm1', delta: 'world' }));
|
|
115
|
+
mirror.mirrorBrainEvent(ev('message_end', { messageId: 'm1', totalLength: 11, finishReason: 'stop' }));
|
|
116
|
+
await mirror.close();
|
|
117
|
+
const report = await readSessionLog(path.join(tmp, 'sess-f', 'events.jsonl'));
|
|
118
|
+
const assistant = report.events.filter((e) => e.kind === 'assistant.message');
|
|
119
|
+
expect(assistant).toHaveLength(1);
|
|
120
|
+
expect(assistant[0].data.text).toBe('Hello world');
|
|
121
|
+
});
|
|
122
|
+
it('message_end without buffered deltas logs nothing (no truncated claim)', async () => {
|
|
123
|
+
const mirror = await SessionSpineMirror.adopt('sess-g', { baseDir: tmp, quiet: true });
|
|
124
|
+
mirror.mirrorBrainEvent(ev('message_end', { messageId: 'orphan', totalLength: 0, finishReason: 'stop' }));
|
|
125
|
+
await mirror.close();
|
|
126
|
+
const report = await readSessionLog(path.join(tmp, 'sess-g', 'events.jsonl'));
|
|
127
|
+
expect(report.events.filter((e) => e.kind === 'assistant.message')).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
it('mirrors tool call+result pair', async () => {
|
|
130
|
+
const mirror = await SessionSpineMirror.adopt('sess-h', { baseDir: tmp, quiet: true });
|
|
131
|
+
mirror.mirrorBrainEvent(ev('tool_execution_start', { toolCallId: 'c9', toolName: 'bash', args: {} }));
|
|
132
|
+
mirror.mirrorBrainEvent(ev('tool_execution_end', { toolCallId: 'c9', result: 'ok', isError: false, durationMs: 12 }));
|
|
133
|
+
await mirror.close();
|
|
134
|
+
const report = await readSessionLog(path.join(tmp, 'sess-h', 'events.jsonl'));
|
|
135
|
+
const kinds = report.events.map((e) => e.kind);
|
|
136
|
+
expect(kinds).toContain('tool.call');
|
|
137
|
+
expect(kinds).toContain('tool.result');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('resumeSpineContext', () => {
|
|
141
|
+
it('returns null for an unknown session and a projection for a logged one', async () => {
|
|
142
|
+
expect(await resumeSpineContext('nope', tmp)).toBeNull();
|
|
143
|
+
const mirror = await SessionSpineMirror.adopt('sess-i', { baseDir: tmp, quiet: true });
|
|
144
|
+
mirror.userMessage('write a test');
|
|
145
|
+
mirror.mirrorBrainEvent(ev('message_end', { messageId: 'm', totalLength: 2, finishReason: 'stop' }));
|
|
146
|
+
// note: no deltas buffered → no assistant message; add one properly
|
|
147
|
+
mirror.mirrorBrainEvent(ev('message_delta', { messageId: 'm2', delta: 'done' }));
|
|
148
|
+
mirror.mirrorBrainEvent(ev('message_end', { messageId: 'm2', totalLength: 4, finishReason: 'stop' }));
|
|
149
|
+
await mirror.close();
|
|
150
|
+
const ctx = await resumeSpineContext('sess-i', tmp);
|
|
151
|
+
expect(ctx).not.toBeNull();
|
|
152
|
+
expect(ctx.derived.map((m) => m.role)).toEqual(['user', 'assistant']);
|
|
153
|
+
expect(ctx.derived[0].content).toBe('write a test');
|
|
154
|
+
expect(ctx.projection.lastSeq).toBeGreaterThan(2);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
describe('SessionSpineMirror.release', () => {
|
|
158
|
+
it('leaves the log without session.ended so resume can continue the seq', async () => {
|
|
159
|
+
const mirror = await SessionSpineMirror.adopt('sess-int', { baseDir: tmp, quiet: true });
|
|
160
|
+
mirror.userMessage('partial');
|
|
161
|
+
await mirror.release();
|
|
162
|
+
const { readSessionLog } = await import('@zelari/core/session');
|
|
163
|
+
const report = await readSessionLog(path.join(tmp, 'sess-int', 'events.jsonl'));
|
|
164
|
+
expect(report.events.some((e) => e.kind === 'session.ended')).toBe(false);
|
|
165
|
+
const again = await SessionSpineMirror.adopt('sess-int', { baseDir: tmp, quiet: true });
|
|
166
|
+
expect(again.status).toBe('active');
|
|
167
|
+
expect(again.resumedFromSeq).toBeGreaterThan(0);
|
|
168
|
+
await again.close('done');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
//# sourceMappingURL=sessionSpine.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionSpine.test.js","sourceRoot":"","sources":["../../src/cli/sessionSpine.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/toolRegistry.js
CHANGED
|
@@ -25,6 +25,7 @@ import { assertShellAllowed, ShellBlockedError } from './safety/shellBlocklist.j
|
|
|
25
25
|
import { AuditLogger } from './safety/auditLogger.js';
|
|
26
26
|
import { runDiagnosticsForFile, formatDiagnostics } from './diagnostics/engine.js';
|
|
27
27
|
import { createTaskTool } from './tools/taskTool.js';
|
|
28
|
+
import { createKrakenSelectTool } from './tools/krakenSelectTool.js';
|
|
28
29
|
import { createAskUserTool } from './tools/askUser.js';
|
|
29
30
|
import { createSkillTool } from './tools/skillTool.js';
|
|
30
31
|
import { createTodoReadTool, createTodoWriteTool } from './tools/todoTools.js';
|
|
@@ -41,6 +42,7 @@ import { createSshTools } from './ssh/tools.js';
|
|
|
41
42
|
import { createWorldModelTools } from './workspace/worldModel.js';
|
|
42
43
|
import { providerFromEnv, providerConfigFor, } from './provider/openai-compatible.js';
|
|
43
44
|
import { buildProviderStream } from './provider/resolveStream.js';
|
|
45
|
+
import { getKrakenVerifierOverride } from './providerConfig.js';
|
|
44
46
|
import { defaultPermissionPolicy, resolveToolPermission, } from './safety/toolPermissions.js';
|
|
45
47
|
import { createDefaultLifecycleHooks } from './safety/lifecycleHooks.js';
|
|
46
48
|
import { withResultCache } from './toolResultCache.js';
|
|
@@ -275,17 +277,26 @@ export function createBuiltinToolRegistry(options = {}) {
|
|
|
275
277
|
});
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
|
-
// The `task` sub-agent tool — parent full registry only (not
|
|
279
|
-
// Sub-agents get a profile-specific registry and never nest another
|
|
280
|
+
// The `task` sub-agent tool — parent full registry only (not explore/verify/general
|
|
281
|
+
// sub-agents). Sub-agents get a profile-specific registry and never nest another
|
|
282
|
+
// `task`. Fase 1 (ADR-0020): PLAN keeps the `task` tool restricted to
|
|
283
|
+
// explore-only (plan-safe) tentacles unless planExploreTask=false; the explore
|
|
284
|
+
// profile is read-only, so PLAN gains parallel research without write/execute reach.
|
|
280
285
|
const enableTask = options.enableTask !== false &&
|
|
281
|
-
|
|
286
|
+
options.readOnly !== true &&
|
|
282
287
|
!verifyMode &&
|
|
283
288
|
profile === 'full' &&
|
|
284
|
-
!options.planMode;
|
|
289
|
+
(!options.planMode || options.planExploreTask !== false);
|
|
285
290
|
if (enableTask) {
|
|
286
291
|
const taskTool = createTaskTool({
|
|
287
|
-
createSubAgentContext: createKrakenSubAgentContextFactory({
|
|
288
|
-
|
|
292
|
+
createSubAgentContext: createKrakenSubAgentContextFactory({
|
|
293
|
+
root,
|
|
294
|
+
audit,
|
|
295
|
+
sessionId,
|
|
296
|
+
...(options.subAgentProvider ? { provider: options.subAgentProvider } : {}),
|
|
297
|
+
...(options.subAgentModel ? { model: options.subAgentModel } : {}),
|
|
298
|
+
}),
|
|
299
|
+
}, options.planMode === true ? { allowedAgents: ['explore'] } : undefined);
|
|
289
300
|
registry.register(withPerm(taskTool));
|
|
290
301
|
tools.push({
|
|
291
302
|
name: taskTool.name,
|
|
@@ -293,6 +304,39 @@ export function createBuiltinToolRegistry(options = {}) {
|
|
|
293
304
|
permissions: taskTool.permissions ?? [],
|
|
294
305
|
});
|
|
295
306
|
}
|
|
307
|
+
// Fase 4 (ADR-0020): `kraken_select` — parent Kraken ONLY. Gated on
|
|
308
|
+
// enableTask (full profile, not read-only, not verifyMode) AND the
|
|
309
|
+
// explicit krakenSelect option, so tentacles/council/zelari never see
|
|
310
|
+
// it. The verifier defaults to the EXACT parent provider/model
|
|
311
|
+
// (subAgentProvider/subAgentModel of this turn/run).
|
|
312
|
+
if (options.krakenSelect === true && enableTask) {
|
|
313
|
+
const selectTool = createKrakenSelectTool({
|
|
314
|
+
loadParentIdentity: async () => {
|
|
315
|
+
const cfg = options.subAgentProvider
|
|
316
|
+
? await providerConfigFor(options.subAgentProvider)
|
|
317
|
+
: await providerFromEnv();
|
|
318
|
+
if (!cfg)
|
|
319
|
+
return null;
|
|
320
|
+
return {
|
|
321
|
+
provider: cfg.providerId,
|
|
322
|
+
model: options.subAgentModel || cfg.model,
|
|
323
|
+
};
|
|
324
|
+
},
|
|
325
|
+
loadStream: async (provider) => {
|
|
326
|
+
const cfg = await providerConfigFor(provider);
|
|
327
|
+
return cfg ? buildProviderStream(cfg) : null;
|
|
328
|
+
},
|
|
329
|
+
// Fase 9 (ADR-0020): persisted verifier override (provider.json
|
|
330
|
+
// `krakenVerifier`). undefined = inherit the EXACT parent model.
|
|
331
|
+
loadVerifierOverride: () => getKrakenVerifierOverride(),
|
|
332
|
+
});
|
|
333
|
+
registry.register(withPerm(selectTool));
|
|
334
|
+
tools.push({
|
|
335
|
+
name: selectTool.name,
|
|
336
|
+
description: selectTool.description,
|
|
337
|
+
permissions: selectTool.permissions ?? [],
|
|
338
|
+
});
|
|
339
|
+
}
|
|
296
340
|
// LSP navigation tools (go_to_definition, find_references, hover_type,
|
|
297
341
|
// document_symbols, rename_symbol). Full + plan (read-only) registry,
|
|
298
342
|
// gated by ZELARI_LSP. All five tools are permissions: ['read']
|