@ricsam/r5dctl 0.0.59 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/dist/cjs/cli.cjs +824 -239
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +814 -239
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +164 -2
- package/package.json +2 -2
package/dist/cjs/cli.cjs
CHANGED
|
@@ -30,6 +30,9 @@ var cli_exports = {};
|
|
|
30
30
|
__export(cli_exports, {
|
|
31
31
|
advanceTransientDevicePollBackoff: () => advanceTransientDevicePollBackoff,
|
|
32
32
|
collectK8sUsage: () => collectK8sUsage,
|
|
33
|
+
dispatchMergeCommand: () => dispatchMergeCommand,
|
|
34
|
+
dispatchSessionRunCommand: () => dispatchSessionRunCommand,
|
|
35
|
+
followR5dctlSession: () => followR5dctlSession,
|
|
33
36
|
formatK8sCpu: () => formatK8sCpu,
|
|
34
37
|
formatK8sMemory: () => formatK8sMemory,
|
|
35
38
|
formatProcessAge: () => formatProcessAge,
|
|
@@ -40,7 +43,9 @@ __export(cli_exports, {
|
|
|
40
43
|
getTransientDevicePollDelay: () => getTransientDevicePollDelay,
|
|
41
44
|
handleAuthLogin: () => handleAuthLogin,
|
|
42
45
|
main: () => main,
|
|
46
|
+
parseAnswerEnvRequestCommandArgs: () => parseAnswerEnvRequestCommandArgs,
|
|
43
47
|
parseAnswerFlags: () => parseAnswerFlags,
|
|
48
|
+
parseAnswerQuestionsCommandArgs: () => parseAnswerQuestionsCommandArgs,
|
|
44
49
|
parseConversationRenderArgs: () => parseConversationRenderArgs,
|
|
45
50
|
parseConversationWorkDetailArgs: () => parseConversationWorkDetailArgs,
|
|
46
51
|
parseEnvFlags: () => parseEnvFlags,
|
|
@@ -48,9 +53,11 @@ __export(cli_exports, {
|
|
|
48
53
|
parseGetEnvsArgs: () => parseGetEnvsArgs,
|
|
49
54
|
parseGlobalArgs: () => parseGlobalArgs,
|
|
50
55
|
parseK8sUsageArgs: () => parseK8sUsageArgs,
|
|
56
|
+
parseMergeCommand: () => parseMergeCommand,
|
|
51
57
|
parsePromptArgs: () => parsePromptArgs,
|
|
52
58
|
parsePsHistoryArgs: () => parsePsHistoryArgs,
|
|
53
59
|
parsePsListArgs: () => parsePsListArgs,
|
|
60
|
+
parseSessionRunCommand: () => parseSessionRunCommand,
|
|
54
61
|
parseSetEnvArgs: () => parseSetEnvArgs,
|
|
55
62
|
parseShellArgs: () => parseShellArgs,
|
|
56
63
|
readDotenvFile: () => readDotenvFile,
|
|
@@ -64,8 +71,11 @@ __export(cli_exports, {
|
|
|
64
71
|
renderProcessHistory: () => renderProcessHistory,
|
|
65
72
|
renderProcessInspection: () => renderProcessInspection,
|
|
66
73
|
renderProcessList: () => renderProcessList,
|
|
74
|
+
renderSessionRunStart: () => renderSessionRunStart,
|
|
75
|
+
renderSessionRunStatus: () => renderSessionRunStatus,
|
|
67
76
|
renderWorkspaceStatus: () => renderWorkspaceStatus,
|
|
68
77
|
renderWorkspaceSync: () => renderWorkspaceSync,
|
|
78
|
+
renderWorktreeMerge: () => renderWorktreeMerge,
|
|
69
79
|
resolveCommandExecution: () => resolveCommandExecution,
|
|
70
80
|
runR5dctlCli: () => runR5dctlCli,
|
|
71
81
|
summarizeEnvData: () => summarizeEnvData,
|
|
@@ -87,15 +97,12 @@ const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
|
87
97
|
"plan",
|
|
88
98
|
"build",
|
|
89
99
|
"agent",
|
|
90
|
-
"explore",
|
|
91
|
-
"test",
|
|
92
|
-
"research",
|
|
93
100
|
"security_review",
|
|
94
101
|
"large_diff_remediation",
|
|
95
102
|
"merge_conflict_resolution"
|
|
96
103
|
]);
|
|
104
|
+
const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "build", "agent"]);
|
|
97
105
|
const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
|
|
98
|
-
const AGENT_TYPES = /* @__PURE__ */ new Set(["research", "debug", "test"]);
|
|
99
106
|
const R5DCTL_PACKAGE_NAME = "@ricsam/r5dctl";
|
|
100
107
|
const CLI_GLOBAL_OPTION_HELP = [
|
|
101
108
|
"--base-url <url>",
|
|
@@ -149,6 +156,26 @@ const K8S_HELP_TEXT = [
|
|
|
149
156
|
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
150
157
|
""
|
|
151
158
|
].join("\n");
|
|
159
|
+
const SESSION_RUN_HELP_TEXT = [
|
|
160
|
+
"Usage:",
|
|
161
|
+
' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
162
|
+
' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
163
|
+
" r5dctl session status <session-id>",
|
|
164
|
+
' r5dctl session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
|
|
165
|
+
" r5dctl session stop <session-id>",
|
|
166
|
+
"",
|
|
167
|
+
"Create, inspect, resume, and stop ordinary agent sessions.",
|
|
168
|
+
"Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
|
|
169
|
+
""
|
|
170
|
+
].join("\n");
|
|
171
|
+
const MERGE_HELP_TEXT = [
|
|
172
|
+
"Usage:",
|
|
173
|
+
" r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
|
|
174
|
+
' r5dctl -p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
|
|
175
|
+
"",
|
|
176
|
+
"Merge a managed worktree into its recorded parent worktree.",
|
|
177
|
+
""
|
|
178
|
+
].join("\n");
|
|
152
179
|
const SHARED_HELP_ENTRIES = [
|
|
153
180
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
154
181
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -216,7 +243,7 @@ const SHARED_HELP_ENTRIES = [
|
|
|
216
243
|
{ section: "sessions", usage: "-s <session-id> delete session", description: "Delete a session." },
|
|
217
244
|
{
|
|
218
245
|
section: "sessions",
|
|
219
|
-
usage: "-s <session-id> conversation [--raw] [--tools] [--system]",
|
|
246
|
+
usage: "-s <session-id> conversation [--raw] [--tools] [--system] [-f|--follow|--watch]",
|
|
220
247
|
description: "Read the agent request transcript in human-readable form."
|
|
221
248
|
},
|
|
222
249
|
{
|
|
@@ -236,17 +263,32 @@ const SHARED_HELP_ENTRIES = [
|
|
|
236
263
|
},
|
|
237
264
|
{
|
|
238
265
|
section: "sessions",
|
|
239
|
-
usage: '-
|
|
240
|
-
description: "
|
|
266
|
+
usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch>) --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
267
|
+
description: "Start an ordinary agent session on an explicit existing or new worktree."
|
|
241
268
|
},
|
|
242
269
|
{
|
|
243
270
|
section: "sessions",
|
|
244
|
-
usage:
|
|
271
|
+
usage: "session status <session-id>",
|
|
272
|
+
description: "Show asynchronous session execution status."
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
section: "sessions",
|
|
276
|
+
usage: 'session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
|
|
277
|
+
description: "Queue a prompt or start a new session generation."
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
section: "sessions",
|
|
281
|
+
usage: "session stop <session-id>",
|
|
282
|
+
description: "Stop an active session generation."
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
section: "sessions",
|
|
286
|
+
usage: '-s <session-id> answer-questions --worker <label> --mode <mode> --model <tier> -a1 "<answer>" ...',
|
|
245
287
|
description: "Answer pending questions in order."
|
|
246
288
|
},
|
|
247
289
|
{
|
|
248
290
|
section: "sessions",
|
|
249
|
-
usage: "-s <session-id> answer-env-request [-e KEY=value] [--context <text>]",
|
|
291
|
+
usage: "-s <session-id> answer-env-request --worker <label> --mode <mode> --model <tier> [-e KEY=value] [--context <text>]",
|
|
250
292
|
description: "Answer a pending environment variable request with values, context, or both."
|
|
251
293
|
},
|
|
252
294
|
{
|
|
@@ -271,19 +313,15 @@ const SHARED_HELP_ENTRIES = [
|
|
|
271
313
|
},
|
|
272
314
|
{ section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
|
|
273
315
|
{
|
|
274
|
-
section: "
|
|
275
|
-
usage:
|
|
276
|
-
description: "
|
|
316
|
+
section: "merge",
|
|
317
|
+
usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
|
|
318
|
+
description: "Merge a worktree into its recorded parent, with an explicit conflict strategy."
|
|
277
319
|
},
|
|
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
320
|
{
|
|
281
|
-
section: "
|
|
282
|
-
usage:
|
|
283
|
-
description: "
|
|
284
|
-
}
|
|
285
|
-
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
286
|
-
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
321
|
+
section: "merge",
|
|
322
|
+
usage: '-p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
|
|
323
|
+
description: "Publish and finish a manually resolved conflict worktree."
|
|
324
|
+
}
|
|
287
325
|
];
|
|
288
326
|
const HELP_SECTION_ORDER = [
|
|
289
327
|
"auth",
|
|
@@ -295,8 +333,7 @@ const HELP_SECTION_ORDER = [
|
|
|
295
333
|
"kubernetes",
|
|
296
334
|
"processes",
|
|
297
335
|
"shell",
|
|
298
|
-
"
|
|
299
|
-
"merges"
|
|
336
|
+
"merge"
|
|
300
337
|
];
|
|
301
338
|
const HELP_SECTION_TITLES = {
|
|
302
339
|
auth: "Auth",
|
|
@@ -308,8 +345,7 @@ const HELP_SECTION_TITLES = {
|
|
|
308
345
|
kubernetes: "Kubernetes",
|
|
309
346
|
processes: "Processes",
|
|
310
347
|
shell: "Shell",
|
|
311
|
-
|
|
312
|
-
merges: "Merges"
|
|
348
|
+
merge: "Worktree merging"
|
|
313
349
|
};
|
|
314
350
|
function getCliHelpText() {
|
|
315
351
|
const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
|
|
@@ -408,7 +444,7 @@ function renderCliOnlyCommandHelp(entry) {
|
|
|
408
444
|
function renderSharedCommandHelp(pathSegments) {
|
|
409
445
|
const entry = SHARED_HELP_ENTRIES.map((candidate) => ({
|
|
410
446
|
candidate,
|
|
411
|
-
usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.
|
|
447
|
+
usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.includes("<") && !segment.startsWith("["))
|
|
412
448
|
})).filter(
|
|
413
449
|
({ usageSegments }) => usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index])
|
|
414
450
|
).sort((left, right) => right.usageSegments.length - left.usageSegments.length)[0]?.candidate;
|
|
@@ -456,6 +492,173 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
|
|
|
456
492
|
function hasBooleanFlag(args, flag) {
|
|
457
493
|
return args.includes(flag);
|
|
458
494
|
}
|
|
495
|
+
function parseCommandOperands(args, valueFlags, booleanFlags = /* @__PURE__ */ new Map(), family = "command", allowFlagLikeValuesAfter = Number.POSITIVE_INFINITY) {
|
|
496
|
+
const positionals = [];
|
|
497
|
+
const values = /* @__PURE__ */ new Map();
|
|
498
|
+
const booleans = /* @__PURE__ */ new Set();
|
|
499
|
+
let parseOptions = true;
|
|
500
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
501
|
+
const arg = args[index];
|
|
502
|
+
if (parseOptions && arg === "--") {
|
|
503
|
+
parseOptions = false;
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const booleanFlag = parseOptions ? booleanFlags.get(arg) : void 0;
|
|
507
|
+
if (booleanFlag) {
|
|
508
|
+
if (booleans.has(booleanFlag)) throw new Error(`${booleanFlag} may only be provided once`);
|
|
509
|
+
booleans.add(booleanFlag);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const inlineFlag = parseOptions ? [...valueFlags].find((flag) => arg.startsWith(`${flag}=`)) : void 0;
|
|
513
|
+
if (inlineFlag) {
|
|
514
|
+
if (values.has(inlineFlag)) throw new Error(`${inlineFlag} may only be provided once`);
|
|
515
|
+
const value = arg.slice(inlineFlag.length + 1).trim();
|
|
516
|
+
if (!value) throw new Error(`Missing value for ${inlineFlag}`);
|
|
517
|
+
values.set(inlineFlag, value);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
if (parseOptions && valueFlags.has(arg)) {
|
|
521
|
+
if (values.has(arg)) throw new Error(`${arg} may only be provided once`);
|
|
522
|
+
const value = args[index + 1];
|
|
523
|
+
if (!value || value === "--" || valueFlags.has(value)) throw new Error(`Missing value for ${arg}`);
|
|
524
|
+
values.set(arg, value.trim());
|
|
525
|
+
index += 1;
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (parseOptions && arg.startsWith("-") && positionals.length < allowFlagLikeValuesAfter) {
|
|
529
|
+
throw new Error(`Unknown ${family} flag: ${arg}`);
|
|
530
|
+
}
|
|
531
|
+
positionals.push(arg);
|
|
532
|
+
}
|
|
533
|
+
return { positionals, values, booleans };
|
|
534
|
+
}
|
|
535
|
+
function requireWorker(values) {
|
|
536
|
+
return requireValue(values.get("--worker")?.trim(), "--worker <label> is required");
|
|
537
|
+
}
|
|
538
|
+
function parseWorktreeSource(rawValue) {
|
|
539
|
+
const raw = requireValue(rawValue?.trim(), "--source worktree:<branch> is required with --new-worktree");
|
|
540
|
+
const separator = raw.indexOf(":");
|
|
541
|
+
const type = raw.slice(0, separator);
|
|
542
|
+
const branchName = raw.slice(separator + 1).trim();
|
|
543
|
+
if (type !== "worktree" || separator < 1 || !branchName) {
|
|
544
|
+
throw new Error("Invalid --source. Expected worktree:<branch>");
|
|
545
|
+
}
|
|
546
|
+
return { type, branchName };
|
|
547
|
+
}
|
|
548
|
+
function requireModel(value) {
|
|
549
|
+
const model = requireValue(value?.trim(), "--model <tier> is required");
|
|
550
|
+
if (!MODEL_TIERS.has(model)) throw new Error("Invalid --model. Expected one of: low, medium, high, max");
|
|
551
|
+
return model;
|
|
552
|
+
}
|
|
553
|
+
function requireSessionMode(value) {
|
|
554
|
+
const mode = requireValue(value?.trim(), "--mode <mode> is required");
|
|
555
|
+
if (!R5DCTL_SESSION_MODES.has(mode)) {
|
|
556
|
+
throw new Error("Invalid --mode. Expected one of: ask, plan, build, agent");
|
|
557
|
+
}
|
|
558
|
+
return mode;
|
|
559
|
+
}
|
|
560
|
+
function rejectSessionRunGlobalScopes(options, allowProject) {
|
|
561
|
+
if (!allowProject && options.project) throw new Error("--project/-p is only supported for `session start`");
|
|
562
|
+
if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
|
|
563
|
+
if (options.session) throw new Error("--session/-s is not supported for `session`; pass the session id explicitly");
|
|
564
|
+
}
|
|
565
|
+
function parseSessionRunCommand(options, args) {
|
|
566
|
+
const subcommand = requireValue(args[0], "Missing session command");
|
|
567
|
+
const commandArgs = args.slice(1);
|
|
568
|
+
if (subcommand === "start") {
|
|
569
|
+
rejectSessionRunGlobalScopes(options, true);
|
|
570
|
+
const project = requireValue(options.project, "--project/-p is required for `session start`");
|
|
571
|
+
const parsed = parseCommandOperands(
|
|
572
|
+
commandArgs,
|
|
573
|
+
/* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--model"]),
|
|
574
|
+
/* @__PURE__ */ new Map([
|
|
575
|
+
["-f", "--follow"],
|
|
576
|
+
["--follow", "--follow"],
|
|
577
|
+
["--watch", "--follow"],
|
|
578
|
+
["--tools", "--tools"]
|
|
579
|
+
]),
|
|
580
|
+
"session start",
|
|
581
|
+
1
|
|
582
|
+
);
|
|
583
|
+
const prompt = parsed.positionals.join(" ").trim();
|
|
584
|
+
if (!prompt) throw new Error("Prompt is required for `session start`");
|
|
585
|
+
const worktree = parsed.values.get("--worktree")?.trim();
|
|
586
|
+
const newWorktree = parsed.values.get("--new-worktree")?.trim();
|
|
587
|
+
if (Boolean(worktree) === Boolean(newWorktree)) {
|
|
588
|
+
throw new Error("Use exactly one of --worktree <branch> or --new-worktree <branch>");
|
|
589
|
+
}
|
|
590
|
+
const sourceValue = parsed.values.get("--source");
|
|
591
|
+
if (worktree && sourceValue) throw new Error("--source is only supported with --new-worktree");
|
|
592
|
+
return {
|
|
593
|
+
kind: "start",
|
|
594
|
+
project,
|
|
595
|
+
worker: requireWorker(parsed.values),
|
|
596
|
+
model: requireModel(parsed.values.get("--model")),
|
|
597
|
+
prompt,
|
|
598
|
+
follow: parsed.booleans.has("--follow"),
|
|
599
|
+
includeTools: parsed.booleans.has("--tools"),
|
|
600
|
+
...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue) }
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
rejectSessionRunGlobalScopes(options, false);
|
|
604
|
+
if (subcommand === "status" || subcommand === "stop") {
|
|
605
|
+
const parsed = parseCommandOperands(commandArgs, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), `session ${subcommand}`);
|
|
606
|
+
const sessionId = requireValue(parsed.positionals[0], `Session id is required for \`session ${subcommand}\``);
|
|
607
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected session ${subcommand} argument: ${parsed.positionals[1]}`);
|
|
608
|
+
return { kind: subcommand, sessionId };
|
|
609
|
+
}
|
|
610
|
+
if (subcommand === "prompt") {
|
|
611
|
+
const parsed = parseCommandOperands(commandArgs, /* @__PURE__ */ new Set(["--worker", "--mode", "--model"]), /* @__PURE__ */ new Map(), "session prompt", 1);
|
|
612
|
+
const sessionId = requireValue(parsed.positionals[0], "Session id is required for `session prompt`");
|
|
613
|
+
const prompt = parsed.positionals.slice(1).join(" ").trim();
|
|
614
|
+
if (!prompt) throw new Error("Prompt is required for `session prompt`");
|
|
615
|
+
return {
|
|
616
|
+
kind: "prompt",
|
|
617
|
+
worker: requireWorker(parsed.values),
|
|
618
|
+
sessionId,
|
|
619
|
+
prompt,
|
|
620
|
+
mode: requireSessionMode(parsed.values.get("--mode")),
|
|
621
|
+
model: requireModel(parsed.values.get("--model"))
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
throw new Error(`Unknown session command: ${subcommand}`);
|
|
625
|
+
}
|
|
626
|
+
function parseMergeCommand(options, args) {
|
|
627
|
+
const project = requireValue(options.project, "--project/-p is required for `merge`");
|
|
628
|
+
if (options.branch) throw new Error("--branch/-b is not supported for `merge`; pass the source worktree explicitly");
|
|
629
|
+
if (options.session) throw new Error("--session/-s is not supported for `merge`");
|
|
630
|
+
if (args[0] === "finish") {
|
|
631
|
+
const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(["--worker", "--summary"]), /* @__PURE__ */ new Map(), "merge finish");
|
|
632
|
+
const conflictWorktree = requireValue(parsed2.positionals[0], "Conflict worktree is required for `merge finish`");
|
|
633
|
+
if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge finish argument: ${parsed2.positionals[1]}`);
|
|
634
|
+
return {
|
|
635
|
+
kind: "finish",
|
|
636
|
+
project,
|
|
637
|
+
conflictWorktree,
|
|
638
|
+
worker: requireWorker(parsed2.values),
|
|
639
|
+
...parsed2.values.get("--summary")?.trim() ? { summary: parsed2.values.get("--summary").trim() } : {}
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
const parsed = parseCommandOperands(
|
|
643
|
+
args,
|
|
644
|
+
/* @__PURE__ */ new Set(["--worker"]),
|
|
645
|
+
/* @__PURE__ */ new Map([
|
|
646
|
+
["--create-conflict-worktree", "--create-conflict-worktree"],
|
|
647
|
+
["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"]
|
|
648
|
+
]),
|
|
649
|
+
"merge"
|
|
650
|
+
);
|
|
651
|
+
const sourceWorktree = requireValue(parsed.positionals[0], "Source worktree is required for `merge`");
|
|
652
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected merge argument: ${parsed.positionals[1]}`);
|
|
653
|
+
const createWorktree = parsed.booleans.has("--create-conflict-worktree");
|
|
654
|
+
const launchAgent = parsed.booleans.has("--launch-conflict-resolution-agent");
|
|
655
|
+
if (createWorktree && launchAgent) {
|
|
656
|
+
throw new Error("Use only one of --create-conflict-worktree or --launch-conflict-resolution-agent");
|
|
657
|
+
}
|
|
658
|
+
const conflictMode = createWorktree ? "worktree" : launchAgent ? "agent" : "none";
|
|
659
|
+
const worker = parsed.values.get("--worker")?.trim();
|
|
660
|
+
return { kind: "merge", project, sourceWorktree, conflictMode, ...worker ? { worker } : {} };
|
|
661
|
+
}
|
|
459
662
|
function assertAuthLoginArgs(args) {
|
|
460
663
|
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
461
664
|
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
@@ -672,6 +875,55 @@ function parseAnswerFlags(args) {
|
|
|
672
875
|
}
|
|
673
876
|
return ordered.map(([, value]) => value);
|
|
674
877
|
}
|
|
878
|
+
function requireChatMode(value) {
|
|
879
|
+
const mode = requireValue(value?.trim(), "--mode <mode> is required");
|
|
880
|
+
if (!CHAT_MODES.has(mode)) throw new Error(`Invalid --mode: ${mode}`);
|
|
881
|
+
return mode;
|
|
882
|
+
}
|
|
883
|
+
function partitionInteractiveExecutionArgs(args, consumesNextValue) {
|
|
884
|
+
const values = /* @__PURE__ */ new Map();
|
|
885
|
+
const payloadArgs = [];
|
|
886
|
+
const executionFlags = ["--worker", "--mode", "--model"];
|
|
887
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
888
|
+
const arg = args[index];
|
|
889
|
+
if (consumesNextValue(arg)) {
|
|
890
|
+
payloadArgs.push(arg);
|
|
891
|
+
const value = args[index + 1];
|
|
892
|
+
if (value === void 0) throw new Error(`Missing value for ${arg}`);
|
|
893
|
+
payloadArgs.push(value);
|
|
894
|
+
index += 1;
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
const inlineFlag = executionFlags.find((flag) => arg.startsWith(`${flag}=`));
|
|
898
|
+
if (inlineFlag) {
|
|
899
|
+
if (values.has(inlineFlag)) throw new Error(`${inlineFlag} may only be provided once`);
|
|
900
|
+
const value = requireValue(arg.slice(inlineFlag.length + 1).trim(), `Missing value for ${inlineFlag}`);
|
|
901
|
+
values.set(inlineFlag, value);
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (executionFlags.includes(arg)) {
|
|
905
|
+
const flag = arg;
|
|
906
|
+
if (values.has(flag)) throw new Error(`${flag} may only be provided once`);
|
|
907
|
+
const value = requireValue(args[index + 1]?.trim(), `Missing value for ${flag}`);
|
|
908
|
+
values.set(flag, value);
|
|
909
|
+
index += 1;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
payloadArgs.push(arg);
|
|
913
|
+
}
|
|
914
|
+
return {
|
|
915
|
+
context: {
|
|
916
|
+
worker: requireValue(values.get("--worker")?.trim(), "--worker <label> is required"),
|
|
917
|
+
mode: requireChatMode(values.get("--mode")),
|
|
918
|
+
model: requireModel(values.get("--model"))
|
|
919
|
+
},
|
|
920
|
+
payloadArgs
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
function parseAnswerQuestionsCommandArgs(args) {
|
|
924
|
+
const { context, payloadArgs } = partitionInteractiveExecutionArgs(args, (arg) => /^-a\d+$/.test(arg));
|
|
925
|
+
return { ...context, answers: parseAnswerFlags(payloadArgs) };
|
|
926
|
+
}
|
|
675
927
|
function validateEnvAssignment(value) {
|
|
676
928
|
const equalsIndex = value.indexOf("=");
|
|
677
929
|
if (equalsIndex <= 0) {
|
|
@@ -831,6 +1083,10 @@ function parseEnvRequestResponseArgs(args) {
|
|
|
831
1083
|
...additionalContext?.trim() ? { additionalContext: additionalContext.trim() } : {}
|
|
832
1084
|
};
|
|
833
1085
|
}
|
|
1086
|
+
function parseAnswerEnvRequestCommandArgs(args) {
|
|
1087
|
+
const { context, payloadArgs } = partitionInteractiveExecutionArgs(args, (arg) => arg === "-e" || arg === "--env" || arg === "--context");
|
|
1088
|
+
return { ...context, ...parseEnvRequestResponseArgs(payloadArgs) };
|
|
1089
|
+
}
|
|
834
1090
|
function parsePromptArgs(args) {
|
|
835
1091
|
let modeRaw;
|
|
836
1092
|
let modelRaw;
|
|
@@ -1231,7 +1487,7 @@ function renderWorkspaceStatus(status) {
|
|
|
1231
1487
|
`Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
|
|
1232
1488
|
`Materialization: ${status.materialization ? `${status.materialization.state}; ${status.materialization.materializedHead ?? "(none)"} -> ${status.materialization.desiredHead ?? "(none)"}; lag ${status.materialization.lagMs}ms` : "not initialized"}`,
|
|
1233
1489
|
...status.workers.map(
|
|
1234
|
-
(worker) => `Worker ${worker.workerLabel}: ${worker.ready ? "ready" : "bootstrapping"}; ${worker.
|
|
1490
|
+
(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`
|
|
1235
1491
|
)
|
|
1236
1492
|
];
|
|
1237
1493
|
return `${lines.join("\n")}
|
|
@@ -1601,44 +1857,158 @@ async function setProjectEnvs(client, projectRef, args) {
|
|
|
1601
1857
|
}
|
|
1602
1858
|
return result;
|
|
1603
1859
|
}
|
|
1604
|
-
function
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1860
|
+
function responseString(response, key) {
|
|
1861
|
+
const value = response[key];
|
|
1862
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1863
|
+
}
|
|
1864
|
+
function formatStatusValue(value) {
|
|
1865
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1866
|
+
if (typeof value === "object" && value !== null) {
|
|
1867
|
+
const status = value.status;
|
|
1868
|
+
if (typeof status === "string" && status.length > 0) return status;
|
|
1869
|
+
return JSON.stringify(value);
|
|
1870
|
+
}
|
|
1871
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
1872
|
+
}
|
|
1873
|
+
function appendMergeDetails(lines, merge) {
|
|
1874
|
+
const mergeStatus = formatStatusValue(merge.status);
|
|
1875
|
+
lines.push(`Merge: ${mergeStatus ?? "pending"}`);
|
|
1876
|
+
for (const [label, key] of [
|
|
1877
|
+
["Attempt", "attemptId"],
|
|
1878
|
+
["Source worktree", "sourceWorktree"],
|
|
1879
|
+
["Target worktree", "targetWorktree"],
|
|
1880
|
+
["Commit", "commitHash"],
|
|
1881
|
+
["Resolver agent", "resolverSessionId"],
|
|
1882
|
+
["Conflict worktree", "conflictWorktree"],
|
|
1883
|
+
["Resolver worktree", "folderPath"]
|
|
1884
|
+
]) {
|
|
1885
|
+
const value = responseString(merge, key);
|
|
1886
|
+
if (value) lines.push(`${label}: ${value}`);
|
|
1887
|
+
}
|
|
1888
|
+
const conflictedFiles = Array.isArray(merge.conflictedFiles) ? merge.conflictedFiles.filter((file) => typeof file === "string") : [];
|
|
1889
|
+
if (conflictedFiles.length > 0) {
|
|
1890
|
+
lines.push("", "Conflicts:", ...conflictedFiles.map((file) => `- ${file}`));
|
|
1891
|
+
}
|
|
1892
|
+
const message = responseString(merge, "message");
|
|
1893
|
+
if (message) lines.push("", message);
|
|
1894
|
+
const failureReason = responseString(merge, "failureReason");
|
|
1895
|
+
if (failureReason) lines.push(`Failure: ${failureReason}`);
|
|
1896
|
+
}
|
|
1897
|
+
function renderSessionRunStart(session) {
|
|
1898
|
+
const lines = [
|
|
1899
|
+
`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
|
|
1900
|
+
`Status: ${formatStatusValue(session.status) ?? "queued"}`,
|
|
1901
|
+
`Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`,
|
|
1902
|
+
`Worker: ${responseString(session, "worker") ?? "(unassigned)"}`
|
|
1903
|
+
];
|
|
1904
|
+
const sessionUrl = responseString(session, "sessionUrl");
|
|
1905
|
+
if (sessionUrl) lines.push(`URL: ${sessionUrl}`);
|
|
1906
|
+
const sessionId = responseString(session, "sessionId");
|
|
1907
|
+
if (sessionId) lines.push("", `Next: r5dctl session status ${sessionId}`);
|
|
1908
|
+
return `${lines.join("\n")}
|
|
1909
|
+
`;
|
|
1611
1910
|
}
|
|
1612
|
-
function
|
|
1613
|
-
const
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
if (
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1911
|
+
function renderSessionRunStatus(session) {
|
|
1912
|
+
const status = formatStatusValue(session.status) ?? "unknown";
|
|
1913
|
+
const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
|
|
1914
|
+
const runStatus = formatStatusValue(session.runStatus);
|
|
1915
|
+
if (runStatus) lines.push(`Run: ${runStatus}`);
|
|
1916
|
+
const promptDisposition = responseString(session, "promptDisposition");
|
|
1917
|
+
if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
|
|
1918
|
+
const branch = responseString(session, "branchName");
|
|
1919
|
+
if (branch) lines.push(`Worktree: ${branch}`);
|
|
1920
|
+
for (const [label, key] of [
|
|
1921
|
+
["Workspace head", "workspaceHead"],
|
|
1922
|
+
["Baseline", "baselineCommit"],
|
|
1923
|
+
["Head", "headCommit"],
|
|
1924
|
+
["Worker", "worker"],
|
|
1925
|
+
["Session", "sessionUrl"]
|
|
1926
|
+
]) {
|
|
1927
|
+
const value = responseString(session, key);
|
|
1928
|
+
if (value) lines.push(`${label}: ${value}`);
|
|
1929
|
+
}
|
|
1930
|
+
const error = responseString(session, "error");
|
|
1931
|
+
if (error) lines.push(`Error: ${error}`);
|
|
1932
|
+
const headCommitError = responseString(session, "headCommitError");
|
|
1933
|
+
if (headCommitError) lines.push(`Head error: ${headCommitError}`);
|
|
1934
|
+
const diffSummary = responseString(session, "diffSummary");
|
|
1935
|
+
if (diffSummary) lines.push("", diffSummary);
|
|
1936
|
+
const summary = responseString(session, "summary");
|
|
1937
|
+
if (summary) lines.push("", summary);
|
|
1938
|
+
const sessionId = responseString(session, "sessionId");
|
|
1939
|
+
if (sessionId && ["provisioning", "queued", "running"].includes(status)) {
|
|
1940
|
+
lines.push("", "Next:", ` r5dctl session status ${sessionId}`, ` r5dctl session stop ${sessionId}`);
|
|
1625
1941
|
}
|
|
1626
1942
|
return `${lines.join("\n")}
|
|
1627
1943
|
`;
|
|
1628
1944
|
}
|
|
1629
|
-
function
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1945
|
+
function renderWorktreeMerge(result, input) {
|
|
1946
|
+
const lines = [];
|
|
1947
|
+
appendMergeDetails(lines, result);
|
|
1948
|
+
const conflictWorktree = responseString(result, "conflictWorktree");
|
|
1949
|
+
const resolverSessionId = responseString(result, "resolverSessionId");
|
|
1950
|
+
const worker = responseString(result, "worker") ?? input.worker;
|
|
1951
|
+
if (resolverSessionId) lines.push("", `Resolver session: ${resolverSessionId}`, ` r5dctl session stop ${resolverSessionId}`);
|
|
1952
|
+
if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(responseString(result, "status") ?? "")) {
|
|
1953
|
+
lines.push(
|
|
1954
|
+
"",
|
|
1955
|
+
"Finish after resolving and reviewing the conflict worktree:",
|
|
1956
|
+
` r5dctl -p ${input.project} merge finish --worker ${worker} ${conflictWorktree} --summary "<summary>"`
|
|
1957
|
+
);
|
|
1634
1958
|
}
|
|
1635
|
-
|
|
1636
|
-
Conflicts:
|
|
1637
|
-
${result.conflictedFiles.map((file) => `- ${file}`).join("\n")}
|
|
1638
|
-
` : "\n";
|
|
1639
|
-
return `Merge has conflicts from ${result.sourceBranch} into ${result.targetBranch}.${files}${result.message}
|
|
1959
|
+
return `${lines.join("\n")}
|
|
1640
1960
|
`;
|
|
1641
1961
|
}
|
|
1962
|
+
async function dispatchSessionRunCommand(client, command) {
|
|
1963
|
+
if (command.kind === "start") {
|
|
1964
|
+
const commonInput = {
|
|
1965
|
+
prompt: command.prompt,
|
|
1966
|
+
worker: command.worker,
|
|
1967
|
+
model: command.model,
|
|
1968
|
+
requestId: (0, import_node_crypto.randomUUID)()
|
|
1969
|
+
};
|
|
1970
|
+
let input;
|
|
1971
|
+
if (command.worktree !== void 0) {
|
|
1972
|
+
input = { ...commonInput, worktree: command.worktree };
|
|
1973
|
+
} else if (command.newWorktree !== void 0 && command.source !== void 0) {
|
|
1974
|
+
input = { ...commonInput, newWorktree: command.newWorktree, source: command.source };
|
|
1975
|
+
} else {
|
|
1976
|
+
throw new Error("Session start requires an existing or new worktree");
|
|
1977
|
+
}
|
|
1978
|
+
const data2 = await client.projects.sessions.start(command.project, input);
|
|
1979
|
+
return { data: data2, human: renderSessionRunStart(data2) };
|
|
1980
|
+
}
|
|
1981
|
+
if (command.kind === "status") {
|
|
1982
|
+
const data2 = await client.sessions.status(command.sessionId);
|
|
1983
|
+
return { data: data2, human: renderSessionRunStatus(data2) };
|
|
1984
|
+
}
|
|
1985
|
+
if (command.kind === "prompt") {
|
|
1986
|
+
const data2 = await client.sessions.prompt(command.sessionId, {
|
|
1987
|
+
prompt: command.prompt,
|
|
1988
|
+
worker: command.worker,
|
|
1989
|
+
mode: command.mode,
|
|
1990
|
+
model: command.model,
|
|
1991
|
+
requestId: (0, import_node_crypto.randomUUID)()
|
|
1992
|
+
});
|
|
1993
|
+
return { data: data2, human: renderSessionRunStatus(data2) };
|
|
1994
|
+
}
|
|
1995
|
+
const data = await client.sessions.stop(command.sessionId);
|
|
1996
|
+
return { data, human: renderSessionRunStatus(data) };
|
|
1997
|
+
}
|
|
1998
|
+
async function dispatchMergeCommand(client, command) {
|
|
1999
|
+
if (command.kind === "merge") {
|
|
2000
|
+
const data2 = await client.projects.worktrees.merge(command.project, command.sourceWorktree, {
|
|
2001
|
+
conflictMode: command.conflictMode,
|
|
2002
|
+
...command.worker ? { worker: command.worker } : {}
|
|
2003
|
+
});
|
|
2004
|
+
return { data: data2, human: renderWorktreeMerge(data2, command) };
|
|
2005
|
+
}
|
|
2006
|
+
const data = await client.projects.worktrees.finishMerge(command.project, command.conflictWorktree, {
|
|
2007
|
+
worker: command.worker,
|
|
2008
|
+
...command.summary ? { summary: command.summary } : {}
|
|
2009
|
+
});
|
|
2010
|
+
return { data, human: renderWorktreeMerge(data, command) };
|
|
2011
|
+
}
|
|
1642
2012
|
function parseProjectUpdateArgs(args) {
|
|
1643
2013
|
const input = {};
|
|
1644
2014
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -1712,7 +2082,7 @@ function parseSessionUpdateArgs(args) {
|
|
|
1712
2082
|
return input;
|
|
1713
2083
|
}
|
|
1714
2084
|
function parseConversationRenderArgs(args) {
|
|
1715
|
-
const options = { format: "human", includeTools: false, includeSystem: false };
|
|
2085
|
+
const options = { format: "human", includeTools: false, includeSystem: false, follow: false };
|
|
1716
2086
|
for (const arg of args) {
|
|
1717
2087
|
if (arg === "--raw") {
|
|
1718
2088
|
options.format = "raw";
|
|
@@ -1726,6 +2096,11 @@ function parseConversationRenderArgs(args) {
|
|
|
1726
2096
|
options.includeSystem = true;
|
|
1727
2097
|
continue;
|
|
1728
2098
|
}
|
|
2099
|
+
if (arg === "-f" || arg === "--follow" || arg === "--watch") {
|
|
2100
|
+
if (options.follow) throw new Error("--follow may only be provided once");
|
|
2101
|
+
options.follow = true;
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
1729
2104
|
throw new Error(`Unknown conversation argument: ${arg}`);
|
|
1730
2105
|
}
|
|
1731
2106
|
return options;
|
|
@@ -1734,6 +2109,306 @@ function renderConversationResponse(read) {
|
|
|
1734
2109
|
return read.agentText.endsWith("\n") ? read.agentText : `${read.agentText}
|
|
1735
2110
|
`;
|
|
1736
2111
|
}
|
|
2112
|
+
function recordValue(value) {
|
|
2113
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2114
|
+
}
|
|
2115
|
+
function nodeId(node) {
|
|
2116
|
+
const id = recordValue(node)?.id;
|
|
2117
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
2118
|
+
}
|
|
2119
|
+
function nodeType(node) {
|
|
2120
|
+
const type = recordValue(node)?.type;
|
|
2121
|
+
return typeof type === "string" ? type : void 0;
|
|
2122
|
+
}
|
|
2123
|
+
function assistantNodeText(node) {
|
|
2124
|
+
const record = recordValue(node);
|
|
2125
|
+
return record?.type === "assistant_message" && typeof record.content === "string" ? record.content : void 0;
|
|
2126
|
+
}
|
|
2127
|
+
function conversationRevision(read) {
|
|
2128
|
+
const revision = recordValue(read.conversation)?.revision;
|
|
2129
|
+
return typeof revision === "number" ? revision : 0;
|
|
2130
|
+
}
|
|
2131
|
+
function activeConversationNodes(conversation) {
|
|
2132
|
+
const record = recordValue(conversation);
|
|
2133
|
+
const responses = recordValue(record?.responses);
|
|
2134
|
+
let currentId = typeof record?.head === "string" ? record.head : "";
|
|
2135
|
+
if (!responses || !currentId) return [];
|
|
2136
|
+
const reversed = [];
|
|
2137
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2138
|
+
while (currentId && !visited.has(currentId)) {
|
|
2139
|
+
visited.add(currentId);
|
|
2140
|
+
const node = responses[currentId];
|
|
2141
|
+
if (!node) break;
|
|
2142
|
+
reversed.push(node);
|
|
2143
|
+
const parent = recordValue(node)?.parent;
|
|
2144
|
+
currentId = typeof parent === "string" ? parent : "";
|
|
2145
|
+
}
|
|
2146
|
+
return reversed.reverse();
|
|
2147
|
+
}
|
|
2148
|
+
function isToolEvent(event) {
|
|
2149
|
+
return ["tool_use_start", "input_json_delta", "tool_call", "tool_progress", "shell_result"].includes(event.type);
|
|
2150
|
+
}
|
|
2151
|
+
function withoutToolDetails(node) {
|
|
2152
|
+
const record = recordValue(node);
|
|
2153
|
+
if (!record || record.type !== "assistant_message" || !Array.isArray(record.toolCalls)) return node;
|
|
2154
|
+
return { ...record, toolCalls: [] };
|
|
2155
|
+
}
|
|
2156
|
+
function jsonConversationData(read, includeTools) {
|
|
2157
|
+
if (includeTools) return read;
|
|
2158
|
+
const { conversation: _conversation, thread, ...rest } = read;
|
|
2159
|
+
return {
|
|
2160
|
+
...rest,
|
|
2161
|
+
thread: Array.isArray(thread) ? thread.filter((node) => nodeType(node) !== "tool_result").map((node) => withoutToolDetails(node)) : []
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
function jsonFollowEvent(event, includeTools) {
|
|
2165
|
+
if (event.type === "session") {
|
|
2166
|
+
const data = recordValue(event.data);
|
|
2167
|
+
if (!data) return event;
|
|
2168
|
+
const { conversation: _conversation, ...rest } = data;
|
|
2169
|
+
return { ...event, data: rest };
|
|
2170
|
+
}
|
|
2171
|
+
if (includeTools) return event;
|
|
2172
|
+
if (isToolEvent(event)) return null;
|
|
2173
|
+
if (event.type === "response") {
|
|
2174
|
+
const data = recordValue(event.data);
|
|
2175
|
+
const node = data?.node;
|
|
2176
|
+
if (nodeType(node) === "tool_result") return null;
|
|
2177
|
+
return { ...event, data: { ...data, node: withoutToolDetails(node) } };
|
|
2178
|
+
}
|
|
2179
|
+
return event;
|
|
2180
|
+
}
|
|
2181
|
+
function terminalRunStatus(response) {
|
|
2182
|
+
const status = formatStatusValue(response.runStatus) ?? formatStatusValue(response.status);
|
|
2183
|
+
return {
|
|
2184
|
+
terminal: status !== void 0 && ["idle", "completed", "failed", "stopped"].includes(status),
|
|
2185
|
+
failed: status === "failed"
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
function isAbortError(error) {
|
|
2189
|
+
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message.toLowerCase().includes("aborted"));
|
|
2190
|
+
}
|
|
2191
|
+
async function followR5dctlSession(client, sessionId, options) {
|
|
2192
|
+
const writeOut = options.stdout ?? ((text) => process.stdout.write(text));
|
|
2193
|
+
const writeErr = options.stderr ?? ((text) => process.stderr.write(text));
|
|
2194
|
+
const seenNodeIds = /* @__PURE__ */ new Set();
|
|
2195
|
+
let latestRevision = 0;
|
|
2196
|
+
let streamedAssistantText = "";
|
|
2197
|
+
let reconnects = 0;
|
|
2198
|
+
const emitJson = (value) => writeOut(`${JSON.stringify(value)}
|
|
2199
|
+
`);
|
|
2200
|
+
const renderNode = (node) => {
|
|
2201
|
+
const id = nodeId(node);
|
|
2202
|
+
if (id && seenNodeIds.has(id)) return;
|
|
2203
|
+
if (id) seenNodeIds.add(id);
|
|
2204
|
+
const text = assistantNodeText(node);
|
|
2205
|
+
if (text !== void 0) {
|
|
2206
|
+
let missing = text;
|
|
2207
|
+
if (streamedAssistantText && text.startsWith(streamedAssistantText)) missing = text.slice(streamedAssistantText.length);
|
|
2208
|
+
else if (streamedAssistantText.startsWith(text)) missing = "";
|
|
2209
|
+
if (missing) writeOut(missing);
|
|
2210
|
+
streamedAssistantText = "";
|
|
2211
|
+
if (options.includeTools) {
|
|
2212
|
+
const toolCalls = recordValue(node)?.toolCalls;
|
|
2213
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) writeErr(`[tools] ${JSON.stringify(toolCalls)}
|
|
2214
|
+
`);
|
|
2215
|
+
}
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
if (options.includeTools && nodeType(node) === "tool_result") writeErr(`[tool results] ${JSON.stringify(node)}
|
|
2219
|
+
`);
|
|
2220
|
+
};
|
|
2221
|
+
const syncConversation = async (initial = false) => {
|
|
2222
|
+
const read = initial && options.initialConversation ? options.initialConversation : await client.sessions.conversation(sessionId, options.renderOptions);
|
|
2223
|
+
const nodes = Array.isArray(read.thread) ? read.thread : [];
|
|
2224
|
+
const newNodes = nodes.filter((node) => {
|
|
2225
|
+
const id = nodeId(node);
|
|
2226
|
+
return !id || !seenNodeIds.has(id);
|
|
2227
|
+
});
|
|
2228
|
+
if (initial && options.printInitialTranscript) {
|
|
2229
|
+
if (options.json)
|
|
2230
|
+
emitJson({ type: "conversation", revision: conversationRevision(read), data: jsonConversationData(read, options.includeTools) });
|
|
2231
|
+
else writeOut(renderConversationResponse(read));
|
|
2232
|
+
for (const node of nodes) {
|
|
2233
|
+
const id = nodeId(node);
|
|
2234
|
+
if (id) seenNodeIds.add(id);
|
|
2235
|
+
}
|
|
2236
|
+
} else if (options.json && newNodes.length > 0) {
|
|
2237
|
+
const filtered = options.includeTools ? newNodes : newNodes.filter((node) => nodeType(node) !== "tool_result");
|
|
2238
|
+
if (filtered.length > 0) {
|
|
2239
|
+
emitJson({
|
|
2240
|
+
type: "conversation_sync",
|
|
2241
|
+
revision: conversationRevision(read),
|
|
2242
|
+
nodes: options.includeTools ? filtered : filtered.map((node) => withoutToolDetails(node))
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
for (const node of newNodes) {
|
|
2246
|
+
const id = nodeId(node);
|
|
2247
|
+
if (id) seenNodeIds.add(id);
|
|
2248
|
+
}
|
|
2249
|
+
} else {
|
|
2250
|
+
for (const node of newNodes) renderNode(node);
|
|
2251
|
+
}
|
|
2252
|
+
latestRevision = Math.max(latestRevision, conversationRevision(read));
|
|
2253
|
+
return read;
|
|
2254
|
+
};
|
|
2255
|
+
await syncConversation(true);
|
|
2256
|
+
while (!options.signal?.aborted) {
|
|
2257
|
+
try {
|
|
2258
|
+
for await (const event of client.sessions.events(sessionId, { signal: options.signal })) {
|
|
2259
|
+
reconnects = 0;
|
|
2260
|
+
if (event.type === "response") {
|
|
2261
|
+
const data = recordValue(event.data);
|
|
2262
|
+
const revision = data?.conversationRevision;
|
|
2263
|
+
const id = nodeId(data?.node);
|
|
2264
|
+
if (typeof revision === "number" && revision <= latestRevision || id !== void 0 && seenNodeIds.has(id)) continue;
|
|
2265
|
+
}
|
|
2266
|
+
if (options.json) {
|
|
2267
|
+
const outputEvent = jsonFollowEvent(event, options.includeTools);
|
|
2268
|
+
if (outputEvent) emitJson(outputEvent);
|
|
2269
|
+
} else if (event.type === "text_delta" && typeof event.text === "string") {
|
|
2270
|
+
writeOut(event.text);
|
|
2271
|
+
streamedAssistantText += event.text;
|
|
2272
|
+
} else if (event.type === "thinking_delta" && typeof event.text === "string") {
|
|
2273
|
+
writeErr(event.text);
|
|
2274
|
+
} else if (event.type === "tool_use_start" && options.includeTools) {
|
|
2275
|
+
writeErr(`[tool] ${String(event.toolName)}
|
|
2276
|
+
`);
|
|
2277
|
+
} else if (event.type === "input_json_delta" && options.includeTools) {
|
|
2278
|
+
writeErr(String(event.partial_json));
|
|
2279
|
+
} else if ((event.type === "tool_call" || event.type === "tool_progress" || event.type === "shell_result") && options.includeTools) {
|
|
2280
|
+
writeErr(`[${event.type}] ${JSON.stringify(event)}
|
|
2281
|
+
`);
|
|
2282
|
+
} else if (event.type === "status") {
|
|
2283
|
+
writeErr(`[status] ${formatStatusValue(event.status) ?? "updated"}
|
|
2284
|
+
`);
|
|
2285
|
+
} else if (event.type === "agent_running") {
|
|
2286
|
+
writeErr(`[running] ${event.running ? "yes" : "no"}
|
|
2287
|
+
`);
|
|
2288
|
+
} else if (event.type === "stream_reset") {
|
|
2289
|
+
streamedAssistantText = "";
|
|
2290
|
+
writeErr(`[retry] ${event.attempt}/${event.maxAttempts}
|
|
2291
|
+
`);
|
|
2292
|
+
} else if (event.type === "response") {
|
|
2293
|
+
const revision = recordValue(event.data)?.conversationRevision;
|
|
2294
|
+
if (typeof revision === "number") latestRevision = revision;
|
|
2295
|
+
renderNode(recordValue(event.data)?.node);
|
|
2296
|
+
} else if (event.type === "session") {
|
|
2297
|
+
const session = recordValue(event.data);
|
|
2298
|
+
const conversation = session?.conversation;
|
|
2299
|
+
for (const node of activeConversationNodes(conversation)) renderNode(node);
|
|
2300
|
+
const revision = recordValue(conversation)?.revision;
|
|
2301
|
+
if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
|
|
2302
|
+
const streamingText = recordValue(session?.streaming)?.text;
|
|
2303
|
+
if (!options.json && typeof streamingText === "string" && streamingText.length > 0) {
|
|
2304
|
+
let missing = streamingText;
|
|
2305
|
+
if (streamedAssistantText && streamingText.startsWith(streamedAssistantText))
|
|
2306
|
+
missing = streamingText.slice(streamedAssistantText.length);
|
|
2307
|
+
else if (streamedAssistantText.startsWith(streamingText)) missing = "";
|
|
2308
|
+
if (missing) writeOut(missing);
|
|
2309
|
+
streamedAssistantText = streamingText;
|
|
2310
|
+
}
|
|
2311
|
+
if (session?.agentRunning === false) {
|
|
2312
|
+
try {
|
|
2313
|
+
const terminal = terminalRunStatus(await client.sessions.status(sessionId));
|
|
2314
|
+
if (terminal.terminal) return terminal.failed ? 1 : 0;
|
|
2315
|
+
} catch (error) {
|
|
2316
|
+
if (error instanceof import_r5d_api.R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2317
|
+
const status = formatStatusValue(session.status);
|
|
2318
|
+
if (error instanceof import_r5d_api.R5dctlApiError && error.status === 400 && status === "idle") return 0;
|
|
2319
|
+
if (error instanceof import_r5d_api.R5dctlApiError && error.status === 400 && status === "error") return 1;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
if (event.type === "response" && options.json) {
|
|
2324
|
+
const revision = recordValue(event.data)?.conversationRevision;
|
|
2325
|
+
if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
|
|
2326
|
+
const id = nodeId(recordValue(event.data)?.node);
|
|
2327
|
+
if (id) seenNodeIds.add(id);
|
|
2328
|
+
}
|
|
2329
|
+
if (event.type === "session" && options.json) {
|
|
2330
|
+
const session = recordValue(event.data);
|
|
2331
|
+
const conversation = session?.conversation;
|
|
2332
|
+
const newNodes = activeConversationNodes(conversation).filter((node) => {
|
|
2333
|
+
const id = nodeId(node);
|
|
2334
|
+
return !id || !seenNodeIds.has(id);
|
|
2335
|
+
});
|
|
2336
|
+
const filteredNodes = options.includeTools ? newNodes : newNodes.filter((node) => nodeType(node) !== "tool_result");
|
|
2337
|
+
if (filteredNodes.length > 0) {
|
|
2338
|
+
emitJson({
|
|
2339
|
+
type: "conversation_sync",
|
|
2340
|
+
revision: recordValue(conversation)?.revision,
|
|
2341
|
+
nodes: options.includeTools ? filteredNodes : filteredNodes.map((node) => withoutToolDetails(node))
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
for (const node of newNodes) {
|
|
2345
|
+
const id = nodeId(node);
|
|
2346
|
+
if (id) seenNodeIds.add(id);
|
|
2347
|
+
}
|
|
2348
|
+
const revision = recordValue(conversation)?.revision;
|
|
2349
|
+
if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
|
|
2350
|
+
if (session?.agentRunning === false) {
|
|
2351
|
+
try {
|
|
2352
|
+
const terminal = terminalRunStatus(await client.sessions.status(sessionId));
|
|
2353
|
+
if (terminal.terminal) return terminal.failed ? 1 : 0;
|
|
2354
|
+
} catch (error) {
|
|
2355
|
+
if (error instanceof import_r5d_api.R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2356
|
+
const status = formatStatusValue(session.status);
|
|
2357
|
+
if (error instanceof import_r5d_api.R5dctlApiError && error.status === 400 && status === "idle") return 0;
|
|
2358
|
+
if (error instanceof import_r5d_api.R5dctlApiError && error.status === 400 && status === "error") return 1;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
if (event.type === "done" || event.type === "stopped") {
|
|
2363
|
+
await syncConversation();
|
|
2364
|
+
if (!options.json) writeErr(`[${event.type}]
|
|
2365
|
+
`);
|
|
2366
|
+
try {
|
|
2367
|
+
const terminal = terminalRunStatus(await client.sessions.status(sessionId));
|
|
2368
|
+
if (terminal.terminal) return terminal.failed ? 1 : 0;
|
|
2369
|
+
if (event.type === "done") continue;
|
|
2370
|
+
return 0;
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
if (error instanceof import_r5d_api.R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2373
|
+
if (error instanceof import_r5d_api.R5dctlApiError && error.status === 400) return 0;
|
|
2374
|
+
throw error;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
if (event.type === "error") {
|
|
2378
|
+
if (!options.json) writeErr(`${typeof event.message === "string" ? event.message : "Session failed"}
|
|
2379
|
+
`);
|
|
2380
|
+
return 1;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
} catch (error) {
|
|
2384
|
+
if (options.signal?.aborted || isAbortError(error)) return 0;
|
|
2385
|
+
if (error instanceof import_r5d_api.R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2386
|
+
}
|
|
2387
|
+
if (options.signal?.aborted) return 0;
|
|
2388
|
+
try {
|
|
2389
|
+
await syncConversation();
|
|
2390
|
+
} catch (error) {
|
|
2391
|
+
if (error instanceof import_r5d_api.R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2392
|
+
}
|
|
2393
|
+
try {
|
|
2394
|
+
const terminal = terminalRunStatus(await client.sessions.status(sessionId));
|
|
2395
|
+
if (terminal.terminal) return terminal.failed ? 1 : 0;
|
|
2396
|
+
} catch (error) {
|
|
2397
|
+
if (error instanceof import_r5d_api.R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2398
|
+
}
|
|
2399
|
+
reconnects += 1;
|
|
2400
|
+
if (reconnects > (options.maxReconnects ?? Number.POSITIVE_INFINITY)) {
|
|
2401
|
+
throw new Error("Session event stream ended before the session became idle");
|
|
2402
|
+
}
|
|
2403
|
+
try {
|
|
2404
|
+
await (0, import_promises.setTimeout)(options.reconnectDelayMs ?? Math.min(250 * 2 ** (reconnects - 1), 5e3), void 0, { signal: options.signal });
|
|
2405
|
+
} catch (error) {
|
|
2406
|
+
if (options.signal?.aborted || isAbortError(error)) return 0;
|
|
2407
|
+
throw error;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
return 0;
|
|
2411
|
+
}
|
|
1737
2412
|
function parseConversationWorkDetailArgs(args) {
|
|
1738
2413
|
let detail = "compact";
|
|
1739
2414
|
let selected = false;
|
|
@@ -1937,65 +2612,6 @@ Sessions: ${result.sessions.length}
|
|
|
1937
2612
|
write(session, renderSessionDescription(session));
|
|
1938
2613
|
return;
|
|
1939
2614
|
}
|
|
1940
|
-
if (first === "start-agent") {
|
|
1941
|
-
const projectRef = requireValue(args[1], "Missing project reference");
|
|
1942
|
-
const sourceBranch = requireValue(args[2], "Missing source branch");
|
|
1943
|
-
const agentType = requireValue(args[3], "Missing agent type");
|
|
1944
|
-
if (!AGENT_TYPES.has(agentType)) {
|
|
1945
|
-
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
1946
|
-
}
|
|
1947
|
-
const prompt = args.slice(4).join(" ").trim();
|
|
1948
|
-
if (!prompt) {
|
|
1949
|
-
throw new Error("Agent prompt is required");
|
|
1950
|
-
}
|
|
1951
|
-
const agent = await client.projects.agents.start(projectRef, {
|
|
1952
|
-
sourceBranch,
|
|
1953
|
-
agentType,
|
|
1954
|
-
prompt
|
|
1955
|
-
});
|
|
1956
|
-
write(agent, renderAgentStart(agent));
|
|
1957
|
-
return;
|
|
1958
|
-
}
|
|
1959
|
-
if (first === "agent-status") {
|
|
1960
|
-
const agent = await client.agents.status(requireValue(args[1], "Missing session id"));
|
|
1961
|
-
write(agent, renderAgentStatus(agent));
|
|
1962
|
-
return;
|
|
1963
|
-
}
|
|
1964
|
-
if (first === "send-prompt") {
|
|
1965
|
-
const sessionId = requireValue(args[1], "Missing session id");
|
|
1966
|
-
const prompt = args.slice(2).join(" ").trim();
|
|
1967
|
-
if (!prompt) {
|
|
1968
|
-
throw new Error("Prompt is required");
|
|
1969
|
-
}
|
|
1970
|
-
const agent = await client.agents.sendPrompt(sessionId, { prompt });
|
|
1971
|
-
write(agent, renderAgentStatus(agent));
|
|
1972
|
-
return;
|
|
1973
|
-
}
|
|
1974
|
-
if (first === "merge-changes") {
|
|
1975
|
-
const result = await client.projects.mergeChanges(requireValue(args[1], "Missing project reference"), {
|
|
1976
|
-
targetBranch: requireValue(args[2], "Missing target branch"),
|
|
1977
|
-
sourceBranch: requireValue(args[3], "Missing source branch")
|
|
1978
|
-
});
|
|
1979
|
-
write(result, renderMergeResult(result));
|
|
1980
|
-
return;
|
|
1981
|
-
}
|
|
1982
|
-
if (first === "continue-merge") {
|
|
1983
|
-
const result = await client.projects.continueMerge(requireValue(args[1], "Missing project reference"), {
|
|
1984
|
-
targetBranch: requireValue(args[2], "Missing target branch")
|
|
1985
|
-
});
|
|
1986
|
-
write(result, `Merge committed: ${result.commitHash}
|
|
1987
|
-
${result.message}
|
|
1988
|
-
`);
|
|
1989
|
-
return;
|
|
1990
|
-
}
|
|
1991
|
-
if (first === "abort-merge") {
|
|
1992
|
-
const result = await client.projects.abortMerge(requireValue(args[1], "Missing project reference"), {
|
|
1993
|
-
targetBranch: requireValue(args[2], "Missing target branch")
|
|
1994
|
-
});
|
|
1995
|
-
write(result, `${result.message}
|
|
1996
|
-
`);
|
|
1997
|
-
return;
|
|
1998
|
-
}
|
|
1999
2615
|
if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
|
|
2000
2616
|
const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
|
|
2001
2617
|
write(session, renderSessionDescription(session));
|
|
@@ -2024,8 +2640,8 @@ ${result.message}
|
|
|
2024
2640
|
throw new Error(`Unexpected conversation inspect-node argument: ${args[4]}`);
|
|
2025
2641
|
}
|
|
2026
2642
|
const sessionId = requireValue(args[2], "Missing session id");
|
|
2027
|
-
const
|
|
2028
|
-
const read = await client.sessions.inspectConversationNode(sessionId,
|
|
2643
|
+
const nodeId2 = requireValue(args[3], "Missing node id");
|
|
2644
|
+
const read = await client.sessions.inspectConversationNode(sessionId, nodeId2);
|
|
2029
2645
|
write(read, renderConversationNodeResponse(read));
|
|
2030
2646
|
return;
|
|
2031
2647
|
}
|
|
@@ -2040,33 +2656,30 @@ ${result.message}
|
|
|
2040
2656
|
if (first === "sessions" && second === "conversation" || first === "conversation") {
|
|
2041
2657
|
const offset = first === "conversation" ? 1 : 2;
|
|
2042
2658
|
const renderOptions = parseConversationRenderArgs(args.slice(offset + 1));
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
}
|
|
2047
|
-
if (first === "sessions" && second === "prompt" || first === "prompt") {
|
|
2048
|
-
const offset = first === "prompt" ? 1 : 2;
|
|
2049
|
-
const read = await client.sessions.prompt(requireValue(args[offset], "Missing session id"), {
|
|
2050
|
-
mode: requireValue(args[offset + 1], "Missing mode"),
|
|
2051
|
-
model: requireValue(args[offset + 2], "Missing model"),
|
|
2052
|
-
message: requireValue(args[offset + 3], "Missing prompt message")
|
|
2053
|
-
});
|
|
2659
|
+
if (renderOptions.follow) throw new Error("Conversation follow must be handled by the CLI stream runner");
|
|
2660
|
+
const { follow: _follow, ...apiRenderOptions } = renderOptions;
|
|
2661
|
+
const read = await client.sessions.conversation(requireValue(args[offset], "Missing session id"), apiRenderOptions);
|
|
2054
2662
|
write(read, renderConversationResponse(read));
|
|
2055
2663
|
return;
|
|
2056
2664
|
}
|
|
2057
2665
|
if (first === "sessions" && second === "answer-questions" || first === "answer-questions") {
|
|
2058
2666
|
const offset = first === "answer-questions" ? 1 : 2;
|
|
2059
|
-
const
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2667
|
+
const input = parseAnswerQuestionsCommandArgs(args.slice(offset + 1));
|
|
2668
|
+
const read = await client.sessions.answerQuestions(requireValue(args[offset], "Missing session id"), {
|
|
2669
|
+
...input,
|
|
2670
|
+
requestId: (0, import_node_crypto.randomUUID)()
|
|
2671
|
+
});
|
|
2672
|
+
write(read, renderSessionRunStatus(read));
|
|
2063
2673
|
return;
|
|
2064
2674
|
}
|
|
2065
2675
|
if (first === "sessions" && second === "answer-env-request" || first === "answer-env-request") {
|
|
2066
2676
|
const offset = first === "answer-env-request" ? 1 : 2;
|
|
2067
|
-
const input =
|
|
2068
|
-
const read = await client.sessions.answerEnvRequest(requireValue(args[offset], "Missing session id"),
|
|
2069
|
-
|
|
2677
|
+
const input = parseAnswerEnvRequestCommandArgs(args.slice(offset + 1));
|
|
2678
|
+
const read = await client.sessions.answerEnvRequest(requireValue(args[offset], "Missing session id"), {
|
|
2679
|
+
...input,
|
|
2680
|
+
requestId: (0, import_node_crypto.randomUUID)()
|
|
2681
|
+
});
|
|
2682
|
+
write(read, renderSessionRunStatus(read));
|
|
2070
2683
|
return;
|
|
2071
2684
|
}
|
|
2072
2685
|
throw new Error(`Unknown command: ${args.join(" ")}`);
|
|
@@ -2091,6 +2704,18 @@ function resolveCommandExecution(options, rest) {
|
|
|
2091
2704
|
text: K8S_HELP_TEXT
|
|
2092
2705
|
};
|
|
2093
2706
|
}
|
|
2707
|
+
if (command === "session" && commandArgs.length === 0) {
|
|
2708
|
+
return {
|
|
2709
|
+
kind: "cli-help",
|
|
2710
|
+
text: SESSION_RUN_HELP_TEXT
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
if (command === "merge" && commandArgs.length === 0) {
|
|
2714
|
+
return {
|
|
2715
|
+
kind: "cli-help",
|
|
2716
|
+
text: MERGE_HELP_TEXT
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2094
2719
|
if (trailingHelp) {
|
|
2095
2720
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
2096
2721
|
if (cliOnlyHelp) {
|
|
@@ -2450,24 +3075,23 @@ function resolveCommandExecution(options, rest) {
|
|
|
2450
3075
|
if (!sessionId2) {
|
|
2451
3076
|
throw new Error("Session id is required for `conversation inspect-node`");
|
|
2452
3077
|
}
|
|
2453
|
-
const
|
|
2454
|
-
if (!
|
|
3078
|
+
const nodeId2 = options.session ? commandArgs[1] : commandArgs[2];
|
|
3079
|
+
if (!nodeId2) {
|
|
2455
3080
|
throw new Error("Node id is required for `conversation inspect-node`");
|
|
2456
3081
|
}
|
|
2457
3082
|
const remainingArgs = options.session ? commandArgs.slice(2) : commandArgs.slice(3);
|
|
2458
3083
|
return {
|
|
2459
3084
|
kind: "plugin",
|
|
2460
|
-
pluginArgs: ["conversation", "inspect-node", sessionId2,
|
|
3085
|
+
pluginArgs: ["conversation", "inspect-node", sessionId2, nodeId2, ...remainingArgs]
|
|
2461
3086
|
};
|
|
2462
3087
|
}
|
|
2463
3088
|
const sessionId = options.session;
|
|
2464
3089
|
if (!sessionId) {
|
|
2465
3090
|
throw new Error("--session/-s is required for `conversation`");
|
|
2466
3091
|
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
};
|
|
3092
|
+
const renderOptions = parseConversationRenderArgs(commandArgs);
|
|
3093
|
+
if (renderOptions.follow) return { kind: "conversation-follow", sessionId, renderOptions };
|
|
3094
|
+
return { kind: "plugin", pluginArgs: ["conversation", sessionId, ...commandArgs] };
|
|
2471
3095
|
}
|
|
2472
3096
|
if (command === "shell") {
|
|
2473
3097
|
if (!options.project) {
|
|
@@ -2484,102 +3108,11 @@ function resolveCommandExecution(options, rest) {
|
|
|
2484
3108
|
...parseShellArgs(commandArgs)
|
|
2485
3109
|
};
|
|
2486
3110
|
}
|
|
2487
|
-
if (command === "
|
|
2488
|
-
|
|
2489
|
-
if (!sessionId) {
|
|
2490
|
-
throw new Error("--session/-s is required for `prompt`");
|
|
2491
|
-
}
|
|
2492
|
-
const parsed = parsePromptArgs(commandArgs);
|
|
2493
|
-
return {
|
|
2494
|
-
kind: "plugin",
|
|
2495
|
-
pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
|
|
2496
|
-
};
|
|
2497
|
-
}
|
|
2498
|
-
if (command === "start-agent") {
|
|
2499
|
-
if (!options.project) {
|
|
2500
|
-
throw new Error("--project/-p is required for `start-agent`");
|
|
2501
|
-
}
|
|
2502
|
-
const sourceBranch = commandArgs[0] ?? options.branch;
|
|
2503
|
-
if (!sourceBranch) {
|
|
2504
|
-
throw new Error("Source branch is required for `start-agent`");
|
|
2505
|
-
}
|
|
2506
|
-
const agentType = requireValue(commandArgs[1], "Agent type is required for `start-agent`");
|
|
2507
|
-
if (!AGENT_TYPES.has(agentType)) {
|
|
2508
|
-
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
2509
|
-
}
|
|
2510
|
-
const prompt = commandArgs.slice(2).join(" ").trim();
|
|
2511
|
-
if (!prompt) {
|
|
2512
|
-
throw new Error("Agent prompt is required");
|
|
2513
|
-
}
|
|
2514
|
-
return {
|
|
2515
|
-
kind: "plugin",
|
|
2516
|
-
pluginArgs: ["start-agent", options.project, sourceBranch, agentType, prompt]
|
|
2517
|
-
};
|
|
3111
|
+
if (command === "session") {
|
|
3112
|
+
return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
|
|
2518
3113
|
}
|
|
2519
|
-
if (command === "
|
|
2520
|
-
|
|
2521
|
-
if (!sessionId) {
|
|
2522
|
-
throw new Error("Session id is required for `agent-status`");
|
|
2523
|
-
}
|
|
2524
|
-
return {
|
|
2525
|
-
kind: "plugin",
|
|
2526
|
-
pluginArgs: ["agent-status", sessionId]
|
|
2527
|
-
};
|
|
2528
|
-
}
|
|
2529
|
-
if (command === "send-prompt") {
|
|
2530
|
-
const sessionId = options.session ?? commandArgs[0];
|
|
2531
|
-
if (!sessionId) {
|
|
2532
|
-
throw new Error("Session id is required for `send-prompt`");
|
|
2533
|
-
}
|
|
2534
|
-
const promptArgs = options.session ? commandArgs : commandArgs.slice(1);
|
|
2535
|
-
const prompt = promptArgs.join(" ").trim();
|
|
2536
|
-
if (!prompt) {
|
|
2537
|
-
throw new Error("Prompt is required for `send-prompt`");
|
|
2538
|
-
}
|
|
2539
|
-
return {
|
|
2540
|
-
kind: "plugin",
|
|
2541
|
-
pluginArgs: ["send-prompt", sessionId, prompt]
|
|
2542
|
-
};
|
|
2543
|
-
}
|
|
2544
|
-
if (command === "merge-changes") {
|
|
2545
|
-
if (!options.project) {
|
|
2546
|
-
throw new Error("--project/-p is required for `merge-changes`");
|
|
2547
|
-
}
|
|
2548
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2549
|
-
if (!targetBranch) {
|
|
2550
|
-
throw new Error("Target branch is required for `merge-changes`");
|
|
2551
|
-
}
|
|
2552
|
-
const sourceBranch = requireValue(commandArgs[1], "Sub-agent branch is required for `merge-changes`");
|
|
2553
|
-
return {
|
|
2554
|
-
kind: "plugin",
|
|
2555
|
-
pluginArgs: ["merge-changes", options.project, targetBranch, sourceBranch]
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
if (command === "continue-merge") {
|
|
2559
|
-
if (!options.project) {
|
|
2560
|
-
throw new Error("--project/-p is required for `continue-merge`");
|
|
2561
|
-
}
|
|
2562
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2563
|
-
if (!targetBranch) {
|
|
2564
|
-
throw new Error("Target branch is required for `continue-merge`");
|
|
2565
|
-
}
|
|
2566
|
-
return {
|
|
2567
|
-
kind: "plugin",
|
|
2568
|
-
pluginArgs: ["continue-merge", options.project, targetBranch]
|
|
2569
|
-
};
|
|
2570
|
-
}
|
|
2571
|
-
if (command === "abort-merge") {
|
|
2572
|
-
if (!options.project) {
|
|
2573
|
-
throw new Error("--project/-p is required for `abort-merge`");
|
|
2574
|
-
}
|
|
2575
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2576
|
-
if (!targetBranch) {
|
|
2577
|
-
throw new Error("Target branch is required for `abort-merge`");
|
|
2578
|
-
}
|
|
2579
|
-
return {
|
|
2580
|
-
kind: "plugin",
|
|
2581
|
-
pluginArgs: ["abort-merge", options.project, targetBranch]
|
|
2582
|
-
};
|
|
3114
|
+
if (command === "merge") {
|
|
3115
|
+
return { kind: "merge", command: parseMergeCommand(options, commandArgs) };
|
|
2583
3116
|
}
|
|
2584
3117
|
if (command === "answer-questions") {
|
|
2585
3118
|
const sessionId = options.session;
|
|
@@ -2588,7 +3121,7 @@ function resolveCommandExecution(options, rest) {
|
|
|
2588
3121
|
}
|
|
2589
3122
|
return {
|
|
2590
3123
|
kind: "plugin",
|
|
2591
|
-
pluginArgs: ["answer-questions", sessionId, ...
|
|
3124
|
+
pluginArgs: ["answer-questions", sessionId, ...commandArgs]
|
|
2592
3125
|
};
|
|
2593
3126
|
}
|
|
2594
3127
|
if (command === "answer-env-request") {
|
|
@@ -2603,6 +3136,16 @@ function resolveCommandExecution(options, rest) {
|
|
|
2603
3136
|
}
|
|
2604
3137
|
throw new Error(`Unknown command: ${command}`);
|
|
2605
3138
|
}
|
|
3139
|
+
async function followWithInterrupt(client, sessionId, options) {
|
|
3140
|
+
const controller = new AbortController();
|
|
3141
|
+
const detach = () => controller.abort();
|
|
3142
|
+
process.once("SIGINT", detach);
|
|
3143
|
+
try {
|
|
3144
|
+
return await followR5dctlSession(client, sessionId, { ...options, signal: controller.signal });
|
|
3145
|
+
} finally {
|
|
3146
|
+
process.removeListener("SIGINT", detach);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
2606
3149
|
async function runCommand(argv) {
|
|
2607
3150
|
const { options, rest } = parseGlobalArgs(argv);
|
|
2608
3151
|
if (options.version) {
|
|
@@ -2629,6 +3172,38 @@ async function runCommand(argv) {
|
|
|
2629
3172
|
process.stdout.write(execution.text);
|
|
2630
3173
|
return 0;
|
|
2631
3174
|
}
|
|
3175
|
+
if (execution.kind === "session-run") {
|
|
3176
|
+
const result = await dispatchSessionRunCommand(client, execution.command);
|
|
3177
|
+
if (execution.command.kind !== "start" || !execution.command.follow) {
|
|
3178
|
+
writeDataOutput(options.json, result.data, result.human);
|
|
3179
|
+
return 0;
|
|
3180
|
+
}
|
|
3181
|
+
const sessionId = requireValue(responseString(result.data, "sessionId"), "Session start did not return a session id");
|
|
3182
|
+
if (options.json) process.stdout.write(`${JSON.stringify({ type: "session_started", data: result.data })}
|
|
3183
|
+
`);
|
|
3184
|
+
else process.stderr.write(result.human);
|
|
3185
|
+
return await followWithInterrupt(client, sessionId, {
|
|
3186
|
+
json: options.json,
|
|
3187
|
+
includeTools: execution.command.includeTools,
|
|
3188
|
+
printInitialTranscript: false
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
if (execution.kind === "merge") {
|
|
3192
|
+
const result = await dispatchMergeCommand(client, execution.command);
|
|
3193
|
+
writeDataOutput(options.json, result.data, result.human);
|
|
3194
|
+
return 0;
|
|
3195
|
+
}
|
|
3196
|
+
if (execution.kind === "conversation-follow") {
|
|
3197
|
+
const { follow: _follow, ...renderOptions } = execution.renderOptions;
|
|
3198
|
+
const initialConversation = await client.sessions.conversation(execution.sessionId, renderOptions);
|
|
3199
|
+
return await followWithInterrupt(client, execution.sessionId, {
|
|
3200
|
+
json: options.json,
|
|
3201
|
+
includeTools: execution.renderOptions.includeTools,
|
|
3202
|
+
renderOptions,
|
|
3203
|
+
initialConversation,
|
|
3204
|
+
printInitialTranscript: true
|
|
3205
|
+
});
|
|
3206
|
+
}
|
|
2632
3207
|
if (execution.kind === "shell") {
|
|
2633
3208
|
const project = await client.projects.describe(options.project);
|
|
2634
3209
|
return await (0, import_shell.runR5dctlShell)({
|
|
@@ -2678,6 +3253,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2678
3253
|
0 && (module.exports = {
|
|
2679
3254
|
advanceTransientDevicePollBackoff,
|
|
2680
3255
|
collectK8sUsage,
|
|
3256
|
+
dispatchMergeCommand,
|
|
3257
|
+
dispatchSessionRunCommand,
|
|
3258
|
+
followR5dctlSession,
|
|
2681
3259
|
formatK8sCpu,
|
|
2682
3260
|
formatK8sMemory,
|
|
2683
3261
|
formatProcessAge,
|
|
@@ -2688,7 +3266,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2688
3266
|
getTransientDevicePollDelay,
|
|
2689
3267
|
handleAuthLogin,
|
|
2690
3268
|
main,
|
|
3269
|
+
parseAnswerEnvRequestCommandArgs,
|
|
2691
3270
|
parseAnswerFlags,
|
|
3271
|
+
parseAnswerQuestionsCommandArgs,
|
|
2692
3272
|
parseConversationRenderArgs,
|
|
2693
3273
|
parseConversationWorkDetailArgs,
|
|
2694
3274
|
parseEnvFlags,
|
|
@@ -2696,9 +3276,11 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2696
3276
|
parseGetEnvsArgs,
|
|
2697
3277
|
parseGlobalArgs,
|
|
2698
3278
|
parseK8sUsageArgs,
|
|
3279
|
+
parseMergeCommand,
|
|
2699
3280
|
parsePromptArgs,
|
|
2700
3281
|
parsePsHistoryArgs,
|
|
2701
3282
|
parsePsListArgs,
|
|
3283
|
+
parseSessionRunCommand,
|
|
2702
3284
|
parseSetEnvArgs,
|
|
2703
3285
|
parseShellArgs,
|
|
2704
3286
|
readDotenvFile,
|
|
@@ -2712,8 +3294,11 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2712
3294
|
renderProcessHistory,
|
|
2713
3295
|
renderProcessInspection,
|
|
2714
3296
|
renderProcessList,
|
|
3297
|
+
renderSessionRunStart,
|
|
3298
|
+
renderSessionRunStatus,
|
|
2715
3299
|
renderWorkspaceStatus,
|
|
2716
3300
|
renderWorkspaceSync,
|
|
3301
|
+
renderWorktreeMerge,
|
|
2717
3302
|
resolveCommandExecution,
|
|
2718
3303
|
runR5dctlCli,
|
|
2719
3304
|
summarizeEnvData,
|