@a5c-ai/babysitter-sdk 0.0.169 → 0.0.170-staging.00aac85c
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/configure.d.ts +124 -0
- package/dist/cli/commands/configure.d.ts.map +1 -0
- package/dist/cli/commands/configure.js +514 -0
- package/dist/cli/commands/health.d.ts +89 -0
- package/dist/cli/commands/health.d.ts.map +1 -0
- package/dist/cli/commands/health.js +579 -0
- package/dist/cli/commands/hookLog.d.ts +15 -0
- package/dist/cli/commands/hookLog.d.ts.map +1 -0
- package/dist/cli/commands/hookLog.js +286 -0
- package/dist/cli/commands/hookRun.d.ts +20 -0
- package/dist/cli/commands/hookRun.d.ts.map +1 -0
- package/dist/cli/commands/hookRun.js +544 -0
- package/dist/cli/commands/runExecuteTasks.d.ts +42 -0
- package/dist/cli/commands/runExecuteTasks.d.ts.map +1 -0
- package/dist/cli/commands/runExecuteTasks.js +377 -0
- package/dist/cli/commands/runIterate.d.ts +5 -1
- package/dist/cli/commands/runIterate.d.ts.map +1 -1
- package/dist/cli/commands/runIterate.js +75 -6
- package/dist/cli/commands/session.d.ts +97 -0
- package/dist/cli/commands/session.d.ts.map +1 -0
- package/dist/cli/commands/session.js +922 -0
- package/dist/cli/commands/skill.d.ts +87 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +869 -0
- package/dist/cli/completionProof.d.ts +4 -0
- package/dist/cli/completionProof.d.ts.map +1 -0
- package/dist/cli/{completionSecret.js → completionProof.js} +7 -7
- package/dist/cli/main.d.ts +14 -0
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +649 -16
- package/dist/config/defaults.d.ts +165 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +281 -0
- package/dist/config/index.d.ts +25 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +35 -0
- package/dist/hooks/dispatcher.d.ts.map +1 -1
- package/dist/hooks/dispatcher.js +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/runtime/constants.d.ts +1 -1
- package/dist/runtime/constants.d.ts.map +1 -1
- package/dist/runtime/createRun.d.ts.map +1 -1
- package/dist/runtime/createRun.js +7 -3
- package/dist/runtime/exceptions.d.ts +186 -3
- package/dist/runtime/exceptions.d.ts.map +1 -1
- package/dist/runtime/exceptions.js +416 -15
- package/dist/runtime/types.d.ts +1 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/session/index.d.ts +9 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +30 -0
- package/dist/session/parse.d.ts +45 -0
- package/dist/session/parse.d.ts.map +1 -0
- package/dist/session/parse.js +159 -0
- package/dist/session/types.d.ts +194 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +45 -0
- package/dist/session/write.d.ts +50 -0
- package/dist/session/write.d.ts.map +1 -0
- package/dist/session/write.js +196 -0
- package/dist/storage/createRunDir.d.ts.map +1 -1
- package/dist/storage/createRunDir.js +1 -0
- package/dist/storage/paths.d.ts +5 -1
- package/dist/storage/paths.d.ts.map +1 -1
- package/dist/storage/paths.js +6 -1
- package/dist/storage/types.d.ts +3 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/tasks/kinds/index.d.ts.map +1 -1
- package/dist/tasks/kinds/index.js +6 -1
- package/dist/testing/runHarness.d.ts.map +1 -1
- package/dist/testing/runHarness.js +5 -1
- package/package.json +1 -2
- package/dist/cli/completionSecret.d.ts +0 -4
- package/dist/cli/completionSecret.d.ts.map +0 -1
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* hook:run CLI command.
|
|
4
|
+
*
|
|
5
|
+
* Replaces heavy bash hook scripts with a single TypeScript command.
|
|
6
|
+
* Dispatches to the appropriate handler based on --hook-type:
|
|
7
|
+
* - "stop" → handleHookRunStop (replaces babysitter-stop-hook.sh)
|
|
8
|
+
* - "session-start" → handleHookRunSessionStart (replaces babysitter-session-start-hook.sh)
|
|
9
|
+
*/
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.handleHookRun = handleHookRun;
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
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
|
+
}
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
// Main dispatcher
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
const SUPPORTED_HARNESSES = ["claude-code"];
|
|
497
|
+
async function handleHookRun(args) {
|
|
498
|
+
const { hookType, harness, json } = args;
|
|
499
|
+
if (!hookType) {
|
|
500
|
+
const error = {
|
|
501
|
+
error: "MISSING_HOOK_TYPE",
|
|
502
|
+
message: "--hook-type is required for hook:run",
|
|
503
|
+
};
|
|
504
|
+
if (json) {
|
|
505
|
+
process.stderr.write(JSON.stringify(error) + "\n");
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
process.stderr.write("Error: --hook-type is required for hook:run\n");
|
|
509
|
+
}
|
|
510
|
+
return 1;
|
|
511
|
+
}
|
|
512
|
+
if (!SUPPORTED_HARNESSES.includes(harness)) {
|
|
513
|
+
const error = {
|
|
514
|
+
error: "UNSUPPORTED_HARNESS",
|
|
515
|
+
message: `Unsupported harness: "${harness}". Supported: ${SUPPORTED_HARNESSES.join(", ")}`,
|
|
516
|
+
};
|
|
517
|
+
if (json) {
|
|
518
|
+
process.stderr.write(JSON.stringify(error) + "\n");
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
process.stderr.write(`Error: ${error.message}\n`);
|
|
522
|
+
}
|
|
523
|
+
return 1;
|
|
524
|
+
}
|
|
525
|
+
switch (hookType) {
|
|
526
|
+
case "stop":
|
|
527
|
+
return await handleHookRunStop(args);
|
|
528
|
+
case "session-start":
|
|
529
|
+
return await handleHookRunSessionStart(args);
|
|
530
|
+
default: {
|
|
531
|
+
const error = {
|
|
532
|
+
error: "UNKNOWN_HOOK_TYPE",
|
|
533
|
+
message: `Unknown hook type: ${hookType}. Supported: stop, session-start`,
|
|
534
|
+
};
|
|
535
|
+
if (json) {
|
|
536
|
+
process.stderr.write(JSON.stringify(error) + "\n");
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
process.stderr.write(`Error: Unknown hook type: ${hookType}. Supported: stop, session-start\n`);
|
|
540
|
+
}
|
|
541
|
+
return 1;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run:execute-tasks command - Execute pending node tasks in a run
|
|
3
|
+
*
|
|
4
|
+
* This command replaces the shell-based task execution logic in native-orchestrator.sh.
|
|
5
|
+
* It:
|
|
6
|
+
* 1. Loads the journal and builds the effect index
|
|
7
|
+
* 2. Filters pending effects by kind (default "node")
|
|
8
|
+
* 3. Slices to maxTasks (default 3)
|
|
9
|
+
* 4. For each task: reads task.json, spawns `node`, captures stdout/stderr, commits result
|
|
10
|
+
* 5. Returns a summary of executed tasks
|
|
11
|
+
*/
|
|
12
|
+
export interface RunExecuteTasksOptions {
|
|
13
|
+
runDir: string;
|
|
14
|
+
maxTasks?: number;
|
|
15
|
+
kind?: string;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
verbose?: boolean;
|
|
18
|
+
json?: boolean;
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface TaskExecutionSummary {
|
|
22
|
+
effectId: string;
|
|
23
|
+
label: string | null;
|
|
24
|
+
status: "ok" | "error";
|
|
25
|
+
exitCode: number;
|
|
26
|
+
durationMs: number;
|
|
27
|
+
stdoutRef: string | null;
|
|
28
|
+
stderrRef: string | null;
|
|
29
|
+
resultRef: string | null;
|
|
30
|
+
error?: {
|
|
31
|
+
name: string;
|
|
32
|
+
message: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export interface RunExecuteTasksResult {
|
|
36
|
+
action: string;
|
|
37
|
+
count: number;
|
|
38
|
+
reason: string;
|
|
39
|
+
tasks: TaskExecutionSummary[];
|
|
40
|
+
}
|
|
41
|
+
export declare function runExecuteTasks(options: RunExecuteTasksOptions): Promise<RunExecuteTasksResult>;
|
|
42
|
+
//# sourceMappingURL=runExecuteTasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runExecuteTasks.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/runExecuteTasks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AA4TD,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+ErG"}
|