@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.
Files changed (76) hide show
  1. package/dist/cli/commands/configure.d.ts +124 -0
  2. package/dist/cli/commands/configure.d.ts.map +1 -0
  3. package/dist/cli/commands/configure.js +514 -0
  4. package/dist/cli/commands/health.d.ts +89 -0
  5. package/dist/cli/commands/health.d.ts.map +1 -0
  6. package/dist/cli/commands/health.js +579 -0
  7. package/dist/cli/commands/hookLog.d.ts +15 -0
  8. package/dist/cli/commands/hookLog.d.ts.map +1 -0
  9. package/dist/cli/commands/hookLog.js +286 -0
  10. package/dist/cli/commands/hookRun.d.ts +20 -0
  11. package/dist/cli/commands/hookRun.d.ts.map +1 -0
  12. package/dist/cli/commands/hookRun.js +544 -0
  13. package/dist/cli/commands/runExecuteTasks.d.ts +42 -0
  14. package/dist/cli/commands/runExecuteTasks.d.ts.map +1 -0
  15. package/dist/cli/commands/runExecuteTasks.js +377 -0
  16. package/dist/cli/commands/runIterate.d.ts +5 -1
  17. package/dist/cli/commands/runIterate.d.ts.map +1 -1
  18. package/dist/cli/commands/runIterate.js +75 -6
  19. package/dist/cli/commands/session.d.ts +97 -0
  20. package/dist/cli/commands/session.d.ts.map +1 -0
  21. package/dist/cli/commands/session.js +922 -0
  22. package/dist/cli/commands/skill.d.ts +87 -0
  23. package/dist/cli/commands/skill.d.ts.map +1 -0
  24. package/dist/cli/commands/skill.js +869 -0
  25. package/dist/cli/completionProof.d.ts +4 -0
  26. package/dist/cli/completionProof.d.ts.map +1 -0
  27. package/dist/cli/{completionSecret.js → completionProof.js} +7 -7
  28. package/dist/cli/main.d.ts +14 -0
  29. package/dist/cli/main.d.ts.map +1 -1
  30. package/dist/cli/main.js +649 -16
  31. package/dist/config/defaults.d.ts +165 -0
  32. package/dist/config/defaults.d.ts.map +1 -0
  33. package/dist/config/defaults.js +281 -0
  34. package/dist/config/index.d.ts +25 -0
  35. package/dist/config/index.d.ts.map +1 -0
  36. package/dist/config/index.js +35 -0
  37. package/dist/hooks/dispatcher.d.ts.map +1 -1
  38. package/dist/hooks/dispatcher.js +2 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -0
  42. package/dist/runtime/constants.d.ts +1 -1
  43. package/dist/runtime/constants.d.ts.map +1 -1
  44. package/dist/runtime/createRun.d.ts.map +1 -1
  45. package/dist/runtime/createRun.js +7 -3
  46. package/dist/runtime/exceptions.d.ts +186 -3
  47. package/dist/runtime/exceptions.d.ts.map +1 -1
  48. package/dist/runtime/exceptions.js +416 -15
  49. package/dist/runtime/types.d.ts +1 -0
  50. package/dist/runtime/types.d.ts.map +1 -1
  51. package/dist/session/index.d.ts +9 -0
  52. package/dist/session/index.d.ts.map +1 -0
  53. package/dist/session/index.js +30 -0
  54. package/dist/session/parse.d.ts +45 -0
  55. package/dist/session/parse.d.ts.map +1 -0
  56. package/dist/session/parse.js +159 -0
  57. package/dist/session/types.d.ts +194 -0
  58. package/dist/session/types.d.ts.map +1 -0
  59. package/dist/session/types.js +45 -0
  60. package/dist/session/write.d.ts +50 -0
  61. package/dist/session/write.d.ts.map +1 -0
  62. package/dist/session/write.js +196 -0
  63. package/dist/storage/createRunDir.d.ts.map +1 -1
  64. package/dist/storage/createRunDir.js +1 -0
  65. package/dist/storage/paths.d.ts +5 -1
  66. package/dist/storage/paths.d.ts.map +1 -1
  67. package/dist/storage/paths.js +6 -1
  68. package/dist/storage/types.d.ts +3 -1
  69. package/dist/storage/types.d.ts.map +1 -1
  70. package/dist/tasks/kinds/index.d.ts.map +1 -1
  71. package/dist/tasks/kinds/index.js +6 -1
  72. package/dist/testing/runHarness.d.ts.map +1 -1
  73. package/dist/testing/runHarness.js +5 -1
  74. package/package.json +1 -2
  75. package/dist/cli/completionSecret.d.ts +0 -4
  76. 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
+ }