@ricsam/r5dctl 0.0.74 → 0.0.76

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
@@ -90,13 +90,6 @@ r5dctl session status <session-id>
90
90
  r5dctl session prompt --worker <label> --mode agent --model max <session-id> "Also check the migration"
91
91
  r5dctl session stop <session-id>
92
92
 
93
- r5dctl -p <project> merge fix/tests
94
- r5dctl -p <project> merge --worker <label> --create-conflict-worktree fix/tests
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>
98
- r5dctl -p <project> merge finish --worker <label> conflicts/fix-tests --summary "Resolved the conflicts"
99
-
100
93
  r5dctl -p <project> -b <branch> shell
101
94
  r5dctl -p <project> -b <branch> shell -c "bun test"
102
95
 
@@ -121,15 +114,9 @@ repository. Trust applies to that workflow path on every Git ref and grants
121
114
  cluster-admin, so protect workflow changes and do not invoke it for untrusted
122
115
  pull requests.
123
116
 
124
- `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.
125
-
126
- 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.
127
-
128
- `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`.
129
-
130
- 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.
117
+ `session start` creates an ordinary agent-mode chat on an explicitly selected existing worktree, or creates a named linked worktree from an explicit `worktree:<branch>` source. It requires project, worker, model, and prompt and never infers them from the current shell. Start returns a durable `provisioning` handle immediately; workspace synchronization, linked-worktree creation, and prompt enqueue continue in the background and are visible through `session status`. Idempotent request retries return the same handle. Prompts return after durable enqueue. 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 Git synchronization finishes asynchronously behind the response.
131
118
 
132
- 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.
119
+ Add `-f`/`--follow`/`--watch` to `session start` to poll the provisioning handle and 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.
133
120
 
134
121
  `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.
135
122
 
package/dist/cjs/cli.cjs CHANGED
@@ -30,7 +30,6 @@ var cli_exports = {};
30
30
  __export(cli_exports, {
31
31
  advanceTransientDevicePollBackoff: () => advanceTransientDevicePollBackoff,
32
32
  collectK8sUsage: () => collectK8sUsage,
33
- dispatchMergeCommand: () => dispatchMergeCommand,
34
33
  dispatchSessionRunCommand: () => dispatchSessionRunCommand,
35
34
  followR5dctlSession: () => followR5dctlSession,
36
35
  formatK8sCpu: () => formatK8sCpu,
@@ -42,7 +41,6 @@ __export(cli_exports, {
42
41
  getR5dctlVersion: () => getR5dctlVersion,
43
42
  getTransientDevicePollDelay: () => getTransientDevicePollDelay,
44
43
  handleAuthLogin: () => handleAuthLogin,
45
- isMergeAttemptSettled: () => isMergeAttemptSettled,
46
44
  main: () => main,
47
45
  parseAnswerEnvRequestCommandArgs: () => parseAnswerEnvRequestCommandArgs,
48
46
  parseAnswerFlags: () => parseAnswerFlags,
@@ -56,7 +54,6 @@ __export(cli_exports, {
56
54
  parseGetEnvsArgs: () => parseGetEnvsArgs,
57
55
  parseGlobalArgs: () => parseGlobalArgs,
58
56
  parseK8sUsageArgs: () => parseK8sUsageArgs,
59
- parseMergeCommand: () => parseMergeCommand,
60
57
  parsePromptArgs: () => parsePromptArgs,
61
58
  parsePsHistoryArgs: () => parsePsHistoryArgs,
62
59
  parsePsListArgs: () => parsePsListArgs,
@@ -80,10 +77,10 @@ __export(cli_exports, {
80
77
  renderSessionRunStatus: () => renderSessionRunStatus,
81
78
  renderWorkspaceStatus: () => renderWorkspaceStatus,
82
79
  renderWorkspaceSync: () => renderWorkspaceSync,
83
- renderWorktreeMerge: () => renderWorktreeMerge,
84
80
  resolveCommandExecution: () => resolveCommandExecution,
85
81
  runR5dctlCli: () => runR5dctlCli,
86
82
  summarizeEnvData: () => summarizeEnvData,
83
+ waitForSessionProvisioning: () => waitForSessionProvisioning,
87
84
  writeConfig: () => writeConfig
88
85
  });
89
86
  module.exports = __toCommonJS(cli_exports);
@@ -97,7 +94,7 @@ var import_promises = require("node:timers/promises");
97
94
  var import_dotenv = require("dotenv");
98
95
  var import_r5d_api = require("@ricsam/r5d-api");
99
96
  var import_shell = require("./shell.cjs");
100
- const CHAT_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent", "large_diff_remediation", "merge_conflict_resolution"]);
97
+ const CHAT_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent", "large_diff_remediation"]);
101
98
  const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent"]);
102
99
  const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
103
100
  const R5DCTL_PACKAGE_NAME = "@ricsam/r5dctl";
@@ -168,16 +165,6 @@ const SESSION_RUN_HELP_TEXT = [
168
165
  "Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
169
166
  ""
170
167
  ].join("\n");
171
- const MERGE_HELP_TEXT = [
172
- "Usage:",
173
- " r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
174
- " r5dctl -p <project> merge status <attempt-id>",
175
- ' r5dctl -p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
176
- "",
177
- "Merge a managed worktree into its recorded parent worktree.",
178
- "Conflict worktrees provision in the background; merge waits by default and --no-wait returns the attempt immediately.",
179
- ""
180
- ].join("\n");
181
168
  const SHARED_HELP_ENTRIES = [
182
169
  { section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
183
170
  { section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
@@ -188,16 +175,16 @@ const SHARED_HELP_ENTRIES = [
188
175
  },
189
176
  { section: "auth", usage: "auth api-key list", description: "List API keys for the current account." },
190
177
  { section: "auth", usage: "auth api-key revoke <key-id>", description: "Revoke an API key." },
191
- { section: "workspace", usage: "workspace status", description: "Inspect the per-user canonical workspace and active remediation." },
178
+ { section: "workspace", usage: "workspace status", description: "Inspect per-user workspace Git state and active remediation." },
192
179
  {
193
180
  section: "workspace",
194
181
  usage: "workspace sync [--worker <label>]",
195
- description: "Publish a connected worker into the canonical workspace now."
182
+ description: "Commit, rebase, and push a connected worker's workspace now."
196
183
  },
197
184
  {
198
185
  section: "workspace",
199
186
  usage: "workspace reset [--worker <label>] --confirm",
200
- description: "Destructively discard a blocked remediation candidate and restore canonical state."
187
+ description: "Destructively discard a blocked local workspace commit and restore origin/main."
201
188
  },
202
189
  { section: "projects", usage: "get projects", description: "List projects you can access." },
203
190
  { section: "projects", usage: "describe project <namespace/name|id>", description: "Show details for a project." },
@@ -334,22 +321,7 @@ const SHARED_HELP_ENTRIES = [
334
321
  usage: "ps inspect <run-id>",
335
322
  description: "Inspect process metadata and bounded stdout/stderr tails."
336
323
  },
337
- { section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
338
- {
339
- section: "merge",
340
- usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
341
- description: "Merge a worktree into its recorded parent, with an explicit conflict strategy."
342
- },
343
- {
344
- section: "merge",
345
- usage: "-p <project> merge status <attempt-id>",
346
- description: "Show a merge attempt's provisioning and resolution status."
347
- },
348
- {
349
- section: "merge",
350
- usage: '-p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
351
- description: "Publish and finish a manually resolved conflict worktree."
352
- }
324
+ { section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." }
353
325
  ];
354
326
  const HELP_SECTION_ORDER = [
355
327
  "auth",
@@ -360,8 +332,7 @@ const HELP_SECTION_ORDER = [
360
332
  "sessions",
361
333
  "kubernetes",
362
334
  "processes",
363
- "shell",
364
- "merge"
335
+ "shell"
365
336
  ];
366
337
  const HELP_SECTION_TITLES = {
367
338
  auth: "Auth",
@@ -372,8 +343,7 @@ const HELP_SECTION_TITLES = {
372
343
  sessions: "Sessions",
373
344
  kubernetes: "Kubernetes",
374
345
  processes: "Processes",
375
- shell: "Shell",
376
- merge: "Worktree merging"
346
+ shell: "Shell"
377
347
  };
378
348
  function getCliHelpText() {
379
349
  const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
@@ -651,56 +621,6 @@ function parseSessionRunCommand(options, args) {
651
621
  }
652
622
  throw new Error(`Unknown session command: ${subcommand}`);
653
623
  }
654
- function parseMergeCommand(options, args) {
655
- const project = requireValue(options.project, "--project/-p is required for `merge`");
656
- if (options.branch) throw new Error("--branch/-b is not supported for `merge`; pass the source worktree explicitly");
657
- if (options.session) throw new Error("--session/-s is not supported for `merge`");
658
- if (args[0] === "finish") {
659
- const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(["--worker", "--summary"]), /* @__PURE__ */ new Map(), "merge finish");
660
- const conflictWorktree = requireValue(parsed2.positionals[0], "Conflict worktree is required for `merge finish`");
661
- if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge finish argument: ${parsed2.positionals[1]}`);
662
- return {
663
- kind: "finish",
664
- project,
665
- conflictWorktree,
666
- worker: requireWorker(parsed2.values),
667
- ...parsed2.values.get("--summary")?.trim() ? { summary: parsed2.values.get("--summary").trim() } : {}
668
- };
669
- }
670
- if (args[0] === "status") {
671
- const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), "merge status");
672
- const attemptId = requireValue(parsed2.positionals[0], "Attempt id is required for `merge status`");
673
- if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge status argument: ${parsed2.positionals[1]}`);
674
- return { kind: "status", project, attemptId };
675
- }
676
- const parsed = parseCommandOperands(
677
- args,
678
- /* @__PURE__ */ new Set(["--worker"]),
679
- /* @__PURE__ */ new Map([
680
- ["--create-conflict-worktree", "--create-conflict-worktree"],
681
- ["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"],
682
- ["--no-wait", "--no-wait"]
683
- ]),
684
- "merge"
685
- );
686
- const sourceWorktree = requireValue(parsed.positionals[0], "Source worktree is required for `merge`");
687
- if (parsed.positionals.length > 1) throw new Error(`Unexpected merge argument: ${parsed.positionals[1]}`);
688
- const createWorktree = parsed.booleans.has("--create-conflict-worktree");
689
- const launchAgent = parsed.booleans.has("--launch-conflict-resolution-agent");
690
- if (createWorktree && launchAgent) {
691
- throw new Error("Use only one of --create-conflict-worktree or --launch-conflict-resolution-agent");
692
- }
693
- const conflictMode = createWorktree ? "worktree" : launchAgent ? "agent" : "none";
694
- const worker = parsed.values.get("--worker")?.trim();
695
- return {
696
- kind: "merge",
697
- project,
698
- sourceWorktree,
699
- conflictMode,
700
- ...worker ? { worker } : {},
701
- ...parsed.booleans.has("--no-wait") ? { noWait: true } : {}
702
- };
703
- }
704
624
  function assertAuthLoginArgs(args) {
705
625
  const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
706
626
  const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
@@ -1527,16 +1447,15 @@ function renderWorkspaceStatus(status) {
1527
1447
  `Workspace HEAD: ${status.head ?? "(not initialized)"}`,
1528
1448
  `Latest sync: ${status.latestSync ? `${status.latestSync.outcome} on ${status.latestSync.workerLabel}` : "(none)"}`,
1529
1449
  `Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
1530
- `Materialization: ${status.materialization ? `${status.materialization.state}; ${status.materialization.materializedHead ?? "(none)"} -> ${status.materialization.desiredHead ?? "(none)"}; lag ${status.materialization.lagMs}ms` : "not initialized"}`,
1531
1450
  ...status.workers.map(
1532
- (worker) => `Worker ${worker.workerLabel}: ${worker.ready ? "ready" : "bootstrapping"}; publication ${worker.publicationState}; head ${worker.localHead ?? "(none)"}; desired ${worker.desiredCanonicalHead ?? "(none)"}; generations ${worker.publishedGeneration}/${worker.dirtyGeneration}; pending checkouts ${worker.pendingCheckouts.length}; CAS retries ${worker.casRetries}; fan-out lag ${worker.fanoutLagMs ?? "unknown"}ms`
1451
+ (worker) => `Worker ${worker.workerLabel}: ${worker.configured ? "configured" : "configuring"}; head ${worker.localHead ?? "(none)"}; pending checkouts ${worker.pendingCheckouts.length}`
1533
1452
  )
1534
1453
  ];
1535
1454
  return `${lines.join("\n")}
1536
1455
  `;
1537
1456
  }
1538
1457
  function renderWorkspaceSync(result) {
1539
- const head = result.publishedHead ?? result.candidateHead ?? result.startingHead ?? "(none)";
1458
+ const head = result.publishedHead ?? result.localHead ?? result.startingHead ?? "(none)";
1540
1459
  const discarded = result.discardedPaths.length > 0 ? `
1541
1460
  Discarded paths: ${result.discardedPaths.join(", ")}` : "";
1542
1461
  const error = result.error ? `
@@ -1912,30 +1831,6 @@ function formatStatusValue(value) {
1912
1831
  }
1913
1832
  return value === void 0 || value === null ? void 0 : String(value);
1914
1833
  }
1915
- function appendMergeDetails(lines, merge) {
1916
- const mergeStatus = formatStatusValue(merge.status);
1917
- lines.push(`Merge: ${mergeStatus ?? "pending"}`);
1918
- for (const [label, key] of [
1919
- ["Attempt", "attemptId"],
1920
- ["Source worktree", "sourceWorktree"],
1921
- ["Target worktree", "targetWorktree"],
1922
- ["Commit", "commitHash"],
1923
- ["Resolver agent", "resolverSessionId"],
1924
- ["Conflict worktree", "conflictWorktree"],
1925
- ["Resolver worktree", "folderPath"]
1926
- ]) {
1927
- const value = responseString(merge, key);
1928
- if (value) lines.push(`${label}: ${value}`);
1929
- }
1930
- const conflictedFiles = Array.isArray(merge.conflictedFiles) ? merge.conflictedFiles.filter((file) => typeof file === "string") : [];
1931
- if (conflictedFiles.length > 0) {
1932
- lines.push("", "Conflicts:", ...conflictedFiles.map((file) => `- ${file}`));
1933
- }
1934
- const message = responseString(merge, "message");
1935
- if (message) lines.push("", message);
1936
- const failureReason = responseString(merge, "failureReason");
1937
- if (failureReason) lines.push(`Failure: ${failureReason}`);
1938
- }
1939
1834
  function renderSessionRunStart(session) {
1940
1835
  const lines = [
1941
1836
  `Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
@@ -1984,28 +1879,6 @@ function renderSessionRunStatus(session) {
1984
1879
  return `${lines.join("\n")}
1985
1880
  `;
1986
1881
  }
1987
- function renderWorktreeMerge(result, input) {
1988
- const lines = [];
1989
- appendMergeDetails(lines, result);
1990
- const status = responseString(result, "status");
1991
- const conflictWorktree = responseString(result, "conflictWorktree");
1992
- const resolverSessionId = responseString(result, "resolverSessionId");
1993
- const attemptId = responseString(result, "attemptId");
1994
- const worker = responseString(result, "worker") ?? input.worker;
1995
- if (resolverSessionId) lines.push("", `Resolver session: ${resolverSessionId}`, ` r5dctl session stop ${resolverSessionId}`);
1996
- if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(status ?? "")) {
1997
- lines.push(
1998
- "",
1999
- "Finish after resolving and reviewing the conflict worktree:",
2000
- ` r5dctl -p ${input.project} merge finish --worker ${worker} ${conflictWorktree} --summary "<summary>"`
2001
- );
2002
- }
2003
- if (attemptId && (status === "provisioning" || status === "resolving" && result.resolverAgentRunning === false)) {
2004
- lines.push("", "Check progress:", ` r5dctl -p ${input.project} merge status ${attemptId}`);
2005
- }
2006
- return `${lines.join("\n")}
2007
- `;
2008
- }
2009
1882
  async function dispatchSessionRunCommand(client, command) {
2010
1883
  if (command.kind === "start") {
2011
1884
  const commonInput = {
@@ -2042,70 +1915,16 @@ async function dispatchSessionRunCommand(client, command) {
2042
1915
  const data = await client.sessions.stop(command.sessionId);
2043
1916
  return { data, human: renderSessionRunStatus(data) };
2044
1917
  }
2045
- const MERGE_POLL_INTERVAL_MS = 2e3;
2046
- const MERGE_POLL_TIMEOUT_MS = 20 * 6e4;
2047
- const MERGE_POLL_MAX_CONSECUTIVE_FAILURES = 5;
2048
- function isMergeAttemptSettled(response) {
2049
- const status = responseString(response, "status");
2050
- if (!status) return true;
2051
- if (status === "resolving") return response.resolverAgentRunning !== false;
2052
- return status !== "provisioning";
2053
- }
2054
- async function pollMergeAttemptUntilSettled(client, project, attemptId, initial, options) {
2055
- const intervalMs = options.pollIntervalMs ?? MERGE_POLL_INTERVAL_MS;
2056
- const timeoutMs = options.pollTimeoutMs ?? MERGE_POLL_TIMEOUT_MS;
2057
- const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2058
- const onProgress = options.onProgress ?? (() => {
2059
- });
2060
- let latest = initial;
2061
- let worktreeReadyReported = latest.worktreeReady === true;
2062
- let consecutiveFailures = 0;
2063
- let waitedMs = 0;
2064
- while (!isMergeAttemptSettled(latest)) {
2065
- if (waitedMs >= timeoutMs) {
2066
- onProgress(`Still waiting after ${Math.round(timeoutMs / 6e4)} minutes; check later with: r5dctl -p ${project} merge status ${attemptId}`);
2067
- return latest;
2068
- }
2069
- await sleep2(intervalMs);
2070
- waitedMs += intervalMs;
2071
- try {
2072
- latest = await client.projects.worktrees.mergeAttempt(project, attemptId);
2073
- consecutiveFailures = 0;
2074
- } catch (error) {
2075
- consecutiveFailures += 1;
2076
- if (consecutiveFailures >= MERGE_POLL_MAX_CONSECUTIVE_FAILURES) throw error;
2077
- continue;
2078
- }
2079
- if (!worktreeReadyReported && latest.worktreeReady === true) {
2080
- worktreeReadyReported = true;
2081
- onProgress("Conflict worktree ready.");
2082
- }
1918
+ async function waitForSessionProvisioning(client, initial, options = {}) {
1919
+ const sessionId = requireValue(responseString(initial, "sessionId"), "Session start did not return a session id");
1920
+ const wait = options.wait ?? import_promises.setTimeout;
1921
+ const intervalMs = options.intervalMs ?? 500;
1922
+ let current = initial;
1923
+ while (formatStatusValue(current.status) === "provisioning") {
1924
+ await wait(intervalMs);
1925
+ current = await client.sessions.status(sessionId);
2083
1926
  }
2084
- return latest;
2085
- }
2086
- async function dispatchMergeCommand(client, command, pollOptions = {}) {
2087
- if (command.kind === "merge") {
2088
- const data2 = await client.projects.worktrees.merge(command.project, command.sourceWorktree, {
2089
- conflictMode: command.conflictMode,
2090
- ...command.worker ? { worker: command.worker } : {}
2091
- });
2092
- const attemptId = responseString(data2, "attemptId");
2093
- if (command.noWait || !attemptId || isMergeAttemptSettled(data2)) {
2094
- return { data: data2, human: renderWorktreeMerge(data2, command) };
2095
- }
2096
- pollOptions.onProgress?.(`Provisioning the conflict worktree (attempt ${attemptId})...`);
2097
- const settled = await pollMergeAttemptUntilSettled(client, command.project, attemptId, data2, pollOptions);
2098
- return { data: settled, human: renderWorktreeMerge(settled, command) };
2099
- }
2100
- if (command.kind === "status") {
2101
- const data2 = await client.projects.worktrees.mergeAttempt(command.project, command.attemptId);
2102
- return { data: data2, human: renderWorktreeMerge(data2, { project: command.project }) };
2103
- }
2104
- const data = await client.projects.worktrees.finishMerge(command.project, command.conflictWorktree, {
2105
- worker: command.worker,
2106
- ...command.summary ? { summary: command.summary } : {}
2107
- });
2108
- return { data, human: renderWorktreeMerge(data, command) };
1927
+ return current;
2109
1928
  }
2110
1929
  function parseProjectUpdateArgs(args) {
2111
1930
  const input = {};
@@ -2923,12 +2742,6 @@ function resolveCommandExecution(options, rest) {
2923
2742
  text: SESSION_RUN_HELP_TEXT
2924
2743
  };
2925
2744
  }
2926
- if (command === "merge" && commandArgs.length === 0) {
2927
- return {
2928
- kind: "cli-help",
2929
- text: MERGE_HELP_TEXT
2930
- };
2931
- }
2932
2745
  if (trailingHelp) {
2933
2746
  const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
2934
2747
  if (cliOnlyHelp) {
@@ -3327,9 +3140,6 @@ function resolveCommandExecution(options, rest) {
3327
3140
  if (command === "session") {
3328
3141
  return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
3329
3142
  }
3330
- if (command === "merge") {
3331
- return { kind: "merge", command: parseMergeCommand(options, commandArgs) };
3332
- }
3333
3143
  if (command === "answer-questions") {
3334
3144
  const sessionId = options.session;
3335
3145
  if (!sessionId) {
@@ -3391,7 +3201,7 @@ async function runCommand(argv) {
3391
3201
  if (execution.kind === "session-run") {
3392
3202
  if (!options.json) {
3393
3203
  if (execution.command.kind === "start") {
3394
- process.stderr.write("Starting session (publishing the workspace and provisioning the worktree)...\n");
3204
+ process.stderr.write("Starting session...\n");
3395
3205
  } else if (execution.command.kind === "stop") {
3396
3206
  process.stderr.write(`Stopping session ${execution.command.sessionId}...
3397
3207
  `);
@@ -3406,28 +3216,20 @@ async function runCommand(argv) {
3406
3216
  if (options.json) process.stdout.write(`${JSON.stringify({ type: "session_started", data: result.data })}
3407
3217
  `);
3408
3218
  else process.stderr.write(result.human);
3219
+ const ready = await waitForSessionProvisioning(client, result.data);
3220
+ const readyStatus = formatStatusValue(ready.status);
3221
+ if (readyStatus === "failed" || readyStatus === "stopped") {
3222
+ if (options.json) process.stdout.write(`${JSON.stringify({ type: "session_status", data: ready })}
3223
+ `);
3224
+ else process.stderr.write(renderSessionRunStatus(ready));
3225
+ return readyStatus === "failed" ? 1 : 0;
3226
+ }
3409
3227
  return await followWithInterrupt(client, sessionId, {
3410
3228
  json: options.json,
3411
3229
  includeTools: execution.command.includeTools,
3412
3230
  printInitialTranscript: false
3413
3231
  });
3414
3232
  }
3415
- if (execution.kind === "merge") {
3416
- if (!options.json) {
3417
- if (execution.command.kind === "merge") {
3418
- process.stderr.write("Merging (publishing the workspace and computing the merge)...\n");
3419
- } else if (execution.command.kind === "finish") {
3420
- process.stderr.write(`Finalizing ${execution.command.conflictWorktree} (publishing the resolver workspace)...
3421
- `);
3422
- }
3423
- }
3424
- const result = await dispatchMergeCommand(client, execution.command, {
3425
- ...options.json ? {} : { onProgress: (line) => process.stderr.write(`${line}
3426
- `) }
3427
- });
3428
- writeDataOutput(options.json, result.data, result.human);
3429
- return 0;
3430
- }
3431
3233
  if (execution.kind === "conversation-follow") {
3432
3234
  const { follow: _follow, ...renderOptions } = execution.renderOptions;
3433
3235
  const initialConversation = await client.sessions.conversation(execution.sessionId, renderOptions);
@@ -3488,7 +3290,6 @@ async function main(argv = process.argv.slice(2)) {
3488
3290
  0 && (module.exports = {
3489
3291
  advanceTransientDevicePollBackoff,
3490
3292
  collectK8sUsage,
3491
- dispatchMergeCommand,
3492
3293
  dispatchSessionRunCommand,
3493
3294
  followR5dctlSession,
3494
3295
  formatK8sCpu,
@@ -3500,7 +3301,6 @@ async function main(argv = process.argv.slice(2)) {
3500
3301
  getR5dctlVersion,
3501
3302
  getTransientDevicePollDelay,
3502
3303
  handleAuthLogin,
3503
- isMergeAttemptSettled,
3504
3304
  main,
3505
3305
  parseAnswerEnvRequestCommandArgs,
3506
3306
  parseAnswerFlags,
@@ -3514,7 +3314,6 @@ async function main(argv = process.argv.slice(2)) {
3514
3314
  parseGetEnvsArgs,
3515
3315
  parseGlobalArgs,
3516
3316
  parseK8sUsageArgs,
3517
- parseMergeCommand,
3518
3317
  parsePromptArgs,
3519
3318
  parsePsHistoryArgs,
3520
3319
  parsePsListArgs,
@@ -3538,9 +3337,9 @@ async function main(argv = process.argv.slice(2)) {
3538
3337
  renderSessionRunStatus,
3539
3338
  renderWorkspaceStatus,
3540
3339
  renderWorkspaceSync,
3541
- renderWorktreeMerge,
3542
3340
  resolveCommandExecution,
3543
3341
  runR5dctlCli,
3544
3342
  summarizeEnvData,
3343
+ waitForSessionProvisioning,
3545
3344
  writeConfig
3546
3345
  });
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.74",
3
+ "version": "0.0.76",
4
4
  "type": "commonjs"
5
5
  }
package/dist/mjs/cli.mjs CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  R5dctlClient
12
12
  } from "@ricsam/r5d-api";
13
13
  import { runR5dctlShell } from "./shell.mjs";
14
- const CHAT_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent", "large_diff_remediation", "merge_conflict_resolution"]);
14
+ const CHAT_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent", "large_diff_remediation"]);
15
15
  const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "agent"]);
16
16
  const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
17
17
  const R5DCTL_PACKAGE_NAME = "@ricsam/r5dctl";
@@ -82,16 +82,6 @@ const SESSION_RUN_HELP_TEXT = [
82
82
  "Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
83
83
  ""
84
84
  ].join("\n");
85
- const MERGE_HELP_TEXT = [
86
- "Usage:",
87
- " r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
88
- " r5dctl -p <project> merge status <attempt-id>",
89
- ' r5dctl -p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
90
- "",
91
- "Merge a managed worktree into its recorded parent worktree.",
92
- "Conflict worktrees provision in the background; merge waits by default and --no-wait returns the attempt immediately.",
93
- ""
94
- ].join("\n");
95
85
  const SHARED_HELP_ENTRIES = [
96
86
  { section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
97
87
  { section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
@@ -102,16 +92,16 @@ const SHARED_HELP_ENTRIES = [
102
92
  },
103
93
  { section: "auth", usage: "auth api-key list", description: "List API keys for the current account." },
104
94
  { section: "auth", usage: "auth api-key revoke <key-id>", description: "Revoke an API key." },
105
- { section: "workspace", usage: "workspace status", description: "Inspect the per-user canonical workspace and active remediation." },
95
+ { section: "workspace", usage: "workspace status", description: "Inspect per-user workspace Git state and active remediation." },
106
96
  {
107
97
  section: "workspace",
108
98
  usage: "workspace sync [--worker <label>]",
109
- description: "Publish a connected worker into the canonical workspace now."
99
+ description: "Commit, rebase, and push a connected worker's workspace now."
110
100
  },
111
101
  {
112
102
  section: "workspace",
113
103
  usage: "workspace reset [--worker <label>] --confirm",
114
- description: "Destructively discard a blocked remediation candidate and restore canonical state."
104
+ description: "Destructively discard a blocked local workspace commit and restore origin/main."
115
105
  },
116
106
  { section: "projects", usage: "get projects", description: "List projects you can access." },
117
107
  { section: "projects", usage: "describe project <namespace/name|id>", description: "Show details for a project." },
@@ -248,22 +238,7 @@ const SHARED_HELP_ENTRIES = [
248
238
  usage: "ps inspect <run-id>",
249
239
  description: "Inspect process metadata and bounded stdout/stderr tails."
250
240
  },
251
- { section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
252
- {
253
- section: "merge",
254
- usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] [--no-wait] <source-worktree>",
255
- description: "Merge a worktree into its recorded parent, with an explicit conflict strategy."
256
- },
257
- {
258
- section: "merge",
259
- usage: "-p <project> merge status <attempt-id>",
260
- description: "Show a merge attempt's provisioning and resolution status."
261
- },
262
- {
263
- section: "merge",
264
- usage: '-p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
265
- description: "Publish and finish a manually resolved conflict worktree."
266
- }
241
+ { section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." }
267
242
  ];
268
243
  const HELP_SECTION_ORDER = [
269
244
  "auth",
@@ -274,8 +249,7 @@ const HELP_SECTION_ORDER = [
274
249
  "sessions",
275
250
  "kubernetes",
276
251
  "processes",
277
- "shell",
278
- "merge"
252
+ "shell"
279
253
  ];
280
254
  const HELP_SECTION_TITLES = {
281
255
  auth: "Auth",
@@ -286,8 +260,7 @@ const HELP_SECTION_TITLES = {
286
260
  sessions: "Sessions",
287
261
  kubernetes: "Kubernetes",
288
262
  processes: "Processes",
289
- shell: "Shell",
290
- merge: "Worktree merging"
263
+ shell: "Shell"
291
264
  };
292
265
  function getCliHelpText() {
293
266
  const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
@@ -565,56 +538,6 @@ function parseSessionRunCommand(options, args) {
565
538
  }
566
539
  throw new Error(`Unknown session command: ${subcommand}`);
567
540
  }
568
- function parseMergeCommand(options, args) {
569
- const project = requireValue(options.project, "--project/-p is required for `merge`");
570
- if (options.branch) throw new Error("--branch/-b is not supported for `merge`; pass the source worktree explicitly");
571
- if (options.session) throw new Error("--session/-s is not supported for `merge`");
572
- if (args[0] === "finish") {
573
- const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(["--worker", "--summary"]), /* @__PURE__ */ new Map(), "merge finish");
574
- const conflictWorktree = requireValue(parsed2.positionals[0], "Conflict worktree is required for `merge finish`");
575
- if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge finish argument: ${parsed2.positionals[1]}`);
576
- return {
577
- kind: "finish",
578
- project,
579
- conflictWorktree,
580
- worker: requireWorker(parsed2.values),
581
- ...parsed2.values.get("--summary")?.trim() ? { summary: parsed2.values.get("--summary").trim() } : {}
582
- };
583
- }
584
- if (args[0] === "status") {
585
- const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), "merge status");
586
- const attemptId = requireValue(parsed2.positionals[0], "Attempt id is required for `merge status`");
587
- if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge status argument: ${parsed2.positionals[1]}`);
588
- return { kind: "status", project, attemptId };
589
- }
590
- const parsed = parseCommandOperands(
591
- args,
592
- /* @__PURE__ */ new Set(["--worker"]),
593
- /* @__PURE__ */ new Map([
594
- ["--create-conflict-worktree", "--create-conflict-worktree"],
595
- ["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"],
596
- ["--no-wait", "--no-wait"]
597
- ]),
598
- "merge"
599
- );
600
- const sourceWorktree = requireValue(parsed.positionals[0], "Source worktree is required for `merge`");
601
- if (parsed.positionals.length > 1) throw new Error(`Unexpected merge argument: ${parsed.positionals[1]}`);
602
- const createWorktree = parsed.booleans.has("--create-conflict-worktree");
603
- const launchAgent = parsed.booleans.has("--launch-conflict-resolution-agent");
604
- if (createWorktree && launchAgent) {
605
- throw new Error("Use only one of --create-conflict-worktree or --launch-conflict-resolution-agent");
606
- }
607
- const conflictMode = createWorktree ? "worktree" : launchAgent ? "agent" : "none";
608
- const worker = parsed.values.get("--worker")?.trim();
609
- return {
610
- kind: "merge",
611
- project,
612
- sourceWorktree,
613
- conflictMode,
614
- ...worker ? { worker } : {},
615
- ...parsed.booleans.has("--no-wait") ? { noWait: true } : {}
616
- };
617
- }
618
541
  function assertAuthLoginArgs(args) {
619
542
  const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
620
543
  const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
@@ -1441,16 +1364,15 @@ function renderWorkspaceStatus(status) {
1441
1364
  `Workspace HEAD: ${status.head ?? "(not initialized)"}`,
1442
1365
  `Latest sync: ${status.latestSync ? `${status.latestSync.outcome} on ${status.latestSync.workerLabel}` : "(none)"}`,
1443
1366
  `Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
1444
- `Materialization: ${status.materialization ? `${status.materialization.state}; ${status.materialization.materializedHead ?? "(none)"} -> ${status.materialization.desiredHead ?? "(none)"}; lag ${status.materialization.lagMs}ms` : "not initialized"}`,
1445
1367
  ...status.workers.map(
1446
- (worker) => `Worker ${worker.workerLabel}: ${worker.ready ? "ready" : "bootstrapping"}; publication ${worker.publicationState}; head ${worker.localHead ?? "(none)"}; desired ${worker.desiredCanonicalHead ?? "(none)"}; generations ${worker.publishedGeneration}/${worker.dirtyGeneration}; pending checkouts ${worker.pendingCheckouts.length}; CAS retries ${worker.casRetries}; fan-out lag ${worker.fanoutLagMs ?? "unknown"}ms`
1368
+ (worker) => `Worker ${worker.workerLabel}: ${worker.configured ? "configured" : "configuring"}; head ${worker.localHead ?? "(none)"}; pending checkouts ${worker.pendingCheckouts.length}`
1447
1369
  )
1448
1370
  ];
1449
1371
  return `${lines.join("\n")}
1450
1372
  `;
1451
1373
  }
1452
1374
  function renderWorkspaceSync(result) {
1453
- const head = result.publishedHead ?? result.candidateHead ?? result.startingHead ?? "(none)";
1375
+ const head = result.publishedHead ?? result.localHead ?? result.startingHead ?? "(none)";
1454
1376
  const discarded = result.discardedPaths.length > 0 ? `
1455
1377
  Discarded paths: ${result.discardedPaths.join(", ")}` : "";
1456
1378
  const error = result.error ? `
@@ -1826,30 +1748,6 @@ function formatStatusValue(value) {
1826
1748
  }
1827
1749
  return value === void 0 || value === null ? void 0 : String(value);
1828
1750
  }
1829
- function appendMergeDetails(lines, merge) {
1830
- const mergeStatus = formatStatusValue(merge.status);
1831
- lines.push(`Merge: ${mergeStatus ?? "pending"}`);
1832
- for (const [label, key] of [
1833
- ["Attempt", "attemptId"],
1834
- ["Source worktree", "sourceWorktree"],
1835
- ["Target worktree", "targetWorktree"],
1836
- ["Commit", "commitHash"],
1837
- ["Resolver agent", "resolverSessionId"],
1838
- ["Conflict worktree", "conflictWorktree"],
1839
- ["Resolver worktree", "folderPath"]
1840
- ]) {
1841
- const value = responseString(merge, key);
1842
- if (value) lines.push(`${label}: ${value}`);
1843
- }
1844
- const conflictedFiles = Array.isArray(merge.conflictedFiles) ? merge.conflictedFiles.filter((file) => typeof file === "string") : [];
1845
- if (conflictedFiles.length > 0) {
1846
- lines.push("", "Conflicts:", ...conflictedFiles.map((file) => `- ${file}`));
1847
- }
1848
- const message = responseString(merge, "message");
1849
- if (message) lines.push("", message);
1850
- const failureReason = responseString(merge, "failureReason");
1851
- if (failureReason) lines.push(`Failure: ${failureReason}`);
1852
- }
1853
1751
  function renderSessionRunStart(session) {
1854
1752
  const lines = [
1855
1753
  `Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
@@ -1898,28 +1796,6 @@ function renderSessionRunStatus(session) {
1898
1796
  return `${lines.join("\n")}
1899
1797
  `;
1900
1798
  }
1901
- function renderWorktreeMerge(result, input) {
1902
- const lines = [];
1903
- appendMergeDetails(lines, result);
1904
- const status = responseString(result, "status");
1905
- const conflictWorktree = responseString(result, "conflictWorktree");
1906
- const resolverSessionId = responseString(result, "resolverSessionId");
1907
- const attemptId = responseString(result, "attemptId");
1908
- const worker = responseString(result, "worker") ?? input.worker;
1909
- if (resolverSessionId) lines.push("", `Resolver session: ${resolverSessionId}`, ` r5dctl session stop ${resolverSessionId}`);
1910
- if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(status ?? "")) {
1911
- lines.push(
1912
- "",
1913
- "Finish after resolving and reviewing the conflict worktree:",
1914
- ` r5dctl -p ${input.project} merge finish --worker ${worker} ${conflictWorktree} --summary "<summary>"`
1915
- );
1916
- }
1917
- if (attemptId && (status === "provisioning" || status === "resolving" && result.resolverAgentRunning === false)) {
1918
- lines.push("", "Check progress:", ` r5dctl -p ${input.project} merge status ${attemptId}`);
1919
- }
1920
- return `${lines.join("\n")}
1921
- `;
1922
- }
1923
1799
  async function dispatchSessionRunCommand(client, command) {
1924
1800
  if (command.kind === "start") {
1925
1801
  const commonInput = {
@@ -1956,70 +1832,16 @@ async function dispatchSessionRunCommand(client, command) {
1956
1832
  const data = await client.sessions.stop(command.sessionId);
1957
1833
  return { data, human: renderSessionRunStatus(data) };
1958
1834
  }
1959
- const MERGE_POLL_INTERVAL_MS = 2e3;
1960
- const MERGE_POLL_TIMEOUT_MS = 20 * 6e4;
1961
- const MERGE_POLL_MAX_CONSECUTIVE_FAILURES = 5;
1962
- function isMergeAttemptSettled(response) {
1963
- const status = responseString(response, "status");
1964
- if (!status) return true;
1965
- if (status === "resolving") return response.resolverAgentRunning !== false;
1966
- return status !== "provisioning";
1967
- }
1968
- async function pollMergeAttemptUntilSettled(client, project, attemptId, initial, options) {
1969
- const intervalMs = options.pollIntervalMs ?? MERGE_POLL_INTERVAL_MS;
1970
- const timeoutMs = options.pollTimeoutMs ?? MERGE_POLL_TIMEOUT_MS;
1971
- const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1972
- const onProgress = options.onProgress ?? (() => {
1973
- });
1974
- let latest = initial;
1975
- let worktreeReadyReported = latest.worktreeReady === true;
1976
- let consecutiveFailures = 0;
1977
- let waitedMs = 0;
1978
- while (!isMergeAttemptSettled(latest)) {
1979
- if (waitedMs >= timeoutMs) {
1980
- onProgress(`Still waiting after ${Math.round(timeoutMs / 6e4)} minutes; check later with: r5dctl -p ${project} merge status ${attemptId}`);
1981
- return latest;
1982
- }
1983
- await sleep2(intervalMs);
1984
- waitedMs += intervalMs;
1985
- try {
1986
- latest = await client.projects.worktrees.mergeAttempt(project, attemptId);
1987
- consecutiveFailures = 0;
1988
- } catch (error) {
1989
- consecutiveFailures += 1;
1990
- if (consecutiveFailures >= MERGE_POLL_MAX_CONSECUTIVE_FAILURES) throw error;
1991
- continue;
1992
- }
1993
- if (!worktreeReadyReported && latest.worktreeReady === true) {
1994
- worktreeReadyReported = true;
1995
- onProgress("Conflict worktree ready.");
1996
- }
1835
+ async function waitForSessionProvisioning(client, initial, options = {}) {
1836
+ const sessionId = requireValue(responseString(initial, "sessionId"), "Session start did not return a session id");
1837
+ const wait = options.wait ?? sleep;
1838
+ const intervalMs = options.intervalMs ?? 500;
1839
+ let current = initial;
1840
+ while (formatStatusValue(current.status) === "provisioning") {
1841
+ await wait(intervalMs);
1842
+ current = await client.sessions.status(sessionId);
1997
1843
  }
1998
- return latest;
1999
- }
2000
- async function dispatchMergeCommand(client, command, pollOptions = {}) {
2001
- if (command.kind === "merge") {
2002
- const data2 = await client.projects.worktrees.merge(command.project, command.sourceWorktree, {
2003
- conflictMode: command.conflictMode,
2004
- ...command.worker ? { worker: command.worker } : {}
2005
- });
2006
- const attemptId = responseString(data2, "attemptId");
2007
- if (command.noWait || !attemptId || isMergeAttemptSettled(data2)) {
2008
- return { data: data2, human: renderWorktreeMerge(data2, command) };
2009
- }
2010
- pollOptions.onProgress?.(`Provisioning the conflict worktree (attempt ${attemptId})...`);
2011
- const settled = await pollMergeAttemptUntilSettled(client, command.project, attemptId, data2, pollOptions);
2012
- return { data: settled, human: renderWorktreeMerge(settled, command) };
2013
- }
2014
- if (command.kind === "status") {
2015
- const data2 = await client.projects.worktrees.mergeAttempt(command.project, command.attemptId);
2016
- return { data: data2, human: renderWorktreeMerge(data2, { project: command.project }) };
2017
- }
2018
- const data = await client.projects.worktrees.finishMerge(command.project, command.conflictWorktree, {
2019
- worker: command.worker,
2020
- ...command.summary ? { summary: command.summary } : {}
2021
- });
2022
- return { data, human: renderWorktreeMerge(data, command) };
1844
+ return current;
2023
1845
  }
2024
1846
  function parseProjectUpdateArgs(args) {
2025
1847
  const input = {};
@@ -2837,12 +2659,6 @@ function resolveCommandExecution(options, rest) {
2837
2659
  text: SESSION_RUN_HELP_TEXT
2838
2660
  };
2839
2661
  }
2840
- if (command === "merge" && commandArgs.length === 0) {
2841
- return {
2842
- kind: "cli-help",
2843
- text: MERGE_HELP_TEXT
2844
- };
2845
- }
2846
2662
  if (trailingHelp) {
2847
2663
  const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
2848
2664
  if (cliOnlyHelp) {
@@ -3241,9 +3057,6 @@ function resolveCommandExecution(options, rest) {
3241
3057
  if (command === "session") {
3242
3058
  return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
3243
3059
  }
3244
- if (command === "merge") {
3245
- return { kind: "merge", command: parseMergeCommand(options, commandArgs) };
3246
- }
3247
3060
  if (command === "answer-questions") {
3248
3061
  const sessionId = options.session;
3249
3062
  if (!sessionId) {
@@ -3305,7 +3118,7 @@ async function runCommand(argv) {
3305
3118
  if (execution.kind === "session-run") {
3306
3119
  if (!options.json) {
3307
3120
  if (execution.command.kind === "start") {
3308
- process.stderr.write("Starting session (publishing the workspace and provisioning the worktree)...\n");
3121
+ process.stderr.write("Starting session...\n");
3309
3122
  } else if (execution.command.kind === "stop") {
3310
3123
  process.stderr.write(`Stopping session ${execution.command.sessionId}...
3311
3124
  `);
@@ -3320,28 +3133,20 @@ async function runCommand(argv) {
3320
3133
  if (options.json) process.stdout.write(`${JSON.stringify({ type: "session_started", data: result.data })}
3321
3134
  `);
3322
3135
  else process.stderr.write(result.human);
3136
+ const ready = await waitForSessionProvisioning(client, result.data);
3137
+ const readyStatus = formatStatusValue(ready.status);
3138
+ if (readyStatus === "failed" || readyStatus === "stopped") {
3139
+ if (options.json) process.stdout.write(`${JSON.stringify({ type: "session_status", data: ready })}
3140
+ `);
3141
+ else process.stderr.write(renderSessionRunStatus(ready));
3142
+ return readyStatus === "failed" ? 1 : 0;
3143
+ }
3323
3144
  return await followWithInterrupt(client, sessionId, {
3324
3145
  json: options.json,
3325
3146
  includeTools: execution.command.includeTools,
3326
3147
  printInitialTranscript: false
3327
3148
  });
3328
3149
  }
3329
- if (execution.kind === "merge") {
3330
- if (!options.json) {
3331
- if (execution.command.kind === "merge") {
3332
- process.stderr.write("Merging (publishing the workspace and computing the merge)...\n");
3333
- } else if (execution.command.kind === "finish") {
3334
- process.stderr.write(`Finalizing ${execution.command.conflictWorktree} (publishing the resolver workspace)...
3335
- `);
3336
- }
3337
- }
3338
- const result = await dispatchMergeCommand(client, execution.command, {
3339
- ...options.json ? {} : { onProgress: (line) => process.stderr.write(`${line}
3340
- `) }
3341
- });
3342
- writeDataOutput(options.json, result.data, result.human);
3343
- return 0;
3344
- }
3345
3150
  if (execution.kind === "conversation-follow") {
3346
3151
  const { follow: _follow, ...renderOptions } = execution.renderOptions;
3347
3152
  const initialConversation = await client.sessions.conversation(execution.sessionId, renderOptions);
@@ -3401,7 +3206,6 @@ async function main(argv = process.argv.slice(2)) {
3401
3206
  export {
3402
3207
  advanceTransientDevicePollBackoff,
3403
3208
  collectK8sUsage,
3404
- dispatchMergeCommand,
3405
3209
  dispatchSessionRunCommand,
3406
3210
  followR5dctlSession,
3407
3211
  formatK8sCpu,
@@ -3413,7 +3217,6 @@ export {
3413
3217
  getR5dctlVersion,
3414
3218
  getTransientDevicePollDelay,
3415
3219
  handleAuthLogin,
3416
- isMergeAttemptSettled,
3417
3220
  main,
3418
3221
  parseAnswerEnvRequestCommandArgs,
3419
3222
  parseAnswerFlags,
@@ -3427,7 +3230,6 @@ export {
3427
3230
  parseGetEnvsArgs,
3428
3231
  parseGlobalArgs,
3429
3232
  parseK8sUsageArgs,
3430
- parseMergeCommand,
3431
3233
  parsePromptArgs,
3432
3234
  parsePsHistoryArgs,
3433
3235
  parsePsListArgs,
@@ -3451,9 +3253,9 @@ export {
3451
3253
  renderSessionRunStatus,
3452
3254
  renderWorkspaceStatus,
3453
3255
  renderWorkspaceSync,
3454
- renderWorktreeMerge,
3455
3256
  resolveCommandExecution,
3456
3257
  runR5dctlCli,
3457
3258
  summarizeEnvData,
3259
+ waitForSessionProvisioning,
3458
3260
  writeConfig
3459
3261
  };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.74",
3
+ "version": "0.0.76",
4
4
  "type": "module"
5
5
  }
@@ -1,4 +1,4 @@
1
- import { R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlK8sResourceHistory, type R5dctlK8sUsage, type R5dctlK8sWorkload, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessRun, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorktreeSource, type R5dctlMergeConflictMode, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
1
+ import { R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlK8sResourceHistory, type R5dctlK8sUsage, type R5dctlK8sWorkload, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessRun, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorktreeSource, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
2
2
  export type GlobalOptions = {
3
3
  baseUrl?: string;
4
4
  json: boolean;
@@ -32,9 +32,6 @@ type CommandExecutionPlan = {
32
32
  } | {
33
33
  kind: "session-run";
34
34
  command: R5dctlSessionRunCommand;
35
- } | {
36
- kind: "merge";
37
- command: R5dctlMergeCommand;
38
35
  } | {
39
36
  kind: "conversation-follow";
40
37
  sessionId: string;
@@ -79,26 +76,7 @@ export type R5dctlSessionRunCommand = R5dctlSessionStartCommand | {
79
76
  kind: "stop";
80
77
  sessionId: string;
81
78
  };
82
- export type R5dctlMergeCommand = {
83
- kind: "merge";
84
- project: string;
85
- sourceWorktree: string;
86
- worker?: string;
87
- conflictMode: R5dctlMergeConflictMode;
88
- noWait?: boolean;
89
- } | {
90
- kind: "status";
91
- project: string;
92
- attemptId: string;
93
- } | {
94
- kind: "finish";
95
- project: string;
96
- conflictWorktree: string;
97
- worker: string;
98
- summary?: string;
99
- };
100
79
  export declare function parseSessionRunCommand(options: Pick<GlobalOptions, "project" | "branch" | "session">, args: string[]): R5dctlSessionRunCommand;
101
- export declare function parseMergeCommand(options: Pick<GlobalOptions, "project" | "branch" | "session">, args: string[]): R5dctlMergeCommand;
102
80
  export declare function parseGlobalArgs(argv: string[]): {
103
81
  options: GlobalOptions;
104
82
  rest: string[];
@@ -224,18 +202,6 @@ export type R5dctlCommandResponse = {
224
202
  error?: string;
225
203
  summary?: string;
226
204
  diffSummary?: string;
227
- attemptId?: string;
228
- sourceWorktree?: string;
229
- targetWorktree?: string;
230
- commitHash?: string;
231
- resolverSessionId?: string;
232
- conflictWorktree?: string;
233
- folderPath?: string;
234
- conflictedFiles?: string[];
235
- failureReason?: string;
236
- message?: string;
237
- worktreeReady?: boolean;
238
- resolverAgentRunning?: boolean;
239
205
  };
240
206
  export type R5dctlSessionRunClient = {
241
207
  projects: {
@@ -255,47 +221,17 @@ export type R5dctlSessionRunClient = {
255
221
  stop: (sessionId: string) => Promise<R5dctlCommandResponse>;
256
222
  };
257
223
  };
258
- export type R5dctlMergeClient = {
259
- projects: {
260
- worktrees: {
261
- merge: (projectRef: string, sourceWorktree: string, input: {
262
- worker?: string;
263
- conflictMode: R5dctlMergeConflictMode;
264
- }) => Promise<R5dctlCommandResponse>;
265
- finishMerge: (projectRef: string, conflictWorktree: string, input: {
266
- worker: string;
267
- summary?: string;
268
- }) => Promise<R5dctlCommandResponse>;
269
- mergeAttempt: (projectRef: string, attemptId: string) => Promise<R5dctlCommandResponse>;
270
- };
271
- };
272
- };
273
224
  export declare function renderSessionRunStart(session: R5dctlCommandResponse): string;
274
225
  export declare function renderSessionRunStatus(session: R5dctlCommandResponse): string;
275
- export declare function renderWorktreeMerge(result: R5dctlCommandResponse, input: {
276
- project: string;
277
- worker?: string;
278
- }): string;
279
226
  export type R5dctlCommandDispatchResult = {
280
227
  data: R5dctlCommandResponse;
281
228
  human: string;
282
229
  };
283
230
  export declare function dispatchSessionRunCommand(client: R5dctlSessionRunClient, command: R5dctlSessionRunCommand): 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>;
231
+ export declare function waitForSessionProvisioning(client: Pick<R5dctlSessionRunClient, "sessions">, initial: R5dctlCommandResponse, options?: {
232
+ intervalMs?: number;
233
+ wait?: (milliseconds: number) => Promise<unknown>;
234
+ }): Promise<R5dctlCommandResponse>;
299
235
  export declare function parseConversationRenderArgs(args: string[]): ConversationRenderOptions;
300
236
  export declare function renderConversationResponse(read: R5dctlConversationResponse): string;
301
237
  type R5dctlFollowClient = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.74",
3
+ "version": "0.0.76",
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.74",
29
+ "@ricsam/r5d-api": "^0.0.76",
30
30
  "dotenv": "^17",
31
31
  "qrcode": "^1.5.4",
32
32
  "ws": "^8.18.3"