@ricsam/r5dctl 0.0.60 → 0.0.61

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/mjs/cli.mjs CHANGED
@@ -16,15 +16,12 @@ const CHAT_MODES = /* @__PURE__ */ new Set([
16
16
  "plan",
17
17
  "build",
18
18
  "agent",
19
- "explore",
20
- "test",
21
- "research",
22
19
  "security_review",
23
20
  "large_diff_remediation",
24
21
  "merge_conflict_resolution"
25
22
  ]);
23
+ const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "build", "agent"]);
26
24
  const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
27
- const AGENT_TYPES = /* @__PURE__ */ new Set(["research", "debug", "test"]);
28
25
  const R5DCTL_PACKAGE_NAME = "@ricsam/r5dctl";
29
26
  const CLI_GLOBAL_OPTION_HELP = [
30
27
  "--base-url <url>",
@@ -78,17 +75,24 @@ const K8S_HELP_TEXT = [
78
75
  "Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
79
76
  ""
80
77
  ].join("\n");
81
- const AGENT_HELP_TEXT = [
78
+ const SESSION_RUN_HELP_TEXT = [
82
79
  "Usage:",
83
- ' r5dctl -p <project> agent start --worker <label> --source worktree:<branch> <research|debug|test> "<prompt>"',
84
- " r5dctl agent status <session-id>",
85
- ' r5dctl agent prompt --worker <label> <session-id> "<prompt>"',
86
- " r5dctl agent merge --worker <label> <session-id>",
87
- " r5dctl agent stop <session-id>",
88
- ' r5dctl agent finish-merge --worker <label> <resolver-session-id> --summary "<summary>"',
80
+ ' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
81
+ ' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
82
+ " r5dctl session status <session-id>",
83
+ ' r5dctl session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
84
+ " r5dctl session stop <session-id>",
89
85
  "",
90
- "Create, inspect, resume, stop, and merge independent branch agents.",
91
- "The source is the named branch worktree, including its committed and uncommitted files.",
86
+ "Create, inspect, resume, and stop ordinary agent sessions.",
87
+ "Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
88
+ ""
89
+ ].join("\n");
90
+ const MERGE_HELP_TEXT = [
91
+ "Usage:",
92
+ " r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
93
+ ' r5dctl -p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
94
+ "",
95
+ "Merge a managed worktree into its recorded parent worktree.",
92
96
  ""
93
97
  ].join("\n");
94
98
  const SHARED_HELP_ENTRIES = [
@@ -158,7 +162,7 @@ const SHARED_HELP_ENTRIES = [
158
162
  { section: "sessions", usage: "-s <session-id> delete session", description: "Delete a session." },
159
163
  {
160
164
  section: "sessions",
161
- usage: "-s <session-id> conversation [--raw] [--tools] [--system]",
165
+ usage: "-s <session-id> conversation [--raw] [--tools] [--system] [-f|--follow|--watch]",
162
166
  description: "Read the agent request transcript in human-readable form."
163
167
  },
164
168
  {
@@ -178,17 +182,32 @@ const SHARED_HELP_ENTRIES = [
178
182
  },
179
183
  {
180
184
  section: "sessions",
181
- usage: '-s <session-id> prompt --mode <mode> --model <tier> "<message>"',
182
- description: "Send a prompt to a session."
185
+ usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch>) --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
186
+ description: "Start an ordinary agent session on an explicit existing or new worktree."
183
187
  },
184
188
  {
185
189
  section: "sessions",
186
- usage: '-s <session-id> answer-questions -a1 "<answer>" -a2 "<answer>" ...',
190
+ usage: "session status <session-id>",
191
+ description: "Show asynchronous session execution status."
192
+ },
193
+ {
194
+ section: "sessions",
195
+ usage: 'session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
196
+ description: "Queue a prompt or start a new session generation."
197
+ },
198
+ {
199
+ section: "sessions",
200
+ usage: "session stop <session-id>",
201
+ description: "Stop an active session generation."
202
+ },
203
+ {
204
+ section: "sessions",
205
+ usage: '-s <session-id> answer-questions --worker <label> --mode <mode> --model <tier> -a1 "<answer>" ...',
187
206
  description: "Answer pending questions in order."
188
207
  },
189
208
  {
190
209
  section: "sessions",
191
- usage: "-s <session-id> answer-env-request [-e KEY=value] [--context <text>]",
210
+ usage: "-s <session-id> answer-env-request --worker <label> --mode <mode> --model <tier> [-e KEY=value] [--context <text>]",
192
211
  description: "Answer a pending environment variable request with values, context, or both."
193
212
  },
194
213
  {
@@ -213,34 +232,14 @@ const SHARED_HELP_ENTRIES = [
213
232
  },
214
233
  { section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
215
234
  {
216
- section: "agents",
217
- usage: '-p <project> agent start --worker <label> --source worktree:<branch> <research|debug|test> "<prompt>"',
218
- description: "Create a branch from an explicit worktree source and start an independent agent."
219
- },
220
- {
221
- section: "agents",
222
- usage: "agent status <session-id>",
223
- description: "Show branch-agent execution, publication, and merge status."
224
- },
225
- {
226
- section: "agents",
227
- usage: 'agent prompt --worker <label> <session-id> "<prompt>"',
228
- description: "Queue a prompt or resume an idle branch agent on the selected worker."
229
- },
230
- {
231
- section: "agents",
232
- usage: "agent merge --worker <label> <session-id>",
233
- description: "Merge a terminal branch agent into its recorded parent worktree."
234
- },
235
- {
236
- section: "agents",
237
- usage: "agent stop <session-id>",
238
- description: "Stop a branch agent or merge resolver."
235
+ section: "merge",
236
+ usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
237
+ description: "Merge a worktree into its recorded parent, with an explicit conflict strategy."
239
238
  },
240
239
  {
241
- section: "agents",
242
- usage: 'agent finish-merge --worker <label> <resolver-session-id> --summary "<summary>"',
243
- description: "Publish and finalize a manually resolved merge."
240
+ section: "merge",
241
+ usage: '-p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
242
+ description: "Publish and finish a manually resolved conflict worktree."
244
243
  }
245
244
  ];
246
245
  const HELP_SECTION_ORDER = [
@@ -253,7 +252,7 @@ const HELP_SECTION_ORDER = [
253
252
  "kubernetes",
254
253
  "processes",
255
254
  "shell",
256
- "agents"
255
+ "merge"
257
256
  ];
258
257
  const HELP_SECTION_TITLES = {
259
258
  auth: "Auth",
@@ -265,7 +264,7 @@ const HELP_SECTION_TITLES = {
265
264
  kubernetes: "Kubernetes",
266
265
  processes: "Processes",
267
266
  shell: "Shell",
268
- agents: "Branch agents"
267
+ merge: "Worktree merging"
269
268
  };
270
269
  function getCliHelpText() {
271
270
  const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
@@ -412,9 +411,10 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
412
411
  function hasBooleanFlag(args, flag) {
413
412
  return args.includes(flag);
414
413
  }
415
- function parseAgentOperands(args, valueFlags, options = {}) {
414
+ function parseCommandOperands(args, valueFlags, booleanFlags = /* @__PURE__ */ new Map(), family = "command", allowFlagLikeValuesAfter = Number.POSITIVE_INFINITY) {
416
415
  const positionals = [];
417
416
  const values = /* @__PURE__ */ new Map();
417
+ const booleans = /* @__PURE__ */ new Set();
418
418
  let parseOptions = true;
419
419
  for (let index = 0; index < args.length; index += 1) {
420
420
  const arg = args[index];
@@ -422,6 +422,12 @@ function parseAgentOperands(args, valueFlags, options = {}) {
422
422
  parseOptions = false;
423
423
  continue;
424
424
  }
425
+ const booleanFlag = parseOptions ? booleanFlags.get(arg) : void 0;
426
+ if (booleanFlag) {
427
+ if (booleans.has(booleanFlag)) throw new Error(`${booleanFlag} may only be provided once`);
428
+ booleans.add(booleanFlag);
429
+ continue;
430
+ }
425
431
  const inlineFlag = parseOptions ? [...valueFlags].find((flag) => arg.startsWith(`${flag}=`)) : void 0;
426
432
  if (inlineFlag) {
427
433
  if (values.has(inlineFlag)) throw new Error(`${inlineFlag} may only be provided once`);
@@ -438,18 +444,18 @@ function parseAgentOperands(args, valueFlags, options = {}) {
438
444
  index += 1;
439
445
  continue;
440
446
  }
441
- if (parseOptions && arg.startsWith("-") && positionals.length < (options.allowFlagLikeValuesAfter ?? Number.POSITIVE_INFINITY)) {
442
- throw new Error(`Unknown agent flag: ${arg}`);
447
+ if (parseOptions && arg.startsWith("-") && positionals.length < allowFlagLikeValuesAfter) {
448
+ throw new Error(`Unknown ${family} flag: ${arg}`);
443
449
  }
444
450
  positionals.push(arg);
445
451
  }
446
- return { positionals, values };
452
+ return { positionals, values, booleans };
447
453
  }
448
- function requireAgentWorker(values) {
454
+ function requireWorker(values) {
449
455
  return requireValue(values.get("--worker")?.trim(), "--worker <label> is required");
450
456
  }
451
- function requireAgentSource(values) {
452
- const raw = requireValue(values.get("--source")?.trim(), "--source worktree:<branch> is required for `agent start`");
457
+ function parseWorktreeSource(rawValue) {
458
+ const raw = requireValue(rawValue?.trim(), "--source worktree:<branch> is required with --new-worktree");
453
459
  const separator = raw.indexOf(":");
454
460
  const type = raw.slice(0, separator);
455
461
  const branchName = raw.slice(separator + 1).trim();
@@ -458,61 +464,119 @@ function requireAgentSource(values) {
458
464
  }
459
465
  return { type, branchName };
460
466
  }
461
- function rejectAgentGlobalScopes(options, allowProject) {
462
- if (!allowProject && options.project) throw new Error("--project/-p is only supported for `agent start`");
463
- if (options.branch) throw new Error("--branch/-b is not supported for `agent`; pass --source explicitly");
464
- if (options.session) throw new Error("--session/-s is not supported for `agent`; pass the session id explicitly");
467
+ function requireModel(value) {
468
+ const model = requireValue(value?.trim(), "--model <tier> is required");
469
+ if (!MODEL_TIERS.has(model)) throw new Error("Invalid --model. Expected one of: low, medium, high, max");
470
+ return model;
471
+ }
472
+ function requireSessionMode(value) {
473
+ const mode = requireValue(value?.trim(), "--mode <mode> is required");
474
+ if (!R5DCTL_SESSION_MODES.has(mode)) {
475
+ throw new Error("Invalid --mode. Expected one of: ask, plan, build, agent");
476
+ }
477
+ return mode;
478
+ }
479
+ function rejectSessionRunGlobalScopes(options, allowProject) {
480
+ if (!allowProject && options.project) throw new Error("--project/-p is only supported for `session start`");
481
+ if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
482
+ if (options.session) throw new Error("--session/-s is not supported for `session`; pass the session id explicitly");
465
483
  }
466
- function parseBranchAgentCommand(options, args) {
467
- const subcommand = requireValue(args[0], "Missing agent command");
484
+ function parseSessionRunCommand(options, args) {
485
+ const subcommand = requireValue(args[0], "Missing session command");
468
486
  const commandArgs = args.slice(1);
469
487
  if (subcommand === "start") {
470
- rejectAgentGlobalScopes(options, true);
471
- const project = requireValue(options.project, "--project/-p is required for `agent start`");
472
- const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker", "--source"]), { allowFlagLikeValuesAfter: 1 });
473
- const agentType = requireValue(parsed.positionals[0], "Agent type is required for `agent start`");
474
- if (!AGENT_TYPES.has(agentType)) {
475
- throw new Error("Invalid agent type. Expected one of: research, debug, test");
476
- }
477
- const prompt = parsed.positionals.slice(1).join(" ").trim();
478
- if (!prompt) throw new Error("Agent prompt is required for `agent start`");
488
+ rejectSessionRunGlobalScopes(options, true);
489
+ const project = requireValue(options.project, "--project/-p is required for `session start`");
490
+ const parsed = parseCommandOperands(
491
+ commandArgs,
492
+ /* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--model"]),
493
+ /* @__PURE__ */ new Map([
494
+ ["-f", "--follow"],
495
+ ["--follow", "--follow"],
496
+ ["--watch", "--follow"],
497
+ ["--tools", "--tools"]
498
+ ]),
499
+ "session start",
500
+ 1
501
+ );
502
+ const prompt = parsed.positionals.join(" ").trim();
503
+ if (!prompt) throw new Error("Prompt is required for `session start`");
504
+ const worktree = parsed.values.get("--worktree")?.trim();
505
+ const newWorktree = parsed.values.get("--new-worktree")?.trim();
506
+ if (Boolean(worktree) === Boolean(newWorktree)) {
507
+ throw new Error("Use exactly one of --worktree <branch> or --new-worktree <branch>");
508
+ }
509
+ const sourceValue = parsed.values.get("--source");
510
+ if (worktree && sourceValue) throw new Error("--source is only supported with --new-worktree");
479
511
  return {
480
512
  kind: "start",
481
513
  project,
482
- worker: requireAgentWorker(parsed.values),
483
- source: requireAgentSource(parsed.values),
484
- agentType,
485
- prompt
514
+ worker: requireWorker(parsed.values),
515
+ model: requireModel(parsed.values.get("--model")),
516
+ prompt,
517
+ follow: parsed.booleans.has("--follow"),
518
+ includeTools: parsed.booleans.has("--tools"),
519
+ ...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue) }
486
520
  };
487
521
  }
488
- rejectAgentGlobalScopes(options, false);
522
+ rejectSessionRunGlobalScopes(options, false);
489
523
  if (subcommand === "status" || subcommand === "stop") {
490
- const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set());
491
- const sessionId = requireValue(parsed.positionals[0], `Session id is required for \`agent ${subcommand}\``);
492
- if (parsed.positionals.length > 1) throw new Error(`Unexpected agent ${subcommand} argument: ${parsed.positionals[1]}`);
524
+ const parsed = parseCommandOperands(commandArgs, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), `session ${subcommand}`);
525
+ const sessionId = requireValue(parsed.positionals[0], `Session id is required for \`session ${subcommand}\``);
526
+ if (parsed.positionals.length > 1) throw new Error(`Unexpected session ${subcommand} argument: ${parsed.positionals[1]}`);
493
527
  return { kind: subcommand, sessionId };
494
528
  }
495
529
  if (subcommand === "prompt") {
496
- const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker"]), { allowFlagLikeValuesAfter: 1 });
497
- const sessionId = requireValue(parsed.positionals[0], "Session id is required for `agent prompt`");
530
+ const parsed = parseCommandOperands(commandArgs, /* @__PURE__ */ new Set(["--worker", "--mode", "--model"]), /* @__PURE__ */ new Map(), "session prompt", 1);
531
+ const sessionId = requireValue(parsed.positionals[0], "Session id is required for `session prompt`");
498
532
  const prompt = parsed.positionals.slice(1).join(" ").trim();
499
- if (!prompt) throw new Error("Prompt is required for `agent prompt`");
500
- return { kind: "prompt", worker: requireAgentWorker(parsed.values), sessionId, prompt };
533
+ if (!prompt) throw new Error("Prompt is required for `session prompt`");
534
+ return {
535
+ kind: "prompt",
536
+ worker: requireWorker(parsed.values),
537
+ sessionId,
538
+ prompt,
539
+ mode: requireSessionMode(parsed.values.get("--mode")),
540
+ model: requireModel(parsed.values.get("--model"))
541
+ };
501
542
  }
502
- if (subcommand === "merge") {
503
- const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker"]));
504
- const sessionId = requireValue(parsed.positionals[0], "Session id is required for `agent merge`");
505
- if (parsed.positionals.length > 1) throw new Error(`Unexpected agent merge argument: ${parsed.positionals[1]}`);
506
- return { kind: "merge", worker: requireAgentWorker(parsed.values), sessionId };
543
+ throw new Error(`Unknown session command: ${subcommand}`);
544
+ }
545
+ function parseMergeCommand(options, args) {
546
+ const project = requireValue(options.project, "--project/-p is required for `merge`");
547
+ if (options.branch) throw new Error("--branch/-b is not supported for `merge`; pass the source worktree explicitly");
548
+ if (options.session) throw new Error("--session/-s is not supported for `merge`");
549
+ if (args[0] === "finish") {
550
+ const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(["--worker", "--summary"]), /* @__PURE__ */ new Map(), "merge finish");
551
+ const conflictWorktree = requireValue(parsed2.positionals[0], "Conflict worktree is required for `merge finish`");
552
+ if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge finish argument: ${parsed2.positionals[1]}`);
553
+ return {
554
+ kind: "finish",
555
+ project,
556
+ conflictWorktree,
557
+ worker: requireWorker(parsed2.values),
558
+ ...parsed2.values.get("--summary")?.trim() ? { summary: parsed2.values.get("--summary").trim() } : {}
559
+ };
507
560
  }
508
- if (subcommand === "finish-merge") {
509
- const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker", "--summary"]));
510
- const resolverSessionId = requireValue(parsed.positionals[0], "Resolver session id is required for `agent finish-merge`");
511
- if (parsed.positionals.length > 1) throw new Error(`Unexpected agent finish-merge argument: ${parsed.positionals[1]}`);
512
- const summary = requireValue(parsed.values.get("--summary")?.trim(), "--summary <text> is required for `agent finish-merge`");
513
- return { kind: "finish-merge", worker: requireAgentWorker(parsed.values), resolverSessionId, summary };
561
+ const parsed = parseCommandOperands(
562
+ args,
563
+ /* @__PURE__ */ new Set(["--worker"]),
564
+ /* @__PURE__ */ new Map([
565
+ ["--create-conflict-worktree", "--create-conflict-worktree"],
566
+ ["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"]
567
+ ]),
568
+ "merge"
569
+ );
570
+ const sourceWorktree = requireValue(parsed.positionals[0], "Source worktree is required for `merge`");
571
+ if (parsed.positionals.length > 1) throw new Error(`Unexpected merge argument: ${parsed.positionals[1]}`);
572
+ const createWorktree = parsed.booleans.has("--create-conflict-worktree");
573
+ const launchAgent = parsed.booleans.has("--launch-conflict-resolution-agent");
574
+ if (createWorktree && launchAgent) {
575
+ throw new Error("Use only one of --create-conflict-worktree or --launch-conflict-resolution-agent");
514
576
  }
515
- throw new Error(`Unknown agent command: ${subcommand}`);
577
+ const conflictMode = createWorktree ? "worktree" : launchAgent ? "agent" : "none";
578
+ const worker = parsed.values.get("--worker")?.trim();
579
+ return { kind: "merge", project, sourceWorktree, conflictMode, ...worker ? { worker } : {} };
516
580
  }
517
581
  function assertAuthLoginArgs(args) {
518
582
  const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
@@ -730,6 +794,55 @@ function parseAnswerFlags(args) {
730
794
  }
731
795
  return ordered.map(([, value]) => value);
732
796
  }
797
+ function requireChatMode(value) {
798
+ const mode = requireValue(value?.trim(), "--mode <mode> is required");
799
+ if (!CHAT_MODES.has(mode)) throw new Error(`Invalid --mode: ${mode}`);
800
+ return mode;
801
+ }
802
+ function partitionInteractiveExecutionArgs(args, consumesNextValue) {
803
+ const values = /* @__PURE__ */ new Map();
804
+ const payloadArgs = [];
805
+ const executionFlags = ["--worker", "--mode", "--model"];
806
+ for (let index = 0; index < args.length; index += 1) {
807
+ const arg = args[index];
808
+ if (consumesNextValue(arg)) {
809
+ payloadArgs.push(arg);
810
+ const value = args[index + 1];
811
+ if (value === void 0) throw new Error(`Missing value for ${arg}`);
812
+ payloadArgs.push(value);
813
+ index += 1;
814
+ continue;
815
+ }
816
+ const inlineFlag = executionFlags.find((flag) => arg.startsWith(`${flag}=`));
817
+ if (inlineFlag) {
818
+ if (values.has(inlineFlag)) throw new Error(`${inlineFlag} may only be provided once`);
819
+ const value = requireValue(arg.slice(inlineFlag.length + 1).trim(), `Missing value for ${inlineFlag}`);
820
+ values.set(inlineFlag, value);
821
+ continue;
822
+ }
823
+ if (executionFlags.includes(arg)) {
824
+ const flag = arg;
825
+ if (values.has(flag)) throw new Error(`${flag} may only be provided once`);
826
+ const value = requireValue(args[index + 1]?.trim(), `Missing value for ${flag}`);
827
+ values.set(flag, value);
828
+ index += 1;
829
+ continue;
830
+ }
831
+ payloadArgs.push(arg);
832
+ }
833
+ return {
834
+ context: {
835
+ worker: requireValue(values.get("--worker")?.trim(), "--worker <label> is required"),
836
+ mode: requireChatMode(values.get("--mode")),
837
+ model: requireModel(values.get("--model"))
838
+ },
839
+ payloadArgs
840
+ };
841
+ }
842
+ function parseAnswerQuestionsCommandArgs(args) {
843
+ const { context, payloadArgs } = partitionInteractiveExecutionArgs(args, (arg) => /^-a\d+$/.test(arg));
844
+ return { ...context, answers: parseAnswerFlags(payloadArgs) };
845
+ }
733
846
  function validateEnvAssignment(value) {
734
847
  const equalsIndex = value.indexOf("=");
735
848
  if (equalsIndex <= 0) {
@@ -889,6 +1002,10 @@ function parseEnvRequestResponseArgs(args) {
889
1002
  ...additionalContext?.trim() ? { additionalContext: additionalContext.trim() } : {}
890
1003
  };
891
1004
  }
1005
+ function parseAnswerEnvRequestCommandArgs(args) {
1006
+ const { context, payloadArgs } = partitionInteractiveExecutionArgs(args, (arg) => arg === "-e" || arg === "--env" || arg === "--context");
1007
+ return { ...context, ...parseEnvRequestResponseArgs(payloadArgs) };
1008
+ }
892
1009
  function parsePromptArgs(args) {
893
1010
  let modeRaw;
894
1011
  let modelRaw;
@@ -1663,11 +1780,6 @@ function responseString(response, key) {
1663
1780
  const value = response[key];
1664
1781
  return typeof value === "string" && value.length > 0 ? value : void 0;
1665
1782
  }
1666
- function responseSource(response) {
1667
- const source = response.source;
1668
- if (!source || source.type !== "worktree" || !source.branchName) return void 0;
1669
- return `${source.type}:${source.branchName}`;
1670
- }
1671
1783
  function formatStatusValue(value) {
1672
1784
  if (typeof value === "string" && value.length > 0) return value;
1673
1785
  if (typeof value === "object" && value !== null) {
@@ -1677,21 +1789,16 @@ function formatStatusValue(value) {
1677
1789
  }
1678
1790
  return value === void 0 || value === null ? void 0 : String(value);
1679
1791
  }
1680
- function asResponseRecord(value) {
1681
- return typeof value === "object" && value !== null ? value : void 0;
1682
- }
1683
- function appendMergeDetails(lines, response) {
1684
- const merge = asResponseRecord(response.merge);
1685
- if (!merge) return;
1792
+ function appendMergeDetails(lines, merge) {
1686
1793
  const mergeStatus = formatStatusValue(merge.status);
1687
- lines.push("", `Merge: ${mergeStatus ?? "pending"}`);
1794
+ lines.push(`Merge: ${mergeStatus ?? "pending"}`);
1688
1795
  for (const [label, key] of [
1689
1796
  ["Attempt", "attemptId"],
1690
- ["Source", "sourceBranch"],
1691
- ["Target", "targetBranch"],
1797
+ ["Source worktree", "sourceWorktree"],
1798
+ ["Target worktree", "targetWorktree"],
1692
1799
  ["Commit", "commitHash"],
1693
1800
  ["Resolver agent", "resolverSessionId"],
1694
- ["Resolver branch", "resolverBranch"],
1801
+ ["Conflict worktree", "conflictWorktree"],
1695
1802
  ["Resolver worktree", "folderPath"]
1696
1803
  ]) {
1697
1804
  const value = responseString(merge, key);
@@ -1705,45 +1812,30 @@ function appendMergeDetails(lines, response) {
1705
1812
  if (message) lines.push("", message);
1706
1813
  const failureReason = responseString(merge, "failureReason");
1707
1814
  if (failureReason) lines.push(`Failure: ${failureReason}`);
1708
- const resolverSessionId = responseString(merge, "resolverSessionId");
1709
- const worker = responseString(merge, "worker") ?? responseString(response, "worker");
1710
- if (resolverSessionId && worker) {
1711
- lines.push(
1712
- "",
1713
- "Resolver takeover:",
1714
- ` r5dctl agent stop ${resolverSessionId}`,
1715
- ` r5dctl agent finish-merge --worker ${worker} ${resolverSessionId} --summary "<summary>"`
1716
- );
1717
- }
1718
1815
  }
1719
- function renderBranchAgentStart(agent) {
1816
+ function renderSessionRunStart(session) {
1720
1817
  const lines = [
1721
- `Agent: ${responseString(agent, "sessionId") ?? "(unknown)"}`,
1722
- `Status: ${formatStatusValue(agent.status) ?? "queued"}`,
1723
- `Branch: ${responseString(agent, "branchName") ?? "(provisioning)"}`,
1724
- `Source: ${responseSource(agent) ?? "(unknown)"}`,
1725
- `Workspace head: ${responseString(agent, "workspaceHead") ?? "(pending)"}`,
1726
- `Baseline: ${responseString(agent, "baselineCommit") ?? "(pending)"}`,
1727
- `Worker: ${responseString(agent, "worker") ?? "(unassigned)"}`
1818
+ `Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
1819
+ `Status: ${formatStatusValue(session.status) ?? "queued"}`,
1820
+ `Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`,
1821
+ `Worker: ${responseString(session, "worker") ?? "(unassigned)"}`
1728
1822
  ];
1729
- const sessionUrl = responseString(agent, "sessionUrl");
1730
- if (sessionUrl) lines.push(`Session: ${sessionUrl}`);
1731
- const sessionId = responseString(agent, "sessionId");
1732
- if (sessionId) lines.push("", `Next: r5dctl agent status ${sessionId}`);
1823
+ const sessionUrl = responseString(session, "sessionUrl");
1824
+ if (sessionUrl) lines.push(`URL: ${sessionUrl}`);
1825
+ const sessionId = responseString(session, "sessionId");
1826
+ if (sessionId) lines.push("", `Next: r5dctl session status ${sessionId}`);
1733
1827
  return `${lines.join("\n")}
1734
1828
  `;
1735
1829
  }
1736
- function renderBranchAgentStatus(agent) {
1737
- const status = formatStatusValue(agent.status) ?? "unknown";
1738
- const lines = [`Agent: ${responseString(agent, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
1739
- const runStatus = formatStatusValue(agent.runStatus);
1830
+ function renderSessionRunStatus(session) {
1831
+ const status = formatStatusValue(session.status) ?? "unknown";
1832
+ const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
1833
+ const runStatus = formatStatusValue(session.runStatus);
1740
1834
  if (runStatus) lines.push(`Run: ${runStatus}`);
1741
- const promptDisposition = responseString(agent, "promptDisposition");
1835
+ const promptDisposition = responseString(session, "promptDisposition");
1742
1836
  if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
1743
- const branch = responseString(agent, "branchName");
1744
- if (branch) lines.push(`Branch: ${branch}`);
1745
- const source = responseSource(agent);
1746
- if (source) lines.push(`Source: ${source}`);
1837
+ const branch = responseString(session, "branchName");
1838
+ if (branch) lines.push(`Worktree: ${branch}`);
1747
1839
  for (const [label, key] of [
1748
1840
  ["Workspace head", "workspaceHead"],
1749
1841
  ["Baseline", "baselineCommit"],
@@ -1751,72 +1843,90 @@ function renderBranchAgentStatus(agent) {
1751
1843
  ["Worker", "worker"],
1752
1844
  ["Session", "sessionUrl"]
1753
1845
  ]) {
1754
- const value = responseString(agent, key);
1846
+ const value = responseString(session, key);
1755
1847
  if (value) lines.push(`${label}: ${value}`);
1756
1848
  }
1757
- const error = responseString(agent, "error");
1849
+ const error = responseString(session, "error");
1758
1850
  if (error) lines.push(`Error: ${error}`);
1759
- const headCommitError = responseString(agent, "headCommitError");
1851
+ const headCommitError = responseString(session, "headCommitError");
1760
1852
  if (headCommitError) lines.push(`Head error: ${headCommitError}`);
1761
- const diffSummary = responseString(agent, "diffSummary");
1853
+ const diffSummary = responseString(session, "diffSummary");
1762
1854
  if (diffSummary) lines.push("", diffSummary);
1763
- const summary = responseString(agent, "summary");
1855
+ const summary = responseString(session, "summary");
1764
1856
  if (summary) lines.push("", summary);
1765
- appendMergeDetails(lines, agent);
1766
- const sessionId = responseString(agent, "sessionId");
1767
- if (sessionId && !asResponseRecord(agent.merge)) {
1768
- if (["provisioning", "queued", "running"].includes(status)) {
1769
- lines.push("", "Next:", ` r5dctl agent status ${sessionId}`, ` r5dctl agent stop ${sessionId}`);
1770
- } else if (["completed", "failed", "stopped"].includes(status)) {
1771
- const worker = responseString(agent, "worker") ?? "<worker>";
1772
- lines.push("", "Next:", ` r5dctl agent merge --worker ${worker} ${sessionId}`);
1773
- }
1857
+ const sessionId = responseString(session, "sessionId");
1858
+ if (sessionId && ["provisioning", "queued", "running"].includes(status)) {
1859
+ lines.push("", "Next:", ` r5dctl session status ${sessionId}`, ` r5dctl session stop ${sessionId}`);
1774
1860
  }
1775
1861
  return `${lines.join("\n")}
1776
1862
  `;
1777
1863
  }
1778
- function renderBranchAgentMerge(result) {
1779
- if (asResponseRecord(result.merge)) return renderBranchAgentStatus(result);
1780
- const wrapped = {
1781
- sessionId: result.sessionId,
1782
- status: result.status,
1783
- worker: result.worker,
1784
- merge: result
1785
- };
1864
+ function renderWorktreeMerge(result, input) {
1786
1865
  const lines = [];
1787
- appendMergeDetails(lines, wrapped);
1788
- return `${lines.slice(lines[0] === "" ? 1 : 0).join("\n")}
1866
+ appendMergeDetails(lines, result);
1867
+ const conflictWorktree = responseString(result, "conflictWorktree");
1868
+ const resolverSessionId = responseString(result, "resolverSessionId");
1869
+ const worker = responseString(result, "worker") ?? input.worker;
1870
+ if (resolverSessionId) lines.push("", `Resolver session: ${resolverSessionId}`, ` r5dctl session stop ${resolverSessionId}`);
1871
+ if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(responseString(result, "status") ?? "")) {
1872
+ lines.push(
1873
+ "",
1874
+ "Finish after resolving and reviewing the conflict worktree:",
1875
+ ` r5dctl -p ${input.project} merge finish --worker ${worker} ${conflictWorktree} --summary "<summary>"`
1876
+ );
1877
+ }
1878
+ return `${lines.join("\n")}
1789
1879
  `;
1790
1880
  }
1791
- async function dispatchBranchAgentCommand(client, command) {
1881
+ async function dispatchSessionRunCommand(client, command) {
1792
1882
  if (command.kind === "start") {
1793
- const data2 = await client.projects.agents.start(command.project, {
1794
- source: command.source,
1795
- agentType: command.agentType,
1883
+ const commonInput = {
1796
1884
  prompt: command.prompt,
1797
1885
  worker: command.worker,
1886
+ model: command.model,
1798
1887
  requestId: randomUUID()
1799
- });
1800
- return { data: data2, human: renderBranchAgentStart(data2) };
1888
+ };
1889
+ let input;
1890
+ if (command.worktree !== void 0) {
1891
+ input = { ...commonInput, worktree: command.worktree };
1892
+ } else if (command.newWorktree !== void 0 && command.source !== void 0) {
1893
+ input = { ...commonInput, newWorktree: command.newWorktree, source: command.source };
1894
+ } else {
1895
+ throw new Error("Session start requires an existing or new worktree");
1896
+ }
1897
+ const data2 = await client.projects.sessions.start(command.project, input);
1898
+ return { data: data2, human: renderSessionRunStart(data2) };
1801
1899
  }
1802
1900
  if (command.kind === "status") {
1803
- const data2 = await client.agents.status(command.sessionId);
1804
- return { data: data2, human: renderBranchAgentStatus(data2) };
1901
+ const data2 = await client.sessions.status(command.sessionId);
1902
+ return { data: data2, human: renderSessionRunStatus(data2) };
1805
1903
  }
1806
1904
  if (command.kind === "prompt") {
1807
- const data2 = await client.agents.sendPrompt(command.sessionId, { prompt: command.prompt, worker: command.worker });
1808
- return { data: data2, human: renderBranchAgentStatus(data2) };
1905
+ const data2 = await client.sessions.prompt(command.sessionId, {
1906
+ prompt: command.prompt,
1907
+ worker: command.worker,
1908
+ mode: command.mode,
1909
+ model: command.model,
1910
+ requestId: randomUUID()
1911
+ });
1912
+ return { data: data2, human: renderSessionRunStatus(data2) };
1809
1913
  }
1914
+ const data = await client.sessions.stop(command.sessionId);
1915
+ return { data, human: renderSessionRunStatus(data) };
1916
+ }
1917
+ async function dispatchMergeCommand(client, command) {
1810
1918
  if (command.kind === "merge") {
1811
- const data2 = await client.agents.merge(command.sessionId, { worker: command.worker });
1812
- return { data: data2, human: renderBranchAgentMerge(data2) };
1813
- }
1814
- if (command.kind === "stop") {
1815
- const data2 = await client.agents.stop(command.sessionId);
1816
- return { data: data2, human: renderBranchAgentStatus(data2) };
1919
+ const data2 = await client.projects.worktrees.merge(command.project, command.sourceWorktree, {
1920
+ conflictMode: command.conflictMode,
1921
+ ...command.worker ? { worker: command.worker } : {}
1922
+ });
1923
+ return { data: data2, human: renderWorktreeMerge(data2, command) };
1817
1924
  }
1818
- const data = await client.agents.finishMerge(command.resolverSessionId, { worker: command.worker, summary: command.summary });
1819
- return { data, human: renderBranchAgentMerge(data) };
1925
+ const data = await client.projects.worktrees.finishMerge(command.project, command.conflictWorktree, {
1926
+ worker: command.worker,
1927
+ ...command.summary ? { summary: command.summary } : {}
1928
+ });
1929
+ return { data, human: renderWorktreeMerge(data, command) };
1820
1930
  }
1821
1931
  function parseProjectUpdateArgs(args) {
1822
1932
  const input = {};
@@ -1891,7 +2001,7 @@ function parseSessionUpdateArgs(args) {
1891
2001
  return input;
1892
2002
  }
1893
2003
  function parseConversationRenderArgs(args) {
1894
- const options = { format: "human", includeTools: false, includeSystem: false };
2004
+ const options = { format: "human", includeTools: false, includeSystem: false, follow: false };
1895
2005
  for (const arg of args) {
1896
2006
  if (arg === "--raw") {
1897
2007
  options.format = "raw";
@@ -1905,6 +2015,11 @@ function parseConversationRenderArgs(args) {
1905
2015
  options.includeSystem = true;
1906
2016
  continue;
1907
2017
  }
2018
+ if (arg === "-f" || arg === "--follow" || arg === "--watch") {
2019
+ if (options.follow) throw new Error("--follow may only be provided once");
2020
+ options.follow = true;
2021
+ continue;
2022
+ }
1908
2023
  throw new Error(`Unknown conversation argument: ${arg}`);
1909
2024
  }
1910
2025
  return options;
@@ -1913,6 +2028,306 @@ function renderConversationResponse(read) {
1913
2028
  return read.agentText.endsWith("\n") ? read.agentText : `${read.agentText}
1914
2029
  `;
1915
2030
  }
2031
+ function recordValue(value) {
2032
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
2033
+ }
2034
+ function nodeId(node) {
2035
+ const id = recordValue(node)?.id;
2036
+ return typeof id === "string" && id.length > 0 ? id : void 0;
2037
+ }
2038
+ function nodeType(node) {
2039
+ const type = recordValue(node)?.type;
2040
+ return typeof type === "string" ? type : void 0;
2041
+ }
2042
+ function assistantNodeText(node) {
2043
+ const record = recordValue(node);
2044
+ return record?.type === "assistant_message" && typeof record.content === "string" ? record.content : void 0;
2045
+ }
2046
+ function conversationRevision(read) {
2047
+ const revision = recordValue(read.conversation)?.revision;
2048
+ return typeof revision === "number" ? revision : 0;
2049
+ }
2050
+ function activeConversationNodes(conversation) {
2051
+ const record = recordValue(conversation);
2052
+ const responses = recordValue(record?.responses);
2053
+ let currentId = typeof record?.head === "string" ? record.head : "";
2054
+ if (!responses || !currentId) return [];
2055
+ const reversed = [];
2056
+ const visited = /* @__PURE__ */ new Set();
2057
+ while (currentId && !visited.has(currentId)) {
2058
+ visited.add(currentId);
2059
+ const node = responses[currentId];
2060
+ if (!node) break;
2061
+ reversed.push(node);
2062
+ const parent = recordValue(node)?.parent;
2063
+ currentId = typeof parent === "string" ? parent : "";
2064
+ }
2065
+ return reversed.reverse();
2066
+ }
2067
+ function isToolEvent(event) {
2068
+ return ["tool_use_start", "input_json_delta", "tool_call", "tool_progress", "shell_result"].includes(event.type);
2069
+ }
2070
+ function withoutToolDetails(node) {
2071
+ const record = recordValue(node);
2072
+ if (!record || record.type !== "assistant_message" || !Array.isArray(record.toolCalls)) return node;
2073
+ return { ...record, toolCalls: [] };
2074
+ }
2075
+ function jsonConversationData(read, includeTools) {
2076
+ if (includeTools) return read;
2077
+ const { conversation: _conversation, thread, ...rest } = read;
2078
+ return {
2079
+ ...rest,
2080
+ thread: Array.isArray(thread) ? thread.filter((node) => nodeType(node) !== "tool_result").map((node) => withoutToolDetails(node)) : []
2081
+ };
2082
+ }
2083
+ function jsonFollowEvent(event, includeTools) {
2084
+ if (event.type === "session") {
2085
+ const data = recordValue(event.data);
2086
+ if (!data) return event;
2087
+ const { conversation: _conversation, ...rest } = data;
2088
+ return { ...event, data: rest };
2089
+ }
2090
+ if (includeTools) return event;
2091
+ if (isToolEvent(event)) return null;
2092
+ if (event.type === "response") {
2093
+ const data = recordValue(event.data);
2094
+ const node = data?.node;
2095
+ if (nodeType(node) === "tool_result") return null;
2096
+ return { ...event, data: { ...data, node: withoutToolDetails(node) } };
2097
+ }
2098
+ return event;
2099
+ }
2100
+ function terminalRunStatus(response) {
2101
+ const status = formatStatusValue(response.runStatus) ?? formatStatusValue(response.status);
2102
+ return {
2103
+ terminal: status !== void 0 && ["idle", "completed", "failed", "stopped"].includes(status),
2104
+ failed: status === "failed"
2105
+ };
2106
+ }
2107
+ function isAbortError(error) {
2108
+ return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message.toLowerCase().includes("aborted"));
2109
+ }
2110
+ async function followR5dctlSession(client, sessionId, options) {
2111
+ const writeOut = options.stdout ?? ((text) => process.stdout.write(text));
2112
+ const writeErr = options.stderr ?? ((text) => process.stderr.write(text));
2113
+ const seenNodeIds = /* @__PURE__ */ new Set();
2114
+ let latestRevision = 0;
2115
+ let streamedAssistantText = "";
2116
+ let reconnects = 0;
2117
+ const emitJson = (value) => writeOut(`${JSON.stringify(value)}
2118
+ `);
2119
+ const renderNode = (node) => {
2120
+ const id = nodeId(node);
2121
+ if (id && seenNodeIds.has(id)) return;
2122
+ if (id) seenNodeIds.add(id);
2123
+ const text = assistantNodeText(node);
2124
+ if (text !== void 0) {
2125
+ let missing = text;
2126
+ if (streamedAssistantText && text.startsWith(streamedAssistantText)) missing = text.slice(streamedAssistantText.length);
2127
+ else if (streamedAssistantText.startsWith(text)) missing = "";
2128
+ if (missing) writeOut(missing);
2129
+ streamedAssistantText = "";
2130
+ if (options.includeTools) {
2131
+ const toolCalls = recordValue(node)?.toolCalls;
2132
+ if (Array.isArray(toolCalls) && toolCalls.length > 0) writeErr(`[tools] ${JSON.stringify(toolCalls)}
2133
+ `);
2134
+ }
2135
+ return;
2136
+ }
2137
+ if (options.includeTools && nodeType(node) === "tool_result") writeErr(`[tool results] ${JSON.stringify(node)}
2138
+ `);
2139
+ };
2140
+ const syncConversation = async (initial = false) => {
2141
+ const read = initial && options.initialConversation ? options.initialConversation : await client.sessions.conversation(sessionId, options.renderOptions);
2142
+ const nodes = Array.isArray(read.thread) ? read.thread : [];
2143
+ const newNodes = nodes.filter((node) => {
2144
+ const id = nodeId(node);
2145
+ return !id || !seenNodeIds.has(id);
2146
+ });
2147
+ if (initial && options.printInitialTranscript) {
2148
+ if (options.json)
2149
+ emitJson({ type: "conversation", revision: conversationRevision(read), data: jsonConversationData(read, options.includeTools) });
2150
+ else writeOut(renderConversationResponse(read));
2151
+ for (const node of nodes) {
2152
+ const id = nodeId(node);
2153
+ if (id) seenNodeIds.add(id);
2154
+ }
2155
+ } else if (options.json && newNodes.length > 0) {
2156
+ const filtered = options.includeTools ? newNodes : newNodes.filter((node) => nodeType(node) !== "tool_result");
2157
+ if (filtered.length > 0) {
2158
+ emitJson({
2159
+ type: "conversation_sync",
2160
+ revision: conversationRevision(read),
2161
+ nodes: options.includeTools ? filtered : filtered.map((node) => withoutToolDetails(node))
2162
+ });
2163
+ }
2164
+ for (const node of newNodes) {
2165
+ const id = nodeId(node);
2166
+ if (id) seenNodeIds.add(id);
2167
+ }
2168
+ } else {
2169
+ for (const node of newNodes) renderNode(node);
2170
+ }
2171
+ latestRevision = Math.max(latestRevision, conversationRevision(read));
2172
+ return read;
2173
+ };
2174
+ await syncConversation(true);
2175
+ while (!options.signal?.aborted) {
2176
+ try {
2177
+ for await (const event of client.sessions.events(sessionId, { signal: options.signal })) {
2178
+ reconnects = 0;
2179
+ if (event.type === "response") {
2180
+ const data = recordValue(event.data);
2181
+ const revision = data?.conversationRevision;
2182
+ const id = nodeId(data?.node);
2183
+ if (typeof revision === "number" && revision <= latestRevision || id !== void 0 && seenNodeIds.has(id)) continue;
2184
+ }
2185
+ if (options.json) {
2186
+ const outputEvent = jsonFollowEvent(event, options.includeTools);
2187
+ if (outputEvent) emitJson(outputEvent);
2188
+ } else if (event.type === "text_delta" && typeof event.text === "string") {
2189
+ writeOut(event.text);
2190
+ streamedAssistantText += event.text;
2191
+ } else if (event.type === "thinking_delta" && typeof event.text === "string") {
2192
+ writeErr(event.text);
2193
+ } else if (event.type === "tool_use_start" && options.includeTools) {
2194
+ writeErr(`[tool] ${String(event.toolName)}
2195
+ `);
2196
+ } else if (event.type === "input_json_delta" && options.includeTools) {
2197
+ writeErr(String(event.partial_json));
2198
+ } else if ((event.type === "tool_call" || event.type === "tool_progress" || event.type === "shell_result") && options.includeTools) {
2199
+ writeErr(`[${event.type}] ${JSON.stringify(event)}
2200
+ `);
2201
+ } else if (event.type === "status") {
2202
+ writeErr(`[status] ${formatStatusValue(event.status) ?? "updated"}
2203
+ `);
2204
+ } else if (event.type === "agent_running") {
2205
+ writeErr(`[running] ${event.running ? "yes" : "no"}
2206
+ `);
2207
+ } else if (event.type === "stream_reset") {
2208
+ streamedAssistantText = "";
2209
+ writeErr(`[retry] ${event.attempt}/${event.maxAttempts}
2210
+ `);
2211
+ } else if (event.type === "response") {
2212
+ const revision = recordValue(event.data)?.conversationRevision;
2213
+ if (typeof revision === "number") latestRevision = revision;
2214
+ renderNode(recordValue(event.data)?.node);
2215
+ } else if (event.type === "session") {
2216
+ const session = recordValue(event.data);
2217
+ const conversation = session?.conversation;
2218
+ for (const node of activeConversationNodes(conversation)) renderNode(node);
2219
+ const revision = recordValue(conversation)?.revision;
2220
+ if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
2221
+ const streamingText = recordValue(session?.streaming)?.text;
2222
+ if (!options.json && typeof streamingText === "string" && streamingText.length > 0) {
2223
+ let missing = streamingText;
2224
+ if (streamedAssistantText && streamingText.startsWith(streamedAssistantText))
2225
+ missing = streamingText.slice(streamedAssistantText.length);
2226
+ else if (streamedAssistantText.startsWith(streamingText)) missing = "";
2227
+ if (missing) writeOut(missing);
2228
+ streamedAssistantText = streamingText;
2229
+ }
2230
+ if (session?.agentRunning === false) {
2231
+ try {
2232
+ const terminal = terminalRunStatus(await client.sessions.status(sessionId));
2233
+ if (terminal.terminal) return terminal.failed ? 1 : 0;
2234
+ } catch (error) {
2235
+ if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
2236
+ const status = formatStatusValue(session.status);
2237
+ if (error instanceof R5dctlApiError && error.status === 400 && status === "idle") return 0;
2238
+ if (error instanceof R5dctlApiError && error.status === 400 && status === "error") return 1;
2239
+ }
2240
+ }
2241
+ }
2242
+ if (event.type === "response" && options.json) {
2243
+ const revision = recordValue(event.data)?.conversationRevision;
2244
+ if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
2245
+ const id = nodeId(recordValue(event.data)?.node);
2246
+ if (id) seenNodeIds.add(id);
2247
+ }
2248
+ if (event.type === "session" && options.json) {
2249
+ const session = recordValue(event.data);
2250
+ const conversation = session?.conversation;
2251
+ const newNodes = activeConversationNodes(conversation).filter((node) => {
2252
+ const id = nodeId(node);
2253
+ return !id || !seenNodeIds.has(id);
2254
+ });
2255
+ const filteredNodes = options.includeTools ? newNodes : newNodes.filter((node) => nodeType(node) !== "tool_result");
2256
+ if (filteredNodes.length > 0) {
2257
+ emitJson({
2258
+ type: "conversation_sync",
2259
+ revision: recordValue(conversation)?.revision,
2260
+ nodes: options.includeTools ? filteredNodes : filteredNodes.map((node) => withoutToolDetails(node))
2261
+ });
2262
+ }
2263
+ for (const node of newNodes) {
2264
+ const id = nodeId(node);
2265
+ if (id) seenNodeIds.add(id);
2266
+ }
2267
+ const revision = recordValue(conversation)?.revision;
2268
+ if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
2269
+ if (session?.agentRunning === false) {
2270
+ try {
2271
+ const terminal = terminalRunStatus(await client.sessions.status(sessionId));
2272
+ if (terminal.terminal) return terminal.failed ? 1 : 0;
2273
+ } catch (error) {
2274
+ if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
2275
+ const status = formatStatusValue(session.status);
2276
+ if (error instanceof R5dctlApiError && error.status === 400 && status === "idle") return 0;
2277
+ if (error instanceof R5dctlApiError && error.status === 400 && status === "error") return 1;
2278
+ }
2279
+ }
2280
+ }
2281
+ if (event.type === "done" || event.type === "stopped") {
2282
+ await syncConversation();
2283
+ if (!options.json) writeErr(`[${event.type}]
2284
+ `);
2285
+ try {
2286
+ const terminal = terminalRunStatus(await client.sessions.status(sessionId));
2287
+ if (terminal.terminal) return terminal.failed ? 1 : 0;
2288
+ if (event.type === "done") continue;
2289
+ return 0;
2290
+ } catch (error) {
2291
+ if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
2292
+ if (error instanceof R5dctlApiError && error.status === 400) return 0;
2293
+ throw error;
2294
+ }
2295
+ }
2296
+ if (event.type === "error") {
2297
+ if (!options.json) writeErr(`${typeof event.message === "string" ? event.message : "Session failed"}
2298
+ `);
2299
+ return 1;
2300
+ }
2301
+ }
2302
+ } catch (error) {
2303
+ if (options.signal?.aborted || isAbortError(error)) return 0;
2304
+ if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
2305
+ }
2306
+ if (options.signal?.aborted) return 0;
2307
+ try {
2308
+ await syncConversation();
2309
+ } catch (error) {
2310
+ if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
2311
+ }
2312
+ try {
2313
+ const terminal = terminalRunStatus(await client.sessions.status(sessionId));
2314
+ if (terminal.terminal) return terminal.failed ? 1 : 0;
2315
+ } catch (error) {
2316
+ if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
2317
+ }
2318
+ reconnects += 1;
2319
+ if (reconnects > (options.maxReconnects ?? Number.POSITIVE_INFINITY)) {
2320
+ throw new Error("Session event stream ended before the session became idle");
2321
+ }
2322
+ try {
2323
+ await sleep(options.reconnectDelayMs ?? Math.min(250 * 2 ** (reconnects - 1), 5e3), void 0, { signal: options.signal });
2324
+ } catch (error) {
2325
+ if (options.signal?.aborted || isAbortError(error)) return 0;
2326
+ throw error;
2327
+ }
2328
+ }
2329
+ return 0;
2330
+ }
1916
2331
  function parseConversationWorkDetailArgs(args) {
1917
2332
  let detail = "compact";
1918
2333
  let selected = false;
@@ -2144,8 +2559,8 @@ Sessions: ${result.sessions.length}
2144
2559
  throw new Error(`Unexpected conversation inspect-node argument: ${args[4]}`);
2145
2560
  }
2146
2561
  const sessionId = requireValue(args[2], "Missing session id");
2147
- const nodeId = requireValue(args[3], "Missing node id");
2148
- const read = await client.sessions.inspectConversationNode(sessionId, nodeId);
2562
+ const nodeId2 = requireValue(args[3], "Missing node id");
2563
+ const read = await client.sessions.inspectConversationNode(sessionId, nodeId2);
2149
2564
  write(read, renderConversationNodeResponse(read));
2150
2565
  return;
2151
2566
  }
@@ -2160,33 +2575,30 @@ Sessions: ${result.sessions.length}
2160
2575
  if (first === "sessions" && second === "conversation" || first === "conversation") {
2161
2576
  const offset = first === "conversation" ? 1 : 2;
2162
2577
  const renderOptions = parseConversationRenderArgs(args.slice(offset + 1));
2163
- const read = await client.sessions.conversation(requireValue(args[offset], "Missing session id"), renderOptions);
2164
- write(read, renderConversationResponse(read));
2165
- return;
2166
- }
2167
- if (first === "sessions" && second === "prompt" || first === "prompt") {
2168
- const offset = first === "prompt" ? 1 : 2;
2169
- const read = await client.sessions.prompt(requireValue(args[offset], "Missing session id"), {
2170
- mode: requireValue(args[offset + 1], "Missing mode"),
2171
- model: requireValue(args[offset + 2], "Missing model"),
2172
- message: requireValue(args[offset + 3], "Missing prompt message")
2173
- });
2578
+ if (renderOptions.follow) throw new Error("Conversation follow must be handled by the CLI stream runner");
2579
+ const { follow: _follow, ...apiRenderOptions } = renderOptions;
2580
+ const read = await client.sessions.conversation(requireValue(args[offset], "Missing session id"), apiRenderOptions);
2174
2581
  write(read, renderConversationResponse(read));
2175
2582
  return;
2176
2583
  }
2177
2584
  if (first === "sessions" && second === "answer-questions" || first === "answer-questions") {
2178
2585
  const offset = first === "answer-questions" ? 1 : 2;
2179
- const answers = args.slice(offset + 1);
2180
- if (answers.length === 0) throw new Error("At least one answer is required");
2181
- const read = await client.sessions.answerQuestions(requireValue(args[offset], "Missing session id"), { answers });
2182
- write(read, renderConversationResponse(read));
2586
+ const input = parseAnswerQuestionsCommandArgs(args.slice(offset + 1));
2587
+ const read = await client.sessions.answerQuestions(requireValue(args[offset], "Missing session id"), {
2588
+ ...input,
2589
+ requestId: randomUUID()
2590
+ });
2591
+ write(read, renderSessionRunStatus(read));
2183
2592
  return;
2184
2593
  }
2185
2594
  if (first === "sessions" && second === "answer-env-request" || first === "answer-env-request") {
2186
2595
  const offset = first === "answer-env-request" ? 1 : 2;
2187
- const input = parseEnvRequestResponseArgs(args.slice(offset + 1));
2188
- const read = await client.sessions.answerEnvRequest(requireValue(args[offset], "Missing session id"), input);
2189
- write(read, renderConversationResponse(read));
2596
+ const input = parseAnswerEnvRequestCommandArgs(args.slice(offset + 1));
2597
+ const read = await client.sessions.answerEnvRequest(requireValue(args[offset], "Missing session id"), {
2598
+ ...input,
2599
+ requestId: randomUUID()
2600
+ });
2601
+ write(read, renderSessionRunStatus(read));
2190
2602
  return;
2191
2603
  }
2192
2604
  throw new Error(`Unknown command: ${args.join(" ")}`);
@@ -2211,10 +2623,16 @@ function resolveCommandExecution(options, rest) {
2211
2623
  text: K8S_HELP_TEXT
2212
2624
  };
2213
2625
  }
2214
- if (command === "agent" && commandArgs.length === 0) {
2626
+ if (command === "session" && commandArgs.length === 0) {
2627
+ return {
2628
+ kind: "cli-help",
2629
+ text: SESSION_RUN_HELP_TEXT
2630
+ };
2631
+ }
2632
+ if (command === "merge" && commandArgs.length === 0) {
2215
2633
  return {
2216
2634
  kind: "cli-help",
2217
- text: AGENT_HELP_TEXT
2635
+ text: MERGE_HELP_TEXT
2218
2636
  };
2219
2637
  }
2220
2638
  if (trailingHelp) {
@@ -2576,24 +2994,23 @@ function resolveCommandExecution(options, rest) {
2576
2994
  if (!sessionId2) {
2577
2995
  throw new Error("Session id is required for `conversation inspect-node`");
2578
2996
  }
2579
- const nodeId = options.session ? commandArgs[1] : commandArgs[2];
2580
- if (!nodeId) {
2997
+ const nodeId2 = options.session ? commandArgs[1] : commandArgs[2];
2998
+ if (!nodeId2) {
2581
2999
  throw new Error("Node id is required for `conversation inspect-node`");
2582
3000
  }
2583
3001
  const remainingArgs = options.session ? commandArgs.slice(2) : commandArgs.slice(3);
2584
3002
  return {
2585
3003
  kind: "plugin",
2586
- pluginArgs: ["conversation", "inspect-node", sessionId2, nodeId, ...remainingArgs]
3004
+ pluginArgs: ["conversation", "inspect-node", sessionId2, nodeId2, ...remainingArgs]
2587
3005
  };
2588
3006
  }
2589
3007
  const sessionId = options.session;
2590
3008
  if (!sessionId) {
2591
3009
  throw new Error("--session/-s is required for `conversation`");
2592
3010
  }
2593
- return {
2594
- kind: "plugin",
2595
- pluginArgs: ["conversation", sessionId, ...commandArgs]
2596
- };
3011
+ const renderOptions = parseConversationRenderArgs(commandArgs);
3012
+ if (renderOptions.follow) return { kind: "conversation-follow", sessionId, renderOptions };
3013
+ return { kind: "plugin", pluginArgs: ["conversation", sessionId, ...commandArgs] };
2597
3014
  }
2598
3015
  if (command === "shell") {
2599
3016
  if (!options.project) {
@@ -2610,19 +3027,11 @@ function resolveCommandExecution(options, rest) {
2610
3027
  ...parseShellArgs(commandArgs)
2611
3028
  };
2612
3029
  }
2613
- if (command === "prompt") {
2614
- const sessionId = options.session;
2615
- if (!sessionId) {
2616
- throw new Error("--session/-s is required for `prompt`");
2617
- }
2618
- const parsed = parsePromptArgs(commandArgs);
2619
- return {
2620
- kind: "plugin",
2621
- pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
2622
- };
3030
+ if (command === "session") {
3031
+ return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
2623
3032
  }
2624
- if (command === "agent") {
2625
- return { kind: "agent", command: parseBranchAgentCommand(options, commandArgs) };
3033
+ if (command === "merge") {
3034
+ return { kind: "merge", command: parseMergeCommand(options, commandArgs) };
2626
3035
  }
2627
3036
  if (command === "answer-questions") {
2628
3037
  const sessionId = options.session;
@@ -2631,7 +3040,7 @@ function resolveCommandExecution(options, rest) {
2631
3040
  }
2632
3041
  return {
2633
3042
  kind: "plugin",
2634
- pluginArgs: ["answer-questions", sessionId, ...parseAnswerFlags(commandArgs)]
3043
+ pluginArgs: ["answer-questions", sessionId, ...commandArgs]
2635
3044
  };
2636
3045
  }
2637
3046
  if (command === "answer-env-request") {
@@ -2646,6 +3055,16 @@ function resolveCommandExecution(options, rest) {
2646
3055
  }
2647
3056
  throw new Error(`Unknown command: ${command}`);
2648
3057
  }
3058
+ async function followWithInterrupt(client, sessionId, options) {
3059
+ const controller = new AbortController();
3060
+ const detach = () => controller.abort();
3061
+ process.once("SIGINT", detach);
3062
+ try {
3063
+ return await followR5dctlSession(client, sessionId, { ...options, signal: controller.signal });
3064
+ } finally {
3065
+ process.removeListener("SIGINT", detach);
3066
+ }
3067
+ }
2649
3068
  async function runCommand(argv) {
2650
3069
  const { options, rest } = parseGlobalArgs(argv);
2651
3070
  if (options.version) {
@@ -2672,11 +3091,38 @@ async function runCommand(argv) {
2672
3091
  process.stdout.write(execution.text);
2673
3092
  return 0;
2674
3093
  }
2675
- if (execution.kind === "agent") {
2676
- const result = await dispatchBranchAgentCommand(client, execution.command);
3094
+ if (execution.kind === "session-run") {
3095
+ const result = await dispatchSessionRunCommand(client, execution.command);
3096
+ if (execution.command.kind !== "start" || !execution.command.follow) {
3097
+ writeDataOutput(options.json, result.data, result.human);
3098
+ return 0;
3099
+ }
3100
+ const sessionId = requireValue(responseString(result.data, "sessionId"), "Session start did not return a session id");
3101
+ if (options.json) process.stdout.write(`${JSON.stringify({ type: "session_started", data: result.data })}
3102
+ `);
3103
+ else process.stderr.write(result.human);
3104
+ return await followWithInterrupt(client, sessionId, {
3105
+ json: options.json,
3106
+ includeTools: execution.command.includeTools,
3107
+ printInitialTranscript: false
3108
+ });
3109
+ }
3110
+ if (execution.kind === "merge") {
3111
+ const result = await dispatchMergeCommand(client, execution.command);
2677
3112
  writeDataOutput(options.json, result.data, result.human);
2678
3113
  return 0;
2679
3114
  }
3115
+ if (execution.kind === "conversation-follow") {
3116
+ const { follow: _follow, ...renderOptions } = execution.renderOptions;
3117
+ const initialConversation = await client.sessions.conversation(execution.sessionId, renderOptions);
3118
+ return await followWithInterrupt(client, execution.sessionId, {
3119
+ json: options.json,
3120
+ includeTools: execution.renderOptions.includeTools,
3121
+ renderOptions,
3122
+ initialConversation,
3123
+ printInitialTranscript: true
3124
+ });
3125
+ }
2680
3126
  if (execution.kind === "shell") {
2681
3127
  const project = await client.projects.describe(options.project);
2682
3128
  return await runR5dctlShell({
@@ -2725,7 +3171,9 @@ async function main(argv = process.argv.slice(2)) {
2725
3171
  export {
2726
3172
  advanceTransientDevicePollBackoff,
2727
3173
  collectK8sUsage,
2728
- dispatchBranchAgentCommand,
3174
+ dispatchMergeCommand,
3175
+ dispatchSessionRunCommand,
3176
+ followR5dctlSession,
2729
3177
  formatK8sCpu,
2730
3178
  formatK8sMemory,
2731
3179
  formatProcessAge,
@@ -2736,8 +3184,9 @@ export {
2736
3184
  getTransientDevicePollDelay,
2737
3185
  handleAuthLogin,
2738
3186
  main,
3187
+ parseAnswerEnvRequestCommandArgs,
2739
3188
  parseAnswerFlags,
2740
- parseBranchAgentCommand,
3189
+ parseAnswerQuestionsCommandArgs,
2741
3190
  parseConversationRenderArgs,
2742
3191
  parseConversationWorkDetailArgs,
2743
3192
  parseEnvFlags,
@@ -2745,15 +3194,14 @@ export {
2745
3194
  parseGetEnvsArgs,
2746
3195
  parseGlobalArgs,
2747
3196
  parseK8sUsageArgs,
3197
+ parseMergeCommand,
2748
3198
  parsePromptArgs,
2749
3199
  parsePsHistoryArgs,
2750
3200
  parsePsListArgs,
3201
+ parseSessionRunCommand,
2751
3202
  parseSetEnvArgs,
2752
3203
  parseShellArgs,
2753
3204
  readDotenvFile,
2754
- renderBranchAgentMerge,
2755
- renderBranchAgentStart,
2756
- renderBranchAgentStatus,
2757
3205
  renderConversationNodeResponse,
2758
3206
  renderConversationOverviewResponse,
2759
3207
  renderConversationResponse,
@@ -2764,8 +3212,11 @@ export {
2764
3212
  renderProcessHistory,
2765
3213
  renderProcessInspection,
2766
3214
  renderProcessList,
3215
+ renderSessionRunStart,
3216
+ renderSessionRunStatus,
2767
3217
  renderWorkspaceStatus,
2768
3218
  renderWorkspaceSync,
3219
+ renderWorktreeMerge,
2769
3220
  resolveCommandExecution,
2770
3221
  runR5dctlCli,
2771
3222
  summarizeEnvData,