@ricsam/r5dctl 0.0.71 → 0.0.73

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/README.md CHANGED
@@ -71,10 +71,12 @@ r5dctl -s <session-id> conversation --system # include system prompt m
71
71
  r5dctl -s <session-id> conversation --raw --tools --system # exact raw agent request transcript
72
72
  r5dctl -s <session-id> conversation -f # print current transcript, then follow new output (--watch also works)
73
73
  r5dctl conversation overview <session-id> # dialogue/thinking with tool work omitted
74
+ r5dctl conversation overview <session-id> --skeleton # user messages, decisions, and per-turn work markers only
74
75
  r5dctl conversation inspect-node <session-id> <node-id> # raw persisted node plus traversal metadata
75
76
  r5dctl conversation inspect-work <session-id> <work-id> # compact call list for one omitted turn
76
77
  r5dctl conversation inspect-work <session-id> <work-id> --summary
77
- r5dctl conversation inspect-work <session-id> <work-id> --full
78
+ r5dctl conversation inspect-work <session-id> <work-id> --full --calls 12-20
79
+ r5dctl conversation inspect-work <session-id> <work-id> --include-polls
78
80
  r5dctl conversation move-head <session-id> <node-id> # rewind or replay the transcript; files stay put
79
81
  r5dctl conversation fork <session-id> <node-id> # copy the branch up to a node into a new session
80
82
  r5dctl conversation fork <session-id> <node-id> --name "Retry with tests"
@@ -91,6 +93,8 @@ r5dctl session stop <session-id>
91
93
  r5dctl -p <project> merge fix/tests
92
94
  r5dctl -p <project> merge --worker <label> --create-conflict-worktree fix/tests
93
95
  r5dctl -p <project> merge --worker <label> --launch-conflict-resolution-agent fix/tests
96
+ r5dctl -p <project> merge --worker <label> --launch-conflict-resolution-agent --no-wait fix/tests
97
+ r5dctl -p <project> merge status <attempt-id>
94
98
  r5dctl -p <project> merge finish --worker <label> conflicts/fix-tests --summary "Resolved the conflicts"
95
99
 
96
100
  r5dctl -p <project> -b <branch> shell
@@ -107,12 +111,16 @@ r5dctl k8s usage --window 7d
107
111
  r5dctl k8s usage --workload default/api --window 7d --json
108
112
  ```
109
113
 
110
- `session start` creates an ordinary agent-mode chat on an explicitly selected existing worktree, or creates a named worktree from an explicit `worktree:<branch>` source. It requires project, worker, model, and prompt and never infers them from the current shell. Starts and prompts return after durable enqueue while execution continues asynchronously. Task specialization belongs in the prompt; there are no predefined debug, explore, research, or test agents.
114
+ `session start` creates an ordinary agent-mode chat on an explicitly selected existing worktree, or creates a named worktree from an explicit `worktree:<branch>` source. It requires project, worker, model, and prompt and never infers them from the current shell. Starts and prompts return after durable enqueue while execution continues asynchronously. Task specialization belongs in the prompt; there are no predefined debug, explore, research, or test agents. `session stop` returns as soon as the agent run is stopped; the stopped worktree's workspace publication finishes asynchronously behind the response, and `merge` republishes at its own boundary before reading the worktree.
111
115
 
112
116
  Add `-f`/`--follow`/`--watch` to `session start` to attach after enqueue, or to `conversation` to print the current transcript and follow new output. Assistant text is written to stdout and lifecycle/tool progress to stderr; `--tools` includes tool calls and results. With global `--json`, follow mode emits one JSON event per line. Ctrl-C only detaches and never stops the session. The CLI reconnects transiently interrupted event streams and resynchronizes committed conversation revisions without repeating assistant text.
113
117
 
114
118
  `merge` merges a managed source worktree into its recorded parent. By default conflicts are reported without creating more work. `--create-conflict-worktree` prepares a manual conflict worktree, while `--launch-conflict-resolution-agent` starts the dedicated merge-conflict resolver; those strategies are mutually exclusive. A worker-bound shell credential supplies its own worker automatically; otherwise pass `--worker`. Finish a reviewed manual resolution with `merge finish`.
115
119
 
120
+ Merging is lockless and optimistic: nothing blocks agents from continuing to work on the involved branches. Every merge pins the branch tips it previewed and publishes through an atomic compare-and-swap, and a concurrent publication simply retries. When a resolver agent finishes after the target advanced, the platform automatically continues the merge — publishing directly if the refreshed merge is conflict-free, or launching a fresh resolver attempt (whose prompt references the superseded session's resolution) — repeating until one resolution publishes. Superseded attempts surface the follow-up attempt id in `merge status`. Only manual conflict worktrees stop at `stale`: re-run `merge` to start a fresh resolution.
121
+
122
+ The server answers merge requests as soon as the result is durably recorded; the slow per-member workspace propagation always completes asynchronously behind the response. For a clean merge that means the merged commit is already published when the command returns. For a conflict strategy the server responds with the merge attempt in a `provisioning` state and prepares the conflict worktree (and starts the resolver agent) in the background; the CLI polls the attempt until the worktree is ready — and, for the agent strategy, until the resolver agent is running — before printing the final result. Pass `--no-wait` to skip the wait and get the attempt id immediately, and use `merge status <attempt-id>` at any time to re-check a provisioning, failed, or completed attempt (a provisioning failure surfaces there as a `failed` attempt with its reason). Ctrl-C during the wait only detaches the CLI; provisioning continues server-side.
123
+
116
124
  `shell` connects to the same live worker PTY as the web Shell tab. Without `-c` it attaches the local terminal interactively. With `-c`/`--command`, it runs the supplied shell expression, streams combined PTY output, and exits with the remote command's status. Because command output uses a PTY, stdout and stderr are combined and programs may emit colors or other terminal control sequences.
117
125
 
118
126
  `ps list` shows active long-running commands managed by r5d. It does not expose the worker host's general OS process table or interactive PTYs. Global project, branch, and session options filter the list, while `--worker` selects a worker label. `ps stop` sends `SIGTERM` to the selected run. Inside an r5d-provided worker shell, the temporary credential can only list and stop runs on that shell's worker.
@@ -132,7 +140,11 @@ Conversation output is human-readable by default and excludes both the system pr
132
140
 
133
141
  When available, conversation rendering uses the newest complete persisted model-request snapshot on the active conversation branch, so its mode, tier, model, thinking mode, tools, and messages reflect the request that actually ran. Legacy or empty sessions without snapshots fall back to reconstruction from the latest conversation context.
134
142
 
135
- `conversation overview` is a deterministic active-branch projection for recovering earlier work after a fresh-context boundary. It keeps user messages, injected context, assistant thinking/commentary/final responses, and user decisions, while replacing each turn's tool batches with a stable work ID and numbered call ranges. Adjacent tool batches with no visible content between them are coalesced into one range. Use `conversation inspect-work` with that session and work ID to inspect the omitted calls. Inspection is compact by default; `--summary` adds bounded targets/outcomes and `--full` renders persisted inputs and results with the normal tool renderers. The explicit `--compact`, `--summary`, and `--full` flags are mutually exclusive. Compact and summary output are designed to be narrowed with `grep`, `head`, `tail`, `sed`, or `cat -n`.
143
+ `conversation overview` is a deterministic active-branch projection for recovering earlier work after a fresh-context boundary. It keeps user messages, injected context, assistant thinking/commentary/final responses, and user decisions, while replacing each turn's tool batches with a stable work ID and numbered call ranges. Adjacent tool batches with no visible content between them are coalesced into one range. `--skeleton` drops thinking and commentary too, keeping only dialogue, questions/answers, terminal responses, and one work marker per turn the fastest first read of a long session. When the last turn is unfinished, both views end with a `[Turn in progress: ...]` footer naming the pending state and last node, so "still running" is distinguishable from "ended without a response".
144
+
145
+ Use `conversation inspect-work` with that session and work ID to inspect the omitted calls. Inspection is compact by default; `--summary` adds bounded targets/outcomes and `--full` renders persisted inputs and results with the normal tool renderers. The explicit `--compact`, `--summary`, and `--full` flags are mutually exclusive. `--calls <n[-m]>` selects a server-side call range while keeping the original call numbers, so a `calls 83–90 omitted` marker in the overview can be inspected directly. Compact and summary output are designed to be narrowed with `grep`, `head`, `tail`, `sed`, or `cat -n`.
146
+
147
+ Persisted shell results are dispatch-time snapshots, so a long-running command's stored status can go stale. Rendering joins each unresolved run against the durable process-run store: summary rows gain `final_status=`/`final_exitCode=` next to the stale snapshot fields, `--full` appends a `## Final process outcomes` section with byte counts and output tails, and `shell_poll` calls whose run already finished are folded into one `[calls N–M folded: ...]` annotation carrying the final status. `--include-polls` restores the raw poll rows; call numbering never changes either way.
136
148
 
137
149
  `conversation inspect-node` reads one exact persisted conversation node as formatted JSON. Its header includes the previous node, every direct next node, the next node on the active branch, whether the selected node is active, and the containing `work_...` ID when one is available. This allows callers that start with only a copied chat node ID to traverse nearby nodes or zoom out with `conversation inspect-work`.
138
150
 
package/dist/cjs/cli.cjs CHANGED
@@ -42,13 +42,15 @@ __export(cli_exports, {
42
42
  getR5dctlVersion: () => getR5dctlVersion,
43
43
  getTransientDevicePollDelay: () => getTransientDevicePollDelay,
44
44
  handleAuthLogin: () => handleAuthLogin,
45
+ isMergeAttemptSettled: () => isMergeAttemptSettled,
45
46
  main: () => main,
46
47
  parseAnswerEnvRequestCommandArgs: () => parseAnswerEnvRequestCommandArgs,
47
48
  parseAnswerFlags: () => parseAnswerFlags,
48
49
  parseAnswerQuestionsCommandArgs: () => parseAnswerQuestionsCommandArgs,
49
50
  parseConversationForkArgs: () => parseConversationForkArgs,
51
+ parseConversationInspectWorkArgs: () => parseConversationInspectWorkArgs,
52
+ parseConversationOverviewArgs: () => parseConversationOverviewArgs,
50
53
  parseConversationRenderArgs: () => parseConversationRenderArgs,
51
- parseConversationWorkDetailArgs: () => parseConversationWorkDetailArgs,
52
54
  parseEnvFlags: () => parseEnvFlags,
53
55
  parseEnvRequestResponseArgs: () => parseEnvRequestResponseArgs,
54
56
  parseGetEnvsArgs: () => parseGetEnvsArgs,
@@ -95,16 +97,8 @@ var import_promises = require("node:timers/promises");
95
97
  var import_dotenv = require("dotenv");
96
98
  var import_r5d_api = require("@ricsam/r5d-api");
97
99
  var import_shell = require("./shell.cjs");
98
- const CHAT_MODES = /* @__PURE__ */ new Set([
99
- "ask",
100
- "plan",
101
- "build",
102
- "agent",
103
- "security_review",
104
- "large_diff_remediation",
105
- "merge_conflict_resolution"
106
- ]);
107
- const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "build", "agent"]);
100
+ const CHAT_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent", "large_diff_remediation", "merge_conflict_resolution"]);
101
+ const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent"]);
108
102
  const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
109
103
  const R5DCTL_PACKAGE_NAME = "@ricsam/r5dctl";
110
104
  const CLI_GLOBAL_OPTION_HELP = [
@@ -173,10 +167,12 @@ const SESSION_RUN_HELP_TEXT = [
173
167
  ].join("\n");
174
168
  const MERGE_HELP_TEXT = [
175
169
  "Usage:",
176
- " r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
170
+ " r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
171
+ " r5dctl -p <project> merge status <attempt-id>",
177
172
  ' r5dctl -p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
178
173
  "",
179
174
  "Merge a managed worktree into its recorded parent worktree.",
175
+ "Conflict worktrees provision in the background; merge waits by default and --no-wait returns the attempt immediately.",
180
176
  ""
181
177
  ].join("\n");
182
178
  const SHARED_HELP_ENTRIES = [
@@ -251,8 +247,8 @@ const SHARED_HELP_ENTRIES = [
251
247
  },
252
248
  {
253
249
  section: "sessions",
254
- usage: "conversation overview <session-id>",
255
- description: "Read dialogue and thinking while replacing tool work with inspectable ranges."
250
+ usage: "conversation overview <session-id> [--skeleton]",
251
+ description: "Read dialogue and thinking while replacing tool work with inspectable ranges; --skeleton keeps only dialogue, decisions, and per-turn work markers."
256
252
  },
257
253
  {
258
254
  section: "sessions",
@@ -261,8 +257,8 @@ const SHARED_HELP_ENTRIES = [
261
257
  },
262
258
  {
263
259
  section: "sessions",
264
- usage: "conversation inspect-work <session-id> <work-id> [--compact|--summary|--full]",
265
- description: "Inspect one turn's omitted tool work at the selected detail."
260
+ usage: "conversation inspect-work <session-id> <work-id> [--compact|--summary|--full] [--calls <n[-m]>] [--include-polls]",
261
+ description: "Inspect one turn's omitted tool work at the selected detail; finished shell polls are folded unless --include-polls."
266
262
  },
267
263
  {
268
264
  section: "sessions",
@@ -327,9 +323,14 @@ const SHARED_HELP_ENTRIES = [
327
323
  { section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
328
324
  {
329
325
  section: "merge",
330
- usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
326
+ usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
331
327
  description: "Merge a worktree into its recorded parent, with an explicit conflict strategy."
332
328
  },
329
+ {
330
+ section: "merge",
331
+ usage: "-p <project> merge status <attempt-id>",
332
+ description: "Show a merge attempt's provisioning and resolution status."
333
+ },
333
334
  {
334
335
  section: "merge",
335
336
  usage: '-p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
@@ -566,7 +567,7 @@ function requireModel(value) {
566
567
  function requireSessionMode(value) {
567
568
  const mode = requireValue(value?.trim(), "--mode <mode> is required");
568
569
  if (!R5DCTL_SESSION_MODES.has(mode)) {
569
- throw new Error("Invalid --mode. Expected one of: ask, plan, build, agent");
570
+ throw new Error("Invalid --mode. Expected one of: ask, plan, agent");
570
571
  }
571
572
  return mode;
572
573
  }
@@ -652,12 +653,19 @@ function parseMergeCommand(options, args) {
652
653
  ...parsed2.values.get("--summary")?.trim() ? { summary: parsed2.values.get("--summary").trim() } : {}
653
654
  };
654
655
  }
656
+ if (args[0] === "status") {
657
+ const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), "merge status");
658
+ const attemptId = requireValue(parsed2.positionals[0], "Attempt id is required for `merge status`");
659
+ if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge status argument: ${parsed2.positionals[1]}`);
660
+ return { kind: "status", project, attemptId };
661
+ }
655
662
  const parsed = parseCommandOperands(
656
663
  args,
657
664
  /* @__PURE__ */ new Set(["--worker"]),
658
665
  /* @__PURE__ */ new Map([
659
666
  ["--create-conflict-worktree", "--create-conflict-worktree"],
660
- ["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"]
667
+ ["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"],
668
+ ["--no-wait", "--no-wait"]
661
669
  ]),
662
670
  "merge"
663
671
  );
@@ -670,7 +678,14 @@ function parseMergeCommand(options, args) {
670
678
  }
671
679
  const conflictMode = createWorktree ? "worktree" : launchAgent ? "agent" : "none";
672
680
  const worker = parsed.values.get("--worker")?.trim();
673
- return { kind: "merge", project, sourceWorktree, conflictMode, ...worker ? { worker } : {} };
681
+ return {
682
+ kind: "merge",
683
+ project,
684
+ sourceWorktree,
685
+ conflictMode,
686
+ ...worker ? { worker } : {},
687
+ ...parsed.booleans.has("--no-wait") ? { noWait: true } : {}
688
+ };
674
689
  }
675
690
  function assertAuthLoginArgs(args) {
676
691
  const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
@@ -1958,17 +1973,22 @@ function renderSessionRunStatus(session) {
1958
1973
  function renderWorktreeMerge(result, input) {
1959
1974
  const lines = [];
1960
1975
  appendMergeDetails(lines, result);
1976
+ const status = responseString(result, "status");
1961
1977
  const conflictWorktree = responseString(result, "conflictWorktree");
1962
1978
  const resolverSessionId = responseString(result, "resolverSessionId");
1979
+ const attemptId = responseString(result, "attemptId");
1963
1980
  const worker = responseString(result, "worker") ?? input.worker;
1964
1981
  if (resolverSessionId) lines.push("", `Resolver session: ${resolverSessionId}`, ` r5dctl session stop ${resolverSessionId}`);
1965
- if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(responseString(result, "status") ?? "")) {
1982
+ if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(status ?? "")) {
1966
1983
  lines.push(
1967
1984
  "",
1968
1985
  "Finish after resolving and reviewing the conflict worktree:",
1969
1986
  ` r5dctl -p ${input.project} merge finish --worker ${worker} ${conflictWorktree} --summary "<summary>"`
1970
1987
  );
1971
1988
  }
1989
+ if (attemptId && (status === "provisioning" || status === "resolving" && result.resolverAgentRunning === false)) {
1990
+ lines.push("", "Check progress:", ` r5dctl -p ${input.project} merge status ${attemptId}`);
1991
+ }
1972
1992
  return `${lines.join("\n")}
1973
1993
  `;
1974
1994
  }
@@ -2008,13 +2028,64 @@ async function dispatchSessionRunCommand(client, command) {
2008
2028
  const data = await client.sessions.stop(command.sessionId);
2009
2029
  return { data, human: renderSessionRunStatus(data) };
2010
2030
  }
2011
- async function dispatchMergeCommand(client, command) {
2031
+ const MERGE_POLL_INTERVAL_MS = 2e3;
2032
+ const MERGE_POLL_TIMEOUT_MS = 20 * 6e4;
2033
+ const MERGE_POLL_MAX_CONSECUTIVE_FAILURES = 5;
2034
+ function isMergeAttemptSettled(response) {
2035
+ const status = responseString(response, "status");
2036
+ if (!status) return true;
2037
+ if (status === "resolving") return response.resolverAgentRunning !== false;
2038
+ return status !== "provisioning";
2039
+ }
2040
+ async function pollMergeAttemptUntilSettled(client, project, attemptId, initial, options) {
2041
+ const intervalMs = options.pollIntervalMs ?? MERGE_POLL_INTERVAL_MS;
2042
+ const timeoutMs = options.pollTimeoutMs ?? MERGE_POLL_TIMEOUT_MS;
2043
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2044
+ const onProgress = options.onProgress ?? (() => {
2045
+ });
2046
+ let latest = initial;
2047
+ let worktreeReadyReported = latest.worktreeReady === true;
2048
+ let consecutiveFailures = 0;
2049
+ let waitedMs = 0;
2050
+ while (!isMergeAttemptSettled(latest)) {
2051
+ if (waitedMs >= timeoutMs) {
2052
+ onProgress(`Still waiting after ${Math.round(timeoutMs / 6e4)} minutes; check later with: r5dctl -p ${project} merge status ${attemptId}`);
2053
+ return latest;
2054
+ }
2055
+ await sleep2(intervalMs);
2056
+ waitedMs += intervalMs;
2057
+ try {
2058
+ latest = await client.projects.worktrees.mergeAttempt(project, attemptId);
2059
+ consecutiveFailures = 0;
2060
+ } catch (error) {
2061
+ consecutiveFailures += 1;
2062
+ if (consecutiveFailures >= MERGE_POLL_MAX_CONSECUTIVE_FAILURES) throw error;
2063
+ continue;
2064
+ }
2065
+ if (!worktreeReadyReported && latest.worktreeReady === true) {
2066
+ worktreeReadyReported = true;
2067
+ onProgress("Conflict worktree ready.");
2068
+ }
2069
+ }
2070
+ return latest;
2071
+ }
2072
+ async function dispatchMergeCommand(client, command, pollOptions = {}) {
2012
2073
  if (command.kind === "merge") {
2013
2074
  const data2 = await client.projects.worktrees.merge(command.project, command.sourceWorktree, {
2014
2075
  conflictMode: command.conflictMode,
2015
2076
  ...command.worker ? { worker: command.worker } : {}
2016
2077
  });
2017
- return { data: data2, human: renderWorktreeMerge(data2, command) };
2078
+ const attemptId = responseString(data2, "attemptId");
2079
+ if (command.noWait || !attemptId || isMergeAttemptSettled(data2)) {
2080
+ return { data: data2, human: renderWorktreeMerge(data2, command) };
2081
+ }
2082
+ pollOptions.onProgress?.(`Provisioning the conflict worktree (attempt ${attemptId})...`);
2083
+ const settled = await pollMergeAttemptUntilSettled(client, command.project, attemptId, data2, pollOptions);
2084
+ return { data: settled, human: renderWorktreeMerge(settled, command) };
2085
+ }
2086
+ if (command.kind === "status") {
2087
+ const data2 = await client.projects.worktrees.mergeAttempt(command.project, command.attemptId);
2088
+ return { data: data2, human: renderWorktreeMerge(data2, { project: command.project }) };
2018
2089
  }
2019
2090
  const data = await client.projects.worktrees.finishMerge(command.project, command.conflictWorktree, {
2020
2091
  worker: command.worker,
@@ -2422,21 +2493,57 @@ async function followR5dctlSession(client, sessionId, options) {
2422
2493
  }
2423
2494
  return 0;
2424
2495
  }
2425
- function parseConversationWorkDetailArgs(args) {
2496
+ function parseConversationInspectWorkArgs(args) {
2426
2497
  let detail = "compact";
2427
2498
  let selected = false;
2428
- for (const arg of args) {
2499
+ let calls;
2500
+ let includePolls = false;
2501
+ for (let index = 0; index < args.length; index += 1) {
2502
+ const arg = args[index];
2429
2503
  const candidate = arg === "--compact" ? "compact" : arg === "--summary" ? "summary" : arg === "--full" ? "full" : void 0;
2430
- if (!candidate) {
2431
- throw new Error(`Unknown inspect-work argument: ${arg}`);
2504
+ if (candidate) {
2505
+ if (selected) {
2506
+ throw new Error("Use only one of --compact, --summary, or --full");
2507
+ }
2508
+ detail = candidate;
2509
+ selected = true;
2510
+ continue;
2432
2511
  }
2433
- if (selected) {
2434
- throw new Error("Use only one of --compact, --summary, or --full");
2512
+ if (arg === "--calls") {
2513
+ if (calls !== void 0) {
2514
+ throw new Error("Use --calls only once");
2515
+ }
2516
+ index += 1;
2517
+ const value = requireValue(args[index], "Missing value for --calls");
2518
+ if (!/^[0-9]+(-[0-9]+)?$/.test(value)) {
2519
+ throw new Error(`Invalid --calls range: ${value} (use N or N-M)`);
2520
+ }
2521
+ calls = value;
2522
+ continue;
2523
+ }
2524
+ if (arg === "--include-polls") {
2525
+ if (includePolls) {
2526
+ throw new Error("Use --include-polls only once");
2527
+ }
2528
+ includePolls = true;
2529
+ continue;
2530
+ }
2531
+ throw new Error(`Unknown inspect-work argument: ${arg}`);
2532
+ }
2533
+ return { detail, ...calls !== void 0 ? { calls } : {}, includePolls };
2534
+ }
2535
+ function parseConversationOverviewArgs(args) {
2536
+ let skeleton = false;
2537
+ for (const arg of args) {
2538
+ if (arg !== "--skeleton") {
2539
+ throw new Error(`Unknown conversation overview argument: ${arg}`);
2435
2540
  }
2436
- detail = candidate;
2437
- selected = true;
2541
+ if (skeleton) {
2542
+ throw new Error("Use --skeleton only once");
2543
+ }
2544
+ skeleton = true;
2438
2545
  }
2439
- return detail;
2546
+ return { skeleton };
2440
2547
  }
2441
2548
  function parseConversationForkArgs(args) {
2442
2549
  let name;
@@ -2665,10 +2772,9 @@ Sessions: ${result.sessions.length}
2665
2772
  return;
2666
2773
  }
2667
2774
  if (first === "conversation" && second === "overview") {
2668
- if (args.length > 3) {
2669
- throw new Error(`Unexpected conversation overview argument: ${args[3]}`);
2670
- }
2671
- const read = await client.sessions.conversationOverview(requireValue(args[2], "Missing session id"));
2775
+ const sessionId = requireValue(args[2], "Missing session id");
2776
+ const { skeleton } = parseConversationOverviewArgs(args.slice(3));
2777
+ const read = await client.sessions.conversationOverview(sessionId, skeleton ? { view: "skeleton" } : {});
2672
2778
  write(read, renderConversationOverviewResponse(read));
2673
2779
  return;
2674
2780
  }
@@ -2703,8 +2809,12 @@ Sessions: ${result.sessions.length}
2703
2809
  if (first === "conversation" && second === "inspect-work") {
2704
2810
  const sessionId = requireValue(args[2], "Missing session id");
2705
2811
  const workId = requireValue(args[3], "Missing work id");
2706
- const detail = parseConversationWorkDetailArgs(args.slice(4));
2707
- const read = await client.sessions.inspectConversationWork(sessionId, workId, { detail });
2812
+ const options = parseConversationInspectWorkArgs(args.slice(4));
2813
+ const read = await client.sessions.inspectConversationWork(sessionId, workId, {
2814
+ detail: options.detail,
2815
+ ...options.calls !== void 0 ? { calls: options.calls } : {},
2816
+ ...options.includePolls ? { polls: "include" } : {}
2817
+ });
2708
2818
  write(read, renderConversationWorkResponse(read));
2709
2819
  return;
2710
2820
  }
@@ -3220,6 +3330,14 @@ async function runCommand(argv) {
3220
3330
  return 0;
3221
3331
  }
3222
3332
  if (execution.kind === "session-run") {
3333
+ if (!options.json) {
3334
+ if (execution.command.kind === "start") {
3335
+ process.stderr.write("Starting session (publishing the workspace and provisioning the worktree)...\n");
3336
+ } else if (execution.command.kind === "stop") {
3337
+ process.stderr.write(`Stopping session ${execution.command.sessionId}...
3338
+ `);
3339
+ }
3340
+ }
3223
3341
  const result = await dispatchSessionRunCommand(client, execution.command);
3224
3342
  if (execution.command.kind !== "start" || !execution.command.follow) {
3225
3343
  writeDataOutput(options.json, result.data, result.human);
@@ -3236,7 +3354,18 @@ async function runCommand(argv) {
3236
3354
  });
3237
3355
  }
3238
3356
  if (execution.kind === "merge") {
3239
- const result = await dispatchMergeCommand(client, execution.command);
3357
+ if (!options.json) {
3358
+ if (execution.command.kind === "merge") {
3359
+ process.stderr.write("Merging (publishing the workspace and computing the merge)...\n");
3360
+ } else if (execution.command.kind === "finish") {
3361
+ process.stderr.write(`Finalizing ${execution.command.conflictWorktree} (publishing the resolver workspace)...
3362
+ `);
3363
+ }
3364
+ }
3365
+ const result = await dispatchMergeCommand(client, execution.command, {
3366
+ ...options.json ? {} : { onProgress: (line) => process.stderr.write(`${line}
3367
+ `) }
3368
+ });
3240
3369
  writeDataOutput(options.json, result.data, result.human);
3241
3370
  return 0;
3242
3371
  }
@@ -3312,13 +3441,15 @@ async function main(argv = process.argv.slice(2)) {
3312
3441
  getR5dctlVersion,
3313
3442
  getTransientDevicePollDelay,
3314
3443
  handleAuthLogin,
3444
+ isMergeAttemptSettled,
3315
3445
  main,
3316
3446
  parseAnswerEnvRequestCommandArgs,
3317
3447
  parseAnswerFlags,
3318
3448
  parseAnswerQuestionsCommandArgs,
3319
3449
  parseConversationForkArgs,
3450
+ parseConversationInspectWorkArgs,
3451
+ parseConversationOverviewArgs,
3320
3452
  parseConversationRenderArgs,
3321
- parseConversationWorkDetailArgs,
3322
3453
  parseEnvFlags,
3323
3454
  parseEnvRequestResponseArgs,
3324
3455
  parseGetEnvsArgs,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "type": "commonjs"
5
5
  }
package/dist/mjs/cli.mjs CHANGED
@@ -11,16 +11,8 @@ import {
11
11
  R5dctlClient
12
12
  } from "@ricsam/r5d-api";
13
13
  import { runR5dctlShell } from "./shell.mjs";
14
- const CHAT_MODES = /* @__PURE__ */ new Set([
15
- "ask",
16
- "plan",
17
- "build",
18
- "agent",
19
- "security_review",
20
- "large_diff_remediation",
21
- "merge_conflict_resolution"
22
- ]);
23
- const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "build", "agent"]);
14
+ const CHAT_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent", "large_diff_remediation", "merge_conflict_resolution"]);
15
+ const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent"]);
24
16
  const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
25
17
  const R5DCTL_PACKAGE_NAME = "@ricsam/r5dctl";
26
18
  const CLI_GLOBAL_OPTION_HELP = [
@@ -89,10 +81,12 @@ const SESSION_RUN_HELP_TEXT = [
89
81
  ].join("\n");
90
82
  const MERGE_HELP_TEXT = [
91
83
  "Usage:",
92
- " r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
84
+ " r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
85
+ " r5dctl -p <project> merge status <attempt-id>",
93
86
  ' r5dctl -p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
94
87
  "",
95
88
  "Merge a managed worktree into its recorded parent worktree.",
89
+ "Conflict worktrees provision in the background; merge waits by default and --no-wait returns the attempt immediately.",
96
90
  ""
97
91
  ].join("\n");
98
92
  const SHARED_HELP_ENTRIES = [
@@ -167,8 +161,8 @@ const SHARED_HELP_ENTRIES = [
167
161
  },
168
162
  {
169
163
  section: "sessions",
170
- usage: "conversation overview <session-id>",
171
- description: "Read dialogue and thinking while replacing tool work with inspectable ranges."
164
+ usage: "conversation overview <session-id> [--skeleton]",
165
+ description: "Read dialogue and thinking while replacing tool work with inspectable ranges; --skeleton keeps only dialogue, decisions, and per-turn work markers."
172
166
  },
173
167
  {
174
168
  section: "sessions",
@@ -177,8 +171,8 @@ const SHARED_HELP_ENTRIES = [
177
171
  },
178
172
  {
179
173
  section: "sessions",
180
- usage: "conversation inspect-work <session-id> <work-id> [--compact|--summary|--full]",
181
- description: "Inspect one turn's omitted tool work at the selected detail."
174
+ usage: "conversation inspect-work <session-id> <work-id> [--compact|--summary|--full] [--calls <n[-m]>] [--include-polls]",
175
+ description: "Inspect one turn's omitted tool work at the selected detail; finished shell polls are folded unless --include-polls."
182
176
  },
183
177
  {
184
178
  section: "sessions",
@@ -243,9 +237,14 @@ const SHARED_HELP_ENTRIES = [
243
237
  { section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
244
238
  {
245
239
  section: "merge",
246
- usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
240
+ usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
247
241
  description: "Merge a worktree into its recorded parent, with an explicit conflict strategy."
248
242
  },
243
+ {
244
+ section: "merge",
245
+ usage: "-p <project> merge status <attempt-id>",
246
+ description: "Show a merge attempt's provisioning and resolution status."
247
+ },
249
248
  {
250
249
  section: "merge",
251
250
  usage: '-p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
@@ -482,7 +481,7 @@ function requireModel(value) {
482
481
  function requireSessionMode(value) {
483
482
  const mode = requireValue(value?.trim(), "--mode <mode> is required");
484
483
  if (!R5DCTL_SESSION_MODES.has(mode)) {
485
- throw new Error("Invalid --mode. Expected one of: ask, plan, build, agent");
484
+ throw new Error("Invalid --mode. Expected one of: ask, plan, agent");
486
485
  }
487
486
  return mode;
488
487
  }
@@ -568,12 +567,19 @@ function parseMergeCommand(options, args) {
568
567
  ...parsed2.values.get("--summary")?.trim() ? { summary: parsed2.values.get("--summary").trim() } : {}
569
568
  };
570
569
  }
570
+ if (args[0] === "status") {
571
+ const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), "merge status");
572
+ const attemptId = requireValue(parsed2.positionals[0], "Attempt id is required for `merge status`");
573
+ if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge status argument: ${parsed2.positionals[1]}`);
574
+ return { kind: "status", project, attemptId };
575
+ }
571
576
  const parsed = parseCommandOperands(
572
577
  args,
573
578
  /* @__PURE__ */ new Set(["--worker"]),
574
579
  /* @__PURE__ */ new Map([
575
580
  ["--create-conflict-worktree", "--create-conflict-worktree"],
576
- ["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"]
581
+ ["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"],
582
+ ["--no-wait", "--no-wait"]
577
583
  ]),
578
584
  "merge"
579
585
  );
@@ -586,7 +592,14 @@ function parseMergeCommand(options, args) {
586
592
  }
587
593
  const conflictMode = createWorktree ? "worktree" : launchAgent ? "agent" : "none";
588
594
  const worker = parsed.values.get("--worker")?.trim();
589
- return { kind: "merge", project, sourceWorktree, conflictMode, ...worker ? { worker } : {} };
595
+ return {
596
+ kind: "merge",
597
+ project,
598
+ sourceWorktree,
599
+ conflictMode,
600
+ ...worker ? { worker } : {},
601
+ ...parsed.booleans.has("--no-wait") ? { noWait: true } : {}
602
+ };
590
603
  }
591
604
  function assertAuthLoginArgs(args) {
592
605
  const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
@@ -1874,17 +1887,22 @@ function renderSessionRunStatus(session) {
1874
1887
  function renderWorktreeMerge(result, input) {
1875
1888
  const lines = [];
1876
1889
  appendMergeDetails(lines, result);
1890
+ const status = responseString(result, "status");
1877
1891
  const conflictWorktree = responseString(result, "conflictWorktree");
1878
1892
  const resolverSessionId = responseString(result, "resolverSessionId");
1893
+ const attemptId = responseString(result, "attemptId");
1879
1894
  const worker = responseString(result, "worker") ?? input.worker;
1880
1895
  if (resolverSessionId) lines.push("", `Resolver session: ${resolverSessionId}`, ` r5dctl session stop ${resolverSessionId}`);
1881
- if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(responseString(result, "status") ?? "")) {
1896
+ if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(status ?? "")) {
1882
1897
  lines.push(
1883
1898
  "",
1884
1899
  "Finish after resolving and reviewing the conflict worktree:",
1885
1900
  ` r5dctl -p ${input.project} merge finish --worker ${worker} ${conflictWorktree} --summary "<summary>"`
1886
1901
  );
1887
1902
  }
1903
+ if (attemptId && (status === "provisioning" || status === "resolving" && result.resolverAgentRunning === false)) {
1904
+ lines.push("", "Check progress:", ` r5dctl -p ${input.project} merge status ${attemptId}`);
1905
+ }
1888
1906
  return `${lines.join("\n")}
1889
1907
  `;
1890
1908
  }
@@ -1924,13 +1942,64 @@ async function dispatchSessionRunCommand(client, command) {
1924
1942
  const data = await client.sessions.stop(command.sessionId);
1925
1943
  return { data, human: renderSessionRunStatus(data) };
1926
1944
  }
1927
- async function dispatchMergeCommand(client, command) {
1945
+ const MERGE_POLL_INTERVAL_MS = 2e3;
1946
+ const MERGE_POLL_TIMEOUT_MS = 20 * 6e4;
1947
+ const MERGE_POLL_MAX_CONSECUTIVE_FAILURES = 5;
1948
+ function isMergeAttemptSettled(response) {
1949
+ const status = responseString(response, "status");
1950
+ if (!status) return true;
1951
+ if (status === "resolving") return response.resolverAgentRunning !== false;
1952
+ return status !== "provisioning";
1953
+ }
1954
+ async function pollMergeAttemptUntilSettled(client, project, attemptId, initial, options) {
1955
+ const intervalMs = options.pollIntervalMs ?? MERGE_POLL_INTERVAL_MS;
1956
+ const timeoutMs = options.pollTimeoutMs ?? MERGE_POLL_TIMEOUT_MS;
1957
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1958
+ const onProgress = options.onProgress ?? (() => {
1959
+ });
1960
+ let latest = initial;
1961
+ let worktreeReadyReported = latest.worktreeReady === true;
1962
+ let consecutiveFailures = 0;
1963
+ let waitedMs = 0;
1964
+ while (!isMergeAttemptSettled(latest)) {
1965
+ if (waitedMs >= timeoutMs) {
1966
+ onProgress(`Still waiting after ${Math.round(timeoutMs / 6e4)} minutes; check later with: r5dctl -p ${project} merge status ${attemptId}`);
1967
+ return latest;
1968
+ }
1969
+ await sleep2(intervalMs);
1970
+ waitedMs += intervalMs;
1971
+ try {
1972
+ latest = await client.projects.worktrees.mergeAttempt(project, attemptId);
1973
+ consecutiveFailures = 0;
1974
+ } catch (error) {
1975
+ consecutiveFailures += 1;
1976
+ if (consecutiveFailures >= MERGE_POLL_MAX_CONSECUTIVE_FAILURES) throw error;
1977
+ continue;
1978
+ }
1979
+ if (!worktreeReadyReported && latest.worktreeReady === true) {
1980
+ worktreeReadyReported = true;
1981
+ onProgress("Conflict worktree ready.");
1982
+ }
1983
+ }
1984
+ return latest;
1985
+ }
1986
+ async function dispatchMergeCommand(client, command, pollOptions = {}) {
1928
1987
  if (command.kind === "merge") {
1929
1988
  const data2 = await client.projects.worktrees.merge(command.project, command.sourceWorktree, {
1930
1989
  conflictMode: command.conflictMode,
1931
1990
  ...command.worker ? { worker: command.worker } : {}
1932
1991
  });
1933
- return { data: data2, human: renderWorktreeMerge(data2, command) };
1992
+ const attemptId = responseString(data2, "attemptId");
1993
+ if (command.noWait || !attemptId || isMergeAttemptSettled(data2)) {
1994
+ return { data: data2, human: renderWorktreeMerge(data2, command) };
1995
+ }
1996
+ pollOptions.onProgress?.(`Provisioning the conflict worktree (attempt ${attemptId})...`);
1997
+ const settled = await pollMergeAttemptUntilSettled(client, command.project, attemptId, data2, pollOptions);
1998
+ return { data: settled, human: renderWorktreeMerge(settled, command) };
1999
+ }
2000
+ if (command.kind === "status") {
2001
+ const data2 = await client.projects.worktrees.mergeAttempt(command.project, command.attemptId);
2002
+ return { data: data2, human: renderWorktreeMerge(data2, { project: command.project }) };
1934
2003
  }
1935
2004
  const data = await client.projects.worktrees.finishMerge(command.project, command.conflictWorktree, {
1936
2005
  worker: command.worker,
@@ -2338,21 +2407,57 @@ async function followR5dctlSession(client, sessionId, options) {
2338
2407
  }
2339
2408
  return 0;
2340
2409
  }
2341
- function parseConversationWorkDetailArgs(args) {
2410
+ function parseConversationInspectWorkArgs(args) {
2342
2411
  let detail = "compact";
2343
2412
  let selected = false;
2344
- for (const arg of args) {
2413
+ let calls;
2414
+ let includePolls = false;
2415
+ for (let index = 0; index < args.length; index += 1) {
2416
+ const arg = args[index];
2345
2417
  const candidate = arg === "--compact" ? "compact" : arg === "--summary" ? "summary" : arg === "--full" ? "full" : void 0;
2346
- if (!candidate) {
2347
- throw new Error(`Unknown inspect-work argument: ${arg}`);
2418
+ if (candidate) {
2419
+ if (selected) {
2420
+ throw new Error("Use only one of --compact, --summary, or --full");
2421
+ }
2422
+ detail = candidate;
2423
+ selected = true;
2424
+ continue;
2348
2425
  }
2349
- if (selected) {
2350
- throw new Error("Use only one of --compact, --summary, or --full");
2426
+ if (arg === "--calls") {
2427
+ if (calls !== void 0) {
2428
+ throw new Error("Use --calls only once");
2429
+ }
2430
+ index += 1;
2431
+ const value = requireValue(args[index], "Missing value for --calls");
2432
+ if (!/^[0-9]+(-[0-9]+)?$/.test(value)) {
2433
+ throw new Error(`Invalid --calls range: ${value} (use N or N-M)`);
2434
+ }
2435
+ calls = value;
2436
+ continue;
2437
+ }
2438
+ if (arg === "--include-polls") {
2439
+ if (includePolls) {
2440
+ throw new Error("Use --include-polls only once");
2441
+ }
2442
+ includePolls = true;
2443
+ continue;
2444
+ }
2445
+ throw new Error(`Unknown inspect-work argument: ${arg}`);
2446
+ }
2447
+ return { detail, ...calls !== void 0 ? { calls } : {}, includePolls };
2448
+ }
2449
+ function parseConversationOverviewArgs(args) {
2450
+ let skeleton = false;
2451
+ for (const arg of args) {
2452
+ if (arg !== "--skeleton") {
2453
+ throw new Error(`Unknown conversation overview argument: ${arg}`);
2351
2454
  }
2352
- detail = candidate;
2353
- selected = true;
2455
+ if (skeleton) {
2456
+ throw new Error("Use --skeleton only once");
2457
+ }
2458
+ skeleton = true;
2354
2459
  }
2355
- return detail;
2460
+ return { skeleton };
2356
2461
  }
2357
2462
  function parseConversationForkArgs(args) {
2358
2463
  let name;
@@ -2581,10 +2686,9 @@ Sessions: ${result.sessions.length}
2581
2686
  return;
2582
2687
  }
2583
2688
  if (first === "conversation" && second === "overview") {
2584
- if (args.length > 3) {
2585
- throw new Error(`Unexpected conversation overview argument: ${args[3]}`);
2586
- }
2587
- const read = await client.sessions.conversationOverview(requireValue(args[2], "Missing session id"));
2689
+ const sessionId = requireValue(args[2], "Missing session id");
2690
+ const { skeleton } = parseConversationOverviewArgs(args.slice(3));
2691
+ const read = await client.sessions.conversationOverview(sessionId, skeleton ? { view: "skeleton" } : {});
2588
2692
  write(read, renderConversationOverviewResponse(read));
2589
2693
  return;
2590
2694
  }
@@ -2619,8 +2723,12 @@ Sessions: ${result.sessions.length}
2619
2723
  if (first === "conversation" && second === "inspect-work") {
2620
2724
  const sessionId = requireValue(args[2], "Missing session id");
2621
2725
  const workId = requireValue(args[3], "Missing work id");
2622
- const detail = parseConversationWorkDetailArgs(args.slice(4));
2623
- const read = await client.sessions.inspectConversationWork(sessionId, workId, { detail });
2726
+ const options = parseConversationInspectWorkArgs(args.slice(4));
2727
+ const read = await client.sessions.inspectConversationWork(sessionId, workId, {
2728
+ detail: options.detail,
2729
+ ...options.calls !== void 0 ? { calls: options.calls } : {},
2730
+ ...options.includePolls ? { polls: "include" } : {}
2731
+ });
2624
2732
  write(read, renderConversationWorkResponse(read));
2625
2733
  return;
2626
2734
  }
@@ -3136,6 +3244,14 @@ async function runCommand(argv) {
3136
3244
  return 0;
3137
3245
  }
3138
3246
  if (execution.kind === "session-run") {
3247
+ if (!options.json) {
3248
+ if (execution.command.kind === "start") {
3249
+ process.stderr.write("Starting session (publishing the workspace and provisioning the worktree)...\n");
3250
+ } else if (execution.command.kind === "stop") {
3251
+ process.stderr.write(`Stopping session ${execution.command.sessionId}...
3252
+ `);
3253
+ }
3254
+ }
3139
3255
  const result = await dispatchSessionRunCommand(client, execution.command);
3140
3256
  if (execution.command.kind !== "start" || !execution.command.follow) {
3141
3257
  writeDataOutput(options.json, result.data, result.human);
@@ -3152,7 +3268,18 @@ async function runCommand(argv) {
3152
3268
  });
3153
3269
  }
3154
3270
  if (execution.kind === "merge") {
3155
- const result = await dispatchMergeCommand(client, execution.command);
3271
+ if (!options.json) {
3272
+ if (execution.command.kind === "merge") {
3273
+ process.stderr.write("Merging (publishing the workspace and computing the merge)...\n");
3274
+ } else if (execution.command.kind === "finish") {
3275
+ process.stderr.write(`Finalizing ${execution.command.conflictWorktree} (publishing the resolver workspace)...
3276
+ `);
3277
+ }
3278
+ }
3279
+ const result = await dispatchMergeCommand(client, execution.command, {
3280
+ ...options.json ? {} : { onProgress: (line) => process.stderr.write(`${line}
3281
+ `) }
3282
+ });
3156
3283
  writeDataOutput(options.json, result.data, result.human);
3157
3284
  return 0;
3158
3285
  }
@@ -3227,13 +3354,15 @@ export {
3227
3354
  getR5dctlVersion,
3228
3355
  getTransientDevicePollDelay,
3229
3356
  handleAuthLogin,
3357
+ isMergeAttemptSettled,
3230
3358
  main,
3231
3359
  parseAnswerEnvRequestCommandArgs,
3232
3360
  parseAnswerFlags,
3233
3361
  parseAnswerQuestionsCommandArgs,
3234
3362
  parseConversationForkArgs,
3363
+ parseConversationInspectWorkArgs,
3364
+ parseConversationOverviewArgs,
3235
3365
  parseConversationRenderArgs,
3236
- parseConversationWorkDetailArgs,
3237
3366
  parseEnvFlags,
3238
3367
  parseEnvRequestResponseArgs,
3239
3368
  parseGetEnvsArgs,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "type": "module"
5
5
  }
@@ -85,6 +85,11 @@ export type R5dctlMergeCommand = {
85
85
  sourceWorktree: string;
86
86
  worker?: string;
87
87
  conflictMode: R5dctlMergeConflictMode;
88
+ noWait?: boolean;
89
+ } | {
90
+ kind: "status";
91
+ project: string;
92
+ attemptId: string;
88
93
  } | {
89
94
  kind: "finish";
90
95
  project: string;
@@ -229,6 +234,8 @@ export type R5dctlCommandResponse = {
229
234
  conflictedFiles?: string[];
230
235
  failureReason?: string;
231
236
  message?: string;
237
+ worktreeReady?: boolean;
238
+ resolverAgentRunning?: boolean;
232
239
  };
233
240
  export type R5dctlSessionRunClient = {
234
241
  projects: {
@@ -259,6 +266,7 @@ export type R5dctlMergeClient = {
259
266
  worker: string;
260
267
  summary?: string;
261
268
  }) => Promise<R5dctlCommandResponse>;
269
+ mergeAttempt: (projectRef: string, attemptId: string) => Promise<R5dctlCommandResponse>;
262
270
  };
263
271
  };
264
272
  };
@@ -273,7 +281,21 @@ export type R5dctlCommandDispatchResult = {
273
281
  human: string;
274
282
  };
275
283
  export declare function dispatchSessionRunCommand(client: R5dctlSessionRunClient, command: R5dctlSessionRunCommand): Promise<R5dctlCommandDispatchResult>;
276
- export declare function dispatchMergeCommand(client: R5dctlMergeClient, command: R5dctlMergeCommand): Promise<R5dctlCommandDispatchResult>;
284
+ export type R5dctlMergePollOptions = {
285
+ onProgress?: (line: string) => void;
286
+ pollIntervalMs?: number;
287
+ pollTimeoutMs?: number;
288
+ sleep?: (ms: number) => Promise<void>;
289
+ };
290
+ /**
291
+ * Whether a merge attempt response is settled for the CLI: terminal, waiting
292
+ * on the user (a ready manual conflict worktree), or actively resolving with
293
+ * its agent running. Anything else is still provisioning or starting. A
294
+ * "resolving" response without the resolverAgentRunning field comes from a
295
+ * server that starts the agent in-request, so it counts as settled.
296
+ */
297
+ export declare function isMergeAttemptSettled(response: R5dctlCommandResponse): boolean;
298
+ export declare function dispatchMergeCommand(client: R5dctlMergeClient, command: R5dctlMergeCommand, pollOptions?: R5dctlMergePollOptions): Promise<R5dctlCommandDispatchResult>;
277
299
  export declare function parseConversationRenderArgs(args: string[]): ConversationRenderOptions;
278
300
  export declare function renderConversationResponse(read: R5dctlConversationResponse): string;
279
301
  type R5dctlFollowClient = {
@@ -298,7 +320,15 @@ export type R5dctlFollowOptions = {
298
320
  maxReconnects?: number;
299
321
  };
300
322
  export declare function followR5dctlSession(client: R5dctlFollowClient, sessionId: string, options: R5dctlFollowOptions): Promise<number>;
301
- export declare function parseConversationWorkDetailArgs(args: string[]): R5dctlConversationWorkDetail;
323
+ export type ConversationInspectWorkOptions = {
324
+ detail: R5dctlConversationWorkDetail;
325
+ calls?: string;
326
+ includePolls: boolean;
327
+ };
328
+ export declare function parseConversationInspectWorkArgs(args: string[]): ConversationInspectWorkOptions;
329
+ export declare function parseConversationOverviewArgs(args: string[]): {
330
+ skeleton: boolean;
331
+ };
302
332
  export declare function parseConversationForkArgs(args: string[]): string | undefined;
303
333
  export declare function renderConversationOverviewResponse(read: R5dctlConversationOverviewResponse): string;
304
334
  export declare function renderConversationNodeResponse(read: R5dctlConversationNodeResponse): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.71",
3
+ "version": "0.0.73",
4
4
  "type": "module",
5
5
  "main": "./dist/cjs/cli.cjs",
6
6
  "module": "./dist/mjs/cli.mjs",
@@ -26,7 +26,7 @@
26
26
  "r5dctl": "dist/cjs/main.cjs"
27
27
  },
28
28
  "dependencies": {
29
- "@ricsam/r5d-api": "^0.0.71",
29
+ "@ricsam/r5d-api": "^0.0.73",
30
30
  "dotenv": "^17",
31
31
  "qrcode": "^1.5.4",
32
32
  "ws": "^8.18.3"