@shardflux/cli 0.28.3 → 0.28.4

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 CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Keep queued messages moving after an obsolete computer upgrade choice is cleared or changed; offer only compatible computer upgrades.
6
+
7
+ ## 0.28.4
8
+
9
+ - Pending native conversation messages keep their original sequence until acknowledged, including across a paused computer or an API restart. The retained computer wakes through its existing authorized path.
10
+
11
+ - Available workspace updates use the current saved machine or project environment, matching the target applied by an update after a saved-version rollback.
12
+
13
+ - Conversation moves require a saved checkpoint when idle. A new turn releases an incomplete idle move while keeping its computer, session, files and queued messages; active turns keep their deferred move.
14
+
15
+ - Read older and dismissed orchestrator answers with `shard orchestrator history`; `--cursor`, `--limit`, and `--organization` select a read-only page. JSON returns `inputs` and `next_cursor`.
16
+
17
+ - Home computer updates select the organization’s current saved machine, including after a machine rollback; repository and explicit update targets keep their existing behavior. Request and response fields are unchanged.
18
+
19
+ - Read current recipient input-file capability through the authenticated orchestrator API; unsupported file requests return a retryable update action, and pending file delivery keeps its actual waiting reason.
20
+
21
+ - Orchestrator dismissal: the API keeps dismissed, unstarted inputs out of dispatch and queue positions until they are restored. Work that already started continues; request and response fields are unchanged.
22
+
23
+ - Folder starts (0.28.4+): an agent's `project_link` with a first prompt (the device helper) never stays running: a start from the folder that does not finish within 5 minutes fails with what held it (for example `Setting up <project>: saving template`), and a start process that ignores its stop is killed 10 seconds later.
24
+
25
+ - The Shardflux app 0.9.5 (0.28.4+): `shard` installs and updates `@shardflux/desktop-darwin-arm64` / `-x64` and
26
+ `@shardflux/desktop-linux-x64` / `-arm64` 0.9.5 (a three-step first run, ⌘2 History search, request details
27
+ from your computer, full-screen pictures and videos on a phone, agents that stay in view, New project and Sign
28
+ out on the web, talking on Android and with several organizations).
29
+
30
+ - Cloud input-file delivery (0.28.4+, SDK 0.30.4): native-route refusals preserve `codex_cloud_input_files_require_native`; explicit `delivery.waiting` returns immediately with its reason and message, and is never reported as delivered.
31
+
32
+ - Settled reason (0.28.4+, SDK 0.30.4): preserve optional `settled_reason` on conversations and agents: `computer_suspended` identifies a computer that froze an active turn; `computer_failed` identifies a failed computer. Absent/null does not establish completion.
33
+
34
+ - Orchestrator usage waits (0.28.4+, SDK 0.30.4): orchestrator waits preserve optional input `waiting.reason` / `waiting.until` and orchestrator `waiting.until`; queued state and queue position stay unchanged. Message-only waits from older APIs remain compatible.
35
+
36
+ - Credential imports (0.28.4+): queue bulk credential imports instead of opening every local source and upload together. Every selected source is processed, and an individual failure does not stop the others.
37
+
5
38
  - The Shardflux app 0.9.4 (0.28.3+): `shard` installs and updates `@shardflux/desktop-darwin-arm64` / `-x64` and
6
39
  `@shardflux/desktop-linux-x64` / `-arm64` 0.9.4 (files with a ⌘2 task, ⌘2 Credentials and Set up from this Mac,
7
40
  talking in Safari, new answers stay visible, device request outcomes).
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @shardflux/cli: `shard`
2
2
 
3
+ Queued messages retain their computer and native session when a pending upgrade choice is cleared or changed.
4
+
3
5
  Use `shard --help` or `shard <command> --help` to explore commands before signing in.
4
6
 
5
7
  For error codes, retry timing, and plan recovery, see the [error reference](https://docs.shardflux.dev/reference/errors).
@@ -1724,6 +1726,8 @@ shard c delete conv_k3m5q7s2u4w6y3b5d7f2h4j6ln --yes
1724
1726
 
1725
1727
  ### Your orchestrator (0.28.0+)
1726
1728
 
1729
+ A queued input (0.28.4+) can carry `waiting.message`: the platform explains what it waits for, including a known usage-reset time. Keep that line alongside its queue position while the input remains queued.
1730
+
1727
1731
  `shard orchestrator say` passes your words to the API as one input, without cutting the final instruction.
1728
1732
 
1729
1733
  Your orchestrator is your own agent on top of every project, on its own Shardflux computer and the same on every
@@ -2130,4 +2134,30 @@ Scoped vault imports (0.28.3+) use `shard secrets import NAME --from hsec --scop
2130
2134
 
2131
2135
  ## Credential setup (0.28.3+)
2132
2136
 
2137
+ Every selected source is processed, and an unavailable source does not stop other credentials from being imported.
2138
+
2133
2139
  Run `shard credentials setup --from-this-computer --if-needed` after machine setup to bring your existing credentials along. Machine credentials are available to all your agents; project `.env` credentials stay local and override machine values by name. Values transfer directly from the local source into the write-only secret store and private credential files. The command prints names and delivery progress. Use `--plan` for an optional review, then `--apply PLAN_ID`. If a requested credential is missing everywhere, your agent asks once through the secure credential field.
2140
+
2141
+ ## Conversation delivery waits (0.28.4+)
2142
+
2143
+ `shard conversations send --wait` prints the server’s explicit delivery-wait message; JSON retains `delivery.waiting.reason` and `message`. Route file-bearing delegated workers through native threads. Reason `codex_cloud_input_files_require_native` identifies a Cloud route that needs a native thread before the message is accepted.
2144
+
2145
+ ### Dismissed orchestrator inputs
2146
+
2147
+ Dismiss an orchestrator input before it starts to keep it from running. Restoring that queued input puts it back in line. Dismissing an input that already started leaves its existing work running.
2148
+
2149
+ ### Home computer updates
2150
+
2151
+ Updating a home conversation’s computer selects the organization’s current saved machine, including after a machine rollback. Repository conversations keep their project’s saved environment.
2152
+
2153
+ ### Recipient file support (0.28.4+)
2154
+
2155
+ Run `shard orchestrator capabilities --json` to read `{capabilities: {input_files}}` for your recipient without creating or waking a computer.
2156
+
2157
+ A conversation moves from a saved turn. A running conversation finishes its turn before moving; a new turn continues on its retained computer when an idle move has no checkpoint.
2158
+
2159
+ Available computer updates report the same current saved machine or project environment that an update applies, including a saved version selected by rollback.
2160
+
2161
+ Paged History (0.28.4+): Read older and dismissed orchestrator answers with `shard orchestrator history`; `--cursor`, `--limit`, and `--organization` select a read-only page. JSON returns `inputs` and `next_cursor`.
2162
+
2163
+ Pending native conversation messages retain their original sequence until the harness acknowledges delivery, including after the computer pauses or the API restarts.
package/dist/args.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const CLI_VERSION = "0.28.3";
1
+ export declare const CLI_VERSION = "0.28.4";
2
2
  export interface OptionSpec {
3
3
  type: 'string' | 'boolean';
4
4
  short?: string;
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.3';
18
+ export const CLI_VERSION = '0.28.4';
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).' },
@@ -87,6 +87,8 @@ const sentLines = (r) => `${r.restored ? 'Restored\n' : ''}${(r.files ?? []).map
87
87
  export const SEND_WAIT_MS = 60_000;
88
88
  /** What happened to a sent message, as `conversations send --wait` prints it (0.27.0+). */
89
89
  export function deliveryLine(d) {
90
+ if (d.waiting)
91
+ return d.waiting.message;
90
92
  if (d.state === 'pending')
91
93
  return 'Not delivered yet.';
92
94
  if (d.state === 'uncertain')
@@ -1,13 +1,12 @@
1
- import type { CredentialSetup, CredentialSetupsApi, SecretsApi, NeedsYouApi } from '@shardflux/sdk';
1
+ import type { CredentialSetup, CredentialSetupsApi } from '@shardflux/sdk';
2
2
  import type { Ctx, Handler } from './commands.js';
3
3
  import type { CredentialDiscoveryOptions } from './credential-sources.js';
4
- interface SetupClient {
5
- credentialSetups: CredentialSetupsApi;
6
- secrets: SecretsApi;
7
- needsYou: NeedsYouApi;
8
- }
4
+ type TransferContext = Pick<Ctx, 'json' | 'io' | 'signal' | 'addWriteOnlyValue'>;
5
+ type TransferClient = {
6
+ credentialSetups: Pick<CredentialSetupsApi, 'storeValue' | 'sourceResult' | 'get'>;
7
+ };
9
8
  /** Onboarding opts in explicitly. The restricted machine agent never reads the source values. */
10
9
  export declare function credentialSetupAfterMachine(ctx: Ctx): Promise<void>;
11
- export declare function transferCredentialSetup(ctx: Ctx, cloud: SetupClient, plan: CredentialSetup, options: CredentialDiscoveryOptions): Promise<CredentialSetup>;
10
+ export declare function transferCredentialSetup(ctx: TransferContext, cloud: TransferClient, plan: CredentialSetup, options: CredentialDiscoveryOptions): Promise<CredentialSetup>;
12
11
  export declare const CREDENTIAL_SETUP_HANDLERS: Record<string, Handler>;
13
12
  export {};
@@ -26,7 +26,8 @@ export async function transferCredentialSetup(ctx, cloud, plan, options) {
26
26
  return plan;
27
27
  if (plan.state === 'denied' || plan.state === 'completed')
28
28
  return plan;
29
- const jobs = plan.items.filter(i => i.state === 'pending' && i.selected_scope !== null).map(async (item) => {
29
+ const items = plan.items.filter(i => i.state === 'pending' && i.selected_scope !== null);
30
+ const transfer = async (item) => {
30
31
  try {
31
32
  const { project_id: sourceProject, ...descriptor } = item;
32
33
  const candidate = { ...descriptor, ...(sourceProject ? { project_id: sourceProject } : {}), preferred: true };
@@ -47,8 +48,18 @@ export async function transferCredentialSetup(ctx, cloud, plan, options) {
47
48
  }
48
49
  catch { /* Read authoritative status below; a lost write response may already have stored it. */ }
49
50
  }
50
- });
51
- await Promise.allSettled(jobs);
51
+ };
52
+ // Queue every approved source, while opening only a few vault readers/uploads together.
53
+ // Read the value inside the worker so queued credentials remain in their original source.
54
+ let next = 0;
55
+ await Promise.all(Array.from({ length: Math.min(4, items.length) }, async () => {
56
+ for (;;) {
57
+ const item = items[next++];
58
+ if (!item)
59
+ return;
60
+ await transfer(item);
61
+ }
62
+ }));
52
63
  return cloud.credentialSetups.get(plan.id, { signal: ctx.signal });
53
64
  }
54
65
  async function picks(ctx, path) {
@@ -46,6 +46,11 @@ type Starter = (ctx: Ctx, folder: string, harness: 'claude' | 'codex', brief: st
46
46
  * prompt. The brief goes in on standard input, never in a command line another user can read.
47
47
  */
48
48
  export declare const startFromFolder: Starter;
49
+ /** How long a start from a folder may take, and how long a child asked to stop may take before it is killed. */
50
+ export declare const startFolderLimits: {
51
+ timeoutMs: number;
52
+ killAfterMs: number;
53
+ };
49
54
  /**
50
55
  * The conversation `shard agents new --json` started, from its whole output: one pretty-printed JSON document
51
56
  * (format.ts json), never its last line (staging 2026-10-16: the last line is `}`).
@@ -149,31 +149,55 @@ export const startFromFolder = (ctx, folder, harness, brief, fromConversationId)
149
149
  env: { ...process.env, ...ctx.io.env, SHARDFLUX_CONFIG_DIR: ctx.store.dir, SHARDFLUX_API_URL: ctx.apiUrl, CI: '1', ...(fromConversationId ? { SF_CONVERSATION_ID: fromConversationId } : {}) },
150
150
  });
151
151
  const out = [], err = [];
152
- let outBytes = 0;
153
- const stop = () => child.kill('SIGTERM');
154
- const timer = setTimeout(stop, 300_000);
155
- ctx.signal.addEventListener('abort', stop, { once: true });
152
+ let outBytes = 0, settled = false, kill;
153
+ const finish = (error, started) => {
154
+ if (settled)
155
+ return;
156
+ settled = true;
157
+ clearTimeout(timer);
158
+ ctx.signal.removeEventListener('abort', abort);
159
+ if (error)
160
+ reject(error);
161
+ else
162
+ resolveStart(started);
163
+ };
164
+ // A child that does not end when asked (its own start waiting on a project setup that never finishes) is killed,
165
+ // and the start fails now with what held it: a device request never stays `running` behind it (production
166
+ // 2026-10-18: a project_link sat running for over an hour, so its orchestrator input never settled).
167
+ const stop = () => {
168
+ child.kill('SIGTERM');
169
+ kill ??= setTimeout(() => child.kill('SIGKILL'), startFolderLimits.killAfterMs);
170
+ kill.unref?.();
171
+ };
172
+ const abort = () => { stop(); finish(new Error('The conversation start was cancelled.')); };
173
+ const timer = setTimeout(() => {
174
+ stop();
175
+ const minutes = Math.max(1, Math.round(startFolderLimits.timeoutMs / 60_000)), held = lastLine(Buffer.concat(err).toString('utf8'));
176
+ finish(new Error(`The conversation did not start within ${minutes} minute${minutes === 1 ? '' : 's'}${held ? `: ${held}` : '.'}`));
177
+ }, startFolderLimits.timeoutMs);
178
+ ctx.signal.addEventListener('abort', abort, { once: true });
156
179
  child.stdout.on('data', (b) => { if ((outBytes += b.length) <= 8 << 20)
157
180
  out.push(b); });
158
181
  child.stderr.on('data', (b) => { if (err.length < 64)
159
182
  err.push(b); });
160
- child.once('error', error => { clearTimeout(timer); reject(error); });
183
+ child.once('error', error => { clearTimeout(kill); finish(error); });
161
184
  child.once('close', code => {
162
- clearTimeout(timer);
163
- ctx.signal.removeEventListener('abort', stop);
185
+ clearTimeout(kill);
164
186
  const text = Buffer.concat(out).toString('utf8');
165
187
  try {
166
188
  if (code !== 0)
167
189
  throw new Error(lastLine(Buffer.concat(err).toString('utf8')) || `The conversation did not start (exit ${code}).`);
168
- resolveStart(startedConversation(text));
190
+ finish(null, startedConversation(text));
169
191
  }
170
192
  catch (error) {
171
- reject(error instanceof Error ? error : new Error(String(error)));
193
+ finish(error instanceof Error ? error : new Error(String(error)));
172
194
  }
173
195
  });
174
196
  child.stdin.end(brief);
175
197
  });
176
198
  };
199
+ /** How long a start from a folder may take, and how long a child asked to stop may take before it is killed. */
200
+ export const startFolderLimits = { timeoutMs: 300_000, killAfterMs: 10_000 };
177
201
  /**
178
202
  * The conversation `shard agents new --json` started, from its whole output: one pretty-printed JSON document
179
203
  * (format.ts json), never its last line (staging 2026-10-16: the last line is `}`).
@@ -9,7 +9,7 @@ const groups = [
9
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
10
  [["agents computers"], async () => (await import("./thread-on-computer.js")).THREAD_HANDLERS],
11
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 show", "orchestrator say", "orchestrator list", "orchestrator retry", "orchestrator dismiss", "orchestrator restore"], async () => (await import("./orchestrator-commands.js")).ORCHESTRATOR_HANDLERS],
12
+ [["orchestrator capabilities", "orchestrator show", "orchestrator say", "orchestrator list", "orchestrator history", "orchestrator retry", "orchestrator dismiss", "orchestrator restore"], async () => (await import("./orchestrator-commands.js")).ORCHESTRATOR_HANDLERS],
13
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
14
  [["conversations transcript", "conversations events"], async () => (await import("./conversation-transcript.js")).TRANSCRIPT_HANDLERS],
15
15
  [["conversations try"], async () => (await import("./conversation-local.js")).CONVERSATION_HANDLERS],
@@ -3,6 +3,20 @@ const ORG = {
3
3
  };
4
4
  const WHO = 'Your orchestrator is yours: one agent on its own Shardflux computer, the same on every device. Uses your signed-in session, or SHARDFLUX_API_KEY when it is set (a project key you created: a new orchestrator is filed under its project).';
5
5
  export const ORCHESTRATOR_COMMANDS = [
6
+ {
7
+ path: ['orchestrator', 'capabilities'],
8
+ summary: 'Read current input file support for your recipient',
9
+ positionals: [], options: ORG,
10
+ description: 'Reads your recipient metadata without creating or waking a computer. With --json: {capabilities: {input_files}}.',
11
+ examples: ['shard orchestrator capabilities --json'],
12
+ },
13
+ {
14
+ path: ['orchestrator', 'history'],
15
+ summary: 'Page through every input, including old and dismissed answers',
16
+ positionals: [],
17
+ options: { cursor: { type: 'string', value: '<cursor>', description: 'The previous page’s next_cursor.' }, limit: { type: 'string', value: '<n>', description: 'Page size, 1-200 (default 100).' }, ...ORG },
18
+ description: 'Reads one chronological page without starting work or waking a computer. Follow next_cursor to read earlier inputs. With --json: {inputs, next_cursor}.',
19
+ },
6
20
  {
7
21
  path: ['orchestrator', 'show'],
8
22
  summary: 'Read an input and its device-request outcomes, failures first',
@@ -11,7 +11,7 @@ import { tmpdir } from 'node:os';
11
11
  import { join, basename } from 'node:path';
12
12
  import { inputSettled } from '@shardflux/sdk';
13
13
  import { parseDuration } from "./args.js";
14
- import { out, str } from "./commands.js";
14
+ import { listLimit, out, str } from "./commands.js";
15
15
  import { EXIT, UsageError } from "./errors.js";
16
16
  import { humanBytes } from "./files-mirror.js";
17
17
  import { inScope } from "./person-scope.js";
@@ -202,6 +202,11 @@ const idOf = (id) => {
202
202
  return id;
203
203
  };
204
204
  export const ORCHESTRATOR_HANDLERS = {
205
+ async 'orchestrator capabilities'(ctx, values) {
206
+ const capabilities = await inScope(ctx, values, (client, scope) => client.orchestrator.capabilities({ ...orgOnly(scope), signal: ctx.signal }));
207
+ out(ctx, `input_files\t${capabilities.input_files}\n`, { capabilities });
208
+ return EXIT.ok;
209
+ },
205
210
  async 'orchestrator show'(ctx, values, [id]) {
206
211
  const detail = await inScope(ctx, values, (client, scope) => client.orchestrator.input(idOf(id), { ...orgOnly(scope), signal: ctx.signal }));
207
212
  out(ctx, [`${detail.input.id}\t${stateLabel(detail.input)}`, resultText(detail.input), ...deviceOutcomeLines(detail.device_requests)].filter(Boolean).join('\n') + '\n', detail);
@@ -283,6 +288,15 @@ export const ORCHESTRATOR_HANDLERS = {
283
288
  out(ctx, human, { inputs: page.inputs ?? [], cursor: page.cursor });
284
289
  return EXIT.ok;
285
290
  },
291
+ async 'orchestrator history'(ctx, values) {
292
+ const limit = listLimit(values, 200);
293
+ const page = await inScope(ctx, values, (client, scope) => client.orchestrator.history({ ...orgOnly(scope), cursor: str(values.cursor), ...(limit !== undefined ? { limit } : {}), signal: ctx.signal }));
294
+ const lines = page.inputs.map(inputLine);
295
+ if (page.nextCursor)
296
+ lines.push(`Next page: shard orchestrator history --cursor ${page.nextCursor}`);
297
+ out(ctx, lines.length ? `${lines.join('\n')}\n` : 'No input history yet.\n', { inputs: page.inputs, next_cursor: page.nextCursor });
298
+ return EXIT.ok;
299
+ },
286
300
  async 'orchestrator retry'(ctx, values, [id]) {
287
301
  const input = await inScope(ctx, values, (client, scope) => client.orchestrator.retry(idOf(id), { ...orgOnly(scope), signal: ctx.signal }));
288
302
  out(ctx, `${input.id}\t${stateLabel(input)}\n`, { input });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/cli",
3
- "version": "0.28.3",
3
+ "version": "0.28.4",
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",
@@ -53,7 +53,7 @@
53
53
  "snappyjs": "0.7.0",
54
54
  "ws": "8.22.0",
55
55
  "yaml": "2.9.1",
56
- "@shardflux/sdk": "^0.30.3"
56
+ "@shardflux/sdk": "^0.30.4"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@eslint/js": "10.0.1",
@@ -65,10 +65,10 @@
65
65
  "typescript-eslint": "8.70.1"
66
66
  },
67
67
  "optionalDependencies": {
68
- "@shardflux/desktop-darwin-arm64": "0.9.4",
69
- "@shardflux/desktop-darwin-x64": "0.9.4",
70
- "@shardflux/desktop-linux-arm64": "0.9.4",
71
- "@shardflux/desktop-linux-x64": "0.9.4"
68
+ "@shardflux/desktop-darwin-arm64": "0.9.5",
69
+ "@shardflux/desktop-darwin-x64": "0.9.5",
70
+ "@shardflux/desktop-linux-arm64": "0.9.5",
71
+ "@shardflux/desktop-linux-x64": "0.9.5"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "node scripts/build.mjs",