@a5c-ai/babysitter-sdk 0.0.170-staging.dae504fd → 0.0.170-staging.dff83fb8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/hookRun.d.ts +3 -4
- package/dist/cli/commands/hookRun.d.ts.map +1 -1
- package/dist/cli/commands/hookRun.js +10 -490
- package/dist/cli/commands/runIterate.d.ts +0 -3
- package/dist/cli/commands/runIterate.d.ts.map +1 -1
- package/dist/cli/commands/runIterate.js +90 -15
- package/dist/cli/commands/skill.d.ts +49 -0
- package/dist/cli/commands/skill.d.ts.map +1 -1
- package/dist/cli/commands/skill.js +87 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +79 -122
- package/dist/harness/claudeCode.d.ts +15 -0
- package/dist/harness/claudeCode.d.ts.map +1 -0
- package/dist/harness/claudeCode.js +735 -0
- package/dist/harness/index.d.ts +5 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +14 -0
- package/dist/harness/nullAdapter.d.ts +9 -0
- package/dist/harness/nullAdapter.d.ts.map +1 -0
- package/dist/harness/nullAdapter.js +46 -0
- package/dist/harness/registry.d.ts +35 -0
- package/dist/harness/registry.d.ts.map +1 -0
- package/dist/harness/registry.js +79 -0
- package/dist/harness/types.d.ts +60 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +10 -0
- package/dist/hooks/dispatcher.d.ts +3 -0
- package/dist/hooks/dispatcher.d.ts.map +1 -1
- package/dist/hooks/dispatcher.js +8 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/runtime/orchestrateIteration.d.ts.map +1 -1
- package/dist/runtime/orchestrateIteration.js +7 -0
- package/package.json +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* hook:run CLI command.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - "session-start" → handleHookRunSessionStart (replaces babysitter-session-start-hook.sh)
|
|
4
|
+
* Dispatches hook handling to the appropriate harness adapter.
|
|
5
|
+
* Each harness (e.g. "claude-code") implements its own stop and
|
|
6
|
+
* session-start handlers via the HarnessAdapter interface.
|
|
8
7
|
*/
|
|
9
8
|
export interface HookRunCommandArgs {
|
|
10
9
|
hookType: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hookRun.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/hookRun.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"hookRun.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/hookRun.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAMD,wBAAsB,aAAa,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmD7E"}
|
|
@@ -2,498 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* hook:run CLI command.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - "session-start" → handleHookRunSessionStart (replaces babysitter-session-start-hook.sh)
|
|
5
|
+
* Dispatches hook handling to the appropriate harness adapter.
|
|
6
|
+
* Each harness (e.g. "claude-code") implements its own stop and
|
|
7
|
+
* session-start handlers via the HarnessAdapter interface.
|
|
9
8
|
*/
|
|
10
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
-
}
|
|
16
|
-
Object.defineProperty(o, k2, desc);
|
|
17
|
-
}) : (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
o[k2] = m[k];
|
|
20
|
-
}));
|
|
21
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
-
}) : function(o, v) {
|
|
24
|
-
o["default"] = v;
|
|
25
|
-
});
|
|
26
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
-
var ownKeys = function(o) {
|
|
28
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
-
var ar = [];
|
|
30
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
-
return ar;
|
|
32
|
-
};
|
|
33
|
-
return ownKeys(o);
|
|
34
|
-
};
|
|
35
|
-
return function (mod) {
|
|
36
|
-
if (mod && mod.__esModule) return mod;
|
|
37
|
-
var result = {};
|
|
38
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
-
__setModuleDefault(result, mod);
|
|
40
|
-
return result;
|
|
41
|
-
};
|
|
42
|
-
})();
|
|
43
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
10
|
exports.handleHookRun = handleHookRun;
|
|
45
|
-
const
|
|
46
|
-
const node_fs_1 = require("node:fs");
|
|
47
|
-
const journal_1 = require("../../storage/journal");
|
|
48
|
-
const runFiles_1 = require("../../storage/runFiles");
|
|
49
|
-
const effectIndex_1 = require("../../runtime/replay/effectIndex");
|
|
50
|
-
const completionProof_1 = require("../completionProof");
|
|
51
|
-
const skill_1 = require("./skill");
|
|
52
|
-
const session_1 = require("../../session");
|
|
53
|
-
const session_2 = require("./session");
|
|
54
|
-
function createHookLogger(hookName) {
|
|
55
|
-
const logDir = process.env.BABYSITTER_LOG_DIR;
|
|
56
|
-
const logFile = logDir ? path.join(logDir, `${hookName}.log`) : null;
|
|
57
|
-
const context = {};
|
|
58
|
-
if (logFile) {
|
|
59
|
-
try {
|
|
60
|
-
(0, node_fs_1.mkdirSync)(logDir, { recursive: true });
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Best-effort
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function write(level, message) {
|
|
67
|
-
if (!logFile)
|
|
68
|
-
return;
|
|
69
|
-
const ts = new Date().toISOString();
|
|
70
|
-
const ctxParts = Object.entries(context).map(([k, v]) => `${k}=${v}`);
|
|
71
|
-
const ctxStr = ctxParts.length > 0 ? ` [${ctxParts.join(" ")}]` : "";
|
|
72
|
-
const line = `[${level}] ${ts}${ctxStr} ${message}\n`;
|
|
73
|
-
try {
|
|
74
|
-
(0, node_fs_1.appendFileSync)(logFile, line);
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
// Best-effort
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
info: (msg) => write("INFO", msg),
|
|
82
|
-
warn: (msg) => write("WARN", msg),
|
|
83
|
-
error: (msg) => write("ERROR", msg),
|
|
84
|
-
setContext: (key, value) => {
|
|
85
|
-
context[key] = value;
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// Stdin reader
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
async function readStdin() {
|
|
93
|
-
return new Promise((resolve, reject) => {
|
|
94
|
-
let data = "";
|
|
95
|
-
process.stdin.setEncoding("utf8");
|
|
96
|
-
process.stdin.on("data", (chunk) => {
|
|
97
|
-
data += chunk;
|
|
98
|
-
});
|
|
99
|
-
process.stdin.on("end", () => resolve(data));
|
|
100
|
-
process.stdin.on("error", reject);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
function parseHookInput(raw) {
|
|
104
|
-
const trimmed = raw.trim();
|
|
105
|
-
if (!trimmed)
|
|
106
|
-
return {};
|
|
107
|
-
try {
|
|
108
|
-
const parsed = JSON.parse(trimmed);
|
|
109
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
110
|
-
return parsed;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
// Malformed JSON — treat as empty
|
|
115
|
-
}
|
|
116
|
-
return {};
|
|
117
|
-
}
|
|
118
|
-
function safeStr(obj, key) {
|
|
119
|
-
const val = obj[key];
|
|
120
|
-
return typeof val === "string" ? val : "";
|
|
121
|
-
}
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
// Pending-by-kind helper (mirrors session.ts countPendingByKind)
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
function countPendingByKind(records) {
|
|
126
|
-
const counts = new Map();
|
|
127
|
-
for (const record of records) {
|
|
128
|
-
const key = record.kind ?? "unknown";
|
|
129
|
-
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
130
|
-
}
|
|
131
|
-
return Object.fromEntries(Array.from(counts.entries()).sort(([a], [b]) => a.localeCompare(b)));
|
|
132
|
-
}
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// handleHookRunStop — Claude Code "SessionEnd" hook handler
|
|
135
|
-
//
|
|
136
|
-
// Claude Code-specific: reads {session_id, transcript_path} from stdin,
|
|
137
|
-
// outputs {decision: "approve"|"block", reason?, systemMessage?, instructions?}
|
|
138
|
-
// ---------------------------------------------------------------------------
|
|
139
|
-
async function handleHookRunStop(args) {
|
|
140
|
-
const { verbose } = args;
|
|
141
|
-
const log = createHookLogger("babysitter-stop-hook");
|
|
142
|
-
log.info("handleHookRunStop started");
|
|
143
|
-
// 1. Read hook input JSON from stdin
|
|
144
|
-
let rawInput;
|
|
145
|
-
try {
|
|
146
|
-
rawInput = await readStdin();
|
|
147
|
-
}
|
|
148
|
-
catch (e) {
|
|
149
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
150
|
-
log.warn(`stdin read error: ${msg}`);
|
|
151
|
-
if (verbose) {
|
|
152
|
-
process.stderr.write(`[hook:run stop] stdin read error: ${msg}\n`);
|
|
153
|
-
}
|
|
154
|
-
// Allow exit on error
|
|
155
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
156
|
-
return 0;
|
|
157
|
-
}
|
|
158
|
-
const hookInput = parseHookInput(rawInput);
|
|
159
|
-
log.info("Hook input received");
|
|
160
|
-
const sessionId = safeStr(hookInput, "session_id");
|
|
161
|
-
if (!sessionId) {
|
|
162
|
-
// No session ID — allow exit
|
|
163
|
-
log.info("No session ID in hook input — allowing exit");
|
|
164
|
-
if (verbose) {
|
|
165
|
-
process.stderr.write("[hook:run stop] No session ID in hook input\n");
|
|
166
|
-
}
|
|
167
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
168
|
-
return 0;
|
|
169
|
-
}
|
|
170
|
-
log.setContext("session", sessionId);
|
|
171
|
-
log.info(`Session ID: ${sessionId}`);
|
|
172
|
-
// 2. Resolve pluginRoot and stateDir
|
|
173
|
-
const pluginRoot = args.pluginRoot || process.env.CLAUDE_PLUGIN_ROOT || "";
|
|
174
|
-
const stateDir = args.stateDir ||
|
|
175
|
-
(pluginRoot ? path.join(pluginRoot, "skills", "babysit", "state") : "");
|
|
176
|
-
if (!stateDir) {
|
|
177
|
-
log.warn("Cannot determine state directory — allowing exit");
|
|
178
|
-
if (verbose) {
|
|
179
|
-
process.stderr.write("[hook:run stop] Cannot determine state directory\n");
|
|
180
|
-
}
|
|
181
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
182
|
-
return 0;
|
|
183
|
-
}
|
|
184
|
-
const runsDir = args.runsDir || ".a5c/runs";
|
|
185
|
-
// 3. Check iteration (replaces session:check-iteration CLI call)
|
|
186
|
-
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
187
|
-
log.info(`Checking session at: ${filePath}`);
|
|
188
|
-
let sessionFile;
|
|
189
|
-
try {
|
|
190
|
-
if (!(await (0, session_1.sessionFileExists)(filePath))) {
|
|
191
|
-
// No active loop
|
|
192
|
-
log.info("No active loop found — allowing exit");
|
|
193
|
-
if (verbose) {
|
|
194
|
-
process.stderr.write(`[hook:run stop] No active loop found for session ${sessionId}\n`);
|
|
195
|
-
}
|
|
196
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
197
|
-
return 0;
|
|
198
|
-
}
|
|
199
|
-
sessionFile = await (0, session_1.readSessionFile)(filePath);
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
// Cannot read session — allow exit
|
|
203
|
-
log.warn("Session file read error — allowing exit");
|
|
204
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
205
|
-
return 0;
|
|
206
|
-
}
|
|
207
|
-
const { state } = sessionFile;
|
|
208
|
-
const prompt = sessionFile.prompt ?? "";
|
|
209
|
-
// Check max iterations
|
|
210
|
-
if (state.maxIterations > 0 && state.iteration >= state.maxIterations) {
|
|
211
|
-
if (verbose) {
|
|
212
|
-
process.stderr.write(`[hook:run stop] Max iterations (${state.maxIterations}) reached\n`);
|
|
213
|
-
}
|
|
214
|
-
await cleanupSession(filePath);
|
|
215
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
216
|
-
return 0;
|
|
217
|
-
}
|
|
218
|
-
// Check iteration timing (runaway loop detection)
|
|
219
|
-
const now = (0, session_1.getCurrentTimestamp)();
|
|
220
|
-
const updatedTimes = state.iteration >= 5
|
|
221
|
-
? (0, session_1.updateIterationTimes)(state.iterationTimes, state.lastIterationAt, now)
|
|
222
|
-
: state.iterationTimes;
|
|
223
|
-
if ((0, session_1.isIterationTooFast)(updatedTimes)) {
|
|
224
|
-
if (verbose) {
|
|
225
|
-
const avg = updatedTimes.reduce((a, b) => a + b, 0) / updatedTimes.length;
|
|
226
|
-
process.stderr.write(`[hook:run stop] Iteration too fast (avg ${avg}s)\n`);
|
|
227
|
-
}
|
|
228
|
-
await cleanupSession(filePath);
|
|
229
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
230
|
-
return 0;
|
|
231
|
-
}
|
|
232
|
-
// shouldContinue=true at this point
|
|
233
|
-
const iteration = state.iteration;
|
|
234
|
-
const maxIterations = state.maxIterations;
|
|
235
|
-
const runId = state.runId ?? "";
|
|
236
|
-
if (runId) {
|
|
237
|
-
log.setContext("run", runId);
|
|
238
|
-
}
|
|
239
|
-
// 4. Parse transcript for last assistant message
|
|
240
|
-
const transcriptPath = safeStr(hookInput, "transcript_path");
|
|
241
|
-
let lastText = null;
|
|
242
|
-
let hasPromise = false;
|
|
243
|
-
let promiseValue = null;
|
|
244
|
-
if (transcriptPath) {
|
|
245
|
-
const resolvedTranscript = path.resolve(transcriptPath);
|
|
246
|
-
if ((0, node_fs_1.existsSync)(resolvedTranscript)) {
|
|
247
|
-
try {
|
|
248
|
-
const content = (0, node_fs_1.readFileSync)(resolvedTranscript, "utf-8");
|
|
249
|
-
const parsed = (0, session_2.parseTranscriptLastAssistantMessage)(content);
|
|
250
|
-
lastText = parsed.text;
|
|
251
|
-
if (parsed.found && parsed.text) {
|
|
252
|
-
promiseValue = (0, session_2.extractPromiseTag)(parsed.text);
|
|
253
|
-
hasPromise = promiseValue !== null;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
// Transcript parse error — cleanup and allow exit
|
|
258
|
-
if (verbose) {
|
|
259
|
-
process.stderr.write(`[hook:run stop] Transcript parse error: ${resolvedTranscript}\n`);
|
|
260
|
-
}
|
|
261
|
-
await cleanupSession(filePath);
|
|
262
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
263
|
-
return 0;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
if (verbose) {
|
|
268
|
-
process.stderr.write(`[hook:run stop] Transcript not found: ${resolvedTranscript}\n`);
|
|
269
|
-
}
|
|
270
|
-
await cleanupSession(filePath);
|
|
271
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
272
|
-
return 0;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (!lastText) {
|
|
276
|
-
if (verbose) {
|
|
277
|
-
process.stderr.write("[hook:run stop] No assistant text content found in transcript\n");
|
|
278
|
-
}
|
|
279
|
-
await cleanupSession(filePath);
|
|
280
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
281
|
-
return 0;
|
|
282
|
-
}
|
|
283
|
-
// 4b. If no run is associated, there's nothing to iterate on — allow exit
|
|
284
|
-
if (!runId) {
|
|
285
|
-
log.info("No run associated with session — allowing exit");
|
|
286
|
-
if (verbose) {
|
|
287
|
-
process.stderr.write(`[hook:run stop] No run associated with session ${sessionId} — allowing exit\n`);
|
|
288
|
-
}
|
|
289
|
-
await cleanupSession(filePath);
|
|
290
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
291
|
-
return 0;
|
|
292
|
-
}
|
|
293
|
-
// 5. If runId is present, get run status
|
|
294
|
-
let runState = "";
|
|
295
|
-
let completionProof = "";
|
|
296
|
-
let pendingKinds = "";
|
|
297
|
-
if (runId) {
|
|
298
|
-
try {
|
|
299
|
-
const runDir = path.isAbsolute(runId)
|
|
300
|
-
? runId
|
|
301
|
-
: path.join(runsDir, runId);
|
|
302
|
-
const metadata = await (0, runFiles_1.readRunMetadata)(runDir);
|
|
303
|
-
const journal = await (0, journal_1.loadJournal)(runDir);
|
|
304
|
-
const index = await (0, effectIndex_1.buildEffectIndex)({ runDir, events: journal });
|
|
305
|
-
const hasCompleted = journal.some((e) => e.type === "RUN_COMPLETED");
|
|
306
|
-
const hasFailed = journal.some((e) => e.type === "RUN_FAILED");
|
|
307
|
-
const pendingRecords = index.listPendingEffects();
|
|
308
|
-
const pendingByKind = countPendingByKind(pendingRecords);
|
|
309
|
-
const kindKeys = Object.keys(pendingByKind);
|
|
310
|
-
if (kindKeys.length > 0) {
|
|
311
|
-
pendingKinds = kindKeys.join(", ");
|
|
312
|
-
}
|
|
313
|
-
if (hasCompleted) {
|
|
314
|
-
runState = "completed";
|
|
315
|
-
completionProof = (0, completionProof_1.resolveCompletionProof)(metadata);
|
|
316
|
-
}
|
|
317
|
-
else if (hasFailed) {
|
|
318
|
-
runState = "failed";
|
|
319
|
-
}
|
|
320
|
-
else if (pendingRecords.length > 0) {
|
|
321
|
-
runState = "waiting";
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
runState = "created";
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
catch {
|
|
328
|
-
runState = "";
|
|
329
|
-
}
|
|
330
|
-
log.info(`Run state: ${runState || "unknown"}`);
|
|
331
|
-
if (completionProof) {
|
|
332
|
-
log.info("Completion proof available");
|
|
333
|
-
}
|
|
334
|
-
// If state is empty (couldn't determine) → cleanup and allow
|
|
335
|
-
if (!runState) {
|
|
336
|
-
if (verbose) {
|
|
337
|
-
process.stderr.write(`[hook:run stop] Run state is empty for ${runId}; run may be misconfigured\n`);
|
|
338
|
-
}
|
|
339
|
-
await cleanupSession(filePath);
|
|
340
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
341
|
-
return 0;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
// 6. If completionProof matches promiseValue → complete
|
|
345
|
-
if (hasPromise) {
|
|
346
|
-
log.info("Detected valid promise tag");
|
|
347
|
-
}
|
|
348
|
-
if (completionProof && hasPromise && promiseValue === completionProof) {
|
|
349
|
-
log.info("Promise matches completion proof — allowing exit");
|
|
350
|
-
if (verbose) {
|
|
351
|
-
process.stderr.write(`[hook:run stop] Valid promise tag detected - run complete\n`);
|
|
352
|
-
}
|
|
353
|
-
await cleanupSession(filePath);
|
|
354
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
355
|
-
return 0;
|
|
356
|
-
}
|
|
357
|
-
// 7. Not complete → continue loop
|
|
358
|
-
if (!prompt) {
|
|
359
|
-
if (verbose) {
|
|
360
|
-
process.stderr.write(`[hook:run stop] State file corrupted - no prompt text\n`);
|
|
361
|
-
}
|
|
362
|
-
await cleanupSession(filePath);
|
|
363
|
-
process.stdout.write('{"decision":"approve"}\n');
|
|
364
|
-
return 0;
|
|
365
|
-
}
|
|
366
|
-
const nextIteration = iteration + 1;
|
|
367
|
-
const currentTime = (0, session_1.getCurrentTimestamp)();
|
|
368
|
-
// Update session state
|
|
369
|
-
const updatedState = {
|
|
370
|
-
...state,
|
|
371
|
-
iteration: nextIteration,
|
|
372
|
-
lastIterationAt: currentTime,
|
|
373
|
-
iterationTimes: updatedTimes,
|
|
374
|
-
};
|
|
375
|
-
try {
|
|
376
|
-
await (0, session_1.writeSessionFile)(filePath, updatedState, prompt);
|
|
377
|
-
}
|
|
378
|
-
catch {
|
|
379
|
-
if (verbose) {
|
|
380
|
-
process.stderr.write(`[hook:run stop] Failed to update session state\n`);
|
|
381
|
-
}
|
|
382
|
-
// Continue anyway — state might be stale but loop should work
|
|
383
|
-
}
|
|
384
|
-
// 8. Build system message
|
|
385
|
-
let systemMessage;
|
|
386
|
-
if (completionProof) {
|
|
387
|
-
systemMessage = `\u{1F504} Babysitter iteration ${nextIteration} | Run completed! To finish: agent must call 'run:status --json' on your run, extract 'completionProof' from the output, then output it in <promise>SECRET</promise> tags. Do not mention or reveal the secret otherwise.`;
|
|
388
|
-
}
|
|
389
|
-
else if (runState === "waiting" && pendingKinds) {
|
|
390
|
-
systemMessage = `\u{1F504} Babysitter iteration ${nextIteration} | Waiting on: ${pendingKinds}. Check if pending effects are resolved, then call run:iterate.`;
|
|
391
|
-
}
|
|
392
|
-
else if (runState === "failed") {
|
|
393
|
-
systemMessage = `\u{1F504} Babysitter iteration ${nextIteration} | Failed. agent must fix the run, journal or process (inspect the sdk.md if needed) and proceed.`;
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
systemMessage = `\u{1F504} Babysitter iteration ${nextIteration} | Agent should continue orchestration (run:iterate)`;
|
|
397
|
-
}
|
|
398
|
-
// 9. Try to resolve skill context
|
|
399
|
-
if (pluginRoot) {
|
|
400
|
-
try {
|
|
401
|
-
const discoverResult = await (0, skill_1.discoverSkillsInternal)({
|
|
402
|
-
pluginRoot,
|
|
403
|
-
runId: runId || undefined,
|
|
404
|
-
runsDir,
|
|
405
|
-
});
|
|
406
|
-
if (discoverResult.summary) {
|
|
407
|
-
systemMessage = `${systemMessage} | Available skills for this task: ${discoverResult.summary}. Use the Skill tool or skill-discovery to load any of these.`;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
// Skill discovery failure is non-fatal
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
// Cap system message at 1200 chars
|
|
415
|
-
if (systemMessage.length > 1200) {
|
|
416
|
-
systemMessage = systemMessage.slice(0, 1197) + "...";
|
|
417
|
-
}
|
|
418
|
-
// 10. Output block decision
|
|
419
|
-
const output = {
|
|
420
|
-
decision: "block",
|
|
421
|
-
instructions: "use the babysitter skill to advance the orchestration to the next state (run:iterate) or perform the pending effects (task:list --pending --json), or fix the run if it failed.",
|
|
422
|
-
reason: prompt,
|
|
423
|
-
systemMessage,
|
|
424
|
-
};
|
|
425
|
-
process.stdout.write(JSON.stringify(output) + "\n");
|
|
426
|
-
log.info(`Decision: block (iteration=${nextIteration}, maxIterations=${maxIterations})`);
|
|
427
|
-
if (verbose) {
|
|
428
|
-
process.stderr.write(`[hook:run stop] Blocking stop, iteration=${nextIteration} maxIterations=${maxIterations}\n`);
|
|
429
|
-
}
|
|
430
|
-
return 0;
|
|
431
|
-
}
|
|
432
|
-
// ---------------------------------------------------------------------------
|
|
433
|
-
// handleHookRunSessionStart — Claude Code "SessionStart" hook handler
|
|
434
|
-
//
|
|
435
|
-
// Claude Code-specific: reads {session_id} from stdin,
|
|
436
|
-
// writes session ID to CLAUDE_ENV_FILE if set, outputs {}
|
|
437
|
-
// ---------------------------------------------------------------------------
|
|
438
|
-
async function handleHookRunSessionStart(args) {
|
|
439
|
-
const { verbose } = args;
|
|
440
|
-
// 1. Read hook input JSON from stdin
|
|
441
|
-
let rawInput;
|
|
442
|
-
try {
|
|
443
|
-
rawInput = await readStdin();
|
|
444
|
-
}
|
|
445
|
-
catch {
|
|
446
|
-
process.stdout.write("{}\n");
|
|
447
|
-
return 0;
|
|
448
|
-
}
|
|
449
|
-
finally {
|
|
450
|
-
// Unref stdin so it doesn't keep the event loop alive.
|
|
451
|
-
// The session-start handler is short-lived and doesn't need stdin after this.
|
|
452
|
-
// (The stop handler is unaffected — its shell script pipes from a temp file
|
|
453
|
-
// which reaches EOF naturally, so the event loop drains without unref.)
|
|
454
|
-
process.stdin.unref();
|
|
455
|
-
}
|
|
456
|
-
const hookInput = parseHookInput(rawInput);
|
|
457
|
-
const sessionId = safeStr(hookInput, "session_id");
|
|
458
|
-
if (!sessionId) {
|
|
459
|
-
process.stdout.write("{}\n");
|
|
460
|
-
return 0;
|
|
461
|
-
}
|
|
462
|
-
// 2. If CLAUDE_ENV_FILE is set, append session ID export
|
|
463
|
-
const envFile = process.env.CLAUDE_ENV_FILE;
|
|
464
|
-
if (envFile) {
|
|
465
|
-
try {
|
|
466
|
-
(0, node_fs_1.appendFileSync)(envFile, `export CLAUDE_SESSION_ID="${sessionId}"\n`);
|
|
467
|
-
}
|
|
468
|
-
catch {
|
|
469
|
-
// Non-fatal — env file write failure
|
|
470
|
-
if (verbose) {
|
|
471
|
-
process.stderr.write(`[hook:run session-start] Failed to write to CLAUDE_ENV_FILE: ${envFile}\n`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
if (verbose) {
|
|
476
|
-
process.stderr.write(`Babysitter session started: ${sessionId}\n`);
|
|
477
|
-
}
|
|
478
|
-
// 3. Output empty object
|
|
479
|
-
process.stdout.write("{}\n");
|
|
480
|
-
return 0;
|
|
481
|
-
}
|
|
482
|
-
// ---------------------------------------------------------------------------
|
|
483
|
-
// Cleanup helper
|
|
484
|
-
// ---------------------------------------------------------------------------
|
|
485
|
-
async function cleanupSession(filePath) {
|
|
486
|
-
try {
|
|
487
|
-
await (0, session_1.deleteSessionFile)(filePath);
|
|
488
|
-
}
|
|
489
|
-
catch {
|
|
490
|
-
// Best-effort cleanup
|
|
491
|
-
}
|
|
492
|
-
}
|
|
11
|
+
const harness_1 = require("../../harness");
|
|
493
12
|
// ---------------------------------------------------------------------------
|
|
494
13
|
// Main dispatcher
|
|
495
14
|
// ---------------------------------------------------------------------------
|
|
496
|
-
const SUPPORTED_HARNESSES = ["claude-code"];
|
|
497
15
|
async function handleHookRun(args) {
|
|
498
16
|
const { hookType, harness, json } = args;
|
|
499
17
|
if (!hookType) {
|
|
@@ -509,10 +27,12 @@ async function handleHookRun(args) {
|
|
|
509
27
|
}
|
|
510
28
|
return 1;
|
|
511
29
|
}
|
|
512
|
-
|
|
30
|
+
const adapter = (0, harness_1.getAdapterByName)(harness);
|
|
31
|
+
if (!adapter) {
|
|
32
|
+
const supported = (0, harness_1.listSupportedHarnesses)();
|
|
513
33
|
const error = {
|
|
514
34
|
error: "UNSUPPORTED_HARNESS",
|
|
515
|
-
message: `Unsupported harness: "${harness}". Supported: ${
|
|
35
|
+
message: `Unsupported harness: "${harness}". Supported: ${supported.join(", ")}`,
|
|
516
36
|
};
|
|
517
37
|
if (json) {
|
|
518
38
|
process.stderr.write(JSON.stringify(error) + "\n");
|
|
@@ -524,9 +44,9 @@ async function handleHookRun(args) {
|
|
|
524
44
|
}
|
|
525
45
|
switch (hookType) {
|
|
526
46
|
case "stop":
|
|
527
|
-
return await
|
|
47
|
+
return await adapter.handleStopHook(args);
|
|
528
48
|
case "session-start":
|
|
529
|
-
return await
|
|
49
|
+
return await adapter.handleSessionStartHook(args);
|
|
530
50
|
default: {
|
|
531
51
|
const error = {
|
|
532
52
|
error: "UNKNOWN_HOOK_TYPE",
|
|
@@ -15,7 +15,6 @@ export interface RunIterateOptions {
|
|
|
15
15
|
iteration?: number;
|
|
16
16
|
verbose?: boolean;
|
|
17
17
|
json?: boolean;
|
|
18
|
-
pluginRoot?: string;
|
|
19
18
|
}
|
|
20
19
|
export interface RunIterateResult {
|
|
21
20
|
iteration: number;
|
|
@@ -31,8 +30,6 @@ export interface RunIterateResult {
|
|
|
31
30
|
runId: string;
|
|
32
31
|
processId: string;
|
|
33
32
|
hookStatus?: string;
|
|
34
|
-
discoveredSkills?: string[];
|
|
35
|
-
discoveredAgents?: string[];
|
|
36
33
|
};
|
|
37
34
|
}
|
|
38
35
|
export declare function runIterate(options: RunIterateOptions): Promise<RunIterateResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runIterate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/runIterate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"runIterate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/runIterate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,qBAAqB,CAAC;AAKzE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE;QACT,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmJtF"}
|