@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/mjs/cli.mjs
CHANGED
|
@@ -16,15 +16,12 @@ const CHAT_MODES = /* @__PURE__ */ new Set([
|
|
|
16
16
|
"plan",
|
|
17
17
|
"build",
|
|
18
18
|
"agent",
|
|
19
|
-
"explore",
|
|
20
|
-
"test",
|
|
21
|
-
"research",
|
|
22
19
|
"security_review",
|
|
23
20
|
"large_diff_remediation",
|
|
24
21
|
"merge_conflict_resolution"
|
|
25
22
|
]);
|
|
23
|
+
const R5DCTL_SESSION_MODES = /* @__PURE__ */ new Set(["ask", "plan", "build", "agent"]);
|
|
26
24
|
const MODEL_TIERS = /* @__PURE__ */ new Set(["low", "medium", "high", "max"]);
|
|
27
|
-
const AGENT_TYPES = /* @__PURE__ */ new Set(["research", "debug", "test"]);
|
|
28
25
|
const R5DCTL_PACKAGE_NAME = "@ricsam/r5dctl";
|
|
29
26
|
const CLI_GLOBAL_OPTION_HELP = [
|
|
30
27
|
"--base-url <url>",
|
|
@@ -78,6 +75,26 @@ const K8S_HELP_TEXT = [
|
|
|
78
75
|
"Show current and rolling CPU and memory usage for managed-vCluster workload containers.",
|
|
79
76
|
""
|
|
80
77
|
].join("\n");
|
|
78
|
+
const SESSION_RUN_HELP_TEXT = [
|
|
79
|
+
"Usage:",
|
|
80
|
+
' r5dctl -p <project> session start --worker <label> --worktree <branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
81
|
+
' r5dctl -p <project> session start --worker <label> --new-worktree <branch> --source worktree:<branch> --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
82
|
+
" r5dctl session status <session-id>",
|
|
83
|
+
' r5dctl session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
|
|
84
|
+
" r5dctl session stop <session-id>",
|
|
85
|
+
"",
|
|
86
|
+
"Create, inspect, resume, and stop ordinary agent sessions.",
|
|
87
|
+
"Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
|
|
88
|
+
""
|
|
89
|
+
].join("\n");
|
|
90
|
+
const MERGE_HELP_TEXT = [
|
|
91
|
+
"Usage:",
|
|
92
|
+
" r5dctl -p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
|
|
93
|
+
' r5dctl -p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
|
|
94
|
+
"",
|
|
95
|
+
"Merge a managed worktree into its recorded parent worktree.",
|
|
96
|
+
""
|
|
97
|
+
].join("\n");
|
|
81
98
|
const SHARED_HELP_ENTRIES = [
|
|
82
99
|
{ section: "auth", usage: "auth status", description: "Show the current authenticated user and credential source." },
|
|
83
100
|
{ section: "auth", usage: "auth logout", description: "Revoke the current credential and clear saved auth." },
|
|
@@ -145,7 +162,7 @@ const SHARED_HELP_ENTRIES = [
|
|
|
145
162
|
{ section: "sessions", usage: "-s <session-id> delete session", description: "Delete a session." },
|
|
146
163
|
{
|
|
147
164
|
section: "sessions",
|
|
148
|
-
usage: "-s <session-id> conversation [--raw] [--tools] [--system]",
|
|
165
|
+
usage: "-s <session-id> conversation [--raw] [--tools] [--system] [-f|--follow|--watch]",
|
|
149
166
|
description: "Read the agent request transcript in human-readable form."
|
|
150
167
|
},
|
|
151
168
|
{
|
|
@@ -165,17 +182,32 @@ const SHARED_HELP_ENTRIES = [
|
|
|
165
182
|
},
|
|
166
183
|
{
|
|
167
184
|
section: "sessions",
|
|
168
|
-
usage: '-
|
|
169
|
-
description: "
|
|
185
|
+
usage: '-p <project> session start --worker <label> (--worktree <branch>|--new-worktree <branch> --source worktree:<branch>) --model <tier> [-f|--follow|--watch] [--tools] "<prompt>"',
|
|
186
|
+
description: "Start an ordinary agent session on an explicit existing or new worktree."
|
|
170
187
|
},
|
|
171
188
|
{
|
|
172
189
|
section: "sessions",
|
|
173
|
-
usage:
|
|
190
|
+
usage: "session status <session-id>",
|
|
191
|
+
description: "Show asynchronous session execution status."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
section: "sessions",
|
|
195
|
+
usage: 'session prompt --worker <label> --mode <mode> --model <tier> <session-id> "<prompt>"',
|
|
196
|
+
description: "Queue a prompt or start a new session generation."
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
section: "sessions",
|
|
200
|
+
usage: "session stop <session-id>",
|
|
201
|
+
description: "Stop an active session generation."
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
section: "sessions",
|
|
205
|
+
usage: '-s <session-id> answer-questions --worker <label> --mode <mode> --model <tier> -a1 "<answer>" ...',
|
|
174
206
|
description: "Answer pending questions in order."
|
|
175
207
|
},
|
|
176
208
|
{
|
|
177
209
|
section: "sessions",
|
|
178
|
-
usage: "-s <session-id> answer-env-request [-e KEY=value] [--context <text>]",
|
|
210
|
+
usage: "-s <session-id> answer-env-request --worker <label> --mode <mode> --model <tier> [-e KEY=value] [--context <text>]",
|
|
179
211
|
description: "Answer a pending environment variable request with values, context, or both."
|
|
180
212
|
},
|
|
181
213
|
{
|
|
@@ -200,19 +232,15 @@ const SHARED_HELP_ENTRIES = [
|
|
|
200
232
|
},
|
|
201
233
|
{ section: "processes", usage: "ps stop <run-id>", description: "Gracefully stop an active process with SIGTERM." },
|
|
202
234
|
{
|
|
203
|
-
section: "
|
|
204
|
-
usage:
|
|
205
|
-
description: "
|
|
235
|
+
section: "merge",
|
|
236
|
+
usage: "-p <project> merge [--worker <label>] [--create-conflict-worktree|--launch-conflict-resolution-agent] <source-worktree>",
|
|
237
|
+
description: "Merge a worktree into its recorded parent, with an explicit conflict strategy."
|
|
206
238
|
},
|
|
207
|
-
{ section: "agents", usage: "agent-status <session-id>", description: "Show detached branch agent status." },
|
|
208
|
-
{ section: "agents", usage: 'send-prompt <session-id> "<prompt>"', description: "Queue or resume a detached branch agent." },
|
|
209
239
|
{
|
|
210
|
-
section: "
|
|
211
|
-
usage:
|
|
212
|
-
description: "
|
|
213
|
-
}
|
|
214
|
-
{ section: "merges", usage: "-p <project> continue-merge <target-branch>", description: "Commit a resolved merge." },
|
|
215
|
-
{ section: "merges", usage: "-p <project> abort-merge <target-branch>", description: "Abort an in-progress merge." }
|
|
240
|
+
section: "merge",
|
|
241
|
+
usage: '-p <project> merge finish --worker <label> [--summary "<summary>"] <conflict-worktree>',
|
|
242
|
+
description: "Publish and finish a manually resolved conflict worktree."
|
|
243
|
+
}
|
|
216
244
|
];
|
|
217
245
|
const HELP_SECTION_ORDER = [
|
|
218
246
|
"auth",
|
|
@@ -224,8 +252,7 @@ const HELP_SECTION_ORDER = [
|
|
|
224
252
|
"kubernetes",
|
|
225
253
|
"processes",
|
|
226
254
|
"shell",
|
|
227
|
-
"
|
|
228
|
-
"merges"
|
|
255
|
+
"merge"
|
|
229
256
|
];
|
|
230
257
|
const HELP_SECTION_TITLES = {
|
|
231
258
|
auth: "Auth",
|
|
@@ -237,8 +264,7 @@ const HELP_SECTION_TITLES = {
|
|
|
237
264
|
kubernetes: "Kubernetes",
|
|
238
265
|
processes: "Processes",
|
|
239
266
|
shell: "Shell",
|
|
240
|
-
|
|
241
|
-
merges: "Merges"
|
|
267
|
+
merge: "Worktree merging"
|
|
242
268
|
};
|
|
243
269
|
function getCliHelpText() {
|
|
244
270
|
const entries = [...CLI_ONLY_HELP_ENTRIES, ...SHARED_HELP_ENTRIES];
|
|
@@ -337,7 +363,7 @@ function renderCliOnlyCommandHelp(entry) {
|
|
|
337
363
|
function renderSharedCommandHelp(pathSegments) {
|
|
338
364
|
const entry = SHARED_HELP_ENTRIES.map((candidate) => ({
|
|
339
365
|
candidate,
|
|
340
|
-
usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.
|
|
366
|
+
usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.includes("<") && !segment.startsWith("["))
|
|
341
367
|
})).filter(
|
|
342
368
|
({ usageSegments }) => usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index])
|
|
343
369
|
).sort((left, right) => right.usageSegments.length - left.usageSegments.length)[0]?.candidate;
|
|
@@ -385,6 +411,173 @@ function parseOptionalFlagValue(args, flag, shortFlag) {
|
|
|
385
411
|
function hasBooleanFlag(args, flag) {
|
|
386
412
|
return args.includes(flag);
|
|
387
413
|
}
|
|
414
|
+
function parseCommandOperands(args, valueFlags, booleanFlags = /* @__PURE__ */ new Map(), family = "command", allowFlagLikeValuesAfter = Number.POSITIVE_INFINITY) {
|
|
415
|
+
const positionals = [];
|
|
416
|
+
const values = /* @__PURE__ */ new Map();
|
|
417
|
+
const booleans = /* @__PURE__ */ new Set();
|
|
418
|
+
let parseOptions = true;
|
|
419
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
420
|
+
const arg = args[index];
|
|
421
|
+
if (parseOptions && arg === "--") {
|
|
422
|
+
parseOptions = false;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const booleanFlag = parseOptions ? booleanFlags.get(arg) : void 0;
|
|
426
|
+
if (booleanFlag) {
|
|
427
|
+
if (booleans.has(booleanFlag)) throw new Error(`${booleanFlag} may only be provided once`);
|
|
428
|
+
booleans.add(booleanFlag);
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const inlineFlag = parseOptions ? [...valueFlags].find((flag) => arg.startsWith(`${flag}=`)) : void 0;
|
|
432
|
+
if (inlineFlag) {
|
|
433
|
+
if (values.has(inlineFlag)) throw new Error(`${inlineFlag} may only be provided once`);
|
|
434
|
+
const value = arg.slice(inlineFlag.length + 1).trim();
|
|
435
|
+
if (!value) throw new Error(`Missing value for ${inlineFlag}`);
|
|
436
|
+
values.set(inlineFlag, value);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (parseOptions && valueFlags.has(arg)) {
|
|
440
|
+
if (values.has(arg)) throw new Error(`${arg} may only be provided once`);
|
|
441
|
+
const value = args[index + 1];
|
|
442
|
+
if (!value || value === "--" || valueFlags.has(value)) throw new Error(`Missing value for ${arg}`);
|
|
443
|
+
values.set(arg, value.trim());
|
|
444
|
+
index += 1;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (parseOptions && arg.startsWith("-") && positionals.length < allowFlagLikeValuesAfter) {
|
|
448
|
+
throw new Error(`Unknown ${family} flag: ${arg}`);
|
|
449
|
+
}
|
|
450
|
+
positionals.push(arg);
|
|
451
|
+
}
|
|
452
|
+
return { positionals, values, booleans };
|
|
453
|
+
}
|
|
454
|
+
function requireWorker(values) {
|
|
455
|
+
return requireValue(values.get("--worker")?.trim(), "--worker <label> is required");
|
|
456
|
+
}
|
|
457
|
+
function parseWorktreeSource(rawValue) {
|
|
458
|
+
const raw = requireValue(rawValue?.trim(), "--source worktree:<branch> is required with --new-worktree");
|
|
459
|
+
const separator = raw.indexOf(":");
|
|
460
|
+
const type = raw.slice(0, separator);
|
|
461
|
+
const branchName = raw.slice(separator + 1).trim();
|
|
462
|
+
if (type !== "worktree" || separator < 1 || !branchName) {
|
|
463
|
+
throw new Error("Invalid --source. Expected worktree:<branch>");
|
|
464
|
+
}
|
|
465
|
+
return { type, branchName };
|
|
466
|
+
}
|
|
467
|
+
function requireModel(value) {
|
|
468
|
+
const model = requireValue(value?.trim(), "--model <tier> is required");
|
|
469
|
+
if (!MODEL_TIERS.has(model)) throw new Error("Invalid --model. Expected one of: low, medium, high, max");
|
|
470
|
+
return model;
|
|
471
|
+
}
|
|
472
|
+
function requireSessionMode(value) {
|
|
473
|
+
const mode = requireValue(value?.trim(), "--mode <mode> is required");
|
|
474
|
+
if (!R5DCTL_SESSION_MODES.has(mode)) {
|
|
475
|
+
throw new Error("Invalid --mode. Expected one of: ask, plan, build, agent");
|
|
476
|
+
}
|
|
477
|
+
return mode;
|
|
478
|
+
}
|
|
479
|
+
function rejectSessionRunGlobalScopes(options, allowProject) {
|
|
480
|
+
if (!allowProject && options.project) throw new Error("--project/-p is only supported for `session start`");
|
|
481
|
+
if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
|
|
482
|
+
if (options.session) throw new Error("--session/-s is not supported for `session`; pass the session id explicitly");
|
|
483
|
+
}
|
|
484
|
+
function parseSessionRunCommand(options, args) {
|
|
485
|
+
const subcommand = requireValue(args[0], "Missing session command");
|
|
486
|
+
const commandArgs = args.slice(1);
|
|
487
|
+
if (subcommand === "start") {
|
|
488
|
+
rejectSessionRunGlobalScopes(options, true);
|
|
489
|
+
const project = requireValue(options.project, "--project/-p is required for `session start`");
|
|
490
|
+
const parsed = parseCommandOperands(
|
|
491
|
+
commandArgs,
|
|
492
|
+
/* @__PURE__ */ new Set(["--worker", "--worktree", "--new-worktree", "--source", "--model"]),
|
|
493
|
+
/* @__PURE__ */ new Map([
|
|
494
|
+
["-f", "--follow"],
|
|
495
|
+
["--follow", "--follow"],
|
|
496
|
+
["--watch", "--follow"],
|
|
497
|
+
["--tools", "--tools"]
|
|
498
|
+
]),
|
|
499
|
+
"session start",
|
|
500
|
+
1
|
|
501
|
+
);
|
|
502
|
+
const prompt = parsed.positionals.join(" ").trim();
|
|
503
|
+
if (!prompt) throw new Error("Prompt is required for `session start`");
|
|
504
|
+
const worktree = parsed.values.get("--worktree")?.trim();
|
|
505
|
+
const newWorktree = parsed.values.get("--new-worktree")?.trim();
|
|
506
|
+
if (Boolean(worktree) === Boolean(newWorktree)) {
|
|
507
|
+
throw new Error("Use exactly one of --worktree <branch> or --new-worktree <branch>");
|
|
508
|
+
}
|
|
509
|
+
const sourceValue = parsed.values.get("--source");
|
|
510
|
+
if (worktree && sourceValue) throw new Error("--source is only supported with --new-worktree");
|
|
511
|
+
return {
|
|
512
|
+
kind: "start",
|
|
513
|
+
project,
|
|
514
|
+
worker: requireWorker(parsed.values),
|
|
515
|
+
model: requireModel(parsed.values.get("--model")),
|
|
516
|
+
prompt,
|
|
517
|
+
follow: parsed.booleans.has("--follow"),
|
|
518
|
+
includeTools: parsed.booleans.has("--tools"),
|
|
519
|
+
...worktree ? { worktree } : { newWorktree, source: parseWorktreeSource(sourceValue) }
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
rejectSessionRunGlobalScopes(options, false);
|
|
523
|
+
if (subcommand === "status" || subcommand === "stop") {
|
|
524
|
+
const parsed = parseCommandOperands(commandArgs, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map(), `session ${subcommand}`);
|
|
525
|
+
const sessionId = requireValue(parsed.positionals[0], `Session id is required for \`session ${subcommand}\``);
|
|
526
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected session ${subcommand} argument: ${parsed.positionals[1]}`);
|
|
527
|
+
return { kind: subcommand, sessionId };
|
|
528
|
+
}
|
|
529
|
+
if (subcommand === "prompt") {
|
|
530
|
+
const parsed = parseCommandOperands(commandArgs, /* @__PURE__ */ new Set(["--worker", "--mode", "--model"]), /* @__PURE__ */ new Map(), "session prompt", 1);
|
|
531
|
+
const sessionId = requireValue(parsed.positionals[0], "Session id is required for `session prompt`");
|
|
532
|
+
const prompt = parsed.positionals.slice(1).join(" ").trim();
|
|
533
|
+
if (!prompt) throw new Error("Prompt is required for `session prompt`");
|
|
534
|
+
return {
|
|
535
|
+
kind: "prompt",
|
|
536
|
+
worker: requireWorker(parsed.values),
|
|
537
|
+
sessionId,
|
|
538
|
+
prompt,
|
|
539
|
+
mode: requireSessionMode(parsed.values.get("--mode")),
|
|
540
|
+
model: requireModel(parsed.values.get("--model"))
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
throw new Error(`Unknown session command: ${subcommand}`);
|
|
544
|
+
}
|
|
545
|
+
function parseMergeCommand(options, args) {
|
|
546
|
+
const project = requireValue(options.project, "--project/-p is required for `merge`");
|
|
547
|
+
if (options.branch) throw new Error("--branch/-b is not supported for `merge`; pass the source worktree explicitly");
|
|
548
|
+
if (options.session) throw new Error("--session/-s is not supported for `merge`");
|
|
549
|
+
if (args[0] === "finish") {
|
|
550
|
+
const parsed2 = parseCommandOperands(args.slice(1), /* @__PURE__ */ new Set(["--worker", "--summary"]), /* @__PURE__ */ new Map(), "merge finish");
|
|
551
|
+
const conflictWorktree = requireValue(parsed2.positionals[0], "Conflict worktree is required for `merge finish`");
|
|
552
|
+
if (parsed2.positionals.length > 1) throw new Error(`Unexpected merge finish argument: ${parsed2.positionals[1]}`);
|
|
553
|
+
return {
|
|
554
|
+
kind: "finish",
|
|
555
|
+
project,
|
|
556
|
+
conflictWorktree,
|
|
557
|
+
worker: requireWorker(parsed2.values),
|
|
558
|
+
...parsed2.values.get("--summary")?.trim() ? { summary: parsed2.values.get("--summary").trim() } : {}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const parsed = parseCommandOperands(
|
|
562
|
+
args,
|
|
563
|
+
/* @__PURE__ */ new Set(["--worker"]),
|
|
564
|
+
/* @__PURE__ */ new Map([
|
|
565
|
+
["--create-conflict-worktree", "--create-conflict-worktree"],
|
|
566
|
+
["--launch-conflict-resolution-agent", "--launch-conflict-resolution-agent"]
|
|
567
|
+
]),
|
|
568
|
+
"merge"
|
|
569
|
+
);
|
|
570
|
+
const sourceWorktree = requireValue(parsed.positionals[0], "Source worktree is required for `merge`");
|
|
571
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected merge argument: ${parsed.positionals[1]}`);
|
|
572
|
+
const createWorktree = parsed.booleans.has("--create-conflict-worktree");
|
|
573
|
+
const launchAgent = parsed.booleans.has("--launch-conflict-resolution-agent");
|
|
574
|
+
if (createWorktree && launchAgent) {
|
|
575
|
+
throw new Error("Use only one of --create-conflict-worktree or --launch-conflict-resolution-agent");
|
|
576
|
+
}
|
|
577
|
+
const conflictMode = createWorktree ? "worktree" : launchAgent ? "agent" : "none";
|
|
578
|
+
const worker = parsed.values.get("--worker")?.trim();
|
|
579
|
+
return { kind: "merge", project, sourceWorktree, conflictMode, ...worker ? { worker } : {} };
|
|
580
|
+
}
|
|
388
581
|
function assertAuthLoginArgs(args) {
|
|
389
582
|
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
390
583
|
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
@@ -601,6 +794,55 @@ function parseAnswerFlags(args) {
|
|
|
601
794
|
}
|
|
602
795
|
return ordered.map(([, value]) => value);
|
|
603
796
|
}
|
|
797
|
+
function requireChatMode(value) {
|
|
798
|
+
const mode = requireValue(value?.trim(), "--mode <mode> is required");
|
|
799
|
+
if (!CHAT_MODES.has(mode)) throw new Error(`Invalid --mode: ${mode}`);
|
|
800
|
+
return mode;
|
|
801
|
+
}
|
|
802
|
+
function partitionInteractiveExecutionArgs(args, consumesNextValue) {
|
|
803
|
+
const values = /* @__PURE__ */ new Map();
|
|
804
|
+
const payloadArgs = [];
|
|
805
|
+
const executionFlags = ["--worker", "--mode", "--model"];
|
|
806
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
807
|
+
const arg = args[index];
|
|
808
|
+
if (consumesNextValue(arg)) {
|
|
809
|
+
payloadArgs.push(arg);
|
|
810
|
+
const value = args[index + 1];
|
|
811
|
+
if (value === void 0) throw new Error(`Missing value for ${arg}`);
|
|
812
|
+
payloadArgs.push(value);
|
|
813
|
+
index += 1;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const inlineFlag = executionFlags.find((flag) => arg.startsWith(`${flag}=`));
|
|
817
|
+
if (inlineFlag) {
|
|
818
|
+
if (values.has(inlineFlag)) throw new Error(`${inlineFlag} may only be provided once`);
|
|
819
|
+
const value = requireValue(arg.slice(inlineFlag.length + 1).trim(), `Missing value for ${inlineFlag}`);
|
|
820
|
+
values.set(inlineFlag, value);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (executionFlags.includes(arg)) {
|
|
824
|
+
const flag = arg;
|
|
825
|
+
if (values.has(flag)) throw new Error(`${flag} may only be provided once`);
|
|
826
|
+
const value = requireValue(args[index + 1]?.trim(), `Missing value for ${flag}`);
|
|
827
|
+
values.set(flag, value);
|
|
828
|
+
index += 1;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
payloadArgs.push(arg);
|
|
832
|
+
}
|
|
833
|
+
return {
|
|
834
|
+
context: {
|
|
835
|
+
worker: requireValue(values.get("--worker")?.trim(), "--worker <label> is required"),
|
|
836
|
+
mode: requireChatMode(values.get("--mode")),
|
|
837
|
+
model: requireModel(values.get("--model"))
|
|
838
|
+
},
|
|
839
|
+
payloadArgs
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function parseAnswerQuestionsCommandArgs(args) {
|
|
843
|
+
const { context, payloadArgs } = partitionInteractiveExecutionArgs(args, (arg) => /^-a\d+$/.test(arg));
|
|
844
|
+
return { ...context, answers: parseAnswerFlags(payloadArgs) };
|
|
845
|
+
}
|
|
604
846
|
function validateEnvAssignment(value) {
|
|
605
847
|
const equalsIndex = value.indexOf("=");
|
|
606
848
|
if (equalsIndex <= 0) {
|
|
@@ -760,6 +1002,10 @@ function parseEnvRequestResponseArgs(args) {
|
|
|
760
1002
|
...additionalContext?.trim() ? { additionalContext: additionalContext.trim() } : {}
|
|
761
1003
|
};
|
|
762
1004
|
}
|
|
1005
|
+
function parseAnswerEnvRequestCommandArgs(args) {
|
|
1006
|
+
const { context, payloadArgs } = partitionInteractiveExecutionArgs(args, (arg) => arg === "-e" || arg === "--env" || arg === "--context");
|
|
1007
|
+
return { ...context, ...parseEnvRequestResponseArgs(payloadArgs) };
|
|
1008
|
+
}
|
|
763
1009
|
function parsePromptArgs(args) {
|
|
764
1010
|
let modeRaw;
|
|
765
1011
|
let modelRaw;
|
|
@@ -1160,7 +1406,7 @@ function renderWorkspaceStatus(status) {
|
|
|
1160
1406
|
`Remediation: ${status.incident ? `${status.incident.kind} ${status.incident.status} (${status.incident.id}) on ${status.incident.originWorkerLabel}` : "none"}`,
|
|
1161
1407
|
`Materialization: ${status.materialization ? `${status.materialization.state}; ${status.materialization.materializedHead ?? "(none)"} -> ${status.materialization.desiredHead ?? "(none)"}; lag ${status.materialization.lagMs}ms` : "not initialized"}`,
|
|
1162
1408
|
...status.workers.map(
|
|
1163
|
-
(worker) => `Worker ${worker.workerLabel}: ${worker.ready ? "ready" : "bootstrapping"}; ${worker.
|
|
1409
|
+
(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`
|
|
1164
1410
|
)
|
|
1165
1411
|
];
|
|
1166
1412
|
return `${lines.join("\n")}
|
|
@@ -1530,44 +1776,158 @@ async function setProjectEnvs(client, projectRef, args) {
|
|
|
1530
1776
|
}
|
|
1531
1777
|
return result;
|
|
1532
1778
|
}
|
|
1533
|
-
function
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1779
|
+
function responseString(response, key) {
|
|
1780
|
+
const value = response[key];
|
|
1781
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1782
|
+
}
|
|
1783
|
+
function formatStatusValue(value) {
|
|
1784
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1785
|
+
if (typeof value === "object" && value !== null) {
|
|
1786
|
+
const status = value.status;
|
|
1787
|
+
if (typeof status === "string" && status.length > 0) return status;
|
|
1788
|
+
return JSON.stringify(value);
|
|
1789
|
+
}
|
|
1790
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
1791
|
+
}
|
|
1792
|
+
function appendMergeDetails(lines, merge) {
|
|
1793
|
+
const mergeStatus = formatStatusValue(merge.status);
|
|
1794
|
+
lines.push(`Merge: ${mergeStatus ?? "pending"}`);
|
|
1795
|
+
for (const [label, key] of [
|
|
1796
|
+
["Attempt", "attemptId"],
|
|
1797
|
+
["Source worktree", "sourceWorktree"],
|
|
1798
|
+
["Target worktree", "targetWorktree"],
|
|
1799
|
+
["Commit", "commitHash"],
|
|
1800
|
+
["Resolver agent", "resolverSessionId"],
|
|
1801
|
+
["Conflict worktree", "conflictWorktree"],
|
|
1802
|
+
["Resolver worktree", "folderPath"]
|
|
1803
|
+
]) {
|
|
1804
|
+
const value = responseString(merge, key);
|
|
1805
|
+
if (value) lines.push(`${label}: ${value}`);
|
|
1806
|
+
}
|
|
1807
|
+
const conflictedFiles = Array.isArray(merge.conflictedFiles) ? merge.conflictedFiles.filter((file) => typeof file === "string") : [];
|
|
1808
|
+
if (conflictedFiles.length > 0) {
|
|
1809
|
+
lines.push("", "Conflicts:", ...conflictedFiles.map((file) => `- ${file}`));
|
|
1810
|
+
}
|
|
1811
|
+
const message = responseString(merge, "message");
|
|
1812
|
+
if (message) lines.push("", message);
|
|
1813
|
+
const failureReason = responseString(merge, "failureReason");
|
|
1814
|
+
if (failureReason) lines.push(`Failure: ${failureReason}`);
|
|
1815
|
+
}
|
|
1816
|
+
function renderSessionRunStart(session) {
|
|
1817
|
+
const lines = [
|
|
1818
|
+
`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`,
|
|
1819
|
+
`Status: ${formatStatusValue(session.status) ?? "queued"}`,
|
|
1820
|
+
`Worktree: ${responseString(session, "branchName") ?? "(provisioning)"}`,
|
|
1821
|
+
`Worker: ${responseString(session, "worker") ?? "(unassigned)"}`
|
|
1822
|
+
];
|
|
1823
|
+
const sessionUrl = responseString(session, "sessionUrl");
|
|
1824
|
+
if (sessionUrl) lines.push(`URL: ${sessionUrl}`);
|
|
1825
|
+
const sessionId = responseString(session, "sessionId");
|
|
1826
|
+
if (sessionId) lines.push("", `Next: r5dctl session status ${sessionId}`);
|
|
1827
|
+
return `${lines.join("\n")}
|
|
1828
|
+
`;
|
|
1540
1829
|
}
|
|
1541
|
-
function
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
if (
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1830
|
+
function renderSessionRunStatus(session) {
|
|
1831
|
+
const status = formatStatusValue(session.status) ?? "unknown";
|
|
1832
|
+
const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
|
|
1833
|
+
const runStatus = formatStatusValue(session.runStatus);
|
|
1834
|
+
if (runStatus) lines.push(`Run: ${runStatus}`);
|
|
1835
|
+
const promptDisposition = responseString(session, "promptDisposition");
|
|
1836
|
+
if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
|
|
1837
|
+
const branch = responseString(session, "branchName");
|
|
1838
|
+
if (branch) lines.push(`Worktree: ${branch}`);
|
|
1839
|
+
for (const [label, key] of [
|
|
1840
|
+
["Workspace head", "workspaceHead"],
|
|
1841
|
+
["Baseline", "baselineCommit"],
|
|
1842
|
+
["Head", "headCommit"],
|
|
1843
|
+
["Worker", "worker"],
|
|
1844
|
+
["Session", "sessionUrl"]
|
|
1845
|
+
]) {
|
|
1846
|
+
const value = responseString(session, key);
|
|
1847
|
+
if (value) lines.push(`${label}: ${value}`);
|
|
1848
|
+
}
|
|
1849
|
+
const error = responseString(session, "error");
|
|
1850
|
+
if (error) lines.push(`Error: ${error}`);
|
|
1851
|
+
const headCommitError = responseString(session, "headCommitError");
|
|
1852
|
+
if (headCommitError) lines.push(`Head error: ${headCommitError}`);
|
|
1853
|
+
const diffSummary = responseString(session, "diffSummary");
|
|
1854
|
+
if (diffSummary) lines.push("", diffSummary);
|
|
1855
|
+
const summary = responseString(session, "summary");
|
|
1856
|
+
if (summary) lines.push("", summary);
|
|
1857
|
+
const sessionId = responseString(session, "sessionId");
|
|
1858
|
+
if (sessionId && ["provisioning", "queued", "running"].includes(status)) {
|
|
1859
|
+
lines.push("", "Next:", ` r5dctl session status ${sessionId}`, ` r5dctl session stop ${sessionId}`);
|
|
1554
1860
|
}
|
|
1555
1861
|
return `${lines.join("\n")}
|
|
1556
1862
|
`;
|
|
1557
1863
|
}
|
|
1558
|
-
function
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1864
|
+
function renderWorktreeMerge(result, input) {
|
|
1865
|
+
const lines = [];
|
|
1866
|
+
appendMergeDetails(lines, result);
|
|
1867
|
+
const conflictWorktree = responseString(result, "conflictWorktree");
|
|
1868
|
+
const resolverSessionId = responseString(result, "resolverSessionId");
|
|
1869
|
+
const worker = responseString(result, "worker") ?? input.worker;
|
|
1870
|
+
if (resolverSessionId) lines.push("", `Resolver session: ${resolverSessionId}`, ` r5dctl session stop ${resolverSessionId}`);
|
|
1871
|
+
if (conflictWorktree && worker && ["conflicts", "conflict_worktree_created", "resolving"].includes(responseString(result, "status") ?? "")) {
|
|
1872
|
+
lines.push(
|
|
1873
|
+
"",
|
|
1874
|
+
"Finish after resolving and reviewing the conflict worktree:",
|
|
1875
|
+
` r5dctl -p ${input.project} merge finish --worker ${worker} ${conflictWorktree} --summary "<summary>"`
|
|
1876
|
+
);
|
|
1563
1877
|
}
|
|
1564
|
-
|
|
1565
|
-
Conflicts:
|
|
1566
|
-
${result.conflictedFiles.map((file) => `- ${file}`).join("\n")}
|
|
1567
|
-
` : "\n";
|
|
1568
|
-
return `Merge has conflicts from ${result.sourceBranch} into ${result.targetBranch}.${files}${result.message}
|
|
1878
|
+
return `${lines.join("\n")}
|
|
1569
1879
|
`;
|
|
1570
1880
|
}
|
|
1881
|
+
async function dispatchSessionRunCommand(client, command) {
|
|
1882
|
+
if (command.kind === "start") {
|
|
1883
|
+
const commonInput = {
|
|
1884
|
+
prompt: command.prompt,
|
|
1885
|
+
worker: command.worker,
|
|
1886
|
+
model: command.model,
|
|
1887
|
+
requestId: randomUUID()
|
|
1888
|
+
};
|
|
1889
|
+
let input;
|
|
1890
|
+
if (command.worktree !== void 0) {
|
|
1891
|
+
input = { ...commonInput, worktree: command.worktree };
|
|
1892
|
+
} else if (command.newWorktree !== void 0 && command.source !== void 0) {
|
|
1893
|
+
input = { ...commonInput, newWorktree: command.newWorktree, source: command.source };
|
|
1894
|
+
} else {
|
|
1895
|
+
throw new Error("Session start requires an existing or new worktree");
|
|
1896
|
+
}
|
|
1897
|
+
const data2 = await client.projects.sessions.start(command.project, input);
|
|
1898
|
+
return { data: data2, human: renderSessionRunStart(data2) };
|
|
1899
|
+
}
|
|
1900
|
+
if (command.kind === "status") {
|
|
1901
|
+
const data2 = await client.sessions.status(command.sessionId);
|
|
1902
|
+
return { data: data2, human: renderSessionRunStatus(data2) };
|
|
1903
|
+
}
|
|
1904
|
+
if (command.kind === "prompt") {
|
|
1905
|
+
const data2 = await client.sessions.prompt(command.sessionId, {
|
|
1906
|
+
prompt: command.prompt,
|
|
1907
|
+
worker: command.worker,
|
|
1908
|
+
mode: command.mode,
|
|
1909
|
+
model: command.model,
|
|
1910
|
+
requestId: randomUUID()
|
|
1911
|
+
});
|
|
1912
|
+
return { data: data2, human: renderSessionRunStatus(data2) };
|
|
1913
|
+
}
|
|
1914
|
+
const data = await client.sessions.stop(command.sessionId);
|
|
1915
|
+
return { data, human: renderSessionRunStatus(data) };
|
|
1916
|
+
}
|
|
1917
|
+
async function dispatchMergeCommand(client, command) {
|
|
1918
|
+
if (command.kind === "merge") {
|
|
1919
|
+
const data2 = await client.projects.worktrees.merge(command.project, command.sourceWorktree, {
|
|
1920
|
+
conflictMode: command.conflictMode,
|
|
1921
|
+
...command.worker ? { worker: command.worker } : {}
|
|
1922
|
+
});
|
|
1923
|
+
return { data: data2, human: renderWorktreeMerge(data2, command) };
|
|
1924
|
+
}
|
|
1925
|
+
const data = await client.projects.worktrees.finishMerge(command.project, command.conflictWorktree, {
|
|
1926
|
+
worker: command.worker,
|
|
1927
|
+
...command.summary ? { summary: command.summary } : {}
|
|
1928
|
+
});
|
|
1929
|
+
return { data, human: renderWorktreeMerge(data, command) };
|
|
1930
|
+
}
|
|
1571
1931
|
function parseProjectUpdateArgs(args) {
|
|
1572
1932
|
const input = {};
|
|
1573
1933
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -1641,7 +2001,7 @@ function parseSessionUpdateArgs(args) {
|
|
|
1641
2001
|
return input;
|
|
1642
2002
|
}
|
|
1643
2003
|
function parseConversationRenderArgs(args) {
|
|
1644
|
-
const options = { format: "human", includeTools: false, includeSystem: false };
|
|
2004
|
+
const options = { format: "human", includeTools: false, includeSystem: false, follow: false };
|
|
1645
2005
|
for (const arg of args) {
|
|
1646
2006
|
if (arg === "--raw") {
|
|
1647
2007
|
options.format = "raw";
|
|
@@ -1655,6 +2015,11 @@ function parseConversationRenderArgs(args) {
|
|
|
1655
2015
|
options.includeSystem = true;
|
|
1656
2016
|
continue;
|
|
1657
2017
|
}
|
|
2018
|
+
if (arg === "-f" || arg === "--follow" || arg === "--watch") {
|
|
2019
|
+
if (options.follow) throw new Error("--follow may only be provided once");
|
|
2020
|
+
options.follow = true;
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
1658
2023
|
throw new Error(`Unknown conversation argument: ${arg}`);
|
|
1659
2024
|
}
|
|
1660
2025
|
return options;
|
|
@@ -1663,6 +2028,306 @@ function renderConversationResponse(read) {
|
|
|
1663
2028
|
return read.agentText.endsWith("\n") ? read.agentText : `${read.agentText}
|
|
1664
2029
|
`;
|
|
1665
2030
|
}
|
|
2031
|
+
function recordValue(value) {
|
|
2032
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2033
|
+
}
|
|
2034
|
+
function nodeId(node) {
|
|
2035
|
+
const id = recordValue(node)?.id;
|
|
2036
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
2037
|
+
}
|
|
2038
|
+
function nodeType(node) {
|
|
2039
|
+
const type = recordValue(node)?.type;
|
|
2040
|
+
return typeof type === "string" ? type : void 0;
|
|
2041
|
+
}
|
|
2042
|
+
function assistantNodeText(node) {
|
|
2043
|
+
const record = recordValue(node);
|
|
2044
|
+
return record?.type === "assistant_message" && typeof record.content === "string" ? record.content : void 0;
|
|
2045
|
+
}
|
|
2046
|
+
function conversationRevision(read) {
|
|
2047
|
+
const revision = recordValue(read.conversation)?.revision;
|
|
2048
|
+
return typeof revision === "number" ? revision : 0;
|
|
2049
|
+
}
|
|
2050
|
+
function activeConversationNodes(conversation) {
|
|
2051
|
+
const record = recordValue(conversation);
|
|
2052
|
+
const responses = recordValue(record?.responses);
|
|
2053
|
+
let currentId = typeof record?.head === "string" ? record.head : "";
|
|
2054
|
+
if (!responses || !currentId) return [];
|
|
2055
|
+
const reversed = [];
|
|
2056
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2057
|
+
while (currentId && !visited.has(currentId)) {
|
|
2058
|
+
visited.add(currentId);
|
|
2059
|
+
const node = responses[currentId];
|
|
2060
|
+
if (!node) break;
|
|
2061
|
+
reversed.push(node);
|
|
2062
|
+
const parent = recordValue(node)?.parent;
|
|
2063
|
+
currentId = typeof parent === "string" ? parent : "";
|
|
2064
|
+
}
|
|
2065
|
+
return reversed.reverse();
|
|
2066
|
+
}
|
|
2067
|
+
function isToolEvent(event) {
|
|
2068
|
+
return ["tool_use_start", "input_json_delta", "tool_call", "tool_progress", "shell_result"].includes(event.type);
|
|
2069
|
+
}
|
|
2070
|
+
function withoutToolDetails(node) {
|
|
2071
|
+
const record = recordValue(node);
|
|
2072
|
+
if (!record || record.type !== "assistant_message" || !Array.isArray(record.toolCalls)) return node;
|
|
2073
|
+
return { ...record, toolCalls: [] };
|
|
2074
|
+
}
|
|
2075
|
+
function jsonConversationData(read, includeTools) {
|
|
2076
|
+
if (includeTools) return read;
|
|
2077
|
+
const { conversation: _conversation, thread, ...rest } = read;
|
|
2078
|
+
return {
|
|
2079
|
+
...rest,
|
|
2080
|
+
thread: Array.isArray(thread) ? thread.filter((node) => nodeType(node) !== "tool_result").map((node) => withoutToolDetails(node)) : []
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
function jsonFollowEvent(event, includeTools) {
|
|
2084
|
+
if (event.type === "session") {
|
|
2085
|
+
const data = recordValue(event.data);
|
|
2086
|
+
if (!data) return event;
|
|
2087
|
+
const { conversation: _conversation, ...rest } = data;
|
|
2088
|
+
return { ...event, data: rest };
|
|
2089
|
+
}
|
|
2090
|
+
if (includeTools) return event;
|
|
2091
|
+
if (isToolEvent(event)) return null;
|
|
2092
|
+
if (event.type === "response") {
|
|
2093
|
+
const data = recordValue(event.data);
|
|
2094
|
+
const node = data?.node;
|
|
2095
|
+
if (nodeType(node) === "tool_result") return null;
|
|
2096
|
+
return { ...event, data: { ...data, node: withoutToolDetails(node) } };
|
|
2097
|
+
}
|
|
2098
|
+
return event;
|
|
2099
|
+
}
|
|
2100
|
+
function terminalRunStatus(response) {
|
|
2101
|
+
const status = formatStatusValue(response.runStatus) ?? formatStatusValue(response.status);
|
|
2102
|
+
return {
|
|
2103
|
+
terminal: status !== void 0 && ["idle", "completed", "failed", "stopped"].includes(status),
|
|
2104
|
+
failed: status === "failed"
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
function isAbortError(error) {
|
|
2108
|
+
return error instanceof DOMException && error.name === "AbortError" || error instanceof Error && (error.name === "AbortError" || error.message.toLowerCase().includes("aborted"));
|
|
2109
|
+
}
|
|
2110
|
+
async function followR5dctlSession(client, sessionId, options) {
|
|
2111
|
+
const writeOut = options.stdout ?? ((text) => process.stdout.write(text));
|
|
2112
|
+
const writeErr = options.stderr ?? ((text) => process.stderr.write(text));
|
|
2113
|
+
const seenNodeIds = /* @__PURE__ */ new Set();
|
|
2114
|
+
let latestRevision = 0;
|
|
2115
|
+
let streamedAssistantText = "";
|
|
2116
|
+
let reconnects = 0;
|
|
2117
|
+
const emitJson = (value) => writeOut(`${JSON.stringify(value)}
|
|
2118
|
+
`);
|
|
2119
|
+
const renderNode = (node) => {
|
|
2120
|
+
const id = nodeId(node);
|
|
2121
|
+
if (id && seenNodeIds.has(id)) return;
|
|
2122
|
+
if (id) seenNodeIds.add(id);
|
|
2123
|
+
const text = assistantNodeText(node);
|
|
2124
|
+
if (text !== void 0) {
|
|
2125
|
+
let missing = text;
|
|
2126
|
+
if (streamedAssistantText && text.startsWith(streamedAssistantText)) missing = text.slice(streamedAssistantText.length);
|
|
2127
|
+
else if (streamedAssistantText.startsWith(text)) missing = "";
|
|
2128
|
+
if (missing) writeOut(missing);
|
|
2129
|
+
streamedAssistantText = "";
|
|
2130
|
+
if (options.includeTools) {
|
|
2131
|
+
const toolCalls = recordValue(node)?.toolCalls;
|
|
2132
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) writeErr(`[tools] ${JSON.stringify(toolCalls)}
|
|
2133
|
+
`);
|
|
2134
|
+
}
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
if (options.includeTools && nodeType(node) === "tool_result") writeErr(`[tool results] ${JSON.stringify(node)}
|
|
2138
|
+
`);
|
|
2139
|
+
};
|
|
2140
|
+
const syncConversation = async (initial = false) => {
|
|
2141
|
+
const read = initial && options.initialConversation ? options.initialConversation : await client.sessions.conversation(sessionId, options.renderOptions);
|
|
2142
|
+
const nodes = Array.isArray(read.thread) ? read.thread : [];
|
|
2143
|
+
const newNodes = nodes.filter((node) => {
|
|
2144
|
+
const id = nodeId(node);
|
|
2145
|
+
return !id || !seenNodeIds.has(id);
|
|
2146
|
+
});
|
|
2147
|
+
if (initial && options.printInitialTranscript) {
|
|
2148
|
+
if (options.json)
|
|
2149
|
+
emitJson({ type: "conversation", revision: conversationRevision(read), data: jsonConversationData(read, options.includeTools) });
|
|
2150
|
+
else writeOut(renderConversationResponse(read));
|
|
2151
|
+
for (const node of nodes) {
|
|
2152
|
+
const id = nodeId(node);
|
|
2153
|
+
if (id) seenNodeIds.add(id);
|
|
2154
|
+
}
|
|
2155
|
+
} else if (options.json && newNodes.length > 0) {
|
|
2156
|
+
const filtered = options.includeTools ? newNodes : newNodes.filter((node) => nodeType(node) !== "tool_result");
|
|
2157
|
+
if (filtered.length > 0) {
|
|
2158
|
+
emitJson({
|
|
2159
|
+
type: "conversation_sync",
|
|
2160
|
+
revision: conversationRevision(read),
|
|
2161
|
+
nodes: options.includeTools ? filtered : filtered.map((node) => withoutToolDetails(node))
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
for (const node of newNodes) {
|
|
2165
|
+
const id = nodeId(node);
|
|
2166
|
+
if (id) seenNodeIds.add(id);
|
|
2167
|
+
}
|
|
2168
|
+
} else {
|
|
2169
|
+
for (const node of newNodes) renderNode(node);
|
|
2170
|
+
}
|
|
2171
|
+
latestRevision = Math.max(latestRevision, conversationRevision(read));
|
|
2172
|
+
return read;
|
|
2173
|
+
};
|
|
2174
|
+
await syncConversation(true);
|
|
2175
|
+
while (!options.signal?.aborted) {
|
|
2176
|
+
try {
|
|
2177
|
+
for await (const event of client.sessions.events(sessionId, { signal: options.signal })) {
|
|
2178
|
+
reconnects = 0;
|
|
2179
|
+
if (event.type === "response") {
|
|
2180
|
+
const data = recordValue(event.data);
|
|
2181
|
+
const revision = data?.conversationRevision;
|
|
2182
|
+
const id = nodeId(data?.node);
|
|
2183
|
+
if (typeof revision === "number" && revision <= latestRevision || id !== void 0 && seenNodeIds.has(id)) continue;
|
|
2184
|
+
}
|
|
2185
|
+
if (options.json) {
|
|
2186
|
+
const outputEvent = jsonFollowEvent(event, options.includeTools);
|
|
2187
|
+
if (outputEvent) emitJson(outputEvent);
|
|
2188
|
+
} else if (event.type === "text_delta" && typeof event.text === "string") {
|
|
2189
|
+
writeOut(event.text);
|
|
2190
|
+
streamedAssistantText += event.text;
|
|
2191
|
+
} else if (event.type === "thinking_delta" && typeof event.text === "string") {
|
|
2192
|
+
writeErr(event.text);
|
|
2193
|
+
} else if (event.type === "tool_use_start" && options.includeTools) {
|
|
2194
|
+
writeErr(`[tool] ${String(event.toolName)}
|
|
2195
|
+
`);
|
|
2196
|
+
} else if (event.type === "input_json_delta" && options.includeTools) {
|
|
2197
|
+
writeErr(String(event.partial_json));
|
|
2198
|
+
} else if ((event.type === "tool_call" || event.type === "tool_progress" || event.type === "shell_result") && options.includeTools) {
|
|
2199
|
+
writeErr(`[${event.type}] ${JSON.stringify(event)}
|
|
2200
|
+
`);
|
|
2201
|
+
} else if (event.type === "status") {
|
|
2202
|
+
writeErr(`[status] ${formatStatusValue(event.status) ?? "updated"}
|
|
2203
|
+
`);
|
|
2204
|
+
} else if (event.type === "agent_running") {
|
|
2205
|
+
writeErr(`[running] ${event.running ? "yes" : "no"}
|
|
2206
|
+
`);
|
|
2207
|
+
} else if (event.type === "stream_reset") {
|
|
2208
|
+
streamedAssistantText = "";
|
|
2209
|
+
writeErr(`[retry] ${event.attempt}/${event.maxAttempts}
|
|
2210
|
+
`);
|
|
2211
|
+
} else if (event.type === "response") {
|
|
2212
|
+
const revision = recordValue(event.data)?.conversationRevision;
|
|
2213
|
+
if (typeof revision === "number") latestRevision = revision;
|
|
2214
|
+
renderNode(recordValue(event.data)?.node);
|
|
2215
|
+
} else if (event.type === "session") {
|
|
2216
|
+
const session = recordValue(event.data);
|
|
2217
|
+
const conversation = session?.conversation;
|
|
2218
|
+
for (const node of activeConversationNodes(conversation)) renderNode(node);
|
|
2219
|
+
const revision = recordValue(conversation)?.revision;
|
|
2220
|
+
if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
|
|
2221
|
+
const streamingText = recordValue(session?.streaming)?.text;
|
|
2222
|
+
if (!options.json && typeof streamingText === "string" && streamingText.length > 0) {
|
|
2223
|
+
let missing = streamingText;
|
|
2224
|
+
if (streamedAssistantText && streamingText.startsWith(streamedAssistantText))
|
|
2225
|
+
missing = streamingText.slice(streamedAssistantText.length);
|
|
2226
|
+
else if (streamedAssistantText.startsWith(streamingText)) missing = "";
|
|
2227
|
+
if (missing) writeOut(missing);
|
|
2228
|
+
streamedAssistantText = streamingText;
|
|
2229
|
+
}
|
|
2230
|
+
if (session?.agentRunning === false) {
|
|
2231
|
+
try {
|
|
2232
|
+
const terminal = terminalRunStatus(await client.sessions.status(sessionId));
|
|
2233
|
+
if (terminal.terminal) return terminal.failed ? 1 : 0;
|
|
2234
|
+
} catch (error) {
|
|
2235
|
+
if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2236
|
+
const status = formatStatusValue(session.status);
|
|
2237
|
+
if (error instanceof R5dctlApiError && error.status === 400 && status === "idle") return 0;
|
|
2238
|
+
if (error instanceof R5dctlApiError && error.status === 400 && status === "error") return 1;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
if (event.type === "response" && options.json) {
|
|
2243
|
+
const revision = recordValue(event.data)?.conversationRevision;
|
|
2244
|
+
if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
|
|
2245
|
+
const id = nodeId(recordValue(event.data)?.node);
|
|
2246
|
+
if (id) seenNodeIds.add(id);
|
|
2247
|
+
}
|
|
2248
|
+
if (event.type === "session" && options.json) {
|
|
2249
|
+
const session = recordValue(event.data);
|
|
2250
|
+
const conversation = session?.conversation;
|
|
2251
|
+
const newNodes = activeConversationNodes(conversation).filter((node) => {
|
|
2252
|
+
const id = nodeId(node);
|
|
2253
|
+
return !id || !seenNodeIds.has(id);
|
|
2254
|
+
});
|
|
2255
|
+
const filteredNodes = options.includeTools ? newNodes : newNodes.filter((node) => nodeType(node) !== "tool_result");
|
|
2256
|
+
if (filteredNodes.length > 0) {
|
|
2257
|
+
emitJson({
|
|
2258
|
+
type: "conversation_sync",
|
|
2259
|
+
revision: recordValue(conversation)?.revision,
|
|
2260
|
+
nodes: options.includeTools ? filteredNodes : filteredNodes.map((node) => withoutToolDetails(node))
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
for (const node of newNodes) {
|
|
2264
|
+
const id = nodeId(node);
|
|
2265
|
+
if (id) seenNodeIds.add(id);
|
|
2266
|
+
}
|
|
2267
|
+
const revision = recordValue(conversation)?.revision;
|
|
2268
|
+
if (typeof revision === "number") latestRevision = Math.max(latestRevision, revision);
|
|
2269
|
+
if (session?.agentRunning === false) {
|
|
2270
|
+
try {
|
|
2271
|
+
const terminal = terminalRunStatus(await client.sessions.status(sessionId));
|
|
2272
|
+
if (terminal.terminal) return terminal.failed ? 1 : 0;
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2275
|
+
const status = formatStatusValue(session.status);
|
|
2276
|
+
if (error instanceof R5dctlApiError && error.status === 400 && status === "idle") return 0;
|
|
2277
|
+
if (error instanceof R5dctlApiError && error.status === 400 && status === "error") return 1;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
if (event.type === "done" || event.type === "stopped") {
|
|
2282
|
+
await syncConversation();
|
|
2283
|
+
if (!options.json) writeErr(`[${event.type}]
|
|
2284
|
+
`);
|
|
2285
|
+
try {
|
|
2286
|
+
const terminal = terminalRunStatus(await client.sessions.status(sessionId));
|
|
2287
|
+
if (terminal.terminal) return terminal.failed ? 1 : 0;
|
|
2288
|
+
if (event.type === "done") continue;
|
|
2289
|
+
return 0;
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2292
|
+
if (error instanceof R5dctlApiError && error.status === 400) return 0;
|
|
2293
|
+
throw error;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
if (event.type === "error") {
|
|
2297
|
+
if (!options.json) writeErr(`${typeof event.message === "string" ? event.message : "Session failed"}
|
|
2298
|
+
`);
|
|
2299
|
+
return 1;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
if (options.signal?.aborted || isAbortError(error)) return 0;
|
|
2304
|
+
if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2305
|
+
}
|
|
2306
|
+
if (options.signal?.aborted) return 0;
|
|
2307
|
+
try {
|
|
2308
|
+
await syncConversation();
|
|
2309
|
+
} catch (error) {
|
|
2310
|
+
if (error instanceof R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2311
|
+
}
|
|
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 R5dctlApiError && [401, 403, 404].includes(error.status)) throw error;
|
|
2317
|
+
}
|
|
2318
|
+
reconnects += 1;
|
|
2319
|
+
if (reconnects > (options.maxReconnects ?? Number.POSITIVE_INFINITY)) {
|
|
2320
|
+
throw new Error("Session event stream ended before the session became idle");
|
|
2321
|
+
}
|
|
2322
|
+
try {
|
|
2323
|
+
await sleep(options.reconnectDelayMs ?? Math.min(250 * 2 ** (reconnects - 1), 5e3), void 0, { signal: options.signal });
|
|
2324
|
+
} catch (error) {
|
|
2325
|
+
if (options.signal?.aborted || isAbortError(error)) return 0;
|
|
2326
|
+
throw error;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
return 0;
|
|
2330
|
+
}
|
|
1666
2331
|
function parseConversationWorkDetailArgs(args) {
|
|
1667
2332
|
let detail = "compact";
|
|
1668
2333
|
let selected = false;
|
|
@@ -1866,65 +2531,6 @@ Sessions: ${result.sessions.length}
|
|
|
1866
2531
|
write(session, renderSessionDescription(session));
|
|
1867
2532
|
return;
|
|
1868
2533
|
}
|
|
1869
|
-
if (first === "start-agent") {
|
|
1870
|
-
const projectRef = requireValue(args[1], "Missing project reference");
|
|
1871
|
-
const sourceBranch = requireValue(args[2], "Missing source branch");
|
|
1872
|
-
const agentType = requireValue(args[3], "Missing agent type");
|
|
1873
|
-
if (!AGENT_TYPES.has(agentType)) {
|
|
1874
|
-
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
1875
|
-
}
|
|
1876
|
-
const prompt = args.slice(4).join(" ").trim();
|
|
1877
|
-
if (!prompt) {
|
|
1878
|
-
throw new Error("Agent prompt is required");
|
|
1879
|
-
}
|
|
1880
|
-
const agent = await client.projects.agents.start(projectRef, {
|
|
1881
|
-
sourceBranch,
|
|
1882
|
-
agentType,
|
|
1883
|
-
prompt
|
|
1884
|
-
});
|
|
1885
|
-
write(agent, renderAgentStart(agent));
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
if (first === "agent-status") {
|
|
1889
|
-
const agent = await client.agents.status(requireValue(args[1], "Missing session id"));
|
|
1890
|
-
write(agent, renderAgentStatus(agent));
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
if (first === "send-prompt") {
|
|
1894
|
-
const sessionId = requireValue(args[1], "Missing session id");
|
|
1895
|
-
const prompt = args.slice(2).join(" ").trim();
|
|
1896
|
-
if (!prompt) {
|
|
1897
|
-
throw new Error("Prompt is required");
|
|
1898
|
-
}
|
|
1899
|
-
const agent = await client.agents.sendPrompt(sessionId, { prompt });
|
|
1900
|
-
write(agent, renderAgentStatus(agent));
|
|
1901
|
-
return;
|
|
1902
|
-
}
|
|
1903
|
-
if (first === "merge-changes") {
|
|
1904
|
-
const result = await client.projects.mergeChanges(requireValue(args[1], "Missing project reference"), {
|
|
1905
|
-
targetBranch: requireValue(args[2], "Missing target branch"),
|
|
1906
|
-
sourceBranch: requireValue(args[3], "Missing source branch")
|
|
1907
|
-
});
|
|
1908
|
-
write(result, renderMergeResult(result));
|
|
1909
|
-
return;
|
|
1910
|
-
}
|
|
1911
|
-
if (first === "continue-merge") {
|
|
1912
|
-
const result = await client.projects.continueMerge(requireValue(args[1], "Missing project reference"), {
|
|
1913
|
-
targetBranch: requireValue(args[2], "Missing target branch")
|
|
1914
|
-
});
|
|
1915
|
-
write(result, `Merge committed: ${result.commitHash}
|
|
1916
|
-
${result.message}
|
|
1917
|
-
`);
|
|
1918
|
-
return;
|
|
1919
|
-
}
|
|
1920
|
-
if (first === "abort-merge") {
|
|
1921
|
-
const result = await client.projects.abortMerge(requireValue(args[1], "Missing project reference"), {
|
|
1922
|
-
targetBranch: requireValue(args[2], "Missing target branch")
|
|
1923
|
-
});
|
|
1924
|
-
write(result, `${result.message}
|
|
1925
|
-
`);
|
|
1926
|
-
return;
|
|
1927
|
-
}
|
|
1928
2534
|
if (first === "sessions" && second === "describe" || first === "describe" && second === "session") {
|
|
1929
2535
|
const session = await client.sessions.describe(requireValue(args[2], "Missing session id"));
|
|
1930
2536
|
write(session, renderSessionDescription(session));
|
|
@@ -1953,8 +2559,8 @@ ${result.message}
|
|
|
1953
2559
|
throw new Error(`Unexpected conversation inspect-node argument: ${args[4]}`);
|
|
1954
2560
|
}
|
|
1955
2561
|
const sessionId = requireValue(args[2], "Missing session id");
|
|
1956
|
-
const
|
|
1957
|
-
const read = await client.sessions.inspectConversationNode(sessionId,
|
|
2562
|
+
const nodeId2 = requireValue(args[3], "Missing node id");
|
|
2563
|
+
const read = await client.sessions.inspectConversationNode(sessionId, nodeId2);
|
|
1958
2564
|
write(read, renderConversationNodeResponse(read));
|
|
1959
2565
|
return;
|
|
1960
2566
|
}
|
|
@@ -1969,33 +2575,30 @@ ${result.message}
|
|
|
1969
2575
|
if (first === "sessions" && second === "conversation" || first === "conversation") {
|
|
1970
2576
|
const offset = first === "conversation" ? 1 : 2;
|
|
1971
2577
|
const renderOptions = parseConversationRenderArgs(args.slice(offset + 1));
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
}
|
|
1976
|
-
if (first === "sessions" && second === "prompt" || first === "prompt") {
|
|
1977
|
-
const offset = first === "prompt" ? 1 : 2;
|
|
1978
|
-
const read = await client.sessions.prompt(requireValue(args[offset], "Missing session id"), {
|
|
1979
|
-
mode: requireValue(args[offset + 1], "Missing mode"),
|
|
1980
|
-
model: requireValue(args[offset + 2], "Missing model"),
|
|
1981
|
-
message: requireValue(args[offset + 3], "Missing prompt message")
|
|
1982
|
-
});
|
|
2578
|
+
if (renderOptions.follow) throw new Error("Conversation follow must be handled by the CLI stream runner");
|
|
2579
|
+
const { follow: _follow, ...apiRenderOptions } = renderOptions;
|
|
2580
|
+
const read = await client.sessions.conversation(requireValue(args[offset], "Missing session id"), apiRenderOptions);
|
|
1983
2581
|
write(read, renderConversationResponse(read));
|
|
1984
2582
|
return;
|
|
1985
2583
|
}
|
|
1986
2584
|
if (first === "sessions" && second === "answer-questions" || first === "answer-questions") {
|
|
1987
2585
|
const offset = first === "answer-questions" ? 1 : 2;
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2586
|
+
const input = parseAnswerQuestionsCommandArgs(args.slice(offset + 1));
|
|
2587
|
+
const read = await client.sessions.answerQuestions(requireValue(args[offset], "Missing session id"), {
|
|
2588
|
+
...input,
|
|
2589
|
+
requestId: randomUUID()
|
|
2590
|
+
});
|
|
2591
|
+
write(read, renderSessionRunStatus(read));
|
|
1992
2592
|
return;
|
|
1993
2593
|
}
|
|
1994
2594
|
if (first === "sessions" && second === "answer-env-request" || first === "answer-env-request") {
|
|
1995
2595
|
const offset = first === "answer-env-request" ? 1 : 2;
|
|
1996
|
-
const input =
|
|
1997
|
-
const read = await client.sessions.answerEnvRequest(requireValue(args[offset], "Missing session id"),
|
|
1998
|
-
|
|
2596
|
+
const input = parseAnswerEnvRequestCommandArgs(args.slice(offset + 1));
|
|
2597
|
+
const read = await client.sessions.answerEnvRequest(requireValue(args[offset], "Missing session id"), {
|
|
2598
|
+
...input,
|
|
2599
|
+
requestId: randomUUID()
|
|
2600
|
+
});
|
|
2601
|
+
write(read, renderSessionRunStatus(read));
|
|
1999
2602
|
return;
|
|
2000
2603
|
}
|
|
2001
2604
|
throw new Error(`Unknown command: ${args.join(" ")}`);
|
|
@@ -2020,6 +2623,18 @@ function resolveCommandExecution(options, rest) {
|
|
|
2020
2623
|
text: K8S_HELP_TEXT
|
|
2021
2624
|
};
|
|
2022
2625
|
}
|
|
2626
|
+
if (command === "session" && commandArgs.length === 0) {
|
|
2627
|
+
return {
|
|
2628
|
+
kind: "cli-help",
|
|
2629
|
+
text: SESSION_RUN_HELP_TEXT
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
if (command === "merge" && commandArgs.length === 0) {
|
|
2633
|
+
return {
|
|
2634
|
+
kind: "cli-help",
|
|
2635
|
+
text: MERGE_HELP_TEXT
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2023
2638
|
if (trailingHelp) {
|
|
2024
2639
|
const cliOnlyHelp = findCliOnlyCommandHelp(normalizedRest);
|
|
2025
2640
|
if (cliOnlyHelp) {
|
|
@@ -2379,24 +2994,23 @@ function resolveCommandExecution(options, rest) {
|
|
|
2379
2994
|
if (!sessionId2) {
|
|
2380
2995
|
throw new Error("Session id is required for `conversation inspect-node`");
|
|
2381
2996
|
}
|
|
2382
|
-
const
|
|
2383
|
-
if (!
|
|
2997
|
+
const nodeId2 = options.session ? commandArgs[1] : commandArgs[2];
|
|
2998
|
+
if (!nodeId2) {
|
|
2384
2999
|
throw new Error("Node id is required for `conversation inspect-node`");
|
|
2385
3000
|
}
|
|
2386
3001
|
const remainingArgs = options.session ? commandArgs.slice(2) : commandArgs.slice(3);
|
|
2387
3002
|
return {
|
|
2388
3003
|
kind: "plugin",
|
|
2389
|
-
pluginArgs: ["conversation", "inspect-node", sessionId2,
|
|
3004
|
+
pluginArgs: ["conversation", "inspect-node", sessionId2, nodeId2, ...remainingArgs]
|
|
2390
3005
|
};
|
|
2391
3006
|
}
|
|
2392
3007
|
const sessionId = options.session;
|
|
2393
3008
|
if (!sessionId) {
|
|
2394
3009
|
throw new Error("--session/-s is required for `conversation`");
|
|
2395
3010
|
}
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
};
|
|
3011
|
+
const renderOptions = parseConversationRenderArgs(commandArgs);
|
|
3012
|
+
if (renderOptions.follow) return { kind: "conversation-follow", sessionId, renderOptions };
|
|
3013
|
+
return { kind: "plugin", pluginArgs: ["conversation", sessionId, ...commandArgs] };
|
|
2400
3014
|
}
|
|
2401
3015
|
if (command === "shell") {
|
|
2402
3016
|
if (!options.project) {
|
|
@@ -2413,102 +3027,11 @@ function resolveCommandExecution(options, rest) {
|
|
|
2413
3027
|
...parseShellArgs(commandArgs)
|
|
2414
3028
|
};
|
|
2415
3029
|
}
|
|
2416
|
-
if (command === "
|
|
2417
|
-
|
|
2418
|
-
if (!sessionId) {
|
|
2419
|
-
throw new Error("--session/-s is required for `prompt`");
|
|
2420
|
-
}
|
|
2421
|
-
const parsed = parsePromptArgs(commandArgs);
|
|
2422
|
-
return {
|
|
2423
|
-
kind: "plugin",
|
|
2424
|
-
pluginArgs: ["prompt", sessionId, parsed.mode, parsed.model, parsed.message]
|
|
2425
|
-
};
|
|
2426
|
-
}
|
|
2427
|
-
if (command === "start-agent") {
|
|
2428
|
-
if (!options.project) {
|
|
2429
|
-
throw new Error("--project/-p is required for `start-agent`");
|
|
2430
|
-
}
|
|
2431
|
-
const sourceBranch = commandArgs[0] ?? options.branch;
|
|
2432
|
-
if (!sourceBranch) {
|
|
2433
|
-
throw new Error("Source branch is required for `start-agent`");
|
|
2434
|
-
}
|
|
2435
|
-
const agentType = requireValue(commandArgs[1], "Agent type is required for `start-agent`");
|
|
2436
|
-
if (!AGENT_TYPES.has(agentType)) {
|
|
2437
|
-
throw new Error("Invalid agent type. Expected one of: research, debug, test");
|
|
2438
|
-
}
|
|
2439
|
-
const prompt = commandArgs.slice(2).join(" ").trim();
|
|
2440
|
-
if (!prompt) {
|
|
2441
|
-
throw new Error("Agent prompt is required");
|
|
2442
|
-
}
|
|
2443
|
-
return {
|
|
2444
|
-
kind: "plugin",
|
|
2445
|
-
pluginArgs: ["start-agent", options.project, sourceBranch, agentType, prompt]
|
|
2446
|
-
};
|
|
3030
|
+
if (command === "session") {
|
|
3031
|
+
return { kind: "session-run", command: parseSessionRunCommand(options, commandArgs) };
|
|
2447
3032
|
}
|
|
2448
|
-
if (command === "
|
|
2449
|
-
|
|
2450
|
-
if (!sessionId) {
|
|
2451
|
-
throw new Error("Session id is required for `agent-status`");
|
|
2452
|
-
}
|
|
2453
|
-
return {
|
|
2454
|
-
kind: "plugin",
|
|
2455
|
-
pluginArgs: ["agent-status", sessionId]
|
|
2456
|
-
};
|
|
2457
|
-
}
|
|
2458
|
-
if (command === "send-prompt") {
|
|
2459
|
-
const sessionId = options.session ?? commandArgs[0];
|
|
2460
|
-
if (!sessionId) {
|
|
2461
|
-
throw new Error("Session id is required for `send-prompt`");
|
|
2462
|
-
}
|
|
2463
|
-
const promptArgs = options.session ? commandArgs : commandArgs.slice(1);
|
|
2464
|
-
const prompt = promptArgs.join(" ").trim();
|
|
2465
|
-
if (!prompt) {
|
|
2466
|
-
throw new Error("Prompt is required for `send-prompt`");
|
|
2467
|
-
}
|
|
2468
|
-
return {
|
|
2469
|
-
kind: "plugin",
|
|
2470
|
-
pluginArgs: ["send-prompt", sessionId, prompt]
|
|
2471
|
-
};
|
|
2472
|
-
}
|
|
2473
|
-
if (command === "merge-changes") {
|
|
2474
|
-
if (!options.project) {
|
|
2475
|
-
throw new Error("--project/-p is required for `merge-changes`");
|
|
2476
|
-
}
|
|
2477
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2478
|
-
if (!targetBranch) {
|
|
2479
|
-
throw new Error("Target branch is required for `merge-changes`");
|
|
2480
|
-
}
|
|
2481
|
-
const sourceBranch = requireValue(commandArgs[1], "Sub-agent branch is required for `merge-changes`");
|
|
2482
|
-
return {
|
|
2483
|
-
kind: "plugin",
|
|
2484
|
-
pluginArgs: ["merge-changes", options.project, targetBranch, sourceBranch]
|
|
2485
|
-
};
|
|
2486
|
-
}
|
|
2487
|
-
if (command === "continue-merge") {
|
|
2488
|
-
if (!options.project) {
|
|
2489
|
-
throw new Error("--project/-p is required for `continue-merge`");
|
|
2490
|
-
}
|
|
2491
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2492
|
-
if (!targetBranch) {
|
|
2493
|
-
throw new Error("Target branch is required for `continue-merge`");
|
|
2494
|
-
}
|
|
2495
|
-
return {
|
|
2496
|
-
kind: "plugin",
|
|
2497
|
-
pluginArgs: ["continue-merge", options.project, targetBranch]
|
|
2498
|
-
};
|
|
2499
|
-
}
|
|
2500
|
-
if (command === "abort-merge") {
|
|
2501
|
-
if (!options.project) {
|
|
2502
|
-
throw new Error("--project/-p is required for `abort-merge`");
|
|
2503
|
-
}
|
|
2504
|
-
const targetBranch = commandArgs[0] ?? options.branch;
|
|
2505
|
-
if (!targetBranch) {
|
|
2506
|
-
throw new Error("Target branch is required for `abort-merge`");
|
|
2507
|
-
}
|
|
2508
|
-
return {
|
|
2509
|
-
kind: "plugin",
|
|
2510
|
-
pluginArgs: ["abort-merge", options.project, targetBranch]
|
|
2511
|
-
};
|
|
3033
|
+
if (command === "merge") {
|
|
3034
|
+
return { kind: "merge", command: parseMergeCommand(options, commandArgs) };
|
|
2512
3035
|
}
|
|
2513
3036
|
if (command === "answer-questions") {
|
|
2514
3037
|
const sessionId = options.session;
|
|
@@ -2517,7 +3040,7 @@ function resolveCommandExecution(options, rest) {
|
|
|
2517
3040
|
}
|
|
2518
3041
|
return {
|
|
2519
3042
|
kind: "plugin",
|
|
2520
|
-
pluginArgs: ["answer-questions", sessionId, ...
|
|
3043
|
+
pluginArgs: ["answer-questions", sessionId, ...commandArgs]
|
|
2521
3044
|
};
|
|
2522
3045
|
}
|
|
2523
3046
|
if (command === "answer-env-request") {
|
|
@@ -2532,6 +3055,16 @@ function resolveCommandExecution(options, rest) {
|
|
|
2532
3055
|
}
|
|
2533
3056
|
throw new Error(`Unknown command: ${command}`);
|
|
2534
3057
|
}
|
|
3058
|
+
async function followWithInterrupt(client, sessionId, options) {
|
|
3059
|
+
const controller = new AbortController();
|
|
3060
|
+
const detach = () => controller.abort();
|
|
3061
|
+
process.once("SIGINT", detach);
|
|
3062
|
+
try {
|
|
3063
|
+
return await followR5dctlSession(client, sessionId, { ...options, signal: controller.signal });
|
|
3064
|
+
} finally {
|
|
3065
|
+
process.removeListener("SIGINT", detach);
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
2535
3068
|
async function runCommand(argv) {
|
|
2536
3069
|
const { options, rest } = parseGlobalArgs(argv);
|
|
2537
3070
|
if (options.version) {
|
|
@@ -2558,6 +3091,38 @@ async function runCommand(argv) {
|
|
|
2558
3091
|
process.stdout.write(execution.text);
|
|
2559
3092
|
return 0;
|
|
2560
3093
|
}
|
|
3094
|
+
if (execution.kind === "session-run") {
|
|
3095
|
+
const result = await dispatchSessionRunCommand(client, execution.command);
|
|
3096
|
+
if (execution.command.kind !== "start" || !execution.command.follow) {
|
|
3097
|
+
writeDataOutput(options.json, result.data, result.human);
|
|
3098
|
+
return 0;
|
|
3099
|
+
}
|
|
3100
|
+
const sessionId = requireValue(responseString(result.data, "sessionId"), "Session start did not return a session id");
|
|
3101
|
+
if (options.json) process.stdout.write(`${JSON.stringify({ type: "session_started", data: result.data })}
|
|
3102
|
+
`);
|
|
3103
|
+
else process.stderr.write(result.human);
|
|
3104
|
+
return await followWithInterrupt(client, sessionId, {
|
|
3105
|
+
json: options.json,
|
|
3106
|
+
includeTools: execution.command.includeTools,
|
|
3107
|
+
printInitialTranscript: false
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
if (execution.kind === "merge") {
|
|
3111
|
+
const result = await dispatchMergeCommand(client, execution.command);
|
|
3112
|
+
writeDataOutput(options.json, result.data, result.human);
|
|
3113
|
+
return 0;
|
|
3114
|
+
}
|
|
3115
|
+
if (execution.kind === "conversation-follow") {
|
|
3116
|
+
const { follow: _follow, ...renderOptions } = execution.renderOptions;
|
|
3117
|
+
const initialConversation = await client.sessions.conversation(execution.sessionId, renderOptions);
|
|
3118
|
+
return await followWithInterrupt(client, execution.sessionId, {
|
|
3119
|
+
json: options.json,
|
|
3120
|
+
includeTools: execution.renderOptions.includeTools,
|
|
3121
|
+
renderOptions,
|
|
3122
|
+
initialConversation,
|
|
3123
|
+
printInitialTranscript: true
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
2561
3126
|
if (execution.kind === "shell") {
|
|
2562
3127
|
const project = await client.projects.describe(options.project);
|
|
2563
3128
|
return await runR5dctlShell({
|
|
@@ -2606,6 +3171,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2606
3171
|
export {
|
|
2607
3172
|
advanceTransientDevicePollBackoff,
|
|
2608
3173
|
collectK8sUsage,
|
|
3174
|
+
dispatchMergeCommand,
|
|
3175
|
+
dispatchSessionRunCommand,
|
|
3176
|
+
followR5dctlSession,
|
|
2609
3177
|
formatK8sCpu,
|
|
2610
3178
|
formatK8sMemory,
|
|
2611
3179
|
formatProcessAge,
|
|
@@ -2616,7 +3184,9 @@ export {
|
|
|
2616
3184
|
getTransientDevicePollDelay,
|
|
2617
3185
|
handleAuthLogin,
|
|
2618
3186
|
main,
|
|
3187
|
+
parseAnswerEnvRequestCommandArgs,
|
|
2619
3188
|
parseAnswerFlags,
|
|
3189
|
+
parseAnswerQuestionsCommandArgs,
|
|
2620
3190
|
parseConversationRenderArgs,
|
|
2621
3191
|
parseConversationWorkDetailArgs,
|
|
2622
3192
|
parseEnvFlags,
|
|
@@ -2624,9 +3194,11 @@ export {
|
|
|
2624
3194
|
parseGetEnvsArgs,
|
|
2625
3195
|
parseGlobalArgs,
|
|
2626
3196
|
parseK8sUsageArgs,
|
|
3197
|
+
parseMergeCommand,
|
|
2627
3198
|
parsePromptArgs,
|
|
2628
3199
|
parsePsHistoryArgs,
|
|
2629
3200
|
parsePsListArgs,
|
|
3201
|
+
parseSessionRunCommand,
|
|
2630
3202
|
parseSetEnvArgs,
|
|
2631
3203
|
parseShellArgs,
|
|
2632
3204
|
readDotenvFile,
|
|
@@ -2640,8 +3212,11 @@ export {
|
|
|
2640
3212
|
renderProcessHistory,
|
|
2641
3213
|
renderProcessInspection,
|
|
2642
3214
|
renderProcessList,
|
|
3215
|
+
renderSessionRunStart,
|
|
3216
|
+
renderSessionRunStatus,
|
|
2643
3217
|
renderWorkspaceStatus,
|
|
2644
3218
|
renderWorkspaceSync,
|
|
3219
|
+
renderWorktreeMerge,
|
|
2645
3220
|
resolveCommandExecution,
|
|
2646
3221
|
runR5dctlCli,
|
|
2647
3222
|
summarizeEnvData,
|