@ricsam/r5dctl 0.0.101 → 0.0.104
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 +18 -1
- package/dist/cjs/cli.cjs +374 -25
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +362 -25
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +53 -1
- package/package.json +2 -2
package/dist/mjs/cli.mjs
CHANGED
|
@@ -72,14 +72,19 @@ const K8S_HELP_TEXT = [
|
|
|
72
72
|
].join("\n");
|
|
73
73
|
const SESSION_RUN_HELP_TEXT = [
|
|
74
74
|
"Usage:",
|
|
75
|
-
' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
76
|
-
' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
75
|
+
' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
|
|
76
|
+
' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> [--working-tree carry|clean] --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
|
|
77
77
|
" r5dctl session status <session-id>",
|
|
78
78
|
' r5dctl session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
|
|
79
79
|
" r5dctl session stop <session-id>",
|
|
80
|
+
" r5dctl session process-log <run-id> [--tail <n>] [--grep <pattern>]",
|
|
80
81
|
"",
|
|
81
82
|
"Create, inspect, resume, and stop ordinary agent sessions.",
|
|
83
|
+
"A new worktree starts with the source checkout's uncommitted changes (--working-tree carry, the default);",
|
|
84
|
+
"--working-tree clean checks out the source branch's current commit only. Start and status print the mode, never a commit hash.",
|
|
82
85
|
"Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
|
|
86
|
+
"process-log prints a shell run's stored stdout/stderr from the server: the whole log, its last n lines,",
|
|
87
|
+
"or the lines matching a JavaScript regular expression. The log is not a file on the worker.",
|
|
83
88
|
""
|
|
84
89
|
].join("\n");
|
|
85
90
|
const SHARED_HELP_ENTRIES = [
|
|
@@ -109,6 +114,21 @@ const SHARED_HELP_ENTRIES = [
|
|
|
109
114
|
{ section: "projects", usage: "delete project <namespace/name|id>", description: "Delete a project." },
|
|
110
115
|
{ section: "branches", usage: "-p <project> get branches", description: "List branches for a project." },
|
|
111
116
|
{ section: "branches", usage: "-p <project> describe branch <branch>", description: "Show branch URLs and sessions." },
|
|
117
|
+
{
|
|
118
|
+
section: "branches",
|
|
119
|
+
usage: "-p <project> create branch <branch> --source worktree:<branch> [--working-tree <carry|clean>] [--worker <label>]",
|
|
120
|
+
description: "Create a linked worktree branch from an existing branch's checkout and print the operation id to poll."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
section: "branches",
|
|
124
|
+
usage: "-p <project> describe branch-operation <operation-id>",
|
|
125
|
+
description: "Show whether a branch creation is provisioning, ready, or failed."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
section: "branches",
|
|
129
|
+
usage: "-p <project> delete branch <branch> [--confirm]",
|
|
130
|
+
description: "Delete a branch and its sessions through the platform; without --confirm, only preview what would be deleted."
|
|
131
|
+
},
|
|
112
132
|
{
|
|
113
133
|
section: "envs",
|
|
114
134
|
usage: "-p <project> get envs [--show-values]",
|
|
@@ -179,8 +199,8 @@ const SHARED_HELP_ENTRIES = [
|
|
|
179
199
|
},
|
|
180
200
|
{
|
|
181
201
|
section: "sessions",
|
|
182
|
-
usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch>) --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
183
|
-
description: "Start an ordinary agent session on an explicit existing or new worktree."
|
|
202
|
+
usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch> [--working-tree carry|clean]) --model <tier> [-f|--follow|--watch] [--tools] [--parent-session <session-id>] "<prompt>"',
|
|
203
|
+
description: "Start an ordinary agent session on an explicit existing or new worktree. A new worktree carries the source checkout's uncommitted changes unless --working-tree clean is passed. --parent-session links it like start_subagent: the parent is notified when the run finishes."
|
|
184
204
|
},
|
|
185
205
|
{
|
|
186
206
|
section: "sessions",
|
|
@@ -197,6 +217,11 @@ const SHARED_HELP_ENTRIES = [
|
|
|
197
217
|
usage: "session stop <session-id>",
|
|
198
218
|
description: "Stop an active session generation."
|
|
199
219
|
},
|
|
220
|
+
{
|
|
221
|
+
section: "sessions",
|
|
222
|
+
usage: "session process-log <run-id> [--tail <n>] [--grep <pattern>]",
|
|
223
|
+
description: "Print a shell run's stored stdout/stderr from the server: the whole log, its last lines, or lines matching a regular expression."
|
|
224
|
+
},
|
|
200
225
|
{
|
|
201
226
|
section: "sessions",
|
|
202
227
|
usage: '-s <session-id> answer-questions --worker <label> --mode <mode> --model <tier> -a1 "<answer>" ...',
|
|
@@ -450,8 +475,8 @@ function parseCommandOperands(args, valueFlags, booleanFlags = /* @__PURE__ */ n
|
|
|
450
475
|
function requireWorker(values) {
|
|
451
476
|
return requireValue(values.get("--worker")?.trim(), "--worker <label> is required");
|
|
452
477
|
}
|
|
453
|
-
function parseWorktreeSource(rawValue) {
|
|
454
|
-
const raw = requireValue(rawValue?.trim(),
|
|
478
|
+
function parseWorktreeSource(rawValue, requiredMessage = "--source worktree:<branch> is required with --new-worktree") {
|
|
479
|
+
const raw = requireValue(rawValue?.trim(), requiredMessage);
|
|
455
480
|
const separator = raw.indexOf(":");
|
|
456
481
|
const type = raw.slice(0, separator);
|
|
457
482
|
const branchName = raw.slice(separator + 1).trim();
|
|
@@ -460,6 +485,12 @@ function parseWorktreeSource(rawValue) {
|
|
|
460
485
|
}
|
|
461
486
|
return { type, branchName };
|
|
462
487
|
}
|
|
488
|
+
function parseWorkingTreeMode(rawValue) {
|
|
489
|
+
if (rawValue === void 0) return "carry";
|
|
490
|
+
const mode = rawValue.trim();
|
|
491
|
+
if (mode !== "carry" && mode !== "clean") throw new Error("Invalid --working-tree. Expected carry or clean");
|
|
492
|
+
return mode;
|
|
493
|
+
}
|
|
463
494
|
function requireModel(value) {
|
|
464
495
|
const model = requireValue(value?.trim(), "--model <tier> is required");
|
|
465
496
|
if (!MODEL_TIERS.has(model)) throw new Error("Invalid --model. Expected one of: low, medium, high, max");
|
|
@@ -472,6 +503,103 @@ function requireSessionMode(value) {
|
|
|
472
503
|
}
|
|
473
504
|
return mode;
|
|
474
505
|
}
|
|
506
|
+
function parseBranchCreateArgs(args) {
|
|
507
|
+
const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--source", "--working-tree", "--worker"]), /* @__PURE__ */ new Map(), "create branch", 1);
|
|
508
|
+
if (parsed.positionals.length > 0) throw new Error(`Unexpected create branch argument: ${parsed.positionals[0]}`);
|
|
509
|
+
const worker = parsed.values.get("--worker")?.trim();
|
|
510
|
+
if (parsed.values.has("--worker") && !worker) throw new Error("Missing value for --worker");
|
|
511
|
+
return {
|
|
512
|
+
source: parseWorktreeSource(parsed.values.get("--source"), "--source worktree:<branch> is required for `create branch`"),
|
|
513
|
+
workingTree: parseWorkingTreeMode(parsed.values.get("--working-tree")),
|
|
514
|
+
...worker ? { worker } : {}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function parseBranchDeleteArgs(args) {
|
|
518
|
+
let confirmed = false;
|
|
519
|
+
for (const arg of args) {
|
|
520
|
+
if (arg === "--confirm") {
|
|
521
|
+
confirmed = true;
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
throw new Error(arg.startsWith("-") ? `Unknown delete branch flag: ${arg}` : `Unexpected delete branch argument: ${arg}`);
|
|
525
|
+
}
|
|
526
|
+
return { confirmed };
|
|
527
|
+
}
|
|
528
|
+
const PRIMARY_BRANCH_NAME = "main";
|
|
529
|
+
function isUnsupportedServerRoute(error) {
|
|
530
|
+
return error instanceof R5dctlApiError && error.status === 404 && extractApiErrorMessage(error) === "Not Found";
|
|
531
|
+
}
|
|
532
|
+
async function requireServerRouteSupport(command, request) {
|
|
533
|
+
try {
|
|
534
|
+
return await request();
|
|
535
|
+
} catch (error) {
|
|
536
|
+
if (isUnsupportedServerRoute(error)) {
|
|
537
|
+
throw new Error(`This r5d server does not support \`${command}\` yet. Update the server, or manage the branch from the web sidebar.`);
|
|
538
|
+
}
|
|
539
|
+
throw error;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async function createBranchThroughPlatform(client, projectRef, input) {
|
|
543
|
+
return await requireServerRouteSupport(
|
|
544
|
+
"create branch",
|
|
545
|
+
() => client.projects.branches.create(projectRef, {
|
|
546
|
+
requestId: randomUUID(),
|
|
547
|
+
branchName: input.branchName,
|
|
548
|
+
source: input.source,
|
|
549
|
+
workingTree: input.workingTree,
|
|
550
|
+
...input.worker ? { worker: input.worker } : {}
|
|
551
|
+
})
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
async function deleteBranchThroughPlatform(client, projectRef, branchName, options) {
|
|
555
|
+
if (branchName === PRIMARY_BRANCH_NAME) throw new Error(`The primary branch ${branchName} cannot be deleted`);
|
|
556
|
+
const branch = await client.projects.branches.describe(projectRef, branchName);
|
|
557
|
+
if (!options.confirmed) return { kind: "preview", branch };
|
|
558
|
+
const result = await requireServerRouteSupport("delete branch", () => client.projects.branches.delete(projectRef, branchName));
|
|
559
|
+
return { kind: "deleted", result };
|
|
560
|
+
}
|
|
561
|
+
function renderBranchDeletionPreview(branch) {
|
|
562
|
+
const count = branch.sessions.length;
|
|
563
|
+
const lines = [
|
|
564
|
+
`Project: ${branch.project.path}`,
|
|
565
|
+
`Branch: ${branch.branchName}`,
|
|
566
|
+
`Sessions: ${count}`,
|
|
567
|
+
...branch.sessions.map((session) => ` ${session.id} ${session.name ?? "(unnamed)"}`),
|
|
568
|
+
`Deleting this branch removes its checkout from every connected worker and deletes the ${count} session(s) above.`
|
|
569
|
+
];
|
|
570
|
+
return `${lines.join("\n")}
|
|
571
|
+
`;
|
|
572
|
+
}
|
|
573
|
+
function branchDeletionConfirmationMessage(branch) {
|
|
574
|
+
return `Nothing was deleted. Re-run with --confirm to delete branch ${branch.branchName} and its ${branch.sessions.length} session(s).`;
|
|
575
|
+
}
|
|
576
|
+
function renderBranchDeletion(result) {
|
|
577
|
+
const lines = [
|
|
578
|
+
`Deleted branch ${result.branchName} and ${result.deletedSessionIds.length} session(s).`,
|
|
579
|
+
...result.deletedSessionIds.map((sessionId) => ` ${sessionId}`)
|
|
580
|
+
];
|
|
581
|
+
return `${lines.join("\n")}
|
|
582
|
+
`;
|
|
583
|
+
}
|
|
584
|
+
function renderBranchOperation(operation, projectRef) {
|
|
585
|
+
const lines = [
|
|
586
|
+
`Operation: ${operation.id}`,
|
|
587
|
+
`Branch: ${operation.branchName}`,
|
|
588
|
+
`Source: worktree:${operation.source.branchName}`,
|
|
589
|
+
`Working tree: ${operation.workingTree}`,
|
|
590
|
+
`Worker: ${operation.worker ?? "(pending)"}`,
|
|
591
|
+
`Status: ${operation.status}`
|
|
592
|
+
];
|
|
593
|
+
if (operation.error) lines.push(`Error: ${operation.error}`);
|
|
594
|
+
if (operation.cleanupPending) lines.push("Cleanup: pending");
|
|
595
|
+
if (operation.status === "provisioning") {
|
|
596
|
+
lines.push(`Check: r5dctl -p ${projectRef} describe branch-operation ${operation.id}`);
|
|
597
|
+
} else if (operation.status === "ready") {
|
|
598
|
+
lines.push(`Branch state: ${formatBranchStateHint(operation.branchName)}`);
|
|
599
|
+
}
|
|
600
|
+
return `${lines.join("\n")}
|
|
601
|
+
`;
|
|
602
|
+
}
|
|
475
603
|
function rejectSessionRunGlobalScopes(options, allowProject) {
|
|
476
604
|
if (!allowProject && options.project) throw new Error("--project/-p is only supported for `session start`");
|
|
477
605
|
if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
|
|
@@ -485,7 +613,7 @@ function parseSessionRunCommand(options, args) {
|
|
|
485
613
|
const project = requireValue(options.project, "--project/-p is required for `session start`");
|
|
486
614
|
const parsed = parseCommandOperands(
|
|
487
615
|
commandArgs,
|
|
488
|
-
/* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--model"]),
|
|
616
|
+
/* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--working-tree", "--model", "--parent-session"]),
|
|
489
617
|
/* @__PURE__ */ new Map([
|
|
490
618
|
["-f", "--follow"],
|
|
491
619
|
["--follow", "--follow"],
|
|
@@ -504,6 +632,9 @@ function parseSessionRunCommand(options, args) {
|
|
|
504
632
|
}
|
|
505
633
|
const sourceValue = parsed.values.get("--source");
|
|
506
634
|
if (worktree && sourceValue) throw new Error("--source is only supported with --new-worktree");
|
|
635
|
+
const workingTreeValue = parsed.values.get("--working-tree");
|
|
636
|
+
if (worktree && workingTreeValue !== void 0) throw new Error("--working-tree is only supported with --new-worktree");
|
|
637
|
+
const parentSessionId = parsed.values.get("--parent-session")?.trim();
|
|
507
638
|
return {
|
|
508
639
|
kind: "start",
|
|
509
640
|
project,
|
|
@@ -512,7 +643,8 @@ function parseSessionRunCommand(options, args) {
|
|
|
512
643
|
prompt,
|
|
513
644
|
follow: parsed.booleans.has("--follow"),
|
|
514
645
|
includeTools: parsed.booleans.has("--tools"),
|
|
515
|
-
...
|
|
646
|
+
...parentSessionId ? { parentSessionId } : {},
|
|
647
|
+
...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue), workingTree: parseWorkingTreeMode(workingTreeValue) }
|
|
516
648
|
};
|
|
517
649
|
}
|
|
518
650
|
rejectSessionRunGlobalScopes(options, false);
|
|
@@ -538,6 +670,16 @@ function parseSessionRunCommand(options, args) {
|
|
|
538
670
|
}
|
|
539
671
|
throw new Error(`Unknown session command: ${subcommand}`);
|
|
540
672
|
}
|
|
673
|
+
function parseSessionProcessLogArgs(args) {
|
|
674
|
+
const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--tail", "--grep"]), /* @__PURE__ */ new Map(), "session process-log");
|
|
675
|
+
const runId = requireValue(parsed.positionals[0], "Process run id is required for `session process-log`");
|
|
676
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected session process-log argument: ${parsed.positionals[1]}`);
|
|
677
|
+
const tailValue = parsed.values.get("--tail");
|
|
678
|
+
const tail = tailValue === void 0 ? void 0 : Number(tailValue);
|
|
679
|
+
if (tail !== void 0 && (!Number.isInteger(tail) || tail < 1)) throw new Error("--tail must be a positive integer");
|
|
680
|
+
const grep = parsed.values.get("--grep");
|
|
681
|
+
return { runId, ...tail !== void 0 ? { tail } : {}, ...grep !== void 0 ? { grep } : {} };
|
|
682
|
+
}
|
|
541
683
|
function assertAuthLoginArgs(args) {
|
|
542
684
|
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
543
685
|
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
@@ -1364,10 +1506,34 @@ function renderWorkspaceStatus(status) {
|
|
|
1364
1506
|
`Workspace HEAD: ${status.head ?? "(not initialized)"}`,
|
|
1365
1507
|
`Latest sync: ${status.latestSync ? `${status.latestSync.outcome} on ${status.latestSync.workerLabel}` : "(none)"}`,
|
|
1366
1508
|
`Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
|
|
1367
|
-
...status.workers.
|
|
1368
|
-
|
|
1369
|
-
|
|
1509
|
+
...status.workers.flatMap((worker) => [
|
|
1510
|
+
`Worker ${worker.workerLabel}: ${worker.configured ? "configured" : "configuring"}; head ${worker.localHead ?? "(none)"}; pending checkouts ${worker.pendingCheckouts.length}`,
|
|
1511
|
+
...renderMissingCheckouts(worker.workerLabel, worker.missingCheckouts ?? [])
|
|
1512
|
+
])
|
|
1370
1513
|
];
|
|
1514
|
+
if (status.latestSync?.missingCheckouts?.length && !status.workers.some((worker) => worker.missingCheckouts?.length)) {
|
|
1515
|
+
lines.push(...renderMissingCheckouts(status.latestSync.workerLabel, status.latestSync.missingCheckouts));
|
|
1516
|
+
}
|
|
1517
|
+
return `${lines.join("\n")}
|
|
1518
|
+
`;
|
|
1519
|
+
}
|
|
1520
|
+
const CHECKOUT_RECOVERY_HINT = "delete the branch with `r5dctl -p <project> delete branch <branch> --confirm`, or reconnect the worker to recreate the checkout";
|
|
1521
|
+
function renderMissingCheckouts(workerLabel, missingCheckouts) {
|
|
1522
|
+
if (missingCheckouts.length === 0) return [];
|
|
1523
|
+
return [
|
|
1524
|
+
`Missing checkouts on ${workerLabel}: ${missingCheckouts.map((checkout) => `${checkout.projectId}/${checkout.branchName} (${checkout.reason})`).join(", ")}`,
|
|
1525
|
+
` Workspace sync skips these branches; ${CHECKOUT_RECOVERY_HINT}.`
|
|
1526
|
+
];
|
|
1527
|
+
}
|
|
1528
|
+
function renderBranchList(branches) {
|
|
1529
|
+
if (branches.length === 0) return "No branches found.\n";
|
|
1530
|
+
const lines = branches.map((branch) => {
|
|
1531
|
+
const checkout = branch.checkoutState ? ` checkout=${branch.checkoutState}${branch.checkoutStateWorkerLabel ? ` on ${branch.checkoutStateWorkerLabel}` : ""}` : "";
|
|
1532
|
+
return `${branch.branchName} sessions=${branch.sessionCount}${checkout}`;
|
|
1533
|
+
});
|
|
1534
|
+
if (branches.some((branch) => branch.checkoutState)) {
|
|
1535
|
+
lines.push(`Workspace sync skips a branch whose checkout is missing or not a worktree; ${CHECKOUT_RECOVERY_HINT}.`);
|
|
1536
|
+
}
|
|
1371
1537
|
return `${lines.join("\n")}
|
|
1372
1538
|
`;
|
|
1373
1539
|
}
|
|
@@ -1595,6 +1761,32 @@ function renderProcessList(processes, nowMs = Date.now()) {
|
|
|
1595
1761
|
function renderProcessHistory(processes, nowMs = Date.now()) {
|
|
1596
1762
|
return processes.length === 0 ? "No process history.\n" : renderProcessList(processes, nowMs);
|
|
1597
1763
|
}
|
|
1764
|
+
function renderProcessLog(log) {
|
|
1765
|
+
const sections = [];
|
|
1766
|
+
for (const [name, stream] of [
|
|
1767
|
+
["stdout", log.stdout],
|
|
1768
|
+
["stderr", log.stderr]
|
|
1769
|
+
]) {
|
|
1770
|
+
if (stream.text.length === 0) continue;
|
|
1771
|
+
sections.push(`==> ${name} <==
|
|
1772
|
+
${stream.text.endsWith("\n") ? stream.text : `${stream.text}
|
|
1773
|
+
`}`);
|
|
1774
|
+
}
|
|
1775
|
+
const selection = [
|
|
1776
|
+
...log.grep !== void 0 ? [`lines matching /${log.grep}/`] : [],
|
|
1777
|
+
...log.tailLines !== void 0 ? [`last ${log.tailLines} lines`] : []
|
|
1778
|
+
];
|
|
1779
|
+
const cut = [log.stdout.truncated ? "stdout" : void 0, log.stderr.truncated ? "stderr" : void 0].filter(Boolean);
|
|
1780
|
+
const summary = [
|
|
1781
|
+
`Run ${log.runId} (${log.status}${log.exitCode !== void 0 ? `, exit ${log.exitCode}` : ""}): `,
|
|
1782
|
+
`stdout ${log.stdout.totalBytes} bytes, stderr ${log.stderr.totalBytes} bytes`,
|
|
1783
|
+
selection.length > 0 ? `; showing ${selection.join(", ")}` : "",
|
|
1784
|
+
cut.length > 0 ? `; ${cut.join(" and ")} cut to ${log.maxBytesPerStream} bytes, narrow with --tail or --grep` : "",
|
|
1785
|
+
sections.length === 0 ? "; no output to show" : ""
|
|
1786
|
+
].join("");
|
|
1787
|
+
return { text: sections.join(""), summary: `${summary}
|
|
1788
|
+
` };
|
|
1789
|
+
}
|
|
1598
1790
|
function renderProcessInspection(process2) {
|
|
1599
1791
|
return [
|
|
1600
1792
|
`Run: ${process2.runId}`,
|
|
@@ -1748,13 +1940,22 @@ function formatStatusValue(value) {
|
|
|
1748
1940
|
}
|
|
1749
1941
|
return value === void 0 || value === null ? void 0 : String(value);
|
|
1750
1942
|
}
|
|
1943
|
+
function pushWorktreeOriginLines(lines, session) {
|
|
1944
|
+
const source = responseString(session, "sourceBranch");
|
|
1945
|
+
const mode = responseString(session, "workingTree");
|
|
1946
|
+
if (!source || !mode) return;
|
|
1947
|
+
lines.push(`Source: ${source}`);
|
|
1948
|
+
const detail = mode === "carry" ? `carried ${source}'s uncommitted changes at launch` : mode === "clean" ? `clean checkout of ${source}'s commit at launch` : void 0;
|
|
1949
|
+
lines.push(`Working tree: ${mode}${detail ? ` (${detail})` : ""}`);
|
|
1950
|
+
}
|
|
1751
1951
|
function renderSessionRunStart(session) {
|
|
1752
1952
|
const lines = [
|
|
1753
1953
|
`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
|
|
1754
1954
|
`Status: ${formatStatusValue(session.status) ?? "queued"}`,
|
|
1755
|
-
`Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}
|
|
1756
|
-
`Worker: ${responseString(session, "worker") ?? "(unassigned)"}`
|
|
1955
|
+
`Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`
|
|
1757
1956
|
];
|
|
1957
|
+
pushWorktreeOriginLines(lines, session);
|
|
1958
|
+
lines.push(`Worker: ${responseString(session, "worker") ?? "(unassigned)"}`);
|
|
1758
1959
|
const sessionUrl = responseString(session, "sessionUrl");
|
|
1759
1960
|
if (sessionUrl) lines.push(`URL: ${sessionUrl}`);
|
|
1760
1961
|
const sessionId = responseString(session, "sessionId");
|
|
@@ -1762,29 +1963,50 @@ function renderSessionRunStart(session) {
|
|
|
1762
1963
|
return `${lines.join("\n")}
|
|
1763
1964
|
`;
|
|
1764
1965
|
}
|
|
1966
|
+
function formatBranchStateHint(branch) {
|
|
1967
|
+
const target = branch ?? "<branch>";
|
|
1968
|
+
const commands = [
|
|
1969
|
+
`git merge-base <your-branch> ${target}`,
|
|
1970
|
+
`git log --oneline <your-branch>..${target}`,
|
|
1971
|
+
"git status in the branch folder"
|
|
1972
|
+
];
|
|
1973
|
+
return `inspect the worktree with git (${commands.join("; ")})`;
|
|
1974
|
+
}
|
|
1975
|
+
function formatQueuedAt(value) {
|
|
1976
|
+
const time = Date.parse(value);
|
|
1977
|
+
return Number.isNaN(time) ? value : new Date(time).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1978
|
+
}
|
|
1979
|
+
function formatProvisioningQueueLine(session) {
|
|
1980
|
+
const position = session.provisioningQueuePosition;
|
|
1981
|
+
const queuedAt = responseString(session, "provisioningQueuedAt");
|
|
1982
|
+
if (typeof position !== "number" || !Number.isInteger(position) || position < 0 || !queuedAt) return void 0;
|
|
1983
|
+
const since = formatQueuedAt(queuedAt);
|
|
1984
|
+
if (position === 0) return `Provisioning: worker started the branch operation at ${since}`;
|
|
1985
|
+
return `Provisioning: queued behind ${position} worker operation${position === 1 ? "" : "s"} since ${since}`;
|
|
1986
|
+
}
|
|
1765
1987
|
function renderSessionRunStatus(session) {
|
|
1766
1988
|
const status = formatStatusValue(session.status) ?? "unknown";
|
|
1767
1989
|
const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
|
|
1768
1990
|
const runStatus = formatStatusValue(session.runStatus);
|
|
1769
1991
|
if (runStatus) lines.push(`Run: ${runStatus}`);
|
|
1992
|
+
const provisioningQueue = formatProvisioningQueueLine(session);
|
|
1993
|
+
if (provisioningQueue) lines.push(provisioningQueue);
|
|
1770
1994
|
const promptDisposition = responseString(session, "promptDisposition");
|
|
1771
1995
|
if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
|
|
1772
1996
|
const branch = responseString(session, "branchName");
|
|
1773
1997
|
if (branch) lines.push(`Worktree: ${branch}`);
|
|
1998
|
+
pushWorktreeOriginLines(lines, session);
|
|
1774
1999
|
for (const [label, key] of [
|
|
1775
|
-
["Workspace head", "workspaceHead"],
|
|
1776
|
-
["Baseline", "baselineCommit"],
|
|
1777
|
-
["Head", "headCommit"],
|
|
1778
2000
|
["Worker", "worker"],
|
|
2001
|
+
["Parent session", "parentSessionId"],
|
|
1779
2002
|
["Session", "sessionUrl"]
|
|
1780
2003
|
]) {
|
|
1781
2004
|
const value = responseString(session, key);
|
|
1782
2005
|
if (value) lines.push(`${label}: ${value}`);
|
|
1783
2006
|
}
|
|
2007
|
+
lines.push(`Branch state: ${formatBranchStateHint(branch)}`);
|
|
1784
2008
|
const error = responseString(session, "error");
|
|
1785
2009
|
if (error) lines.push(`Error: ${error}`);
|
|
1786
|
-
const headCommitError = responseString(session, "headCommitError");
|
|
1787
|
-
if (headCommitError) lines.push(`Head error: ${headCommitError}`);
|
|
1788
2010
|
const diffSummary = responseString(session, "diffSummary");
|
|
1789
2011
|
if (diffSummary) lines.push("", diffSummary);
|
|
1790
2012
|
const summary = responseString(session, "summary");
|
|
@@ -1802,13 +2024,14 @@ async function dispatchSessionRunCommand(client, command) {
|
|
|
1802
2024
|
prompt: command.prompt,
|
|
1803
2025
|
worker: command.worker,
|
|
1804
2026
|
model: command.model,
|
|
1805
|
-
requestId: randomUUID()
|
|
2027
|
+
requestId: randomUUID(),
|
|
2028
|
+
...command.parentSessionId ? { parentSessionId: command.parentSessionId } : {}
|
|
1806
2029
|
};
|
|
1807
2030
|
let input;
|
|
1808
2031
|
if (command.worktree !== void 0) {
|
|
1809
2032
|
input = { ...commonInput, worktree: command.worktree };
|
|
1810
2033
|
} else if (command.newWorktree !== void 0 && command.source !== void 0) {
|
|
1811
|
-
input = { ...commonInput, newWorktree: command.newWorktree, source: command.source };
|
|
2034
|
+
input = { ...commonInput, newWorktree: command.newWorktree, source: command.source, workingTree: command.workingTree };
|
|
1812
2035
|
} else {
|
|
1813
2036
|
throw new Error("Session start requires an existing or new worktree");
|
|
1814
2037
|
}
|
|
@@ -2448,6 +2671,17 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
2448
2671
|
`);
|
|
2449
2672
|
return;
|
|
2450
2673
|
}
|
|
2674
|
+
if (first === "session" && second === "process-log") {
|
|
2675
|
+
const selection = parseSessionProcessLogArgs(args.slice(2));
|
|
2676
|
+
const log = await client.processes.log(selection.runId, {
|
|
2677
|
+
...selection.tail !== void 0 ? { tail: selection.tail } : {},
|
|
2678
|
+
...selection.grep !== void 0 ? { grep: selection.grep } : {}
|
|
2679
|
+
});
|
|
2680
|
+
const rendered = renderProcessLog(log);
|
|
2681
|
+
write(log, rendered.text);
|
|
2682
|
+
if (!json) process.stderr.write(rendered.summary);
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2451
2685
|
if (first === "sessions" && second === "recent") {
|
|
2452
2686
|
const sessions = await client.sessions.recent(parseRecentSessionArgs(args.slice(2)));
|
|
2453
2687
|
write(sessions, renderRecentSessionList(sessions));
|
|
@@ -2477,11 +2711,7 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
2477
2711
|
if (first === "projects" && second === "branches" && third === "list" || first === "get" && second === "branches") {
|
|
2478
2712
|
const projectRef = requireValue(first === "get" ? args[2] : args[3], "Missing project reference");
|
|
2479
2713
|
const branches = await client.projects.branches.list(projectRef);
|
|
2480
|
-
write(
|
|
2481
|
-
branches,
|
|
2482
|
-
branches.length === 0 ? "No branches found.\n" : `${branches.map((branch) => `${branch.branchName} sessions=${branch.sessionCount}`).join("\n")}
|
|
2483
|
-
`
|
|
2484
|
-
);
|
|
2714
|
+
write(branches, renderBranchList(branches));
|
|
2485
2715
|
return;
|
|
2486
2716
|
}
|
|
2487
2717
|
if (first === "projects" && second === "branches" && third === "describe" || first === "describe" && second === "branch") {
|
|
@@ -2496,6 +2726,40 @@ Sessions: ${result.sessions.length}
|
|
|
2496
2726
|
`);
|
|
2497
2727
|
return;
|
|
2498
2728
|
}
|
|
2729
|
+
if (first === "projects" && second === "branches" && third === "create" || first === "create" && second === "branch") {
|
|
2730
|
+
const offset = first === "create" ? 2 : 3;
|
|
2731
|
+
const projectRef = requireValue(args[offset], "Missing project reference");
|
|
2732
|
+
const branchName = requireValue(args[offset + 1], "Missing branch name");
|
|
2733
|
+
const operation = await createBranchThroughPlatform(client, projectRef, {
|
|
2734
|
+
branchName,
|
|
2735
|
+
...parseBranchCreateArgs(args.slice(offset + 2))
|
|
2736
|
+
});
|
|
2737
|
+
write(operation, renderBranchOperation(operation, projectRef));
|
|
2738
|
+
return;
|
|
2739
|
+
}
|
|
2740
|
+
if (first === "projects" && second === "branches" && third === "operation" || first === "describe" && second === "branch-operation") {
|
|
2741
|
+
const offset = first === "describe" ? 2 : 3;
|
|
2742
|
+
const projectRef = requireValue(args[offset], "Missing project reference");
|
|
2743
|
+
const operationId = requireValue(args[offset + 1], "Missing operation id");
|
|
2744
|
+
const operation = await requireServerRouteSupport(
|
|
2745
|
+
"describe branch-operation",
|
|
2746
|
+
() => client.projects.branches.describeOperation(projectRef, operationId)
|
|
2747
|
+
);
|
|
2748
|
+
write(operation, renderBranchOperation(operation, projectRef));
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
if (first === "projects" && second === "branches" && third === "delete" || first === "delete" && second === "branch") {
|
|
2752
|
+
const offset = first === "delete" ? 2 : 3;
|
|
2753
|
+
const projectRef = requireValue(args[offset], "Missing project reference");
|
|
2754
|
+
const branchName = requireValue(args[offset + 1], "Missing branch name");
|
|
2755
|
+
const deletion = await deleteBranchThroughPlatform(client, projectRef, branchName, parseBranchDeleteArgs(args.slice(offset + 2)));
|
|
2756
|
+
if (deletion.kind === "preview") {
|
|
2757
|
+
write({ ...deletion.branch, confirmed: false }, renderBranchDeletionPreview(deletion.branch));
|
|
2758
|
+
throw new Error(branchDeletionConfirmationMessage(deletion.branch));
|
|
2759
|
+
}
|
|
2760
|
+
write(deletion.result, renderBranchDeletion(deletion.result));
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2499
2763
|
if (first === "get" && second === "envs") {
|
|
2500
2764
|
const projectRef = requireValue(args[2], "Missing project reference");
|
|
2501
2765
|
const options = parseGetEnvsArgs(args.slice(3));
|
|
@@ -2912,6 +3176,19 @@ function resolveCommandExecution(options, rest) {
|
|
|
2912
3176
|
pluginArgs: ["describe", "branch", options.project, branch]
|
|
2913
3177
|
};
|
|
2914
3178
|
}
|
|
3179
|
+
if (target === "branch-operation") {
|
|
3180
|
+
if (!options.project) {
|
|
3181
|
+
throw new Error("--project/-p is required for `describe branch-operation`");
|
|
3182
|
+
}
|
|
3183
|
+
const operationId = requireValue(commandArgs[1], "Operation id is required for `describe branch-operation`");
|
|
3184
|
+
if (commandArgs.length > 2) {
|
|
3185
|
+
throw new Error(`Unexpected describe branch-operation argument: ${commandArgs[2]}`);
|
|
3186
|
+
}
|
|
3187
|
+
return {
|
|
3188
|
+
kind: "plugin",
|
|
3189
|
+
pluginArgs: ["describe", "branch-operation", options.project, operationId]
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
2915
3192
|
if (target === "session") {
|
|
2916
3193
|
const sessionId = commandArgs[1] ?? options.session;
|
|
2917
3194
|
if (!sessionId) {
|
|
@@ -2944,6 +3221,23 @@ function resolveCommandExecution(options, rest) {
|
|
|
2944
3221
|
pluginArgs: ["create", "session", projectRef, branch, ...flagArgs]
|
|
2945
3222
|
};
|
|
2946
3223
|
}
|
|
3224
|
+
if (target === "branch") {
|
|
3225
|
+
if (!options.project) {
|
|
3226
|
+
throw new Error("--project/-p is required for `create branch`");
|
|
3227
|
+
}
|
|
3228
|
+
const rawArgs = commandArgs.slice(1);
|
|
3229
|
+
const positionalBranch = rawArgs[0] && !rawArgs[0].startsWith("-") ? rawArgs[0] : void 0;
|
|
3230
|
+
const branch = positionalBranch ?? options.branch;
|
|
3231
|
+
if (!branch) {
|
|
3232
|
+
throw new Error("Branch name is required for `create branch`");
|
|
3233
|
+
}
|
|
3234
|
+
const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
|
|
3235
|
+
parseBranchCreateArgs(flagArgs);
|
|
3236
|
+
return {
|
|
3237
|
+
kind: "plugin",
|
|
3238
|
+
pluginArgs: ["create", "branch", options.project, branch, ...flagArgs]
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
2947
3241
|
throw new Error("Unknown create command");
|
|
2948
3242
|
}
|
|
2949
3243
|
if (command === "update") {
|
|
@@ -2994,6 +3288,23 @@ function resolveCommandExecution(options, rest) {
|
|
|
2994
3288
|
pluginArgs: ["delete", "session", sessionId]
|
|
2995
3289
|
};
|
|
2996
3290
|
}
|
|
3291
|
+
if (target === "branch") {
|
|
3292
|
+
if (!options.project) {
|
|
3293
|
+
throw new Error("--project/-p is required for `delete branch`");
|
|
3294
|
+
}
|
|
3295
|
+
const rawArgs = commandArgs.slice(1);
|
|
3296
|
+
const positionalBranch = rawArgs[0] && !rawArgs[0].startsWith("-") ? rawArgs[0] : void 0;
|
|
3297
|
+
const branch = positionalBranch ?? options.branch;
|
|
3298
|
+
if (!branch) {
|
|
3299
|
+
throw new Error("Branch name is required for `delete branch`");
|
|
3300
|
+
}
|
|
3301
|
+
const flagArgs = positionalBranch ? rawArgs.slice(1) : rawArgs;
|
|
3302
|
+
parseBranchDeleteArgs(flagArgs);
|
|
3303
|
+
return {
|
|
3304
|
+
kind: "plugin",
|
|
3305
|
+
pluginArgs: ["delete", "branch", options.project, branch, ...flagArgs]
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
2997
3308
|
throw new Error("Unknown delete command");
|
|
2998
3309
|
}
|
|
2999
3310
|
if (command === "conversation") {
|
|
@@ -3055,6 +3366,20 @@ function resolveCommandExecution(options, rest) {
|
|
|
3055
3366
|
};
|
|
3056
3367
|
}
|
|
3057
3368
|
if (command === "session") {
|
|
3369
|
+
if (commandArgs[0] === "process-log") {
|
|
3370
|
+
rejectSessionRunGlobalScopes(options, false);
|
|
3371
|
+
const selection = parseSessionProcessLogArgs(commandArgs.slice(1));
|
|
3372
|
+
return {
|
|
3373
|
+
kind: "plugin",
|
|
3374
|
+
pluginArgs: [
|
|
3375
|
+
"session",
|
|
3376
|
+
"process-log",
|
|
3377
|
+
selection.runId,
|
|
3378
|
+
...selection.tail !== void 0 ? [`--tail=${selection.tail}`] : [],
|
|
3379
|
+
...selection.grep !== void 0 ? [`--grep=${selection.grep}`] : []
|
|
3380
|
+
]
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3058
3383
|
return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
|
|
3059
3384
|
}
|
|
3060
3385
|
if (command === "answer-questions") {
|
|
@@ -3206,7 +3531,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3206
3531
|
}
|
|
3207
3532
|
export {
|
|
3208
3533
|
advanceTransientDevicePollBackoff,
|
|
3534
|
+
branchDeletionConfirmationMessage,
|
|
3209
3535
|
collectK8sUsage,
|
|
3536
|
+
createBranchThroughPlatform,
|
|
3537
|
+
deleteBranchThroughPlatform,
|
|
3210
3538
|
dispatchSessionRunCommand,
|
|
3211
3539
|
followR5dctlSession,
|
|
3212
3540
|
formatK8sCpu,
|
|
@@ -3218,10 +3546,13 @@ export {
|
|
|
3218
3546
|
getR5dctlVersion,
|
|
3219
3547
|
getTransientDevicePollDelay,
|
|
3220
3548
|
handleAuthLogin,
|
|
3549
|
+
isUnsupportedServerRoute,
|
|
3221
3550
|
main,
|
|
3222
3551
|
parseAnswerEnvRequestCommandArgs,
|
|
3223
3552
|
parseAnswerFlags,
|
|
3224
3553
|
parseAnswerQuestionsCommandArgs,
|
|
3554
|
+
parseBranchCreateArgs,
|
|
3555
|
+
parseBranchDeleteArgs,
|
|
3225
3556
|
parseConversationForkArgs,
|
|
3226
3557
|
parseConversationInspectWorkArgs,
|
|
3227
3558
|
parseConversationOverviewArgs,
|
|
@@ -3234,10 +3565,15 @@ export {
|
|
|
3234
3565
|
parsePromptArgs,
|
|
3235
3566
|
parsePsHistoryArgs,
|
|
3236
3567
|
parsePsListArgs,
|
|
3568
|
+
parseSessionProcessLogArgs,
|
|
3237
3569
|
parseSessionRunCommand,
|
|
3238
3570
|
parseSetEnvArgs,
|
|
3239
3571
|
parseShellArgs,
|
|
3240
3572
|
readDotenvFile,
|
|
3573
|
+
renderBranchDeletion,
|
|
3574
|
+
renderBranchDeletionPreview,
|
|
3575
|
+
renderBranchList,
|
|
3576
|
+
renderBranchOperation,
|
|
3241
3577
|
renderConversationForkResponse,
|
|
3242
3578
|
renderConversationHeadResponse,
|
|
3243
3579
|
renderConversationNodeResponse,
|
|
@@ -3250,6 +3586,7 @@ export {
|
|
|
3250
3586
|
renderProcessHistory,
|
|
3251
3587
|
renderProcessInspection,
|
|
3252
3588
|
renderProcessList,
|
|
3589
|
+
renderProcessLog,
|
|
3253
3590
|
renderSessionRunStart,
|
|
3254
3591
|
renderSessionRunStatus,
|
|
3255
3592
|
renderWorkspaceStatus,
|
package/dist/mjs/package.json
CHANGED