zelari-code 2.46.1 → 2.48.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 +3 -3
- package/dist/cli/automations/browser/cookieDisk.js +56 -0
- package/dist/cli/automations/browser/cookieDisk.js.map +1 -0
- package/dist/cli/automations/browser/pageAdapter.js +130 -0
- package/dist/cli/automations/browser/pageAdapter.js.map +1 -0
- package/dist/cli/automations/browser/selectors.js +178 -0
- package/dist/cli/automations/browser/selectors.js.map +1 -0
- package/dist/cli/automations/browser/session.js +243 -0
- package/dist/cli/automations/browser/session.js.map +1 -0
- package/dist/cli/budget/budgetRuntime.js +9 -13
- package/dist/cli/budget/budgetRuntime.js.map +1 -1
- package/dist/cli/budget/essentialBashConfig.js +118 -0
- package/dist/cli/budget/essentialBashConfig.js.map +1 -0
- package/dist/cli/headless/runOneTurn.js +36 -6
- package/dist/cli/headless/runOneTurn.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +30 -7
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/keyStore.js +2 -0
- package/dist/cli/keyStore.js.map +1 -1
- package/dist/cli/kraken/candidateRegistry.js +13 -0
- package/dist/cli/kraken/candidateRegistry.js.map +1 -1
- package/dist/cli/kraken/executor.js +200 -22
- package/dist/cli/kraken/executor.js.map +1 -1
- package/dist/cli/kraken/narrativeFloor.js +23 -0
- package/dist/cli/kraken/narrativeFloor.js.map +1 -0
- package/dist/cli/kraken/planner.js +27 -0
- package/dist/cli/kraken/planner.js.map +1 -1
- package/dist/cli/kraken/strictWaiver.js +22 -0
- package/dist/cli/kraken/strictWaiver.js.map +1 -0
- package/dist/cli/kraken/unresolvedGate.js +32 -0
- package/dist/cli/kraken/unresolvedGate.js.map +1 -0
- package/dist/cli/kraken/verificationBridge.js +67 -8
- package/dist/cli/kraken/verificationBridge.js.map +1 -1
- package/dist/cli/kraken/verifyHonestVerdict.js +30 -0
- package/dist/cli/kraken/verifyHonestVerdict.js.map +1 -0
- package/dist/cli/kraken/worktreeMergeRollback.js +277 -0
- package/dist/cli/kraken/worktreeMergeRollback.js.map +1 -0
- package/dist/cli/kraken/worktreeScheduling.js +11 -0
- package/dist/cli/kraken/worktreeScheduling.js.map +1 -1
- package/dist/cli/main.bundled.js +3278 -1483
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +1 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/modelDiscovery.js +5 -1
- package/dist/cli/modelDiscovery.js.map +1 -1
- package/dist/cli/museOAuth.js +384 -0
- package/dist/cli/museOAuth.js.map +1 -0
- package/dist/cli/oauthDesktop.js +18 -1
- package/dist/cli/oauthDesktop.js.map +1 -1
- package/dist/cli/provider/openai-compatible.js +1 -0
- package/dist/cli/provider/openai-compatible.js.map +1 -1
- package/dist/cli/provider/resolveStream.js +2 -0
- package/dist/cli/provider/resolveStream.js.map +1 -1
- package/dist/cli/providerConfig.js +2 -0
- package/dist/cli/providerConfig.js.map +1 -1
- package/dist/cli/refreshRegistry.js +6 -0
- package/dist/cli/refreshRegistry.js.map +1 -1
- package/dist/cli/runHeadless.js +184 -100
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/slashCommands.js +6 -6
- package/dist/cli/slashCommands.js.map +1 -1
- package/dist/cli/slashHandlers/krakenGraph.js +25 -7
- package/dist/cli/slashHandlers/krakenGraph.js.map +1 -1
- package/dist/cli/slashHandlers/provider.js +14 -4
- package/dist/cli/slashHandlers/provider.js.map +1 -1
- package/dist/cli/thinking.js +1 -0
- package/dist/cli/thinking.js.map +1 -1
- package/dist/cli/thinkingCapability.js +2 -0
- package/dist/cli/thinkingCapability.js.map +1 -1
- package/dist/cli/toolRegistry.js +22 -3
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/bashWriteWatch.js +195 -0
- package/dist/cli/tools/bashWriteWatch.js.map +1 -0
- package/dist/cli/tools/krakenModel.js +50 -0
- package/dist/cli/tools/krakenModel.js.map +1 -1
- package/dist/cli/tools/krakenRadio.js +50 -5
- package/dist/cli/tools/krakenRadio.js.map +1 -1
- package/dist/cli/tools/krakenWorktree.js +35 -6
- package/dist/cli/tools/krakenWorktree.js.map +1 -1
- package/dist/cli/tools/taskTool.js +328 -34
- package/dist/cli/tools/taskTool.js.map +1 -1
- package/dist/cli/tools/verifyDebtSpine.js +117 -0
- package/dist/cli/tools/verifyDebtSpine.js.map +1 -0
- package/package.json +2 -2
|
@@ -26,11 +26,12 @@ import { createKrakenWorktree, cleanupKrakenWorktree, formatWorktreeFooter, isKr
|
|
|
26
26
|
import { krakenTentacleStart, krakenTentacleEnd } from './krakenLive.js';
|
|
27
27
|
import { resolveWorktreeMode } from '../kraken/worktreeScheduling.js';
|
|
28
28
|
import { randomUUID } from 'node:crypto';
|
|
29
|
-
import { candidateInstructions, isKrakenSelectionEnabled, krakenRequiredChecks, parseCandidateReport, registerCandidate, reserveCandidateSlot, setKrakenCheckResults, } from '../kraken/candidateRegistry.js';
|
|
29
|
+
import { candidateInstructions, isKrakenSelectionEnabled, krakenRequiredChecks, parseCandidateReport, registerCandidate, reserveCandidateSlot, setKrakenCheckResults, setLastVerifyToolTrace, getLastVerifyToolTrace, } from '../kraken/candidateRegistry.js';
|
|
30
30
|
import { allUnknownCheckResults, parseVerifyReport } from '../kraken/verifyReport.js';
|
|
31
31
|
import { recordCandidateTokens } from '../kraken/metrics.js';
|
|
32
32
|
import { parseVerifyVerdict } from '@zelari/core';
|
|
33
33
|
import { startTentacleHeartbeat } from './tentacleHeartbeat.js';
|
|
34
|
+
import { emitVerifyDebtCleared, emitVerifyDebtOpen, enqueueVerifyDebtPersist, loadSessionEventsForVerifyDebt, replayOpenVerifyDebts, } from './verifyDebtSpine.js';
|
|
34
35
|
/**
|
|
35
36
|
* Wall-clock bound for the `task` tool wrapper (parent AgentHarness invoke).
|
|
36
37
|
* Must cover a `general` writer on a slow reasoning model (thinking:max,
|
|
@@ -95,24 +96,196 @@ const VERIFY_PROMPT = [
|
|
|
95
96
|
'Use status=unknown when you could NOT determine the outcome (degraded',
|
|
96
97
|
'tool, timeout, inconclusive evidence) — never guess pass.',
|
|
97
98
|
].join('\n');
|
|
98
|
-
/**
|
|
99
|
-
|
|
99
|
+
/** Sentinel taskId used by the legacy single-slot seam (tests). */
|
|
100
|
+
const SEED_TASK_ID = '__seed__';
|
|
101
|
+
/**
|
|
102
|
+
* K3.3 / F16: bucket key for callers with NO session id (the unit-test seam
|
|
103
|
+
* and pre-session turn boundaries). It IS the legacy `globalThis` slot, so an
|
|
104
|
+
* id-less caller keeps exactly the process-wide behaviour it had before.
|
|
105
|
+
*/
|
|
106
|
+
const LEGACY_SESSION_KEY = 'default';
|
|
107
|
+
/** K3.3 / F16: did the caller hand us a real session id? */
|
|
108
|
+
function isSessionScoped(sessionId) {
|
|
109
|
+
return typeof sessionId === 'string' && sessionId.trim().length > 0;
|
|
110
|
+
}
|
|
111
|
+
/** K3.3 / F16: bucket key of a session id (the legacy slot when id-less). */
|
|
112
|
+
function sessionKey(sessionId) {
|
|
113
|
+
return isSessionScoped(sessionId) ? sessionId : LEGACY_SESSION_KEY;
|
|
114
|
+
}
|
|
115
|
+
/** K3.3 / F16: spawn counters of every non-legacy session. */
|
|
116
|
+
function spawnCountMap() {
|
|
117
|
+
const g = globalThis;
|
|
118
|
+
if (!g.__zelariTaskSpawnCountBySession)
|
|
119
|
+
g.__zelariTaskSpawnCountBySession = new Map();
|
|
120
|
+
return g.__zelariTaskSpawnCountBySession;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* K3.3 / F16: charge one task spawn to `sessionId` and return THAT session's
|
|
124
|
+
* new count. Counting per session (same numeric cap as before) is what stops a
|
|
125
|
+
* concurrent session in the same process from eating another one's budget.
|
|
126
|
+
*/
|
|
127
|
+
function bumpTaskSpawnCount(sessionId) {
|
|
128
|
+
const g = globalThis;
|
|
129
|
+
const key = sessionKey(sessionId);
|
|
130
|
+
if (key === LEGACY_SESSION_KEY) {
|
|
131
|
+
g.__zelariTaskSpawnCount = (g.__zelariTaskSpawnCount ?? 0) + 1;
|
|
132
|
+
return g.__zelariTaskSpawnCount;
|
|
133
|
+
}
|
|
134
|
+
const next = (spawnCountMap().get(key) ?? 0) + 1;
|
|
135
|
+
spawnCountMap().set(key, next);
|
|
136
|
+
return next;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* K3.3 / F16: verify-debt bucket of ONE session — the legacy `globalThis` slot
|
|
140
|
+
* for an id-less caller, `__zelariGeneralVerifyDebtBySession` otherwise.
|
|
141
|
+
*/
|
|
142
|
+
function debtStore(sessionId) {
|
|
143
|
+
const g = globalThis;
|
|
144
|
+
const key = sessionKey(sessionId);
|
|
145
|
+
if (key === LEGACY_SESSION_KEY) {
|
|
146
|
+
if (!g.__zelariGeneralVerifyDebt)
|
|
147
|
+
g.__zelariGeneralVerifyDebt = new Map();
|
|
148
|
+
return g.__zelariGeneralVerifyDebt;
|
|
149
|
+
}
|
|
150
|
+
if (!g.__zelariGeneralVerifyDebtBySession)
|
|
151
|
+
g.__zelariGeneralVerifyDebtBySession = new Map();
|
|
152
|
+
let bucket = g.__zelariGeneralVerifyDebtBySession.get(key);
|
|
153
|
+
if (!bucket) {
|
|
154
|
+
bucket = new Map();
|
|
155
|
+
g.__zelariGeneralVerifyDebtBySession.set(key, bucket);
|
|
156
|
+
}
|
|
157
|
+
return bucket;
|
|
158
|
+
}
|
|
159
|
+
/** K3.3 / F16: every non-legacy session bucket (aggregate gate reads). */
|
|
160
|
+
function sessionDebtStores() {
|
|
161
|
+
const g = globalThis;
|
|
162
|
+
const bySession = g.__zelariGeneralVerifyDebtBySession;
|
|
163
|
+
return bySession ? [...bySession.values()] : [];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* K3.3 / F16: the buckets a read must consult — ONE session when `sessionId`
|
|
167
|
+
* is given, EVERY session (legacy bucket first) when it is omitted. The
|
|
168
|
+
* id-less read is the strict-done gate's view (`runOneTurn.ts` and
|
|
169
|
+
* `useChatTurn.ts` call it with no argument) and stays FAIL-CLOSED: debt open
|
|
170
|
+
* in ANY session blocks the turn.
|
|
171
|
+
*/
|
|
172
|
+
function debtScopes(sessionId) {
|
|
173
|
+
return isSessionScoped(sessionId) ? [debtStore(sessionId)] : [debtStore(), ...sessionDebtStores()];
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Reset the spawn counter (call at start of each parent user turn).
|
|
177
|
+
*
|
|
178
|
+
* K3.3 / F16: `sessionId` resets ONLY that session's budget and never touches
|
|
179
|
+
* a concurrent session; omitting it keeps the legacy process-wide reset used
|
|
180
|
+
* by the unit-test seam and the headless per-turn boundary.
|
|
181
|
+
*/
|
|
182
|
+
export function resetTaskSpawnCount(sessionId) {
|
|
100
183
|
const g = globalThis;
|
|
101
|
-
|
|
184
|
+
if (!isSessionScoped(sessionId)) {
|
|
185
|
+
g.__zelariTaskSpawnCount = 0;
|
|
186
|
+
g.__zelariTaskSpawnCountBySession?.clear();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (sessionKey(sessionId) === LEGACY_SESSION_KEY) {
|
|
190
|
+
g.__zelariTaskSpawnCount = 0;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
g.__zelariTaskSpawnCountBySession?.delete(sessionId);
|
|
102
194
|
}
|
|
103
|
-
/**
|
|
104
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Reset the general⇒verify obligation (call at start of each parent user turn).
|
|
197
|
+
*
|
|
198
|
+
* K3.3 / F16: `sessionId` drops ONLY that session's debt and never a
|
|
199
|
+
* concurrent session's; omitting it keeps the legacy process-wide reset used
|
|
200
|
+
* by the unit-test seam and the headless per-turn boundary.
|
|
201
|
+
*/
|
|
202
|
+
export function resetTaskVerifyObligation(sessionId) {
|
|
105
203
|
const g = globalThis;
|
|
106
|
-
|
|
204
|
+
if (!isSessionScoped(sessionId)) {
|
|
205
|
+
g.__zelariGeneralVerifyDebt = new Map();
|
|
206
|
+
g.__zelariGeneralVerifyDebtBySession?.clear();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (sessionKey(sessionId) === LEGACY_SESSION_KEY) {
|
|
210
|
+
g.__zelariGeneralVerifyDebt = new Map();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
g.__zelariGeneralVerifyDebtBySession?.delete(sessionId);
|
|
107
214
|
}
|
|
108
215
|
/**
|
|
109
216
|
* Open verify obligation, or null when every general this turn has been
|
|
110
217
|
* verified PASS by the runtime auto-spawn (t78). Consulted by the headless
|
|
111
218
|
* strict-done gate — an open obligation closes the turn blocked (exit 4).
|
|
219
|
+
*
|
|
220
|
+
* K1.1: returns the first open record (any one is enough to block). The map
|
|
221
|
+
* may carry MORE records than this returns; use `listTaskVerifyObligations()`
|
|
222
|
+
* or `hasOpenTaskVerifyDebt()` to see the full state.
|
|
223
|
+
*
|
|
224
|
+
* K3.3 / F16: scoped to ONE session when `sessionId` is given; an id-less read
|
|
225
|
+
* aggregates every session (fail-closed), so no session's debt is invisible.
|
|
112
226
|
*/
|
|
113
|
-
export function taskVerifyObligation() {
|
|
114
|
-
const
|
|
115
|
-
|
|
227
|
+
export function taskVerifyObligation(sessionId) {
|
|
228
|
+
for (const store of debtScopes(sessionId)) {
|
|
229
|
+
if (store.size === 0)
|
|
230
|
+
continue;
|
|
231
|
+
// Map iteration is insertion-ordered; the newest insert wins (matches the
|
|
232
|
+
// pre-K1.1 "newest wins" semantics for the single open record).
|
|
233
|
+
const last = store.keys().next().value;
|
|
234
|
+
if (last)
|
|
235
|
+
return store.get(last) ?? null;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* K1.1: number of open verify obligations (strict-gate invariant: > 0 ⇒
|
|
241
|
+
* blocked). Useful for diagnostics and tests; the strict gate itself keeps
|
|
242
|
+
* using the boolean `taskVerifyObligation() != null` check.
|
|
243
|
+
* K3.3 / F16: `sessionId` scopes the listing (id-less = every session).
|
|
244
|
+
*/
|
|
245
|
+
export function listTaskVerifyObligations(sessionId) {
|
|
246
|
+
return debtScopes(sessionId).flatMap((store) => [...store.values()]);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* K1.1: did any general leave a runtime verify obligation open? Strict-gate
|
|
250
|
+
* friendly boolean — `true` ⇒ the strict-done gate must block the turn.
|
|
251
|
+
* K3.3 / F16: `sessionId` scopes the check (id-less = every session).
|
|
252
|
+
*/
|
|
253
|
+
export function hasOpenTaskVerifyDebt(sessionId) {
|
|
254
|
+
return debtScopes(sessionId).some((store) => store.size > 0);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* K1.1: register (or replace) the verify obligation for one specific task.
|
|
258
|
+
* Used by `runAutoVerifyAfterGeneral` to record the debt of EACH general it
|
|
259
|
+
* services — multiple tasks can be open at the same time.
|
|
260
|
+
*
|
|
261
|
+
* K3.3 / F16: `sessionId` writes the debt into THAT session's bucket (id-less
|
|
262
|
+
* callers keep the legacy bucket), so a companion-serve session never opens
|
|
263
|
+
* debt in another session's view.
|
|
264
|
+
*/
|
|
265
|
+
export function addTaskVerifyObligation(taskId, debt, sessionId) {
|
|
266
|
+
debtStore(sessionId).set(taskId, debt);
|
|
267
|
+
enqueueVerifyDebtPersist(() => emitVerifyDebtOpen(undefined, {
|
|
268
|
+
taskId,
|
|
269
|
+
description: debt.description,
|
|
270
|
+
detail: debt.detail,
|
|
271
|
+
timestamp: Date.now(),
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* K1.1: clear the verify obligation for ONE task. Used on a PASS of THAT
|
|
276
|
+
* task's verify tentacle — clearing is per-task, so a sibling general's
|
|
277
|
+
* debt stays open.
|
|
278
|
+
*
|
|
279
|
+
* K3.3 / F16: clear inside ONE bucket — the session that opened the debt
|
|
280
|
+
* (id-less callers keep the legacy bucket); a concurrent session's debt with
|
|
281
|
+
* the same taskId is never touched.
|
|
282
|
+
*/
|
|
283
|
+
export function clearTaskVerifyObligation(taskId, sessionId) {
|
|
284
|
+
const store = debtStore(sessionId);
|
|
285
|
+
if (!store.has(taskId))
|
|
286
|
+
return;
|
|
287
|
+
store.delete(taskId);
|
|
288
|
+
enqueueVerifyDebtPersist(() => emitVerifyDebtCleared(undefined, { taskId }));
|
|
116
289
|
}
|
|
117
290
|
/**
|
|
118
291
|
* F3.3 (verify trust chain): memory only on PASS. `true` when no general⇒verify
|
|
@@ -124,17 +297,52 @@ export function taskVerifyObligation() {
|
|
|
124
297
|
* and both `promoteOpsKnowledgeSafe` sites on this.
|
|
125
298
|
*/
|
|
126
299
|
export function outcomeMemoryAllowed() {
|
|
127
|
-
return
|
|
300
|
+
return !hasOpenTaskVerifyDebt();
|
|
128
301
|
}
|
|
129
302
|
/**
|
|
130
303
|
* Test seam: seed an open general⇒verify obligation without running a tentacle.
|
|
131
304
|
* Production code must never call this — the auto-verify chain owns the slot.
|
|
305
|
+
*
|
|
306
|
+
* K1.1: the optional `taskId` argument disambiguates which slot to seed; when
|
|
307
|
+
* omitted, the legacy sentinel key `'__seed__'` is used so the existing
|
|
308
|
+
* strict-exit test (`runOneTurn.strictExit.test.ts`) keeps working unchanged.
|
|
309
|
+
* K3.3 / F16: the optional `sessionId` seeds that session's bucket (omitted ⇒
|
|
310
|
+
* the legacy bucket, which the id-less strict gate still sees).
|
|
132
311
|
*/
|
|
133
|
-
export function seedTaskVerifyObligation(debt) {
|
|
134
|
-
const
|
|
135
|
-
|
|
312
|
+
export function seedTaskVerifyObligation(debt, taskId = SEED_TASK_ID, sessionId) {
|
|
313
|
+
const store = debtStore(sessionId);
|
|
314
|
+
if (debt === null) {
|
|
315
|
+
store.delete(taskId);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
store.set(taskId, debt);
|
|
136
319
|
}
|
|
137
|
-
/**
|
|
320
|
+
/**
|
|
321
|
+
* K1.5 / F5: merge un-cleared `verify.debt_open` events into the process
|
|
322
|
+
* cache WITHOUT emitting (replay must not re-append). Existing slots
|
|
323
|
+
* (including the test seed) are overwritten for matching taskIds and
|
|
324
|
+
* otherwise left alone — callers that want a blank cache reset first.
|
|
325
|
+
*
|
|
326
|
+
* K3.3 / F16: hydrate the bucket of the session the log belongs to
|
|
327
|
+
* (`sessionId`), never the process-wide/other-session view.
|
|
328
|
+
*/
|
|
329
|
+
export function hydrateTaskVerifyDebtFromEvents(events, sessionId) {
|
|
330
|
+
const open = replayOpenVerifyDebts(events);
|
|
331
|
+
const store = debtStore(sessionId);
|
|
332
|
+
for (const [taskId, debt] of open) {
|
|
333
|
+
store.set(taskId, debt);
|
|
334
|
+
}
|
|
335
|
+
return open.size;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* K1.5: hydrate the cache from `<sessionsDir>/<sessionId>/events.jsonl`.
|
|
339
|
+
* K3.3 / F16: the replay lands in the bucket of the session that owns the log.
|
|
340
|
+
*/
|
|
341
|
+
export async function hydrateTaskVerifyDebtFromSpine(source) {
|
|
342
|
+
const events = await loadSessionEventsForVerifyDebt(source);
|
|
343
|
+
return hydrateTaskVerifyDebtFromEvents(events, source.sessionId);
|
|
344
|
+
}
|
|
345
|
+
/** Max concurrent/serial task spawns per parent turn, PER SESSION (env override). */
|
|
138
346
|
export function maxTaskSpawnsPerTurn() {
|
|
139
347
|
const raw = process.env.ZELARI_KRAKEN_MAX_TASK_SPAWNS;
|
|
140
348
|
if (raw === undefined || raw === '')
|
|
@@ -256,10 +464,14 @@ async function rememberVerifiedGeneralOutcome(opts) {
|
|
|
256
464
|
* the task result (or null when nothing ran — e.g. explore/verify agents).
|
|
257
465
|
*/
|
|
258
466
|
export async function runAutoVerifyAfterGeneral(opts) {
|
|
259
|
-
const g = globalThis;
|
|
260
467
|
// Debt exists from the moment the general finished — even a mid-chain
|
|
261
468
|
// failure must not leave the work silently "verified".
|
|
262
|
-
|
|
469
|
+
//
|
|
470
|
+
// K1.1: key the slot by this general's `agentId` (always populated by
|
|
471
|
+
// TentacleSuccess) so a sibling general's PASS cannot clear our debt.
|
|
472
|
+
const debtKey = opts.general.agentId ?? opts.original.description;
|
|
473
|
+
// K3.3 / F16: the debt is opened in THIS session's bucket (opts.sessionId).
|
|
474
|
+
addTaskVerifyObligation(debtKey, { description: opts.original.description }, opts.sessionId);
|
|
263
475
|
// t94: live phase captions on the general's activity row (agent_status)
|
|
264
476
|
// mirrored into the radio 'progress' trail — the parent sees the general
|
|
265
477
|
// flip to "verifying…" and then to the verdict without polling.
|
|
@@ -309,8 +521,20 @@ export async function runAutoVerifyAfterGeneral(opts) {
|
|
|
309
521
|
sessionId: opts.sessionId,
|
|
310
522
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
311
523
|
});
|
|
524
|
+
// K1.3 (rev): every inner-verify return MUST publish its tool trace to
|
|
525
|
+
// the per-turn channel `__zelariVerifyToolTrace` BEFORE we read it back
|
|
526
|
+
// at the floor check, because the inner verify is a direct `runTentacle`
|
|
527
|
+
// call that does NOT go through `createTaskTool.execute` (which is the
|
|
528
|
+
// only other publisher of that channel). Without this publish, (1) an
|
|
529
|
+
// honest PASS with tool captures still fails the floor (positive path
|
|
530
|
+
// broken), and (2) a stale trace from an earlier OUTER verify could be
|
|
531
|
+
// seen here (stale-trace leakage). The helper centralizes the rule.
|
|
532
|
+
const publishInnerVerifyTrace = (result) => {
|
|
533
|
+
setLastVerifyToolTrace(result.ok ? result.toolTrace ?? [] : []);
|
|
534
|
+
};
|
|
312
535
|
emitVerifyPhase('verifying…');
|
|
313
536
|
let verify = await runVerify(`verify: ${opts.original.description}`);
|
|
537
|
+
publishInnerVerifyTrace(verify);
|
|
314
538
|
// Parsed like the graph executor: last VERDICT trailer wins; a failed run is
|
|
315
539
|
// an unknown (degraded observation is never proof).
|
|
316
540
|
let verdict = verify.ok ? parseVerifyVerdict(verify.result).verdict : 'unknown';
|
|
@@ -342,7 +566,10 @@ export async function runAutoVerifyAfterGeneral(opts) {
|
|
|
342
566
|
});
|
|
343
567
|
if (!rework.ok) {
|
|
344
568
|
const detail = `rework round ${round} failed: ${rework.error}`;
|
|
345
|
-
|
|
569
|
+
addTaskVerifyObligation(debtKey, {
|
|
570
|
+
description: opts.original.description,
|
|
571
|
+
detail,
|
|
572
|
+
}, opts.sessionId);
|
|
346
573
|
appendKrakenRadio(opts.parentCwd, opts.sessionId, {
|
|
347
574
|
kind: 'error',
|
|
348
575
|
agent: 'general',
|
|
@@ -355,12 +582,36 @@ export async function runAutoVerifyAfterGeneral(opts) {
|
|
|
355
582
|
`(${rework.error}). Work stays UNVERIFIED; strict done will close this turn blocked.`);
|
|
356
583
|
}
|
|
357
584
|
verify = await runVerify(`verify: ${opts.original.description} (rework ${round})`);
|
|
585
|
+
publishInnerVerifyTrace(verify);
|
|
358
586
|
verdict = verify.ok ? parseVerifyVerdict(verify.result).verdict : 'unknown';
|
|
359
587
|
findings = verify.ok ? parseVerifyVerdict(verify.result).findings : '';
|
|
360
588
|
}
|
|
361
589
|
}
|
|
362
590
|
if (verdict === 'pass') {
|
|
363
|
-
|
|
591
|
+
// K1.3: the verify trailer may say PASS but the verify must also have
|
|
592
|
+
// executed ≥ 1 tool (real bash / read / etc.) before the debt is cleared.
|
|
593
|
+
// A bare-text PASS — "I read the diff and it's fine" with no tool
|
|
594
|
+
// captures — is narrative-only and cannot clear the obligation.
|
|
595
|
+
const trace = getLastVerifyToolTrace();
|
|
596
|
+
const instrumental = Array.isArray(trace) && trace.length > 0;
|
|
597
|
+
if (!instrumental) {
|
|
598
|
+
const detail = 'verify produced VERDICT: PASS but executed no tool — narrative-only PASS does not satisfy the auto-verify floor';
|
|
599
|
+
addTaskVerifyObligation(debtKey, {
|
|
600
|
+
description: opts.original.description,
|
|
601
|
+
detail,
|
|
602
|
+
}, opts.sessionId);
|
|
603
|
+
emitVerifyPhase('verify PASS without tool evidence', false, 'failed');
|
|
604
|
+
appendKrakenRadio(opts.parentCwd, opts.sessionId, {
|
|
605
|
+
kind: 'error',
|
|
606
|
+
agent: 'verify',
|
|
607
|
+
description: `verify: ${opts.original.description}`,
|
|
608
|
+
detail,
|
|
609
|
+
ok: false,
|
|
610
|
+
});
|
|
611
|
+
return (`\n\n[kraken:auto-verify] ${detail}. Work stays UNVERIFIED; ` +
|
|
612
|
+
`strict done will close this turn blocked.`);
|
|
613
|
+
}
|
|
614
|
+
clearTaskVerifyObligation(debtKey, opts.sessionId);
|
|
364
615
|
await rememberVerifiedGeneralOutcome(opts);
|
|
365
616
|
emitVerifyPhase('verify PASS', true, 'completed');
|
|
366
617
|
return `\n\n[kraken:auto-verify] verify PASS — general⇒verify obligation satisfied.`;
|
|
@@ -372,7 +623,10 @@ export async function runAutoVerifyAfterGeneral(opts) {
|
|
|
372
623
|
: verify.ok
|
|
373
624
|
? 'verify produced no parseable VERDICT — unverified'
|
|
374
625
|
: `verify tentacle failed: ${verify.error}`;
|
|
375
|
-
|
|
626
|
+
addTaskVerifyObligation(debtKey, {
|
|
627
|
+
description: opts.original.description,
|
|
628
|
+
detail,
|
|
629
|
+
}, opts.sessionId);
|
|
376
630
|
appendKrakenRadio(opts.parentCwd, opts.sessionId, {
|
|
377
631
|
kind: 'error',
|
|
378
632
|
agent: 'verify',
|
|
@@ -647,18 +901,42 @@ export async function runTentacle(opts) {
|
|
|
647
901
|
// never share the parent tree, and merges stay sequential.
|
|
648
902
|
let worktree = null;
|
|
649
903
|
let effectiveCwd = opts.cwdOverride || parentCwd;
|
|
904
|
+
const worktreeMode = resolveWorktreeMode(process.env.ZELARI_KRAKEN_WORKTREE);
|
|
650
905
|
const wantWt = agent === 'general' &&
|
|
651
906
|
deps.allowWorktree !== false &&
|
|
652
|
-
(isKrakenWorktreeEnabled() ||
|
|
653
|
-
resolveWorktreeMode(process.env.ZELARI_KRAKEN_WORKTREE) === 'auto');
|
|
907
|
+
(isKrakenWorktreeEnabled() || worktreeMode === 'auto');
|
|
654
908
|
if (wantWt) {
|
|
655
909
|
try {
|
|
656
910
|
worktree = await createKrakenWorktree(parentCwd, args.description);
|
|
657
911
|
if (worktree)
|
|
658
912
|
effectiveCwd = worktree.path;
|
|
659
913
|
}
|
|
660
|
-
catch {
|
|
914
|
+
catch (err) {
|
|
915
|
+
// F12 (K2.4): creation failed → fail OPEN (the tentacle still runs) but
|
|
916
|
+
// make the degradation LOUD. Under ZELARI_KRAKEN_WORKTREE=auto the graph
|
|
917
|
+
// scheduler admits overlapping writers in parallel ONLY because it
|
|
918
|
+
// assumes worktree isolation; falling back silently would run them
|
|
919
|
+
// unisolated in the shared parent tree with no trace. The radio event
|
|
920
|
+
// plus the deps callback let the executor stop rescuing overlapping
|
|
921
|
+
// writers (serial admission) for the rest of the run.
|
|
922
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
661
923
|
worktree = null;
|
|
924
|
+
appendKrakenRadio(parentCwd, sessionId, {
|
|
925
|
+
kind: 'worktree.fallback_shared_tree',
|
|
926
|
+
agent,
|
|
927
|
+
thoroughness,
|
|
928
|
+
description: args.description,
|
|
929
|
+
detail: `worktree creation failed — running in the shared parent tree: ${reason}`,
|
|
930
|
+
mode: worktreeMode,
|
|
931
|
+
reason,
|
|
932
|
+
...(opts.nodeId !== undefined ? { nodeId: opts.nodeId } : {}),
|
|
933
|
+
ok: false,
|
|
934
|
+
});
|
|
935
|
+
deps.onWorktreeFallback?.({
|
|
936
|
+
reason,
|
|
937
|
+
mode: worktreeMode,
|
|
938
|
+
...(opts.nodeId !== undefined ? { nodeId: opts.nodeId } : {}),
|
|
939
|
+
});
|
|
662
940
|
}
|
|
663
941
|
}
|
|
664
942
|
appendKrakenRadio(parentCwd, sessionId, {
|
|
@@ -971,7 +1249,7 @@ export async function runTentacle(opts) {
|
|
|
971
1249
|
if (!kept && isKrakenWorktreeAutoMergeEnabled()) {
|
|
972
1250
|
emitPhase('merging…');
|
|
973
1251
|
try {
|
|
974
|
-
merge = await mergeKrakenWorktree(worktree, { message: `kraken: merge ${args.description.slice(0, 80)}
|
|
1252
|
+
merge = await mergeKrakenWorktree(worktree, { message: `kraken: merge ${args.description.slice(0, 80)}`, sessionId });
|
|
975
1253
|
}
|
|
976
1254
|
catch (err) {
|
|
977
1255
|
merge = {
|
|
@@ -1034,7 +1312,13 @@ export async function runTentacle(opts) {
|
|
|
1034
1312
|
let memoryId;
|
|
1035
1313
|
if (deps.memoryService && deps.memoryAutoWrite !== false) {
|
|
1036
1314
|
try {
|
|
1037
|
-
|
|
1315
|
+
// F4 (K1.4): memory only when the CANONICAL trailer parser says PASS.
|
|
1316
|
+
// The previous free regex /(?:VERDICT:\s*PASS|status:\s*pass)/i matched
|
|
1317
|
+
// any "status: pass" string inside the body (tables, quotes, prose) and
|
|
1318
|
+
// could turn an actual trailer `VERDICT: FAIL` into a memory PASS.
|
|
1319
|
+
// `parseVerifyVerdict` is the last-trailer-wins parser used everywhere
|
|
1320
|
+
// else in the engine — reuse it so memory writes share the same gate.
|
|
1321
|
+
const verifyPass = agent === 'verify' && parseVerifyVerdict(result).verdict === 'pass';
|
|
1038
1322
|
// F3.3 (verify trust chain): memory only on PASS.
|
|
1039
1323
|
// - verify/verification: written only when its OWN verdict parsed PASS;
|
|
1040
1324
|
// - general/outcome: NEVER here — a general result is durable only once
|
|
@@ -1149,13 +1433,14 @@ export function createTaskTool(deps, policy = {}) {
|
|
|
1149
1433
|
candidateSlot = slot.index;
|
|
1150
1434
|
}
|
|
1151
1435
|
const thoroughness = args.thoroughness ?? 'medium';
|
|
1152
|
-
const sessionId = ctx.sessionId ||
|
|
1436
|
+
const sessionId = ctx.sessionId || LEGACY_SESSION_KEY;
|
|
1153
1437
|
const parentCwd = ctx.cwd || process.cwd();
|
|
1154
|
-
// Per-
|
|
1155
|
-
|
|
1156
|
-
|
|
1438
|
+
// Per-SESSION spawn cap (Kraken K3 + K3.3 / F16): the numeric limit is
|
|
1439
|
+
// unchanged, but it is counted per sessionId — a concurrent session in
|
|
1440
|
+
// the same process (companion serve) cannot exhaust this session's
|
|
1441
|
+
// budget. Reset via resetTaskSpawnCount(sessionId) each parent turn.
|
|
1157
1442
|
const spawnCap = maxTaskSpawnsPerTurn();
|
|
1158
|
-
if (
|
|
1443
|
+
if (bumpTaskSpawnCount(sessionId) > spawnCap) {
|
|
1159
1444
|
return typedErr(`task: spawn cap reached (${spawnCap}). Finish the current slice or raise ZELARI_KRAKEN_MAX_TASK_SPAWNS.`);
|
|
1160
1445
|
}
|
|
1161
1446
|
const res = await runTentacle({
|
|
@@ -1224,11 +1509,18 @@ export function createTaskTool(deps, policy = {}) {
|
|
|
1224
1509
|
// never proof. Only runs when a selection exists this turn (required
|
|
1225
1510
|
// checks come from a `selected` verdict — Fase 6 routing).
|
|
1226
1511
|
if (agent === 'verify') {
|
|
1512
|
+
// K1.3: ALWAYS anchor the latest verify tentacle's tool trace on the
|
|
1513
|
+
// per-turn channel so the auto-verify floor (≥ 1 tool execution per
|
|
1514
|
+
// PASS) is observable even when no selection ran this turn (no
|
|
1515
|
+
// required-checks path). The strict gate for PASS is
|
|
1516
|
+
// `getLastVerifyToolTrace().length > 0`.
|
|
1517
|
+
const verifyTrace = res.ok ? res.toolTrace ?? [] : [];
|
|
1518
|
+
setLastVerifyToolTrace(verifyTrace);
|
|
1227
1519
|
const required = krakenRequiredChecks();
|
|
1228
1520
|
if (required.length > 0) {
|
|
1229
1521
|
setKrakenCheckResults(res.ok
|
|
1230
1522
|
? parseVerifyReport(res.result, required)
|
|
1231
|
-
: allUnknownCheckResults(required, `verify tentacle failed: ${res.error}`),
|
|
1523
|
+
: allUnknownCheckResults(required, `verify tentacle failed: ${res.error}`), verifyTrace);
|
|
1232
1524
|
}
|
|
1233
1525
|
}
|
|
1234
1526
|
if (!res.ok)
|
|
@@ -1256,11 +1548,13 @@ export function createTaskTool(deps, policy = {}) {
|
|
|
1256
1548
|
}
|
|
1257
1549
|
catch (err) {
|
|
1258
1550
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1259
|
-
|
|
1260
|
-
|
|
1551
|
+
// K1.1: key the debt by the runtime agentId (when present) so it
|
|
1552
|
+
// matches the key opened by runAutoVerifyAfterGeneral; fall back
|
|
1553
|
+
// to the description so an early throw still lands in the same slot.
|
|
1554
|
+
addTaskVerifyObligation(res.agentId ?? args.description, {
|
|
1261
1555
|
description: args.description,
|
|
1262
1556
|
detail: `auto-verify chain failed: ${msg}`,
|
|
1263
|
-
};
|
|
1557
|
+
}, sessionId);
|
|
1264
1558
|
result += `\n\n[kraken:auto-verify] auto-verify chain failed (${msg}) — work stays UNVERIFIED.`;
|
|
1265
1559
|
}
|
|
1266
1560
|
}
|