@shardflux/cli 0.28.0 → 0.28.1
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/CHANGELOG.md +41 -17
- package/README.md +76 -11
- package/dist/account-commands.d.ts +2 -4
- package/dist/account-commands.js +2 -6
- package/dist/agent-git-commands.d.ts +1 -2
- package/dist/agent-git-commands.js +2 -21
- package/dist/agent-setup.d.ts +4 -2
- package/dist/agent-setup.js +6 -4
- package/dist/agent-shell.d.ts +2 -0
- package/dist/agent-shell.js +5 -0
- package/dist/args.d.ts +1 -1
- package/dist/args.js +1 -1
- package/dist/cli-proxy.d.ts +1 -13
- package/dist/cli-proxy.js +2 -27
- package/dist/codex-cloud.js +3 -1
- package/dist/device-bridge.d.ts +4 -1
- package/dist/device-bridge.js +9 -2
- package/dist/format.js +3 -0
- package/dist/forwarder-refresh.d.ts +15 -0
- package/dist/forwarder-refresh.js +32 -0
- package/dist/handler-loader.d.ts +3 -0
- package/dist/handler-loader.js +43 -0
- package/dist/land.d.ts +1 -1
- package/dist/land.js +6 -5
- package/dist/linux-helper.js +1 -2
- package/dist/machine-setup-guard.d.ts +9 -0
- package/dist/machine-setup-guard.js +22 -0
- package/dist/machine-setup.d.ts +1 -8
- package/dist/machine-setup.js +2 -22
- package/dist/main.js +17 -16
- package/dist/orchestrator-commands.js +1 -3
- package/dist/startup-housekeeping.d.ts +5 -0
- package/dist/startup-housekeeping.js +30 -0
- package/dist/t3-provider.js +4 -2
- package/dist/worktree-twin-engine.d.ts +1 -1
- package/dist/worktree-twin-engine.js +9 -6
- package/package.json +12 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- The Shardflux app 0.9.2 (0.28.1+): `shard` installs and updates `@shardflux/desktop-darwin-arm64` / `-x64` and
|
|
6
|
+
`@shardflux/desktop-linux-x64` / `-arm64` 0.9.2 (Stop in ⌘1, organization choice at the first sign-in, Sign in after
|
|
7
|
+
signing out).
|
|
8
|
+
|
|
9
|
+
- Return the coding-agent setup guide without loading the account command handlers.
|
|
10
|
+
|
|
11
|
+
- Manual landing uses the result’s own ready computer or the persistent project reader, so deleting an unrelated thread does not interrupt it.
|
|
12
|
+
|
|
13
|
+
- Check T3 presence directly during startup, avoiding unrelated desktop-app probes before every command.
|
|
14
|
+
|
|
15
|
+
- Orchestrator inputs preserve long pastes, including their final instruction, without an application character cap. `shard orchestrator say` sends the full text to the API.
|
|
16
|
+
|
|
17
|
+
- Land a finished conversation in its own repository's linked folder when a project holds several repositories.
|
|
18
|
+
Preserve the hub's default branch when it belongs to another repository.
|
|
19
|
+
|
|
20
|
+
- Load only the selected command and skip optional setup modules when their local state is absent.
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
- Load interactive command surfaces on demand, keeping help/version and simple commands fast while preserving command parsing, saved-work housekeeping, setup guards and install registration.
|
|
24
|
+
|
|
25
|
+
- Codex Cloud admission (0.28.1+, SDK 0.30.1): a stopped startup/open reports its stored reason and recovery instruction immediately (`codex_admission_stopped`), instead of calling it pending or pointing only at connector status.
|
|
26
|
+
|
|
27
|
+
- Stored-file recovery (0.28.1+): workspace views show when files are saved for recovery; resume restarts the workspace from those files.
|
|
28
|
+
|
|
5
29
|
- `shard orchestrator say` (0.28.0+, SDK 0.30.0): every input waits its turn in order,
|
|
6
30
|
however many wait (no more 429), and one of your orchestrator's threads hears each input at once, all in parallel: a
|
|
7
31
|
follow-up reaches the thread of the request it is about (the input reads merged), the row you point at reaches the
|
|
@@ -17,22 +41,22 @@
|
|
|
17
41
|
- Restore an orchestrator input (0.28.0+, SDK 0.30.0): `shard orchestrator restore <input-id>` brings an input you
|
|
18
42
|
dismissed back into `shard orchestrator list` on every device and prints `Restored: <your words>` (`--json`:
|
|
19
43
|
`{input}`). Restoring an input that is not dismissed changes nothing.
|
|
20
|
-
- Orchestrator answer media (0.
|
|
44
|
+
- Orchestrator answer media (0.28.0+, SDK 0.29.0): `shard orchestrator say --wait` prints a line for each picture,
|
|
21
45
|
video or file the answer shows, after its points: ` media: loss-curve.png (image, 1200×800, 84.0 KB)` (the size in
|
|
22
46
|
pixels once measured). `--json` adds the answer's `media`; `shard drive get <file.path> --sha256 <file.sha256>` reads
|
|
23
47
|
one.
|
|
24
48
|
|
|
25
|
-
- Orchestrator report cards (0.
|
|
49
|
+
- Orchestrator report cards (0.28.0+, SDK 0.29.0): `shard orchestrator say --wait` prints an answer the orchestrator
|
|
26
50
|
wrote as a report as its verdict, then its points under their group headings, one line each:
|
|
27
51
|
`- Loss is down to 1.84: from 2.31 at step 5,000 (14:02)`, the time short and in this computer's local time; a point
|
|
28
52
|
written without a lead prints its words. Every point, source and follow-up prints whole, however long. An answer
|
|
29
53
|
written as facts prints its facts as before. `--json` adds the answer's `points`.
|
|
30
54
|
|
|
31
|
-
- Board content at any length (0.
|
|
55
|
+
- Board content at any length (0.28.0+, SDK 0.29.0): `shard task show` prints a task's title, its plan steps and each
|
|
32
56
|
report's title and outcome whole, however long the agent wrote them (the terminal wraps them); `shard task edit
|
|
33
57
|
--title` takes a title of any length.
|
|
34
58
|
|
|
35
|
-
- Your brain (0.
|
|
59
|
+
- Your brain (0.28.0+, SDK 0.29.0): `shard brain ls [prefix]` lists your brain's files (size, saved time, type and
|
|
36
60
|
path; `--all`, `--limit`, `--cursor`; `--json` adds `next_cursor` and the brain's `version`) and `shard brain get
|
|
37
61
|
<path>` writes a file's bytes to standard output, to a file with `-o`, or as `--json` (`--sha256` reads exactly that
|
|
38
62
|
version). Your brain is your archive in an organization, kept by your orchestrator: its published answers as
|
|
@@ -58,18 +82,18 @@
|
|
|
58
82
|
the one that branch holds, or two of its workers were), with what to do instead; nothing moves on the project hub or
|
|
59
83
|
in your folder. It was a Git error ("refusing to merge unrelated histories") that read as if trying again could help.
|
|
60
84
|
|
|
61
|
-
- Auto land switch (0.
|
|
85
|
+
- Auto land switch (0.28.0+): `shard settings --auto-land on|off` turns automatic landing on or off for every project
|
|
62
86
|
on this computer (on by default). Off, the live twin lands no conversation and new conversations start from your
|
|
63
87
|
HEAD; `shard task land` and `shard conversations land` still land on request. `shard settings` shows it and `--json`
|
|
64
88
|
adds `auto_land`. The Shardflux app reads the same switch to land finished board tasks with no click.
|
|
65
89
|
|
|
66
|
-
- Message delivery (0.
|
|
90
|
+
- Message delivery (0.28.0+, SDK 0.29.0): `shard conversations send` reaches a working agent in its running turn
|
|
67
91
|
(Claude Code, Codex and Codex Cloud) and starts an idle one's next turn; its help says so instead of "next turn".
|
|
68
92
|
`--wait` waits up to 60 s and prints what happened: `Steered into the running turn.`, `Started the next turn.` or
|
|
69
93
|
`Not delivered yet.` (`Delivery not confirmed yet: …` when Codex Cloud has not confirmed it); `--json` adds
|
|
70
94
|
`delivery`. Without `--wait` the output stays `Message sent (<seq>).`
|
|
71
95
|
|
|
72
|
-
- One Shardflux app (0.
|
|
96
|
+
- One Shardflux app (0.28.0+): `shard` installs and updates the one desktop app (menu bar or tray, onboarding,
|
|
73
97
|
launcher, conversations with their computers, device approvals, one Settings window) from
|
|
74
98
|
`@shardflux/desktop-darwin-arm64` / `-x64` 0.9.0 on macOS (replacing `@shardflux/menubar-darwin`) and from
|
|
75
99
|
`@shardflux/desktop-linux-x64` / `-arm64` 0.9.0 on Linux (replacing the GTK tray app), at the same places as before
|
|
@@ -84,31 +108,31 @@
|
|
|
84
108
|
executable behind the app's launcher and names each library the computer is missing.
|
|
85
109
|
- 0.27.0 includes the changes prepared as 0.26.0 (not released on its own): the entries marked 0.27.0 below.
|
|
86
110
|
|
|
87
|
-
- Device registration (0.
|
|
111
|
+
- Device registration (0.28.0+): isolate each project, back off transient failures and stop after eight attempts with
|
|
88
112
|
an actionable needs-attention message. A device id another sign-in registered (the API key `shard setup` replaced,
|
|
89
113
|
or another account) is replaced by a new one (at most once every 10 minutes), with one line in the helper's log, and every project
|
|
90
114
|
registers again at once, so your agents reach this computer without a restart. The old id is kept in
|
|
91
115
|
`device-id-history.json`.
|
|
92
|
-
- Your agents on this computer (0.
|
|
116
|
+
- Your agents on this computer (0.28.0+): `shard device approvals auto|read|ask` chooses how this computer answers
|
|
93
117
|
your own agents: `auto` runs everything they ask without asking, `read` runs folder listings and files copied to a
|
|
94
118
|
conversation and asks before anything else (commands, files saved here, ports, linking a folder), `ask` asks every
|
|
95
119
|
time. The first request asks for the choice (the Shardflux app's prompt; without the app, `shard` prints the two
|
|
96
120
|
commands once) and runs under the mode chosen. A teammate's request always asks. What ran without asking is listed
|
|
97
121
|
by `shard device approvals` (`--json`: `recent`) and the app's Settings. Approvals carry `choose_mode: true` while
|
|
98
122
|
nothing is chosen.
|
|
99
|
-
- First prompts (0.
|
|
123
|
+
- First prompts (0.28.0+): a Codex Cloud start queues its initial prompt while the executor prepares. Resuming an accepted start uses the same prompt key; an unconfirmed send keeps its created conversation.
|
|
100
124
|
|
|
101
|
-
- Device helper (0.
|
|
125
|
+
- Device helper (0.28.0+): a command your orchestrator asks your computer to run (a home conversation, so no folder of
|
|
102
126
|
it on this computer) asks you once with the exact command and folder ("Run on your Mac: sh -c tail -n 5 train.log ·
|
|
103
127
|
in ~/code/lm-train"), Allow once or Deny, and runs in your home folder or the ~/ folder it names. A request whose
|
|
104
128
|
conversation has no folder on this computer is refused with the reason instead of staying pending.
|
|
105
|
-
- Filesystem recovery (0.
|
|
129
|
+
- Filesystem recovery (0.28.0+): resume JSON/timing keeps `cold_boot_reason: guest_filesystem`; the existing note reports restarted processes. Ships with SDK 0.29.0.
|
|
106
130
|
|
|
107
|
-
- Admission queue waits (0.
|
|
131
|
+
- Admission queue waits (0.28.0+, SDK 0.29.0): workspace opens retry `429 admission_queued` within the admission timeout with the same body/key, honor full `Retry-After`, and display `queued for admission; retrying in 2 s` while waiting.
|
|
108
132
|
- 0.27.0: workspace disk caps report the admitted and acknowledged size. Completed disk grows refresh the disk grant; resize results retain the original request.
|
|
109
133
|
|
|
110
134
|
|
|
111
|
-
- `shard orchestrator` (0.
|
|
135
|
+
- `shard orchestrator` (0.28.0+, SDK 0.29.0): your orchestrator agent from a terminal. `shard orchestrator say <words>`
|
|
112
136
|
sends your exact words (`--task`, `--report`, `--needs-you`, `--input`, `--conversation` point at a row) and prints
|
|
113
137
|
the input id and its place in line; every input is handled in parallel by its own thread. `--wait` follows it until
|
|
114
138
|
it is done, merged, failed or waiting and prints its curated result: the answer's headline and facts with their
|
|
@@ -118,13 +142,13 @@
|
|
|
118
142
|
<input-id>` sends a failed one again and `shard orchestrator dismiss <input-id>` takes one out of the list. Signed in
|
|
119
143
|
they reach your orchestrator; with `SHARDFLUX_API_KEY`, the orchestrator of the person who created the key. `--json`
|
|
120
144
|
prints `{input}` / `{inputs, cursor}`.
|
|
121
|
-
- `shard needs-you` (0.
|
|
145
|
+
- `shard needs-you` (0.28.0+, SDK 0.29.0) lists your orchestrator's questions (`oq_…`, answered with `--text`) and what
|
|
122
146
|
it asked your computer while it was offline (`dev_…`, "Waiting for your computer: list ~/code"; `shard needs-you
|
|
123
147
|
answer dev_… cancel` drops it).
|
|
124
148
|
- Deprecated (0.27.0): `shard route` (POST /v1/router) and `shard ask` (POST /v1/questions); use the orchestrator
|
|
125
149
|
instead (`shard orchestrator say`). Each prints a one-line notice on stderr.
|
|
126
150
|
|
|
127
|
-
- `shard agents models` (0.
|
|
151
|
+
- `shard agents models` (0.28.0+): the models each active account can use, as Claude and ChatGPT list them for that
|
|
128
152
|
account (asked server side the way Claude Code and Codex fill their pickers; never starts a computer): effort levels
|
|
129
153
|
with the default starred, the default model, and unavailable models with the provider's reason. `--account <ref>`,
|
|
130
154
|
`--provider`, `--all`, `--refresh`; `--json` prints the API answer. `--model` on `shard agents new` (and `shard ./`)
|
|
@@ -140,7 +164,7 @@
|
|
|
140
164
|
- `shard transcribe <file>` (0.25.0+, SDK 0.28.0) prints what a recording says (WebM/Opus or PCM WAV from the file;
|
|
141
165
|
raw PCM with `--format pcm16 --sample-rate`); `--words` adds each word's start and end, `--json` prints the
|
|
142
166
|
transcript. Where cloud transcription is not on for the organization it says so in one line
|
|
143
|
-
(`cloud_transcription_off`) and exits 1 (0.
|
|
167
|
+
(`cloud_transcription_off`) and exits 1 (0.28.0+, SDK 0.29.0).
|
|
144
168
|
- 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.
|
|
145
169
|
- A folder without Git that `shard` makes a repository of (a first chat, `shard projects link`) starts with its files as
|
|
146
170
|
the first commit (0.25.0+; `.gitignore` respected, large media stay untracked), so a conversation starts from the
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @shardflux/cli: `shard`
|
|
2
2
|
|
|
3
|
+
Use `shard --help` or `shard <command> --help` to explore commands before signing in.
|
|
4
|
+
|
|
3
5
|
For error codes, retry timing, and plan recovery, see the [error reference](https://docs.shardflux.dev/reference/errors).
|
|
4
6
|
|
|
5
7
|
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.
|
|
@@ -127,8 +129,8 @@ Model choices come from your cloud agent's session and use the same coding tools
|
|
|
127
129
|
Project files synchronize before each message and before its completion (0.22.0+): review remote edits in T3’s
|
|
128
130
|
Changes panel and checkpoints, and send local edits to the next turn. Conflicting versions are retained with
|
|
129
131
|
a `.shard-conflict-<agent>` suffix. Sync runs between turns; an idle T3 thread needs no file watcher.
|
|
130
|
-
|
|
131
|
-
(0.17.0+). Choose **Sign in** on its card in T3's provider settings: T3 runs `shard acp --login` in its own terminal,
|
|
132
|
+
At command startup, Shardflux checks for T3 Code and registers the provider automatically on macOS and on Linux
|
|
133
|
+
from its .deb or AppImage (0.17.0+). Choose **Sign in** on its card in T3's provider settings: T3 runs `shard acp --login` in its own terminal,
|
|
132
134
|
which creates or signs in to your account (with this computer's Codex login, or your email) and connects Claude Code
|
|
133
135
|
and Codex, skipping every step that is already done (0.15.0+). Editors that launch agents from the ACP registry
|
|
134
136
|
run the same sign-in as `shard --login` (0.22.0+).
|
|
@@ -305,6 +307,11 @@ shard conversations landing # auto (default), pr or off for this proj
|
|
|
305
307
|
shard conversations landing backups # then: shard conversations landing restore <backup>
|
|
306
308
|
```
|
|
307
309
|
|
|
310
|
+
To land a finished conversation manually, run `shard conversations land <id> merge`. In a project with several
|
|
311
|
+
repositories it chooses a linked folder of that conversation's repository; the hub's default branch for another
|
|
312
|
+
repository stays where it is. `--path <folder>` chooses the destination explicitly. From 0.28.1, manual landing
|
|
313
|
+
uses the result’s own ready computer or the project reader, so deleting an unrelated thread does not interrupt it.
|
|
314
|
+
|
|
308
315
|
For application workspace commands, `shard signup` creates an account and saves a project key, or set
|
|
309
316
|
`SHARDFLUX_API_KEY` to a key from the console and run `shard login`. `shard --help` lists every command.
|
|
310
317
|
|
|
@@ -735,8 +742,10 @@ shows the status; `shard version --check` asks now (`--json`: `{"cli", "sdk", "u
|
|
|
735
742
|
| `version [--check]` | Print the CLI and SDK versions and the update status (`--check` asks the API now). |
|
|
736
743
|
| `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+). |
|
|
737
744
|
| `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). |
|
|
738
|
-
| `orchestrator say [--wait [--timeout D]] [--task ID\|--report ID\|--needs-you ID\|--input ID\|--conversation ID] <words...>`, `orchestrator list [--all]`, `orchestrator retry\|dismiss <input-id>` | Your orchestrator (0.28.0+): say something and every input is handled at once by its own thread; `--wait` prints its curated result. See [Your orchestrator](#your-orchestrator-
|
|
745
|
+
| `orchestrator say [--wait [--timeout D]] [--task ID\|--report ID\|--needs-you ID\|--input ID\|--conversation ID] <words...>`, `orchestrator list [--all]`, `orchestrator retry\|dismiss\|restore <input-id>` | Your orchestrator (0.28.0+): say something and every input is handled at once by its own thread; `--wait` prints its curated result. See [Your orchestrator](#your-orchestrator-0280). |
|
|
746
|
+
| `brain ls [prefix] [--org O] [--all] [--limit N] [--cursor C]`, `brain get <path> [--org O] [--sha256 HEX] [-o FILE]` | Your brain (0.28.0+): your orchestrator's answers, reports and notes, read from any device. See [Your brain](#your-brain-0280). |
|
|
739
747
|
| `route [--task ID] [--org O] [--project P] <text...>`, `ask [--task ID] [--org O] [--project P] <question...>` | Deprecated; use `shard orchestrator say`. The board's input bar (0.25.0+): where a line of input goes, and a quick question about your work. |
|
|
748
|
+
| `conversations archive\|unarchive\|seen <id...>` (`c archive`) | Your inbox, the same on every device (0.28.0+): archive conversations, bring them back, or mark them seen. `--json` prints `{data, skipped}`. See [Conversations](#conversations). |
|
|
740
749
|
| `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). |
|
|
741
750
|
| `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). |
|
|
742
751
|
| `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+). |
|
|
@@ -1671,6 +1680,24 @@ Conversation JSON includes `join_mode` and the worktree used by reviewers and wo
|
|
|
1671
1680
|
|
|
1672
1681
|
`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.
|
|
1673
1682
|
|
|
1683
|
+
Your inbox is the same on every device (0.28.0+): `shard conversations archive <id...>` archives conversations from
|
|
1684
|
+
your own inbox, `shard conversations unarchive <id...>` brings them back, and `shard conversations seen <id...>` records
|
|
1685
|
+
that you opened them now, so a result that finished before then is no longer new to you in the Shardflux app or any
|
|
1686
|
+
other client. Name one conversation or many in one command. Only your view changes: the conversations, their agents
|
|
1687
|
+
and computers carry on, and everyone else keeps their own inbox. `--json` prints the API's answer, `{data, skipped}`:
|
|
1688
|
+
the conversations with your `seen_at` and `dismissed_at`, and the ids not found. `shard c get --json` and
|
|
1689
|
+
`shard c list --json` carry the same two fields.
|
|
1690
|
+
|
|
1691
|
+
```sh
|
|
1692
|
+
shard c archive conv_k3m5q7s2u4w6y3b5d7f2h4j6ln conv_a7k2m4q6s3t5v7x2z4b6d3f5h7
|
|
1693
|
+
# Archived conv_k3m5q7s2u4w6y3b5d7f2h4j6ln Fix login
|
|
1694
|
+
# Archived conv_a7k2m4q6s3t5v7x2z4b6d3f5h7 Upgrade the test runner
|
|
1695
|
+
shard c unarchive conv_a7k2m4q6s3t5v7x2z4b6d3f5h7
|
|
1696
|
+
# Unarchived conv_a7k2m4q6s3t5v7x2z4b6d3f5h7 Upgrade the test runner
|
|
1697
|
+
shard c seen conv_k3m5q7s2u4w6y3b5d7f2h4j6ln
|
|
1698
|
+
# Marked seen conv_k3m5q7s2u4w6y3b5d7f2h4j6ln Fix login
|
|
1699
|
+
```
|
|
1700
|
+
|
|
1674
1701
|
`shard conversations delete <id> --yes` (0.25.0+) deletes a conversation everywhere at once: its agent stops, its
|
|
1675
1702
|
computer is deleted (a computer other conversations share stays for them), and its messages, transcript and branch go
|
|
1676
1703
|
with it, on every device. From then on it leaves every list and reads not found; `shard c feed` shows it once as
|
|
@@ -1684,6 +1711,8 @@ shard c delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes
|
|
|
1684
1711
|
|
|
1685
1712
|
### Your orchestrator (0.28.0+)
|
|
1686
1713
|
|
|
1714
|
+
`shard orchestrator say` passes your words to the API as one input, without cutting the final instruction.
|
|
1715
|
+
|
|
1687
1716
|
Your orchestrator is your own agent on top of every project, on its own Shardflux computer and the same on every
|
|
1688
1717
|
device. `shard orchestrator say` sends it your exact words and answers at once with the input and its place in line;
|
|
1689
1718
|
every input is handled in parallel by its own thread, which answers it, puts tasks on the board, asks you something or
|
|
@@ -1691,25 +1720,33 @@ adds it to an input in flight. `--wait` follows it and prints what it became:
|
|
|
1691
1720
|
|
|
1692
1721
|
```sh
|
|
1693
1722
|
shard orchestrator say --wait Lets check on our language model training progress
|
|
1694
|
-
# Training is 73% done,
|
|
1695
|
-
#
|
|
1696
|
-
# -
|
|
1697
|
-
# -
|
|
1723
|
+
# Training is 73% done, on pace to finish at 16:40
|
|
1724
|
+
# Moved
|
|
1725
|
+
# - Loss is down to 1.84: from 2.31 at step 5,000 (14:02)
|
|
1726
|
+
# - Checkpoint saved: at step 7,000
|
|
1727
|
+
# Still open
|
|
1728
|
+
# - Eval has not run: it starts after the last checkpoint
|
|
1729
|
+
# media: loss-curve.png (image, 1200×800, 84.0 KB)
|
|
1698
1730
|
# Sources: train.log (~/code/lm-train/train.log), wandb.ai (https://wandb.ai/acme/lm)
|
|
1699
1731
|
# Follow-ups: Check again in an hour
|
|
1700
1732
|
shard orchestrator say --task task_h7g4d7tqyv2pz3a2npyo4rpgzq use postgres instead
|
|
1701
1733
|
# in_q3vd5k2xbnwjm7e4rtyhcu6aio queued (1 in line)
|
|
1702
1734
|
shard orchestrator list
|
|
1703
1735
|
# in_q3vd5k2xbnwjm7e4rtyhcu6aio working use postgres instead
|
|
1704
|
-
# in_7wcx227ovjpei4u6i47llu7w4i done Lets check on our language model training progress Training is 73% done,
|
|
1736
|
+
# in_7wcx227ovjpei4u6i47llu7w4i done Lets check on our language model training progress Training is 73% done, on pace to finish at 16:40
|
|
1705
1737
|
```
|
|
1706
1738
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1739
|
+
The answer card is the verdict, then its points under the headings the orchestrator chose (a point's time in your
|
|
1740
|
+
local time; an answer written as plain facts prints its facts), then a media line for each picture, video or file it
|
|
1741
|
+
shows (an image with its width×height once measured). Each is a file on the project drive: `--json` has its
|
|
1742
|
+
`file.path` and `file.sha256`, and `shard drive get <path> --sha256 <sha256> -o <file>` saves it. `--task`, `--report`, `--needs-you`, `--input`
|
|
1743
|
+
and `--conversation` point at the row your words are about. `--wait` stops when the input is done (its answer card and
|
|
1744
|
+
the tasks it created), merged (the input it was added to), waiting
|
|
1709
1745
|
(the line it waits on; a question names the `shard needs-you answer oq_… --text <answer>` that answers it) or failed
|
|
1710
1746
|
(exit 6; `shard orchestrator retry <input-id>` sends it again); `--timeout` (default 10m) stops following with exit 5
|
|
1711
1747
|
while the input goes on. `shard orchestrator list` shows your recent inputs newest first (`--all` adds the ones you
|
|
1712
|
-
dismissed with `shard orchestrator dismiss`)
|
|
1748
|
+
dismissed with `shard orchestrator dismiss`); `shard orchestrator restore <input-id>` (0.28.0+) brings one back on every
|
|
1749
|
+
device. Signed in, they reach your orchestrator (`--org <id|slug>` when you belong
|
|
1713
1750
|
to several); with `SHARDFLUX_API_KEY` set, the orchestrator of the person who created that key. `--json` prints the
|
|
1714
1751
|
API's objects (`{input}`, `{inputs, cursor}`).
|
|
1715
1752
|
|
|
@@ -1977,6 +2014,32 @@ shard drive rm tasks/t_ab12/notes.md
|
|
|
1977
2014
|
- The drive is the API key's project, or `--project <id>`. Signed in (`shard auth login`), `--project` takes any of
|
|
1978
2015
|
your projects by id or slug, and without an API key the drive is your default project's.
|
|
1979
2016
|
|
|
2017
|
+
## Your brain (0.28.0+)
|
|
2018
|
+
|
|
2019
|
+
`shard brain` reads your brain: your archive in an organization, kept by your orchestrator. Every answer it publishes is
|
|
2020
|
+
filed as `reports/<YYYY-MM-DD>/<folder>/report.md` with the answer's media next to it (image thumbnails under
|
|
2021
|
+
`.thumbs/`), with its own notes and files and an `index.md` of everything, newest first. Your orchestrator writes it;
|
|
2022
|
+
you read it from any device, with every computer asleep. Paths are relative to the brain's root, the names `~/brain`
|
|
2023
|
+
shows on your orchestrator's computer.
|
|
2024
|
+
|
|
2025
|
+
```sh
|
|
2026
|
+
shard brain get index.md
|
|
2027
|
+
shard brain ls reports/2026-10-17/
|
|
2028
|
+
# SIZE SAVED TYPE PATH
|
|
2029
|
+
# 195.3 KB 2026-10-17 09:12:40Z image/png reports/2026-10-17/training-progress-ab12cd34/loss.png
|
|
2030
|
+
# 1.2 KB 2026-10-17 09:12:41Z text/markdown reports/2026-10-17/training-progress-ab12cd34/report.md
|
|
2031
|
+
shard brain get reports/2026-10-17/training-progress-ab12cd34/report.md
|
|
2032
|
+
shard brain get reports/2026-10-17/training-progress-ab12cd34/loss.png -o loss.png
|
|
2033
|
+
```
|
|
2034
|
+
|
|
2035
|
+
- `ls [prefix]` lists the current files by path, 200 to a page (`--limit` up to 1000, `--cursor`, `--all`); `--json`
|
|
2036
|
+
returns `data` (each file with its `sha256`), `next_cursor` and the brain's `version`, which moves whenever anything
|
|
2037
|
+
in it changes.
|
|
2038
|
+
- `get` writes the bytes to standard output, or to a file with `-o`, once they match their SHA-256; `--sha256` reads
|
|
2039
|
+
exactly that version, and `--json` returns `content_base64` with `sha256`, `size` and `content_type`.
|
|
2040
|
+
- Signed in, it is your brain in your saved organization (`shard orgs use`), else your only one, or `--org <id|slug>`;
|
|
2041
|
+
with `SHARDFLUX_API_KEY` set, the brain of the person who created that key.
|
|
2042
|
+
|
|
1980
2043
|
## Model template (0.25.0+)
|
|
1981
2044
|
|
|
1982
2045
|
`shard models` shows which harness, model and effort each role of a task runs with, from the Claude and Codex accounts
|
|
@@ -2021,3 +2084,5 @@ shard volumes detach --machine datasets
|
|
|
2021
2084
|
```
|
|
2022
2085
|
|
|
2023
2086
|
Defaults mount in new and running conversations. Removal waits for current work to finish and preserves manual attachments. Add `--json` to read propagation results.
|
|
2087
|
+
|
|
2088
|
+
Bare `shard` in a coding-agent shell or without a TTY returns its setup guide using locally saved sign-in and API-key state.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import type {
|
|
1
|
+
export { agentSetupGuide } from './agent-setup.js';
|
|
2
|
+
import type { Handler } from './commands.js';
|
|
3
3
|
export declare const DEFAULT_API_URL = "https://api.shardflux.dev";
|
|
4
4
|
/** What `shard setup` gave a key before 0.15.1: every tool but `computer`. Such a saved key is replaced with a full one. */
|
|
5
5
|
export declare function outdatedDefaultKey(permissions: readonly string[]): boolean;
|
|
@@ -18,6 +18,4 @@ export declare function exportLine(shell: ShellKind, name: string, value: string
|
|
|
18
18
|
export declare function parseTime(text: string, option: string, now?: number): string;
|
|
19
19
|
/** A spend cap in dollars ("9", "9.5", "9.50", "$9") as minor units (cents); at most two decimals. */
|
|
20
20
|
export declare function dollarsToMinor(text: string, what: string): number;
|
|
21
|
-
/** The setup guide for this computer: whether a session and an API key are saved (no network call). */
|
|
22
|
-
export declare function agentSetupGuide(ctx: Ctx): ReturnType<typeof setupGuide>;
|
|
23
21
|
export declare const ACCOUNT_HANDLERS: Record<string, Handler>;
|
package/dist/account-commands.js
CHANGED
|
@@ -21,7 +21,8 @@ import { API_KEY_TOOL_PERMISSIONS, CheckoutTimeoutError, Shardflux, ShardfluxAcc
|
|
|
21
21
|
import { CLI_VERSION, UUID, parseDuration, parsePositiveInt } from "./args.js";
|
|
22
22
|
import { openUrl } from "./browser.js";
|
|
23
23
|
import { defaultClientName, startDeviceAuthorization, waitForDeviceSession } from "./browser-login.js";
|
|
24
|
-
import {
|
|
24
|
+
import { agentSetupGuide, setupGuideText } from "./agent-setup.js";
|
|
25
|
+
export { agentSetupGuide } from "./agent-setup.js";
|
|
25
26
|
import { allOrgs, allProjects, byRef, choices, findOrg, gone, keyOrganizationWarning, keyScope, resolveOrg, resolveProject, staleDefaultOrg } from "./account-scope.js";
|
|
26
27
|
import { readFileSync, rmSync } from 'node:fs';
|
|
27
28
|
import { join } from 'node:path';
|
|
@@ -768,11 +769,6 @@ function wantsBrowser(ctx, values) {
|
|
|
768
769
|
return true;
|
|
769
770
|
return ctx.input.interactive && !ctx.json;
|
|
770
771
|
}
|
|
771
|
-
/** The setup guide for this computer: whether a session and an API key are saved (no network call). */
|
|
772
|
-
export function agentSetupGuide(ctx) {
|
|
773
|
-
const profile = ctx.store.profile(ctx.apiUrl);
|
|
774
|
-
return setupGuide({ signedIn: ctx.sessionSource() !== null, email: profile.session?.user?.email ?? null, apiKey: profile.api_key !== undefined || Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim()) });
|
|
775
|
-
}
|
|
776
772
|
/**
|
|
777
773
|
* After `orgs use` / `projects use` (0.20.2+): the defaults moved, but workspace, agent and usage commands use the API
|
|
778
774
|
* key, which may act in another organization. Says so; on a terminal offers to make a key for the new default (a
|
|
@@ -6,5 +6,4 @@ export declare function rememberGitAgent(ctx: Ctx, ws: Workspace, folder: string
|
|
|
6
6
|
export declare const AGENT_GIT_HANDLERS: Record<string, Handler>;
|
|
7
7
|
/** No Git or network work when no records are due. Active passes are bounded and never infer deletion from an error. */
|
|
8
8
|
export declare function housekeeping(ctx: HousekeepingCtx, now?: () => number, budgetMs?: number): Promise<void>;
|
|
9
|
-
|
|
10
|
-
export declare function startupHousekeeping(ctx: Pick<Ctx, 'io' | 'store' | 'signal'>): Promise<void>;
|
|
9
|
+
export { startupHousekeeping } from './startup-housekeeping.js';
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
-
import {
|
|
3
|
+
import { ShardfluxApiError } from '@shardflux/sdk';
|
|
4
4
|
import { out, resolveWorkspace, str } from "./commands.js";
|
|
5
5
|
import { AgentStateStore } from "./agent-state.js";
|
|
6
|
-
import { resolveApiUrl } from "./args.js";
|
|
7
6
|
import { AgentGit, agentName } from "./agent-git.js";
|
|
8
7
|
import { shardRemote } from "./git-remote.js";
|
|
9
8
|
import { projectIdentity } from "./project-identity.js";
|
|
@@ -274,22 +273,4 @@ export async function housekeeping(ctx, now = Date.now, budgetMs = 1500) {
|
|
|
274
273
|
clearTimeout(timer);
|
|
275
274
|
}
|
|
276
275
|
}
|
|
277
|
-
|
|
278
|
-
export async function startupHousekeeping(ctx) {
|
|
279
|
-
try {
|
|
280
|
-
const state = new AgentStateStore(ctx.store.dir).read();
|
|
281
|
-
if (!state.git_repositories || !Object.keys(state.git_repositories).length)
|
|
282
|
-
return;
|
|
283
|
-
const apiUrl = resolveApiUrl(undefined, ctx.io.env.SHARDFLUX_API_URL ?? ctx.io.env.SHARDFLUX_BASE_URL);
|
|
284
|
-
let cloud;
|
|
285
|
-
await housekeeping({ ...ctx, apiUrl, cloud: () => {
|
|
286
|
-
const key = ctx.io.env.SHARDFLUX_API_KEY?.trim() || ctx.store.profile(apiUrl).api_key?.token;
|
|
287
|
-
if (!key)
|
|
288
|
-
throw new UsageError('saved agents need a project key for housekeeping; run shard setup');
|
|
289
|
-
return cloud ??= new Shardflux({ apiKey: key, baseUrl: apiUrl, versionCheck: false });
|
|
290
|
-
} });
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
ctx.io.stderr.write(`shard housekeeping: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
276
|
+
export { startupHousekeeping } from "./startup-housekeeping.js";
|
package/dist/agent-setup.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import type { Ctx } from './commands.js';
|
|
1
2
|
/**
|
|
2
3
|
* Set in a coding agent's shell: Claude Code (CLAUDECODE), Codex (CODEX_SANDBOX, CODEX_THREAD_ID), Cursor's agent
|
|
3
4
|
* (CURSOR_AGENT) and Gemini CLI (GEMINI_CLI). Such a shell may have a terminal (Cursor and Codex can run commands in a
|
|
4
5
|
* PTY), but nobody types into it: a full-screen view or a prompt there waits forever.
|
|
5
6
|
*/
|
|
6
|
-
export
|
|
7
|
-
export declare function codingAgentShell(env: Record<string, string | undefined>): boolean;
|
|
7
|
+
export { AGENT_SHELL_VARIABLES, codingAgentShell } from './agent-shell.js';
|
|
8
8
|
/**
|
|
9
9
|
* How the agent runs the setup (0.20.1+): it does the work, the person only decides. Onboarding testers (2026-10-12)
|
|
10
10
|
* found agents handing them commands, links and codes, writing paragraphs and asking open questions.
|
|
@@ -46,4 +46,6 @@ export interface GuideState {
|
|
|
46
46
|
apiKey: boolean;
|
|
47
47
|
}
|
|
48
48
|
export declare function setupGuide(state: GuideState): SetupGuide;
|
|
49
|
+
/** The setup guide for this computer: whether a session and an API key are saved (no network call). */
|
|
50
|
+
export declare function agentSetupGuide(ctx: Ctx): ReturnType<typeof setupGuide>;
|
|
49
51
|
export declare function setupGuideText(g: SetupGuide): string;
|
package/dist/agent-setup.js
CHANGED
|
@@ -18,10 +18,7 @@ import { legalFields } from "./legal.js";
|
|
|
18
18
|
* (CURSOR_AGENT) and Gemini CLI (GEMINI_CLI). Such a shell may have a terminal (Cursor and Codex can run commands in a
|
|
19
19
|
* PTY), but nobody types into it: a full-screen view or a prompt there waits forever.
|
|
20
20
|
*/
|
|
21
|
-
export
|
|
22
|
-
export function codingAgentShell(env) {
|
|
23
|
-
return AGENT_SHELL_VARIABLES.some((name) => (env[name]?.trim() ?? '') !== '');
|
|
24
|
-
}
|
|
21
|
+
export { AGENT_SHELL_VARIABLES, codingAgentShell } from "./agent-shell.js";
|
|
25
22
|
/**
|
|
26
23
|
* How the agent runs the setup (0.20.1+): it does the work, the person only decides. Onboarding testers (2026-10-12)
|
|
27
24
|
* found agents handing them commands, links and codes, writing paragraphs and asking open questions.
|
|
@@ -137,6 +134,11 @@ function stepText(n, s) {
|
|
|
137
134
|
...(s.note ? [` ${wrap(s.note, ' ', 96)}`] : []),
|
|
138
135
|
].join('\n');
|
|
139
136
|
}
|
|
137
|
+
/** The setup guide for this computer: whether a session and an API key are saved (no network call). */
|
|
138
|
+
export function agentSetupGuide(ctx) {
|
|
139
|
+
const profile = ctx.store.profile(ctx.apiUrl);
|
|
140
|
+
return setupGuide({ signedIn: ctx.sessionSource() !== null, email: profile.session?.user?.email ?? null, apiKey: profile.api_key !== undefined || Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim()) });
|
|
141
|
+
}
|
|
140
142
|
export function setupGuideText(g) {
|
|
141
143
|
return [
|
|
142
144
|
'Set up Shardflux for your user. How:',
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Identify an agent shell before loading onboarding or command handlers.
|
|
2
|
+
export const AGENT_SHELL_VARIABLES = ['CLAUDECODE', 'CODEX_SANDBOX', 'CODEX_THREAD_ID', 'CURSOR_AGENT', 'GEMINI_CLI'];
|
|
3
|
+
export function codingAgentShell(env) {
|
|
4
|
+
return AGENT_SHELL_VARIABLES.some((name) => (env[name]?.trim() ?? '') !== '');
|
|
5
|
+
}
|
package/dist/args.d.ts
CHANGED
package/dist/args.js
CHANGED
|
@@ -15,7 +15,7 @@ import { BOARD_COMMANDS } from "./board-args.js";
|
|
|
15
15
|
import { ORCHESTRATOR_COMMANDS } from "./orchestrator-args.js";
|
|
16
16
|
import { UsageError } from "./errors.js";
|
|
17
17
|
import { DEFAULT_CHOICE, MODEL_CHOICES } from "./agent-models.js";
|
|
18
|
-
export const CLI_VERSION = '0.28.
|
|
18
|
+
export const CLI_VERSION = '0.28.1';
|
|
19
19
|
export const GLOBAL_OPTIONS = {
|
|
20
20
|
'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
|
|
21
21
|
json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
|
package/dist/cli-proxy.d.ts
CHANGED
|
@@ -16,8 +16,7 @@ export declare const cliProxyTools: {
|
|
|
16
16
|
inVm: () => boolean;
|
|
17
17
|
kill: (pid: number) => void;
|
|
18
18
|
};
|
|
19
|
-
|
|
20
|
-
export declare const FORWARD_LABEL = "dev.shardflux.cli-proxy-forward";
|
|
19
|
+
export { FORWARD_LABEL, forwarderPlistPath, refreshForwarderAgent } from './forwarder-refresh.js';
|
|
21
20
|
/** Where every VM finds the hub info (the machine template carries it). */
|
|
22
21
|
export declare const VM_HUB_INFO_PATH = "/home/user/.config/shardflux-cli-proxy.json";
|
|
23
22
|
export interface LocalLogin {
|
|
@@ -151,7 +150,6 @@ export interface PlistOptions {
|
|
|
151
150
|
}
|
|
152
151
|
/** The launchd agent: `shard cli-proxy forward` at login, started again whenever it exits. */
|
|
153
152
|
export declare function forwarderPlist(o: PlistOptions): string;
|
|
154
|
-
export declare function forwarderPlistPath(home: string): string;
|
|
155
153
|
/** Whether Shardflux's forwarder answers on 127.0.0.1:port, and for which hub. */
|
|
156
154
|
export interface ForwarderIdentity {
|
|
157
155
|
hub: string | null; /** `mac`: shard cli-proxy forward; `vm`: sf-agent cli-proxy-forward. */
|
|
@@ -168,16 +166,6 @@ export interface LaunchdDeps {
|
|
|
168
166
|
identity?: () => Promise<ForwarderIdentity | null>;
|
|
169
167
|
sleep?: (ms: number) => Promise<void>;
|
|
170
168
|
}
|
|
171
|
-
/**
|
|
172
|
-
* After `shard` is updated, the launchd forwarder still runs the program it started with. Called on every shard run
|
|
173
|
-
* (one file read on macOS): an installed forwarder agent recorded for another shard version is started again, on this
|
|
174
|
-
* one. Returns whether it was.
|
|
175
|
-
*/
|
|
176
|
-
export declare function refreshForwarderAgent(env: Record<string, string | undefined>, dir: string, deps?: {
|
|
177
|
-
run?: ProgramRunner;
|
|
178
|
-
uid?: number;
|
|
179
|
-
platform?: NodeJS.Platform;
|
|
180
|
-
}): Promise<boolean>;
|
|
181
169
|
/** Installs (or refreshes) the launchd agent and waits until the forwarder answers. macOS only. */
|
|
182
170
|
export declare function installForwarderAgent(ctx: Ctx, apiUrl: string, deps?: LaunchdDeps): Promise<string>;
|
|
183
171
|
/**
|
package/dist/cli-proxy.js
CHANGED
|
@@ -47,7 +47,8 @@ export const cliProxyTools = {
|
|
|
47
47
|
kill: (pid) => { process.kill(pid, 'SIGTERM'); },
|
|
48
48
|
};
|
|
49
49
|
/** The launchd agent that keeps the forwarder on 127.0.0.1:8317 on a Mac. */
|
|
50
|
-
|
|
50
|
+
import { FORWARD_LABEL, forwarderPlistPath, recordForwarder } from "./forwarder-refresh.js";
|
|
51
|
+
export { FORWARD_LABEL, forwarderPlistPath, refreshForwarderAgent } from "./forwarder-refresh.js";
|
|
51
52
|
/** Where every VM finds the hub info (the machine template carries it). */
|
|
52
53
|
// Outside ~/.config/shardflux, which machines and templates never capture (the platform's per-VM state).
|
|
53
54
|
export const VM_HUB_INFO_PATH = '/home/user/.config/shardflux-cli-proxy.json';
|
|
@@ -470,9 +471,6 @@ ${Object.entries(env).map(([k, v]) => ` <key>${k}</key>\n <string>${xml(v)
|
|
|
470
471
|
</plist>
|
|
471
472
|
`;
|
|
472
473
|
}
|
|
473
|
-
export function forwarderPlistPath(home) {
|
|
474
|
-
return join(home, 'Library', 'LaunchAgents', `${FORWARD_LABEL}.plist`);
|
|
475
|
-
}
|
|
476
474
|
/** Whether Shardflux's forwarder (this computer's, or a VM's sf-agent) answers on 127.0.0.1:port, and for which hub. */
|
|
477
475
|
export async function forwarderIdentity(port = CLI_PROXY_PORT) {
|
|
478
476
|
try {
|
|
@@ -492,29 +490,6 @@ export function inShardfluxVm() {
|
|
|
492
490
|
}
|
|
493
491
|
/** The forwarder that answers is this shard's (an update replaces the program, not the running process). */
|
|
494
492
|
const current = (id) => id !== null && id.kind === 'mac' && id.version === CLI_VERSION;
|
|
495
|
-
/** The shard version the launchd forwarder runs (refreshForwarderAgent compares it with this one). */
|
|
496
|
-
function recordForwarder(dir) {
|
|
497
|
-
new AgentStateStore(dir).update((doc) => { doc.cli_proxy_forwarder = { version: CLI_VERSION }; });
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* After `shard` is updated, the launchd forwarder still runs the program it started with. Called on every shard run
|
|
501
|
-
* (one file read on macOS): an installed forwarder agent recorded for another shard version is started again, on this
|
|
502
|
-
* one. Returns whether it was.
|
|
503
|
-
*/
|
|
504
|
-
export async function refreshForwarderAgent(env, dir, deps = {}) {
|
|
505
|
-
if ((deps.platform ?? process.platform) !== 'darwin')
|
|
506
|
-
return false;
|
|
507
|
-
if (!existsSync(forwarderPlistPath(env.HOME || homedir())))
|
|
508
|
-
return false;
|
|
509
|
-
const store = new AgentStateStore(dir);
|
|
510
|
-
const record = store.read().cli_proxy_forwarder;
|
|
511
|
-
if (record?.version === CLI_VERSION)
|
|
512
|
-
return false;
|
|
513
|
-
// Recorded first: a failed restart is not retried on every command (the next import installs the agent again).
|
|
514
|
-
recordForwarder(dir);
|
|
515
|
-
await (deps.run ?? cliProxyTools.run)('launchctl', ['kickstart', '-k', `gui/${deps.uid ?? userInfo().uid}/${FORWARD_LABEL}`]);
|
|
516
|
-
return true;
|
|
517
|
-
}
|
|
518
493
|
/** Installs (or refreshes) the launchd agent and waits until the forwarder answers. macOS only. */
|
|
519
494
|
export async function installForwarderAgent(ctx, apiUrl, deps = {}) {
|
|
520
495
|
const runtime = ctx.runtime;
|
package/dist/codex-cloud.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import { ShardfluxApiError, codexAccountMatches, localCodexLogin } from '@shardflux/sdk';
|
|
3
|
+
import { CodexAdmissionStoppedError, ShardfluxApiError, codexAccountMatches, localCodexLogin } from '@shardflux/sdk';
|
|
4
4
|
import { CodedError, UsageError } from "./errors.js";
|
|
5
5
|
import { json } from "./format.js";
|
|
6
6
|
import { syncAgentCredentials } from "./agent-credentials.js";
|
|
@@ -311,6 +311,8 @@ export async function openCodexConversation(ctx, c, history) {
|
|
|
311
311
|
if (task.state !== 'canceled')
|
|
312
312
|
task = await ctx.cloud().codex.waitTask(c.id, { timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal });
|
|
313
313
|
}
|
|
314
|
+
if (task.state === 'attention' && task.next_attempt_at === null)
|
|
315
|
+
throw new CodexAdmissionStoppedError(task);
|
|
314
316
|
if (task.state === 'canceled')
|
|
315
317
|
throw new UsageError('This conversation’s Codex executor was removed. Start a new Cloud conversation; its saved branch and history remain available.');
|
|
316
318
|
if (task.state !== 'active' || !task.thread_id)
|
package/dist/device-bridge.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { CellClient, Workspace } from '@shardflux/sdk';
|
|
|
2
2
|
import type { Ctx } from './commands.js';
|
|
3
3
|
import type { Conversation } from './conversations.js';
|
|
4
4
|
import type { DirListing, LinkedProject } from './device-folders.js';
|
|
5
|
+
import type { HubSeed } from './project-hub-seed.js';
|
|
5
6
|
export type DeviceChoice = 'once' | 'always' | 'deny';
|
|
6
7
|
export interface DeviceRequest {
|
|
7
8
|
id: string;
|
|
@@ -121,7 +122,9 @@ export declare function deviceExec(argv: string[], cwd: string, timeoutSeconds?:
|
|
|
121
122
|
export type DeviceAsk = (approval: DeviceApproval, signal?: AbortSignal) => Promise<DeviceChoice>;
|
|
122
123
|
export declare class DeviceExecutor {
|
|
123
124
|
#private;
|
|
124
|
-
constructor(ctx: Ctx, ask: DeviceAsk, deviceId: string
|
|
125
|
+
constructor(ctx: Ctx, ask: DeviceAsk, deviceId: string, options?: {
|
|
126
|
+
seedHub?: (ctx: Ctx, folder: string, projectId: string) => Promise<HubSeed>;
|
|
127
|
+
});
|
|
125
128
|
/** A new device id for a new sign-in (device-helper.ts): requests for the old one are no longer this device's. */
|
|
126
129
|
set deviceId(id: string);
|
|
127
130
|
get deviceId(): string;
|
package/dist/device-bridge.js
CHANGED
|
@@ -229,7 +229,14 @@ export class DeviceExecutor {
|
|
|
229
229
|
/** Requests that run without asking (device-mode.ts), until their outcome is recorded. */
|
|
230
230
|
#auto = new Map();
|
|
231
231
|
#identity = { at: 0 };
|
|
232
|
-
|
|
232
|
+
/** How an agent's folder link reaches the project hub (project-hub-seed.ts unless a test gives its own hub). */
|
|
233
|
+
#seedHub;
|
|
234
|
+
constructor(ctx, ask, deviceId, options = {}) {
|
|
235
|
+
this.#ctx = ctx;
|
|
236
|
+
this.#ask = ask;
|
|
237
|
+
this.#deviceId = deviceId;
|
|
238
|
+
this.#seedHub = options.seedHub;
|
|
239
|
+
}
|
|
233
240
|
/** A new device id for a new sign-in (device-helper.ts): requests for the old one are no longer this device's. */
|
|
234
241
|
set deviceId(id) { this.#deviceId = id; this.#identity = { at: 0 }; }
|
|
235
242
|
get deviceId() { return this.#deviceId; }
|
|
@@ -578,7 +585,7 @@ export class DeviceExecutor {
|
|
|
578
585
|
if (r.kind === 'dir_list')
|
|
579
586
|
result = { state: 'done', dir: await listDeviceDir(target, r.dir ?? {}, ctx.signal) };
|
|
580
587
|
else if (r.kind === 'project_link')
|
|
581
|
-
result = { state: 'done', project: await linkLocalProject(ctx, target, { ...(r.link?.brief ? { brief: r.link.brief } : {}), harness: r.link?.harness ?? current.harness, projectId: current.project_id, seed: true }) };
|
|
588
|
+
result = { state: 'done', project: await linkLocalProject(ctx, target, { ...(r.link?.brief ? { brief: r.link.brief } : {}), harness: r.link?.harness ?? current.harness, projectId: current.project_id, seed: true }, undefined, this.#seedHub) };
|
|
582
589
|
else {
|
|
583
590
|
const f = r.files;
|
|
584
591
|
if (!f.remote_path.startsWith('/') || /[\0\r\n]/.test(f.remote_path))
|
package/dist/format.js
CHANGED
|
@@ -83,6 +83,8 @@ export function workspaceState(w) {
|
|
|
83
83
|
tags.push('session');
|
|
84
84
|
if (w.deleted_at)
|
|
85
85
|
tags.push(w.ended_reason ? `ended: ${w.ended_reason}` : 'deleted');
|
|
86
|
+
if (w.storage_recovery)
|
|
87
|
+
tags.push('files stored');
|
|
86
88
|
return tags.length ? `${w.observed_state} (${tags.join(', ')})` : w.observed_state;
|
|
87
89
|
}
|
|
88
90
|
export function workspaceRow(w) {
|
|
@@ -230,6 +232,7 @@ export function workspaceDetail(w) {
|
|
|
230
232
|
['id', w.id],
|
|
231
233
|
['key', w.workspace_key],
|
|
232
234
|
['state', `${w.observed_state} (desired ${w.desired_state})`],
|
|
235
|
+
...(w.storage_recovery ? [['storage', w.storage_recovery.message]] : []),
|
|
233
236
|
['ready', isReady(w) ? 'yes' : 'no'],
|
|
234
237
|
['template', `${w.template.slug} v${w.template.version} (${w.template.version_id})`],
|
|
235
238
|
// Immutable paths (0.8.0): the template version whose immutable paths the workspace has mounted, when it has one.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProgramRunner } from './cli-proxy.js';
|
|
2
|
+
export declare const FORWARD_LABEL = "dev.shardflux.cli-proxy-forward";
|
|
3
|
+
export declare function forwarderPlistPath(home: string): string;
|
|
4
|
+
/** The shard version the launchd forwarder runs (refreshForwarderAgent compares it with this one). */
|
|
5
|
+
export declare function recordForwarder(dir: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* After `shard` is updated, the launchd forwarder still runs the program it started with. Called on every shard run
|
|
8
|
+
* (one file read on macOS): an installed forwarder agent recorded for another shard version is started again, on this
|
|
9
|
+
* one. Returns whether it was.
|
|
10
|
+
*/
|
|
11
|
+
export declare function refreshForwarderAgent(env: Record<string, string | undefined>, dir: string, deps?: {
|
|
12
|
+
run?: ProgramRunner;
|
|
13
|
+
uid?: number;
|
|
14
|
+
platform?: NodeJS.Platform;
|
|
15
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { homedir, userInfo } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { AgentStateStore } from "./agent-state.js";
|
|
5
|
+
import { CLI_VERSION } from "./args.js";
|
|
6
|
+
export const FORWARD_LABEL = 'dev.shardflux.cli-proxy-forward';
|
|
7
|
+
export function forwarderPlistPath(home) {
|
|
8
|
+
return join(home, 'Library', 'LaunchAgents', `${FORWARD_LABEL}.plist`);
|
|
9
|
+
}
|
|
10
|
+
/** The shard version the launchd forwarder runs (refreshForwarderAgent compares it with this one). */
|
|
11
|
+
export function recordForwarder(dir) {
|
|
12
|
+
new AgentStateStore(dir).update((doc) => { doc.cli_proxy_forwarder = { version: CLI_VERSION }; });
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* After `shard` is updated, the launchd forwarder still runs the program it started with. Called on every shard run
|
|
16
|
+
* (one file read on macOS): an installed forwarder agent recorded for another shard version is started again, on this
|
|
17
|
+
* one. Returns whether it was.
|
|
18
|
+
*/
|
|
19
|
+
export async function refreshForwarderAgent(env, dir, deps = {}) {
|
|
20
|
+
if ((deps.platform ?? process.platform) !== 'darwin')
|
|
21
|
+
return false;
|
|
22
|
+
if (!existsSync(forwarderPlistPath(env.HOME || homedir())))
|
|
23
|
+
return false;
|
|
24
|
+
const store = new AgentStateStore(dir);
|
|
25
|
+
const record = store.read().cli_proxy_forwarder;
|
|
26
|
+
if (record?.version === CLI_VERSION)
|
|
27
|
+
return false;
|
|
28
|
+
// Recorded first: a failed restart is not retried on every command (the next import installs the agent again).
|
|
29
|
+
recordForwarder(dir);
|
|
30
|
+
await (deps.run ?? (await import("./cli-proxy.js")).cliProxyTools.run)('launchctl', ['kickstart', '-k', `gui/${deps.uid ?? userInfo().uid}/${FORWARD_LABEL}`]);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Exact command ownership, in the same override order as HANDLERS. The parity test catches additions
|
|
2
|
+
// or moves without loading every command module during an ordinary command.
|
|
3
|
+
const groups = [
|
|
4
|
+
[["projects sync list", "projects sync add", "projects sync pause", "projects sync remove", "projects sync run"], async () => (await import("./project-preparation.js")).PROJECT_PREPARATION_HANDLERS],
|
|
5
|
+
[["projects link"], async () => (await import("./device-folders.js")).PROJECT_LINK_HANDLERS],
|
|
6
|
+
[["move"], async () => (await import("./move.js")).MOVE_HANDLERS],
|
|
7
|
+
[["device approvals", "device requests", "device approve", "device deny"], async () => (await import("./device-approvals.js")).DEVICE_HANDLERS],
|
|
8
|
+
[["agents codex status", "agents codex enable", "agents codex disable"], async () => (await import("./codex-cloud.js")).CODEX_CLOUD_HANDLERS],
|
|
9
|
+
[["cli-proxy", "cli-proxy import", "cli-proxy export", "cli-proxy use", "cli-proxy forward"], async () => (await import("./cli-proxy.js")).CLI_PROXY_HANDLERS],
|
|
10
|
+
[["agents computers"], async () => (await import("./thread-on-computer.js")).THREAD_HANDLERS],
|
|
11
|
+
[["conversations resume", "conversations reader", "conversations list", "conversations feed", "conversations get", "conversations permissions", "conversations answer", "conversations watch", "conversations send", "conversations start", "conversations wait", "conversations land", "files pull", "conversations move", "conversations archive", "conversations unarchive", "conversations seen", "conversations delete"], async () => (await import("./conversation-commands.js")).CONVERSATION_HANDLERS],
|
|
12
|
+
[["orchestrator say", "orchestrator list", "orchestrator retry", "orchestrator dismiss", "orchestrator restore"], async () => (await import("./orchestrator-commands.js")).ORCHESTRATOR_HANDLERS],
|
|
13
|
+
[["board", "task add", "task show", "task edit", "task rm", "task steer", "task land", "task clear-done", "needs-you", "needs-you answer", "approvals", "approvals set", "approvals allow", "approvals remove"], async () => (await import("./board-commands.js")).BOARD_HANDLERS],
|
|
14
|
+
[["conversations transcript", "conversations events"], async () => (await import("./conversation-transcript.js")).TRANSCRIPT_HANDLERS],
|
|
15
|
+
[["conversations try"], async () => (await import("./conversation-local.js")).CONVERSATION_HANDLERS],
|
|
16
|
+
[["conversations landing"], async () => (await import("./worktree-twin-engine.js")).TWIN_HANDLERS],
|
|
17
|
+
[["secrets log", "credentials list", "credentials enable", "credentials approve", "credentials deny", "credentials forget"], async () => (await import("./credential-prompts.js")).CREDENTIAL_HANDLERS],
|
|
18
|
+
[["agents credentials login", "agents credentials sync", "agents credentials status", "agents credentials forget"], async () => (await import("./agent-credentials.js")).AGENT_CREDENTIAL_HANDLERS],
|
|
19
|
+
[["agents accounts", "agents accounts list", "agents accounts usage", "agents accounts add", "agents accounts remove", "agents accounts use", "accounts", "accounts list", "accounts usage", "accounts add", "accounts remove", "accounts use"], async () => (await import("./account-pool.js")).ACCOUNT_POOL_HANDLERS],
|
|
20
|
+
[["agents models"], async () => (await import("./account-pool.js")).AGENT_MODELS_HANDLERS],
|
|
21
|
+
[["agents list", "login", "whoami", "version", "feedback", "usage", "workspaces labels", "workspaces hint", "workspaces idle", "workspaces exec-start", "workspaces tunnel", "workspaces exec-read", "workspaces exec-cancel", "workspaces exec-input", "workspaces retention", "projects retention", "workspaces open", "workspaces list", "workspaces get", "workspaces inputs", "workspaces exec", "files read-many", "files read", "files write", "files delete", "files ls", "files stat", "files search", "files patch", "workspaces suspend", "workspaces resume", "workspaces delete", "workspaces close", "workspaces upgrade", "workspaces reset", "workspaces save-as-template", "workspaces changes", "templates list", "templates get", "templates init", "templates build", "templates export", "templates test", "templates builds list", "templates builds get", "templates builds log", "templates builds cancel", "templates languages", "templates packages search", "templates files", "templates diff", "templates draft open", "templates draft status", "templates draft state", "templates draft test", "templates draft publish", "templates draft discard", "workspaces fork", "workspaces resize", "workspaces sessions", "workspaces port expose", "workspaces port ls", "workspaces port close", "workspaces port token", "workspaces port link", "workspaces port callback", "workspaces computer status", "workspaces computer screenshot", "workspaces computer act", "workspaces computer click", "workspaces computer type", "workspaces computer paste-text", "workspaces computer key", "workspaces computer scroll", "workspaces computer move", "workspaces computer drag", "workspaces computer mouse-down", "workspaces computer mouse-up", "workspaces computer hold-key", "workspaces computer wait", "workspaces computer zoom", "workspaces computer cursor", "workspaces computer start", "workspaces computer stop", "workspaces computer stream", "workspaces computer enable", "workspaces computer disable", "workspaces computer inherit", "templates computer-use", "workspaces secrets", "secrets import", "secrets create", "secrets list", "secrets get", "secrets update", "secrets rotate", "secrets versions", "secrets delete", "secrets access-log", "operations list", "operations get", "operations progress", "operations cancel", "operations wait", "executions get"], async () => (await import("./commands.js")).KEY_HANDLERS],
|
|
22
|
+
[["auth register", "auth verify-email", "auth resend-verification", "auth login", "auth mfa", "auth status", "auth logout", "auth sessions", "auth step-up", "auth password change", "auth password reset", "auth password reset-confirm", "auth email change", "auth email confirm", "auth totp enroll", "auth totp confirm", "auth totp disable", "auth totp recovery-codes", "setup", "signup", "verify", "env", "orgs list", "orgs create", "orgs get", "orgs use", "orgs export", "orgs workspaces", "orgs deletion", "orgs delete", "projects list", "projects create", "projects get", "projects use", "api-keys list", "api-keys create", "api-keys revoke", "members list", "members set-role", "members remove", "invitations list", "invitations create", "invitations revoke", "invitations accept", "billing plans", "billing status", "billing upgrade", "billing portal", "billing invoices", "billing alerts", "billing overage", "audit list", "audit export", "account status", "account export", "account delete", "account cancel-deletion", "egress get", "egress set", "egress history", "egress clear", "volumes list", "status", "volumes open", "volumes create", "volumes get", "volumes delete", "volumes attachments", "volumes attach", "volumes detach", "templates publish", "templates archive"], async () => (await import("./account-commands.js")).ACCOUNT_HANDLERS],
|
|
23
|
+
[["agents open"], async () => (await import("./agent-commands.js")).AGENT_HANDLERS],
|
|
24
|
+
[["agents new", "agents fork", "agents park", "agents logs"], async () => (await import("./command-center-actions.js")).CENTER_HANDLERS],
|
|
25
|
+
[["agents targets", "agents terminal-run", "agents open claude-desktop", "agents open codex-app", "agents open t3", "agents open web", "ssh-proxy"], async () => (await import("./agent-target-commands.js")).TARGET_HANDLERS],
|
|
26
|
+
[["agents merge", "agents keep", "agents discard", "agents worktree", "agents restore"], async () => (await import("./agent-git-commands.js")).AGENT_GIT_HANDLERS],
|
|
27
|
+
[["menubar start", "menubar stop", "menubar status", "menubar account"], async () => (await import("./menubar.js")).MENUBAR_HANDLERS],
|
|
28
|
+
[["machine", "machine open", "machine save", "machine rollback", "template", "template save", "template rollback", "template delete"], async () => (await import("./filesystem-commands.js")).FILESYSTEM_HANDLERS],
|
|
29
|
+
[["machine setup"], async () => (await import("./machine-setup.js")).MACHINE_SETUP_HANDLERS],
|
|
30
|
+
[["settings"], async () => (await import("./client-settings.js")).SETTINGS_HANDLERS],
|
|
31
|
+
[["update"], async () => (await import("./update.js")).UPDATE_HANDLERS],
|
|
32
|
+
[["agents config-sync"], async () => (await import("./agent-config-sync.js")).AGENT_CONFIG_HANDLERS],
|
|
33
|
+
[["route", "ask"], async () => (await import("./router-commands.js")).ROUTER_HANDLERS],
|
|
34
|
+
[["transcribe"], async () => (await import("./transcribe-commands.js")).TRANSCRIBE_HANDLERS],
|
|
35
|
+
[["drive ls", "drive get", "drive put", "drive rm", "models", "models primary"], async () => (await import("./drive-commands.js")).DRIVE_HANDLERS],
|
|
36
|
+
[["brain ls", "brain get"], async () => (await import("./brain-commands.js")).BRAIN_HANDLERS],
|
|
37
|
+
];
|
|
38
|
+
const owners = new Map(groups.flatMap(([paths, load]) => paths.map(path => [path, load])));
|
|
39
|
+
export const HANDLER_PATHS = [...owners.keys()];
|
|
40
|
+
export async function handlerFor(path) {
|
|
41
|
+
const load = owners.get(path);
|
|
42
|
+
return load ? (await load())[path] : undefined;
|
|
43
|
+
}
|
package/dist/land.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export interface LandDeps {
|
|
|
97
97
|
tasks: Pick<ReturnType<typeof conversationClient>['tasks'], 'get' | 'recordLanding' | 'steer'>;
|
|
98
98
|
conversations: Pick<ReturnType<typeof conversationClient>['conversations'], 'get' | 'land'>;
|
|
99
99
|
};
|
|
100
|
-
hubCell(projectId: string): Promise<{
|
|
100
|
+
hubCell(projectId: string, workspaceIds?: readonly string[]): Promise<{
|
|
101
101
|
cell: TwinCell;
|
|
102
102
|
close(): void;
|
|
103
103
|
}>;
|
package/dist/land.js
CHANGED
|
@@ -127,11 +127,11 @@ function message(o) {
|
|
|
127
127
|
return hub;
|
|
128
128
|
}
|
|
129
129
|
export function landDeps(ctx) {
|
|
130
|
-
return { client: conversationClient(ctx), hubCell: p => projectHubCell(ctx, p, ctx.signal, 'shard-land'), folder: (p, c, r) => linkedFolder(ctx, p, c, r),
|
|
130
|
+
return { client: conversationClient(ctx), hubCell: (p, ids) => projectHubCell(ctx, p, ctx.signal, 'shard-land', ids), folder: (p, c, r) => linkedFolder(ctx, p, c, r),
|
|
131
131
|
device: deviceId(ctx.store.dir), stateDir: ctx.store.dir, signal: ctx.signal };
|
|
132
132
|
}
|
|
133
133
|
async function land(d, l, onConflict) {
|
|
134
|
-
const vm = await d.hubCell(l.projectId);
|
|
134
|
+
const vm = await d.hubCell(l.projectId, l.workspaceIds);
|
|
135
135
|
try {
|
|
136
136
|
const input = { result: l.result, sources: l.sources.map(s => s.branch), title: l.title, ...(d.hub ? { hub: d.hub } : {}) };
|
|
137
137
|
let hub = await landOnHub(vm.cell, input, d.signal);
|
|
@@ -228,7 +228,7 @@ export async function landTask(d, taskId, opts = {}) {
|
|
|
228
228
|
const repository = workers.find(w => w.repo_remote)?.repo_remote ?? (task.master_id ? (await client.conversations.get(task.master_id).catch(() => null))?.repo_remote : undefined) ?? null;
|
|
229
229
|
const folder = opts.path ?? d.folder(task.project_id, undefined, repository ?? undefined);
|
|
230
230
|
const done = await land(d, { projectId: task.project_id, title, name: taskId, result: resultBranch(taskId), sources: workers.map(w => ({ branch: w.branch, conversation_id: w.id })),
|
|
231
|
-
base: workers.find(w => w.base_commit)?.base_commit ?? null, folder, repository }, async (hub) => {
|
|
231
|
+
base: workers.find(w => w.base_commit)?.base_commit ?? null, folder, repository, workspaceIds: workers.map(w => w.workspace_id).filter(Boolean) }, async (hub) => {
|
|
232
232
|
const what = hub.stage === 'integrate' ? `${hub.source} into ${resultBranch(taskId)}` : `${resultBranch(taskId)} into ${hub.into}`;
|
|
233
233
|
await client.tasks.steer(taskId, `Land could not merge ${what}: ${list(hub.conflicts ?? [])} changed on both sides. Merge ${hub.into} from the project hub and every worker branch into ${resultBranch(taskId)}, resolve the conflicts so both changes work (ask the person only if the choice is theirs), run the tests, push ${resultBranch(taskId)} to the hub and seal done again.`, signal);
|
|
234
234
|
});
|
|
@@ -267,9 +267,10 @@ export async function landConversation(d, id, opts = {}) {
|
|
|
267
267
|
const c = await client.conversations.get(id);
|
|
268
268
|
if (c.changes?.uncommitted)
|
|
269
269
|
throw new CodedError('uncommitted_changes', 'The conversation has uncommitted changes.', 'ask its agent to commit them, then land');
|
|
270
|
-
const
|
|
270
|
+
const repository = c.repo_remote ?? null;
|
|
271
|
+
const folder = opts.path ?? d.folder(c.project_id, c.id, repository ?? undefined);
|
|
271
272
|
const done = await land(d, { projectId: c.project_id, title: c.title || c.branch, name: c.name ?? c.branch.replace(/^shard\//, ''), result: c.branch, sources: [],
|
|
272
|
-
base: c.base_commit ?? null, folder }, () => Promise.resolve());
|
|
273
|
+
base: c.base_commit ?? null, folder, repository, workspaceIds: [c.workspace_id] }, () => Promise.resolve());
|
|
273
274
|
let recorded = false, record_error;
|
|
274
275
|
try {
|
|
275
276
|
await client.conversations.land(id, 'merge', { into: done.hub.into, ...(d.signal ? { signal: d.signal } : {}) });
|
package/dist/linux-helper.js
CHANGED
|
@@ -4,7 +4,6 @@ import { readFileSync, realpathSync } from 'node:fs';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { CLI_VERSION } from "./args.js";
|
|
6
6
|
import { writePrivateFile } from "./credentials.js";
|
|
7
|
-
import { runDeviceHelper } from "./device-helper.js";
|
|
8
7
|
export const HELPER_UNIT = 'shardflux-helper.service';
|
|
9
8
|
export const helperUnitPath = (home) => join(home, '.config/systemd/user', HELPER_UNIT);
|
|
10
9
|
const quote = (s) => '"' + s.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%').replaceAll('\n', '\\n').replaceAll('\r', '\\r') + '"';
|
|
@@ -83,6 +82,6 @@ export function startLinuxHelper(ctx) {
|
|
|
83
82
|
writePrivateFile(ctx.store.dir, notice, '1\n');
|
|
84
83
|
}
|
|
85
84
|
const abort = new AbortController();
|
|
86
|
-
const work = runDeviceHelper({ ...ctx, signal: AbortSignal.any([ctx.signal, abort.signal]) }, { service: true }).catch(() => undefined);
|
|
85
|
+
const work = import("./device-helper.js").then(({ runDeviceHelper }) => runDeviceHelper({ ...ctx, signal: AbortSignal.any([ctx.signal, abort.signal]) }, { service: true })).catch(() => undefined);
|
|
87
86
|
return async () => { abort.abort(); await work; };
|
|
88
87
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Values } from './args.js';
|
|
2
|
+
type Env = Record<string, string | undefined>;
|
|
3
|
+
export declare const SETUP_VM_ENV = "SHARDFLUX_MACHINE_SETUP_VM";
|
|
4
|
+
/**
|
|
5
|
+
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
6
|
+
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
7
|
+
*/
|
|
8
|
+
export declare function setupGuard(env: Env, command: string, values: Values, positionals: string[]): string | undefined;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const SETUP_VM_ENV = 'SHARDFLUX_MACHINE_SETUP_VM';
|
|
2
|
+
/**
|
|
3
|
+
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
4
|
+
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
5
|
+
*/
|
|
6
|
+
export function setupGuard(env, command, values, positionals) {
|
|
7
|
+
const vm = env[SETUP_VM_ENV]?.trim();
|
|
8
|
+
if (!vm)
|
|
9
|
+
return undefined;
|
|
10
|
+
const allowed = ['workspaces exec', 'files write', 'files read', 'files ls', 'files stat', 'version'];
|
|
11
|
+
if (!allowed.includes(command))
|
|
12
|
+
return `machine setup runs only shard ws exec, files write and files read on ${vm}`;
|
|
13
|
+
if (command === 'version')
|
|
14
|
+
return undefined;
|
|
15
|
+
if (positionals[0] !== vm)
|
|
16
|
+
return `machine setup runs commands only on ${vm}`;
|
|
17
|
+
if (command === 'files write' && values.from !== undefined && values.from !== '-')
|
|
18
|
+
return 'machine setup writes files only from standard input (--from -)';
|
|
19
|
+
if (command === 'files read' && values.out !== undefined)
|
|
20
|
+
return 'machine setup reads files only to standard output';
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
package/dist/machine-setup.d.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { AgentStateStore } from './agent-state.js';
|
|
|
4
4
|
import { type Values } from './args.js';
|
|
5
5
|
export type Runner = 'claude' | 'codex' | 'cloud';
|
|
6
6
|
type Env = Record<string, string | undefined>;
|
|
7
|
-
|
|
8
|
-
export declare const SETUP_VM_ENV = "SHARDFLUX_MACHINE_SETUP_VM";
|
|
7
|
+
export { SETUP_VM_ENV, setupGuard } from './machine-setup-guard.js';
|
|
9
8
|
export declare const DEFAULT_AGENT_TIMEOUT_MS: number;
|
|
10
9
|
export interface ChildResult {
|
|
11
10
|
code: number | null;
|
|
@@ -71,11 +70,6 @@ export declare const SECRET_PATHS: readonly ["~/.ssh/**", "~/.aws/**", "~/.azure
|
|
|
71
70
|
export declare function claudeArgs(key: string, help: string): string[];
|
|
72
71
|
/** `codex exec` argv: writes only in `workDir` (and temp folders), network on for `shard`, the task on standard input. */
|
|
73
72
|
export declare function codexArgs(workDir: string, help: string): string[];
|
|
74
|
-
/**
|
|
75
|
-
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
76
|
-
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
77
|
-
*/
|
|
78
|
-
export declare function setupGuard(env: Env, command: string, values: Values, positionals: string[]): string | undefined;
|
|
79
73
|
export interface Inventory {
|
|
80
74
|
/** The home whose files were inventoried; a local agent's login shell can use a different home. */
|
|
81
75
|
source_home?: string;
|
|
@@ -187,4 +181,3 @@ export declare function machineSetup(ctx: Ctx, values: Values, deps?: SetupDeps)
|
|
|
187
181
|
*/
|
|
188
182
|
export declare function startMachineSetupInBackground(ctx: Ctx): boolean;
|
|
189
183
|
export declare const MACHINE_SETUP_HANDLERS: Record<string, Handler>;
|
|
190
|
-
export {};
|
package/dist/machine-setup.js
CHANGED
|
@@ -31,7 +31,8 @@ import { progress as stderrProgress } from "./progress.js";
|
|
|
31
31
|
import { writeProgress } from "./seed-phases.js";
|
|
32
32
|
import { parseDuration } from "./args.js";
|
|
33
33
|
/** Set for the local agent's processes: the `shard` it runs only reaches this machine VM (see `setupGuard`). */
|
|
34
|
-
|
|
34
|
+
import { SETUP_VM_ENV } from "./machine-setup-guard.js";
|
|
35
|
+
export { SETUP_VM_ENV, setupGuard } from "./machine-setup-guard.js";
|
|
35
36
|
export const DEFAULT_AGENT_TIMEOUT_MS = 10 * 60_000;
|
|
36
37
|
const SAVE_TIMEOUT_MS = 10 * 60_000;
|
|
37
38
|
/**
|
|
@@ -228,27 +229,6 @@ export function codexArgs(workDir, help) {
|
|
|
228
229
|
'-',
|
|
229
230
|
];
|
|
230
231
|
}
|
|
231
|
-
/**
|
|
232
|
-
* While `SHARDFLUX_MACHINE_SETUP_VM` is set (the setup agent's own commands), shard only runs commands on that VM: exec,
|
|
233
|
-
* file writes from standard input, reads and listings. Returns the refusal, or undefined when the command may run.
|
|
234
|
-
*/
|
|
235
|
-
export function setupGuard(env, command, values, positionals) {
|
|
236
|
-
const vm = env[SETUP_VM_ENV]?.trim();
|
|
237
|
-
if (!vm)
|
|
238
|
-
return undefined;
|
|
239
|
-
const allowed = ['workspaces exec', 'files write', 'files read', 'files ls', 'files stat', 'version'];
|
|
240
|
-
if (!allowed.includes(command))
|
|
241
|
-
return `machine setup runs only shard ws exec, files write and files read on ${vm}`;
|
|
242
|
-
if (command === 'version')
|
|
243
|
-
return undefined;
|
|
244
|
-
if (positionals[0] !== vm)
|
|
245
|
-
return `machine setup runs commands only on ${vm}`;
|
|
246
|
-
if (command === 'files write' && values.from !== undefined && values.from !== '-')
|
|
247
|
-
return 'machine setup writes files only from standard input (--from -)';
|
|
248
|
-
if (command === 'files read' && values.out !== undefined)
|
|
249
|
-
return 'machine setup reads files only to standard output';
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
232
|
function setupCapacity(ws) {
|
|
253
233
|
const disk = ws.grants?.disk_gib;
|
|
254
234
|
if (typeof disk !== 'number' || !Number.isFinite(disk) || disk <= 0)
|
package/dist/main.js
CHANGED
|
@@ -14,27 +14,17 @@ import { homedir, hostname } from 'node:os';
|
|
|
14
14
|
import { statSync } from 'node:fs';
|
|
15
15
|
import { SDK_VERSION, Shardflux, ShardfluxAccount, aboutSeconds, isSessionToken } from '@shardflux/sdk';
|
|
16
16
|
import { CLI_VERSION, CENTER_COMMAND, GUIDE_COMMAND, ONBOARDING_COMMAND, PROJECT_COMMAND, parseCommandLine, resolveApiUrl, resolveWake, resolveWakeTimeout } from "./args.js";
|
|
17
|
-
import { commandCenter } from "./command-center.js";
|
|
18
|
-
import { launchProjectCommand } from "./conversation-terminal.js";
|
|
19
|
-
import { runAcp } from "./acp.js";
|
|
20
17
|
import { registerInstalledT3 } from "./t3-provider.js";
|
|
21
|
-
import {
|
|
22
|
-
import { runAcpLogin } from "./acp-login.js";
|
|
23
|
-
import { terminalOnboarding } from "./terminal-onboarding.js";
|
|
24
|
-
import { codingAgentShell, setupGuideText } from "./agent-setup.js";
|
|
25
|
-
import { agentSetupGuide } from "./account-commands.js";
|
|
26
|
-
import { tryLinuxDesktop } from "./desktop-linux.js";
|
|
18
|
+
import { codingAgentShell } from "./agent-shell.js";
|
|
27
19
|
import { startLinuxHelper } from "./linux-helper.js";
|
|
28
|
-
import {
|
|
29
|
-
import { housekeeping, startupHousekeeping } from "./agent-git-commands.js";
|
|
20
|
+
import { housekeepingIfTracked, startupHousekeeping } from "./startup-housekeeping.js";
|
|
30
21
|
import { CredentialStore, configDir } from "./credentials.js";
|
|
31
22
|
import { recordInstall } from "./update.js";
|
|
32
23
|
import { refreshMenubarInBackground, runMenubarRefresh } from "./menubar.js";
|
|
33
24
|
import { NPM_CLIENTS, npmLatestAll, npmRegistry, withRegistryLatest } from "./registry.js";
|
|
34
25
|
import { AuthConfigError, EXIT, ExecutionPendingError, InterruptedError, UsageError, describeError, exitCodeFor, ownHint, redact } from "./errors.js";
|
|
35
26
|
import { errorText, feedbackCommand, json, time } from "./format.js";
|
|
36
|
-
import {
|
|
37
|
-
import { SETUP_VM_ENV, setupGuard } from "./machine-setup.js";
|
|
27
|
+
import { SETUP_VM_ENV, setupGuard } from "./machine-setup-guard.js";
|
|
38
28
|
import { makeFetch } from "./http.js";
|
|
39
29
|
import { Input } from "./input.js";
|
|
40
30
|
import { TimingRecorder, notices, timingJson, timingText } from "./timing.js";
|
|
@@ -726,6 +716,7 @@ export async function run(argv, io, opts = {}) {
|
|
|
726
716
|
};
|
|
727
717
|
// Bare shard without a terminal (0.17.1+): the setup guide, read from local state only (never waits).
|
|
728
718
|
if (parsed.command === GUIDE_COMMAND) {
|
|
719
|
+
const { setupGuideText, agentSetupGuide } = await import("./agent-setup.js");
|
|
729
720
|
io.stdout.write(setupGuideText(agentSetupGuide(ctx)));
|
|
730
721
|
return EXIT.ok;
|
|
731
722
|
}
|
|
@@ -733,14 +724,24 @@ export async function run(argv, io, opts = {}) {
|
|
|
733
724
|
const refused = setupGuard(io.env, command, parsed.values, parsed.positionals);
|
|
734
725
|
if (refused)
|
|
735
726
|
return report(new UsageError(refused));
|
|
736
|
-
|
|
727
|
+
// Load the selected interactive surface only after help/version and credential parsing. Plain commands do
|
|
728
|
+
// not need the terminal UI, ACP session machinery, device bridge or onboarding just to start.
|
|
729
|
+
const handler = parsed.command.path[0] === 'acp'
|
|
730
|
+
? async (ctx) => parsed.values.login === true ? (await import("./acp-login.js")).runAcpLogin(ctx) : (await import("./acp.js")).runAcp(ctx)
|
|
731
|
+
: parsed.command.path[0] === 'device-helper'
|
|
732
|
+
? async (ctx) => (await import("./device-helper.js")).runDeviceHelper(ctx, { service: parsed.values.service === true })
|
|
733
|
+
: command === 'menubar refresh' ? runMenubarRefresh
|
|
734
|
+
: parsed.command === PROJECT_COMMAND ? (await import("./conversation-terminal.js")).launchProjectCommand
|
|
735
|
+
: parsed.command === ONBOARDING_COMMAND ? async (ctx, values) => await (await import("./desktop-linux.js")).tryLinuxDesktop(ctx) ? 0 : (await import("./terminal-onboarding.js")).terminalOnboarding(ctx, values, { startHelper })
|
|
736
|
+
: parsed.command === CENTER_COMMAND ? (await import("./command-center.js")).commandCenter
|
|
737
|
+
: await (await import("./handler-loader.js")).handlerFor(parsed.command.path.join(' '));
|
|
737
738
|
if (!handler)
|
|
738
739
|
return report(new UsageError(`unknown command "${parsed.command.path.join(' ')}"`));
|
|
739
740
|
// Long-running helpers other programs start (an editor, launchd, the menu bar) skip the per-command housekeeping.
|
|
740
741
|
if (!['acp', 'device-helper'].includes(parsed.command.path[0]) && command !== 'menubar refresh' && command !== 'cli-proxy forward' && !io.env[SETUP_VM_ENV]) {
|
|
741
742
|
// An updated shard restarts this computer's CLIProxyAPI forwarder on itself (a no-op unless the version changed).
|
|
742
|
-
await refreshForwarderAgent(io.env, ctx.store.dir).catch(() => false);
|
|
743
|
-
await
|
|
743
|
+
await import("./forwarder-refresh.js").then(({ refreshForwarderAgent }) => refreshForwarderAgent(io.env, ctx.store.dir)).catch(() => false);
|
|
744
|
+
await housekeepingIfTracked(ctx);
|
|
744
745
|
startHelper();
|
|
745
746
|
}
|
|
746
747
|
const running = handler(ctx, parsed.values, parsed.positionals).then((code) => ({ code }), (err) => ({ err }));
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* a session) reaches the orchestrator of the member who created that key (person-scope.ts). Text output is one line
|
|
7
7
|
* per input, tab-separated, id first; --json prints the API's objects.
|
|
8
8
|
*/
|
|
9
|
-
import { inputSettled
|
|
9
|
+
import { inputSettled } from '@shardflux/sdk';
|
|
10
10
|
import { parseDuration } from "./args.js";
|
|
11
11
|
import { out, str } from "./commands.js";
|
|
12
12
|
import { EXIT, UsageError } from "./errors.js";
|
|
@@ -202,8 +202,6 @@ export const ORCHESTRATOR_HANDLERS = {
|
|
|
202
202
|
const text = words.join(' ');
|
|
203
203
|
if (!text.trim())
|
|
204
204
|
throw new UsageError('missing <words...> for "shard orchestrator say"');
|
|
205
|
-
if (text.length > ORCHESTRATOR_TEXT_MAX)
|
|
206
|
-
throw new UsageError(`your words are ${text.length} characters; one input takes up to ${ORCHESTRATOR_TEXT_MAX}`);
|
|
207
205
|
const selection = selectionOf(values);
|
|
208
206
|
const wait = values.wait === true;
|
|
209
207
|
const timeoutMs = str(values.timeout) === undefined ? SAY_WAIT_MS : parseDuration(str(values.timeout), 'timeout');
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Ctx } from './commands.js';
|
|
2
|
+
/** Help/version starts also clean saved work, without requiring credentials when nothing is due. */
|
|
3
|
+
export declare function startupHousekeeping(ctx: Pick<Ctx, 'io' | 'store' | 'signal'>): Promise<void>;
|
|
4
|
+
/** Keep the command's already selected auth behavior, loading Git machinery only for tracked repositories. */
|
|
5
|
+
export declare function housekeepingIfTracked(ctx: Pick<Ctx, 'io' | 'apiUrl' | 'signal' | 'store' | 'cloud'>): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Shardflux } from '@shardflux/sdk';
|
|
2
|
+
import { AgentStateStore } from "./agent-state.js";
|
|
3
|
+
import { resolveApiUrl } from "./args.js";
|
|
4
|
+
import { UsageError } from "./errors.js";
|
|
5
|
+
/** Help/version starts also clean saved work, without requiring credentials when nothing is due. */
|
|
6
|
+
export async function startupHousekeeping(ctx) {
|
|
7
|
+
try {
|
|
8
|
+
const state = new AgentStateStore(ctx.store.dir).read();
|
|
9
|
+
if (!state.git_repositories || !Object.keys(state.git_repositories).length)
|
|
10
|
+
return;
|
|
11
|
+
const apiUrl = resolveApiUrl(undefined, ctx.io.env.SHARDFLUX_API_URL ?? ctx.io.env.SHARDFLUX_BASE_URL);
|
|
12
|
+
let cloud;
|
|
13
|
+
await (await import("./agent-git-commands.js")).housekeeping({ ...ctx, apiUrl, cloud: () => {
|
|
14
|
+
const key = ctx.io.env.SHARDFLUX_API_KEY?.trim() || ctx.store.profile(apiUrl).api_key?.token;
|
|
15
|
+
if (!key)
|
|
16
|
+
throw new UsageError('saved agents need a project key for housekeeping; run shard setup');
|
|
17
|
+
return cloud ??= new Shardflux({ apiKey: key, baseUrl: apiUrl, versionCheck: false });
|
|
18
|
+
} });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
ctx.io.stderr.write(`shard housekeeping: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Keep the command's already selected auth behavior, loading Git machinery only for tracked repositories. */
|
|
25
|
+
export async function housekeepingIfTracked(ctx) {
|
|
26
|
+
const repos = new AgentStateStore(ctx.store.dir).read().git_repositories;
|
|
27
|
+
if (!repos || !Object.keys(repos).length)
|
|
28
|
+
return;
|
|
29
|
+
await (await import("./agent-git-commands.js")).housekeeping(ctx);
|
|
30
|
+
}
|
package/dist/t3-provider.js
CHANGED
|
@@ -3,13 +3,15 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { writePrivateFile } from "./credentials.js";
|
|
5
5
|
import { withTargetLock } from "./target-lock.js";
|
|
6
|
-
import {
|
|
6
|
+
import { applicationDirs, t3Apps } from "./agent-targets.js";
|
|
7
7
|
import { t3ArchiveSupportsAcp } from "./t3-asar.js";
|
|
8
8
|
const ID = 'shardflux';
|
|
9
9
|
const provider = { driver: 'acpRegistry', displayName: 'Shardflux', enabled: true, config: { source: 'local', commandPath: 'shard', commandArgs: ['acp'] } };
|
|
10
10
|
/** Every shard invocation registers the one installed T3 provider without adding terminal noise (macOS and Linux). */
|
|
11
11
|
export async function registerInstalledT3(env, dir, signal, platform = process.platform) {
|
|
12
|
-
|
|
12
|
+
// Registration only needs T3 presence; probing other apps or choosing a
|
|
13
|
+
// preferred T3 version would inspect unrelated bundles on every command.
|
|
14
|
+
if (!t3Apps(applicationDirs(env, platform), platform, env).length)
|
|
13
15
|
return;
|
|
14
16
|
try {
|
|
15
17
|
await configureT3Provider(env, dir, false, signal);
|
|
@@ -123,7 +123,7 @@ export declare function runTwinLoop(deps: LoopDeps, signal: AbortSignal): Promis
|
|
|
123
123
|
* A VM of the project that reads the project hub: an awake one reads it without waking anything; else the project's
|
|
124
124
|
* small space reader (woken). The twin lands from it when a conversation's VM is gone; `shard task land` merges there.
|
|
125
125
|
*/
|
|
126
|
-
export declare function projectHubCell(ctx: Ctx, projectId: string, signal?: AbortSignal, agentLabel?: string): Promise<{
|
|
126
|
+
export declare function projectHubCell(ctx: Ctx, projectId: string, signal?: AbortSignal, agentLabel?: string, workspaceIds?: readonly string[]): Promise<{
|
|
127
127
|
cell: TwinCell;
|
|
128
128
|
close(): void;
|
|
129
129
|
}>;
|
|
@@ -508,14 +508,17 @@ const openCell = (ws, agentLabel) => {
|
|
|
508
508
|
* A VM of the project that reads the project hub: an awake one reads it without waking anything; else the project's
|
|
509
509
|
* small space reader (woken). The twin lands from it when a conversation's VM is gone; `shard task land` merges there.
|
|
510
510
|
*/
|
|
511
|
-
export async function projectHubCell(ctx, projectId, signal, agentLabel = 'shard-twin') {
|
|
511
|
+
export async function projectHubCell(ctx, projectId, signal, agentLabel = 'shard-twin', workspaceIds) {
|
|
512
512
|
const client = conversationClient(ctx);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
513
|
+
// A manual landing uses its own result's computer. Borrowing an unrelated thread races that person's deletion:
|
|
514
|
+
// staging 2026-10-17 cut off a real landing when a newer home thread's VM was deleted. If the result's computers
|
|
515
|
+
// are gone or asleep, the persistent space reader owns the operation instead; do not wake a finished agent.
|
|
516
|
+
const ids = workspaceIds ?? (await listConversations(ctx, projectId)).filter(c => !ended(c))
|
|
517
|
+
.sort((a, b) => (b.last_activity_at ?? '').localeCompare(a.last_activity_at ?? '')).map(c => c.workspace_id);
|
|
518
|
+
for (const id of new Set(ids)) {
|
|
516
519
|
try {
|
|
517
|
-
const ws = await client.workspaces.get(
|
|
518
|
-
if (!ws.data.deleted_at && ws.ready)
|
|
520
|
+
const ws = await client.workspaces.get(id);
|
|
521
|
+
if (ws.data.project_id === projectId && !ws.data.deleted_at && ws.ready)
|
|
519
522
|
return openCell(ws, agentLabel);
|
|
520
523
|
}
|
|
521
524
|
catch { /* another may be awake */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "shard: the Shardflux command line. Create an account, sign in, set up keys and billing, then open, run commands in, move files to, suspend, resume and fork serverless agent workspaces.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -31,7 +31,12 @@
|
|
|
31
31
|
"import": "./dist/index.js",
|
|
32
32
|
"default": "./dist/index.js"
|
|
33
33
|
},
|
|
34
|
-
"./bin": "./dist/bin.js"
|
|
34
|
+
"./bin": "./dist/bin.js",
|
|
35
|
+
"./main": {
|
|
36
|
+
"types": "./dist/main.d.ts",
|
|
37
|
+
"import": "./dist/main.js",
|
|
38
|
+
"default": "./dist/main.js"
|
|
39
|
+
}
|
|
35
40
|
},
|
|
36
41
|
"files": [
|
|
37
42
|
"dist",
|
|
@@ -48,7 +53,7 @@
|
|
|
48
53
|
"snappyjs": "0.7.0",
|
|
49
54
|
"ws": "8.22.0",
|
|
50
55
|
"yaml": "2.9.1",
|
|
51
|
-
"@shardflux/sdk": "^0.30.
|
|
56
|
+
"@shardflux/sdk": "^0.30.1"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
59
|
"@eslint/js": "10.0.1",
|
|
@@ -60,10 +65,10 @@
|
|
|
60
65
|
"typescript-eslint": "8.70.1"
|
|
61
66
|
},
|
|
62
67
|
"optionalDependencies": {
|
|
63
|
-
"@shardflux/desktop-darwin-arm64": "0.9.
|
|
64
|
-
"@shardflux/desktop-darwin-x64": "0.9.
|
|
65
|
-
"@shardflux/desktop-linux-arm64": "0.9.
|
|
66
|
-
"@shardflux/desktop-linux-x64": "0.9.
|
|
68
|
+
"@shardflux/desktop-darwin-arm64": "0.9.2",
|
|
69
|
+
"@shardflux/desktop-darwin-x64": "0.9.2",
|
|
70
|
+
"@shardflux/desktop-linux-arm64": "0.9.2",
|
|
71
|
+
"@shardflux/desktop-linux-x64": "0.9.2"
|
|
67
72
|
},
|
|
68
73
|
"scripts": {
|
|
69
74
|
"build": "node scripts/build.mjs",
|