@ricsam/r5dctl 0.0.116 → 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 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
- "Use --delivery steer for active-session coordination, including merge notices, pause requests, blockers, and handoffs.",
188
- "Use force when the current tool round must be interrupted; queue is only for work deliberately deferred until the turn ends.",
189
- "Prompt delivery is not acknowledgment. If a shared-checkout change depends on a pause, wait for the recipient's reply before proceeding.",
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 thinking while replacing tool work with inspectable ranges; --skeleton keeps only dialogue, decisions, and per-turn work markers."
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. Use steer for active-session coordination; queue only deliberately deferred work. --sender adds an optional title above the message."
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 sender = parsed.values.get("--sender")?.trim();
831
- if (sender !== void 0 && !sender) throw new Error("--sender must not be empty");
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
- for (const arg of args) {
2698
- if (arg !== "--skeleton") {
2699
- throw new Error(`Unknown conversation overview argument: ${arg}`);
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
- skeleton = true;
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, skeleton ? { view: "skeleton" } : {});
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,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.116",
3
+ "version": "0.0.118",
4
4
  "type": "commonjs"
5
5
  }
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
- "Use --delivery steer for active-session coordination, including merge notices, pause requests, blockers, and handoffs.",
88
- "Use force when the current tool round must be interrupted; queue is only for work deliberately deferred until the turn ends.",
89
- "Prompt delivery is not acknowledgment. If a shared-checkout change depends on a pause, wait for the recipient's reply before proceeding.",
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 thinking while replacing tool work with inspectable ranges; --skeleton keeps only dialogue, decisions, and per-turn work markers."
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. Use steer for active-session coordination; queue only deliberately deferred work. --sender adds an optional title above the message."
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 sender = parsed.values.get("--sender")?.trim();
731
- if (sender !== void 0 && !sender) throw new Error("--sender must not be empty");
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
- for (const arg of args) {
2598
- if (arg !== "--skeleton") {
2599
- throw new Error(`Unknown conversation overview argument: ${arg}`);
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
- skeleton = true;
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, skeleton ? { view: "skeleton" } : {});
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,
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.116",
3
+ "version": "0.0.118",
4
4
  "type": "module"
5
5
  }
@@ -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.116",
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.116",
29
+ "@ricsam/r5d-api": "^0.0.118",
30
30
  "dotenv": "^17",
31
31
  "qrcode": "^1.5.4",
32
32
  "ws": "^8.18.3"