@dadado/agent-kit-cli 6.3.0 → 6.5.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 +190 -32
- package/dashboard/lib/chat-sessions.d.mts +63 -0
- package/dashboard/lib/chat-sessions.mjs +430 -0
- package/dashboard/{logo-cursor.svg → logo-bare-metal.svg} +1 -1
- package/dashboard/logo-marketplace.svg +1 -1
- package/dashboard/serve.mjs +5 -1
- package/dashboard/social-preview.svg +2 -2
- package/dist/{add-VA2W7I6P.js → add-IT6XFPKT.js} +2 -2
- package/dist/{chunk-NNKOWUWM.js → chunk-236IYK6L.js} +64 -28
- package/dist/{chunk-YRFSTHR5.js → chunk-2OIPMQHA.js} +13 -219
- package/dist/{chunk-JEI6HACW.js → chunk-2TCRKQHD.js} +35 -379
- package/dist/{chunk-U26MRIWN.js → chunk-34BBPQKI.js} +1 -1
- package/dist/{chunk-GUQYNUV4.js → chunk-4XZXM2ZA.js} +1 -1
- package/dist/{chunk-Z7RYIHHX.js → chunk-5LQP4JGH.js} +3 -3
- package/dist/{chunk-6KUSG7BJ.js → chunk-5WSFO35Y.js} +4 -1
- package/dist/{chunk-MAPOQEA2.js → chunk-6RRG3WOE.js} +8 -1
- package/dist/{chunk-OWWFKGKS.js → chunk-EI3VQ7FU.js} +2 -2
- package/dist/{chunk-QTBTKEXK.js → chunk-FM32XR7A.js} +1 -1
- package/dist/{chunk-KVY425NM.js → chunk-GIZNM73K.js} +4 -2
- package/dist/{chunk-MO63KZBP.js → chunk-HKYJG2FJ.js} +8 -6
- package/dist/{chunk-SV2BR2LZ.js → chunk-I2D75F4I.js} +7 -7
- package/dist/{chunk-25J2SMJQ.js → chunk-IN4MQ3P3.js} +1 -1
- package/dist/{chunk-OVOXRH5W.js → chunk-MEZUW3LL.js} +1 -1
- package/dist/chunk-MTLQN57O.js +380 -0
- package/dist/chunk-R6WH5P7R.js +223 -0
- package/dist/{chunk-VQBI3JVS.js → chunk-TNO7GTAJ.js} +2 -2
- package/dist/{chunk-Z7ABX5QI.js → chunk-U7XOLYDG.js} +4 -2
- package/dist/{chunk-WMVMBPCR.js → chunk-WOGBBJCX.js} +1 -1
- package/dist/{chunk-6GYKOHIL.js → chunk-YIOTJQHL.js} +2 -2
- package/dist/{contribute-NLM2BN6Z.js → contribute-V7PV5UMK.js} +3 -3
- package/dist/{countdown-QXJD6TDB.js → countdown-YONBTSJ2.js} +10 -8
- package/dist/{diff-HHG4JEDE.js → diff-GWIHGH65.js} +4 -4
- package/dist/{doctor-TWF2M3S3.js → doctor-FLIQCVFN.js} +8 -8
- package/dist/engine-info-UQNVNOA2.js +89 -0
- package/dist/{ground-control-NMYYIVCV.js → ground-control-6W2AKTKI.js} +5 -4
- package/dist/{guard-EHXF76ID.js → guard-ZZRQW3DJ.js} +107 -8
- package/dist/{hook-VP7FHRSZ.js → hook-2SX2V2T6.js} +10 -4
- package/dist/index.js +26 -22
- package/dist/{init-UOTV4ASJ.js → init-4B2IRH27.js} +11 -11
- package/dist/{install-VCHPXQV3.js → install-3JVTG35O.js} +11 -11
- package/dist/{run-R66G276Q.js → run-AYT6CRHI.js} +12 -9
- package/dist/{run-plan-24Z5K4P3.js → run-plan-5XIISG3W.js} +11 -7
- package/dist/sessions-O7MYEMCT.js +343 -0
- package/dist/{setup-global-M6LKGJHQ.js → setup-global-6JM5XTAT.js} +5 -5
- package/dist/{status-5UKVIKOI.js → status-PMAI7NH3.js} +6 -6
- package/dist/{update-VQBGEUWS.js → update-3PLY4FZA.js} +9 -9
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
formatStaleCliRefusal,
|
|
7
7
|
installL0,
|
|
8
8
|
syncFromManifest
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5LQP4JGH.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-34BBPQKI.js";
|
|
19
19
|
import {
|
|
20
20
|
assessClaudeCommandAdapters,
|
|
21
21
|
executeSafeReadinessFixes
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-WOGBBJCX.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-236IYK6L.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-EI3VQ7FU.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-YIOTJQHL.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-5WSFO35Y.js";
|
|
81
81
|
import {
|
|
82
82
|
KIT_VERSION,
|
|
83
83
|
pinnedCliSpec
|
|
@@ -0,0 +1,380 @@
|
|
|
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
|
+
backgroundTasks = 0;
|
|
309
|
+
decoder = new StringDecoder("utf8");
|
|
310
|
+
feed(chunk) {
|
|
311
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
312
|
+
if (!text) return;
|
|
313
|
+
this.lines.feed(text, (line) => this.take(line));
|
|
314
|
+
}
|
|
315
|
+
end() {
|
|
316
|
+
this.lines.feed(this.decoder.end(), (line) => this.take(line));
|
|
317
|
+
const rest = this.lines.end();
|
|
318
|
+
if (rest.trim()) this.take(rest);
|
|
319
|
+
}
|
|
320
|
+
take(line) {
|
|
321
|
+
const trimmed = line.trim();
|
|
322
|
+
if (!trimmed.startsWith("{")) return;
|
|
323
|
+
let event;
|
|
324
|
+
try {
|
|
325
|
+
event = JSON.parse(trimmed);
|
|
326
|
+
} catch {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (typeof event !== "object" || event === null) return;
|
|
330
|
+
const ev = event;
|
|
331
|
+
if (ev.type === "system" && ev.subtype === "background_tasks_changed") {
|
|
332
|
+
this.backgroundTasks = Array.isArray(ev.tasks) ? ev.tasks.length : 0;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (ev.type === "assistant") {
|
|
336
|
+
const content = ev.message?.content;
|
|
337
|
+
if (typeof content === "string") {
|
|
338
|
+
if (content) this.assistantTexts.push(content);
|
|
339
|
+
} else if (Array.isArray(content)) {
|
|
340
|
+
for (const part of content) {
|
|
341
|
+
if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") {
|
|
342
|
+
this.assistantTexts.push(part.text);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (ev.type === "result") {
|
|
349
|
+
const texts = this.assistantTexts;
|
|
350
|
+
this.assistantTexts = [];
|
|
351
|
+
this.onResult({
|
|
352
|
+
resultText: typeof ev.result === "string" ? ev.result : "",
|
|
353
|
+
assistantTexts: texts,
|
|
354
|
+
isError: ev.is_error === true,
|
|
355
|
+
subtype: typeof ev.subtype === "string" ? ev.subtype : null,
|
|
356
|
+
errors: Array.isArray(ev.errors) ? ev.errors.filter((e) => typeof e === "string") : [],
|
|
357
|
+
backgroundTasks: this.backgroundTasks
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export {
|
|
364
|
+
LineSplitter,
|
|
365
|
+
RESERVED_GATE_IDS,
|
|
366
|
+
HITL_UNANSWERED_EXIT_CODE,
|
|
367
|
+
formatHitlGateLine,
|
|
368
|
+
findHitlGateInText,
|
|
369
|
+
detectHitlGate,
|
|
370
|
+
createTerminalAnswerReader,
|
|
371
|
+
emptyHitlRecord,
|
|
372
|
+
operatorQuitStop,
|
|
373
|
+
unansweredStop,
|
|
374
|
+
noRelayStop,
|
|
375
|
+
askOperator,
|
|
376
|
+
formatHitlSummary,
|
|
377
|
+
hitlExitCode,
|
|
378
|
+
userEventLine,
|
|
379
|
+
TurnWatcher
|
|
380
|
+
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatHitlGateLine
|
|
3
|
+
} from "./chunk-MTLQN57O.js";
|
|
4
|
+
|
|
5
|
+
// src/plan-loop/driver-events.ts
|
|
6
|
+
import { createInterface } from "readline";
|
|
7
|
+
import { StringDecoder } from "string_decoder";
|
|
8
|
+
var DRIVER_PROTOCOL_MAJOR = 1;
|
|
9
|
+
var DRIVER_PROTOCOL_VERSION = "1.1.0";
|
|
10
|
+
var DRIVER_EVENTS_FORMATS = ["ndjson"];
|
|
11
|
+
var INBOUND_MAX_CHARS = 64 * 1024;
|
|
12
|
+
var AGENT_PENDING_MAX_CHARS = 4 * 1024 * 1024;
|
|
13
|
+
function formatDriverEvent(event, now = () => /* @__PURE__ */ new Date()) {
|
|
14
|
+
return `${JSON.stringify({ v: DRIVER_PROTOCOL_MAJOR, ts: now().toISOString(), ...event })}
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
17
|
+
function createDriverEmitter(write = (text) => {
|
|
18
|
+
process.stdout.write(text);
|
|
19
|
+
}, now) {
|
|
20
|
+
return {
|
|
21
|
+
emit(event) {
|
|
22
|
+
try {
|
|
23
|
+
write(formatDriverEvent(event, now));
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function parseDriverInbound(line) {
|
|
30
|
+
const text = line.trim();
|
|
31
|
+
if (text.length > INBOUND_MAX_CHARS) return { ok: false, error: "line too long" };
|
|
32
|
+
let value;
|
|
33
|
+
try {
|
|
34
|
+
value = JSON.parse(text);
|
|
35
|
+
} catch {
|
|
36
|
+
return { ok: false, error: "not JSON" };
|
|
37
|
+
}
|
|
38
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
39
|
+
return { ok: false, error: "not a JSON object" };
|
|
40
|
+
}
|
|
41
|
+
const msg = value;
|
|
42
|
+
if (msg.v !== void 0 && msg.v !== DRIVER_PROTOCOL_MAJOR) {
|
|
43
|
+
return { ok: false, error: `unsupported protocol v${String(msg.v)}` };
|
|
44
|
+
}
|
|
45
|
+
if (msg.type === "stop") {
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
message: { type: "stop", ...typeof msg.reason === "string" ? { reason: msg.reason } : {} }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (msg.type !== "hitl_reply")
|
|
52
|
+
return { ok: false, error: `unknown type ${JSON.stringify(msg.type)}` };
|
|
53
|
+
if (typeof msg.askId !== "string" || !/^[a-z0-9-]+$/.test(msg.askId)) {
|
|
54
|
+
return { ok: false, error: "hitl_reply.askId must be a kebab-case string" };
|
|
55
|
+
}
|
|
56
|
+
if (typeof msg.reply === "number") {
|
|
57
|
+
if (!Number.isInteger(msg.reply) || msg.reply < 1) {
|
|
58
|
+
return { ok: false, error: "hitl_reply.reply number must be a positive integer" };
|
|
59
|
+
}
|
|
60
|
+
return { ok: true, message: { type: "hitl_reply", askId: msg.askId, reply: msg.reply } };
|
|
61
|
+
}
|
|
62
|
+
if (typeof msg.reply === "string" && msg.reply.trim() && !/[\r\n]/.test(msg.reply)) {
|
|
63
|
+
return { ok: true, message: { type: "hitl_reply", askId: msg.askId, reply: msg.reply.trim() } };
|
|
64
|
+
}
|
|
65
|
+
return { ok: false, error: "hitl_reply.reply must be a number or a one-line non-empty string" };
|
|
66
|
+
}
|
|
67
|
+
function inboundToRawAnswer(gate, parsed) {
|
|
68
|
+
if (!parsed.ok) return { kind: "stop", cause: "protocol error", detail: parsed.error };
|
|
69
|
+
const message = parsed.message;
|
|
70
|
+
if (message.type === "stop") return { kind: "stop", cause: "driver stop" };
|
|
71
|
+
if (message.askId !== gate.askId) {
|
|
72
|
+
return {
|
|
73
|
+
kind: "stop",
|
|
74
|
+
cause: "protocol error",
|
|
75
|
+
detail: `reply for ${message.askId} while gate ${gate.askId} is open`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { kind: "line", text: String(message.reply) };
|
|
79
|
+
}
|
|
80
|
+
function createDriverAnswerReader(opts) {
|
|
81
|
+
const input = opts.input ?? process.stdin;
|
|
82
|
+
const queue = [];
|
|
83
|
+
let ended = false;
|
|
84
|
+
let waiter = null;
|
|
85
|
+
let rl = null;
|
|
86
|
+
const ensure = () => {
|
|
87
|
+
if (rl) return;
|
|
88
|
+
rl = createInterface({ input, terminal: false, crlfDelay: Number.POSITIVE_INFINITY });
|
|
89
|
+
rl.on("line", (line) => {
|
|
90
|
+
if (!line.trim()) return;
|
|
91
|
+
if (waiter) {
|
|
92
|
+
const w = waiter;
|
|
93
|
+
waiter = null;
|
|
94
|
+
w(line);
|
|
95
|
+
} else queue.push(line);
|
|
96
|
+
});
|
|
97
|
+
rl.on("close", () => {
|
|
98
|
+
ended = true;
|
|
99
|
+
if (waiter) {
|
|
100
|
+
const w = waiter;
|
|
101
|
+
waiter = null;
|
|
102
|
+
w(null);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const nextLine = (signal) => new Promise((resolve) => {
|
|
107
|
+
const queued = queue.shift();
|
|
108
|
+
if (queued !== void 0) {
|
|
109
|
+
resolve(queued);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (ended) {
|
|
113
|
+
resolve(null);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const onAbort = () => {
|
|
117
|
+
if (waiter === settle) waiter = null;
|
|
118
|
+
resolve(null);
|
|
119
|
+
};
|
|
120
|
+
const settle = (line) => {
|
|
121
|
+
signal?.removeEventListener("abort", onAbort);
|
|
122
|
+
resolve(line);
|
|
123
|
+
};
|
|
124
|
+
if (signal?.aborted) {
|
|
125
|
+
resolve(null);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
129
|
+
waiter = settle;
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
read: async (gate, readOpts) => {
|
|
133
|
+
ensure();
|
|
134
|
+
opts.emitter.emit({
|
|
135
|
+
type: "hitl_gate",
|
|
136
|
+
tick: opts.tick(),
|
|
137
|
+
askId: gate.askId,
|
|
138
|
+
labels: [...gate.labels],
|
|
139
|
+
detection: gate.detection,
|
|
140
|
+
line: formatHitlGateLine(gate)
|
|
141
|
+
});
|
|
142
|
+
const line = await nextLine(readOpts.signal);
|
|
143
|
+
if (line === null) return { kind: "eof" };
|
|
144
|
+
return inboundToRawAnswer(gate, parseDriverInbound(line));
|
|
145
|
+
},
|
|
146
|
+
close: () => {
|
|
147
|
+
const r = rl;
|
|
148
|
+
rl = null;
|
|
149
|
+
r?.close();
|
|
150
|
+
if (typeof input.pause === "function") {
|
|
151
|
+
input.pause();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
var DriverAgentSink = class {
|
|
157
|
+
constructor(emitter, tick) {
|
|
158
|
+
this.emitter = emitter;
|
|
159
|
+
this.tick = tick;
|
|
160
|
+
}
|
|
161
|
+
emitter;
|
|
162
|
+
tick;
|
|
163
|
+
pending = "";
|
|
164
|
+
dropped = false;
|
|
165
|
+
decoder = new StringDecoder("utf8");
|
|
166
|
+
feed(chunk) {
|
|
167
|
+
try {
|
|
168
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
169
|
+
if (!text) return;
|
|
170
|
+
const lines = (this.pending + text).split("\n");
|
|
171
|
+
this.pending = lines.pop() ?? "";
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
if (this.dropped) {
|
|
174
|
+
this.dropped = false;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
this.take(line);
|
|
178
|
+
}
|
|
179
|
+
if (this.pending.length > AGENT_PENDING_MAX_CHARS) {
|
|
180
|
+
this.pending = "";
|
|
181
|
+
this.dropped = true;
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
end() {
|
|
187
|
+
try {
|
|
188
|
+
const rest = this.pending + this.decoder.end();
|
|
189
|
+
this.pending = "";
|
|
190
|
+
if (rest && !this.dropped) this.take(rest);
|
|
191
|
+
this.dropped = false;
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
take(raw) {
|
|
196
|
+
const line = raw.replace(/\r$/, "");
|
|
197
|
+
if (!line.trim()) return;
|
|
198
|
+
const trimmed = line.trim();
|
|
199
|
+
if (trimmed.startsWith("{")) {
|
|
200
|
+
try {
|
|
201
|
+
const event = JSON.parse(trimmed);
|
|
202
|
+
if (typeof event === "object" && event !== null && !Array.isArray(event)) {
|
|
203
|
+
this.emitter.emit({
|
|
204
|
+
type: "agent_event",
|
|
205
|
+
tick: this.tick,
|
|
206
|
+
event
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
this.emitter.emit({ type: "agent_text", tick: this.tick, text: line });
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export {
|
|
218
|
+
DRIVER_PROTOCOL_VERSION,
|
|
219
|
+
DRIVER_EVENTS_FORMATS,
|
|
220
|
+
createDriverEmitter,
|
|
221
|
+
createDriverAnswerReader,
|
|
222
|
+
DriverAgentSink
|
|
223
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
managedPairs
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IN4MQ3P3.js";
|
|
4
4
|
import {
|
|
5
5
|
isProtectedPath,
|
|
6
6
|
resolveContained,
|
|
7
7
|
resolveProtectedGlobs
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5WSFO35Y.js";
|
|
9
9
|
|
|
10
10
|
// src/lifecycle/diff.ts
|
|
11
11
|
import { readFile } from "fs/promises";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
RESERVED_GATE_IDS,
|
|
3
2
|
checkClaudeVersion,
|
|
4
3
|
claudeChildEnv,
|
|
5
4
|
claudeHeadlessArgs,
|
|
@@ -13,7 +12,10 @@ import {
|
|
|
13
12
|
redactSecrets,
|
|
14
13
|
resolveHitlRunOptions,
|
|
15
14
|
spawnLogged
|
|
16
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-2TCRKQHD.js";
|
|
16
|
+
import {
|
|
17
|
+
RESERVED_GATE_IDS
|
|
18
|
+
} from "./chunk-MTLQN57O.js";
|
|
17
19
|
import {
|
|
18
20
|
ensureLoopLogsDir,
|
|
19
21
|
runnerStatePaths
|