zelari-code 2.7.0 → 2.8.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/README.md +10 -2
- package/dist/cli/hooks/useChatTurn.js +109 -1
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useSlashDispatch.js +7 -0
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
- package/dist/cli/kraken/executor.js +73 -0
- package/dist/cli/kraken/executor.js.map +1 -1
- package/dist/cli/main.bundled.js +5323 -1502
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +46 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/memory/fileBackend.js +8 -0
- package/dist/cli/memory/fileBackend.js.map +1 -1
- package/dist/cli/memory/jsonApi.js +83 -0
- package/dist/cli/memory/jsonApi.js.map +1 -0
- package/dist/cli/memory/legacyImport.js +91 -0
- package/dist/cli/memory/legacyImport.js.map +1 -0
- package/dist/cli/memory/mcpAdapter.js +255 -0
- package/dist/cli/memory/mcpAdapter.js.map +1 -0
- package/dist/cli/memory/mcpCli.js +19 -0
- package/dist/cli/memory/mcpCli.js.map +1 -0
- package/dist/cli/memory/mcpServer.js +126 -0
- package/dist/cli/memory/mcpServer.js.map +1 -0
- package/dist/cli/memory/promotion.js +68 -0
- package/dist/cli/memory/promotion.js.map +1 -0
- package/dist/cli/memory/serviceFactory.js +94 -0
- package/dist/cli/memory/serviceFactory.js.map +1 -0
- package/dist/cli/memory/sqliteBackend.js +636 -0
- package/dist/cli/memory/sqliteBackend.js.map +1 -0
- package/dist/cli/memory/sqliteCodec.js +114 -0
- package/dist/cli/memory/sqliteCodec.js.map +1 -0
- package/dist/cli/memory/sqliteRpc.js +120 -0
- package/dist/cli/memory/sqliteRpc.js.map +1 -0
- package/dist/cli/memory/sqliteSchema.js +181 -0
- package/dist/cli/memory/sqliteSchema.js.map +1 -0
- package/dist/cli/memory/sqliteWorker.mjs +264 -0
- package/dist/cli/runHeadless.js +97 -3
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/semantic/embeddings.js +13 -1
- package/dist/cli/semantic/embeddings.js.map +1 -1
- package/dist/cli/semantic/provider.js +11 -2
- package/dist/cli/semantic/provider.js.map +1 -1
- package/dist/cli/slashCommands.js +10 -0
- package/dist/cli/slashCommands.js.map +1 -1
- package/dist/cli/slashHandlers/krakenGraph.js +11 -0
- package/dist/cli/slashHandlers/krakenGraph.js.map +1 -1
- package/dist/cli/slashHandlers/memory.js +239 -0
- package/dist/cli/slashHandlers/memory.js.map +1 -0
- package/dist/cli/toolRegistry.js +4 -0
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/taskTool.js +41 -0
- package/dist/cli/tools/taskTool.js.map +1 -1
- package/dist/cli/zelariMission.js +6 -0
- package/dist/cli/zelariMission.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { parentPort } from 'node:worker_threads';
|
|
2
|
+
import { backup, DatabaseSync } from 'node:sqlite';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { closeSync, existsSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
if (!parentPort) throw new Error('sqliteWorker must run inside a worker thread');
|
|
7
|
+
|
|
8
|
+
let database = null;
|
|
9
|
+
|
|
10
|
+
function waitSync(ms) {
|
|
11
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function acquireMigrationLock(dbPath, timeoutMs = 10_000) {
|
|
15
|
+
if (!dbPath || dbPath === ':memory:') return { path: null, fd: null };
|
|
16
|
+
const lockPath = `${dbPath}.migration.lock`;
|
|
17
|
+
const started = Date.now();
|
|
18
|
+
while (true) {
|
|
19
|
+
try {
|
|
20
|
+
const fd = openSync(lockPath, 'wx');
|
|
21
|
+
writeFileSync(fd, JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }));
|
|
22
|
+
return { path: lockPath, fd };
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
25
|
+
try {
|
|
26
|
+
if (Date.now() - statSync(lockPath).mtimeMs > 300_000) {
|
|
27
|
+
let ownerAlive = false;
|
|
28
|
+
try {
|
|
29
|
+
const owner = JSON.parse(readFileSync(lockPath, 'utf8'));
|
|
30
|
+
if (Number.isInteger(owner?.pid) && owner.pid > 0) {
|
|
31
|
+
try { process.kill(owner.pid, 0); ownerAlive = true; } catch {}
|
|
32
|
+
}
|
|
33
|
+
} catch {}
|
|
34
|
+
if (!ownerAlive) unlinkSync(lockPath);
|
|
35
|
+
}
|
|
36
|
+
} catch (statError) {
|
|
37
|
+
if (statError?.code !== 'ENOENT') throw statError;
|
|
38
|
+
}
|
|
39
|
+
if (Date.now() - started >= timeoutMs) {
|
|
40
|
+
const locked = new Error(`Timed out waiting for memory migration lock: ${lockPath}`);
|
|
41
|
+
locked.code = 'ZELARI_MEMORY_MIGRATION_LOCKED';
|
|
42
|
+
throw locked;
|
|
43
|
+
}
|
|
44
|
+
waitSync(50);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function releaseMigrationLock(lock) {
|
|
50
|
+
if (lock.fd !== null) {
|
|
51
|
+
try { closeSync(lock.fd); } catch {}
|
|
52
|
+
}
|
|
53
|
+
if (lock.path) {
|
|
54
|
+
try { unlinkSync(lock.path); } catch {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function invoke(statement, mode, params = []) {
|
|
59
|
+
const fn = statement[mode];
|
|
60
|
+
if (typeof fn !== 'function') throw new Error(`Unsupported SQLite statement mode: ${mode}`);
|
|
61
|
+
return Array.isArray(params) ? fn.call(statement, ...params) : fn.call(statement, params);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function executeStep(step) {
|
|
65
|
+
const statement = database.prepare(step.sql);
|
|
66
|
+
const result = invoke(statement, step.mode ?? 'run', step.params ?? []);
|
|
67
|
+
if (step.mode === 'run' || !step.mode) {
|
|
68
|
+
return {
|
|
69
|
+
changes: Number(result.changes ?? 0),
|
|
70
|
+
lastInsertRowid: String(result.lastInsertRowid ?? ''),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hashContent(content) {
|
|
77
|
+
return createHash('sha256').update(String(content ?? '')).digest('hex');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function decodeVector(value, dimensions) {
|
|
81
|
+
if (typeof value !== 'string') return null;
|
|
82
|
+
try {
|
|
83
|
+
const vector = JSON.parse(value);
|
|
84
|
+
const expected = Number(dimensions);
|
|
85
|
+
return Array.isArray(vector) && vector.length === expected && vector.length > 0 &&
|
|
86
|
+
vector.every((item) => typeof item === 'number' && Number.isFinite(item))
|
|
87
|
+
? vector
|
|
88
|
+
: null;
|
|
89
|
+
} catch { return null; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function cosine(left, right) {
|
|
93
|
+
if (!left.length || left.length !== right.length) return 0;
|
|
94
|
+
let dot = 0;
|
|
95
|
+
let leftNorm = 0;
|
|
96
|
+
let rightNorm = 0;
|
|
97
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
98
|
+
dot += left[index] * right[index];
|
|
99
|
+
leftNorm += left[index] * left[index];
|
|
100
|
+
rightNorm += right[index] * right[index];
|
|
101
|
+
}
|
|
102
|
+
return leftNorm && rightNorm ? dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm)) : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const handlers = {
|
|
106
|
+
async open(args) {
|
|
107
|
+
if (database) return { fts: true };
|
|
108
|
+
const lock = acquireMigrationLock(args.dbPath, args.migrationLockTimeoutMs ?? 30_000);
|
|
109
|
+
try {
|
|
110
|
+
database = new DatabaseSync(args.dbPath, {
|
|
111
|
+
timeout: args.timeoutMs ?? 5_000,
|
|
112
|
+
enableForeignKeyConstraints: true,
|
|
113
|
+
});
|
|
114
|
+
const row = database.prepare('PRAGMA user_version').get();
|
|
115
|
+
const currentVersion = Number(row?.user_version ?? 0);
|
|
116
|
+
if (currentVersion > args.schemaVersion) {
|
|
117
|
+
const error = new Error(
|
|
118
|
+
`Memory database schema v${currentVersion} is newer than runtime v${args.schemaVersion}; refusing to downgrade.`,
|
|
119
|
+
);
|
|
120
|
+
error.code = 'ZELARI_MEMORY_SCHEMA_NEWER';
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
let migratedFrom;
|
|
124
|
+
let backupPath;
|
|
125
|
+
if (currentVersion === 0) {
|
|
126
|
+
database.exec(args.schemaSql);
|
|
127
|
+
database.exec(`PRAGMA user_version = ${Number(args.schemaVersion)}`);
|
|
128
|
+
} else if (currentVersion < args.schemaVersion) {
|
|
129
|
+
migratedFrom = currentVersion;
|
|
130
|
+
const baseBackupPath = `${args.dbPath}.v${currentVersion}.bak`;
|
|
131
|
+
backupPath = existsSync(baseBackupPath)
|
|
132
|
+
? `${baseBackupPath}.${Date.now()}`
|
|
133
|
+
: baseBackupPath;
|
|
134
|
+
await backup(database, backupPath);
|
|
135
|
+
const migrations = [...(args.migrations ?? [])]
|
|
136
|
+
.filter((migration) => migration.version > currentVersion && migration.version <= args.schemaVersion)
|
|
137
|
+
.sort((a, b) => a.version - b.version);
|
|
138
|
+
let expected = currentVersion + 1;
|
|
139
|
+
for (const migration of migrations) {
|
|
140
|
+
if (migration.version !== expected) {
|
|
141
|
+
const error = new Error(`Missing memory migration v${expected} -> v${migration.version}.`);
|
|
142
|
+
error.code = 'ZELARI_MEMORY_MIGRATION_GAP';
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
database.exec('BEGIN EXCLUSIVE');
|
|
146
|
+
try {
|
|
147
|
+
database.exec(migration.sql);
|
|
148
|
+
database.exec(`PRAGMA user_version = ${Number(migration.version)}`);
|
|
149
|
+
database.exec('COMMIT');
|
|
150
|
+
} catch (error) {
|
|
151
|
+
try { database.exec('ROLLBACK'); } catch {}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
expected += 1;
|
|
155
|
+
}
|
|
156
|
+
if (expected - 1 !== args.schemaVersion) {
|
|
157
|
+
const error = new Error(`No complete migration path from v${currentVersion} to v${args.schemaVersion}.`);
|
|
158
|
+
error.code = 'ZELARI_MEMORY_MIGRATION_GAP';
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
database.exec(args.schemaSql);
|
|
162
|
+
} else {
|
|
163
|
+
database.exec(args.schemaSql);
|
|
164
|
+
}
|
|
165
|
+
let fts = true;
|
|
166
|
+
try {
|
|
167
|
+
database.exec(args.ftsSql);
|
|
168
|
+
} catch {
|
|
169
|
+
fts = false;
|
|
170
|
+
}
|
|
171
|
+
return { fts, migratedFrom, backupPath };
|
|
172
|
+
} catch (error) {
|
|
173
|
+
try { database?.close(); } catch {}
|
|
174
|
+
database = null;
|
|
175
|
+
throw error;
|
|
176
|
+
} finally {
|
|
177
|
+
releaseMigrationLock(lock);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
exec(args) {
|
|
181
|
+
database.exec(args.sql);
|
|
182
|
+
return null;
|
|
183
|
+
},
|
|
184
|
+
statement(args) {
|
|
185
|
+
return executeStep(args);
|
|
186
|
+
},
|
|
187
|
+
vectorSearch(args) {
|
|
188
|
+
const rows = invoke(database.prepare(args.sql), 'all', args.params ?? []);
|
|
189
|
+
const scored = [];
|
|
190
|
+
for (const row of rows) {
|
|
191
|
+
const vector = decodeVector(row.vector_json, row.dimensions);
|
|
192
|
+
if (!vector || vector.length !== args.vector.length) continue;
|
|
193
|
+
if (row.content_hash !== hashContent(row.content)) continue;
|
|
194
|
+
scored.push({
|
|
195
|
+
...row,
|
|
196
|
+
semantic_relevance: Math.max(0, Math.min(1, cosine(args.vector, vector))),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
scored.sort((left, right) =>
|
|
200
|
+
right.semantic_relevance - left.semantic_relevance ||
|
|
201
|
+
Number(right.importance ?? 0) - Number(left.importance ?? 0));
|
|
202
|
+
return scored.slice(0, Math.max(1, Number(args.limit) || 1));
|
|
203
|
+
},
|
|
204
|
+
semanticStatus(args) {
|
|
205
|
+
const rows = database.prepare(`SELECT n.content, e.content_hash, e.dimensions,
|
|
206
|
+
e.vector_json, e.indexed_at FROM memory_nodes n LEFT JOIN memory_embeddings e
|
|
207
|
+
ON e.memory_id=n.id AND e.model=? WHERE n.project_id=? AND n.status='active'`)
|
|
208
|
+
.all(args.model, args.projectId);
|
|
209
|
+
let indexed = 0;
|
|
210
|
+
let stale = 0;
|
|
211
|
+
let corrupt = 0;
|
|
212
|
+
let lastIndexedAt;
|
|
213
|
+
for (const row of rows) {
|
|
214
|
+
const vector = decodeVector(row.vector_json, row.dimensions);
|
|
215
|
+
const fresh = vector && row.content_hash === hashContent(row.content);
|
|
216
|
+
if (fresh) {
|
|
217
|
+
indexed += 1;
|
|
218
|
+
if (typeof row.indexed_at === 'string' && (!lastIndexedAt || row.indexed_at > lastIndexedAt)) {
|
|
219
|
+
lastIndexedAt = row.indexed_at;
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
stale += 1;
|
|
223
|
+
if (row.vector_json !== null && row.vector_json !== undefined) corrupt += 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { indexed, stale, corrupt, lastIndexedAt };
|
|
227
|
+
},
|
|
228
|
+
batch(args) {
|
|
229
|
+
database.exec(args.immediate === false ? 'BEGIN' : 'BEGIN IMMEDIATE');
|
|
230
|
+
try {
|
|
231
|
+
const results = args.steps.map(executeStep);
|
|
232
|
+
database.exec('COMMIT');
|
|
233
|
+
return results;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
try { database.exec('ROLLBACK'); } catch {}
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
close() {
|
|
240
|
+
database?.close();
|
|
241
|
+
database = null;
|
|
242
|
+
return null;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
parentPort.on('message', async ({ id, operation, args }) => {
|
|
247
|
+
try {
|
|
248
|
+
const handler = handlers[operation];
|
|
249
|
+
if (!handler) throw new Error(`Unknown SQLite worker operation: ${operation}`);
|
|
250
|
+
parentPort.postMessage({ id, ok: true, value: await handler(args ?? {}) });
|
|
251
|
+
} catch (error) {
|
|
252
|
+
parentPort.postMessage({
|
|
253
|
+
id,
|
|
254
|
+
ok: false,
|
|
255
|
+
error: {
|
|
256
|
+
name: error?.name ?? 'Error',
|
|
257
|
+
message: error?.message ?? String(error),
|
|
258
|
+
stack: error?.stack,
|
|
259
|
+
code: error?.code,
|
|
260
|
+
errcode: error?.errcode,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
package/dist/cli/runHeadless.js
CHANGED
|
@@ -224,6 +224,10 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
224
224
|
const { createKrakenSubAgentContextFactory } = await import('./toolRegistry.js');
|
|
225
225
|
const cwd = process.cwd();
|
|
226
226
|
const sessionId = crypto.randomUUID();
|
|
227
|
+
const { getMemoryService, isMemoryAutoWriteEnabled, isMemoryV2Enabled } = await import('./memory/serviceFactory.js');
|
|
228
|
+
const graphMemory = isMemoryV2Enabled()
|
|
229
|
+
? await getMemoryService(cwd, process.env)
|
|
230
|
+
: undefined;
|
|
227
231
|
const log = (message) => {
|
|
228
232
|
if (opts.output === 'json') {
|
|
229
233
|
emitEvent({ type: 'log', message });
|
|
@@ -323,6 +327,8 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
323
327
|
provider,
|
|
324
328
|
model,
|
|
325
329
|
}),
|
|
330
|
+
...(graphMemory ? { memoryService: graphMemory } : {}),
|
|
331
|
+
memoryAutoWrite: isMemoryAutoWriteEnabled(),
|
|
326
332
|
},
|
|
327
333
|
parentCwd: cwd,
|
|
328
334
|
sessionId,
|
|
@@ -371,6 +377,7 @@ async function runHeadlessKrakenGraph(opts, provider, model) {
|
|
|
371
377
|
}
|
|
372
378
|
finally {
|
|
373
379
|
process.off('SIGINT', onSigint);
|
|
380
|
+
await graphMemory?.close().catch(() => undefined);
|
|
374
381
|
}
|
|
375
382
|
}
|
|
376
383
|
function planModeFromOpts(opts) {
|
|
@@ -420,6 +427,11 @@ async function registerHeadlessMcp(toolRegistry, opts) {
|
|
|
420
427
|
}
|
|
421
428
|
async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
422
429
|
const sessionId = crypto.randomUUID();
|
|
430
|
+
const memoryFactory = await import('./memory/serviceFactory.js');
|
|
431
|
+
const nativeMemory = memoryFactory.isMemoryV2Enabled()
|
|
432
|
+
? await memoryFactory.getMemoryService(process.cwd(), process.env)
|
|
433
|
+
: undefined;
|
|
434
|
+
const memoryAutoWrite = memoryFactory.isMemoryAutoWriteEnabled();
|
|
423
435
|
// Headless / Desktop: no interactive permission UI — auto-allow "ask" rules
|
|
424
436
|
// unless the user set an explicit deny. Override with ZELARI_AUTO=0 and
|
|
425
437
|
// ZELARI_PERMISSION_*=deny for hard lockdown.
|
|
@@ -457,6 +469,8 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
457
469
|
ui: 'allow',
|
|
458
470
|
auto: true,
|
|
459
471
|
},
|
|
472
|
+
...(nativeMemory ? { memoryService: nativeMemory } : {}),
|
|
473
|
+
memoryAutoWrite,
|
|
460
474
|
});
|
|
461
475
|
// Parity with TUI: project MCP tools must be available from Desktop/headless.
|
|
462
476
|
await registerHeadlessMcp(toolRegistry, opts);
|
|
@@ -672,6 +686,13 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
672
686
|
// command is a test/typecheck/build/git-diff line.
|
|
673
687
|
toolCallGate: (name, args) => spine.gateResourceToolCall(name, args) ?? { allowed: true },
|
|
674
688
|
maxToolLoopIterations: maxToolLoop,
|
|
689
|
+
...(nativeMemory
|
|
690
|
+
? {
|
|
691
|
+
memoryService: nativeMemory,
|
|
692
|
+
memoryQuery: opts.task,
|
|
693
|
+
memoryContextChars: 2_000,
|
|
694
|
+
}
|
|
695
|
+
: {}),
|
|
675
696
|
});
|
|
676
697
|
let finalReason = 'completed';
|
|
677
698
|
let exitCode = 0;
|
|
@@ -973,6 +994,35 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
973
994
|
}
|
|
974
995
|
catch { /* export is best-effort */ }
|
|
975
996
|
}
|
|
997
|
+
if (nativeMemory && memoryAutoWrite && pass.finalReason !== 'error') {
|
|
998
|
+
try {
|
|
999
|
+
const finalContent = [...pass.messages]
|
|
1000
|
+
.reverse()
|
|
1001
|
+
.find((message) => message.role === 'assistant' && message.content.trim())
|
|
1002
|
+
?.content.trim();
|
|
1003
|
+
if (finalContent) {
|
|
1004
|
+
await nativeMemory.remember({
|
|
1005
|
+
kind: planModeFromOpts(opts) ? 'finding' : 'outcome',
|
|
1006
|
+
content: finalContent.slice(0, 8_000),
|
|
1007
|
+
importance: planModeFromOpts(opts) ? 0.55 : 0.7,
|
|
1008
|
+
confidence: strictExit === 0 ? 0.75 : 0.45,
|
|
1009
|
+
source: { agent: 'zelari-headless', sessionId: spine.sessionId },
|
|
1010
|
+
tags: ['headless', `phase:${opts.phase ?? 'build'}`],
|
|
1011
|
+
metadata: {
|
|
1012
|
+
objective: opts.task.slice(0, 2_000),
|
|
1013
|
+
successfulWrites: pass.successfulWrites,
|
|
1014
|
+
strictExit,
|
|
1015
|
+
writeClass: planModeFromOpts(opts) ? 'candidate' : 'auto',
|
|
1016
|
+
},
|
|
1017
|
+
writeClass: planModeFromOpts(opts) ? 'candidate' : 'auto',
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
catch {
|
|
1022
|
+
// Headless exit status is never governed by memory persistence.
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
await nativeMemory?.close().catch(() => undefined);
|
|
976
1026
|
if (pass.finalReason === 'error')
|
|
977
1027
|
return 3;
|
|
978
1028
|
// E2.2: strict done gate — a blocked verdict overrides a clean pass exit.
|
|
@@ -980,7 +1030,7 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
980
1030
|
return strictExit;
|
|
981
1031
|
return pass.exitCode;
|
|
982
1032
|
}
|
|
983
|
-
async function buildCouncilToolRegistry(planMode, opts) {
|
|
1033
|
+
async function buildCouncilToolRegistry(planMode, opts, memoryService, memoryAutoWrite = false) {
|
|
984
1034
|
const { registry: toolRegistry } = createBuiltinToolRegistry({
|
|
985
1035
|
planMode,
|
|
986
1036
|
permissionPolicy: {
|
|
@@ -991,6 +1041,8 @@ async function buildCouncilToolRegistry(planMode, opts) {
|
|
|
991
1041
|
ui: 'allow',
|
|
992
1042
|
auto: true,
|
|
993
1043
|
},
|
|
1044
|
+
...(memoryService ? { memoryService } : {}),
|
|
1045
|
+
memoryAutoWrite,
|
|
994
1046
|
});
|
|
995
1047
|
const { createWorkspaceContext, createWorkspaceStubs } = await import('./workspace/stubs.js');
|
|
996
1048
|
const { createWorkspaceToolRegistry } = await import('./workspace/toolRegistry.js');
|
|
@@ -1011,6 +1063,11 @@ async function buildCouncilToolRegistry(planMode, opts) {
|
|
|
1011
1063
|
async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
1012
1064
|
const { dispatchCouncil } = await import('./councilDispatcher.js');
|
|
1013
1065
|
const sessionId = crypto.randomUUID();
|
|
1066
|
+
const memoryFactory = await import('./memory/serviceFactory.js');
|
|
1067
|
+
const nativeMemory = memoryFactory.isMemoryV2Enabled()
|
|
1068
|
+
? await memoryFactory.getMemoryService(process.cwd(), process.env)
|
|
1069
|
+
: undefined;
|
|
1070
|
+
const memoryAutoWrite = memoryFactory.isMemoryAutoWriteEnabled();
|
|
1014
1071
|
const spine = await openHeadlessSpine({
|
|
1015
1072
|
sessionId: opts.resumeSessionId ?? sessionId,
|
|
1016
1073
|
mode: opts.mode,
|
|
@@ -1038,7 +1095,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
1038
1095
|
process.stderr.write('[zelari-code --headless] council build soft-gate: forced design-phase ' +
|
|
1039
1096
|
'(set ZELARI_COUNCIL_CAN_BUILD=1 to allow Lucifero implement)\n');
|
|
1040
1097
|
}
|
|
1041
|
-
const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts) || softGated, opts);
|
|
1098
|
+
const { toolRegistry } = await buildCouncilToolRegistry(planModeFromOpts(opts) || softGated, opts, nativeMemory, memoryAutoWrite);
|
|
1042
1099
|
const { FeedbackStore } = await import('./councilFeedback.js');
|
|
1043
1100
|
const feedbackStore = new FeedbackStore();
|
|
1044
1101
|
// Multi-turn: Desktop passes --history, but council used to ignore it →
|
|
@@ -1091,11 +1148,20 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
1091
1148
|
const { loadDurableContext } = await import('./state/loadDurableContext.js');
|
|
1092
1149
|
const cwd = process.cwd();
|
|
1093
1150
|
const durableState = await loadDurableContext(cwd);
|
|
1151
|
+
const memoryContext = nativeMemory
|
|
1152
|
+
? (await nativeMemory.buildContext({
|
|
1153
|
+
text: effectiveTask,
|
|
1154
|
+
useGraph: true,
|
|
1155
|
+
maxChars: 2_000,
|
|
1156
|
+
maxMemories: 8,
|
|
1157
|
+
})).text
|
|
1158
|
+
: '';
|
|
1094
1159
|
const composed = composeProjectContext({
|
|
1095
1160
|
mode: 'council',
|
|
1096
1161
|
cwd,
|
|
1097
1162
|
userMessage: opts.task,
|
|
1098
1163
|
includeLessons: true,
|
|
1164
|
+
memoryHits: memoryContext || undefined,
|
|
1099
1165
|
durableState: durableState || undefined,
|
|
1100
1166
|
includeDurableState: false,
|
|
1101
1167
|
});
|
|
@@ -1164,6 +1230,7 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
1164
1230
|
}
|
|
1165
1231
|
catch (err) {
|
|
1166
1232
|
process.stderr.write(`[zelari-code --headless] council error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1233
|
+
await nativeMemory?.close().catch(() => undefined);
|
|
1167
1234
|
return 2;
|
|
1168
1235
|
}
|
|
1169
1236
|
// Desktop multi-turn: append this turn so the next "procedi" has context.
|
|
@@ -1185,6 +1252,32 @@ async function runHeadlessCouncil(opts, provider, model, providerStream) {
|
|
|
1185
1252
|
}
|
|
1186
1253
|
catch { /* export is best-effort */ }
|
|
1187
1254
|
}
|
|
1255
|
+
if (nativeMemory && memoryAutoWrite && lastAssistantText) {
|
|
1256
|
+
try {
|
|
1257
|
+
await nativeMemory.remember({
|
|
1258
|
+
kind: councilRunMode === 'design-phase' ? 'decision' : 'outcome',
|
|
1259
|
+
content: lastAssistantText.slice(0, 12_000),
|
|
1260
|
+
importance: councilRunMode === 'design-phase' ? 0.8 : 0.75,
|
|
1261
|
+
confidence: exitCode === 0 ? 0.78 : 0.45,
|
|
1262
|
+
source: { agent: 'council-headless', sessionId: spine.sessionId },
|
|
1263
|
+
tags: ['council', 'headless', `run-mode:${councilRunMode}`],
|
|
1264
|
+
metadata: {
|
|
1265
|
+
objective: opts.task.slice(0, 2_000),
|
|
1266
|
+
exitCode,
|
|
1267
|
+
writeClass: exitCode === 0 ? 'auto' : 'candidate',
|
|
1268
|
+
},
|
|
1269
|
+
writeClass: exitCode === 0 ? 'auto' : 'candidate',
|
|
1270
|
+
});
|
|
1271
|
+
await nativeMemory.consolidate({
|
|
1272
|
+
source: { agent: 'council-headless', sessionId: spine.sessionId },
|
|
1273
|
+
minOccurrences: 2,
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
catch {
|
|
1277
|
+
// Memory is not part of the council completion gate.
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
await nativeMemory?.close().catch(() => undefined);
|
|
1188
1281
|
return exitCode;
|
|
1189
1282
|
}
|
|
1190
1283
|
/**
|
|
@@ -1220,7 +1313,8 @@ async function runHeadlessZelari(opts, provider, model, providerStream) {
|
|
|
1220
1313
|
hasPlan: hasWorkspacePlan(projectRoot),
|
|
1221
1314
|
});
|
|
1222
1315
|
const memory = await getMemoryBackend(projectRoot);
|
|
1223
|
-
const
|
|
1316
|
+
const nativeMissionMemory = memory.service;
|
|
1317
|
+
const { toolRegistry, workspaceCtx } = await buildCouncilToolRegistry(planModeFromOpts(opts), opts, nativeMissionMemory, Boolean(nativeMissionMemory) && process.env.ZELARI_MEMORY_AUTO_WRITE !== '0');
|
|
1224
1318
|
const feedbackStore = new FeedbackStore();
|
|
1225
1319
|
const chairmanBudget = envNumber(process.env.ZELARI_MODE_MAX_TOOLS_LUCIFER, {
|
|
1226
1320
|
default: 30,
|