@ricsam/r5dctl 0.0.58 → 0.0.60
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 +9 -0
- package/dist/cjs/cli.cjs +322 -189
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +317 -189
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +97 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,6 +78,13 @@ r5dctl -s <session-id> prompt --mode plan --model max "..."
|
|
|
78
78
|
r5dctl -s <session-id> answer-questions -a1 "Recipe Collection" -a2 "Both Manual & AI"
|
|
79
79
|
r5dctl -s <session-id> answer-env-request -e KEY=value --context "Generate the remaining salts"
|
|
80
80
|
|
|
81
|
+
r5dctl -p <project> agent start --worker <label> --source worktree:main debug "Fix the failing tests"
|
|
82
|
+
r5dctl agent status <session-id>
|
|
83
|
+
r5dctl agent prompt --worker <label> <session-id> "Also check the migration"
|
|
84
|
+
r5dctl agent merge --worker <label> <session-id>
|
|
85
|
+
r5dctl agent stop <session-id>
|
|
86
|
+
r5dctl agent finish-merge --worker <label> <resolver-session-id> --summary "Resolved the conflicts"
|
|
87
|
+
|
|
81
88
|
r5dctl -p <project> -b <branch> shell
|
|
82
89
|
r5dctl -p <project> -b <branch> shell -c "bun test"
|
|
83
90
|
|
|
@@ -92,6 +99,8 @@ r5dctl k8s usage --window 7d
|
|
|
92
99
|
r5dctl k8s usage --workload default/api --window 7d --json
|
|
93
100
|
```
|
|
94
101
|
|
|
102
|
+
Branch agents are independent sessions on automatically created branches. `agent start` requires an explicit worker and `--source worktree:<branch>`; it never infers either from the local directory. The source is the named branch worktree, including its committed and uncommitted files. Starting returns after that worktree has been copied into the new branch worktree and queued; execution continues asynchronously. Follow-up, merge, and resolver commands must name the session's bound worker; they do not move work between workers implicitly. A prompt response reports whether it was `queued` into an active generation or `started` as a new generation. Use `agent status` to inspect progress, then `agent merge` after the agent reaches a terminal state. A conflicting merge starts a resolver session; stop that resolver for manual takeover and use `agent finish-merge` after resolving and reviewing its worktree.
|
|
103
|
+
|
|
95
104
|
`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.
|
|
96
105
|
|
|
97
106
|
`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.
|
package/dist/cjs/cli.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var cli_exports = {};
|
|
|
30
30
|
__export(cli_exports, {
|
|
31
31
|
advanceTransientDevicePollBackoff: () => advanceTransientDevicePollBackoff,
|
|
32
32
|
collectK8sUsage: () => collectK8sUsage,
|
|
33
|
+
dispatchBranchAgentCommand: () => dispatchBranchAgentCommand,
|
|
33
34
|
formatK8sCpu: () => formatK8sCpu,
|
|
34
35
|
formatK8sMemory: () => formatK8sMemory,
|
|
35
36
|
formatProcessAge: () => formatProcessAge,
|
|
@@ -41,6 +42,7 @@ __export(cli_exports, {
|
|
|
41
42
|
handleAuthLogin: () => handleAuthLogin,
|
|
42
43
|
main: () => main,
|
|
43
44
|
parseAnswerFlags: () => parseAnswerFlags,
|
|
45
|
+
parseBranchAgentCommand: () => parseBranchAgentCommand,
|
|
44
46
|
parseConversationRenderArgs: () => parseConversationRenderArgs,
|
|
45
47
|
parseConversationWorkDetailArgs: () => parseConversationWorkDetailArgs,
|
|
46
48
|
parseEnvFlags: () => parseEnvFlags,
|
|
@@ -54,6 +56,9 @@ __export(cli_exports, {
|
|
|
54
56
|
parseSetEnvArgs: () => parseSetEnvArgs,
|
|
55
57
|
parseShellArgs: () => parseShellArgs,
|
|
56
58
|
readDotenvFile: () => readDotenvFile,
|
|
59
|
+
renderBranchAgentMerge: () => renderBranchAgentMerge,
|
|
60
|
+
renderBranchAgentStart: () => renderBranchAgentStart,
|
|
61
|
+
renderBranchAgentStatus: () => renderBranchAgentStatus,
|
|
57
62
|
renderConversationNodeResponse: () => renderConversationNodeResponse,
|
|
58
63
|
renderConversationOverviewResponse: () => renderConversationOverviewResponse,
|
|
59
64
|
renderConversationResponse: () => renderConversationResponse,
|
|
@@ -149,6 +154,19 @@ const K8S_HELP_TEXT = [
|
|
|
149
154
|
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
150
155
|
""
|
|
151
156
|
].join("\n");
|
|
157
|
+
const AGENT_HELP_TEXT = [
|
|
158
|
+
"Usage:",
|
|
159
|
+
' r5dctl -p <project> agent start --worker <label> --source worktree:<branch> <research|debug|test> "<prompt>"',
|
|
160
|
+
" r5dctl agent status <session-id>",
|
|
161
|
+
' r5dctl agent prompt --worker <label> <session-id> "<prompt>"',
|
|
162
|
+
" r5dctl agent merge --worker <label> <session-id>",
|
|
163
|
+
" r5dctl agent stop <session-id>",
|
|
164
|
+
' r5dctl agent finish-merge --worker <label> <resolver-session-id> --summary "<summary>"',
|
|
165
|
+
"",
|
|
166
|
+
"Create, inspect, resume, stop, and merge independent branch agents.",
|
|
167
|
+
"The source is the named branch worktree, including its committed and uncommitted files.",
|
|
168
|
+
""
|
|
169
|
+
].join("\n");
|
|
152
170
|
const SHARED_HELP_ENTRIES = [
|
|
153
171
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
154
172
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -272,18 +290,34 @@ const SHARED_HELP_ENTRIES = [
|
|
|
272
290
|
{ section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
|
|
273
291
|
{
|
|
274
292
|
section: "agents",
|
|
275
|
-
usage: '-p <project>
|
|
276
|
-
description: "
|
|
293
|
+
usage: '-p <project> agent start --worker <label> --source worktree:<branch> <research|debug|test> "<prompt>"',
|
|
294
|
+
description: "Create a branch from an explicit worktree source and start an independent agent."
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
section: "agents",
|
|
298
|
+
usage: "agent status <session-id>",
|
|
299
|
+
description: "Show branch-agent execution, publication, and merge status."
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
section: "agents",
|
|
303
|
+
usage: 'agent prompt --worker <label> <session-id> "<prompt>"',
|
|
304
|
+
description: "Queue a prompt or resume an idle branch agent on the selected worker."
|
|
277
305
|
},
|
|
278
|
-
{ section: "agents", usage: "agent-status <session-id>", description: "Show detached branch agent status." },
|
|
279
|
-
{ section: "agents", usage: 'send-prompt <session-id> "<prompt>"', description: "Queue or resume a detached branch agent." },
|
|
280
306
|
{
|
|
281
|
-
section: "
|
|
282
|
-
usage: "
|
|
283
|
-
description: "
|
|
307
|
+
section: "agents",
|
|
308
|
+
usage: "agent merge --worker <label> <session-id>",
|
|
309
|
+
description: "Merge a terminal branch agent into its recorded parent worktree."
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
section: "agents",
|
|
313
|
+
usage: "agent stop <session-id>",
|
|
314
|
+
description: "Stop a branch agent or merge resolver."
|
|
284
315
|
},
|
|
285
|
-
{
|
|
286
|
-
|
|
316
|
+
{
|
|
317
|
+
section: "agents",
|
|
318
|
+
usage: 'agent finish-merge --worker <label> <resolver-session-id> --summary "<summary>"',
|
|
319
|
+
description: "Publish and finalize a manually resolved merge."
|
|
320
|
+
}
|
|
287
321
|
];
|
|
288
322
|
const HELP_SECTION_ORDER = [
|
|
289
323
|
"auth",
|
|
@@ -295,8 +329,7 @@ const HELP_SECTION_ORDER = [
|
|
|
295
329
|
"kubernetes",
|
|
296
330
|
"processes",
|
|
297
331
|
"shell",
|
|
298
|
-
"agents"
|
|
299
|
-
"merges"
|
|
332
|
+
"agents"
|
|
300
333
|
];
|
|
301
334
|
const HELP_SECTION_TITLES = {
|
|
302
335
|
auth: "Auth",
|
|
@@ -308,8 +341,7 @@ const HELP_SECTION_TITLES = {
|
|
|
308
341
|
kubernetes: "Kubernetes",
|
|
309
342
|
processes: "Processes",
|
|
310
343
|
shell: "Shell",
|
|
311
|
-
agents: "
|
|
312
|
-
merges: "Merges"
|
|
344
|
+
agents: "Branch agents"
|
|
313
345
|
};
|
|
314
346
|
function getCliHelpText() {
|
|
315
347
|
const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
|
|
@@ -408,7 +440,7 @@ function renderCliOnlyCommandHelp(entry) {
|
|
|
408
440
|
function renderSharedCommandHelp(pathSegments) {
|
|
409
441
|
const entry = SHARED_HELP_ENTRIES.map((candidate) => ({
|
|
410
442
|
candidate,
|
|
411
|
-
usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.
|
|
443
|
+
usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.includes("<") && !segment.startsWith("["))
|
|
412
444
|
})).filter(
|
|
413
445
|
({ usageSegments }) => usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index])
|
|
414
446
|
).sort((left, right) => right.usageSegments.length - left.usageSegments.length)[0]?.candidate;
|
|
@@ -456,6 +488,108 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
|
|
|
456
488
|
function hasBooleanFlag(args, flag) {
|
|
457
489
|
return args.includes(flag);
|
|
458
490
|
}
|
|
491
|
+
function parseAgentOperands(args, valueFlags, options = {}) {
|
|
492
|
+
const positionals = [];
|
|
493
|
+
const values = /* @__PURE__ */ new Map();
|
|
494
|
+
let parseOptions = true;
|
|
495
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
496
|
+
const arg = args[index];
|
|
497
|
+
if (parseOptions && arg === "--") {
|
|
498
|
+
parseOptions = false;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const inlineFlag = parseOptions ? [...valueFlags].find((flag) => arg.startsWith(`${flag}=`)) : void 0;
|
|
502
|
+
if (inlineFlag) {
|
|
503
|
+
if (values.has(inlineFlag)) throw new Error(`${inlineFlag} may only be provided once`);
|
|
504
|
+
const value = arg.slice(inlineFlag.length + 1).trim();
|
|
505
|
+
if (!value) throw new Error(`Missing value for ${inlineFlag}`);
|
|
506
|
+
values.set(inlineFlag, value);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (parseOptions && valueFlags.has(arg)) {
|
|
510
|
+
if (values.has(arg)) throw new Error(`${arg} may only be provided once`);
|
|
511
|
+
const value = args[index + 1];
|
|
512
|
+
if (!value || value === "--" || valueFlags.has(value)) throw new Error(`Missing value for ${arg}`);
|
|
513
|
+
values.set(arg, value.trim());
|
|
514
|
+
index += 1;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (parseOptions && arg.startsWith("-") && positionals.length < (options.allowFlagLikeValuesAfter ?? Number.POSITIVE_INFINITY)) {
|
|
518
|
+
throw new Error(`Unknown agent flag: ${arg}`);
|
|
519
|
+
}
|
|
520
|
+
positionals.push(arg);
|
|
521
|
+
}
|
|
522
|
+
return { positionals, values };
|
|
523
|
+
}
|
|
524
|
+
function requireAgentWorker(values) {
|
|
525
|
+
return requireValue(values.get("--worker")?.trim(), "--worker <label> is required");
|
|
526
|
+
}
|
|
527
|
+
function requireAgentSource(values) {
|
|
528
|
+
const raw = requireValue(values.get("--source")?.trim(), "--source worktree:<branch> is required for `agent start`");
|
|
529
|
+
const separator = raw.indexOf(":");
|
|
530
|
+
const type = raw.slice(0, separator);
|
|
531
|
+
const branchName = raw.slice(separator + 1).trim();
|
|
532
|
+
if (type !== "worktree" || separator < 1 || !branchName) {
|
|
533
|
+
throw new Error("Invalid --source. Expected worktree:<branch>");
|
|
534
|
+
}
|
|
535
|
+
return { type, branchName };
|
|
536
|
+
}
|
|
537
|
+
function rejectAgentGlobalScopes(options, allowProject) {
|
|
538
|
+
if (!allowProject && options.project) throw new Error("--project/-p is only supported for `agent start`");
|
|
539
|
+
if (options.branch) throw new Error("--branch/-b is not supported for `agent`; pass --source explicitly");
|
|
540
|
+
if (options.session) throw new Error("--session/-s is not supported for `agent`; pass the session id explicitly");
|
|
541
|
+
}
|
|
542
|
+
function parseBranchAgentCommand(options, args) {
|
|
543
|
+
const subcommand = requireValue(args[0], "Missing agent command");
|
|
544
|
+
const commandArgs = args.slice(1);
|
|
545
|
+
if (subcommand === "start") {
|
|
546
|
+
rejectAgentGlobalScopes(options, true);
|
|
547
|
+
const project = requireValue(options.project, "--project/-p is required for `agent start`");
|
|
548
|
+
const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker", "--source"]), { allowFlagLikeValuesAfter: 1 });
|
|
549
|
+
const agentType = requireValue(parsed.positionals[0], "Agent type is required for `agent start`");
|
|
550
|
+
if (!AGENT_TYPES.has(agentType)) {
|
|
551
|
+
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
552
|
+
}
|
|
553
|
+
const prompt = parsed.positionals.slice(1).join(" ").trim();
|
|
554
|
+
if (!prompt) throw new Error("Agent prompt is required for `agent start`");
|
|
555
|
+
return {
|
|
556
|
+
kind: "start",
|
|
557
|
+
project,
|
|
558
|
+
worker: requireAgentWorker(parsed.values),
|
|
559
|
+
source: requireAgentSource(parsed.values),
|
|
560
|
+
agentType,
|
|
561
|
+
prompt
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
rejectAgentGlobalScopes(options, false);
|
|
565
|
+
if (subcommand === "status" || subcommand === "stop") {
|
|
566
|
+
const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set());
|
|
567
|
+
const sessionId = requireValue(parsed.positionals[0], `Session id is required for \`agent ${subcommand}\``);
|
|
568
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected agent ${subcommand} argument: ${parsed.positionals[1]}`);
|
|
569
|
+
return { kind: subcommand, sessionId };
|
|
570
|
+
}
|
|
571
|
+
if (subcommand === "prompt") {
|
|
572
|
+
const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker"]), { allowFlagLikeValuesAfter: 1 });
|
|
573
|
+
const sessionId = requireValue(parsed.positionals[0], "Session id is required for `agent prompt`");
|
|
574
|
+
const prompt = parsed.positionals.slice(1).join(" ").trim();
|
|
575
|
+
if (!prompt) throw new Error("Prompt is required for `agent prompt`");
|
|
576
|
+
return { kind: "prompt", worker: requireAgentWorker(parsed.values), sessionId, prompt };
|
|
577
|
+
}
|
|
578
|
+
if (subcommand === "merge") {
|
|
579
|
+
const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker"]));
|
|
580
|
+
const sessionId = requireValue(parsed.positionals[0], "Session id is required for `agent merge`");
|
|
581
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected agent merge argument: ${parsed.positionals[1]}`);
|
|
582
|
+
return { kind: "merge", worker: requireAgentWorker(parsed.values), sessionId };
|
|
583
|
+
}
|
|
584
|
+
if (subcommand === "finish-merge") {
|
|
585
|
+
const parsed = parseAgentOperands(commandArgs, /* @__PURE__ */ new Set(["--worker", "--summary"]));
|
|
586
|
+
const resolverSessionId = requireValue(parsed.positionals[0], "Resolver session id is required for `agent finish-merge`");
|
|
587
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected agent finish-merge argument: ${parsed.positionals[1]}`);
|
|
588
|
+
const summary = requireValue(parsed.values.get("--summary")?.trim(), "--summary <text> is required for `agent finish-merge`");
|
|
589
|
+
return { kind: "finish-merge", worker: requireAgentWorker(parsed.values), resolverSessionId, summary };
|
|
590
|
+
}
|
|
591
|
+
throw new Error(`Unknown agent command: ${subcommand}`);
|
|
592
|
+
}
|
|
459
593
|
function assertAuthLoginArgs(args) {
|
|
460
594
|
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
461
595
|
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
@@ -1228,7 +1362,11 @@ function renderWorkspaceStatus(status) {
|
|
|
1228
1362
|
const lines = [
|
|
1229
1363
|
`Workspace HEAD: ${status.head ?? "(not initialized)"}`,
|
|
1230
1364
|
`Latest sync: ${status.latestSync ? `${status.latestSync.outcome} on ${status.latestSync.workerLabel}` : "(none)"}`,
|
|
1231
|
-
`Remediation: ${status.incident ? `${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}
|
|
1365
|
+
`Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
|
|
1366
|
+
`Materialization: ${status.materialization ? `${status.materialization.state}; ${status.materialization.materializedHead ?? "(none)"} -> ${status.materialization.desiredHead ?? "(none)"}; lag ${status.materialization.lagMs}ms` : "not initialized"}`,
|
|
1367
|
+
...status.workers.map(
|
|
1368
|
+
(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`
|
|
1369
|
+
)
|
|
1232
1370
|
];
|
|
1233
1371
|
return `${lines.join("\n")}
|
|
1234
1372
|
`;
|
|
@@ -1597,43 +1735,164 @@ async function setProjectEnvs(client, projectRef, args) {
|
|
|
1597
1735
|
}
|
|
1598
1736
|
return result;
|
|
1599
1737
|
}
|
|
1600
|
-
function
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
if (
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1738
|
+
function responseString(response, key) {
|
|
1739
|
+
const value = response[key];
|
|
1740
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1741
|
+
}
|
|
1742
|
+
function responseSource(response) {
|
|
1743
|
+
const source = response.source;
|
|
1744
|
+
if (!source || source.type !== "worktree" || !source.branchName) return void 0;
|
|
1745
|
+
return `${source.type}:${source.branchName}`;
|
|
1746
|
+
}
|
|
1747
|
+
function formatStatusValue(value) {
|
|
1748
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1749
|
+
if (typeof value === "object" && value !== null) {
|
|
1750
|
+
const status = value.status;
|
|
1751
|
+
if (typeof status === "string" && status.length > 0) return status;
|
|
1752
|
+
return JSON.stringify(value);
|
|
1753
|
+
}
|
|
1754
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
1755
|
+
}
|
|
1756
|
+
function asResponseRecord(value) {
|
|
1757
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
1758
|
+
}
|
|
1759
|
+
function appendMergeDetails(lines, response) {
|
|
1760
|
+
const merge = asResponseRecord(response.merge);
|
|
1761
|
+
if (!merge) return;
|
|
1762
|
+
const mergeStatus = formatStatusValue(merge.status);
|
|
1763
|
+
lines.push("", `Merge: ${mergeStatus ?? "pending"}`);
|
|
1764
|
+
for (const [label, key] of [
|
|
1765
|
+
["Attempt", "attemptId"],
|
|
1766
|
+
["Source", "sourceBranch"],
|
|
1767
|
+
["Target", "targetBranch"],
|
|
1768
|
+
["Commit", "commitHash"],
|
|
1769
|
+
["Resolver agent", "resolverSessionId"],
|
|
1770
|
+
["Resolver branch", "resolverBranch"],
|
|
1771
|
+
["Resolver worktree", "folderPath"]
|
|
1772
|
+
]) {
|
|
1773
|
+
const value = responseString(merge, key);
|
|
1774
|
+
if (value) lines.push(`${label}: ${value}`);
|
|
1775
|
+
}
|
|
1776
|
+
const conflictedFiles = Array.isArray(merge.conflictedFiles) ? merge.conflictedFiles.filter((file) => typeof file === "string") : [];
|
|
1777
|
+
if (conflictedFiles.length > 0) {
|
|
1778
|
+
lines.push("", "Conflicts:", ...conflictedFiles.map((file) => `- ${file}`));
|
|
1779
|
+
}
|
|
1780
|
+
const message = responseString(merge, "message");
|
|
1781
|
+
if (message) lines.push("", message);
|
|
1782
|
+
const failureReason = responseString(merge, "failureReason");
|
|
1783
|
+
if (failureReason) lines.push(`Failure: ${failureReason}`);
|
|
1784
|
+
const resolverSessionId = responseString(merge, "resolverSessionId");
|
|
1785
|
+
const worker = responseString(merge, "worker") ?? responseString(response, "worker");
|
|
1786
|
+
if (resolverSessionId && worker) {
|
|
1787
|
+
lines.push(
|
|
1788
|
+
"",
|
|
1789
|
+
"Resolver takeover:",
|
|
1790
|
+
` r5dctl agent stop ${resolverSessionId}`,
|
|
1791
|
+
` r5dctl agent finish-merge --worker ${worker} ${resolverSessionId} --summary "<summary>"`
|
|
1792
|
+
);
|
|
1618
1793
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1794
|
+
}
|
|
1795
|
+
function renderBranchAgentStart(agent) {
|
|
1796
|
+
const lines = [
|
|
1797
|
+
`Agent: ${responseString(agent, "sessionId") ?? "(unknown)"}`,
|
|
1798
|
+
`Status: ${formatStatusValue(agent.status) ?? "queued"}`,
|
|
1799
|
+
`Branch: ${responseString(agent, "branchName") ?? "(provisioning)"}`,
|
|
1800
|
+
`Source: ${responseSource(agent) ?? "(unknown)"}`,
|
|
1801
|
+
`Workspace head: ${responseString(agent, "workspaceHead") ?? "(pending)"}`,
|
|
1802
|
+
`Baseline: ${responseString(agent, "baselineCommit") ?? "(pending)"}`,
|
|
1803
|
+
`Worker: ${responseString(agent, "worker") ?? "(unassigned)"}`
|
|
1804
|
+
];
|
|
1805
|
+
const sessionUrl = responseString(agent, "sessionUrl");
|
|
1806
|
+
if (sessionUrl) lines.push(`Session: ${sessionUrl}`);
|
|
1807
|
+
const sessionId = responseString(agent, "sessionId");
|
|
1808
|
+
if (sessionId) lines.push("", `Next: r5dctl agent status ${sessionId}`);
|
|
1809
|
+
return `${lines.join("\n")}
|
|
1810
|
+
`;
|
|
1811
|
+
}
|
|
1812
|
+
function renderBranchAgentStatus(agent) {
|
|
1813
|
+
const status = formatStatusValue(agent.status) ?? "unknown";
|
|
1814
|
+
const lines = [`Agent: ${responseString(agent, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
|
|
1815
|
+
const runStatus = formatStatusValue(agent.runStatus);
|
|
1816
|
+
if (runStatus) lines.push(`Run: ${runStatus}`);
|
|
1817
|
+
const promptDisposition = responseString(agent, "promptDisposition");
|
|
1818
|
+
if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
|
|
1819
|
+
const branch = responseString(agent, "branchName");
|
|
1820
|
+
if (branch) lines.push(`Branch: ${branch}`);
|
|
1821
|
+
const source = responseSource(agent);
|
|
1822
|
+
if (source) lines.push(`Source: ${source}`);
|
|
1823
|
+
for (const [label, key] of [
|
|
1824
|
+
["Workspace head", "workspaceHead"],
|
|
1825
|
+
["Baseline", "baselineCommit"],
|
|
1826
|
+
["Head", "headCommit"],
|
|
1827
|
+
["Worker", "worker"],
|
|
1828
|
+
["Session", "sessionUrl"]
|
|
1829
|
+
]) {
|
|
1830
|
+
const value = responseString(agent, key);
|
|
1831
|
+
if (value) lines.push(`${label}: ${value}`);
|
|
1832
|
+
}
|
|
1833
|
+
const error = responseString(agent, "error");
|
|
1834
|
+
if (error) lines.push(`Error: ${error}`);
|
|
1835
|
+
const headCommitError = responseString(agent, "headCommitError");
|
|
1836
|
+
if (headCommitError) lines.push(`Head error: ${headCommitError}`);
|
|
1837
|
+
const diffSummary = responseString(agent, "diffSummary");
|
|
1838
|
+
if (diffSummary) lines.push("", diffSummary);
|
|
1839
|
+
const summary = responseString(agent, "summary");
|
|
1840
|
+
if (summary) lines.push("", summary);
|
|
1841
|
+
appendMergeDetails(lines, agent);
|
|
1842
|
+
const sessionId = responseString(agent, "sessionId");
|
|
1843
|
+
if (sessionId && !asResponseRecord(agent.merge)) {
|
|
1844
|
+
if (["provisioning", "queued", "running"].includes(status)) {
|
|
1845
|
+
lines.push("", "Next:", ` r5dctl agent status ${sessionId}`, ` r5dctl agent stop ${sessionId}`);
|
|
1846
|
+
} else if (["completed", "failed", "stopped"].includes(status)) {
|
|
1847
|
+
const worker = responseString(agent, "worker") ?? "<worker>";
|
|
1848
|
+
lines.push("", "Next:", ` r5dctl agent merge --worker ${worker} ${sessionId}`);
|
|
1849
|
+
}
|
|
1621
1850
|
}
|
|
1622
1851
|
return `${lines.join("\n")}
|
|
1623
1852
|
`;
|
|
1624
1853
|
}
|
|
1625
|
-
function
|
|
1626
|
-
if (result.
|
|
1627
|
-
|
|
1628
|
-
|
|
1854
|
+
function renderBranchAgentMerge(result) {
|
|
1855
|
+
if (asResponseRecord(result.merge)) return renderBranchAgentStatus(result);
|
|
1856
|
+
const wrapped = {
|
|
1857
|
+
sessionId: result.sessionId,
|
|
1858
|
+
status: result.status,
|
|
1859
|
+
worker: result.worker,
|
|
1860
|
+
merge: result
|
|
1861
|
+
};
|
|
1862
|
+
const lines = [];
|
|
1863
|
+
appendMergeDetails(lines, wrapped);
|
|
1864
|
+
return `${lines.slice(lines[0] === "" ? 1 : 0).join("\n")}
|
|
1629
1865
|
`;
|
|
1866
|
+
}
|
|
1867
|
+
async function dispatchBranchAgentCommand(client, command) {
|
|
1868
|
+
if (command.kind === "start") {
|
|
1869
|
+
const data2 = await client.projects.agents.start(command.project, {
|
|
1870
|
+
source: command.source,
|
|
1871
|
+
agentType: command.agentType,
|
|
1872
|
+
prompt: command.prompt,
|
|
1873
|
+
worker: command.worker,
|
|
1874
|
+
requestId: (0, import_node_crypto.randomUUID)()
|
|
1875
|
+
});
|
|
1876
|
+
return { data: data2, human: renderBranchAgentStart(data2) };
|
|
1630
1877
|
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1878
|
+
if (command.kind === "status") {
|
|
1879
|
+
const data2 = await client.agents.status(command.sessionId);
|
|
1880
|
+
return { data: data2, human: renderBranchAgentStatus(data2) };
|
|
1881
|
+
}
|
|
1882
|
+
if (command.kind === "prompt") {
|
|
1883
|
+
const data2 = await client.agents.sendPrompt(command.sessionId, { prompt: command.prompt, worker: command.worker });
|
|
1884
|
+
return { data: data2, human: renderBranchAgentStatus(data2) };
|
|
1885
|
+
}
|
|
1886
|
+
if (command.kind === "merge") {
|
|
1887
|
+
const data2 = await client.agents.merge(command.sessionId, { worker: command.worker });
|
|
1888
|
+
return { data: data2, human: renderBranchAgentMerge(data2) };
|
|
1889
|
+
}
|
|
1890
|
+
if (command.kind === "stop") {
|
|
1891
|
+
const data2 = await client.agents.stop(command.sessionId);
|
|
1892
|
+
return { data: data2, human: renderBranchAgentStatus(data2) };
|
|
1893
|
+
}
|
|
1894
|
+
const data = await client.agents.finishMerge(command.resolverSessionId, { worker: command.worker, summary: command.summary });
|
|
1895
|
+
return { data, human: renderBranchAgentMerge(data) };
|
|
1637
1896
|
}
|
|
1638
1897
|
function parseProjectUpdateArgs(args) {
|
|
1639
1898
|
const input = {};
|
|
@@ -1933,65 +2192,6 @@ Sessions: ${result.sessions.length}
|
|
|
1933
2192
|
write(session, renderSessionDescription(session));
|
|
1934
2193
|
return;
|
|
1935
2194
|
}
|
|
1936
|
-
if (first === "start-agent") {
|
|
1937
|
-
const projectRef = requireValue(args[1], "Missing project reference");
|
|
1938
|
-
const sourceBranch = requireValue(args[2], "Missing source branch");
|
|
1939
|
-
const agentType = requireValue(args[3], "Missing agent type");
|
|
1940
|
-
if (!AGENT_TYPES.has(agentType)) {
|
|
1941
|
-
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
1942
|
-
}
|
|
1943
|
-
const prompt = args.slice(4).join(" ").trim();
|
|
1944
|
-
if (!prompt) {
|
|
1945
|
-
throw new Error("Agent prompt is required");
|
|
1946
|
-
}
|
|
1947
|
-
const agent = await client.projects.agents.start(projectRef, {
|
|
1948
|
-
sourceBranch,
|
|
1949
|
-
agentType,
|
|
1950
|
-
prompt
|
|
1951
|
-
});
|
|
1952
|
-
write(agent, renderAgentStart(agent));
|
|
1953
|
-
return;
|
|
1954
|
-
}
|
|
1955
|
-
if (first === "agent-status") {
|
|
1956
|
-
const agent = await client.agents.status(requireValue(args[1], "Missing session id"));
|
|
1957
|
-
write(agent, renderAgentStatus(agent));
|
|
1958
|
-
return;
|
|
1959
|
-
}
|
|
1960
|
-
if (first === "send-prompt") {
|
|
1961
|
-
const sessionId = requireValue(args[1], "Missing session id");
|
|
1962
|
-
const prompt = args.slice(2).join(" ").trim();
|
|
1963
|
-
if (!prompt) {
|
|
1964
|
-
throw new Error("Prompt is required");
|
|
1965
|
-
}
|
|
1966
|
-
const agent = await client.agents.sendPrompt(sessionId, { prompt });
|
|
1967
|
-
write(agent, renderAgentStatus(agent));
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
if (first === "merge-changes") {
|
|
1971
|
-
const result = await client.projects.mergeChanges(requireValue(args[1], "Missing project reference"), {
|
|
1972
|
-
targetBranch: requireValue(args[2], "Missing target branch"),
|
|
1973
|
-
sourceBranch: requireValue(args[3], "Missing source branch")
|
|
1974
|
-
});
|
|
1975
|
-
write(result, renderMergeResult(result));
|
|
1976
|
-
return;
|
|
1977
|
-
}
|
|
1978
|
-
if (first === "continue-merge") {
|
|
1979
|
-
const result = await client.projects.continueMerge(requireValue(args[1], "Missing project reference"), {
|
|
1980
|
-
targetBranch: requireValue(args[2], "Missing target branch")
|
|
1981
|
-
});
|
|
1982
|
-
write(result, `Merge committed: ${result.commitHash}
|
|
1983
|
-
${result.message}
|
|
1984
|
-
`);
|
|
1985
|
-
return;
|
|
1986
|
-
}
|
|
1987
|
-
if (first === "abort-merge") {
|
|
1988
|
-
const result = await client.projects.abortMerge(requireValue(args[1], "Missing project reference"), {
|
|
1989
|
-
targetBranch: requireValue(args[2], "Missing target branch")
|
|
1990
|
-
});
|
|
1991
|
-
write(result, `${result.message}
|
|
1992
|
-
`);
|
|
1993
|
-
return;
|
|
1994
|
-
}
|
|
1995
2195
|
if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
|
|
1996
2196
|
const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
|
|
1997
2197
|
write(session, renderSessionDescription(session));
|
|
@@ -2087,6 +2287,12 @@ function resolveCommandExecution(options, rest) {
|
|
|
2087
2287
|
text: K8S_HELP_TEXT
|
|
2088
2288
|
};
|
|
2089
2289
|
}
|
|
2290
|
+
if (command === "agent" && commandArgs.length === 0) {
|
|
2291
|
+
return {
|
|
2292
|
+
kind: "cli-help",
|
|
2293
|
+
text: AGENT_HELP_TEXT
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2090
2296
|
if (trailingHelp) {
|
|
2091
2297
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
2092
2298
|
if (cliOnlyHelp) {
|
|
@@ -2491,91 +2697,8 @@ function resolveCommandExecution(options, rest) {
|
|
|
2491
2697
|
pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
|
|
2492
2698
|
};
|
|
2493
2699
|
}
|
|
2494
|
-
if (command === "
|
|
2495
|
-
|
|
2496
|
-
throw new Error("--project/-p is required for `start-agent`");
|
|
2497
|
-
}
|
|
2498
|
-
const sourceBranch = commandArgs[0] ?? options.branch;
|
|
2499
|
-
if (!sourceBranch) {
|
|
2500
|
-
throw new Error("Source branch is required for `start-agent`");
|
|
2501
|
-
}
|
|
2502
|
-
const agentType = requireValue(commandArgs[1], "Agent type is required for `start-agent`");
|
|
2503
|
-
if (!AGENT_TYPES.has(agentType)) {
|
|
2504
|
-
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
2505
|
-
}
|
|
2506
|
-
const prompt = commandArgs.slice(2).join(" ").trim();
|
|
2507
|
-
if (!prompt) {
|
|
2508
|
-
throw new Error("Agent prompt is required");
|
|
2509
|
-
}
|
|
2510
|
-
return {
|
|
2511
|
-
kind: "plugin",
|
|
2512
|
-
pluginArgs: ["start-agent", options.project, sourceBranch, agentType, prompt]
|
|
2513
|
-
};
|
|
2514
|
-
}
|
|
2515
|
-
if (command === "agent-status") {
|
|
2516
|
-
const sessionId = commandArgs[0] ?? options.session;
|
|
2517
|
-
if (!sessionId) {
|
|
2518
|
-
throw new Error("Session id is required for `agent-status`");
|
|
2519
|
-
}
|
|
2520
|
-
return {
|
|
2521
|
-
kind: "plugin",
|
|
2522
|
-
pluginArgs: ["agent-status", sessionId]
|
|
2523
|
-
};
|
|
2524
|
-
}
|
|
2525
|
-
if (command === "send-prompt") {
|
|
2526
|
-
const sessionId = options.session ?? commandArgs[0];
|
|
2527
|
-
if (!sessionId) {
|
|
2528
|
-
throw new Error("Session id is required for `send-prompt`");
|
|
2529
|
-
}
|
|
2530
|
-
const promptArgs = options.session ? commandArgs : commandArgs.slice(1);
|
|
2531
|
-
const prompt = promptArgs.join(" ").trim();
|
|
2532
|
-
if (!prompt) {
|
|
2533
|
-
throw new Error("Prompt is required for `send-prompt`");
|
|
2534
|
-
}
|
|
2535
|
-
return {
|
|
2536
|
-
kind: "plugin",
|
|
2537
|
-
pluginArgs: ["send-prompt", sessionId, prompt]
|
|
2538
|
-
};
|
|
2539
|
-
}
|
|
2540
|
-
if (command === "merge-changes") {
|
|
2541
|
-
if (!options.project) {
|
|
2542
|
-
throw new Error("--project/-p is required for `merge-changes`");
|
|
2543
|
-
}
|
|
2544
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2545
|
-
if (!targetBranch) {
|
|
2546
|
-
throw new Error("Target branch is required for `merge-changes`");
|
|
2547
|
-
}
|
|
2548
|
-
const sourceBranch = requireValue(commandArgs[1], "Sub-agent branch is required for `merge-changes`");
|
|
2549
|
-
return {
|
|
2550
|
-
kind: "plugin",
|
|
2551
|
-
pluginArgs: ["merge-changes", options.project, targetBranch, sourceBranch]
|
|
2552
|
-
};
|
|
2553
|
-
}
|
|
2554
|
-
if (command === "continue-merge") {
|
|
2555
|
-
if (!options.project) {
|
|
2556
|
-
throw new Error("--project/-p is required for `continue-merge`");
|
|
2557
|
-
}
|
|
2558
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2559
|
-
if (!targetBranch) {
|
|
2560
|
-
throw new Error("Target branch is required for `continue-merge`");
|
|
2561
|
-
}
|
|
2562
|
-
return {
|
|
2563
|
-
kind: "plugin",
|
|
2564
|
-
pluginArgs: ["continue-merge", options.project, targetBranch]
|
|
2565
|
-
};
|
|
2566
|
-
}
|
|
2567
|
-
if (command === "abort-merge") {
|
|
2568
|
-
if (!options.project) {
|
|
2569
|
-
throw new Error("--project/-p is required for `abort-merge`");
|
|
2570
|
-
}
|
|
2571
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2572
|
-
if (!targetBranch) {
|
|
2573
|
-
throw new Error("Target branch is required for `abort-merge`");
|
|
2574
|
-
}
|
|
2575
|
-
return {
|
|
2576
|
-
kind: "plugin",
|
|
2577
|
-
pluginArgs: ["abort-merge", options.project, targetBranch]
|
|
2578
|
-
};
|
|
2700
|
+
if (command === "agent") {
|
|
2701
|
+
return { kind: "agent", command: parseBranchAgentCommand(options, commandArgs) };
|
|
2579
2702
|
}
|
|
2580
2703
|
if (command === "answer-questions") {
|
|
2581
2704
|
const sessionId = options.session;
|
|
@@ -2625,6 +2748,11 @@ async function runCommand(argv) {
|
|
|
2625
2748
|
process.stdout.write(execution.text);
|
|
2626
2749
|
return 0;
|
|
2627
2750
|
}
|
|
2751
|
+
if (execution.kind === "agent") {
|
|
2752
|
+
const result = await dispatchBranchAgentCommand(client, execution.command);
|
|
2753
|
+
writeDataOutput(options.json, result.data, result.human);
|
|
2754
|
+
return 0;
|
|
2755
|
+
}
|
|
2628
2756
|
if (execution.kind === "shell") {
|
|
2629
2757
|
const project = await client.projects.describe(options.project);
|
|
2630
2758
|
return await (0, import_shell.runR5dctlShell)({
|
|
@@ -2674,6 +2802,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2674
2802
|
0 && (module.exports = {
|
|
2675
2803
|
advanceTransientDevicePollBackoff,
|
|
2676
2804
|
collectK8sUsage,
|
|
2805
|
+
dispatchBranchAgentCommand,
|
|
2677
2806
|
formatK8sCpu,
|
|
2678
2807
|
formatK8sMemory,
|
|
2679
2808
|
formatProcessAge,
|
|
@@ -2685,6 +2814,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2685
2814
|
handleAuthLogin,
|
|
2686
2815
|
main,
|
|
2687
2816
|
parseAnswerFlags,
|
|
2817
|
+
parseBranchAgentCommand,
|
|
2688
2818
|
parseConversationRenderArgs,
|
|
2689
2819
|
parseConversationWorkDetailArgs,
|
|
2690
2820
|
parseEnvFlags,
|
|
@@ -2698,6 +2828,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2698
2828
|
parseSetEnvArgs,
|
|
2699
2829
|
parseShellArgs,
|
|
2700
2830
|
readDotenvFile,
|
|
2831
|
+
renderBranchAgentMerge,
|
|
2832
|
+
renderBranchAgentStart,
|
|
2833
|
+
renderBranchAgentStatus,
|
|
2701
2834
|
renderConversationNodeResponse,
|
|
2702
2835
|
renderConversationOverviewResponse,
|
|
2703
2836
|
renderConversationResponse,
|