@dadado/agent-kit-cli 6.3.0 → 6.4.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/dashboard/dashboard-data.mjs +14 -2
- package/dashboard/dashboard.html +139 -4
- package/dashboard/lib/chat-sessions.d.mts +63 -0
- package/dashboard/lib/chat-sessions.mjs +430 -0
- package/dashboard/serve.mjs +5 -1
- package/dist/{add-VA2W7I6P.js → add-HFDX3U3R.js} +2 -2
- package/dist/{chunk-6GYKOHIL.js → chunk-3OM7JODZ.js} +2 -2
- package/dist/{chunk-Z7RYIHHX.js → chunk-6OTJEQEC.js} +3 -3
- package/dist/{chunk-MAPOQEA2.js → chunk-6RRG3WOE.js} +8 -1
- package/dist/{chunk-6KUSG7BJ.js → chunk-7BEHUBEJ.js} +3 -1
- package/dist/{chunk-VQBI3JVS.js → chunk-ANPSYWQO.js} +2 -2
- package/dist/{chunk-WMVMBPCR.js → chunk-AXLXEAVJ.js} +1 -1
- package/dist/{chunk-25J2SMJQ.js → chunk-E465FAWM.js} +1 -1
- package/dist/{chunk-U26MRIWN.js → chunk-H46W7NHC.js} +1 -1
- package/dist/{chunk-NNKOWUWM.js → chunk-HHR56LJQ.js} +64 -28
- package/dist/{chunk-SV2BR2LZ.js → chunk-IMIVTDMK.js} +7 -7
- package/dist/chunk-IRTS7WEA.js +374 -0
- package/dist/{chunk-MO63KZBP.js → chunk-KX3OUY4W.js} +8 -6
- package/dist/chunk-MWTXI6C5.js +223 -0
- package/dist/{chunk-JEI6HACW.js → chunk-NJVWTROP.js} +24 -377
- package/dist/{chunk-OVOXRH5W.js → chunk-Q4PCMRM5.js} +1 -1
- package/dist/{chunk-GUQYNUV4.js → chunk-R7DA4KXG.js} +1 -1
- package/dist/{chunk-OWWFKGKS.js → chunk-SE7ZIRLX.js} +2 -2
- package/dist/{chunk-KVY425NM.js → chunk-TZ5XYIEF.js} +4 -2
- package/dist/{chunk-Z7ABX5QI.js → chunk-WOHZ4T5V.js} +4 -2
- package/dist/{chunk-YRFSTHR5.js → chunk-XEEB4YSA.js} +13 -219
- package/dist/{chunk-QTBTKEXK.js → chunk-ZPZVROS5.js} +1 -1
- package/dist/{contribute-NLM2BN6Z.js → contribute-PP7HMPW7.js} +3 -3
- package/dist/{countdown-QXJD6TDB.js → countdown-LAUZMI7U.js} +10 -8
- package/dist/{diff-HHG4JEDE.js → diff-HYWC4G6Y.js} +4 -4
- package/dist/{doctor-TWF2M3S3.js → doctor-2X3HBRJJ.js} +8 -8
- package/dist/engine-info-4ZWEIHSX.js +89 -0
- package/dist/{ground-control-NMYYIVCV.js → ground-control-WKV2BMOW.js} +5 -4
- package/dist/{guard-EHXF76ID.js → guard-ZZRQW3DJ.js} +107 -8
- package/dist/{hook-VP7FHRSZ.js → hook-UEYR4M3Z.js} +10 -4
- package/dist/index.js +26 -22
- package/dist/{init-UOTV4ASJ.js → init-UF5VEIJX.js} +11 -11
- package/dist/{install-VCHPXQV3.js → install-6ILVA22C.js} +11 -11
- package/dist/{run-R66G276Q.js → run-2EKD2BEC.js} +12 -9
- package/dist/{run-plan-24Z5K4P3.js → run-plan-G72XMC5Z.js} +11 -7
- package/dist/sessions-O7MYEMCT.js +343 -0
- package/dist/{setup-global-M6LKGJHQ.js → setup-global-ZZEY5KWE.js} +5 -5
- package/dist/{status-5UKVIKOI.js → status-4LK2QXVQ.js} +6 -6
- package/dist/{update-VQBGEUWS.js → update-FPR2NZND.js} +9 -9
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
INTERACTIVE_ALLOW_RULES,
|
|
2
3
|
INTERACTIVE_DENY_RULES
|
|
3
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6RRG3WOE.js";
|
|
4
5
|
import {
|
|
5
6
|
assertRealContained,
|
|
6
7
|
assertRealParentContained,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
resolveContained,
|
|
11
12
|
saveManagedHashLedger,
|
|
12
13
|
shouldPreserveCustomizedOverlay
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-7BEHUBEJ.js";
|
|
14
15
|
import {
|
|
15
16
|
ensureDir,
|
|
16
17
|
fileExists
|
|
@@ -149,17 +150,18 @@ var ClaudeSettingsLedgerInvalidError = class extends Error {
|
|
|
149
150
|
path;
|
|
150
151
|
};
|
|
151
152
|
function emptyLedger() {
|
|
152
|
-
return { schemaVersion: 1, denyRowsWritten: [], hookEntriesWritten: [] };
|
|
153
|
+
return { schemaVersion: 1, denyRowsWritten: [], allowRowsWritten: [], hookEntriesWritten: [] };
|
|
153
154
|
}
|
|
154
155
|
function hookKey(r) {
|
|
155
156
|
return `${r.event}\0${r.marker}`;
|
|
156
157
|
}
|
|
157
|
-
function normalize(denyRows, hooks) {
|
|
158
|
+
function normalize(denyRows, allowRows, hooks) {
|
|
158
159
|
const seen = /* @__PURE__ */ new Map();
|
|
159
160
|
for (const h of hooks) seen.set(hookKey(h), { event: h.event, marker: h.marker });
|
|
160
161
|
return {
|
|
161
162
|
schemaVersion: 1,
|
|
162
163
|
denyRowsWritten: [...new Set(denyRows)].sort(),
|
|
164
|
+
allowRowsWritten: [...new Set(allowRows)].sort(),
|
|
163
165
|
hookEntriesWritten: [...seen.values()].sort(
|
|
164
166
|
(a, b) => a.event.localeCompare(b.event) || a.marker.localeCompare(b.marker)
|
|
165
167
|
)
|
|
@@ -178,6 +180,8 @@ function parseLedger(raw) {
|
|
|
178
180
|
if (!Array.isArray(o.denyRowsWritten) || !o.denyRowsWritten.every((r) => typeof r === "string")) {
|
|
179
181
|
return null;
|
|
180
182
|
}
|
|
183
|
+
const allowRows = o.allowRowsWritten ?? [];
|
|
184
|
+
if (!Array.isArray(allowRows) || !allowRows.every((r) => typeof r === "string")) return null;
|
|
181
185
|
if (!Array.isArray(o.hookEntriesWritten)) return null;
|
|
182
186
|
const hooks = [];
|
|
183
187
|
for (const h of o.hookEntriesWritten) {
|
|
@@ -189,7 +193,7 @@ function parseLedger(raw) {
|
|
|
189
193
|
marker: h.marker
|
|
190
194
|
});
|
|
191
195
|
}
|
|
192
|
-
return normalize(o.denyRowsWritten, hooks);
|
|
196
|
+
return normalize(o.denyRowsWritten, allowRows, hooks);
|
|
193
197
|
}
|
|
194
198
|
async function readState(root) {
|
|
195
199
|
const abs = resolveContained(root, CLAUDE_SETTINGS_LEDGER_REL);
|
|
@@ -224,6 +228,7 @@ async function saveClaudeSettingsLedger(root, additions, opts = {}) {
|
|
|
224
228
|
const base = state.kind === "ok" ? state.ledger : emptyLedger();
|
|
225
229
|
const merged = normalize(
|
|
226
230
|
[...base.denyRowsWritten, ...additions.denyRowsWritten ?? []],
|
|
231
|
+
[...base.allowRowsWritten, ...additions.allowRowsWritten ?? []],
|
|
227
232
|
[...base.hookEntriesWritten, ...additions.hookEntriesWritten ?? []]
|
|
228
233
|
);
|
|
229
234
|
await mkdir(path2.dirname(abs), { recursive: true });
|
|
@@ -300,8 +305,12 @@ function mergeSessionStartHookIntoSettings(existingRaw, opts = {}) {
|
|
|
300
305
|
};
|
|
301
306
|
}
|
|
302
307
|
}
|
|
303
|
-
const pending = { denyRows: [], hookEntries: [] };
|
|
304
|
-
const additions = {
|
|
308
|
+
const pending = { denyRows: [], allowRows: [], hookEntries: [] };
|
|
309
|
+
const additions = {
|
|
310
|
+
denyRowsWritten: [],
|
|
311
|
+
allowRowsWritten: [],
|
|
312
|
+
hookEntriesWritten: []
|
|
313
|
+
};
|
|
305
314
|
const hooks = isPlainObject(root.hooks) ? { ...root.hooks } : {};
|
|
306
315
|
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
307
316
|
const preToolUse = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
|
|
@@ -342,8 +351,17 @@ function mergeSessionStartHookIntoSettings(existingRaw, opts = {}) {
|
|
|
342
351
|
(acc, next, i) => i === 0 ? next : combineStatus(acc, next),
|
|
343
352
|
"unchanged"
|
|
344
353
|
);
|
|
345
|
-
const
|
|
346
|
-
const
|
|
354
|
+
const ctx = { ledger, authorized };
|
|
355
|
+
const denyAdded = mergeKitRuleRows(root, "deny", INTERACTIVE_DENY_RULES, ctx, pending, additions);
|
|
356
|
+
const allowAdded = mergeKitRuleRows(
|
|
357
|
+
root,
|
|
358
|
+
"allow",
|
|
359
|
+
INTERACTIVE_ALLOW_RULES,
|
|
360
|
+
ctx,
|
|
361
|
+
pending,
|
|
362
|
+
additions
|
|
363
|
+
);
|
|
364
|
+
const finalStatus = (denyAdded || allowAdded) && status === "unchanged" ? "refreshed" : status;
|
|
347
365
|
return {
|
|
348
366
|
content: `${JSON.stringify(root, null, 2)}
|
|
349
367
|
`,
|
|
@@ -355,23 +373,25 @@ function mergeSessionStartHookIntoSettings(existingRaw, opts = {}) {
|
|
|
355
373
|
function combineStatus(a, b) {
|
|
356
374
|
return a === "unchanged" && b !== "unchanged" ? "refreshed" : a;
|
|
357
375
|
}
|
|
358
|
-
function
|
|
376
|
+
function mergeKitRuleRows(root, kind, rules, ctx, pending, additions) {
|
|
359
377
|
if (root.permissions !== void 0 && !isPlainObject(root.permissions)) return false;
|
|
360
378
|
const permissions = isPlainObject(root.permissions) ? { ...root.permissions } : {};
|
|
361
|
-
if (permissions
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const missing = INTERACTIVE_DENY_RULES.filter(
|
|
368
|
-
(rule) => !deny.includes(rule) && !written.has(rule)
|
|
379
|
+
if (permissions[kind] !== void 0 && !Array.isArray(permissions[kind])) return false;
|
|
380
|
+
const list = Array.isArray(permissions[kind]) ? [...permissions[kind]] : [];
|
|
381
|
+
const pendingRows = kind === "deny" ? pending.denyRows : pending.allowRows;
|
|
382
|
+
const written = kind === "deny" ? additions.denyRowsWritten : additions.allowRowsWritten;
|
|
383
|
+
const ledgerRows = new Set(
|
|
384
|
+
(kind === "deny" ? ctx.ledger?.denyRowsWritten : ctx.ledger?.allowRowsWritten) ?? []
|
|
369
385
|
);
|
|
386
|
+
for (const rule of rules) {
|
|
387
|
+
if (list.includes(rule)) written.push(rule);
|
|
388
|
+
}
|
|
389
|
+
const missing = rules.filter((rule) => !list.includes(rule) && !ledgerRows.has(rule));
|
|
370
390
|
const toAdd = ctx.ledger === null && !ctx.authorized ? [] : missing;
|
|
371
|
-
if (ctx.ledger === null && !ctx.authorized)
|
|
391
|
+
if (ctx.ledger === null && !ctx.authorized) pendingRows.push(...missing);
|
|
372
392
|
if (toAdd.length === 0) return false;
|
|
373
|
-
|
|
374
|
-
permissions
|
|
393
|
+
written.push(...toAdd);
|
|
394
|
+
permissions[kind] = [...list, ...toAdd];
|
|
375
395
|
root.permissions = permissions;
|
|
376
396
|
return true;
|
|
377
397
|
}
|
|
@@ -425,7 +445,7 @@ async function writeClaudeSessionStartHook(rootDir, opts = {}) {
|
|
|
425
445
|
relativePath: CLAUDE_SETTINGS_REL,
|
|
426
446
|
status: merged.status
|
|
427
447
|
};
|
|
428
|
-
if (merged.pending && (merged.pending.denyRows.length || merged.pending.hookEntries.length)) {
|
|
448
|
+
if (merged.pending && (merged.pending.denyRows.length || merged.pending.allowRows.length || merged.pending.hookEntries.length)) {
|
|
429
449
|
result.pending = merged.pending;
|
|
430
450
|
}
|
|
431
451
|
if (authorized && merged.ledgerAdditions && ledgerGrows(ledger, merged.ledgerAdditions)) {
|
|
@@ -437,8 +457,9 @@ async function writeClaudeSessionStartHook(rootDir, opts = {}) {
|
|
|
437
457
|
function ledgerGrows(ledger, add) {
|
|
438
458
|
if (ledger === null) return true;
|
|
439
459
|
const rows = new Set(ledger.denyRowsWritten);
|
|
460
|
+
const allowRows = new Set(ledger.allowRowsWritten);
|
|
440
461
|
const hooks = new Set(ledger.hookEntriesWritten.map((h) => `${h.event}\0${h.marker}`));
|
|
441
|
-
return add.denyRowsWritten.some((r) => !rows.has(r)) || add.hookEntriesWritten.some((h) => !hooks.has(`${h.event}\0${h.marker}`));
|
|
462
|
+
return add.denyRowsWritten.some((r) => !rows.has(r)) || add.allowRowsWritten.some((r) => !allowRows.has(r)) || add.hookEntriesWritten.some((h) => !hooks.has(`${h.event}\0${h.marker}`));
|
|
442
463
|
}
|
|
443
464
|
async function planClaudeSettingsDrift(rootDir) {
|
|
444
465
|
const abs = resolveContained(rootDir, CLAUDE_SETTINGS_REL);
|
|
@@ -477,17 +498,23 @@ async function planClaudeSettingsDrift(rootDir) {
|
|
|
477
498
|
const merged = mergeSessionStartHookIntoSettings(raw, { ledger, authorized: false });
|
|
478
499
|
if (merged.content === null) return null;
|
|
479
500
|
const next = JSON.parse(merged.content);
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
501
|
+
const rowsOf = (r, kind) => {
|
|
502
|
+
const list = isPlainObject(r.permissions) ? r.permissions[kind] : void 0;
|
|
503
|
+
return Array.isArray(list) ? list.filter((x) => typeof x === "string") : [];
|
|
504
|
+
};
|
|
505
|
+
const hadDeny = new Set(rowsOf(existing, "deny"));
|
|
506
|
+
const hadAllow = new Set(rowsOf(existing, "allow"));
|
|
507
|
+
const denyRows = rowsOf(next, "deny").filter((r) => !hadDeny.has(r));
|
|
508
|
+
const allowRows = rowsOf(next, "allow").filter((r) => !hadAllow.has(r));
|
|
483
509
|
const ambiguousDenyRows = merged.pending?.denyRows ?? [];
|
|
510
|
+
const ambiguousAllowRows = merged.pending?.allowRows ?? [];
|
|
484
511
|
const missingEntries = kitEntries.filter((e) => !present.includes(e));
|
|
485
512
|
const ledgerHas = (e) => Boolean(ledger?.hookEntriesWritten.some((h) => h.event === e.event && h.marker === e.marker));
|
|
486
513
|
const hookEntries = missingEntries.filter((e) => !ledgerHas(e));
|
|
487
|
-
if (denyRows.length === 0 && ambiguousDenyRows.length === 0 && hookEntries.length === 0) {
|
|
514
|
+
if (denyRows.length === 0 && ambiguousDenyRows.length === 0 && allowRows.length === 0 && ambiguousAllowRows.length === 0 && hookEntries.length === 0) {
|
|
488
515
|
return null;
|
|
489
516
|
}
|
|
490
|
-
return { denyRows, ambiguousDenyRows, hookEntries };
|
|
517
|
+
return { denyRows, ambiguousDenyRows, allowRows, ambiguousAllowRows, hookEntries };
|
|
491
518
|
}
|
|
492
519
|
function formatClaudeSettingsDrift(report, opts) {
|
|
493
520
|
const rows = [...report.denyRows, ...report.ambiguousDenyRows];
|
|
@@ -502,6 +529,15 @@ function formatClaudeSettingsDrift(report, opts) {
|
|
|
502
529
|
);
|
|
503
530
|
}
|
|
504
531
|
}
|
|
532
|
+
const allowRows = [...report.allowRows, ...report.ambiguousAllowRows];
|
|
533
|
+
if (allowRows.length > 0) {
|
|
534
|
+
lines.push("Add to permissions.allow:", JSON.stringify(allowRows, null, 2));
|
|
535
|
+
if (report.ambiguousAllowRows.length > 0) {
|
|
536
|
+
lines.push(
|
|
537
|
+
`(${report.ambiguousAllowRows.length} row(s) ambiguous: no ${CLAUDE_SETTINGS_LEDGER_REL} ledger, so a deliberate deletion cannot be told apart.)`
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
505
541
|
if (report.hookEntries.length > 0) {
|
|
506
542
|
lines.push("Missing hook entries (add by hand or use the command below):");
|
|
507
543
|
for (const e of report.hookEntries) lines.push(` ${e.event}: ${e.marker}`);
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
formatStaleCliRefusal,
|
|
7
7
|
installL0,
|
|
8
8
|
syncFromManifest
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6OTJEQEC.js";
|
|
10
10
|
import {
|
|
11
11
|
SkippedSymlinkError,
|
|
12
12
|
formatPersonalizationSkippedSymlinks,
|
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
REGISTRY_CLI_ARGS,
|
|
17
17
|
resolveRegistryFromCli
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-H46W7NHC.js";
|
|
19
19
|
import {
|
|
20
20
|
assessClaudeCommandAdapters,
|
|
21
21
|
executeSafeReadinessFixes
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-AXLXEAVJ.js";
|
|
23
23
|
import {
|
|
24
24
|
writeReadinessSnapshot
|
|
25
25
|
} from "./chunk-HAFRAXGV.js";
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
CLAUDE_HITL_FALLBACK,
|
|
28
28
|
generateClaudeCommandAdapters,
|
|
29
29
|
writeClaudeSessionStartHook
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-HHR56LJQ.js";
|
|
31
31
|
import {
|
|
32
32
|
shouldUseWelcomeColor,
|
|
33
33
|
withCliProgress
|
|
@@ -54,13 +54,13 @@ import {
|
|
|
54
54
|
npxPinned,
|
|
55
55
|
pathCliStatus,
|
|
56
56
|
syncPathCliToRuntime
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-SE7ZIRLX.js";
|
|
58
58
|
import {
|
|
59
59
|
assessEnvironment
|
|
60
60
|
} from "./chunk-B3VR5YBQ.js";
|
|
61
61
|
import {
|
|
62
62
|
warnIfRunningCliBehindNpm
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-3OM7JODZ.js";
|
|
64
64
|
import {
|
|
65
65
|
DOMAIN_PACK_IDS,
|
|
66
66
|
allPacks,
|
|
@@ -77,7 +77,7 @@ import {
|
|
|
77
77
|
resolveProtectedGlobs,
|
|
78
78
|
saveManifest,
|
|
79
79
|
skillTargetDir
|
|
80
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-7BEHUBEJ.js";
|
|
81
81
|
import {
|
|
82
82
|
KIT_VERSION,
|
|
83
83
|
pinnedCliSpec
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// src/plan-loop/hitl-relay.ts
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
import { StringDecoder } from "string_decoder";
|
|
4
|
+
|
|
5
|
+
// src/plan-loop/line-splitter.ts
|
|
6
|
+
var PENDING_MAX_CHARS = 4 * 1024 * 1024;
|
|
7
|
+
var LineSplitter = class {
|
|
8
|
+
constructor(maxChars = PENDING_MAX_CHARS) {
|
|
9
|
+
this.maxChars = maxChars;
|
|
10
|
+
}
|
|
11
|
+
maxChars;
|
|
12
|
+
parts = [];
|
|
13
|
+
length = 0;
|
|
14
|
+
dropping = false;
|
|
15
|
+
/** Calls `onLine` for every line the chunk completes (without the `\n`). */
|
|
16
|
+
feed(text, onLine) {
|
|
17
|
+
let start = 0;
|
|
18
|
+
let nl = text.indexOf("\n", start);
|
|
19
|
+
while (nl !== -1) {
|
|
20
|
+
const segment = text.slice(start, nl);
|
|
21
|
+
if (this.dropping) {
|
|
22
|
+
this.dropping = false;
|
|
23
|
+
} else if (this.parts.length === 0) {
|
|
24
|
+
onLine(segment);
|
|
25
|
+
} else {
|
|
26
|
+
this.parts.push(segment);
|
|
27
|
+
const line = this.parts.join("");
|
|
28
|
+
this.parts = [];
|
|
29
|
+
this.length = 0;
|
|
30
|
+
onLine(line);
|
|
31
|
+
}
|
|
32
|
+
start = nl + 1;
|
|
33
|
+
nl = text.indexOf("\n", start);
|
|
34
|
+
}
|
|
35
|
+
if (start >= text.length || this.dropping) return;
|
|
36
|
+
const tail = start === 0 ? text : text.slice(start);
|
|
37
|
+
this.parts.push(tail);
|
|
38
|
+
this.length += tail.length;
|
|
39
|
+
if (this.length > this.maxChars) {
|
|
40
|
+
this.parts = [];
|
|
41
|
+
this.length = 0;
|
|
42
|
+
this.dropping = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** The held partial line ("" when none or when it was dropped); resets. */
|
|
46
|
+
end() {
|
|
47
|
+
const rest = this.dropping ? "" : this.parts.join("");
|
|
48
|
+
this.parts = [];
|
|
49
|
+
this.length = 0;
|
|
50
|
+
this.dropping = false;
|
|
51
|
+
return rest;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/plan-loop/hitl-relay.ts
|
|
56
|
+
var HITL_GATE_LINE_RE = /^HITL_GATE:\s*([a-z0-9-]+)\s*\|\s*(.+)$/;
|
|
57
|
+
var HITL_REPLY_PREFIX = "HITL_REPLY:";
|
|
58
|
+
var RESERVED_GATE_IDS = ["git-prod", "kit-prod"];
|
|
59
|
+
var FALLBACK_ASK_ID = "prose-fallback";
|
|
60
|
+
var HITL_UNANSWERED_EXIT_CODE = 4;
|
|
61
|
+
var HITL_SIGINT_EXIT_CODE = 130;
|
|
62
|
+
var NUMBERED_LINE_RE = /^\s*(\d+)\.\s+(.+?)\s*$/;
|
|
63
|
+
var FALLBACK_WORDING_RES = [
|
|
64
|
+
/reply with (?:the |a )?number/i,
|
|
65
|
+
/ask questions is not available/i,
|
|
66
|
+
/skip or cancel means stop/i
|
|
67
|
+
];
|
|
68
|
+
var TICK_SENTINEL_RE = /LOOP_TICK_RESULT:/;
|
|
69
|
+
function parseHitlGateLine(line) {
|
|
70
|
+
const m = HITL_GATE_LINE_RE.exec(line.trim());
|
|
71
|
+
if (!m?.[1] || !m[2]) return null;
|
|
72
|
+
const labels = m[2].split("|").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
73
|
+
if (labels.length === 0) return null;
|
|
74
|
+
return { askId: m[1], labels, detection: "sentinel" };
|
|
75
|
+
}
|
|
76
|
+
function formatHitlGateLine(gate) {
|
|
77
|
+
return `HITL_GATE: ${gate.askId} | ${gate.labels.join(" | ")}`;
|
|
78
|
+
}
|
|
79
|
+
function findHitlGateInText(text) {
|
|
80
|
+
let last = null;
|
|
81
|
+
for (const line of text.split(/\r?\n/)) {
|
|
82
|
+
const gate = parseHitlGateLine(line);
|
|
83
|
+
if (gate) last = gate;
|
|
84
|
+
}
|
|
85
|
+
return last;
|
|
86
|
+
}
|
|
87
|
+
function cleanFallbackLabel(raw) {
|
|
88
|
+
let label = raw.trim();
|
|
89
|
+
const bold = /^\*\*(.+?)\*\*/.exec(label);
|
|
90
|
+
if (bold?.[1]) label = bold[1];
|
|
91
|
+
else label = label.replace(/\*\*/g, "").replace(/^`(.+)`$/, "$1");
|
|
92
|
+
label = label.split(/\s+[—–]\s+/)[0] ?? label;
|
|
93
|
+
return label.replace(/[.:]+$/, "").trim();
|
|
94
|
+
}
|
|
95
|
+
function detectFallbackGate(text) {
|
|
96
|
+
if (!FALLBACK_WORDING_RES.some((re) => re.test(text))) return null;
|
|
97
|
+
const labels = [];
|
|
98
|
+
let expected = 1;
|
|
99
|
+
for (const line of text.split(/\r?\n/)) {
|
|
100
|
+
const m = NUMBERED_LINE_RE.exec(line);
|
|
101
|
+
if (!m?.[1] || !m[2]) continue;
|
|
102
|
+
const n = Number(m[1]);
|
|
103
|
+
if (n === 1) {
|
|
104
|
+
labels.length = 0;
|
|
105
|
+
expected = 1;
|
|
106
|
+
}
|
|
107
|
+
if (n !== expected) continue;
|
|
108
|
+
labels.push(cleanFallbackLabel(m[2]));
|
|
109
|
+
expected += 1;
|
|
110
|
+
}
|
|
111
|
+
if (labels.length < 2) return null;
|
|
112
|
+
return { askId: FALLBACK_ASK_ID, labels, detection: "fallback" };
|
|
113
|
+
}
|
|
114
|
+
function detectHitlGate(resultText, assistantTexts) {
|
|
115
|
+
if (TICK_SENTINEL_RE.test(resultText)) return null;
|
|
116
|
+
const fromResult = findHitlGateInText(resultText);
|
|
117
|
+
if (fromResult) return fromResult;
|
|
118
|
+
for (let i = assistantTexts.length - 1; i >= 0; i -= 1) {
|
|
119
|
+
const text = assistantTexts[i] ?? "";
|
|
120
|
+
if (TICK_SENTINEL_RE.test(text)) return null;
|
|
121
|
+
const gate = findHitlGateInText(text);
|
|
122
|
+
if (gate) return gate;
|
|
123
|
+
}
|
|
124
|
+
const fallback = detectFallbackGate(resultText);
|
|
125
|
+
if (fallback) return fallback;
|
|
126
|
+
for (let i = assistantTexts.length - 1; i >= 0; i -= 1) {
|
|
127
|
+
const gate = detectFallbackGate(assistantTexts[i] ?? "");
|
|
128
|
+
if (gate) return gate;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function isReservedGateId(askId) {
|
|
133
|
+
return RESERVED_GATE_IDS.includes(askId);
|
|
134
|
+
}
|
|
135
|
+
var STOP_WORDS = /* @__PURE__ */ new Set(["skip", "cancel"]);
|
|
136
|
+
function resolveOperatorAnswer(input, labels) {
|
|
137
|
+
const text = input.trim();
|
|
138
|
+
if (!text) return { kind: "stop", cause: "empty" };
|
|
139
|
+
const exact = labels.indexOf(text);
|
|
140
|
+
if (exact !== -1) return { kind: "reply", n: exact + 1, label: labels[exact] ?? text };
|
|
141
|
+
if (/^\d+$/.test(text)) {
|
|
142
|
+
const n = Number(text);
|
|
143
|
+
const label = labels[n - 1];
|
|
144
|
+
if (n >= 1 && label !== void 0) return { kind: "reply", n, label };
|
|
145
|
+
}
|
|
146
|
+
const lower = text.toLowerCase();
|
|
147
|
+
if (STOP_WORDS.has(lower)) return { kind: "stop", cause: lower };
|
|
148
|
+
return { kind: "other", text };
|
|
149
|
+
}
|
|
150
|
+
function formatHitlReply(askId, answer) {
|
|
151
|
+
if (answer.kind === "reply") {
|
|
152
|
+
return `${HITL_REPLY_PREFIX} ${askId} | operator reply ${answer.n} | ${answer.label}`;
|
|
153
|
+
}
|
|
154
|
+
if (answer.kind === "other") {
|
|
155
|
+
return `${HITL_REPLY_PREFIX} ${askId} | operator reply other | ${answer.text}`;
|
|
156
|
+
}
|
|
157
|
+
throw new Error("a stop answer has no reply stamp");
|
|
158
|
+
}
|
|
159
|
+
function createTerminalAnswerReader(opts = {}) {
|
|
160
|
+
return (_gate, readOpts) => new Promise((resolve) => {
|
|
161
|
+
const input = opts.input ?? process.stdin;
|
|
162
|
+
const output = opts.output ?? process.stdout;
|
|
163
|
+
const terminal = opts.terminal ?? Boolean(input.isTTY);
|
|
164
|
+
const rl = createInterface({ input, output, terminal });
|
|
165
|
+
let settled = false;
|
|
166
|
+
const finish = (answer) => {
|
|
167
|
+
if (settled) return;
|
|
168
|
+
settled = true;
|
|
169
|
+
readOpts.signal?.removeEventListener("abort", onAbort);
|
|
170
|
+
rl.close();
|
|
171
|
+
if (typeof input.pause === "function") {
|
|
172
|
+
input.pause();
|
|
173
|
+
}
|
|
174
|
+
resolve(answer);
|
|
175
|
+
};
|
|
176
|
+
const onAbort = () => finish({ kind: "eof" });
|
|
177
|
+
if (readOpts.signal?.aborted) {
|
|
178
|
+
onAbort();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
readOpts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
182
|
+
rl.on("SIGINT", () => finish({ kind: "sigint" }));
|
|
183
|
+
rl.on("close", () => finish({ kind: "eof" }));
|
|
184
|
+
rl.question("> ", (line) => finish({ kind: "line", text: line }));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function emptyHitlRecord() {
|
|
188
|
+
return { replies: [], fallbackDetections: 0 };
|
|
189
|
+
}
|
|
190
|
+
function operatorStop(askId, cause) {
|
|
191
|
+
return {
|
|
192
|
+
askId,
|
|
193
|
+
cause,
|
|
194
|
+
message: `stopped-by-operator: gate ${askId}, no reply (${cause})`,
|
|
195
|
+
exitCode: cause === "SIGINT" ? HITL_SIGINT_EXIT_CODE : HITL_UNANSWERED_EXIT_CODE
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function operatorQuitStop(key) {
|
|
199
|
+
return {
|
|
200
|
+
askId: "none",
|
|
201
|
+
cause: "quit",
|
|
202
|
+
message: `stopped-by-operator: quit (${key}), no gate open`,
|
|
203
|
+
exitCode: HITL_SIGINT_EXIT_CODE
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function unansweredStop(askId, cause) {
|
|
207
|
+
return {
|
|
208
|
+
askId,
|
|
209
|
+
cause,
|
|
210
|
+
message: `stopped: unanswered gate (${cause})`,
|
|
211
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function driverStop(askId, raw) {
|
|
215
|
+
const detail = raw.detail ? `: ${raw.detail}` : "";
|
|
216
|
+
return {
|
|
217
|
+
askId,
|
|
218
|
+
cause: raw.cause,
|
|
219
|
+
message: raw.cause === "driver stop" ? `stopped-by-operator: gate ${askId}, no reply (driver stop)` : `stopped: unanswered gate (driver protocol error${detail})`,
|
|
220
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function noRelayStop(askId, backend) {
|
|
224
|
+
return {
|
|
225
|
+
askId,
|
|
226
|
+
cause: "no relay",
|
|
227
|
+
message: `stopped: unanswered gate (backend ${backend} has no relay)`,
|
|
228
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function reservedStop(askId) {
|
|
232
|
+
return {
|
|
233
|
+
askId,
|
|
234
|
+
cause: "reserved",
|
|
235
|
+
message: `stopped: gate ${askId} is operator-gated; run the /${askId} slash yourself (never headless)`,
|
|
236
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function formatGatePrompt(gate) {
|
|
240
|
+
const head = gate.detection === "fallback" ? `HITL gate (prose fallback, ask-id ${gate.askId})` : `HITL gate ${gate.askId}`;
|
|
241
|
+
const lines = [
|
|
242
|
+
"",
|
|
243
|
+
`${head}: reply with the number or the label; anything else is sent as "other"; empty, skip, cancel or Ctrl-C stops the run.`,
|
|
244
|
+
...gate.labels.map((label, i) => ` ${i + 1}. ${label}`)
|
|
245
|
+
];
|
|
246
|
+
return `${lines.join("\n")}
|
|
247
|
+
`;
|
|
248
|
+
}
|
|
249
|
+
async function askOperator(gate, opts) {
|
|
250
|
+
if (isReservedGateId(gate.askId)) return { kind: "stop", stop: reservedStop(gate.askId) };
|
|
251
|
+
if (opts.policy === "off") return { kind: "stop", stop: unansweredStop(gate.askId, "--no-hitl") };
|
|
252
|
+
if (!opts.isTTY && opts.policy !== "driver") {
|
|
253
|
+
return { kind: "stop", stop: unansweredStop(gate.askId, "no TTY") };
|
|
254
|
+
}
|
|
255
|
+
opts.onPromptStart?.();
|
|
256
|
+
let raw;
|
|
257
|
+
try {
|
|
258
|
+
opts.write(formatGatePrompt(gate));
|
|
259
|
+
raw = await opts.readAnswer(gate, { signal: opts.signal });
|
|
260
|
+
} finally {
|
|
261
|
+
opts.onPromptEnd?.();
|
|
262
|
+
}
|
|
263
|
+
if (opts.signal?.aborted) {
|
|
264
|
+
return { kind: "stop", stop: unansweredStop(gate.askId, "child exited") };
|
|
265
|
+
}
|
|
266
|
+
if (raw.kind === "eof") return { kind: "stop", stop: operatorStop(gate.askId, "EOF") };
|
|
267
|
+
if (raw.kind === "sigint") return { kind: "stop", stop: operatorStop(gate.askId, "SIGINT") };
|
|
268
|
+
if (raw.kind === "stop") return { kind: "stop", stop: driverStop(gate.askId, raw) };
|
|
269
|
+
const answer = resolveOperatorAnswer(raw.text, gate.labels);
|
|
270
|
+
if (answer.kind === "stop") return { kind: "stop", stop: operatorStop(gate.askId, answer.cause) };
|
|
271
|
+
const line = formatHitlReply(gate.askId, answer);
|
|
272
|
+
const stamp = {
|
|
273
|
+
askId: gate.askId,
|
|
274
|
+
reply: answer.kind === "reply" ? `operator reply ${answer.n}` : "operator reply other",
|
|
275
|
+
label: answer.kind === "reply" ? answer.label : answer.text,
|
|
276
|
+
line,
|
|
277
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
278
|
+
detection: gate.detection
|
|
279
|
+
};
|
|
280
|
+
return { kind: "reply", stamp };
|
|
281
|
+
}
|
|
282
|
+
function formatHitlSummary(record) {
|
|
283
|
+
if (!record) return [];
|
|
284
|
+
const lines = record.replies.map(
|
|
285
|
+
(s) => `HITL ${s.askId}: ${s.reply} (${s.label}) ${s.at}${s.detection === "fallback" ? " [fallback]" : ""}`
|
|
286
|
+
);
|
|
287
|
+
if (record.fallbackDetections > 0) {
|
|
288
|
+
lines.push(`hitl-detect: prose-fallback x${record.fallbackDetections}`);
|
|
289
|
+
}
|
|
290
|
+
if (record.stop) lines.push(record.stop.message);
|
|
291
|
+
return lines;
|
|
292
|
+
}
|
|
293
|
+
function hitlExitCode(childExitCode, record) {
|
|
294
|
+
if (record?.stop) return record.stop.exitCode;
|
|
295
|
+
return childExitCode;
|
|
296
|
+
}
|
|
297
|
+
function userEventLine(text) {
|
|
298
|
+
return `${JSON.stringify({ type: "user", message: { role: "user", content: text } })}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
var TurnWatcher = class {
|
|
302
|
+
constructor(onResult) {
|
|
303
|
+
this.onResult = onResult;
|
|
304
|
+
}
|
|
305
|
+
onResult;
|
|
306
|
+
lines = new LineSplitter();
|
|
307
|
+
assistantTexts = [];
|
|
308
|
+
decoder = new StringDecoder("utf8");
|
|
309
|
+
feed(chunk) {
|
|
310
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
311
|
+
if (!text) return;
|
|
312
|
+
this.lines.feed(text, (line) => this.take(line));
|
|
313
|
+
}
|
|
314
|
+
end() {
|
|
315
|
+
this.lines.feed(this.decoder.end(), (line) => this.take(line));
|
|
316
|
+
const rest = this.lines.end();
|
|
317
|
+
if (rest.trim()) this.take(rest);
|
|
318
|
+
}
|
|
319
|
+
take(line) {
|
|
320
|
+
const trimmed = line.trim();
|
|
321
|
+
if (!trimmed.startsWith("{")) return;
|
|
322
|
+
let event;
|
|
323
|
+
try {
|
|
324
|
+
event = JSON.parse(trimmed);
|
|
325
|
+
} catch {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (typeof event !== "object" || event === null) return;
|
|
329
|
+
const ev = event;
|
|
330
|
+
if (ev.type === "assistant") {
|
|
331
|
+
const content = ev.message?.content;
|
|
332
|
+
if (typeof content === "string") {
|
|
333
|
+
if (content) this.assistantTexts.push(content);
|
|
334
|
+
} else if (Array.isArray(content)) {
|
|
335
|
+
for (const part of content) {
|
|
336
|
+
if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") {
|
|
337
|
+
this.assistantTexts.push(part.text);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (ev.type === "result") {
|
|
344
|
+
const texts = this.assistantTexts;
|
|
345
|
+
this.assistantTexts = [];
|
|
346
|
+
this.onResult({
|
|
347
|
+
resultText: typeof ev.result === "string" ? ev.result : "",
|
|
348
|
+
assistantTexts: texts,
|
|
349
|
+
isError: ev.is_error === true,
|
|
350
|
+
subtype: typeof ev.subtype === "string" ? ev.subtype : null,
|
|
351
|
+
errors: Array.isArray(ev.errors) ? ev.errors.filter((e) => typeof e === "string") : []
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
export {
|
|
358
|
+
LineSplitter,
|
|
359
|
+
RESERVED_GATE_IDS,
|
|
360
|
+
HITL_UNANSWERED_EXIT_CODE,
|
|
361
|
+
formatHitlGateLine,
|
|
362
|
+
findHitlGateInText,
|
|
363
|
+
detectHitlGate,
|
|
364
|
+
createTerminalAnswerReader,
|
|
365
|
+
emptyHitlRecord,
|
|
366
|
+
operatorQuitStop,
|
|
367
|
+
unansweredStop,
|
|
368
|
+
noRelayStop,
|
|
369
|
+
askOperator,
|
|
370
|
+
formatHitlSummary,
|
|
371
|
+
hitlExitCode,
|
|
372
|
+
userEventLine,
|
|
373
|
+
TurnWatcher
|
|
374
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isBranchInBase
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZPZVROS5.js";
|
|
4
4
|
import {
|
|
5
5
|
GATE_EXIT_BRANCH_PREFIX,
|
|
6
6
|
GATE_EXIT_NOTE_DIR,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
isSafeBaseRef,
|
|
12
12
|
landGateExit,
|
|
13
13
|
resolveGateExitRefs
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-TZ5XYIEF.js";
|
|
15
15
|
import {
|
|
16
16
|
ensurePathIgnored
|
|
17
17
|
} from "./chunk-MUTTNRX4.js";
|
|
@@ -19,12 +19,14 @@ import {
|
|
|
19
19
|
ensureDispatchLogPath,
|
|
20
20
|
readCommandFile,
|
|
21
21
|
runHeadlessDispatch
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WOHZ4T5V.js";
|
|
23
23
|
import {
|
|
24
|
-
formatHitlSummary,
|
|
25
|
-
hitlExitCode,
|
|
26
24
|
redactSecrets
|
|
27
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-NJVWTROP.js";
|
|
26
|
+
import {
|
|
27
|
+
formatHitlSummary,
|
|
28
|
+
hitlExitCode
|
|
29
|
+
} from "./chunk-IRTS7WEA.js";
|
|
28
30
|
|
|
29
31
|
// src/plan-loop/review-job.ts
|
|
30
32
|
import { randomBytes } from "crypto";
|