@ricsam/r5dctl 0.0.133 → 0.0.135

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -158,13 +158,15 @@ r5dctl ps stop <run-id>
158
158
 
159
159
  `session start` creates an ordinary agent-mode chat on an explicitly selected existing worktree, or creates a named linked worktree from an explicit `worktree:<branch>` source. A new worktree is cut from the source branch's current commit; `--working-tree carry` (the default, `git switch -c` semantics) also copies the source checkout's uncommitted changes into it, while `--working-tree clean` checks out that commit only. The new session's first message states which mode applies, and the start output prints `Source` and `Working tree` lines. A clean request fails with a clear error when the selected worker predates the option, because such a worker would silently carry instead. It requires project, worker, model, and prompt and never infers them from the current shell. Start returns a durable `provisioning` handle immediately; workspace synchronization, linked-worktree creation, and prompt enqueue continue in the background and are visible through `session status`. Idempotent request retries return the same handle. Prompts return after durable enqueue. Task specialization belongs in the prompt; there are no predefined debug, explore, research, or test agents.
160
160
 
161
- `session prompt` works on any project session you can access, including one created in the web UI. `--delivery` decides how the prompt reaches a session whose agent is busy: `steer` (the default) lands inside the current turn right after the agent's next tool result, `queue` waits until the agent finishes the whole turn, and `force` aborts the agent's current round of tool calls and continues from the prompt instead. An idle session starts a new generation whatever the delivery, and a steer queued while the session was idle with an unfinished turn is still heard by the generation that resumes it. The response's `Prompt:` line reports `started`, `steered`, `queued`, or `forced`. `session stop` returns as soon as the agent run is stopped; the stopped worktree's workspace Git synchronization finishes asynchronously behind the response.
161
+ `session prompt` works on any project session you can access, including one created in the web UI. `--delivery` decides how the prompt reaches a session whose agent is busy: `steer` (the default) lands inside the current turn right after the agent's next tool result, `queue` waits until the agent finishes the whole turn, and `force` aborts the agent's current round of tool calls and continues from the prompt instead. An idle conversation can start a new generation, but an unfinished turn may still require an explicit user continuation, an interactive answer, worker recovery, or confirmation of interrupted tool replay. A peer prompt never grants that authority. Prompt and answer responses include `messageDelivery: { messageId, queueId, status, blocker }`; `status` distinguishes `blocked` from ordinary `queued`, `steered`, `starting`, `started`, or `forced` delivery. The original message ID and current queue key let callers identify the saved input without sending it again. A blocked response does not start a generation, even when the command exits successfully. Continuation blockers retain the accepted input; `superseded_message` instead identifies a discarded obsolete notice, and `message_already_processed` identifies input already recorded in the conversation.
162
+
163
+ For `pending_tool_replay`, inspect the named tools and current files before deciding whether their actions can safely be repeated. To continue without replaying the interrupted round, an authorized user can use **Force now** on an existing queued message in the session UI. Dispatch the earliest urgent queued message first so later urgent messages retain their order. Force changes the active conversation branch; it does not undo filesystem effects. Do not send another force prompt to replace an already queued continuation. `session stop` returns as soon as the agent run is stopped; the stopped worktree's workspace Git synchronization finishes asynchronously behind the response.
162
164
 
163
165
  Use the optional `--sender "Authentication review"` flag to display a title above the usual message bubble. Agent shells expose `R5D_SESSION_NAME`, refreshed at each command start from the session name (or its ID until named), so agents can pass `--sender "$R5D_SESSION_NAME"`. The sender is a caller-supplied label; omitting the flag sends no title and leaves the message body unchanged.
164
166
 
165
167
  For messages to an active session, explicitly use `--delivery steer` for coordination such as merge notices, requests to pause or avoid files, blockers, corrections, and handoff or resume notices. Use `--delivery force` when its current tool round must be interrupted. Reserve `--delivery queue` for work intentionally deferred until the entire current turn ends; even an FYI needs steering when it affects current work. These rules apply equally to peers, parents, and children.
166
168
 
167
- Delivery is not acknowledgment. If a merge or shared-checkout change depends on another session pausing edits, send the pause request separately, wait for its explicit reply, and recheck the working tree before proceeding. `Prompt: queued` cannot establish that coordination, and even `steered` or `forced` does not prove the agent has read or acted on the request. Reply to coordination requests and send resume notices with `--delivery steer`.
169
+ Delivery is not acknowledgment. If a merge or shared-checkout change depends on another session pausing edits, send the pause request separately, wait for its explicit reply, and recheck the working tree before proceeding. `Delivery: queued` or `blocked` cannot establish that coordination, and even `steered` or `forced` does not prove the agent has read or acted on the request. Reply to coordination requests and send resume notices with `--delivery steer`.
168
170
 
169
171
  `get sessions` lists a project's sessions, newest first, and appends each session's live state (`running`, `stopping`, `paused`, `provisioning`, `queued`, or `idle`) and how long ago its conversation last advanced (`running, last active 12s ago`). Several sessions can work in the same branch checkout at once (task chips, shared-mode sub-agents, sessions the user starts), so run it with `--branch` before relying on a working tree or reviewing what changed in it. A server that predates the fields prints the id, branch, and name only.
170
172
 
package/dist/cjs/cli.cjs CHANGED
@@ -189,7 +189,8 @@ const SESSION_RUN_HELP_TEXT = [
189
189
  "",
190
190
  "Create, inspect, resume, and stop ordinary agent sessions.",
191
191
  "prompt reaches a busy session by --delivery: steer (default) lands after the agent's next tool result,",
192
- "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.",
192
+ "queue waits for the current turn to finish; force replaces the current tool round. Idle unfinished sessions may still require a user continuation.",
193
+ "Blocked delivery does not start a generation: inspect its reason and original message/queue ID. Use the session UI for Resume or queued Force now.",
193
194
  "Send only messages that need action: grants, releases, blockers, corrections, pause/resume, questions, and completion reports;",
194
195
  "batch progress and waiting reports into the update the recipient asked for. Every steer wakes a waiting recipient for a full turn.",
195
196
  "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.",
@@ -347,7 +348,7 @@ const SHARED_HELP_ENTRIES = [
347
348
  {
348
349
  section: "sessions",
349
350
  usage: 'session prompt --worker <label> --mode <mode> --model <tier> [--delivery queue|steer|force] [--sender <name>] <session-id> "<prompt>"',
350
- 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."
351
+ description: "Prompt a session: steer after the next tool result (default), queue a deferred follow-up, or force in place of the current tool round. An idle unfinished session may require explicit continuation or confirmation of interrupted tools. Blocked delivery reports the saved message/queue ID and reason; inspect the existing queue in the session UI instead of sending duplicates. The recipient sees the verified sender session id; --sender only adds a display title."
351
352
  },
352
353
  {
353
354
  section: "sessions",
@@ -523,10 +524,14 @@ function renderCliOnlyCommandHelp(entry) {
523
524
  `;
524
525
  }
525
526
  function renderSharedCommandHelp(pathSegments) {
526
- const entry = SHARED_HELP_ENTRIES.map((candidate) => ({
527
- candidate,
528
- usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.includes("<") && !segment.startsWith("["))
529
- })).filter(
527
+ const entry = SHARED_HELP_ENTRIES.map((candidate) => {
528
+ const usageSegments = [];
529
+ for (const segment of candidate.usage.split(/\s+/)) {
530
+ if (/^[a-z][a-z-]*$/.test(segment)) usageSegments.push(segment);
531
+ else if (usageSegments.length > 0) break;
532
+ }
533
+ return { candidate, usageSegments };
534
+ }).filter(
530
535
  ({ usageSegments }) => usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index])
531
536
  ).sort((left, right) => right.usageSegments.length - left.usageSegments.length)[0]?.candidate;
532
537
  if (!entry) {
@@ -2331,6 +2336,49 @@ function formatChildSessionsLine(session) {
2331
2336
  if (!Number.isInteger(total) || !Number.isInteger(active) || total <= 0 || active < 0) return void 0;
2332
2337
  return `Child sessions: ${active} active of ${total}`;
2333
2338
  }
2339
+ function formatMessageDelivery(delivery) {
2340
+ const blockedDescription = delivery.blocker?.reason === "superseded_message" ? "superseded; not accepted" : "accepted; not started";
2341
+ const lines = [
2342
+ `Message: ${delivery.messageId}`,
2343
+ ...delivery.queueId ? [`Queue item: ${delivery.queueId}`] : [],
2344
+ `Delivery: ${delivery.status === "blocked" ? `blocked (${blockedDescription})` : delivery.status}`
2345
+ ];
2346
+ if (delivery.status === "recorded") lines.push("This message is already recorded; this retry did not request another generation.");
2347
+ const blocker = delivery.blocker;
2348
+ if (!blocker) return lines;
2349
+ switch (blocker.reason) {
2350
+ case "pending_tool_replay":
2351
+ lines.push(`Blocked by: interrupted tools require replay confirmation (${blocker.pendingToolNames.join(", ")}).`);
2352
+ break;
2353
+ case "manual_stop":
2354
+ lines.push("Blocked by: explicit Stop. An authorized user must resume the session in the UI.");
2355
+ break;
2356
+ case "worker_recovery":
2357
+ lines.push("Blocked by: worker recovery. Wait for recovery, then resume the session explicitly.");
2358
+ break;
2359
+ case "interactive_input":
2360
+ lines.push(
2361
+ `Blocked by: pending ${[blocker.questionIds.length ? `questions (${blocker.questionIds.join(", ")})` : "", blocker.environmentRequest ? "environment request" : ""].filter(Boolean).join(" and ") || "interactive input"}. Answer it in the session UI.`
2362
+ );
2363
+ break;
2364
+ case "explicit_continuation_required":
2365
+ lines.push("Blocked by: this unfinished turn needs an explicit user continuation; a peer message cannot start it.");
2366
+ break;
2367
+ case "workspace_remediation":
2368
+ lines.push("Blocked by: workspace remediation. The saved input waits for that recovery.");
2369
+ break;
2370
+ case "superseded_message":
2371
+ lines.push("The source outcome changed; this superseded notice cannot start a generation.");
2372
+ break;
2373
+ }
2374
+ if (delivery.queueId && ["pending_tool_replay", "explicit_continuation_required"].includes(blocker.reason)) {
2375
+ lines.push(
2376
+ "To replace the interrupted round without replaying it, use Force now on the earliest urgent queued message in the session UI. Later urgent messages retain their order.",
2377
+ "Inspect current files before retrying interrupted edits. Use the existing queue item; do not send the prompt again."
2378
+ );
2379
+ }
2380
+ return lines;
2381
+ }
2334
2382
  function renderSessionRunStatus(session) {
2335
2383
  const status = formatStatusValue(session.status) ?? "unknown";
2336
2384
  const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
@@ -2338,8 +2386,7 @@ function renderSessionRunStatus(session) {
2338
2386
  if (runStatus) lines.push(`Run: ${runStatus}`);
2339
2387
  const provisioningQueue = formatProvisioningQueueLine(session);
2340
2388
  if (provisioningQueue) lines.push(provisioningQueue);
2341
- const promptDisposition = responseString(session, "promptDisposition");
2342
- if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
2389
+ if (session.messageDelivery) lines.push(...formatMessageDelivery(session.messageDelivery));
2343
2390
  const branch = responseString(session, "branchName");
2344
2391
  if (branch) lines.push(`Worktree: ${branch}`);
2345
2392
  pushWorktreeOriginLines(lines, session);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.133",
3
+ "version": "0.0.135",
4
4
  "type": "commonjs"
5
5
  }
package/dist/mjs/cli.mjs CHANGED
@@ -79,7 +79,8 @@ const SESSION_RUN_HELP_TEXT = [
79
79
  "",
80
80
  "Create, inspect, resume, and stop ordinary agent sessions.",
81
81
  "prompt reaches a busy session by --delivery: steer (default) lands after the agent's next tool result,",
82
- "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.",
82
+ "queue waits for the current turn to finish; force replaces the current tool round. Idle unfinished sessions may still require a user continuation.",
83
+ "Blocked delivery does not start a generation: inspect its reason and original message/queue ID. Use the session UI for Resume or queued Force now.",
83
84
  "Send only messages that need action: grants, releases, blockers, corrections, pause/resume, questions, and completion reports;",
84
85
  "batch progress and waiting reports into the update the recipient asked for. Every steer wakes a waiting recipient for a full turn.",
85
86
  "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.",
@@ -237,7 +238,7 @@ const SHARED_HELP_ENTRIES = [
237
238
  {
238
239
  section: "sessions",
239
240
  usage: 'session prompt --worker <label> --mode <mode> --model <tier> [--delivery queue|steer|force] [--sender <name>] <session-id> "<prompt>"',
240
- 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."
241
+ description: "Prompt a session: steer after the next tool result (default), queue a deferred follow-up, or force in place of the current tool round. An idle unfinished session may require explicit continuation or confirmation of interrupted tools. Blocked delivery reports the saved message/queue ID and reason; inspect the existing queue in the session UI instead of sending duplicates. The recipient sees the verified sender session id; --sender only adds a display title."
241
242
  },
242
243
  {
243
244
  section: "sessions",
@@ -413,10 +414,14 @@ function renderCliOnlyCommandHelp(entry) {
413
414
  `;
414
415
  }
415
416
  function renderSharedCommandHelp(pathSegments) {
416
- const entry = SHARED_HELP_ENTRIES.map((candidate) => ({
417
- candidate,
418
- usageSegments: candidate.usage.split(" ").map((segment) => segment.replace(/^\[/, "").replace(/\]$/, "")).filter((segment) => !segment.startsWith("-") && !segment.includes("<") && !segment.startsWith("["))
419
- })).filter(
417
+ const entry = SHARED_HELP_ENTRIES.map((candidate) => {
418
+ const usageSegments = [];
419
+ for (const segment of candidate.usage.split(/\s+/)) {
420
+ if (/^[a-z][a-z-]*$/.test(segment)) usageSegments.push(segment);
421
+ else if (usageSegments.length > 0) break;
422
+ }
423
+ return { candidate, usageSegments };
424
+ }).filter(
420
425
  ({ usageSegments }) => usageSegments.length <= pathSegments.length && usageSegments.every((segment, index) => segment === pathSegments[index])
421
426
  ).sort((left, right) => right.usageSegments.length - left.usageSegments.length)[0]?.candidate;
422
427
  if (!entry) {
@@ -2221,6 +2226,49 @@ function formatChildSessionsLine(session) {
2221
2226
  if (!Number.isInteger(total) || !Number.isInteger(active) || total <= 0 || active < 0) return void 0;
2222
2227
  return `Child sessions: ${active} active of ${total}`;
2223
2228
  }
2229
+ function formatMessageDelivery(delivery) {
2230
+ const blockedDescription = delivery.blocker?.reason === "superseded_message" ? "superseded; not accepted" : "accepted; not started";
2231
+ const lines = [
2232
+ `Message: ${delivery.messageId}`,
2233
+ ...delivery.queueId ? [`Queue item: ${delivery.queueId}`] : [],
2234
+ `Delivery: ${delivery.status === "blocked" ? `blocked (${blockedDescription})` : delivery.status}`
2235
+ ];
2236
+ if (delivery.status === "recorded") lines.push("This message is already recorded; this retry did not request another generation.");
2237
+ const blocker = delivery.blocker;
2238
+ if (!blocker) return lines;
2239
+ switch (blocker.reason) {
2240
+ case "pending_tool_replay":
2241
+ lines.push(`Blocked by: interrupted tools require replay confirmation (${blocker.pendingToolNames.join(", ")}).`);
2242
+ break;
2243
+ case "manual_stop":
2244
+ lines.push("Blocked by: explicit Stop. An authorized user must resume the session in the UI.");
2245
+ break;
2246
+ case "worker_recovery":
2247
+ lines.push("Blocked by: worker recovery. Wait for recovery, then resume the session explicitly.");
2248
+ break;
2249
+ case "interactive_input":
2250
+ lines.push(
2251
+ `Blocked by: pending ${[blocker.questionIds.length ? `questions (${blocker.questionIds.join(", ")})` : "", blocker.environmentRequest ? "environment request" : ""].filter(Boolean).join(" and ") || "interactive input"}. Answer it in the session UI.`
2252
+ );
2253
+ break;
2254
+ case "explicit_continuation_required":
2255
+ lines.push("Blocked by: this unfinished turn needs an explicit user continuation; a peer message cannot start it.");
2256
+ break;
2257
+ case "workspace_remediation":
2258
+ lines.push("Blocked by: workspace remediation. The saved input waits for that recovery.");
2259
+ break;
2260
+ case "superseded_message":
2261
+ lines.push("The source outcome changed; this superseded notice cannot start a generation.");
2262
+ break;
2263
+ }
2264
+ if (delivery.queueId && ["pending_tool_replay", "explicit_continuation_required"].includes(blocker.reason)) {
2265
+ lines.push(
2266
+ "To replace the interrupted round without replaying it, use Force now on the earliest urgent queued message in the session UI. Later urgent messages retain their order.",
2267
+ "Inspect current files before retrying interrupted edits. Use the existing queue item; do not send the prompt again."
2268
+ );
2269
+ }
2270
+ return lines;
2271
+ }
2224
2272
  function renderSessionRunStatus(session) {
2225
2273
  const status = formatStatusValue(session.status) ?? "unknown";
2226
2274
  const lines = [`Session: ${responseString(session, "sessionId") ?? "(unknown)"}`, `Status: ${status}`];
@@ -2228,8 +2276,7 @@ function renderSessionRunStatus(session) {
2228
2276
  if (runStatus) lines.push(`Run: ${runStatus}`);
2229
2277
  const provisioningQueue = formatProvisioningQueueLine(session);
2230
2278
  if (provisioningQueue) lines.push(provisioningQueue);
2231
- const promptDisposition = responseString(session, "promptDisposition");
2232
- if (promptDisposition) lines.push(`Prompt: ${promptDisposition}`);
2279
+ if (session.messageDelivery) lines.push(...formatMessageDelivery(session.messageDelivery));
2233
2280
  const branch = responseString(session, "branchName");
2234
2281
  if (branch) lines.push(`Worktree: ${branch}`);
2235
2282
  pushWorktreeOriginLines(lines, session);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.133",
3
+ "version": "0.0.135",
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 R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessLog, type R5dctlProcessRun, type R5dctlWorkerCapacityReport, type R5dctlBranch, type R5dctlBranchDeletionStatus, type R5dctlBranchDescription, type R5dctlBranchOperation, type R5dctlSessionSummary, type R5dctlCheckoutPathResponse, type R5dctlSessionClaim, type R5dctlSessionClaimListResponse, type R5dctlSessionClaimScope, 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 R5dctlProcessInspection, type R5dctlProcessListInput, type R5dctlProcessLog, type R5dctlProcessRun, type R5dctlWorkerCapacityReport, type R5dctlBranch, type R5dctlBranchDeletionStatus, type R5dctlBranchDescription, type R5dctlBranchOperation, type R5dctlSessionSummary, type R5dctlCheckoutPathResponse, type R5dctlSessionClaim, type R5dctlSessionClaimListResponse, type R5dctlSessionClaimScope, type R5dctlSessionEvent, type R5dctlSessionStartInput, type R5dctlWorkspaceStatus, type R5dctlWorkspaceSyncResult, type R5dctlWorkingTreeMode, type R5dctlWorktreeSource, type R5dctlPromptDelivery, type R5dctlMessageDelivery, type R5dctlSessionMode, type ChatMode, type ModelTier } from "@ricsam/r5d-api";
2
2
  import { type AccountCommand } from "./account";
3
3
  export type GlobalOptions = {
4
4
  baseUrl?: string;
@@ -359,7 +359,7 @@ export type R5dctlCommandResponse = {
359
359
  /** Legacy fields older servers still send; never rendered because a reported hash goes stale. */
360
360
  headCommit?: string | null;
361
361
  headCommitError?: string;
362
- promptDisposition?: string;
362
+ messageDelivery?: R5dctlMessageDelivery;
363
363
  error?: string;
364
364
  summary?: string;
365
365
  diffSummary?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5dctl",
3
- "version": "0.0.133",
3
+ "version": "0.0.135",
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.133",
29
+ "@ricsam/r5d-api": "^0.0.135",
30
30
  "dotenv": "^17",
31
31
  "qrcode": "^1.5.4",
32
32
  "ws": "^8.18.3"