@shardflux/cli 0.24.0 → 0.25.0

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +263 -1
  3. package/dist/account-args.js +5 -0
  4. package/dist/account-pool.js +5 -1
  5. package/dist/acp-journal.js +23 -1
  6. package/dist/acp-permission-elsewhere.d.ts +38 -0
  7. package/dist/acp-permission-elsewhere.js +85 -0
  8. package/dist/acp-rpc.d.ts +2 -1
  9. package/dist/acp-rpc.js +11 -2
  10. package/dist/acp.js +95 -22
  11. package/dist/agent-credentials.d.ts +3 -0
  12. package/dist/agent-credentials.js +7 -0
  13. package/dist/args.d.ts +1 -1
  14. package/dist/args.js +172 -9
  15. package/dist/board-args.d.ts +6 -0
  16. package/dist/board-args.js +172 -0
  17. package/dist/board-commands.d.ts +62 -0
  18. package/dist/board-commands.js +412 -0
  19. package/dist/codex-cloud.d.ts +30 -3
  20. package/dist/codex-cloud.js +86 -12
  21. package/dist/conversation-commands.d.ts +30 -0
  22. package/dist/conversation-commands.js +328 -9
  23. package/dist/conversation-transcript.d.ts +44 -0
  24. package/dist/conversation-transcript.js +206 -0
  25. package/dist/conversations.d.ts +7 -3
  26. package/dist/conversations.js +20 -10
  27. package/dist/device-approvals.js +4 -3
  28. package/dist/device-bridge.d.ts +42 -1
  29. package/dist/device-bridge.js +223 -12
  30. package/dist/device-folders.d.ts +66 -0
  31. package/dist/device-folders.js +236 -0
  32. package/dist/device-helper.d.ts +6 -0
  33. package/dist/device-helper.js +98 -14
  34. package/dist/drive-commands.d.ts +25 -0
  35. package/dist/drive-commands.js +201 -0
  36. package/dist/errors.js +2 -1
  37. package/dist/format.js +6 -2
  38. package/dist/handlers.js +7 -1
  39. package/dist/land.d.ts +116 -0
  40. package/dist/land.js +238 -0
  41. package/dist/main.js +10 -1
  42. package/dist/move-files.d.ts +39 -0
  43. package/dist/move-files.js +167 -0
  44. package/dist/move.js +12 -11
  45. package/dist/onboarding.d.ts +10 -6
  46. package/dist/onboarding.js +24 -6
  47. package/dist/project-git.d.ts +7 -1
  48. package/dist/project-git.js +16 -3
  49. package/dist/project-preparation.d.ts +7 -0
  50. package/dist/project-preparation.js +14 -0
  51. package/dist/project-template-setup.js +32 -11
  52. package/dist/router-commands.d.ts +33 -0
  53. package/dist/router-commands.js +179 -0
  54. package/dist/task-land-hub.py +150 -0
  55. package/dist/transcribe-commands.d.ts +7 -0
  56. package/dist/transcribe-commands.js +80 -0
  57. package/dist/worktree-twin-engine.d.ts +8 -0
  58. package/dist/worktree-twin-engine.js +28 -22
  59. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,153 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - Device requests reach the right computer (0.25.0+): the background helper tells the API which conversations are
6
+ local to this computer (started or opened here), so their requests come here first, then to the computer you used
7
+ last, never to whichever was seen last. A project link with a first task (`project_link` with `brief`) reads the
8
+ start's whole JSON output; before, it failed with "Unexpected token '}'" although the conversation started.
9
+
10
+ - `shard board` and `shard task show` show what a working task does now ("working (Fixing the login cookie)") and
11
+ what each working step does, next to the plain waiting line of a task that waits to start.
12
+ - `shard transcribe <file>` (0.25.0+, SDK 0.28.0) prints what a recording says (WebM/Opus or PCM WAV from the file;
13
+ raw PCM with `--format pcm16 --sample-rate`); `--words` adds each word's start and end, `--json` prints the
14
+ transcript.
15
+ - Faster conversation starts (0.24.1): the project's dependency fingerprint is read from Git's own file list instead of walking the whole folder, so ignored and untracked trees beside the project (agent worktrees, build output) no longer hold a start on **Getting your project…**. A folder with 1.2 million such files took 32 s per start; it now takes 0.3 s, with the same fingerprint.
16
+ - A folder without Git that `shard` makes a repository of (a first chat, `shard projects link`) starts with its files as
17
+ the first commit (0.25.0+; `.gitignore` respected, large media stay untracked), so a conversation starts from the
18
+ same commit as the folder and its work comes back by fast-forward, as in any repository. Before, an empty first
19
+ commit left every file to the starting snapshot and the landing asked the agent to merge. A repository of your own
20
+ keeps its staged and untracked files as they are.
21
+ - Device requests (0.25.0+) are answered between turns and as a turn ends; only an archived conversation refuses them.
22
+ - Background preparation of a project with no build or test command ends ready (0.25.0+): there is nothing to check,
23
+ so `shard projects sync list` and the folders `shard projects link` adds no longer show an error.
24
+ - `shard move` (0.25.0+) brings the files the session used outside the project: files you @-mentioned or named in a
25
+ prompt, files it read or edited, and the files a handed file names (a `~/Downloads` handoff and what it points to).
26
+ Each goes to the same place under the VM's home, and this computer's home path leads there, so the history's paths
27
+ open in the cloud. Before, only the checkout and the transcript moved, and a moved session stopped at a handoff it
28
+ could no longer open. Key and sign-in stores (`~/.ssh`, `~/.aws`, `.env` files, private keys) stay here. Up to 64
29
+ files, 64 MiB in all, 16 MiB each; the command names what it brings and what stays, and `--json` adds `files` and
30
+ `files_left`.
31
+ - 0.25.0 (pending): Allowance hints respect unavailable overage; operation errors retain the service’s recovery message and retry delay.
32
+
33
+ - Landing that lands (0.25.0+): `shard task land <task>` merges a finished task's result (`shard/<task id>`, where its
34
+ master merged its workers' branches; when a master did not, their branches are merged into it here when they merge
35
+ cleanly) into the project's default branch on the project hub, a fast-forward or a clean merge commit, never by
36
+ force; brings it into your folder on this computer the way finished conversations land (your branch moves, your
37
+ uncommitted edits stay, rewritten files are backed up first); checks the commit is in the branch; and only then
38
+ records it on the board (`Landed in ~/code/best-todo · 3f2a1c9`). A conflict changes nothing and asks the task's
39
+ master to merge (`land_conflict`). `shard needs-you answer land_… land` does the same, and
40
+ `shard conversations land <id> merge` now lands the conversation's branch the same way before it records it (before,
41
+ both only recorded a landing). `--path <folder>` picks the folder; `--json` returns `{landed, result, hub, folder,
42
+ recorded, message}`.
43
+
44
+ - `shard conversations delete <id> --yes` (`shard c delete`, 0.25.0+, SDK 0.28.0) deletes a conversation everywhere at
45
+ once: its agent stops, its computer is deleted (a computer other conversations share stays for them), and its
46
+ messages, transcript and branch go with it, on every device. Without `--yes` it is refused before any request, as
47
+ every deletion is. `--json` prints the API's answer, `{id, deleted, deleted_at, operation}`; deleting it again answers
48
+ the same. The person who started it (or created the API key that did) and organization owners and admins delete
49
+ conversations. `shard conversations feed` shows a deleted conversation once as `<id> Deleted conversation deleted`.
50
+ - ACP input recovery (0.25.0): temporary guest-agent interruptions use bounded backoff with the same native input identity. The editor retains its running turn; permanent errors and unverified receipts still require loading the session.
51
+
52
+ - Tunnel recovery (0.24.1, SDK 0.27.3): a foreground reverse tunnel replaces its guest helper automatically when replay is lost. Existing TCP peers reset; fresh connections reach the new listener. SSH and device port-forward connections close cleanly for a new connection instead of looping on an unrecoverable cursor.
53
+ - Folders on this computer (0.25.0+): an agent can list a folder here (`dir_list`: names, kinds, sizes and Git
54
+ remotes, never file contents), make one of your projects from it (`project_link`, with an optional first task), and
55
+ move a file to or from anywhere here with an absolute or `~/` path. Nothing runs before you approve it: **Allow once**,
56
+ or **Always for this folder**, which covers that kind of request for that folder and what is inside it, from any of
57
+ your conversations. A path outside every such folder asks again, and commands never run under a folder grant. The
58
+ background helper now answers for your signed-in project (and, signed in without a key, every project you can reach),
59
+ so a home conversation or an Orchestrator task can ask too, and reads every project's requests in one poll.
60
+ - `shard projects link <path> [--prompt <text>] [--app claude|codex]` (0.25.0+) makes a local folder one of your
61
+ projects: prepared in the background and kept as this computer's live copy; `--prompt` also starts a conversation
62
+ there. `--json`: `{project: {project_id, repo_remote, folder, name, conversation_id?}}`.
63
+ - `shard device-helper` (0.25.0+) asks the desktop app first when it serves `device-approver.sock` in the config folder,
64
+ so approvals show there as Needs-you items; otherwise the menu bar, an editor (ACP) or `shard device approve` as before.
65
+
66
+ - `shard route <text...>` and `shard ask <question...>` (0.25.0+, SDK 0.28.0): the board's input bar in a terminal.
67
+ `shard route` prints where the input goes (its project or your home) and whether it is a task, a question or a steer
68
+ for an open task, with each task in your words and its title when the input asks for several; `shard ask` streams
69
+ the answer to a quick question about your work as it is written. `--task <task-id>` names the selected task, `--org`
70
+ and `--project` the scope (signed in: your organization's projects; with `SHARDFLUX_API_KEY` set: that key's
71
+ project), and `--json` prints the API's `RouterResult` or the whole answer.
72
+ - The board (0.25.0+, SDK 0.28.0): `shard board` shows what waits on you first, then every task in Now, Next and
73
+ Later with its state, title or words and project; `--follow` prints each change as it happens, until Ctrl-C
74
+ (`--json`: the board, then one feed answer per line). `shard task add <words...>` (`--next`, `--later`, `--first`,
75
+ `--after <task-id>`, `--home`, `--project`), `show`, `edit` (`--text`, `--title`, `--now|--next|--later`,
76
+ `--first|--after`, `--keep`, `--clear`), `rm`, `steer` and `clear-done` manage tasks in your own words, and `shard
77
+ task show` lists each report with its evidence. `shard needs-you` lists everything your agents wait on, and `shard
78
+ needs-you answer <id>` answers any of it (`allow|always|deny`, `--everywhere`, `--pick`, `--text`,
79
+ `land|keep|discard`, `--keep <task-id>`). `shard approvals` shows the approval mode and always-allow rules; `set
80
+ <ask|routine|trusted|default> [--project <id>|--default]`, `allow <tool> <pattern...> [--project <id>|--everywhere]`
81
+ and `remove <rule-id>` change them.
82
+
83
+ - Project drive (0.25.0+, SDK 0.28.0): `shard drive put <local-file> [<drive-path>]` saves a file to the project's
84
+ drive as a new version and prints its path and sha256 (the drive path defaults to the file's name; `-` reads standard
85
+ input; `--content-type`); `shard drive get <path>` writes its bytes to standard output or with `-o` to a file, checked
86
+ against their SHA-256, and `--sha256` reads exactly that version, also after the path was overwritten or removed;
87
+ `shard drive ls [prefix]` lists the current files (size, saved time, type, path; `--limit`, `--cursor`, `--all`);
88
+ `shard drive rm <path>`. The API key's project or `--project`; signed in, any of your projects by id or slug.
89
+ `--json` everywhere.
90
+ - Model template (0.25.0+, SDK 0.28.0): `shard models` prints which harness, model and effort each role of a task
91
+ (master, builder, hard, light, reviewer) runs with, until when a role waits for its account's next usage window and
92
+ what the other provider would run now; `--harness` and `--attempt` show a running task's and a retry's. `shard models
93
+ primary claude|codex|clear` sets the project's primary provider, `--org <id|slug>` the organization's default.
94
+ - `shard conversations list` and `shard conversations feed` (0.25.0+, SDK 0.28.0): signed in without an API key, every
95
+ project you work in, across your organizations, on one cursor; `--org <id|slug>` narrows them to one organization
96
+ (and uses your sign-in when you have one). With an API key they cover its project, as before.
97
+
98
+ - `shard conversations answer <id> <permission-id> --pick <option>` (0.25.0+) answers the agent's question (Claude Code's
99
+ AskUserQuestion) with exactly the picked option; repeat `--pick` for a multi-select question, `--pick
100
+ "<question>=<option>"` when it asks several; `deny` declines it.
101
+
102
+ - `shard acp`: a conversation computer on a newer template keeps the models the session offered until its live list
103
+ arrives, so the editor's first model choice is taken; before, the list went empty for a few seconds after
104
+ `session/new` and `session/set_model` answered "This model is no longer available".
105
+
106
+ - Codex accounts (0.25.0+, SDK 0.28.0): `shard agents open` of a Codex Cloud conversation checks the Codex app on this
107
+ computer first. When the app is signed out or signed in to another ChatGPT account than the thread's, it says so in
108
+ one line with the account to sign in as and the thread's web link, instead of opening a link the app cannot show.
109
+ Earlier threads are listed with their accounts (`Earlier history: <url> (account)`). `--json` adds `account`,
110
+ `codex_app_login`, `account_mismatch` (`line`, `sign_in_as`, `web_url`) and each earlier thread's `account`.
111
+ `shard agents accounts use` of a Codex account says when the Codex app here is signed in to another account
112
+ (`--json`: `codex_app`).
113
+ - Codex sign-in (0.25.0+, SDK 0.28.0): when Codex Cloud refuses the copied ChatGPT login, `shard setup --inspect`
114
+ says so in one line with the next step: `codex login` (Sign in to Codex again), then, once this computer has the new
115
+ sign-in, Copy sign-in (`shard agents credentials sync`); `--connect` copies it. `shard ./ codex` and
116
+ `shard agents codex enable` copy this computer's login and continue when it is new, or stop at once with
117
+ `codex_sign_in_required` and that line. The Codex remote device is never restarted for a sign-in (its Cloud threads
118
+ keep running), and one starting again with the new login reads as connecting.
119
+
120
+ - `shard conversations start` starts a conversation without a folder: from the project hub (`--repo`, `--branch`), or with `--home` a coordinator on your machine that spawns threads in the project's repositories. `--app`, `--model`, `--effort`, `--title`, `--project`; `--prompt` sends the first message, with `--file` attachments. Prints the conversation's id, key and state (`--json`: the conversation, its workspace and the message).
121
+
122
+ - `shard conversations send <id> <text> --file <path>` (repeatable, up to 20 files of 64 MiB each) puts each file in the conversation's computer before the message, which lists their paths, and prints `Attached <path>` for each. The text may be left out when files are attached, and unquoted words are sent as one message.
123
+
124
+ - Home conversations list as `home` in `shard conversations list` and `get`, and open without a repository in `/home/user`.
125
+
126
+ - `shard acp`: a permission prompt answered through the API (the Shard client, `shard conversations answer`, MCP)
127
+ while the editor shows it is withdrawn from the editor (`$/cancel_request`) and the turn goes on in the editor; before,
128
+ the editor saw nothing more of the turn until it answered its own prompt.
129
+
130
+ - Permission requests (0.25.0+, SDK 0.28.0): `shard conversations permissions <id>` lists what a conversation's agent
131
+ asked permission for before running a tool (Claude Code, Codex and ACP agents), oldest first, with `--state`,
132
+ `--all` and `--json`; `shard conversations answer <id> <permission-id> allow|always|deny` answers one.
133
+ `shard conversations get` names the requests a `needs_you` conversation waits on (`Waiting on: Run npm test`).
134
+
135
+ - `shard conversations watch <id>` shows a conversation's terminal live and read-only: its last 256 KiB, then the agent's
136
+ output as it works, until its session exits or Ctrl-C. It never types into the terminal and never keeps the computer
137
+ awake while the agent is idle.
138
+ - `shard conversations get <id>` prints one line per port a process listens on in the conversation's computer: the
139
+ port, the program, and its URL once exposed (or `127.0.0.1 only`). `--json` includes `pty_id`, `listening_ports` and
140
+ `listening_ports_at`.
141
+
142
+ - `shard conversations feed` (0.25.0+): each conversation of the project as it changes, until interrupted; `--json`
143
+ prints one `{conversation, cursor}` object per line, `--cursor` continues after one. `shard conversations list
144
+ --updated-after <time>` filters by `updated_at`.
145
+ - Conversation titles in human output are the agent's own short title (`title_short`) once it has one.
146
+
147
+ - `shard acp` opens a conversation by its id: `session/load` with a `conv_…` session id attaches an ACP client to that conversation's live session in its computer, with its history replayed by the agent and no local folder synced.
148
+
149
+ - `shard conversations transcript <id>` prints a conversation’s transcript, the agent’s own Claude Code or Codex JSONL byte for byte, for working, parked and finished conversations alike (`--offset`, `--session`; `--follow` keeps printing new lines). `shard conversations events <id>` follows it live: state changes, new transcript lines, tool calls and the reply as it streams; `--json` prints one event per line.
150
+
3
151
  ## 0.24.0
4
152
 
5
153
  - Finish all active device port-forward cleanup and close the local listener and cell before returning the original tunnel-close error. Repeated stop and abort keep the same error and promise.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @shardflux/cli: `shard`
2
2
 
3
+ For error codes, retry timing, and plan recovery, see the [error reference](https://docs.shardflux.dev/reference/errors).
4
+
3
5
  Historical template file lists: reads queue checksum-verified reconstruction when a cold list is absent from the SQL cache. TypeScript SDK 0.27.0+ / Python 0.23.0+ wait full Retry-After for retryable API 409 file_list_indexing on GET/HEAD within the admission timeout. Template objects and version history stay available.
4
6
 
5
7
  Plan-derived API budgets (0.23.0+, SDK 0.27.0): `--json` error output includes `rate_limit` and `retry_after_seconds`; the text hint names the shared scope and delay. API request pacing follows the SDK admission timeout and preserves the request body. Reuse workspace credentials and reduce status polling when the returned budget asks for a delay.
@@ -144,6 +146,21 @@ shard ./ --on fix-login --review # a read-only Claude reviewer of it
144
146
  the new thread. Codex threads are Codex Cloud threads, on the web and your phone too. `shard agents list` shows each
145
147
  thread's conversation and computer.
146
148
 
149
+ Start a conversation without a folder (0.25.0+): its repository is checked out from the project's saved branches, or
150
+ with `--home` a coordinator starts on your machine and spawns threads in the project's repositories.
151
+
152
+ ```sh
153
+ shard conversations start --prompt "Add CSV export to the invoices page" --file mockup.png
154
+ shard conversations start --home --title "Plan the billing work"
155
+ shard conversations send conv_6qz2dmu4kd3xa7v5l2hgmfnpjy "Here is the failing run" --file ci.log
156
+ ```
157
+
158
+ `--repo` and `--branch` choose the repository and the branch it starts from (default: the project's most recently
159
+ active repository, from `main`, then `master`); `--app`, `--model` and `--effort` work as for `shard ./`. It prints the
160
+ conversation's id, key and state; `shard agents open <id>` opens it. `--file` (repeatable, up to 20 files of 64 MiB
161
+ each) puts each file in the conversation's computer before the message and prints where it landed; `--json` returns
162
+ the stored paths.
163
+
147
164
  Choose the model and reasoning effort a new thread starts with (0.17.0+):
148
165
 
149
166
  ```sh
@@ -235,6 +252,8 @@ shard conversations try <conversation>
235
252
  Your branch opens in `.shard/worktrees/<name>`, syncs both ways, and its dev servers appear at localhost.
236
253
  Ctrl-C stops local sync. Your main checkout stays available for your own work.
237
254
  Device tools ask for approval on your computer before preparing the conversation’s local worktree. With CLI 0.15.0 and menu bar 0.3.0, Review shows the exact command, file direction, paths or port mapping; access outside the conversation folder only offers Allow once. Live transcripts stay in
255
+
256
+ From 0.25.0, an agent can also list a folder on your computer, make one of your projects from it, and move a file to or from anywhere on it by an absolute or `~/` path. Each asks first: **Allow once**, or **Always for this folder**, which covers that kind of request for that folder and everything inside it. `shard projects link <path> [--prompt <text>]` does the same from a terminal: the folder is prepared in the background, kept as this computer’s live copy, and `--prompt` starts a conversation there.
238
257
  `~/.config/shardflux/conversations/`; ended sessions enter Claude Code’s or Codex’s native history. Transcript files up to 64 MiB are supported (0.14.0+).
239
258
  From 0.14.3, transcript copying runs independently of device approvals. Unknown entries are skipped, and failed copies retry with backoff; diagnostics name the conversation and affected entry.
240
259
 
@@ -687,6 +706,8 @@ shows the status; `shard version --check` asks now (`--json`: `{"cli", "sdk", "u
687
706
  | `version [--check]` | Print the CLI and SDK versions and the update status (`--check` asks the API now). |
688
707
  | `usage [--org <id>] [--estimate \| --series \| --workspace <id\|key>] [--from T] [--to T] [--granularity hour\|day] [--meter M]` | Usage summary for the current period: plan, meters and allowances. `--estimate`: the bill so far and projected allowance use; `--series`: usage over time; `--workspace`: one workspace's usage over time (0.5.0+). The summary includes the [overage](#usage-and-overage) block (0.5.1+). |
689
708
  | `feedback [--yes] [--category C] [--workspace W] [--request-id ID] [--error-code CODE] [--command TEXT] [--from-file F\|-] <message...>` | Preview feedback; `--yes` sends after explicit user authorization (0.22.0+). See [Feedback](#feedback-preview-and-user-approval). |
709
+ | `route [--task ID] [--org O] [--project P] <text...>`, `ask [--task ID] [--org O] [--project P] <question...>` | The board's input bar (0.25.0+): where a line of input goes (its project or your home; a task, a question or a steer, split per task), and a quick question about your work, answered as it is written. |
710
+ | `conversations delete <id> --yes` (`c delete`) | Delete a conversation everywhere at once (0.25.0+): its agent stops, its computer is deleted, and its messages, transcript and branch go with it. `--json` prints `{id, deleted, deleted_at, operation}`. See [Conversations](#conversations). |
690
711
  | `workspaces open <key> --template <slug> [--secret NAME]... [--input NAME=value]... [--input-file F] [--lifetime persistent\|session] [--mode processful\|file-first] [--cpu-millis N --memory-mib N --disk-gib N] [--allocation-mode fixed\|elastic] [--memory-held N] [--no-wait] [--timeout 5m] [--timing]` | Open by key. Creates the workspace on first use and reconnects or resumes it afterwards, never resetting it. Waits until it is ready (the template's start commands and services included) unless you pass `--no-wait`. `--secret` binds secret names (replacing the binding of an existing workspace). `--input` (0.4.0+) passes the template's text inputs (replacing those of an existing workspace); `--input-file` reads `NAME=value` lines or a JSON object. `--lifetime session`: the workspace is discarded when the session ends (`workspaces close` or the idle timeout), and the key then opens a new workspace. `--mode file-first` (0.5.0+) opens a [file-first workspace](#file-first-workspaces); the default is processful for a new key and the stored mode for an existing one. `--allocation-mode elastic` (0.8.0+): [elastic memory](#elastic-memory-080). |
691
712
  | `workspaces list [--state S] [--prefix P] [--lifetime persistent\|session\|any] [--purpose standard\|template_draft\|template_test\|any] [--include-deleted] [--all] [--limit N] [--cursor C]` | One page, or every page with `--all`. The next cursor is printed on stderr. By default only persistent standard workspaces are listed. A file-first workspace's state reads `running (file-first)` (0.5.0+). |
692
713
  | `workspaces get <id\|key>` | One workspace (deleted ones included), with the startup of its template's start commands and services (0.4.0+): a failed step shows its exit code, reason and output tail. Its mode, and a file-first workspace's tree revision (0.5.0+). Its memory (0.8.0+): `fixed 4096 MiB`, or `elastic: promised …, held …, plugged +… MiB`. The template version whose [immutable paths](#immutable-paths-080) it has mounted (0.8.0+): `immutable paths v3 (mounted read-only)`. |
@@ -1446,7 +1467,7 @@ A message from claude.ai or the Claude app wakes a waiting Claude conversation a
1446
1467
 
1447
1468
  Run `shard ./` in your project to start Claude Code or Codex, usable immediately while setup runs in the background.
1448
1469
 
1449
- New project folders are ready to use: `shard ./` and `shard agents new` initialize Git on `main` when needed and create an empty initial commit (CLI 0.14.2+). Existing files and staged work stay yours; the initial commit contains no files. Repositories without commits keep their current branch. If Git identity is missing, that one commit uses `Shardflux <noreply@shardflux.dev>` without changing your Git configuration.
1470
+ New project folders are ready to use: `shard ./` and `shard agents new` initialize Git on `main` when needed (CLI 0.14.2+). From 0.25.0 a folder `shard` makes a repository of gets its files as the first commit (`.gitignore` respected; large media stay untracked), so a conversation's work comes back by fast-forward; a repository of your own without commits keeps its staged and untracked files behind an empty initial commit. Repositories without commits keep their current branch. If Git identity is missing, that one commit uses `Shardflux <noreply@shardflux.dev>` without changing your Git configuration.
1450
1471
 
1451
1472
  Machine saves require a machine source VM (`labels.shard_layer = "machine"`). On your computer, run
1452
1473
  `shard machine open --json`, make changes in that VM, then `shard machine save --wait`. The computer's CLI
@@ -1559,6 +1580,40 @@ account, make another one active first; your own you can always remove. `--json`
1559
1580
 
1560
1581
  From 0.14.0, `shard conversations list|get|send|wait|land|move` manages project conversations. `shard c` is the short form. Conversations retain their branch and transcript as their VM binding changes.
1561
1582
 
1583
+ Watch an agent work: `shard conversations watch <id>` shows the conversation's terminal live, read-only. It starts
1584
+ with the last 256 KiB the agent printed, then streams everything it prints, until its session exits or you press
1585
+ Ctrl-C. Nothing you type reaches the agent, and watching never keeps its computer awake while the agent is idle.
1586
+ `shard conversations get <id>` lists the servers running in the conversation's computer, one line per port, with
1587
+ each port's URL once exposed (`shard ws port expose`); `--json` returns them as `listening_ports`, and the terminal as
1588
+ `pty_id`.
1589
+
1590
+ ```sh
1591
+ shard conversations watch conv_a7k2m4q6s3t5v7x2z4b6d3f5h7
1592
+ shard conversations get conv_a7k2m4q6s3t5v7x2z4b6d3f5h7
1593
+ # Fix login
1594
+ # working
1595
+ # shard/fix-login
1596
+ # 5173 node https://5173-<handle>.shardflux.app (exposed)
1597
+ # 5432 postgres 127.0.0.1 only
1598
+ ```
1599
+
1600
+ `shard conversations transcript <id>` (0.25.0+) prints the conversation's transcript: the agent's own Claude Code or
1601
+ Codex JSONL, byte for byte, for a working, parked or finished conversation alike, without waking its VM. `--follow`
1602
+ keeps printing lines as the agent writes them; `--offset` starts at a byte offset and `--session` reads an earlier
1603
+ session. `shard conversations events <id>` (0.25.0+) follows the conversation live: its state, new transcript lines,
1604
+ tool calls and the reply as it streams; `--json` prints one event per line.
1605
+
1606
+ ```sh
1607
+ shard c transcript conv_k3m5q7s2u4w6y3b5d7f2h4j6ln > session.jsonl
1608
+ shard c transcript conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --follow | jq -c 'select(.type=="assistant")'
1609
+ shard c events conv_k3m5q7s2u4w6y3b5d7f2h4j6ln
1610
+ # state: working
1611
+ # Looking at the login test.
1612
+ # tool: Read login.test.ts (pending)
1613
+ # tool: Read login.test.ts (completed)
1614
+ # state: done
1615
+ ```
1616
+
1562
1617
 
1563
1618
  Desktop project connections (0.14.0+): `shard agents open codex-app <project>/<workspace>` and
1564
1619
  `shard agents open claude-desktop <project>/<workspace>` register **Shardflux · <project>** in the app.
@@ -1568,6 +1623,78 @@ Conversation JSON includes `join_mode` and the worktree used by reviewers and wo
1568
1623
 
1569
1624
  `shard conversations resume <id>` continues a saved conversation in a fresh VM under the same identity (0.14.0+). `shard ./ --resume` uses this same path. `shard conversations reader <project-id>` opens a small read-only workspace for saved branches and transcripts; the Mac transcript mirror opens it automatically when no project VM is live. Adaptive idle reclaims the reader.
1570
1625
 
1626
+ `shard conversations delete <id> --yes` (0.25.0+) deletes a conversation everywhere at once: its agent stops, its
1627
+ computer is deleted (a computer other conversations share stays for them), and its messages, transcript and branch go
1628
+ with it, on every device. From then on it leaves every list and reads not found; `shard c feed` shows it once as
1629
+ `deleted`. The person who started it and organization owners and admins delete conversations. `--json` prints the
1630
+ API's answer: `{id, deleted, deleted_at, operation}`, where `operation` is the deletion of its computer.
1631
+
1632
+ ```sh
1633
+ shard c delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes
1634
+ # Deleted: conv_k3m5q7s2u4w6y3b5d7f2h4j6ln
1635
+ ```
1636
+
1637
+ ### Route input and ask about your work (0.25.0+)
1638
+
1639
+ `shard route` reads a line of input the way the board's input bar does: which project it belongs to (or your home),
1640
+ whether it is a new task, a question or a steer for an open task, and each task in your own words when it asks for
1641
+ several. `shard ask` answers a quick question about your work from the board, printing the answer as it is written.
1642
+
1643
+ ```sh
1644
+ shard route fix the hero image on the landing page and add a GPU row to the pricing table
1645
+ # 2 tasks in site
1646
+ # 1. Fix the landing page hero image
1647
+ # fix the hero image on the landing page
1648
+ # 2. Add a GPU pricing row
1649
+ # add a GPU row to the pricing table
1650
+ shard route --task task_healthzhealthzhealthzhealt return 503 instead of 500 when the database is down
1651
+ # steer task_healthzhealthzhealthzhealt in status-board
1652
+ # return 503 instead of 500 when the database is down
1653
+ shard ask what is the healthz work doing right now
1654
+ # Your “Add /healthz endpoint” agent is working on shard/healthz with 2 commits and was last active 4 minutes ago. …
1655
+ ```
1656
+
1657
+ Signed in, both read every project of your organization (`--org <id|slug>` when you belong to several, `--project` to
1658
+ lean to one); with `SHARDFLUX_API_KEY` set they read that key's project. `--task` names the selected task. `--json`
1659
+ prints the API's object: the route (`kind`, `parts`, `task_ref`, `uncertain`, …) or the whole answer (`text`,
1660
+ `needs_work`, `sources`, …). An answer that needs work ends with "Make it a task?". Neither starts work or wakes a
1661
+ computer.
1662
+
1663
+ ### Transcribe a recording (0.25.0+)
1664
+
1665
+ `shard transcribe <file>` prints what a recording says. `--words` adds each word with its start and end; `--json`
1666
+ prints the transcript (`text`, `words` with `w`, `start` and `end` in seconds, `language`, `duration`).
1667
+
1668
+ ```sh
1669
+ shard transcribe note.webm
1670
+ # Take this sidebar, and move it over here, then make the header a bit taller, and change that color to blue.
1671
+ shard transcribe hello.wav --words
1672
+ # Move this button over here, then make that panel a little wider.
1673
+ # 0:00.00 0:00.18 Move
1674
+ # 0:00.18 0:00.44 this
1675
+ # …
1676
+ ```
1677
+
1678
+ WebM/Opus and PCM WAV are read from the file; raw 16-bit mono PCM takes `--format pcm16 --sample-rate 16000`.
1679
+ `--language en` names the language when you know it. Up to 120 s of audio. Signed in, or with `SHARDFLUX_API_KEY`.
1680
+
1681
+ ### Permission requests (0.25.0+)
1682
+
1683
+ When a conversation's agent asks before it runs a tool (Claude Code, Codex or an ACP agent), the conversation is
1684
+ `needs_you` and `shard conversations get` names what it waits on. Answer it from any terminal:
1685
+
1686
+ ```sh
1687
+ shard c permissions <conversation-id> --state pending
1688
+ # 01a1300c-… pending Run npm test npm test
1689
+ shard c answer <conversation-id> <permission-id> allow # or always, deny
1690
+ # Allowed once: Run npm test
1691
+ ```
1692
+
1693
+ Each line is the request id, its state, what the agent wants to do, the command, path or URL, and the decision once
1694
+ answered. Without `--state` every request is listed, oldest first; `--all` reads every page and `--json` prints `data`
1695
+ and `nextCursor`. `always` lets the agent's harness stop asking for this kind of call. The agent goes on with its turn
1696
+ as soon as you answer, and a parked computer wakes to take the answer.
1697
+
1571
1698
  ### Starting from your working state (0.22.0+)
1572
1699
 
1573
1700
  A new conversation starts from your worktree as it is: committed history, uncommitted edits, untracked files and your
@@ -1608,6 +1735,56 @@ With CLI 0.23.0+, a full conversation key or id opens that same Claude or Codex
1608
1735
 
1609
1736
  Use `shard agents open <workspace> --in auto --json` to resolve where a conversation opens: a saved target first; then, for Claude (0.15.1+), the conversation itself in Claude Desktop when it is installed (`{"target":"claude-desktop","url":"claude://code/session_…","app_bundle_id":…}`), else Claude Code in the terminal (`{"target":"terminal","command":["shard","agents","open","<workspace>"]}`; without `--json` it attaches in this terminal). Codex Cloud opens its current thread in your browser (CLI 0.23.0+). claude.ai opens with `--in web`. The caller opens the `url` or app, or runs the command in a terminal. Explicit `--in terminal` retains terminal attachment. The menu uses this resolver for new and existing conversations, without provider URLs in Swift.
1610
1737
 
1738
+ ## The board (0.25.0+)
1739
+
1740
+ The board is one list of what you asked your agents for, the same on every device: what waits on you first, then every
1741
+ task in Now, Next and Later, each in your own words. The agent that does the work seals a report with its evidence, and
1742
+ reading the board never wakes a computer. The board commands use your project API key: the board is its project's.
1743
+
1744
+ ```sh
1745
+ shard task add Fix the wake race in resume # your words, kept exactly; the project's master starts the work
1746
+ # task_h7g4d7tqyv2pz3a2npyo4rpgzq now queued
1747
+ shard board
1748
+ # Needs you
1749
+ # perm_01a1300c-… Run npm test? npm test
1750
+ #
1751
+ # Now
1752
+ # task_h7g4d7tqyv2pz3a2npyo4rpgzq working Fix the wake race in resume 01a1415f-…
1753
+ shard needs-you answer perm_01a1300c-… always # allow, always or deny
1754
+ # Always allowed: Run npm test (rule rule_ym4xywjgj7vvvrhkqlduotkvb4)
1755
+ shard task show task_h7g4d7tqyv2pz3a2npyo4rpgzq # the plan, then each report with its evidence
1756
+ shard board --follow # each change as it happens, until Ctrl-C
1757
+ ```
1758
+
1759
+ **Tasks.** `shard task add <words...>` adds a task to Now (`--next`, `--later`), last in its bucket unless `--first` or
1760
+ `--after <task-id>` places it; `--home` runs a personal task on the project's machine home. `shard task edit <id>`
1761
+ changes the words (`--text`, and the task's master hears it), the title (`--title`), the bucket (`--now`, `--next`,
1762
+ `--later`) and the order (`--first`, `--after`), keeps an agent's suggestion as your own (`--keep`) or clears the task
1763
+ (`--clear`; `--no-clear` brings it back). `shard task steer <id> <words...>` sends steering words to the task's master,
1764
+ `shard task rm <id>` deletes the task, and `shard task clear-done` clears every finished one. `shard task show <id>`
1765
+ prints the task, its plan and each report: milestone, outcome and evidence (`tests 12/12 (pnpm test)`).
1766
+ `shard task land <id>` lands a finished task: its result (the branch `shard/<task id>`, where its master merged its
1767
+ workers' branches) goes into the project's default branch and into your folder on this computer the way finished
1768
+ conversations land there (your uncommitted edits stay), and the board shows it shipped once the commit is in the
1769
+ branch: `Landed in ~/code/best-todo · 3f2a1c9`.
1770
+
1771
+ **Needs you.** `shard needs-you` lists permission requests, questions, credential requests, finished work to land and
1772
+ Later tasks to keep, oldest first. `shard needs-you answer <id>` answers any of them: `allow`, `always` (also adds the
1773
+ always-allow rule for the project; `--everywhere` for every project) or `deny` for a permission request, `--pick` for an
1774
+ agent's question in one, `--text` for a question, `land` (as `shard task land`), `keep` or `discard` for finished work, and `--keep <task-id>`
1775
+ (or `keep`, `discard`) for Later tasks. The agent goes on as soon as you answer.
1776
+
1777
+ **Approvals.** The approval mode answers your agents' permission requests before they reach you: `ask` sends every one
1778
+ to you, `routine` (the default) approves tests, builds, reads and installs, `trusted` approves everything except
1779
+ deploys, pushes to main and spending. `shard approvals` shows the modes and rules. `shard approvals set <mode>` sets the
1780
+ project's mode (`--project <id>`; `default` puts it back on the default mode), `shard approvals allow <tool>
1781
+ <pattern...>` always allows a tool for commands that start with the pattern (`shard approvals allow Bash pnpm test`),
1782
+ and `shard approvals remove <rule-id>` removes a rule. Requests already waiting that a new mode or rule covers are
1783
+ answered at once.
1784
+
1785
+ Each line is one item, tab-separated, id first. `--json` prints the API's objects; `shard board --follow --json` prints
1786
+ the board on its first line, then one feed answer per line.
1787
+
1611
1788
  ## Codex Cloud (0.15.0+)
1612
1789
 
1613
1790
  New Codex conversations use `shard agents new --app codex --path <directory>` or `shard ./ codex`. Each gets a fresh conversation VM and a genuine Codex Cloud thread. `shard agents open <conversation-id>` opens the existing thread without waking its VM. These paths create no SSH device entries and need no pairing.
@@ -1633,10 +1810,95 @@ Older clients read the same session response fields.
1633
1810
 
1634
1811
  Cloud history (0.23.0+): `shard agents open <conversation-id> --original` opens the original chat; `--history <thread-id>` opens an earlier linked chat. `--json` includes `previous_threads` with each history URL. Sign in with the Codex account that owns the selected chat.
1635
1812
 
1813
+ Codex accounts (0.25.0+): before opening a Cloud thread, `shard agents open` checks which ChatGPT account the Codex app on this computer is signed in to. When it is not the thread's account, it names the account to sign in as and prints the thread's web link. Earlier threads are listed with their accounts.
1814
+
1636
1815
  ## Complete conversation lists (0.23.1+)
1637
1816
 
1817
+ `shard conversations feed` (0.25.0+) prints each conversation of the project the moment it changes, until you stop it:
1818
+ `<id> <title> <state>`, or with `--json` one `{conversation, cursor}` object per line (`--cursor` continues after
1819
+ it). `shard conversations list --updated-after <time>` lists only conversations changed since then. Titles show the
1820
+ agent's own short name for a conversation once it has one (`title_short`).
1821
+
1822
+ Signed in (`shard auth login`) without an API key, `shard conversations list` and `shard conversations feed` (0.25.0+)
1823
+ cover every project you work in, across your organizations, with one cursor; `--org <id|slug>` narrows them to one
1824
+ organization and `--project` to one project. With an API key they cover the key's project, and `--org` uses your
1825
+ sign-in when you have one:
1826
+
1827
+ ```sh
1828
+ shard c list --org acme --state needs_you
1829
+ shard c feed --org acme --json
1830
+ ```
1831
+
1638
1832
  `shard conversations list --all` reads every page, keeping the selected project, state and parent filters. `--json` returns `data` and `nextCursor`. Linux desktop 0.1.11+ reads all conversation and computer pages and shows the selected project key’s organization, usage, spend and connector status in Settings.
1639
1833
 
1834
+ ## Project drive (0.25.0+)
1835
+
1836
+ `shard drive` keeps a project's files: the screenshots, logs, reports and notes your agents and people save and read by
1837
+ path, the same from a phone, a script or another agent whether the project's computers are working, asleep or gone.
1838
+ Paths are relative to the project space root, the names agents see under `/shardflux/project`
1839
+ (`tasks/t_ab12/evidence/healthz.png`, `BOARD.md`).
1840
+
1841
+ ```sh
1842
+ shard drive put ./out/healthz.png tasks/t_ab12/evidence/healthz.png
1843
+ # Saved tasks/t_ab12/evidence/healthz.png (48.2 KB, image/png)
1844
+ # sha256 <sha256>
1845
+ shard drive put BOARD.md # saved as BOARD.md
1846
+ git diff | shard drive put - tasks/t_ab12/review.diff
1847
+ shard drive ls tasks/t_ab12/
1848
+ # SIZE SAVED TYPE PATH
1849
+ # 48.2 KB 2026-10-15 23:13:47Z image/png tasks/t_ab12/evidence/healthz.png
1850
+ # 24 bytes 2026-10-15 23:13:48Z text/markdown; charset=utf-8 tasks/t_ab12/notes.md
1851
+ shard drive get tasks/t_ab12/evidence/healthz.png -o healthz.png
1852
+ shard drive get tasks/t_ab12/notes.md --sha256 <sha256> # exactly that version
1853
+ shard drive rm tasks/t_ab12/notes.md
1854
+ ```
1855
+
1856
+ - Every `put` is a new version named by the SHA-256 of its bytes, printed after the path; saving the bytes that are
1857
+ already current keeps that version. `get --sha256` reads any version, also after its path was overwritten or
1858
+ removed, so a report that names a file's sha256 always opens the bytes it was written with.
1859
+ - `get` writes the bytes to standard output, or to a file with `-o`, once they match their SHA-256; with `--json` it
1860
+ returns `content_base64` with `sha256`, `size` and `content_type`.
1861
+ - `put <local-file> [<drive-path>]`: the drive path defaults to the file's name, and `-` reads standard input. The
1862
+ type comes from the extension (`.png` image/png, `.md` text/markdown) unless `--content-type` names one. `--json`
1863
+ returns the saved `file`.
1864
+ - `ls [prefix]` lists the current files by path, 200 to a page (`--limit` up to 1000, `--cursor`, `--all`); `--json`
1865
+ returns `data` (each file with its `sha256`) and `next_cursor`.
1866
+ - The drive is the API key's project, or `--project <id>`. Signed in (`shard auth login`), `--project` takes any of
1867
+ your projects by id or slug, and without an API key the drive is your default project's.
1868
+
1869
+ ## Model template (0.25.0+)
1870
+
1871
+ `shard models` shows which harness, model and effort each role of a task runs with, from the Claude and Codex accounts
1872
+ your organization connected: master plans and coordinates, builder does the work, hard takes the hardest parts, light
1873
+ the small ones and reviewer reviews with a fresh context.
1874
+
1875
+ ```sh
1876
+ shard models
1877
+ # Primary: claude (connected first), secondary: codex
1878
+ # Connected: claude (max), codex (pro)
1879
+ #
1880
+ # ROLE HARNESS MODEL EFFORT WAITS UNTIL
1881
+ # master claude opus medium (plans high) -
1882
+ # builder claude opus high -
1883
+ # hard claude fable high -
1884
+ # light codex gpt-6.1-sol low -
1885
+ # reviewer codex gpt-6.1-sol medium -
1886
+ shard models primary codex # this project's primary
1887
+ shard models primary claude --org acme # the organization's default
1888
+ shard models primary clear
1889
+ ```
1890
+
1891
+ - A role's harness, model and effort go straight to `shard conversations start --app <harness> --model <model>
1892
+ --effort <effort>` (`default`: leave out `--effort`).
1893
+ - `--harness claude|codex` keeps a running task's master, builder and hard work on its provider; `--attempt <n>` (1 to
1894
+ 10) shows the template for a retry, on the other provider or one tier up.
1895
+ - A role whose account waits for its next usage window shows until when, and what the other provider would run now,
1896
+ to offer the person; nothing switches on its own.
1897
+ - The primary provider is the project's choice when it has one, else the organization's, else the provider connected
1898
+ first. `shard models primary` sets the project's (owners and admins, or the project's API key), and with `--org` the
1899
+ organization's default (owners and admins, signed in); `clear` removes the choice. `--json` returns the template:
1900
+ `primary`, `primary_source`, `secondary`, `connected` and `roles`.
1901
+
1640
1902
  ## Default shared volumes (0.24.0+)
1641
1903
 
1642
1904
  ```sh
@@ -269,6 +269,11 @@ export const ACCOUNT_COMMANDS = [
269
269
  options: { confirm: { type: 'string', value: '<slug>', description: 'The organization’s slug, typed out (required).' }, ...YES('the deletion') },
270
270
  },
271
271
  { path: ['projects', 'list'], summary: 'List the organization’s projects (* marks the default)', positionals: [], options: { ...ORG } },
272
+ { path: ['projects', 'link'], summary: 'Make a local folder one of your projects, prepared in the background and kept as this computer’s live copy (0.25.0+)', positionals: [{ name: 'path', required: true }], options: {
273
+ prompt: { type: 'string', value: '<text>', description: 'Also start a conversation in that folder with this first message.' },
274
+ 'prompt-file': { type: 'string', value: '<file|->', description: 'Read that first message from a UTF-8 file or stdin (-), up to 256 KiB.' },
275
+ app: { type: 'string', value: '<claude|codex>', description: 'Agent app of that conversation (default claude).' },
276
+ } },
272
277
  { path: ['projects', 'sync', 'list'], summary: 'Show selected local projects and their preparation state', positionals: [], options: {} },
273
278
  { path: ['projects', 'sync', 'add'], summary: 'Prepare and keep this selected local project synced', positionals: [{ name: 'path', required: true }], options: { wait: { type: 'boolean', description: 'Prepare now and wait for its published environment; otherwise the device helper prepares it in the background.' } } },
274
279
  { path: ['projects', 'sync', 'pause'], summary: 'Pause background preparation of this local project', positionals: [{ name: 'path', required: true }], options: {} },
@@ -2,6 +2,7 @@ import { out, resolveWorkspace } from "./commands.js";
2
2
  import { UsageError } from "./errors.js";
3
3
  import { claudeLoginUrls, requireClaudeTerminal, runWorkspaceLogin } from "./claude-cloud-login.js";
4
4
  import { openMachine } from "./filesystem-commands.js";
5
+ import { codexAppAccountNotice } from "./codex-cloud.js";
5
6
  const PROVIDERS = { claude: 'Claude', codex: 'ChatGPT / Codex' };
6
7
  const WINDOW_LABELS = { five_hour: '5-hour', seven_day: 'Weekly', seven_day_opus: 'Weekly Opus', seven_day_sonnet: 'Weekly Sonnet', primary: 'Primary', secondary: 'Secondary' };
7
8
  /** A window's length as a title, as Claude's windows read: "Weekly", "5-hour", "2-day", "90-minute". */
@@ -245,7 +246,10 @@ export const ACCOUNT_POOL_HANDLERS = {
245
246
  const follows = account.provider === 'claude'
246
247
  ? 'New conversations start with it; running ones continue on it, and Remote Control turns back on by itself.'
247
248
  : `New conversations and every running conversation VM use it; Codex Cloud moves to it${previous ? ` (Cloud threads started on ${name(previous)} stay in that ChatGPT account)` : ''}.`;
248
- out(ctx, previous || !isActive(account) ? `${name(active)} is the active ${PROVIDERS[account.provider]} account. ${follows}\n` : `${name(active)} is already the active ${PROVIDERS[account.provider]} account.\n`, { ...list, account: active });
249
+ // codex_app (0.25.0+): the Codex app here opens the account's Cloud threads only once it is signed in to it.
250
+ const app = account.provider === 'codex' ? await codexAppAccountNotice(ctx, active) : null;
251
+ out(ctx, (previous || !isActive(account) ? `${name(active)} is the active ${PROVIDERS[account.provider]} account. ${follows}\n` : `${name(active)} is already the active ${PROVIDERS[account.provider]} account.\n`)
252
+ + (app?.line ? `${app.line}\n` : ''), { ...list, account: active, ...(account.provider === 'codex' ? { codex_app: app ? { login: app.login, notice: app.line } : null } : {}) });
249
253
  return 0;
250
254
  },
251
255
  };
@@ -90,6 +90,28 @@ export class AcpJournal {
90
90
  }
91
91
  #wanted() { return !this.#signal.aborted && !this.rpc.closed && this.#paused === 0 && (this.rpc.busy || this.#cursor.prompt_id !== undefined); }
92
92
  async #deliver(inputId, message) {
93
+ let failures = 0;
94
+ const started = performance.now();
95
+ while (true) {
96
+ this.#signal.throwIfAborted();
97
+ try {
98
+ return await this.#deliverOnce(inputId, message);
99
+ }
100
+ catch (error) {
101
+ // Guest restarts can refuse a new input command while its native
102
+ // supervisor keeps running. Its persisted receipt deduplicates this
103
+ // exact input id, including a command accepted before the refusal.
104
+ // An unverified receipt or a permanent error still stops delivery.
105
+ if (!(error instanceof ShardfluxApiError) || !error.retryable)
106
+ throw error;
107
+ const remaining = RECONNECT_BUDGET_MS - (performance.now() - started);
108
+ if (remaining <= 0)
109
+ throw new Error('ACP input reconnect timed out; load this session to continue.', { cause: error });
110
+ await delay(Math.min(reattachDelayMs(error, ++failures), remaining), undefined, { signal: this.#signal });
111
+ }
112
+ }
113
+ }
114
+ async #deliverOnce(inputId, message) {
93
115
  const dir = `/home/user/.local/share/shardflux/acp/${this.#conversation}`;
94
116
  const payload = Buffer.from(`{"mode":"input","input_id":${JSON.stringify(inputId)},"message":${message}}\n`);
95
117
  if (payload.length <= ONE_SHOT_INPUT) {
@@ -133,7 +155,7 @@ export class AcpJournal {
133
155
  finally {
134
156
  await this.#cell.exec.cancel(session.session_id, 0).catch(() => undefined);
135
157
  }
136
- // Never resend an uncertain mutation. Its stable input identity is fenced by CVB before provider stdin.
158
+ // Retries retain inputId: CVB persists its receipt before provider stdin.
137
159
  }
138
160
  start() {
139
161
  this.#initial ??= this.#replay().then(() => { this.#ready = true; this.#follow(); }).catch(error => { this.rpc.close(error instanceof Error ? error : new Error(String(error))); throw error; });
@@ -0,0 +1,38 @@
1
+ import type { ConversationPermission } from '@shardflux/sdk';
2
+ import type { ObjectValue } from './acp-rpc.js';
3
+ /** The part of the SDK client the watch reads: GET /v1/conversations/{id}/permissions (0.28.0+). */
4
+ export interface PermissionsReader {
5
+ conversations: {
6
+ permissions(id: string, p: {
7
+ state?: ConversationPermission['state'];
8
+ limit?: number;
9
+ cursor?: string;
10
+ }): Promise<{
11
+ data: ConversationPermission[];
12
+ nextCursor: string | null;
13
+ }>;
14
+ permission(id: string, permissionId: string): Promise<ConversationPermission>;
15
+ };
16
+ }
17
+ /** How often the watch reads the API while the editor's permission prompt is open (it never touches the VM). */
18
+ export declare const ELSEWHERE_POLL_MS = 1500;
19
+ /** The tool and command, path or URL the guest records for an ACP permission request (services/guest-agent ACPPermissionAsk). */
20
+ export declare function requestIdentity(params: ObjectValue): {
21
+ tool: string | null;
22
+ detail: string;
23
+ };
24
+ /**
25
+ * While the editor shows the agent's permission prompt, the journal relay is paused, so nothing else of the turn reaches
26
+ * the editor. The same request is listed through the API, where a person can answer it too (the Shard client, the CLI,
27
+ * MCP). This resolves with the request once it leaves `pending` that way (answered through the API, or closed because
28
+ * the agent no longer waits), so the relay can withdraw the editor's prompt and go on with the turn. The request is the
29
+ * newest one of the conversation with the prompt's tool and command, path or URL that was reported as the prompt opened
30
+ * or still waits. It resolves null on an API without permission requests (404, 503) or when `signal` ends the prompt;
31
+ * other errors are retried.
32
+ */
33
+ export declare function answeredElsewhere(client: PermissionsReader, conversationId: string, params: ObjectValue, openedAt: number, signal: AbortSignal, pollMs?: number): Promise<ConversationPermission | null>;
34
+ /**
35
+ * The editor-side answer matching a request answered elsewhere: the option of its decision's kind (as the guest picks
36
+ * it), or a cancelled outcome. The agent already has the first answer; this one only completes the relayed request.
37
+ */
38
+ export declare function outcomeFor(p: ConversationPermission, params: ObjectValue): ObjectValue;