@ricsam/r5dctl 0.0.117 → 0.0.118
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/dist/cjs/cli.cjs +229 -18
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/cli.mjs +224 -18
- package/dist/mjs/package.json +1 -1
- package/dist/types/cli.d.ts +26 -2
- package/package.json +2 -2
package/dist/cjs/cli.cjs
CHANGED
|
@@ -54,6 +54,7 @@ __export(cli_exports, {
|
|
|
54
54
|
parseAnswerQuestionsCommandArgs: () => parseAnswerQuestionsCommandArgs,
|
|
55
55
|
parseBranchCreateArgs: () => parseBranchCreateArgs,
|
|
56
56
|
parseBranchDeleteArgs: () => parseBranchDeleteArgs,
|
|
57
|
+
parseClaimDuration: () => parseClaimDuration,
|
|
57
58
|
parseConversationForkArgs: () => parseConversationForkArgs,
|
|
58
59
|
parseConversationInspectWorkArgs: () => parseConversationInspectWorkArgs,
|
|
59
60
|
parseConversationOverviewArgs: () => parseConversationOverviewArgs,
|
|
@@ -66,6 +67,8 @@ __export(cli_exports, {
|
|
|
66
67
|
parsePromptArgs: () => parsePromptArgs,
|
|
67
68
|
parsePsHistoryArgs: () => parsePsHistoryArgs,
|
|
68
69
|
parsePsListArgs: () => parsePsListArgs,
|
|
70
|
+
parseSessionClaimArgs: () => parseSessionClaimArgs,
|
|
71
|
+
parseSessionClaimsListArgs: () => parseSessionClaimsListArgs,
|
|
69
72
|
parseSessionProcessLogArgs: () => parseSessionProcessLogArgs,
|
|
70
73
|
parseSessionRunCommand: () => parseSessionRunCommand,
|
|
71
74
|
parseSetEnvArgs: () => parseSetEnvArgs,
|
|
@@ -89,6 +92,8 @@ __export(cli_exports, {
|
|
|
89
92
|
renderProcessInspection: () => renderProcessInspection,
|
|
90
93
|
renderProcessList: () => renderProcessList,
|
|
91
94
|
renderProcessLog: () => renderProcessLog,
|
|
95
|
+
renderSessionClaimList: () => renderSessionClaimList,
|
|
96
|
+
renderSessionClaimResponse: () => renderSessionClaimResponse,
|
|
92
97
|
renderSessionList: () => renderSessionList,
|
|
93
98
|
renderSessionRunStart: () => renderSessionRunStart,
|
|
94
99
|
renderSessionRunStatus: () => renderSessionRunStatus,
|
|
@@ -180,13 +185,22 @@ const SESSION_RUN_HELP_TEXT = [
|
|
|
180
185
|
' r5dctl session prompt --worker <label> --mode <mode> --model <tier> [--delivery queue|steer|force] [--sender <name>] <session-id> "<prompt>"',
|
|
181
186
|
" r5dctl session stop <session-id>",
|
|
182
187
|
" r5dctl session process-log <run-id> [--tail <n>] [--grep <pattern>]",
|
|
188
|
+
" r5dctl [-s <session-id>] session claim <topic> [--ttl <duration>] [--note <text>]",
|
|
189
|
+
" r5dctl [-s <session-id>] session claim <topic> --status | --renew [--ttl <duration>] | --release",
|
|
190
|
+
" r5dctl [-s <session-id>] session claims",
|
|
183
191
|
"",
|
|
184
192
|
"Create, inspect, resume, and stop ordinary agent sessions.",
|
|
185
193
|
"prompt reaches a busy session by --delivery: steer (default) lands after the agent's next tool result,",
|
|
186
194
|
"queue waits for the current turn to finish, force replaces the agent's current tool round. An idle session starts a new generation either way.",
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
"
|
|
195
|
+
"Send only messages that need action: grants, releases, blockers, corrections, pause/resume, questions, and completion reports;",
|
|
196
|
+
"batch progress and waiting reports into the update the recipient asked for. Every steer wakes a waiting recipient for a full turn.",
|
|
197
|
+
"Use force only when the current tool round must be interrupted; queue is only for a follow-up task deliberately deferred until the turn ends.",
|
|
198
|
+
"The recipient sees the sending session's verified id and name from the credential; --sender only adds a display title",
|
|
199
|
+
"(defaults to $R5D_SESSION_NAME in an agent shell). Prompt delivery is not acknowledgment: if a shared-checkout change depends",
|
|
200
|
+
"on a pause, wait for the recipient's explicit reply before proceeding.",
|
|
201
|
+
"claim takes atomic ownership of a topic within the session's project branch (one winner; a losing call prints the holder and expiry).",
|
|
202
|
+
"Claims expire at --ttl (default 30m, max 24h) unless renewed; --renew and --release are fenced to the holder and its claim generation.",
|
|
203
|
+
"Agent shells act as their own session; user credentials select the acting session with -s or R5D_SESSION_ID.",
|
|
190
204
|
"A new worktree starts with the source checkout's uncommitted changes (--working-tree carry, the default);",
|
|
191
205
|
"--working-tree clean checks out the source branch's current commit only. Start and status print the mode, never a commit hash.",
|
|
192
206
|
"Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
|
|
@@ -281,8 +295,8 @@ const SHARED_HELP_ENTRIES = [
|
|
|
281
295
|
},
|
|
282
296
|
{
|
|
283
297
|
section: "sessions",
|
|
284
|
-
usage: "conversation overview <session-id> [--skeleton]",
|
|
285
|
-
description: "Read dialogue and
|
|
298
|
+
usage: "conversation overview <session-id> [--skeleton] [--tail <turns>]",
|
|
299
|
+
description: "Read attributed dialogue and inspectable work ranges; --skeleton keeps bounded commentary and decisions. --tail N shows recent turns in order, capped at 48 KB with explicit omissions; redirect the full overview for earlier history."
|
|
286
300
|
},
|
|
287
301
|
{
|
|
288
302
|
section: "sessions",
|
|
@@ -317,13 +331,23 @@ const SHARED_HELP_ENTRIES = [
|
|
|
317
331
|
{
|
|
318
332
|
section: "sessions",
|
|
319
333
|
usage: 'session prompt --worker <label> --mode <mode> --model <tier> [--delivery queue|steer|force] [--sender <name>] <session-id> "<prompt>"',
|
|
320
|
-
description: "Prompt a session: steer into the current turn after the next tool result (default), queue behind the turn, or force in place of the current tool round; an idle session starts a new generation.
|
|
334
|
+
description: "Prompt a session: steer into the current turn after the next tool result (default), queue a deliberately deferred follow-up behind the turn, or force in place of the current tool round; an idle session starts a new generation. Send only messages that need action; the recipient sees the sender's verified session id, and --sender only adds a display title."
|
|
321
335
|
},
|
|
322
336
|
{
|
|
323
337
|
section: "sessions",
|
|
324
338
|
usage: "session stop <session-id>",
|
|
325
339
|
description: "Stop an active session generation."
|
|
326
340
|
},
|
|
341
|
+
{
|
|
342
|
+
section: "sessions",
|
|
343
|
+
usage: "[-s <session-id>] session claim <topic> [--ttl <duration>] [--note <text>] [--status|--renew|--release]",
|
|
344
|
+
description: "Atomically own a coordination topic within the session's project branch; one winner, and a losing call prints the current holder and expiry. --status reads it, --renew extends your lease, --release gives it up. Agent shells act as their own session."
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
section: "sessions",
|
|
348
|
+
usage: "[-s <session-id>] session claims",
|
|
349
|
+
description: "List the claims on the session's project branch with holder, generation, and expiry."
|
|
350
|
+
},
|
|
327
351
|
{
|
|
328
352
|
section: "sessions",
|
|
329
353
|
usage: "session process-log <run-id> [--tail <n>] [--grep <pattern>]",
|
|
@@ -767,7 +791,7 @@ function rejectSessionRunGlobalScopes(options, allowProject) {
|
|
|
767
791
|
if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
|
|
768
792
|
if (options.session) throw new Error("--session/-s is not supported for `session`; pass the session id explicitly");
|
|
769
793
|
}
|
|
770
|
-
function parseSessionRunCommand(options, args) {
|
|
794
|
+
function parseSessionRunCommand(options, args, env = process.env) {
|
|
771
795
|
const subcommand = requireValue(args[0], "Missing session command");
|
|
772
796
|
const commandArgs = args.slice(1);
|
|
773
797
|
if (subcommand === "start") {
|
|
@@ -827,8 +851,9 @@ function parseSessionRunCommand(options, args) {
|
|
|
827
851
|
const sessionId = requireValue(parsed.positionals[0], "Session id is required for `session prompt`");
|
|
828
852
|
const prompt = parsed.positionals.slice(1).join(" ").trim();
|
|
829
853
|
if (!prompt) throw new Error("Prompt is required for `session prompt`");
|
|
830
|
-
const
|
|
831
|
-
if (
|
|
854
|
+
const explicitSender = parsed.values.get("--sender")?.trim();
|
|
855
|
+
if (explicitSender !== void 0 && !explicitSender) throw new Error("--sender must not be empty");
|
|
856
|
+
const sender = explicitSender ?? env.R5D_SESSION_NAME?.trim();
|
|
832
857
|
return {
|
|
833
858
|
kind: "prompt",
|
|
834
859
|
worker: requireWorker(parsed.values),
|
|
@@ -852,6 +877,106 @@ function parseSessionProcessLogArgs(args) {
|
|
|
852
877
|
const grep = parsed.values.get("--grep");
|
|
853
878
|
return { runId, ...tail !== void 0 ? { tail } : {}, ...grep !== void 0 ? { grep } : {} };
|
|
854
879
|
}
|
|
880
|
+
const CLAIM_DURATION_PATTERN = /^(\d+)([smh])$/;
|
|
881
|
+
function parseClaimDuration(value) {
|
|
882
|
+
const match = CLAIM_DURATION_PATTERN.exec(value.trim());
|
|
883
|
+
if (!match) throw new Error("Invalid --ttl. Expected a duration such as 90s, 30m, or 2h");
|
|
884
|
+
const amount = Number(match[1]);
|
|
885
|
+
const seconds = match[2] === "s" ? amount : match[2] === "m" ? amount * 60 : amount * 3600;
|
|
886
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > 86400) throw new Error("Invalid --ttl. Use a duration between 1s and 24h");
|
|
887
|
+
return seconds;
|
|
888
|
+
}
|
|
889
|
+
function resolveClaimSessionId(explicit, options, env) {
|
|
890
|
+
return explicit?.trim() || options.session?.trim() || env.R5D_SESSION_ID?.trim() || void 0;
|
|
891
|
+
}
|
|
892
|
+
function parseSessionClaimArgs(args, options = {}, env = process.env) {
|
|
893
|
+
const parsed = parseCommandOperands(
|
|
894
|
+
args,
|
|
895
|
+
/* @__PURE__ */ new Set(["--ttl", "--note", "--generation", "--session"]),
|
|
896
|
+
/* @__PURE__ */ new Map([
|
|
897
|
+
["--status", "--status"],
|
|
898
|
+
["--renew", "--renew"],
|
|
899
|
+
["--release", "--release"]
|
|
900
|
+
]),
|
|
901
|
+
"session claim"
|
|
902
|
+
);
|
|
903
|
+
const topic = requireValue(parsed.positionals[0]?.trim(), "Topic is required for `session claim`");
|
|
904
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected session claim argument: ${parsed.positionals[1]}`);
|
|
905
|
+
if (parsed.booleans.size > 1) throw new Error("Use only one of --status, --renew, or --release");
|
|
906
|
+
const action = parsed.booleans.has("--status") ? "status" : parsed.booleans.has("--renew") ? "renew" : parsed.booleans.has("--release") ? "release" : "acquire";
|
|
907
|
+
const ttl = parsed.values.get("--ttl");
|
|
908
|
+
if (ttl !== void 0 && action !== "acquire" && action !== "renew") throw new Error("--ttl is only supported when acquiring or renewing a claim");
|
|
909
|
+
const note = parsed.values.get("--note");
|
|
910
|
+
if (note !== void 0 && action !== "acquire") throw new Error("--note is only supported when acquiring a claim");
|
|
911
|
+
const generationValue = parsed.values.get("--generation");
|
|
912
|
+
if (generationValue !== void 0 && action !== "renew" && action !== "release") {
|
|
913
|
+
throw new Error("--generation is only supported with --renew or --release");
|
|
914
|
+
}
|
|
915
|
+
const generation = generationValue === void 0 ? void 0 : Number(generationValue);
|
|
916
|
+
if (generation !== void 0 && (!Number.isSafeInteger(generation) || generation < 1)) throw new Error("--generation must be a positive integer");
|
|
917
|
+
const sessionId = resolveClaimSessionId(parsed.values.get("--session"), options, env);
|
|
918
|
+
return {
|
|
919
|
+
action,
|
|
920
|
+
topic,
|
|
921
|
+
...sessionId ? { sessionId } : {},
|
|
922
|
+
...ttl !== void 0 ? { ttlSeconds: parseClaimDuration(ttl) } : {},
|
|
923
|
+
...note !== void 0 ? { note } : {},
|
|
924
|
+
...generation !== void 0 ? { generation } : {}
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
function parseSessionClaimsListArgs(args, options = {}, env = process.env) {
|
|
928
|
+
const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--session"]), /* @__PURE__ */ new Map(), "session claims");
|
|
929
|
+
if (parsed.positionals.length > 0) throw new Error(`Unexpected session claims argument: ${parsed.positionals[0]}`);
|
|
930
|
+
const sessionId = resolveClaimSessionId(parsed.values.get("--session"), options, env);
|
|
931
|
+
return sessionId ? { sessionId } : {};
|
|
932
|
+
}
|
|
933
|
+
const CLAIM_OUTCOME_TEXT = {
|
|
934
|
+
acquired: "acquired",
|
|
935
|
+
renewed: "renewed",
|
|
936
|
+
held_by_other: "not acquired: held by another session",
|
|
937
|
+
not_holder: "not the holder",
|
|
938
|
+
stale_generation: "stale generation: the claim changed hands since it was observed",
|
|
939
|
+
expired: "expired: renew failed, claim it again",
|
|
940
|
+
missing: "no such claim",
|
|
941
|
+
released: "released"
|
|
942
|
+
};
|
|
943
|
+
function formatClaimExpiry(expiresAt, nowMs) {
|
|
944
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
945
|
+
if (!Number.isFinite(expiresAtMs)) return expiresAt;
|
|
946
|
+
return expiresAtMs <= nowMs ? `${expiresAt} (expired ${formatProcessAge(expiresAt, nowMs)} ago)` : `${expiresAt} (in ${formatProcessAge(new Date(nowMs).toISOString(), expiresAtMs)})`;
|
|
947
|
+
}
|
|
948
|
+
function describeClaimHolder(claim) {
|
|
949
|
+
const name = claim.holderSessionName ? ` ${JSON.stringify(claim.holderSessionName)}` : "";
|
|
950
|
+
return `${claim.holderSessionId}${name}${claim.heldByActor ? " (this session)" : ""}`;
|
|
951
|
+
}
|
|
952
|
+
function renderSessionClaimResponse(response, nowMs = Date.now()) {
|
|
953
|
+
const lines = [`Outcome: ${CLAIM_OUTCOME_TEXT[response.outcome] ?? response.outcome}`, `Acting session: ${response.actorSessionId}`];
|
|
954
|
+
if (response.claim) {
|
|
955
|
+
lines.push(
|
|
956
|
+
`Claim: ${response.claim.topic}`,
|
|
957
|
+
`Branch: ${response.claim.branchName}`,
|
|
958
|
+
`Holder: ${describeClaimHolder(response.claim)}`,
|
|
959
|
+
`Generation: ${response.claim.generation}`,
|
|
960
|
+
`Expires: ${formatClaimExpiry(response.claim.expiresAt, nowMs)}`
|
|
961
|
+
);
|
|
962
|
+
if (response.claim.note) lines.push(`Note: ${response.claim.note}`);
|
|
963
|
+
}
|
|
964
|
+
return `${lines.join("\n")}
|
|
965
|
+
`;
|
|
966
|
+
}
|
|
967
|
+
function renderSessionClaimList(response, nowMs = Date.now()) {
|
|
968
|
+
const header = `Claims on ${response.branchName} (acting session ${response.actorSessionId})`;
|
|
969
|
+
if (response.claims.length === 0) return `${header}: none
|
|
970
|
+
`;
|
|
971
|
+
const rows = response.claims.map((claim) => {
|
|
972
|
+
const expiry = claim.expired ? "expired" : "expires";
|
|
973
|
+
const note = claim.note ? ` note: ${claim.note}` : "";
|
|
974
|
+
return `- ${claim.topic} holder ${describeClaimHolder(claim)} generation ${claim.generation} ${expiry} ${formatClaimExpiry(claim.expiresAt, nowMs)}${note}`;
|
|
975
|
+
});
|
|
976
|
+
return `${header}:
|
|
977
|
+
${rows.join("\n")}
|
|
978
|
+
`;
|
|
979
|
+
}
|
|
855
980
|
function assertAuthLoginArgs(args) {
|
|
856
981
|
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
857
982
|
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
@@ -2694,16 +2819,26 @@ function parseConversationInspectWorkArgs(args) {
|
|
|
2694
2819
|
}
|
|
2695
2820
|
function parseConversationOverviewArgs(args) {
|
|
2696
2821
|
let skeleton = false;
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2822
|
+
let tail;
|
|
2823
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2824
|
+
const arg = args[index];
|
|
2825
|
+
if (arg === "--tail") {
|
|
2826
|
+
if (tail !== void 0) throw new Error("Use --tail only once");
|
|
2827
|
+
const value = requireValue(args[++index], "Missing value for --tail");
|
|
2828
|
+
if (!/^[1-9][0-9]*$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) > 200) {
|
|
2829
|
+
throw new Error("Invalid --tail value: use a whole number from 1 to 200");
|
|
2830
|
+
}
|
|
2831
|
+
tail = Number(value);
|
|
2832
|
+
continue;
|
|
2700
2833
|
}
|
|
2701
|
-
if (skeleton) {
|
|
2702
|
-
throw new Error("Use --skeleton only once");
|
|
2834
|
+
if (arg === "--skeleton") {
|
|
2835
|
+
if (skeleton) throw new Error("Use --skeleton only once");
|
|
2836
|
+
skeleton = true;
|
|
2837
|
+
continue;
|
|
2703
2838
|
}
|
|
2704
|
-
|
|
2839
|
+
throw new Error(`Unknown conversation overview argument: ${arg}`);
|
|
2705
2840
|
}
|
|
2706
|
-
return { skeleton };
|
|
2841
|
+
return { skeleton, ...tail !== void 0 ? { tail } : {} };
|
|
2707
2842
|
}
|
|
2708
2843
|
function parseConversationForkArgs(args) {
|
|
2709
2844
|
let name;
|
|
@@ -2858,6 +2993,55 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
2858
2993
|
`);
|
|
2859
2994
|
return;
|
|
2860
2995
|
}
|
|
2996
|
+
if (first === "session" && second === "claim") {
|
|
2997
|
+
const claim = parseSessionClaimArgs(args.slice(2), {}, {});
|
|
2998
|
+
const scope = claim.sessionId ? { sessionId: claim.sessionId } : {};
|
|
2999
|
+
const nowMs = Date.now();
|
|
3000
|
+
if (claim.action === "acquire") {
|
|
3001
|
+
const result2 = await client.claims.acquire({
|
|
3002
|
+
topic: claim.topic,
|
|
3003
|
+
...scope,
|
|
3004
|
+
...claim.ttlSeconds !== void 0 ? { ttlSeconds: claim.ttlSeconds } : {},
|
|
3005
|
+
...claim.note !== void 0 ? { note: claim.note } : {}
|
|
3006
|
+
});
|
|
3007
|
+
write(result2, renderSessionClaimResponse(result2, nowMs));
|
|
3008
|
+
if (result2.outcome === "held_by_other") {
|
|
3009
|
+
throw new Error(`Claim ${claim.topic} is held by session ${result2.claim.holderSessionId} until ${result2.claim.expiresAt}; not acquired`);
|
|
3010
|
+
}
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
3013
|
+
if (claim.action === "status") {
|
|
3014
|
+
const list = await client.claims.list({ ...scope, topic: claim.topic });
|
|
3015
|
+
write(list, renderSessionClaimList(list, nowMs));
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
let generation = claim.generation;
|
|
3019
|
+
if (generation === void 0) {
|
|
3020
|
+
const list = await client.claims.list({ ...scope, topic: claim.topic });
|
|
3021
|
+
const current = list.claims[0];
|
|
3022
|
+
if (!current) {
|
|
3023
|
+
write({ outcome: "missing", actorSessionId: list.actorSessionId, claim: null }, `No claim on ${claim.topic}.
|
|
3024
|
+
`);
|
|
3025
|
+
if (claim.action === "renew") throw new Error(`No claim on ${claim.topic} to renew`);
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
if (!current.heldByActor) {
|
|
3029
|
+
write({ outcome: "not_holder", actorSessionId: list.actorSessionId, claim: current }, renderSessionClaimResponse({ outcome: "not_holder", actorSessionId: list.actorSessionId, claim: current }, nowMs));
|
|
3030
|
+
throw new Error(`Claim ${claim.topic} is held by session ${current.holderSessionId}, not by ${list.actorSessionId}`);
|
|
3031
|
+
}
|
|
3032
|
+
generation = current.generation;
|
|
3033
|
+
}
|
|
3034
|
+
const result = claim.action === "renew" ? await client.claims.renew({ topic: claim.topic, ...scope, generation, ...claim.ttlSeconds !== void 0 ? { ttlSeconds: claim.ttlSeconds } : {} }) : await client.claims.release({ topic: claim.topic, ...scope, generation });
|
|
3035
|
+
write(result, renderSessionClaimResponse(result, nowMs));
|
|
3036
|
+
const succeeded = result.outcome === "renewed" || result.outcome === "released" || claim.action === "release" && result.outcome === "missing";
|
|
3037
|
+
if (!succeeded) throw new Error(`Claim ${claim.topic}: ${CLAIM_OUTCOME_TEXT[result.outcome] ?? result.outcome}`);
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
if (first === "session" && second === "claims") {
|
|
3041
|
+
const list = await client.claims.list(parseSessionClaimsListArgs(args.slice(2), {}, {}));
|
|
3042
|
+
write(list, renderSessionClaimList(list));
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
2861
3045
|
if (first === "session" && second === "process-log") {
|
|
2862
3046
|
const selection = parseSessionProcessLogArgs(args.slice(2));
|
|
2863
3047
|
const log = await client.processes.log(selection.runId, {
|
|
@@ -3015,8 +3199,11 @@ Sessions: ${result.sessions.length}
|
|
|
3015
3199
|
}
|
|
3016
3200
|
if (first === "conversation" && second === "overview") {
|
|
3017
3201
|
const sessionId = requireValue(args[2], "Missing session id");
|
|
3018
|
-
const { skeleton } = parseConversationOverviewArgs(args.slice(3));
|
|
3019
|
-
const read = await client.sessions.conversationOverview(sessionId,
|
|
3202
|
+
const { skeleton, tail } = parseConversationOverviewArgs(args.slice(3));
|
|
3203
|
+
const read = await client.sessions.conversationOverview(sessionId, {
|
|
3204
|
+
...skeleton ? { view: "skeleton" } : {},
|
|
3205
|
+
...tail !== void 0 ? { tail } : {}
|
|
3206
|
+
});
|
|
3020
3207
|
write(read, renderConversationOverviewResponse(read));
|
|
3021
3208
|
return;
|
|
3022
3209
|
}
|
|
@@ -3557,6 +3744,25 @@ function resolveCommandExecution(options, rest) {
|
|
|
3557
3744
|
};
|
|
3558
3745
|
}
|
|
3559
3746
|
if (command === "session") {
|
|
3747
|
+
if (commandArgs[0] === "claim" || commandArgs[0] === "claims") {
|
|
3748
|
+
if (options.project) throw new Error("--project/-p is not supported for `session claim`; the acting session selects the project branch");
|
|
3749
|
+
if (options.branch) throw new Error("--branch/-b is not supported for `session claim`; the acting session selects the branch");
|
|
3750
|
+
const parsed = commandArgs[0] === "claim" ? parseSessionClaimArgs(commandArgs.slice(1), options) : parseSessionClaimsListArgs(commandArgs.slice(1), options);
|
|
3751
|
+
const withoutSessionValue = [];
|
|
3752
|
+
for (let index = 0; index < commandArgs.slice(1).length; index += 1) {
|
|
3753
|
+
const arg = commandArgs.slice(1)[index];
|
|
3754
|
+
if (arg === "--session") {
|
|
3755
|
+
index += 1;
|
|
3756
|
+
continue;
|
|
3757
|
+
}
|
|
3758
|
+
if (arg.startsWith("--session=")) continue;
|
|
3759
|
+
withoutSessionValue.push(arg);
|
|
3760
|
+
}
|
|
3761
|
+
return {
|
|
3762
|
+
kind: "plugin",
|
|
3763
|
+
pluginArgs: ["session", commandArgs[0], ...withoutSessionValue, ...parsed.sessionId ? [`--session=${parsed.sessionId}`] : []]
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3560
3766
|
if (commandArgs[0] === "process-log") {
|
|
3561
3767
|
rejectSessionRunGlobalScopes(options, false);
|
|
3562
3768
|
const selection = parseSessionProcessLogArgs(commandArgs.slice(1));
|
|
@@ -3748,6 +3954,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3748
3954
|
parseAnswerQuestionsCommandArgs,
|
|
3749
3955
|
parseBranchCreateArgs,
|
|
3750
3956
|
parseBranchDeleteArgs,
|
|
3957
|
+
parseClaimDuration,
|
|
3751
3958
|
parseConversationForkArgs,
|
|
3752
3959
|
parseConversationInspectWorkArgs,
|
|
3753
3960
|
parseConversationOverviewArgs,
|
|
@@ -3760,6 +3967,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3760
3967
|
parsePromptArgs,
|
|
3761
3968
|
parsePsHistoryArgs,
|
|
3762
3969
|
parsePsListArgs,
|
|
3970
|
+
parseSessionClaimArgs,
|
|
3971
|
+
parseSessionClaimsListArgs,
|
|
3763
3972
|
parseSessionProcessLogArgs,
|
|
3764
3973
|
parseSessionRunCommand,
|
|
3765
3974
|
parseSetEnvArgs,
|
|
@@ -3783,6 +3992,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
3783
3992
|
renderProcessInspection,
|
|
3784
3993
|
renderProcessList,
|
|
3785
3994
|
renderProcessLog,
|
|
3995
|
+
renderSessionClaimList,
|
|
3996
|
+
renderSessionClaimResponse,
|
|
3786
3997
|
renderSessionList,
|
|
3787
3998
|
renderSessionRunStart,
|
|
3788
3999
|
renderSessionRunStatus,
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/cli.mjs
CHANGED
|
@@ -80,13 +80,22 @@ const SESSION_RUN_HELP_TEXT = [
|
|
|
80
80
|
' r5dctl session prompt --worker <label> --mode <mode> --model <tier> [--delivery queue|steer|force] [--sender <name>] <session-id> "<prompt>"',
|
|
81
81
|
" r5dctl session stop <session-id>",
|
|
82
82
|
" r5dctl session process-log <run-id> [--tail <n>] [--grep <pattern>]",
|
|
83
|
+
" r5dctl [-s <session-id>] session claim <topic> [--ttl <duration>] [--note <text>]",
|
|
84
|
+
" r5dctl [-s <session-id>] session claim <topic> --status | --renew [--ttl <duration>] | --release",
|
|
85
|
+
" r5dctl [-s <session-id>] session claims",
|
|
83
86
|
"",
|
|
84
87
|
"Create, inspect, resume, and stop ordinary agent sessions.",
|
|
85
88
|
"prompt reaches a busy session by --delivery: steer (default) lands after the agent's next tool result,",
|
|
86
89
|
"queue waits for the current turn to finish, force replaces the agent's current tool round. An idle session starts a new generation either way.",
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
+
"Send only messages that need action: grants, releases, blockers, corrections, pause/resume, questions, and completion reports;",
|
|
91
|
+
"batch progress and waiting reports into the update the recipient asked for. Every steer wakes a waiting recipient for a full turn.",
|
|
92
|
+
"Use force only when the current tool round must be interrupted; queue is only for a follow-up task deliberately deferred until the turn ends.",
|
|
93
|
+
"The recipient sees the sending session's verified id and name from the credential; --sender only adds a display title",
|
|
94
|
+
"(defaults to $R5D_SESSION_NAME in an agent shell). Prompt delivery is not acknowledgment: if a shared-checkout change depends",
|
|
95
|
+
"on a pause, wait for the recipient's explicit reply before proceeding.",
|
|
96
|
+
"claim takes atomic ownership of a topic within the session's project branch (one winner; a losing call prints the holder and expiry).",
|
|
97
|
+
"Claims expire at --ttl (default 30m, max 24h) unless renewed; --renew and --release are fenced to the holder and its claim generation.",
|
|
98
|
+
"Agent shells act as their own session; user credentials select the acting session with -s or R5D_SESSION_ID.",
|
|
90
99
|
"A new worktree starts with the source checkout's uncommitted changes (--working-tree carry, the default);",
|
|
91
100
|
"--working-tree clean checks out the source branch's current commit only. Start and status print the mode, never a commit hash.",
|
|
92
101
|
"Use -f/--follow/--watch to attach after a start; Ctrl-C detaches without stopping the session.",
|
|
@@ -181,8 +190,8 @@ const SHARED_HELP_ENTRIES = [
|
|
|
181
190
|
},
|
|
182
191
|
{
|
|
183
192
|
section: "sessions",
|
|
184
|
-
usage: "conversation overview <session-id> [--skeleton]",
|
|
185
|
-
description: "Read dialogue and
|
|
193
|
+
usage: "conversation overview <session-id> [--skeleton] [--tail <turns>]",
|
|
194
|
+
description: "Read attributed dialogue and inspectable work ranges; --skeleton keeps bounded commentary and decisions. --tail N shows recent turns in order, capped at 48 KB with explicit omissions; redirect the full overview for earlier history."
|
|
186
195
|
},
|
|
187
196
|
{
|
|
188
197
|
section: "sessions",
|
|
@@ -217,13 +226,23 @@ const SHARED_HELP_ENTRIES = [
|
|
|
217
226
|
{
|
|
218
227
|
section: "sessions",
|
|
219
228
|
usage: 'session prompt --worker <label> --mode <mode> --model <tier> [--delivery queue|steer|force] [--sender <name>] <session-id> "<prompt>"',
|
|
220
|
-
description: "Prompt a session: steer into the current turn after the next tool result (default), queue behind the turn, or force in place of the current tool round; an idle session starts a new generation.
|
|
229
|
+
description: "Prompt a session: steer into the current turn after the next tool result (default), queue a deliberately deferred follow-up behind the turn, or force in place of the current tool round; an idle session starts a new generation. Send only messages that need action; the recipient sees the sender's verified session id, and --sender only adds a display title."
|
|
221
230
|
},
|
|
222
231
|
{
|
|
223
232
|
section: "sessions",
|
|
224
233
|
usage: "session stop <session-id>",
|
|
225
234
|
description: "Stop an active session generation."
|
|
226
235
|
},
|
|
236
|
+
{
|
|
237
|
+
section: "sessions",
|
|
238
|
+
usage: "[-s <session-id>] session claim <topic> [--ttl <duration>] [--note <text>] [--status|--renew|--release]",
|
|
239
|
+
description: "Atomically own a coordination topic within the session's project branch; one winner, and a losing call prints the current holder and expiry. --status reads it, --renew extends your lease, --release gives it up. Agent shells act as their own session."
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
section: "sessions",
|
|
243
|
+
usage: "[-s <session-id>] session claims",
|
|
244
|
+
description: "List the claims on the session's project branch with holder, generation, and expiry."
|
|
245
|
+
},
|
|
227
246
|
{
|
|
228
247
|
section: "sessions",
|
|
229
248
|
usage: "session process-log <run-id> [--tail <n>] [--grep <pattern>]",
|
|
@@ -667,7 +686,7 @@ function rejectSessionRunGlobalScopes(options, allowProject) {
|
|
|
667
686
|
if (options.branch) throw new Error("--branch/-b is not supported for `session`; select the worktree explicitly");
|
|
668
687
|
if (options.session) throw new Error("--session/-s is not supported for `session`; pass the session id explicitly");
|
|
669
688
|
}
|
|
670
|
-
function parseSessionRunCommand(options, args) {
|
|
689
|
+
function parseSessionRunCommand(options, args, env = process.env) {
|
|
671
690
|
const subcommand = requireValue(args[0], "Missing session command");
|
|
672
691
|
const commandArgs = args.slice(1);
|
|
673
692
|
if (subcommand === "start") {
|
|
@@ -727,8 +746,9 @@ function parseSessionRunCommand(options, args) {
|
|
|
727
746
|
const sessionId = requireValue(parsed.positionals[0], "Session id is required for `session prompt`");
|
|
728
747
|
const prompt = parsed.positionals.slice(1).join(" ").trim();
|
|
729
748
|
if (!prompt) throw new Error("Prompt is required for `session prompt`");
|
|
730
|
-
const
|
|
731
|
-
if (
|
|
749
|
+
const explicitSender = parsed.values.get("--sender")?.trim();
|
|
750
|
+
if (explicitSender !== void 0 && !explicitSender) throw new Error("--sender must not be empty");
|
|
751
|
+
const sender = explicitSender ?? env.R5D_SESSION_NAME?.trim();
|
|
732
752
|
return {
|
|
733
753
|
kind: "prompt",
|
|
734
754
|
worker: requireWorker(parsed.values),
|
|
@@ -752,6 +772,106 @@ function parseSessionProcessLogArgs(args) {
|
|
|
752
772
|
const grep = parsed.values.get("--grep");
|
|
753
773
|
return { runId, ...tail !== void 0 ? { tail } : {}, ...grep !== void 0 ? { grep } : {} };
|
|
754
774
|
}
|
|
775
|
+
const CLAIM_DURATION_PATTERN = /^(\d+)([smh])$/;
|
|
776
|
+
function parseClaimDuration(value) {
|
|
777
|
+
const match = CLAIM_DURATION_PATTERN.exec(value.trim());
|
|
778
|
+
if (!match) throw new Error("Invalid --ttl. Expected a duration such as 90s, 30m, or 2h");
|
|
779
|
+
const amount = Number(match[1]);
|
|
780
|
+
const seconds = match[2] === "s" ? amount : match[2] === "m" ? amount * 60 : amount * 3600;
|
|
781
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > 86400) throw new Error("Invalid --ttl. Use a duration between 1s and 24h");
|
|
782
|
+
return seconds;
|
|
783
|
+
}
|
|
784
|
+
function resolveClaimSessionId(explicit, options, env) {
|
|
785
|
+
return explicit?.trim() || options.session?.trim() || env.R5D_SESSION_ID?.trim() || void 0;
|
|
786
|
+
}
|
|
787
|
+
function parseSessionClaimArgs(args, options = {}, env = process.env) {
|
|
788
|
+
const parsed = parseCommandOperands(
|
|
789
|
+
args,
|
|
790
|
+
/* @__PURE__ */ new Set(["--ttl", "--note", "--generation", "--session"]),
|
|
791
|
+
/* @__PURE__ */ new Map([
|
|
792
|
+
["--status", "--status"],
|
|
793
|
+
["--renew", "--renew"],
|
|
794
|
+
["--release", "--release"]
|
|
795
|
+
]),
|
|
796
|
+
"session claim"
|
|
797
|
+
);
|
|
798
|
+
const topic = requireValue(parsed.positionals[0]?.trim(), "Topic is required for `session claim`");
|
|
799
|
+
if (parsed.positionals.length > 1) throw new Error(`Unexpected session claim argument: ${parsed.positionals[1]}`);
|
|
800
|
+
if (parsed.booleans.size > 1) throw new Error("Use only one of --status, --renew, or --release");
|
|
801
|
+
const action = parsed.booleans.has("--status") ? "status" : parsed.booleans.has("--renew") ? "renew" : parsed.booleans.has("--release") ? "release" : "acquire";
|
|
802
|
+
const ttl = parsed.values.get("--ttl");
|
|
803
|
+
if (ttl !== void 0 && action !== "acquire" && action !== "renew") throw new Error("--ttl is only supported when acquiring or renewing a claim");
|
|
804
|
+
const note = parsed.values.get("--note");
|
|
805
|
+
if (note !== void 0 && action !== "acquire") throw new Error("--note is only supported when acquiring a claim");
|
|
806
|
+
const generationValue = parsed.values.get("--generation");
|
|
807
|
+
if (generationValue !== void 0 && action !== "renew" && action !== "release") {
|
|
808
|
+
throw new Error("--generation is only supported with --renew or --release");
|
|
809
|
+
}
|
|
810
|
+
const generation = generationValue === void 0 ? void 0 : Number(generationValue);
|
|
811
|
+
if (generation !== void 0 && (!Number.isSafeInteger(generation) || generation < 1)) throw new Error("--generation must be a positive integer");
|
|
812
|
+
const sessionId = resolveClaimSessionId(parsed.values.get("--session"), options, env);
|
|
813
|
+
return {
|
|
814
|
+
action,
|
|
815
|
+
topic,
|
|
816
|
+
...sessionId ? { sessionId } : {},
|
|
817
|
+
...ttl !== void 0 ? { ttlSeconds: parseClaimDuration(ttl) } : {},
|
|
818
|
+
...note !== void 0 ? { note } : {},
|
|
819
|
+
...generation !== void 0 ? { generation } : {}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function parseSessionClaimsListArgs(args, options = {}, env = process.env) {
|
|
823
|
+
const parsed = parseCommandOperands(args, /* @__PURE__ */ new Set(["--session"]), /* @__PURE__ */ new Map(), "session claims");
|
|
824
|
+
if (parsed.positionals.length > 0) throw new Error(`Unexpected session claims argument: ${parsed.positionals[0]}`);
|
|
825
|
+
const sessionId = resolveClaimSessionId(parsed.values.get("--session"), options, env);
|
|
826
|
+
return sessionId ? { sessionId } : {};
|
|
827
|
+
}
|
|
828
|
+
const CLAIM_OUTCOME_TEXT = {
|
|
829
|
+
acquired: "acquired",
|
|
830
|
+
renewed: "renewed",
|
|
831
|
+
held_by_other: "not acquired: held by another session",
|
|
832
|
+
not_holder: "not the holder",
|
|
833
|
+
stale_generation: "stale generation: the claim changed hands since it was observed",
|
|
834
|
+
expired: "expired: renew failed, claim it again",
|
|
835
|
+
missing: "no such claim",
|
|
836
|
+
released: "released"
|
|
837
|
+
};
|
|
838
|
+
function formatClaimExpiry(expiresAt, nowMs) {
|
|
839
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
840
|
+
if (!Number.isFinite(expiresAtMs)) return expiresAt;
|
|
841
|
+
return expiresAtMs <= nowMs ? `${expiresAt} (expired ${formatProcessAge(expiresAt, nowMs)} ago)` : `${expiresAt} (in ${formatProcessAge(new Date(nowMs).toISOString(), expiresAtMs)})`;
|
|
842
|
+
}
|
|
843
|
+
function describeClaimHolder(claim) {
|
|
844
|
+
const name = claim.holderSessionName ? ` ${JSON.stringify(claim.holderSessionName)}` : "";
|
|
845
|
+
return `${claim.holderSessionId}${name}${claim.heldByActor ? " (this session)" : ""}`;
|
|
846
|
+
}
|
|
847
|
+
function renderSessionClaimResponse(response, nowMs = Date.now()) {
|
|
848
|
+
const lines = [`Outcome: ${CLAIM_OUTCOME_TEXT[response.outcome] ?? response.outcome}`, `Acting session: ${response.actorSessionId}`];
|
|
849
|
+
if (response.claim) {
|
|
850
|
+
lines.push(
|
|
851
|
+
`Claim: ${response.claim.topic}`,
|
|
852
|
+
`Branch: ${response.claim.branchName}`,
|
|
853
|
+
`Holder: ${describeClaimHolder(response.claim)}`,
|
|
854
|
+
`Generation: ${response.claim.generation}`,
|
|
855
|
+
`Expires: ${formatClaimExpiry(response.claim.expiresAt, nowMs)}`
|
|
856
|
+
);
|
|
857
|
+
if (response.claim.note) lines.push(`Note: ${response.claim.note}`);
|
|
858
|
+
}
|
|
859
|
+
return `${lines.join("\n")}
|
|
860
|
+
`;
|
|
861
|
+
}
|
|
862
|
+
function renderSessionClaimList(response, nowMs = Date.now()) {
|
|
863
|
+
const header = `Claims on ${response.branchName} (acting session ${response.actorSessionId})`;
|
|
864
|
+
if (response.claims.length === 0) return `${header}: none
|
|
865
|
+
`;
|
|
866
|
+
const rows = response.claims.map((claim) => {
|
|
867
|
+
const expiry = claim.expired ? "expired" : "expires";
|
|
868
|
+
const note = claim.note ? ` note: ${claim.note}` : "";
|
|
869
|
+
return `- ${claim.topic} holder ${describeClaimHolder(claim)} generation ${claim.generation} ${expiry} ${formatClaimExpiry(claim.expiresAt, nowMs)}${note}`;
|
|
870
|
+
});
|
|
871
|
+
return `${header}:
|
|
872
|
+
${rows.join("\n")}
|
|
873
|
+
`;
|
|
874
|
+
}
|
|
755
875
|
function assertAuthLoginArgs(args) {
|
|
756
876
|
const valueFlags = /* @__PURE__ */ new Set(["--device-name", "--worker-label"]);
|
|
757
877
|
const booleanFlags = /* @__PURE__ */ new Set(["--no-open", "--no-qr"]);
|
|
@@ -2594,16 +2714,26 @@ function parseConversationInspectWorkArgs(args) {
|
|
|
2594
2714
|
}
|
|
2595
2715
|
function parseConversationOverviewArgs(args) {
|
|
2596
2716
|
let skeleton = false;
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2717
|
+
let tail;
|
|
2718
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2719
|
+
const arg = args[index];
|
|
2720
|
+
if (arg === "--tail") {
|
|
2721
|
+
if (tail !== void 0) throw new Error("Use --tail only once");
|
|
2722
|
+
const value = requireValue(args[++index], "Missing value for --tail");
|
|
2723
|
+
if (!/^[1-9][0-9]*$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) > 200) {
|
|
2724
|
+
throw new Error("Invalid --tail value: use a whole number from 1 to 200");
|
|
2725
|
+
}
|
|
2726
|
+
tail = Number(value);
|
|
2727
|
+
continue;
|
|
2600
2728
|
}
|
|
2601
|
-
if (skeleton) {
|
|
2602
|
-
throw new Error("Use --skeleton only once");
|
|
2729
|
+
if (arg === "--skeleton") {
|
|
2730
|
+
if (skeleton) throw new Error("Use --skeleton only once");
|
|
2731
|
+
skeleton = true;
|
|
2732
|
+
continue;
|
|
2603
2733
|
}
|
|
2604
|
-
|
|
2734
|
+
throw new Error(`Unknown conversation overview argument: ${arg}`);
|
|
2605
2735
|
}
|
|
2606
|
-
return { skeleton };
|
|
2736
|
+
return { skeleton, ...tail !== void 0 ? { tail } : {} };
|
|
2607
2737
|
}
|
|
2608
2738
|
function parseConversationForkArgs(args) {
|
|
2609
2739
|
let name;
|
|
@@ -2758,6 +2888,55 @@ async function executeR5dctlCommand(client, json, args) {
|
|
|
2758
2888
|
`);
|
|
2759
2889
|
return;
|
|
2760
2890
|
}
|
|
2891
|
+
if (first === "session" && second === "claim") {
|
|
2892
|
+
const claim = parseSessionClaimArgs(args.slice(2), {}, {});
|
|
2893
|
+
const scope = claim.sessionId ? { sessionId: claim.sessionId } : {};
|
|
2894
|
+
const nowMs = Date.now();
|
|
2895
|
+
if (claim.action === "acquire") {
|
|
2896
|
+
const result2 = await client.claims.acquire({
|
|
2897
|
+
topic: claim.topic,
|
|
2898
|
+
...scope,
|
|
2899
|
+
...claim.ttlSeconds !== void 0 ? { ttlSeconds: claim.ttlSeconds } : {},
|
|
2900
|
+
...claim.note !== void 0 ? { note: claim.note } : {}
|
|
2901
|
+
});
|
|
2902
|
+
write(result2, renderSessionClaimResponse(result2, nowMs));
|
|
2903
|
+
if (result2.outcome === "held_by_other") {
|
|
2904
|
+
throw new Error(`Claim ${claim.topic} is held by session ${result2.claim.holderSessionId} until ${result2.claim.expiresAt}; not acquired`);
|
|
2905
|
+
}
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
if (claim.action === "status") {
|
|
2909
|
+
const list = await client.claims.list({ ...scope, topic: claim.topic });
|
|
2910
|
+
write(list, renderSessionClaimList(list, nowMs));
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
let generation = claim.generation;
|
|
2914
|
+
if (generation === void 0) {
|
|
2915
|
+
const list = await client.claims.list({ ...scope, topic: claim.topic });
|
|
2916
|
+
const current = list.claims[0];
|
|
2917
|
+
if (!current) {
|
|
2918
|
+
write({ outcome: "missing", actorSessionId: list.actorSessionId, claim: null }, `No claim on ${claim.topic}.
|
|
2919
|
+
`);
|
|
2920
|
+
if (claim.action === "renew") throw new Error(`No claim on ${claim.topic} to renew`);
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
if (!current.heldByActor) {
|
|
2924
|
+
write({ outcome: "not_holder", actorSessionId: list.actorSessionId, claim: current }, renderSessionClaimResponse({ outcome: "not_holder", actorSessionId: list.actorSessionId, claim: current }, nowMs));
|
|
2925
|
+
throw new Error(`Claim ${claim.topic} is held by session ${current.holderSessionId}, not by ${list.actorSessionId}`);
|
|
2926
|
+
}
|
|
2927
|
+
generation = current.generation;
|
|
2928
|
+
}
|
|
2929
|
+
const result = claim.action === "renew" ? await client.claims.renew({ topic: claim.topic, ...scope, generation, ...claim.ttlSeconds !== void 0 ? { ttlSeconds: claim.ttlSeconds } : {} }) : await client.claims.release({ topic: claim.topic, ...scope, generation });
|
|
2930
|
+
write(result, renderSessionClaimResponse(result, nowMs));
|
|
2931
|
+
const succeeded = result.outcome === "renewed" || result.outcome === "released" || claim.action === "release" && result.outcome === "missing";
|
|
2932
|
+
if (!succeeded) throw new Error(`Claim ${claim.topic}: ${CLAIM_OUTCOME_TEXT[result.outcome] ?? result.outcome}`);
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
if (first === "session" && second === "claims") {
|
|
2936
|
+
const list = await client.claims.list(parseSessionClaimsListArgs(args.slice(2), {}, {}));
|
|
2937
|
+
write(list, renderSessionClaimList(list));
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2761
2940
|
if (first === "session" && second === "process-log") {
|
|
2762
2941
|
const selection = parseSessionProcessLogArgs(args.slice(2));
|
|
2763
2942
|
const log = await client.processes.log(selection.runId, {
|
|
@@ -2915,8 +3094,11 @@ Sessions: ${result.sessions.length}
|
|
|
2915
3094
|
}
|
|
2916
3095
|
if (first === "conversation" && second === "overview") {
|
|
2917
3096
|
const sessionId = requireValue(args[2], "Missing session id");
|
|
2918
|
-
const { skeleton } = parseConversationOverviewArgs(args.slice(3));
|
|
2919
|
-
const read = await client.sessions.conversationOverview(sessionId,
|
|
3097
|
+
const { skeleton, tail } = parseConversationOverviewArgs(args.slice(3));
|
|
3098
|
+
const read = await client.sessions.conversationOverview(sessionId, {
|
|
3099
|
+
...skeleton ? { view: "skeleton" } : {},
|
|
3100
|
+
...tail !== void 0 ? { tail } : {}
|
|
3101
|
+
});
|
|
2920
3102
|
write(read, renderConversationOverviewResponse(read));
|
|
2921
3103
|
return;
|
|
2922
3104
|
}
|
|
@@ -3457,6 +3639,25 @@ function resolveCommandExecution(options, rest) {
|
|
|
3457
3639
|
};
|
|
3458
3640
|
}
|
|
3459
3641
|
if (command === "session") {
|
|
3642
|
+
if (commandArgs[0] === "claim" || commandArgs[0] === "claims") {
|
|
3643
|
+
if (options.project) throw new Error("--project/-p is not supported for `session claim`; the acting session selects the project branch");
|
|
3644
|
+
if (options.branch) throw new Error("--branch/-b is not supported for `session claim`; the acting session selects the branch");
|
|
3645
|
+
const parsed = commandArgs[0] === "claim" ? parseSessionClaimArgs(commandArgs.slice(1), options) : parseSessionClaimsListArgs(commandArgs.slice(1), options);
|
|
3646
|
+
const withoutSessionValue = [];
|
|
3647
|
+
for (let index = 0; index < commandArgs.slice(1).length; index += 1) {
|
|
3648
|
+
const arg = commandArgs.slice(1)[index];
|
|
3649
|
+
if (arg === "--session") {
|
|
3650
|
+
index += 1;
|
|
3651
|
+
continue;
|
|
3652
|
+
}
|
|
3653
|
+
if (arg.startsWith("--session=")) continue;
|
|
3654
|
+
withoutSessionValue.push(arg);
|
|
3655
|
+
}
|
|
3656
|
+
return {
|
|
3657
|
+
kind: "plugin",
|
|
3658
|
+
pluginArgs: ["session", commandArgs[0], ...withoutSessionValue, ...parsed.sessionId ? [`--session=${parsed.sessionId}`] : []]
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3460
3661
|
if (commandArgs[0] === "process-log") {
|
|
3461
3662
|
rejectSessionRunGlobalScopes(options, false);
|
|
3462
3663
|
const selection = parseSessionProcessLogArgs(commandArgs.slice(1));
|
|
@@ -3647,6 +3848,7 @@ export {
|
|
|
3647
3848
|
parseAnswerQuestionsCommandArgs,
|
|
3648
3849
|
parseBranchCreateArgs,
|
|
3649
3850
|
parseBranchDeleteArgs,
|
|
3851
|
+
parseClaimDuration,
|
|
3650
3852
|
parseConversationForkArgs,
|
|
3651
3853
|
parseConversationInspectWorkArgs,
|
|
3652
3854
|
parseConversationOverviewArgs,
|
|
@@ -3659,6 +3861,8 @@ export {
|
|
|
3659
3861
|
parsePromptArgs,
|
|
3660
3862
|
parsePsHistoryArgs,
|
|
3661
3863
|
parsePsListArgs,
|
|
3864
|
+
parseSessionClaimArgs,
|
|
3865
|
+
parseSessionClaimsListArgs,
|
|
3662
3866
|
parseSessionProcessLogArgs,
|
|
3663
3867
|
parseSessionRunCommand,
|
|
3664
3868
|
parseSetEnvArgs,
|
|
@@ -3682,6 +3886,8 @@ export {
|
|
|
3682
3886
|
renderProcessInspection,
|
|
3683
3887
|
renderProcessList,
|
|
3684
3888
|
renderProcessLog,
|
|
3889
|
+
renderSessionClaimList,
|
|
3890
|
+
renderSessionClaimResponse,
|
|
3685
3891
|
renderSessionList,
|
|
3686
3892
|
renderSessionRunStart,
|
|
3687
3893
|
renderSessionRunStatus,
|
package/dist/mjs/package.json
CHANGED
package/dist/types/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R5dctlApiError, R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlK8sResourceHistory, type R5dctlK8sUsage, type R5dctlK8sWorkload, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessLog, type R5dctlProcessRun, type R5dctlBranch, type R5dctlBranchDeleteResponse, type R5dctlBranchDescription, type R5dctlBranchOperation, type R5dctlSessionSummary, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorkingTreeMode, type R5dctlWorktreeSource, type R5dctlPromptDelivery, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
1
|
+
import { R5dctlApiError, R5dctlClient, type R5dctlConversationForkResponse, type R5dctlConversationHeadResponse, type R5dctlConversationRenderOptions, type R5dctlConversationResponse, type R5dctlConversationNodeResponse, type R5dctlConversationOverviewResponse, type R5dctlConversationWorkDetail, type R5dctlConversationWorkResponse, type R5dctlEnvData, type R5dctlK8sResourceHistory, type R5dctlK8sUsage, type R5dctlK8sWorkload, type R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessLog, type R5dctlProcessRun, type R5dctlBranch, type R5dctlBranchDeleteResponse, type R5dctlBranchDescription, type R5dctlBranchOperation, type R5dctlSessionSummary, type R5dctlSessionClaim, type R5dctlSessionClaimListResponse, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorkingTreeMode, type R5dctlWorktreeSource, type R5dctlPromptDelivery, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
|
|
2
2
|
export type GlobalOptions = {
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
json: boolean;
|
|
@@ -140,13 +140,36 @@ export declare function branchDeletionConfirmationMessage(branch: R5dctlBranchDe
|
|
|
140
140
|
export declare function renderBranchDeletion(result: R5dctlBranchDeleteResponse): string;
|
|
141
141
|
/** Operation facts only: no commit hash, because a hash is stale as soon as the branch commits. */
|
|
142
142
|
export declare function renderBranchOperation(operation: R5dctlBranchOperation, projectRef: string): string;
|
|
143
|
-
export declare function parseSessionRunCommand(options: Pick<GlobalOptions, "project" | "branch" | "session">, args: string[]): R5dctlSessionRunCommand;
|
|
143
|
+
export declare function parseSessionRunCommand(options: Pick<GlobalOptions, "project" | "branch" | "session">, args: string[], env?: Record<string, string | undefined>): R5dctlSessionRunCommand;
|
|
144
144
|
export type R5dctlSessionProcessLogArgs = {
|
|
145
145
|
runId: string;
|
|
146
146
|
tail?: number;
|
|
147
147
|
grep?: string;
|
|
148
148
|
};
|
|
149
149
|
export declare function parseSessionProcessLogArgs(args: string[]): R5dctlSessionProcessLogArgs;
|
|
150
|
+
export type R5dctlSessionClaimAction = "acquire" | "status" | "renew" | "release";
|
|
151
|
+
export type R5dctlSessionClaimArgs = {
|
|
152
|
+
action: R5dctlSessionClaimAction;
|
|
153
|
+
topic: string;
|
|
154
|
+
/** The acting session; omitted lets a worker credential act as its own verified session. */
|
|
155
|
+
sessionId?: string;
|
|
156
|
+
ttlSeconds?: number;
|
|
157
|
+
note?: string;
|
|
158
|
+
/** Claim generation observed by the caller; renew and release are fenced to it. */
|
|
159
|
+
generation?: number;
|
|
160
|
+
};
|
|
161
|
+
/** `90s`, `30m`, `2h`; the server clamps to between one minute and one day. */
|
|
162
|
+
export declare function parseClaimDuration(value: string): number;
|
|
163
|
+
export declare function parseSessionClaimArgs(args: string[], options?: Pick<GlobalOptions, "session">, env?: Record<string, string | undefined>): R5dctlSessionClaimArgs;
|
|
164
|
+
export declare function parseSessionClaimsListArgs(args: string[], options?: Pick<GlobalOptions, "session">, env?: Record<string, string | undefined>): {
|
|
165
|
+
sessionId?: string;
|
|
166
|
+
};
|
|
167
|
+
export declare function renderSessionClaimResponse(response: {
|
|
168
|
+
outcome: string;
|
|
169
|
+
actorSessionId: string;
|
|
170
|
+
claim: R5dctlSessionClaim | null;
|
|
171
|
+
}, nowMs?: number): string;
|
|
172
|
+
export declare function renderSessionClaimList(response: R5dctlSessionClaimListResponse, nowMs?: number): string;
|
|
150
173
|
export declare function parseGlobalArgs(argv: string[]): {
|
|
151
174
|
options: GlobalOptions;
|
|
152
175
|
rest: string[];
|
|
@@ -355,6 +378,7 @@ export type ConversationInspectWorkOptions = {
|
|
|
355
378
|
export declare function parseConversationInspectWorkArgs(args: string[]): ConversationInspectWorkOptions;
|
|
356
379
|
export declare function parseConversationOverviewArgs(args: string[]): {
|
|
357
380
|
skeleton: boolean;
|
|
381
|
+
tail?: number;
|
|
358
382
|
};
|
|
359
383
|
export declare function parseConversationForkArgs(args: string[]): string | undefined;
|
|
360
384
|
export declare function renderConversationOverviewResponse(read: R5dctlConversationOverviewResponse): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/r5dctl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.118",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/cjs/cli.cjs",
|
|
6
6
|
"module": "./dist/mjs/cli.mjs",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"r5dctl": "dist/cjs/main.cjs"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ricsam/r5d-api": "^0.0.
|
|
29
|
+
"@ricsam/r5d-api": "^0.0.118",
|
|
30
30
|
"dotenv": "^17",
|
|
31
31
|
"qrcode": "^1.5.4",
|
|
32
32
|
"ws": "^8.18.3"
|