@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
package/dist/cli/main.js CHANGED
@@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports._resolveRunDir = resolveRunDir;
38
+ exports._collapseDoubledA5cRuns = collapseDoubledA5cRuns;
37
39
  exports.createBabysitterCli = createBabysitterCli;
38
40
  const node_fs_1 = require("node:fs");
39
41
  const path = __importStar(require("node:path"));
@@ -48,17 +50,42 @@ const tasks_1 = require("../storage/tasks");
48
50
  const journal_1 = require("../storage/journal");
49
51
  const runFiles_1 = require("../storage/runFiles");
50
52
  const runIterate_1 = require("./commands/runIterate");
51
- const completionSecret_1 = require("./completionSecret");
53
+ const runExecuteTasks_1 = require("./commands/runExecuteTasks");
54
+ const health_1 = require("./commands/health");
55
+ const configure_1 = require("./commands/configure");
56
+ const session_1 = require("./commands/session");
57
+ const skill_1 = require("./commands/skill");
58
+ const hookLog_1 = require("./commands/hookLog");
59
+ const hookRun_1 = require("./commands/hookRun");
60
+ const completionProof_1 = require("./completionProof");
61
+ const exceptions_1 = require("../runtime/exceptions");
62
+ const defaults_1 = require("../config/defaults");
63
+ const session_2 = require("../session");
52
64
  const USAGE = `Usage:
53
- babysitter run:create --process-id <id> --entry <path#export> [--runs-dir <dir>] [--inputs <file>] [--run-id <id>] [--process-revision <rev>] [--request <id>] [--json] [--dry-run]
65
+ babysitter run:create --process-id <id> --entry <path#export> [--runs-dir <dir>] [--inputs <file>] [--run-id <id>] [--process-revision <rev>] [--request <id>] [--prompt <text>] [--session-id <id>] [--plugin-root <dir>] [--json] [--dry-run]
54
66
  babysitter run:status <runDir> [--runs-dir <dir>] [--json]
55
67
  babysitter run:events <runDir> [--runs-dir <dir>] [--json] [--limit <n>] [--reverse] [--filter-type <type>]
56
68
  babysitter run:rebuild-state <runDir> [--runs-dir <dir>] [--json] [--dry-run]
57
69
  babysitter run:repair-journal <runDir> [--runs-dir <dir>] [--json] [--dry-run]
58
70
  babysitter run:iterate <runDir> [--runs-dir <dir>] [--json] [--verbose] [--iteration <n>]
71
+ babysitter run:execute-tasks <runDir> [--runs-dir <dir>] [--json] [--verbose] [--dry-run] [--max-tasks <n>] [--kind <kind>] [--timeout <ms>]
59
72
  babysitter task:post <runDir> <effectId> --status <ok|error> [--runs-dir <dir>] [--json] [--dry-run] [--value <file>] [--error <file>] [--stdout-ref <ref>] [--stderr-ref <ref>] [--stdout-file <file>] [--stderr-file <file>] [--started-at <iso8601>] [--finished-at <iso8601>] [--metadata <file>] [--invocation-key <key>]
60
73
  babysitter task:list <runDir> [--runs-dir <dir>] [--pending] [--kind <kind>] [--json]
61
74
  babysitter task:show <runDir> <effectId> [--runs-dir <dir>] [--json]
75
+ babysitter session:init --session-id <id> --state-dir <dir> [--max-iterations <n>] [--run-id <id>] [--prompt <text>] [--json]
76
+ babysitter session:associate --session-id <id> --state-dir <dir> --run-id <id> [--json]
77
+ babysitter session:resume --session-id <id> --state-dir <dir> --run-id <id> [--max-iterations <n>] [--runs-dir <dir>] [--json]
78
+ babysitter session:state --session-id <id> --state-dir <dir> [--json]
79
+ babysitter session:update --session-id <id> --state-dir <dir> [--iteration <n>] [--last-iteration-at <iso8601>] [--iteration-times <csv>] [--delete] [--json]
80
+ babysitter session:check-iteration --session-id <id> --state-dir <dir> [--json]
81
+ babysitter session:last-message --transcript-path <file> [--json]
82
+ babysitter session:iteration-message --iteration <n> [--run-id <id>] [--runs-dir <dir>] [--plugin-root <dir>] [--json]
83
+ babysitter skill:discover --plugin-root <dir> [--run-id <id>] [--cache-ttl <seconds>] [--runs-dir <dir>] [--include-remote] [--summary-only] [--process-path <path>] [--json]
84
+ babysitter hook:log --hook-type <type> --log-file <path> [--json]
85
+ babysitter hook:run --hook-type <stop|session-start> [--harness <claude-code>] [--plugin-root <dir>] [--state-dir <dir>] [--runs-dir <dir>] [--json] [--verbose]
86
+ babysitter skill:fetch-remote --source-type <github|well-known> --url <url> [--json]
87
+ babysitter health [--json] [--verbose]
88
+ babysitter configure [show|validate|paths] [--json] [--defaults-only]
62
89
  babysitter version
63
90
 
64
91
  Global flags:
@@ -66,20 +93,27 @@ Global flags:
66
93
  --json Emit JSON output when supported by the command.
67
94
  --dry-run Describe planned mutations without changing on-disk state.
68
95
  --verbose Log resolved paths and options to stderr for debugging.
96
+ --show-config Show current configuration before executing command.
69
97
  --help, -h Show this help text.
70
98
  --version, -v Show CLI version.`;
71
- const LARGE_RESULT_PREVIEW_LIMIT = 1024 * 1024; // 1 MiB
99
+ /**
100
+ * Maximum size for inline result preview.
101
+ * @see DEFAULTS.largeResultPreviewLimit for the centralized default
102
+ */
103
+ const LARGE_RESULT_PREVIEW_LIMIT = defaults_1.DEFAULTS.largeResultPreviewLimit;
72
104
  function parseArgs(argv) {
73
105
  const [initialCommand, ...rest] = argv;
74
106
  const parsed = {
75
107
  command: initialCommand,
76
- runsDir: ".a5c/runs",
108
+ runsDir: process.env[defaults_1.CONFIG_ENV_VARS.RUNS_DIR] ?? defaults_1.DEFAULTS.runsDir,
77
109
  json: false,
78
110
  dryRun: false,
79
111
  verbose: false,
80
112
  helpRequested: false,
81
113
  pendingOnly: false,
82
114
  reverseOrder: false,
115
+ showConfig: false,
116
+ defaultsOnly: false,
83
117
  };
84
118
  if (parsed.command === "--help" || parsed.command === "-h") {
85
119
  parsed.command = undefined;
@@ -214,6 +248,105 @@ function parseArgs(argv) {
214
248
  parsed.requestId = expectFlagValue(rest, ++i, "--request");
215
249
  continue;
216
250
  }
251
+ if (arg === "--show-config") {
252
+ parsed.showConfig = true;
253
+ continue;
254
+ }
255
+ if (arg === "--defaults-only") {
256
+ parsed.defaultsOnly = true;
257
+ continue;
258
+ }
259
+ // Session command flags
260
+ if (arg === "--session-id") {
261
+ parsed.sessionId = expectFlagValue(rest, ++i, "--session-id");
262
+ continue;
263
+ }
264
+ if (arg === "--state-dir") {
265
+ parsed.stateDir = expectFlagValue(rest, ++i, "--state-dir");
266
+ continue;
267
+ }
268
+ if (arg === "--max-iterations") {
269
+ const raw = expectFlagValue(rest, ++i, "--max-iterations");
270
+ parsed.maxIterations = parsePositiveInteger(raw, "--max-iterations");
271
+ continue;
272
+ }
273
+ if (arg === "--prompt") {
274
+ parsed.prompt = expectFlagValue(rest, ++i, "--prompt");
275
+ continue;
276
+ }
277
+ if (arg === "--last-iteration-at") {
278
+ parsed.lastIterationAt = expectFlagValue(rest, ++i, "--last-iteration-at");
279
+ continue;
280
+ }
281
+ if (arg === "--iteration-times") {
282
+ parsed.iterationTimes = expectFlagValue(rest, ++i, "--iteration-times");
283
+ continue;
284
+ }
285
+ if (arg === "--max-tasks") {
286
+ const raw = expectFlagValue(rest, ++i, "--max-tasks");
287
+ parsed.maxTasks = parsePositiveInteger(raw, "--max-tasks");
288
+ continue;
289
+ }
290
+ if (arg === "--timeout") {
291
+ const raw = expectFlagValue(rest, ++i, "--timeout");
292
+ parsed.timeout = parsePositiveInteger(raw, "--timeout");
293
+ continue;
294
+ }
295
+ if (arg === "--delete") {
296
+ parsed.deleteSession = true;
297
+ continue;
298
+ }
299
+ if (arg === "--transcript-path") {
300
+ parsed.transcriptPath = expectFlagValue(rest, ++i, "--transcript-path");
301
+ continue;
302
+ }
303
+ // Hook command flags
304
+ if (arg === "--hook-type") {
305
+ parsed.hookType = expectFlagValue(rest, ++i, "--hook-type");
306
+ continue;
307
+ }
308
+ if (arg === "--harness") {
309
+ parsed.harness = expectFlagValue(rest, ++i, "--harness");
310
+ continue;
311
+ }
312
+ if (arg === "--log-file") {
313
+ parsed.logFile = expectFlagValue(rest, ++i, "--log-file");
314
+ continue;
315
+ }
316
+ // Skill command flags
317
+ if (arg === "--plugin-root") {
318
+ parsed.pluginRoot = expectFlagValue(rest, ++i, "--plugin-root");
319
+ continue;
320
+ }
321
+ if (arg === "--cache-ttl") {
322
+ const raw = expectFlagValue(rest, ++i, "--cache-ttl");
323
+ parsed.cacheTtl = parsePositiveInteger(raw, "--cache-ttl");
324
+ continue;
325
+ }
326
+ if (arg === "--source-type") {
327
+ const raw = expectFlagValue(rest, ++i, "--source-type");
328
+ if (raw !== "github" && raw !== "well-known") {
329
+ throw new Error(`--source-type must be "github" or "well-known" (received: ${raw})`);
330
+ }
331
+ parsed.sourceType = raw;
332
+ continue;
333
+ }
334
+ if (arg === "--url") {
335
+ parsed.url = expectFlagValue(rest, ++i, "--url");
336
+ continue;
337
+ }
338
+ if (arg === "--include-remote") {
339
+ parsed.includeRemote = true;
340
+ continue;
341
+ }
342
+ if (arg === "--summary-only") {
343
+ parsed.summaryOnly = true;
344
+ continue;
345
+ }
346
+ if (arg === "--process-path") {
347
+ parsed.processPath = expectFlagValue(rest, ++i, "--process-path");
348
+ continue;
349
+ }
217
350
  positionals.push(arg);
218
351
  }
219
352
  if (parsed.command === "task:post") {
@@ -234,18 +367,73 @@ function parseArgs(argv) {
234
367
  else if (parsed.command === "run:events") {
235
368
  [parsed.runDirArg] = positionals;
236
369
  }
370
+ else if (parsed.command === "run:execute-tasks") {
371
+ [parsed.runDirArg] = positionals;
372
+ }
237
373
  else if (parsed.command === "run:rebuild-state") {
238
374
  [parsed.runDirArg] = positionals;
239
375
  }
240
376
  else if (parsed.command === "run:repair-journal") {
241
377
  [parsed.runDirArg] = positionals;
242
378
  }
379
+ else if (parsed.command === "configure") {
380
+ [parsed.configureSubcommand] = positionals;
381
+ }
243
382
  return parsed;
244
383
  }
384
+ /**
385
+ * Resolve a run directory from a base directory and a user-provided argument.
386
+ *
387
+ * Handles several common edge cases:
388
+ * - Absolute paths are used directly (no base dir prepended).
389
+ * - If the arg already contains the base dir prefix (e.g. ".a5c/runs/01RUN"
390
+ * when base is ".a5c/runs"), it is resolved from CWD to avoid doubling.
391
+ * - Doubled ".a5c/runs" segments in the final path are collapsed.
392
+ * - If the resolved path doesn't exist, falls back to resolving from CWD.
393
+ */
245
394
  function resolveRunDir(baseDir, runDirArg) {
246
395
  if (!runDirArg)
247
396
  throw new Error("Run directory argument is required.");
248
- return path.resolve(baseDir, runDirArg);
397
+ // Absolute path → use directly
398
+ if (path.isAbsolute(runDirArg)) {
399
+ return collapseDoubledA5cRuns(path.normalize(runDirArg));
400
+ }
401
+ // Detect if arg already starts with the base dir prefix to avoid
402
+ // ".a5c/runs" + ".a5c/runs/01RUN" → ".a5c/runs/.a5c/runs/01RUN"
403
+ const normalBase = normalizePosix(baseDir);
404
+ const normalArg = normalizePosix(runDirArg);
405
+ if (normalBase && (normalArg === normalBase || normalArg.startsWith(normalBase + "/"))) {
406
+ return collapseDoubledA5cRuns(path.resolve(runDirArg));
407
+ }
408
+ // Standard resolution: baseDir + arg
409
+ const standard = collapseDoubledA5cRuns(path.resolve(baseDir, runDirArg));
410
+ // Fallback: if the standard path doesn't exist, try from CWD
411
+ try {
412
+ if (!(0, node_fs_1.existsSync)(standard)) {
413
+ const fromCwd = path.resolve(runDirArg);
414
+ if ((0, node_fs_1.existsSync)(fromCwd))
415
+ return collapseDoubledA5cRuns(fromCwd);
416
+ }
417
+ }
418
+ catch {
419
+ // Ignore filesystem errors during resolution
420
+ }
421
+ return standard;
422
+ }
423
+ /** Normalize a path to forward slashes with no trailing slash (for prefix comparison). */
424
+ function normalizePosix(p) {
425
+ return path.normalize(p).replace(/\\/g, "/").replace(/\/+$/, "");
426
+ }
427
+ /** Collapse doubled ".a5c/runs" segments: ".a5c/runs/.a5c/runs/X" → ".a5c/runs/X" */
428
+ function collapseDoubledA5cRuns(p) {
429
+ // Match both forward and back-slash variants
430
+ const pattern = /([/\\]?\.a5c[/\\]runs)[/\\]\.a5c[/\\]runs([/\\]|$)/;
431
+ let result = p;
432
+ // Collapse repeatedly in case of triple+ duplication
433
+ while (pattern.test(result)) {
434
+ result = result.replace(pattern, "$1$2");
435
+ }
436
+ return result;
249
437
  }
250
438
  function expectFlagValue(args, index, flag) {
251
439
  const value = args[index];
@@ -484,6 +672,96 @@ async function readInputsFile(filePath) {
484
672
  throw new Error(`Failed to parse inputs file ${absolute} as JSON: ${error instanceof Error ? error.message : String(error)}`);
485
673
  }
486
674
  }
675
+ /**
676
+ * Dispatch session binding to the appropriate harness implementation.
677
+ */
678
+ async function bindRunToHarnessSession(opts) {
679
+ switch (opts.harness) {
680
+ case "claude-code":
681
+ return bindRunToClaudeCodeSession(opts);
682
+ default:
683
+ // Unknown harness — return a descriptive error but don't fail the run
684
+ return {
685
+ harness: opts.harness,
686
+ sessionId: opts.sessionId,
687
+ error: `Unknown harness "${opts.harness}" — session not bound`,
688
+ };
689
+ }
690
+ }
691
+ /**
692
+ * Claude Code harness: create a session state file (markdown + YAML frontmatter)
693
+ * with the run ID already associated. Combines session:init + session:associate
694
+ * into a single atomic operation.
695
+ *
696
+ * State dir resolution: --state-dir flag > $PLUGIN_ROOT/skills/babysit/state
697
+ */
698
+ async function bindRunToClaudeCodeSession(opts) {
699
+ const { sessionId, runId, pluginRoot, maxIterations = 256, prompt, verbose } = opts;
700
+ // Resolve state directory
701
+ let stateDir = opts.stateDir;
702
+ if (!stateDir && pluginRoot) {
703
+ stateDir = path.join(pluginRoot, "skills", "babysit", "state");
704
+ }
705
+ if (!stateDir) {
706
+ return {
707
+ harness: "claude-code",
708
+ sessionId,
709
+ error: "Cannot bind session: --state-dir or --plugin-root required for claude-code harness",
710
+ };
711
+ }
712
+ const filePath = (0, session_2.getSessionFilePath)(stateDir, sessionId);
713
+ // Check for existing session (prevent re-entrant runs)
714
+ if (await (0, session_2.sessionFileExists)(filePath)) {
715
+ try {
716
+ const existing = await (0, session_2.readSessionFile)(filePath);
717
+ if (existing.state.runId && existing.state.runId !== runId) {
718
+ return {
719
+ harness: "claude-code",
720
+ sessionId,
721
+ stateFile: filePath,
722
+ error: `Session already associated with run: ${existing.state.runId}`,
723
+ };
724
+ }
725
+ // Session exists but has no run or same run — update it
726
+ await (0, session_2.updateSessionState)(filePath, { runId, active: true }, {
727
+ state: existing.state,
728
+ prompt: existing.prompt,
729
+ });
730
+ if (verbose) {
731
+ process.stderr.write(`[run:create] Updated existing session ${sessionId} with run ${runId}\n`);
732
+ }
733
+ return { harness: "claude-code", sessionId, stateFile: filePath };
734
+ }
735
+ catch {
736
+ // Corrupted state file — overwrite it
737
+ }
738
+ }
739
+ // Create new session state file with run already associated
740
+ const now = (0, session_2.getCurrentTimestamp)();
741
+ const state = {
742
+ active: true,
743
+ iteration: 1,
744
+ maxIterations,
745
+ runId,
746
+ startedAt: now,
747
+ lastIterationAt: now,
748
+ iterationTimes: [],
749
+ };
750
+ try {
751
+ await (0, session_2.writeSessionFile)(filePath, state, prompt);
752
+ }
753
+ catch (e) {
754
+ return {
755
+ harness: "claude-code",
756
+ sessionId,
757
+ error: `Failed to write session state: ${e instanceof Error ? e.message : String(e)}`,
758
+ };
759
+ }
760
+ if (verbose) {
761
+ process.stderr.write(`[run:create] Session ${sessionId} initialized and bound to run ${runId}\n`);
762
+ }
763
+ return { harness: "claude-code", sessionId, stateFile: filePath };
764
+ }
487
765
  async function handleRunCreate(parsed) {
488
766
  if (!parsed.processId) {
489
767
  console.error("--process-id is required for run:create");
@@ -513,6 +791,7 @@ async function handleRunCreate(parsed) {
513
791
  dryRun: parsed.dryRun,
514
792
  json: parsed.json,
515
793
  request: parsed.requestId,
794
+ prompt: parsed.prompt,
516
795
  processRevision: parsed.processRevision,
517
796
  runId: parsed.runIdOverride,
518
797
  inputsPath: parsed.inputsPath ? path.resolve(parsed.inputsPath) : undefined,
@@ -570,6 +849,7 @@ async function handleRunCreate(parsed) {
570
849
  runsDir,
571
850
  runId: parsed.runIdOverride,
572
851
  request: parsed.requestId,
852
+ prompt: parsed.prompt,
573
853
  processRevision: parsed.processRevision,
574
854
  process: {
575
855
  processId: parsed.processId,
@@ -579,11 +859,59 @@ async function handleRunCreate(parsed) {
579
859
  inputs,
580
860
  });
581
861
  const entrySpec = formatEntrypointSpecifier(result.metadata.entrypoint);
862
+ // --- Harness-specific session binding ---
863
+ // When --session-id and --harness are provided, bind the new run to the
864
+ // caller's session. Each harness has its own session management strategy.
865
+ let sessionBound;
866
+ const sessionId = parsed.sessionId || process.env.CLAUDE_SESSION_ID;
867
+ const harness = parsed.harness;
868
+ if (sessionId && harness) {
869
+ sessionBound = await bindRunToHarnessSession({
870
+ harness,
871
+ sessionId,
872
+ runId: result.runId,
873
+ runDir: result.runDir,
874
+ pluginRoot: parsed.pluginRoot || process.env.CLAUDE_PLUGIN_ROOT,
875
+ stateDir: parsed.stateDir,
876
+ maxIterations: parsed.maxIterations,
877
+ prompt: parsed.prompt ?? "",
878
+ verbose: parsed.verbose,
879
+ json: parsed.json,
880
+ });
881
+ }
882
+ // Discover available skills and agents for the new run
883
+ let discoveredSkills;
884
+ let discoveredAgents;
885
+ const discoverPluginRoot = parsed.pluginRoot || process.env.CLAUDE_PLUGIN_ROOT;
886
+ if (discoverPluginRoot) {
887
+ try {
888
+ const discoverResult = await (0, skill_1.discoverSkillsInternal)({
889
+ pluginRoot: discoverPluginRoot,
890
+ runId: result.runId,
891
+ runsDir: parsed.runsDir,
892
+ });
893
+ discoveredSkills = discoverResult.skills.map(s => s.name);
894
+ discoveredAgents = discoverResult.agents.map(a => a.name);
895
+ }
896
+ catch {
897
+ // Non-fatal
898
+ }
899
+ }
582
900
  if (parsed.json) {
583
- console.log(JSON.stringify({ runId: result.runId, runDir: result.runDir, entry: entrySpec }));
901
+ console.log(JSON.stringify({
902
+ runId: result.runId,
903
+ runDir: result.runDir,
904
+ entry: entrySpec,
905
+ session: sessionBound ?? undefined,
906
+ discoveredSkills,
907
+ discoveredAgents,
908
+ }));
584
909
  }
585
910
  else {
586
911
  console.log(`[run:create] runId=${result.runId} runDir=${result.runDir} entry=${entrySpec}`);
912
+ if (sessionBound) {
913
+ console.log(`[run:create] session=${sessionId} bound via ${harness}`);
914
+ }
587
915
  }
588
916
  return 0;
589
917
  }
@@ -618,20 +946,29 @@ async function handleRunStatus(parsed) {
618
946
  const lastLifecycleEvent = findLastLifecycleEvent(journal);
619
947
  const state = deriveRunState(lastLifecycleEvent?.type, pendingTotal);
620
948
  const lastSummary = formatLastEventSummary(lastEvent);
949
+ const autoRunnableCount = pendingRecords.filter(r => r.kind === "node").length;
950
+ const pendingEffectsSummary = {
951
+ totalPending: pendingTotal,
952
+ countsByKind: pendingByKind,
953
+ autoRunnableCount,
954
+ };
955
+ const needsMoreIterations = state === "waiting" && autoRunnableCount > 0;
621
956
  if (parsed.json) {
622
- const completionSecret = state === "completed" ? (0, completionSecret_1.resolveCompletionSecret)(metadata) : null;
957
+ const completionProof = state === "completed" ? (0, completionProof_1.resolveCompletionProof)(metadata) : null;
623
958
  console.log(JSON.stringify({
624
959
  state,
625
960
  lastEvent: lastEvent ? serializeJournalEvent(lastEvent, runDir) : null,
626
961
  pendingByKind,
962
+ pendingEffectsSummary,
963
+ needsMoreIterations,
627
964
  metadata: formattedMetadata.jsonMetadata ?? null,
628
- completionSecret,
965
+ completionProof,
629
966
  }));
630
967
  return 0;
631
968
  }
632
969
  const suffix = formattedMetadata.textParts.length ? ` ${formattedMetadata.textParts.join(" ")}` : "";
633
- const completionSecret = state === "completed" ? (0, completionSecret_1.resolveCompletionSecret)(metadata) : undefined;
634
- const secretSuffix = completionSecret ? ` completionSecret=${completionSecret}` : "";
970
+ const completionProof = state === "completed" ? (0, completionProof_1.resolveCompletionProof)(metadata) : undefined;
971
+ const secretSuffix = completionProof ? ` completionProof=${completionProof}` : "";
635
972
  console.log(`[run:status] state=${state} last=${lastSummary}${suffix}${secretSuffix}`);
636
973
  return 0;
637
974
  }
@@ -653,6 +990,7 @@ async function handleRunIterate(parsed) {
653
990
  iteration: parsed.iteration,
654
991
  verbose: parsed.verbose,
655
992
  json: parsed.json,
993
+ pluginRoot: parsed.pluginRoot || process.env.CLAUDE_PLUGIN_ROOT,
656
994
  });
657
995
  if (parsed.json) {
658
996
  console.log(JSON.stringify(result, null, 2));
@@ -660,9 +998,10 @@ async function handleRunIterate(parsed) {
660
998
  else {
661
999
  const countInfo = result.count ? ` count=${result.count}` : "";
662
1000
  const actionInfo = result.action ? ` action=${result.action}` : "";
663
- console.log(`[run:iterate] iteration=${result.iteration} status=${result.status}${actionInfo}${countInfo} reason=${result.reason}`);
664
- if (result.status === "completed" && result.completionSecret) {
665
- console.log(`[run:iterate] completionSecret=${result.completionSecret}`);
1001
+ const progressInfo = result.iterationCount > 0 ? ` (${result.iterationCount} completed)` : "";
1002
+ console.log(`[run:iterate] iteration=${result.iteration}${progressInfo} status=${result.status}${actionInfo}${countInfo} reason=${result.reason}`);
1003
+ if (result.status === "completed" && result.completionProof) {
1004
+ console.log(`[run:iterate] completionProof=${result.completionProof}`);
666
1005
  }
667
1006
  if (result.status === "waiting" && result.until) {
668
1007
  console.log(`[run:iterate] Waiting until: ${new Date(result.until).toISOString()}`);
@@ -675,6 +1014,47 @@ async function handleRunIterate(parsed) {
675
1014
  return 1;
676
1015
  }
677
1016
  }
1017
+ async function handleRunExecuteTasks(parsed) {
1018
+ if (!parsed.runDirArg) {
1019
+ console.error(USAGE);
1020
+ return 1;
1021
+ }
1022
+ const runDir = resolveRunDir(parsed.runsDir, parsed.runDirArg);
1023
+ logVerbose("run:execute-tasks", parsed, {
1024
+ runDir,
1025
+ maxTasks: parsed.maxTasks,
1026
+ kind: parsed.kindFilter,
1027
+ timeout: parsed.timeout,
1028
+ dryRun: parsed.dryRun,
1029
+ json: parsed.json,
1030
+ verbose: parsed.verbose,
1031
+ });
1032
+ try {
1033
+ const result = await (0, runExecuteTasks_1.runExecuteTasks)({
1034
+ runDir,
1035
+ maxTasks: parsed.maxTasks,
1036
+ kind: parsed.kindFilter,
1037
+ timeout: parsed.timeout,
1038
+ verbose: parsed.verbose,
1039
+ json: parsed.json,
1040
+ dryRun: parsed.dryRun,
1041
+ });
1042
+ if (parsed.json) {
1043
+ console.log(JSON.stringify(result, null, 2));
1044
+ }
1045
+ else {
1046
+ const taskSummary = result.tasks.length > 0
1047
+ ? ` tasks=[${result.tasks.map((t) => `${t.effectId}:${t.status}`).join(",")}]`
1048
+ : "";
1049
+ console.log(`[run:execute-tasks] action=${result.action} count=${result.count} reason=${result.reason}${taskSummary}`);
1050
+ }
1051
+ return 0;
1052
+ }
1053
+ catch (error) {
1054
+ console.error(`[run:execute-tasks] Error: ${error instanceof Error ? error.message : String(error)}`);
1055
+ return 1;
1056
+ }
1057
+ }
678
1058
  async function handleRunEvents(parsed) {
679
1059
  if (!parsed.runDirArg) {
680
1060
  console.error(USAGE);
@@ -1363,11 +1743,126 @@ async function readCliVersion() {
1363
1743
  const parsed = JSON.parse(raw);
1364
1744
  return parsed.version ?? "unknown";
1365
1745
  }
1746
+ /**
1747
+ * Checks if stdout/stderr supports colors
1748
+ */
1749
+ function supportsColors() {
1750
+ // Check NO_COLOR environment variable (https://no-color.org/)
1751
+ if (process.env.NO_COLOR !== undefined) {
1752
+ return false;
1753
+ }
1754
+ // Check FORCE_COLOR environment variable
1755
+ if (process.env.FORCE_COLOR !== undefined) {
1756
+ return true;
1757
+ }
1758
+ // Check if running in a TTY
1759
+ if (process.stderr && typeof process.stderr.isTTY === "boolean") {
1760
+ return process.stderr.isTTY;
1761
+ }
1762
+ return false;
1763
+ }
1764
+ /**
1765
+ * Valid CLI commands
1766
+ */
1767
+ const VALID_COMMANDS = [
1768
+ "run:create",
1769
+ "run:status",
1770
+ "run:iterate",
1771
+ "run:events",
1772
+ "run:rebuild-state",
1773
+ "run:repair-journal",
1774
+ "run:execute-tasks",
1775
+ "task:post",
1776
+ "task:list",
1777
+ "task:show",
1778
+ "session:init",
1779
+ "session:associate",
1780
+ "session:resume",
1781
+ "session:state",
1782
+ "session:update",
1783
+ "session:check-iteration",
1784
+ "session:last-message",
1785
+ "session:iteration-message",
1786
+ "hook:log",
1787
+ "hook:run",
1788
+ "skill:discover",
1789
+ "skill:fetch-remote",
1790
+ "health",
1791
+ "configure",
1792
+ "version",
1793
+ ];
1794
+ /**
1795
+ * Handles unknown commands with suggestions
1796
+ */
1797
+ function handleUnknownCommand(command, json) {
1798
+ const suggestion = (0, exceptions_1.suggestCommand)(command);
1799
+ const suggestions = suggestion ? [`Did you mean: ${suggestion}?`] : [];
1800
+ const nextSteps = ["Run with --help to see all available commands"];
1801
+ const error = new exceptions_1.BabysitterRuntimeError("UnknownCommandError", `Unknown command: ${command}`, {
1802
+ category: exceptions_1.ErrorCategory.Validation,
1803
+ suggestions,
1804
+ nextSteps,
1805
+ details: { command, validCommands: VALID_COMMANDS },
1806
+ });
1807
+ if (json) {
1808
+ console.error(JSON.stringify((0, exceptions_1.toStructuredError)(error)));
1809
+ }
1810
+ else {
1811
+ const colors = supportsColors();
1812
+ console.error((0, exceptions_1.formatErrorWithContext)(error, { colors }));
1813
+ }
1814
+ return 1;
1815
+ }
1816
+ /**
1817
+ * Shows configuration before executing a command when --show-config is provided
1818
+ */
1819
+ async function showConfigBeforeCommand(parsed) {
1820
+ const { configureShow } = await Promise.resolve().then(() => __importStar(require("./commands/configure")));
1821
+ const result = configureShow({ json: parsed.json, defaultsOnly: false });
1822
+ if (parsed.json) {
1823
+ console.error(JSON.stringify({ showConfig: result }, null, 2));
1824
+ }
1825
+ else {
1826
+ console.error("[show-config] Current effective configuration:");
1827
+ for (const item of result.values) {
1828
+ const sourceTag = item.source === "env" ? " (env)" : "";
1829
+ console.error(` ${item.key}=${formatVerboseValue(item.value)}${sourceTag}`);
1830
+ }
1831
+ console.error("");
1832
+ }
1833
+ }
1834
+ /**
1835
+ * Formats and outputs an error in the appropriate format
1836
+ */
1837
+ function outputError(error, options) {
1838
+ const { json, verbose = false } = options;
1839
+ if (json) {
1840
+ console.error(JSON.stringify((0, exceptions_1.toStructuredError)(error, verbose)));
1841
+ }
1842
+ else {
1843
+ const colors = supportsColors();
1844
+ if ((0, exceptions_1.isBabysitterError)(error)) {
1845
+ console.error((0, exceptions_1.formatErrorWithContext)(error, { colors, includeStack: verbose }));
1846
+ }
1847
+ else {
1848
+ // For non-babysitter errors, wrap them with basic formatting
1849
+ const wrappedError = new exceptions_1.BabysitterRuntimeError(error.name || "Error", error.message, {
1850
+ category: exceptions_1.ErrorCategory.Internal,
1851
+ nextSteps: ["If this error persists, please report it as a bug"],
1852
+ });
1853
+ console.error((0, exceptions_1.formatErrorWithContext)(wrappedError, { colors, includeStack: verbose }));
1854
+ }
1855
+ }
1856
+ }
1366
1857
  function createBabysitterCli() {
1367
1858
  return {
1368
1859
  async run(argv = process.argv.slice(2)) {
1860
+ let parsedJson = false;
1861
+ let parsedVerbose = false;
1369
1862
  try {
1370
1863
  const parsed = parseArgs(argv);
1864
+ parsedJson = parsed.json;
1865
+ parsedVerbose = parsed.verbose;
1371
1866
  if (parsed.command === "version") {
1372
1867
  console.log(await readCliVersion());
1373
1868
  return 0;
@@ -1376,6 +1871,14 @@ function createBabysitterCli() {
1376
1871
  console.log(USAGE);
1377
1872
  return 0;
1378
1873
  }
1874
+ // Show config if --show-config flag is provided
1875
+ if (parsed.showConfig) {
1876
+ await showConfigBeforeCommand(parsed);
1877
+ }
1878
+ // Check for valid commands and provide suggestions for unknown ones
1879
+ if (!VALID_COMMANDS.includes(parsed.command)) {
1880
+ return handleUnknownCommand(parsed.command, parsed.json);
1881
+ }
1379
1882
  if (parsed.command === "run:create") {
1380
1883
  return await handleRunCreate(parsed);
1381
1884
  }
@@ -1391,6 +1894,9 @@ function createBabysitterCli() {
1391
1894
  if (parsed.command === "run:iterate") {
1392
1895
  return await handleRunIterate(parsed);
1393
1896
  }
1897
+ if (parsed.command === "run:execute-tasks") {
1898
+ return await handleRunExecuteTasks(parsed);
1899
+ }
1394
1900
  if (parsed.command === "run:events") {
1395
1901
  return await handleRunEvents(parsed);
1396
1902
  }
@@ -1403,11 +1909,138 @@ function createBabysitterCli() {
1403
1909
  if (parsed.command === "task:show") {
1404
1910
  return await handleTaskShow(parsed);
1405
1911
  }
1406
- console.error(USAGE);
1407
- return 1;
1912
+ // Session commands
1913
+ if (parsed.command === "session:init") {
1914
+ return await (0, session_1.handleSessionInit)({
1915
+ sessionId: parsed.sessionId,
1916
+ stateDir: parsed.stateDir,
1917
+ maxIterations: parsed.maxIterations,
1918
+ runId: parsed.runIdOverride,
1919
+ prompt: parsed.prompt,
1920
+ json: parsed.json,
1921
+ });
1922
+ }
1923
+ if (parsed.command === "session:associate") {
1924
+ return await (0, session_1.handleSessionAssociate)({
1925
+ sessionId: parsed.sessionId,
1926
+ stateDir: parsed.stateDir,
1927
+ runId: parsed.runIdOverride,
1928
+ json: parsed.json,
1929
+ });
1930
+ }
1931
+ if (parsed.command === "session:resume") {
1932
+ return await (0, session_1.handleSessionResume)({
1933
+ sessionId: parsed.sessionId,
1934
+ stateDir: parsed.stateDir,
1935
+ runId: parsed.runIdOverride,
1936
+ maxIterations: parsed.maxIterations,
1937
+ runsDir: parsed.runsDir,
1938
+ json: parsed.json,
1939
+ });
1940
+ }
1941
+ if (parsed.command === "session:state") {
1942
+ return await (0, session_1.handleSessionState)({
1943
+ sessionId: parsed.sessionId,
1944
+ stateDir: parsed.stateDir,
1945
+ json: parsed.json,
1946
+ });
1947
+ }
1948
+ if (parsed.command === "session:update") {
1949
+ return await (0, session_1.handleSessionUpdate)({
1950
+ sessionId: parsed.sessionId,
1951
+ stateDir: parsed.stateDir,
1952
+ iteration: parsed.iteration,
1953
+ lastIterationAt: parsed.lastIterationAt,
1954
+ iterationTimes: parsed.iterationTimes,
1955
+ delete: parsed.deleteSession,
1956
+ json: parsed.json,
1957
+ });
1958
+ }
1959
+ if (parsed.command === "session:check-iteration") {
1960
+ return await (0, session_1.handleSessionCheckIteration)({
1961
+ sessionId: parsed.sessionId,
1962
+ stateDir: parsed.stateDir,
1963
+ json: parsed.json,
1964
+ });
1965
+ }
1966
+ if (parsed.command === "session:last-message") {
1967
+ if (!parsed.transcriptPath) {
1968
+ console.error("--transcript-path is required for session:last-message");
1969
+ console.error(USAGE);
1970
+ return 1;
1971
+ }
1972
+ return (0, session_1.handleSessionLastMessage)({
1973
+ transcriptPath: parsed.transcriptPath,
1974
+ json: parsed.json,
1975
+ });
1976
+ }
1977
+ if (parsed.command === "session:iteration-message") {
1978
+ return await (0, session_1.handleSessionIterationMessage)({
1979
+ iteration: parsed.iteration,
1980
+ runId: parsed.runIdOverride,
1981
+ runsDir: parsed.runsDir,
1982
+ pluginRoot: parsed.pluginRoot,
1983
+ json: parsed.json,
1984
+ });
1985
+ }
1986
+ // Hook commands
1987
+ if (parsed.command === "hook:log") {
1988
+ return await (0, hookLog_1.handleHookLog)({
1989
+ hookType: parsed.hookType ?? "",
1990
+ logFile: parsed.logFile ?? "",
1991
+ json: parsed.json,
1992
+ });
1993
+ }
1994
+ if (parsed.command === "hook:run") {
1995
+ return await (0, hookRun_1.handleHookRun)({
1996
+ hookType: parsed.hookType ?? "",
1997
+ harness: parsed.harness ?? "claude-code",
1998
+ pluginRoot: parsed.pluginRoot,
1999
+ stateDir: parsed.stateDir,
2000
+ runsDir: parsed.runsDir,
2001
+ json: parsed.json,
2002
+ verbose: parsed.verbose,
2003
+ });
2004
+ }
2005
+ // Skill commands
2006
+ if (parsed.command === "skill:discover") {
2007
+ return await (0, skill_1.handleSkillDiscover)({
2008
+ pluginRoot: parsed.pluginRoot,
2009
+ runId: parsed.runIdOverride,
2010
+ cacheTtl: parsed.cacheTtl,
2011
+ runsDir: parsed.runsDir,
2012
+ json: parsed.json,
2013
+ includeRemote: parsed.includeRemote,
2014
+ summaryOnly: parsed.summaryOnly,
2015
+ processPath: parsed.processPath,
2016
+ });
2017
+ }
2018
+ if (parsed.command === "skill:fetch-remote") {
2019
+ return await (0, skill_1.handleSkillFetchRemote)({
2020
+ sourceType: parsed.sourceType,
2021
+ url: parsed.url,
2022
+ json: parsed.json,
2023
+ });
2024
+ }
2025
+ if (parsed.command === "health") {
2026
+ return await (0, health_1.handleHealthCommand)({
2027
+ json: parsed.json,
2028
+ verbose: parsed.verbose,
2029
+ });
2030
+ }
2031
+ if (parsed.command === "configure") {
2032
+ const args = parsed.configureSubcommand ? [parsed.configureSubcommand] : [];
2033
+ return await (0, configure_1.handleConfigureCommand)(args, {
2034
+ json: parsed.json,
2035
+ defaultsOnly: parsed.defaultsOnly,
2036
+ });
2037
+ }
2038
+ // This should not be reached due to the VALID_COMMANDS check above
2039
+ return handleUnknownCommand(parsed.command, parsed.json);
1408
2040
  }
1409
2041
  catch (error) {
1410
- console.error(error instanceof Error ? error.message : String(error));
2042
+ const err = error instanceof Error ? error : new Error(String(error));
2043
+ outputError(err, { json: parsedJson, verbose: parsedVerbose });
1411
2044
  return 1;
1412
2045
  }
1413
2046
  },