@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,922 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session management CLI commands.
|
|
4
|
+
* Replaces bash logic from babysitter plugin shell scripts.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.handleSessionInit = handleSessionInit;
|
|
41
|
+
exports.handleSessionAssociate = handleSessionAssociate;
|
|
42
|
+
exports.handleSessionResume = handleSessionResume;
|
|
43
|
+
exports.handleSessionState = handleSessionState;
|
|
44
|
+
exports.handleSessionUpdate = handleSessionUpdate;
|
|
45
|
+
exports.handleSessionCheckIteration = handleSessionCheckIteration;
|
|
46
|
+
exports.parseTranscriptLastAssistantMessage = parseTranscriptLastAssistantMessage;
|
|
47
|
+
exports.extractPromiseTag = extractPromiseTag;
|
|
48
|
+
exports.handleSessionLastMessage = handleSessionLastMessage;
|
|
49
|
+
exports.handleSessionIterationMessage = handleSessionIterationMessage;
|
|
50
|
+
const node_fs_1 = require("node:fs");
|
|
51
|
+
const path = __importStar(require("node:path"));
|
|
52
|
+
const journal_1 = require("../../storage/journal");
|
|
53
|
+
const runFiles_1 = require("../../storage/runFiles");
|
|
54
|
+
const effectIndex_1 = require("../../runtime/replay/effectIndex");
|
|
55
|
+
const completionProof_1 = require("../completionProof");
|
|
56
|
+
const skill_1 = require("./skill");
|
|
57
|
+
const session_1 = require("../../session");
|
|
58
|
+
/**
|
|
59
|
+
* Handle session:init command.
|
|
60
|
+
* Initializes a new session state file.
|
|
61
|
+
*/
|
|
62
|
+
async function handleSessionInit(args) {
|
|
63
|
+
const { sessionId, stateDir, maxIterations = 256, runId = '', prompt = '', json } = args;
|
|
64
|
+
if (!sessionId) {
|
|
65
|
+
const error = { error: 'MISSING_SESSION_ID', message: '--session-id is required' };
|
|
66
|
+
if (json) {
|
|
67
|
+
console.error(JSON.stringify(error));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.error('❌ Error: --session-id is required');
|
|
71
|
+
}
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
if (!stateDir) {
|
|
75
|
+
const error = { error: 'MISSING_STATE_DIR', message: '--state-dir is required' };
|
|
76
|
+
if (json) {
|
|
77
|
+
console.error(JSON.stringify(error));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.error('❌ Error: --state-dir is required');
|
|
81
|
+
}
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
85
|
+
// Check for existing state file (prevent re-entrant runs)
|
|
86
|
+
if (await (0, session_1.sessionFileExists)(filePath)) {
|
|
87
|
+
try {
|
|
88
|
+
const existing = await (0, session_1.readSessionFile)(filePath);
|
|
89
|
+
if (existing.state.runId) {
|
|
90
|
+
const error = {
|
|
91
|
+
error: 'SESSION_EXISTS',
|
|
92
|
+
message: `Session already associated with run: ${existing.state.runId}`,
|
|
93
|
+
runId: existing.state.runId,
|
|
94
|
+
};
|
|
95
|
+
if (json) {
|
|
96
|
+
console.error(JSON.stringify(error));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.error(`❌ Error: This session is already associated with a run (${existing.state.runId})`);
|
|
100
|
+
}
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
const error = {
|
|
104
|
+
error: 'SESSION_EXISTS',
|
|
105
|
+
message: 'A babysitter run is already active for this session',
|
|
106
|
+
};
|
|
107
|
+
if (json) {
|
|
108
|
+
console.error(JSON.stringify(error));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.error('❌ Error: A babysitter run is already active for this session, but with no associated run ID.');
|
|
112
|
+
}
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
// If we can't read it, it might be corrupted - report exists
|
|
117
|
+
const error = { error: 'SESSION_EXISTS', message: 'Session state file exists but could not be read' };
|
|
118
|
+
if (json) {
|
|
119
|
+
console.error(JSON.stringify(error));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.error('❌ Error: Session state file exists but could not be read');
|
|
123
|
+
}
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const now = (0, session_1.getCurrentTimestamp)();
|
|
128
|
+
const state = {
|
|
129
|
+
active: true,
|
|
130
|
+
iteration: 1,
|
|
131
|
+
maxIterations,
|
|
132
|
+
runId,
|
|
133
|
+
startedAt: now,
|
|
134
|
+
lastIterationAt: now,
|
|
135
|
+
iterationTimes: [],
|
|
136
|
+
};
|
|
137
|
+
try {
|
|
138
|
+
await (0, session_1.writeSessionFile)(filePath, state, prompt);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
const err = e instanceof session_1.SessionError ? e : new Error(String(e));
|
|
142
|
+
const error = { error: 'FS_ERROR', message: err.message };
|
|
143
|
+
if (json) {
|
|
144
|
+
console.error(JSON.stringify(error));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.error(`❌ Error: Failed to create state file: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
const result = {
|
|
152
|
+
stateFile: filePath,
|
|
153
|
+
iteration: state.iteration,
|
|
154
|
+
maxIterations: state.maxIterations,
|
|
155
|
+
runId: state.runId,
|
|
156
|
+
};
|
|
157
|
+
if (json) {
|
|
158
|
+
console.log(JSON.stringify(result));
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
console.log(`✅ Session initialized`);
|
|
162
|
+
console.log(` State file: ${filePath}`);
|
|
163
|
+
console.log(` Iteration: ${state.iteration}`);
|
|
164
|
+
console.log(` Max iterations: ${maxIterations > 0 ? maxIterations : 'unlimited'}`);
|
|
165
|
+
if (runId)
|
|
166
|
+
console.log(` Run ID: ${runId}`);
|
|
167
|
+
}
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Handle session:associate command.
|
|
172
|
+
* Associates a session with a run ID.
|
|
173
|
+
*/
|
|
174
|
+
async function handleSessionAssociate(args) {
|
|
175
|
+
const { sessionId, stateDir, runId, json } = args;
|
|
176
|
+
if (!sessionId) {
|
|
177
|
+
const error = { error: 'MISSING_SESSION_ID', message: '--session-id is required' };
|
|
178
|
+
if (json) {
|
|
179
|
+
console.error(JSON.stringify(error));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.error('❌ Error: --session-id is required');
|
|
183
|
+
}
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
if (!stateDir) {
|
|
187
|
+
const error = { error: 'MISSING_STATE_DIR', message: '--state-dir is required' };
|
|
188
|
+
if (json) {
|
|
189
|
+
console.error(JSON.stringify(error));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
console.error('❌ Error: --state-dir is required');
|
|
193
|
+
}
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
if (!runId) {
|
|
197
|
+
const error = { error: 'MISSING_RUN_ID', message: '--run-id is required' };
|
|
198
|
+
if (json) {
|
|
199
|
+
console.error(JSON.stringify(error));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.error('❌ Error: --run-id is required');
|
|
203
|
+
}
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
206
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
207
|
+
// Read existing state
|
|
208
|
+
let existing;
|
|
209
|
+
try {
|
|
210
|
+
existing = await (0, session_1.readSessionFile)(filePath);
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
const err = e instanceof session_1.SessionError ? e : new Error(String(e));
|
|
214
|
+
const error = { error: 'SESSION_NOT_FOUND', message: err.message };
|
|
215
|
+
if (json) {
|
|
216
|
+
console.error(JSON.stringify(error));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.error(`❌ Error: No active babysitter session found`);
|
|
220
|
+
console.error(` Expected state file: ${filePath}`);
|
|
221
|
+
console.error('');
|
|
222
|
+
console.error(' You must first call session:init to initialize the session.');
|
|
223
|
+
}
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
// Check if already associated
|
|
227
|
+
if (existing.state.runId) {
|
|
228
|
+
const error = {
|
|
229
|
+
error: 'RUN_ALREADY_ASSOCIATED',
|
|
230
|
+
message: `Session already associated with run: ${existing.state.runId}`,
|
|
231
|
+
existingRunId: existing.state.runId,
|
|
232
|
+
};
|
|
233
|
+
if (json) {
|
|
234
|
+
console.error(JSON.stringify(error));
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
console.error(`❌ Error: This session is already associated with run: ${existing.state.runId}`);
|
|
238
|
+
}
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
// Update run ID
|
|
242
|
+
const updatedState = {
|
|
243
|
+
...existing.state,
|
|
244
|
+
runId,
|
|
245
|
+
};
|
|
246
|
+
try {
|
|
247
|
+
await (0, session_1.writeSessionFile)(filePath, updatedState, existing.prompt);
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
const err = e instanceof session_1.SessionError ? e : new Error(String(e));
|
|
251
|
+
const error = { error: 'FS_ERROR', message: err.message };
|
|
252
|
+
if (json) {
|
|
253
|
+
console.error(JSON.stringify(error));
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
console.error(`❌ Error: Failed to update state file: ${err.message}`);
|
|
257
|
+
}
|
|
258
|
+
return 1;
|
|
259
|
+
}
|
|
260
|
+
const result = {
|
|
261
|
+
stateFile: filePath,
|
|
262
|
+
runId,
|
|
263
|
+
};
|
|
264
|
+
if (json) {
|
|
265
|
+
console.log(JSON.stringify(result));
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
console.log(`✅ Associated session with run: ${runId}`);
|
|
269
|
+
console.log(` State file: ${filePath}`);
|
|
270
|
+
}
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Handle session:resume command.
|
|
275
|
+
* Resumes an existing run in a new session.
|
|
276
|
+
*/
|
|
277
|
+
async function handleSessionResume(args) {
|
|
278
|
+
const { sessionId, stateDir, runId, maxIterations = 256, runsDir = '.a5c/runs', json } = args;
|
|
279
|
+
if (!sessionId) {
|
|
280
|
+
const error = { error: 'MISSING_SESSION_ID', message: '--session-id is required' };
|
|
281
|
+
if (json) {
|
|
282
|
+
console.error(JSON.stringify(error));
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
console.error('❌ Error: --session-id is required');
|
|
286
|
+
}
|
|
287
|
+
return 1;
|
|
288
|
+
}
|
|
289
|
+
if (!stateDir) {
|
|
290
|
+
const error = { error: 'MISSING_STATE_DIR', message: '--state-dir is required' };
|
|
291
|
+
if (json) {
|
|
292
|
+
console.error(JSON.stringify(error));
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
console.error('❌ Error: --state-dir is required');
|
|
296
|
+
}
|
|
297
|
+
return 1;
|
|
298
|
+
}
|
|
299
|
+
if (!runId) {
|
|
300
|
+
const error = { error: 'MISSING_RUN_ID', message: '--run-id is required' };
|
|
301
|
+
if (json) {
|
|
302
|
+
console.error(JSON.stringify(error));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
console.error('❌ Error: --run-id is required');
|
|
306
|
+
}
|
|
307
|
+
return 1;
|
|
308
|
+
}
|
|
309
|
+
// Verify run exists
|
|
310
|
+
const runDir = path.join(runsDir, runId);
|
|
311
|
+
try {
|
|
312
|
+
await node_fs_1.promises.access(runDir);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
const error = { error: 'RUN_NOT_FOUND', message: `Run not found: ${runId}`, runDir };
|
|
316
|
+
if (json) {
|
|
317
|
+
console.error(JSON.stringify(error));
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
console.error(`❌ Error: Run not found: ${runId}`);
|
|
321
|
+
console.error(` Expected directory: ${runDir}`);
|
|
322
|
+
}
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
// Get run status
|
|
326
|
+
let runState = 'unknown';
|
|
327
|
+
let processId = 'unknown';
|
|
328
|
+
try {
|
|
329
|
+
const runJsonPath = path.join(runDir, 'run.json');
|
|
330
|
+
const runJson = JSON.parse(await node_fs_1.promises.readFile(runJsonPath, 'utf8'));
|
|
331
|
+
processId = (typeof runJson.processId === 'string' ? runJson.processId : undefined) ?? 'unknown';
|
|
332
|
+
// Check journal for completion
|
|
333
|
+
const journalDir = path.join(runDir, 'journal');
|
|
334
|
+
const journalFiles = await node_fs_1.promises.readdir(journalDir);
|
|
335
|
+
const lastFile = journalFiles.filter(f => f.endsWith('.json')).sort().pop();
|
|
336
|
+
if (lastFile) {
|
|
337
|
+
const lastEvent = JSON.parse(await node_fs_1.promises.readFile(path.join(journalDir, lastFile), 'utf8'));
|
|
338
|
+
if (lastEvent.type === 'RUN_COMPLETED') {
|
|
339
|
+
runState = 'completed';
|
|
340
|
+
}
|
|
341
|
+
else if (lastEvent.type === 'RUN_FAILED') {
|
|
342
|
+
runState = 'failed';
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
runState = 'waiting';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
runState = 'unknown';
|
|
351
|
+
}
|
|
352
|
+
// Check if run is completed
|
|
353
|
+
if (runState === 'completed') {
|
|
354
|
+
const error = { error: 'RUN_COMPLETED', message: 'Run is already completed', runId };
|
|
355
|
+
if (json) {
|
|
356
|
+
console.error(JSON.stringify(error));
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
console.error('❌ Error: Run is already completed');
|
|
360
|
+
console.error(` Run ID: ${runId}`);
|
|
361
|
+
console.error(' Cannot resume a completed run.');
|
|
362
|
+
}
|
|
363
|
+
return 1;
|
|
364
|
+
}
|
|
365
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
366
|
+
// Create prompt for resume
|
|
367
|
+
const prompt = `Resume Babysitter run: ${runId}
|
|
368
|
+
|
|
369
|
+
Process: ${processId}
|
|
370
|
+
Current state: ${runState}
|
|
371
|
+
|
|
372
|
+
Continue orchestration using run:iterate, task:post, etc. or fix the run if it's broken/failed/unknown.`;
|
|
373
|
+
const now = (0, session_1.getCurrentTimestamp)();
|
|
374
|
+
const state = {
|
|
375
|
+
active: true,
|
|
376
|
+
iteration: 1,
|
|
377
|
+
maxIterations,
|
|
378
|
+
runId,
|
|
379
|
+
startedAt: now,
|
|
380
|
+
lastIterationAt: now,
|
|
381
|
+
iterationTimes: [],
|
|
382
|
+
};
|
|
383
|
+
try {
|
|
384
|
+
await (0, session_1.writeSessionFile)(filePath, state, prompt);
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
const err = e instanceof session_1.SessionError ? e : new Error(String(e));
|
|
388
|
+
const error = { error: 'FS_ERROR', message: err.message };
|
|
389
|
+
if (json) {
|
|
390
|
+
console.error(JSON.stringify(error));
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
console.error(`❌ Error: Failed to create state file: ${err.message}`);
|
|
394
|
+
}
|
|
395
|
+
return 1;
|
|
396
|
+
}
|
|
397
|
+
const result = {
|
|
398
|
+
stateFile: filePath,
|
|
399
|
+
runId,
|
|
400
|
+
runState,
|
|
401
|
+
processId,
|
|
402
|
+
};
|
|
403
|
+
if (json) {
|
|
404
|
+
console.log(JSON.stringify(result));
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
console.log(`✅ Session resumed for run: ${runId}`);
|
|
408
|
+
console.log(` State file: ${filePath}`);
|
|
409
|
+
console.log(` Process: ${processId}`);
|
|
410
|
+
console.log(` Run state: ${runState}`);
|
|
411
|
+
}
|
|
412
|
+
return 0;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Handle session:state command.
|
|
416
|
+
* Reads and returns session state.
|
|
417
|
+
*/
|
|
418
|
+
async function handleSessionState(args) {
|
|
419
|
+
const { sessionId, stateDir, json } = args;
|
|
420
|
+
if (!sessionId) {
|
|
421
|
+
const error = { error: 'MISSING_SESSION_ID', message: '--session-id is required' };
|
|
422
|
+
if (json) {
|
|
423
|
+
console.error(JSON.stringify(error));
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.error('❌ Error: --session-id is required');
|
|
427
|
+
}
|
|
428
|
+
return 1;
|
|
429
|
+
}
|
|
430
|
+
if (!stateDir) {
|
|
431
|
+
const error = { error: 'MISSING_STATE_DIR', message: '--state-dir is required' };
|
|
432
|
+
if (json) {
|
|
433
|
+
console.error(JSON.stringify(error));
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
console.error('❌ Error: --state-dir is required');
|
|
437
|
+
}
|
|
438
|
+
return 1;
|
|
439
|
+
}
|
|
440
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
441
|
+
if (!(await (0, session_1.sessionFileExists)(filePath))) {
|
|
442
|
+
const result = { found: false, stateFile: filePath };
|
|
443
|
+
if (json) {
|
|
444
|
+
console.log(JSON.stringify(result));
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
console.log(`[session:state] not found: ${filePath}`);
|
|
448
|
+
}
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
const file = await (0, session_1.readSessionFile)(filePath);
|
|
453
|
+
const result = {
|
|
454
|
+
found: true,
|
|
455
|
+
state: file.state,
|
|
456
|
+
prompt: file.prompt,
|
|
457
|
+
stateFile: filePath,
|
|
458
|
+
};
|
|
459
|
+
if (json) {
|
|
460
|
+
console.log(JSON.stringify(result));
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.log(`[session:state] found: ${filePath}`);
|
|
464
|
+
console.log(` active: ${file.state.active}`);
|
|
465
|
+
console.log(` iteration: ${file.state.iteration}`);
|
|
466
|
+
console.log(` maxIterations: ${file.state.maxIterations}`);
|
|
467
|
+
console.log(` runId: ${file.state.runId || '(none)'}`);
|
|
468
|
+
console.log(` startedAt: ${file.state.startedAt}`);
|
|
469
|
+
console.log(` lastIterationAt: ${file.state.lastIterationAt}`);
|
|
470
|
+
console.log(` iterationTimes: [${file.state.iterationTimes.join(', ')}]`);
|
|
471
|
+
}
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
474
|
+
catch (e) {
|
|
475
|
+
const err = e instanceof session_1.SessionError ? e : new Error(String(e));
|
|
476
|
+
const error = { error: 'CORRUPTED_STATE', message: err.message, stateFile: filePath };
|
|
477
|
+
if (json) {
|
|
478
|
+
console.error(JSON.stringify(error));
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
console.error(`❌ Error: Failed to read state file: ${err.message}`);
|
|
482
|
+
}
|
|
483
|
+
return 1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Handle session:update command.
|
|
488
|
+
* Updates session state fields.
|
|
489
|
+
*/
|
|
490
|
+
async function handleSessionUpdate(args) {
|
|
491
|
+
const { sessionId, stateDir, iteration, lastIterationAt, iterationTimes, json } = args;
|
|
492
|
+
const shouldDelete = args.delete;
|
|
493
|
+
if (!sessionId) {
|
|
494
|
+
const error = { error: 'MISSING_SESSION_ID', message: '--session-id is required' };
|
|
495
|
+
if (json) {
|
|
496
|
+
console.error(JSON.stringify(error));
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
console.error('❌ Error: --session-id is required');
|
|
500
|
+
}
|
|
501
|
+
return 1;
|
|
502
|
+
}
|
|
503
|
+
if (!stateDir) {
|
|
504
|
+
const error = { error: 'MISSING_STATE_DIR', message: '--state-dir is required' };
|
|
505
|
+
if (json) {
|
|
506
|
+
console.error(JSON.stringify(error));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
console.error('❌ Error: --state-dir is required');
|
|
510
|
+
}
|
|
511
|
+
return 1;
|
|
512
|
+
}
|
|
513
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
514
|
+
// Handle delete
|
|
515
|
+
if (shouldDelete) {
|
|
516
|
+
const deleted = await (0, session_1.deleteSessionFile)(filePath);
|
|
517
|
+
const result = { success: true, deleted, stateFile: filePath };
|
|
518
|
+
if (json) {
|
|
519
|
+
console.log(JSON.stringify(result));
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
console.log(`✅ Session state file ${deleted ? 'deleted' : 'not found (already deleted)'}`);
|
|
523
|
+
}
|
|
524
|
+
return 0;
|
|
525
|
+
}
|
|
526
|
+
// Read existing state
|
|
527
|
+
let existing;
|
|
528
|
+
try {
|
|
529
|
+
existing = await (0, session_1.readSessionFile)(filePath);
|
|
530
|
+
}
|
|
531
|
+
catch (e) {
|
|
532
|
+
const err = e instanceof session_1.SessionError ? e : new Error(String(e));
|
|
533
|
+
const error = { error: 'SESSION_NOT_FOUND', message: err.message };
|
|
534
|
+
if (json) {
|
|
535
|
+
console.error(JSON.stringify(error));
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
console.error(`❌ Error: Session not found: ${err.message}`);
|
|
539
|
+
}
|
|
540
|
+
return 1;
|
|
541
|
+
}
|
|
542
|
+
// Build updates
|
|
543
|
+
const updates = {};
|
|
544
|
+
if (iteration !== undefined) {
|
|
545
|
+
updates.iteration = iteration;
|
|
546
|
+
}
|
|
547
|
+
if (lastIterationAt !== undefined) {
|
|
548
|
+
updates.lastIterationAt = lastIterationAt;
|
|
549
|
+
}
|
|
550
|
+
if (iterationTimes !== undefined) {
|
|
551
|
+
updates.iterationTimes = iterationTimes
|
|
552
|
+
.split(',')
|
|
553
|
+
.map(s => parseInt(s.trim(), 10))
|
|
554
|
+
.filter(n => Number.isFinite(n) && n > 0);
|
|
555
|
+
}
|
|
556
|
+
// Apply updates
|
|
557
|
+
const updatedState = {
|
|
558
|
+
...existing.state,
|
|
559
|
+
...updates,
|
|
560
|
+
};
|
|
561
|
+
try {
|
|
562
|
+
await (0, session_1.writeSessionFile)(filePath, updatedState, existing.prompt);
|
|
563
|
+
}
|
|
564
|
+
catch (e) {
|
|
565
|
+
const err = e instanceof session_1.SessionError ? e : new Error(String(e));
|
|
566
|
+
const error = { error: 'FS_ERROR', message: err.message };
|
|
567
|
+
if (json) {
|
|
568
|
+
console.error(JSON.stringify(error));
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
console.error(`❌ Error: Failed to update state file: ${err.message}`);
|
|
572
|
+
}
|
|
573
|
+
return 1;
|
|
574
|
+
}
|
|
575
|
+
const result = {
|
|
576
|
+
success: true,
|
|
577
|
+
state: updatedState,
|
|
578
|
+
stateFile: filePath,
|
|
579
|
+
};
|
|
580
|
+
if (json) {
|
|
581
|
+
console.log(JSON.stringify(result));
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
console.log(`✅ Session state updated`);
|
|
585
|
+
console.log(` State file: ${filePath}`);
|
|
586
|
+
if (iteration !== undefined)
|
|
587
|
+
console.log(` iteration: ${iteration}`);
|
|
588
|
+
if (lastIterationAt !== undefined)
|
|
589
|
+
console.log(` lastIterationAt: ${lastIterationAt}`);
|
|
590
|
+
if (iterationTimes !== undefined)
|
|
591
|
+
console.log(` iterationTimes: [${updatedState.iterationTimes.join(', ')}]`);
|
|
592
|
+
}
|
|
593
|
+
return 0;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Handle session:check-iteration command.
|
|
597
|
+
* Checks if iteration should continue based on timing and limits.
|
|
598
|
+
*/
|
|
599
|
+
async function handleSessionCheckIteration(args) {
|
|
600
|
+
const { sessionId, stateDir, json } = args;
|
|
601
|
+
if (!sessionId || !stateDir) {
|
|
602
|
+
const error = { error: 'MISSING_ARGS', message: '--session-id and --state-dir are required' };
|
|
603
|
+
if (json) {
|
|
604
|
+
console.error(JSON.stringify(error));
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
console.error('❌ Error: --session-id and --state-dir are required');
|
|
608
|
+
}
|
|
609
|
+
return 1;
|
|
610
|
+
}
|
|
611
|
+
const filePath = (0, session_1.getSessionFilePath)(stateDir, sessionId);
|
|
612
|
+
let file;
|
|
613
|
+
try {
|
|
614
|
+
file = await (0, session_1.readSessionFile)(filePath);
|
|
615
|
+
}
|
|
616
|
+
catch {
|
|
617
|
+
const result = {
|
|
618
|
+
found: false,
|
|
619
|
+
shouldContinue: false,
|
|
620
|
+
reason: 'session_not_found',
|
|
621
|
+
iteration: 0,
|
|
622
|
+
maxIterations: 0,
|
|
623
|
+
runId: '',
|
|
624
|
+
prompt: '',
|
|
625
|
+
stopMessage: 'Session not found',
|
|
626
|
+
};
|
|
627
|
+
if (json) {
|
|
628
|
+
console.log(JSON.stringify(result));
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
console.log('[session:check-iteration] shouldContinue=false reason=session_not_found');
|
|
632
|
+
}
|
|
633
|
+
return 0;
|
|
634
|
+
}
|
|
635
|
+
const { state } = file;
|
|
636
|
+
// Check max iterations
|
|
637
|
+
if (state.maxIterations > 0 && state.iteration >= state.maxIterations) {
|
|
638
|
+
const result = {
|
|
639
|
+
found: true,
|
|
640
|
+
shouldContinue: false,
|
|
641
|
+
reason: 'max_iterations_reached',
|
|
642
|
+
iteration: state.iteration,
|
|
643
|
+
maxIterations: state.maxIterations,
|
|
644
|
+
runId: state.runId ?? '',
|
|
645
|
+
prompt: file.prompt ?? '',
|
|
646
|
+
stopMessage: `Max iterations (${state.maxIterations}) reached`,
|
|
647
|
+
};
|
|
648
|
+
if (json) {
|
|
649
|
+
console.log(JSON.stringify(result));
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
console.log(`[session:check-iteration] shouldContinue=false reason=max_iterations_reached iteration=${state.iteration}`);
|
|
653
|
+
}
|
|
654
|
+
return 0;
|
|
655
|
+
}
|
|
656
|
+
// Check iteration timing (runaway loop detection)
|
|
657
|
+
const now = (0, session_1.getCurrentTimestamp)();
|
|
658
|
+
const updatedTimes = state.iteration >= 5
|
|
659
|
+
? (0, session_1.updateIterationTimes)(state.iterationTimes, state.lastIterationAt, now)
|
|
660
|
+
: state.iterationTimes;
|
|
661
|
+
if ((0, session_1.isIterationTooFast)(updatedTimes)) {
|
|
662
|
+
const avg = updatedTimes.reduce((a, b) => a + b, 0) / updatedTimes.length;
|
|
663
|
+
const result = {
|
|
664
|
+
found: true,
|
|
665
|
+
shouldContinue: false,
|
|
666
|
+
reason: 'iteration_too_fast',
|
|
667
|
+
averageTime: avg,
|
|
668
|
+
threshold: 15,
|
|
669
|
+
iteration: state.iteration,
|
|
670
|
+
maxIterations: state.maxIterations,
|
|
671
|
+
runId: state.runId ?? '',
|
|
672
|
+
prompt: file.prompt ?? '',
|
|
673
|
+
stopMessage: `Average iteration time too fast (${avg}s <= 15s)`,
|
|
674
|
+
};
|
|
675
|
+
if (json) {
|
|
676
|
+
console.log(JSON.stringify(result));
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
console.log(`[session:check-iteration] shouldContinue=false reason=iteration_too_fast avg=${avg}s`);
|
|
680
|
+
}
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
const result = {
|
|
684
|
+
found: true,
|
|
685
|
+
shouldContinue: true,
|
|
686
|
+
nextIteration: state.iteration + 1,
|
|
687
|
+
updatedIterationTimes: updatedTimes,
|
|
688
|
+
iteration: state.iteration,
|
|
689
|
+
maxIterations: state.maxIterations,
|
|
690
|
+
runId: state.runId ?? '',
|
|
691
|
+
prompt: file.prompt ?? '',
|
|
692
|
+
};
|
|
693
|
+
if (json) {
|
|
694
|
+
console.log(JSON.stringify(result));
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
console.log(`[session:check-iteration] shouldContinue=true nextIteration=${state.iteration + 1}`);
|
|
698
|
+
}
|
|
699
|
+
return 0;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Parse a JSONL transcript file and extract the last assistant text message.
|
|
703
|
+
* Also detects <promise>...</promise> tags in the text.
|
|
704
|
+
*/
|
|
705
|
+
function parseTranscriptLastAssistantMessage(content) {
|
|
706
|
+
const lines = content.split('\n').filter((l) => l.trim().length > 0);
|
|
707
|
+
let lastAssistant = null;
|
|
708
|
+
for (const line of lines) {
|
|
709
|
+
try {
|
|
710
|
+
const parsed = JSON.parse(line);
|
|
711
|
+
if (parsed && typeof parsed === 'object') {
|
|
712
|
+
const obj = parsed;
|
|
713
|
+
// Match assistant entries in multiple transcript formats:
|
|
714
|
+
// 1. Top-level role: "assistant" (test/simple format)
|
|
715
|
+
// 2. Top-level type: "assistant" with message.role: "assistant" (real Claude Code JSONL)
|
|
716
|
+
// 3. message.role: "assistant" regardless of top-level fields (general fallback)
|
|
717
|
+
const isAssistant = obj.role === 'assistant' ||
|
|
718
|
+
obj.type === 'assistant' ||
|
|
719
|
+
(obj.message &&
|
|
720
|
+
typeof obj.message === 'object' &&
|
|
721
|
+
obj.message.role === 'assistant');
|
|
722
|
+
if (isAssistant) {
|
|
723
|
+
lastAssistant = parsed;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
catch {
|
|
728
|
+
// Skip malformed lines
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (!lastAssistant) {
|
|
732
|
+
return { found: false, text: null };
|
|
733
|
+
}
|
|
734
|
+
const msg = lastAssistant;
|
|
735
|
+
// Handle message.content array structure (Claude API format)
|
|
736
|
+
// or content array directly
|
|
737
|
+
const contentArr = (msg.message && typeof msg.message === 'object'
|
|
738
|
+
? msg.message.content
|
|
739
|
+
: msg.content);
|
|
740
|
+
if (!Array.isArray(contentArr)) {
|
|
741
|
+
return { found: false, text: null };
|
|
742
|
+
}
|
|
743
|
+
const textParts = contentArr
|
|
744
|
+
.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
745
|
+
.map((block) => block.text);
|
|
746
|
+
if (textParts.length === 0) {
|
|
747
|
+
return { found: false, text: null };
|
|
748
|
+
}
|
|
749
|
+
return { found: true, text: textParts.join('\n') };
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Extract content from first <promise>...</promise> tag.
|
|
753
|
+
* Trims whitespace and collapses internal whitespace to single spaces.
|
|
754
|
+
*/
|
|
755
|
+
function extractPromiseTag(text) {
|
|
756
|
+
const match = text.match(/<promise>([\s\S]*?)<\/promise>/);
|
|
757
|
+
if (!match)
|
|
758
|
+
return null;
|
|
759
|
+
return match[1].trim().replace(/\s+/g, ' ');
|
|
760
|
+
}
|
|
761
|
+
function handleSessionLastMessage(args) {
|
|
762
|
+
const result = {
|
|
763
|
+
found: false,
|
|
764
|
+
text: null,
|
|
765
|
+
hasPromise: false,
|
|
766
|
+
promiseValue: null,
|
|
767
|
+
};
|
|
768
|
+
const transcriptPath = path.resolve(args.transcriptPath);
|
|
769
|
+
if (!(0, node_fs_1.existsSync)(transcriptPath)) {
|
|
770
|
+
result.error = 'TRANSCRIPT_NOT_FOUND';
|
|
771
|
+
if (args.json) {
|
|
772
|
+
console.log(JSON.stringify(result));
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
console.log(`[session:last-message] error=TRANSCRIPT_NOT_FOUND path=${transcriptPath}`);
|
|
776
|
+
}
|
|
777
|
+
return 0;
|
|
778
|
+
}
|
|
779
|
+
try {
|
|
780
|
+
const content = (0, node_fs_1.readFileSync)(transcriptPath, 'utf-8');
|
|
781
|
+
const parsed = parseTranscriptLastAssistantMessage(content);
|
|
782
|
+
result.found = parsed.found;
|
|
783
|
+
result.text = parsed.text;
|
|
784
|
+
if (parsed.found && parsed.text) {
|
|
785
|
+
const promiseValue = extractPromiseTag(parsed.text);
|
|
786
|
+
result.hasPromise = promiseValue !== null;
|
|
787
|
+
result.promiseValue = promiseValue;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
result.error = 'TRANSCRIPT_PARSE_ERROR';
|
|
792
|
+
}
|
|
793
|
+
if (args.json) {
|
|
794
|
+
console.log(JSON.stringify(result));
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
console.log(`[session:last-message] found=${result.found} hasPromise=${result.hasPromise}${result.promiseValue ? ` promiseValue=${result.promiseValue}` : ''}`);
|
|
798
|
+
}
|
|
799
|
+
return 0;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Count pending effects grouped by kind, returning a record of kind -> count.
|
|
803
|
+
*/
|
|
804
|
+
function countPendingByKind(records) {
|
|
805
|
+
const counts = new Map();
|
|
806
|
+
for (const record of records) {
|
|
807
|
+
const key = record.kind ?? 'unknown';
|
|
808
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
809
|
+
}
|
|
810
|
+
return Object.fromEntries(Array.from(counts.entries()).sort(([a], [b]) => a.localeCompare(b)));
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Handle session:iteration-message command.
|
|
814
|
+
* Generates the formatted system message for the next babysitter iteration.
|
|
815
|
+
* Replaces ~22 lines of bash branching + skill-context-resolver call in babysitter-stop-hook.sh.
|
|
816
|
+
*/
|
|
817
|
+
async function handleSessionIterationMessage(args) {
|
|
818
|
+
const { iteration, runId, runsDir, pluginRoot, json } = args;
|
|
819
|
+
// Validate --iteration is provided
|
|
820
|
+
if (iteration === undefined) {
|
|
821
|
+
const error = { error: 'MISSING_ITERATION', message: '--iteration is required' };
|
|
822
|
+
if (json) {
|
|
823
|
+
console.error(JSON.stringify(error));
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
console.error('Error: --iteration is required for session:iteration-message');
|
|
827
|
+
}
|
|
828
|
+
return 1;
|
|
829
|
+
}
|
|
830
|
+
let runState = null;
|
|
831
|
+
let completionProof = null;
|
|
832
|
+
let pendingKinds = null;
|
|
833
|
+
// If --run-id is provided, resolve run state from SDK internals
|
|
834
|
+
if (runId) {
|
|
835
|
+
const runDir = path.isAbsolute(runId) ? runId : path.join(runsDir, runId);
|
|
836
|
+
try {
|
|
837
|
+
const metadata = await (0, runFiles_1.readRunMetadata)(runDir);
|
|
838
|
+
const journal = await (0, journal_1.loadJournal)(runDir);
|
|
839
|
+
const index = await (0, effectIndex_1.buildEffectIndex)({ runDir, events: journal });
|
|
840
|
+
// Check for completion proof
|
|
841
|
+
const hasCompleted = journal.some((e) => e.type === 'RUN_COMPLETED');
|
|
842
|
+
const hasFailed = journal.some((e) => e.type === 'RUN_FAILED');
|
|
843
|
+
if (hasCompleted) {
|
|
844
|
+
completionProof = (0, completionProof_1.resolveCompletionProof)(metadata);
|
|
845
|
+
}
|
|
846
|
+
// Determine pending effects
|
|
847
|
+
const pendingRecords = index.listPendingEffects();
|
|
848
|
+
const pendingByKind = countPendingByKind(pendingRecords);
|
|
849
|
+
const kindKeys = Object.keys(pendingByKind);
|
|
850
|
+
if (kindKeys.length > 0) {
|
|
851
|
+
pendingKinds = kindKeys.join(', ');
|
|
852
|
+
}
|
|
853
|
+
// Derive run state
|
|
854
|
+
if (completionProof) {
|
|
855
|
+
runState = 'completed';
|
|
856
|
+
}
|
|
857
|
+
else if (hasFailed) {
|
|
858
|
+
runState = 'failed';
|
|
859
|
+
}
|
|
860
|
+
else if (pendingRecords.length > 0) {
|
|
861
|
+
runState = 'waiting';
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
runState = 'created';
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
catch {
|
|
868
|
+
// If we can't read run state, continue without it
|
|
869
|
+
runState = null;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// Build system message using 4-branch logic
|
|
873
|
+
let systemMessage;
|
|
874
|
+
if (completionProof) {
|
|
875
|
+
systemMessage =
|
|
876
|
+
`\u{1F504} Babysitter iteration ${iteration} | 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.`;
|
|
877
|
+
}
|
|
878
|
+
else if (runState === 'waiting' && pendingKinds) {
|
|
879
|
+
systemMessage =
|
|
880
|
+
`\u{1F504} Babysitter iteration ${iteration} | Waiting on: ${pendingKinds}. Check if pending effects are resolved, then call run:iterate.`;
|
|
881
|
+
}
|
|
882
|
+
else if (runState === 'failed') {
|
|
883
|
+
systemMessage =
|
|
884
|
+
`\u{1F504} Babysitter iteration ${iteration} | Failed. agent must fix the run, journal or process (inspect the sdk.md if needed) and proceed.`;
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
systemMessage =
|
|
888
|
+
`\u{1F504} Babysitter iteration ${iteration} | Agent should continue orchestration (run:iterate)`;
|
|
889
|
+
}
|
|
890
|
+
// Resolve skill context via CLI skill discovery when pluginRoot is provided
|
|
891
|
+
let skillContext = null;
|
|
892
|
+
if (pluginRoot) {
|
|
893
|
+
try {
|
|
894
|
+
const discoverResult = await (0, skill_1.discoverSkillsInternal)({
|
|
895
|
+
pluginRoot,
|
|
896
|
+
runId,
|
|
897
|
+
runsDir,
|
|
898
|
+
});
|
|
899
|
+
skillContext = discoverResult.summary || null;
|
|
900
|
+
}
|
|
901
|
+
catch {
|
|
902
|
+
// Skill discovery failure is non-fatal
|
|
903
|
+
skillContext = null;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const result = {
|
|
907
|
+
systemMessage,
|
|
908
|
+
runState,
|
|
909
|
+
completionProof,
|
|
910
|
+
pendingKinds,
|
|
911
|
+
skillContext,
|
|
912
|
+
iteration,
|
|
913
|
+
};
|
|
914
|
+
if (json) {
|
|
915
|
+
console.log(JSON.stringify(result));
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
console.log(`[session:iteration-message] iteration=${iteration} runState=${runState ?? 'none'}`);
|
|
919
|
+
console.log(` systemMessage: ${systemMessage}`);
|
|
920
|
+
}
|
|
921
|
+
return 0;
|
|
922
|
+
}
|