@shardflux/cli 0.18.0 → 0.18.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 CHANGED
@@ -1,6 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## 0.18.0 (not yet published)
3
+ ## 0.18.1 (not yet published)
4
+
5
+ - `shard agents logs` takes a conversation id (`shard agents logs conv_… --json`) and follows that conversation's own
6
+ terminal: the PTY its computer recorded for it (a fork's inherited one included), also when you name the
7
+ conversation's VM by its key. `--json` adds `conversation: { id }`. Before, a conversation id read as an unknown
8
+ workspace key and a conversation VM's key looked for a terminal named after the VM.
9
+ - A finished conversation whose VM was removed opens again from every app: `shard agents open <conversation>` with
10
+ `--in auto` (the menu bar), `--in web`, `--in claude-desktop` or `--in t3` restores it into a fresh VM from its saved
11
+ branch and transcript (the same conversation, its session resumed and started) instead of answering `This
12
+ conversation is saved. Resume it to continue.` A restore says `Restored` once. `shard conversations send` says it
13
+ too when the API restored the conversation to take the message.
14
+ - Brings the Shardflux app for Ubuntu 0.1.1, which reads this CLI's `--json` answers whole: Settings → Agents shows
15
+ Connected, the top-bar panel lists your conversations, and Start opens the conversation it started. The optional
16
+ dependency on `@shardflux/desktop-linux-x64` / `-arm64` is exactly 0.1.1.
17
+
18
+ ## 0.18.0 (2026-10-10)
4
19
 
5
20
  - A conversation that needs you says why: `shard conversations list` adds it as a fifth column, `shard conversations
6
21
  get` prints it after the branch, and `shard agents list` shows it as the session's LAST MESSAGE (one line, cut to
package/README.md CHANGED
@@ -428,7 +428,7 @@ plan only when Stripe confirms the payment, so `--wait` polls until the subscrip
428
428
  open). An organization that already has a subscription changes plans in the billing portal: the
429
429
  command prints the portal URL instead (exit 0).
430
430
 
431
- On the signup trial (60 days of Pro, no card), `billing status` shows when it ends and
431
+ On the signup trial (30 days of Pro, no card), `billing status` shows when it ends and
432
432
  what follows (0.6.2+): `plan trial, ends 2026-11-29 14:03:00Z, then free (trial, catalog ...)`, then
433
433
  `keep these limits after the trial: shard billing upgrade pro` (0.13.4+). A subscription outranks the
434
434
  trial and ends it. The trial is granted, never bought, so `billing plans` leaves it out.
@@ -28,7 +28,7 @@ import { openInFor } from "./t3-mode.js";
28
28
  import { openClaudeDesktopSession, openDesktopWorkspace, queueT3Prewarm } from "./agent-target-commands.js";
29
29
  import { submitInitialPrompt, prepareInitialCodexProject } from "./initial-prompt.js";
30
30
  import { conversationWorkspace, conversationsUnavailable, getConversation, listConversations, resumeConversation } from "./conversations.js";
31
- import { conversationTerminal } from "./conversation-terminal.js";
31
+ import { conversationTerminal, reopenConversation } from "./conversation-terminal.js";
32
32
  import { CHOICE_PENDING, choiceReport, hasChoice, launchChoice, modelChoice, runFlags } from "./agent-models.js";
33
33
  const active = (session) => session.state === 'running' || session.state === 'starting';
34
34
  /** `<slug>-<6 hex of sha256(identity)>`: the same repository gets the same key on every machine. */
@@ -299,7 +299,12 @@ export const AGENT_HANDLERS = {
299
299
  if (values.in === 'auto') {
300
300
  // The menu and other launchers use the same CLI-owned target selection. Never put provider URLs in a client.
301
301
  // A conversation id opens that conversation's own session, also when its computer hosts other threads.
302
- const ws = resolvedConversation ? await conversationWorkspace(ctx, resolvedConversation) : await resolveWorkspace(ctx, agent);
302
+ // A conversation whose VM was removed restores into a fresh one first (0.18.1+).
303
+ let ws;
304
+ if (resolvedConversation)
305
+ ({ conversation: resolvedConversation, ws } = await reopenConversation(ctx, resolvedConversation, values));
306
+ else
307
+ ws = await resolveWorkspace(ctx, agent);
303
308
  const project = ws.labels.repo_remote || ws.labels.shard_project || ws.key;
304
309
  const store = new AgentStateStore(ctx.store.dir);
305
310
  const app = values.app ?? resolvedConversation?.harness ?? ws.labels.shard_app ?? ws.labels.app ?? store.app(project);
@@ -5,7 +5,7 @@ import { ensureCloudClaude } from "./claude-cloud-login.js";
5
5
  import { spawn } from 'node:child_process';
6
6
  import { Readable, Duplex } from 'node:stream';
7
7
  import { ShardfluxApiError, WorkspaceTunnels } from '@shardflux/sdk';
8
- import { conversationWorkspace, getConversation } from "./conversations.js";
8
+ import { getConversation } from "./conversations.js";
9
9
  import { out, resolveWorkspace } from "./commands.js";
10
10
  import { MODEL_CHOICES } from "./agent-models.js";
11
11
  import { AgentTargets, installedTargets, targetLabel } from "./agent-targets.js";
@@ -229,14 +229,21 @@ const openTarget = (target) => async (ctx, values, [ref = 'main']) => {
229
229
  if (target === 't3' && /^conv_[a-z0-9]{26}$/.test(ref))
230
230
  return openT3Conversation(ctx, ref, values);
231
231
  // A conversation id opens that conversation's own session on its computer (claude.ai, the desktop apps).
232
- const conversation = /^conv_[a-z0-9]{26}$/.test(ref) ? await getConversation(ctx, ref) : undefined;
232
+ let conversation = /^conv_[a-z0-9]{26}$/.test(ref) ? await getConversation(ctx, ref) : undefined;
233
233
  // A pooled thread (0.18.0+) has no provider login on its session: it opens remotely in T3 Code.
234
234
  if (conversation && conversationPooled(conversation)) {
235
235
  if (!detected.targets.includes('t3'))
236
236
  throw pooledNeedsT3(ref);
237
237
  return openT3Conversation(ctx, ref, values);
238
238
  }
239
- let ws = conversation ? await conversationWorkspace(ctx, conversation) : await resolveWorkspace(ctx, keyOf(ref));
239
+ // A conversation whose VM was removed restores into a fresh one first (0.18.1+).
240
+ let ws;
241
+ if (conversation) {
242
+ const { reopenConversation } = await import("./conversation-terminal.js");
243
+ ({ conversation, ws } = await reopenConversation(ctx, conversation, values));
244
+ }
245
+ else
246
+ ws = await resolveWorkspace(ctx, keyOf(ref));
240
247
  if (values.reachable !== undefined)
241
248
  ws = await ctx.cloud().workspaces.open({ key: ws.key, ...agentReachability(values), wait: false });
242
249
  // T3 lists both agents and opens without Claude (it shows Claude as signed out); the Claude apps need it.
package/dist/args.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const CLI_VERSION = "0.18.0";
1
+ export declare const CLI_VERSION = "0.18.1";
2
2
  export interface OptionSpec {
3
3
  type: 'string' | 'boolean';
4
4
  short?: string;
package/dist/args.js CHANGED
@@ -13,7 +13,7 @@ import { parseArgs } from 'node:util';
13
13
  import { ACCOUNT_COMMANDS } from "./account-args.js";
14
14
  import { UsageError } from "./errors.js";
15
15
  import { DEFAULT_CHOICE, MODEL_CHOICES } from "./agent-models.js";
16
- export const CLI_VERSION = '0.18.0';
16
+ export const CLI_VERSION = '0.18.1';
17
17
  export const GLOBAL_OPTIONS = {
18
18
  'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
19
19
  json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
@@ -1288,8 +1288,8 @@ const AGENT_COMMANDS = [
1288
1288
  } },
1289
1289
  { path: ['agents', 'fork'], summary: 'Fork an agent’s memory and disk', positionals: [{ name: 'agent', required: true }, { name: 'name', required: true }], options: WAIT },
1290
1290
  { path: ['agents', 'park'], summary: 'Suspend an agent when idle', positionals: [{ name: 'agent', required: true }], options: { 'when-idle': { type: 'string', value: '<duration>', description: 'Idle grace before suspend (default 1s).' } } },
1291
- { path: ['agents', 'logs'], summary: 'Tail an agent PTY from its output offset', positionals: [{ name: 'agent', required: true }], options: {
1292
- app: { type: 'string', value: '<claude|codex>', description: 'Agent app (default remembered choice).' },
1291
+ { path: ['agents', 'logs'], summary: 'Tail an agent’s terminal from its output offset: a conversation id (0.18.1+) or an agent workspace key', positionals: [{ name: 'agent', required: true }], options: {
1292
+ app: { type: 'string', value: '<claude|codex>', description: 'Agent app (default remembered choice; a conversation uses its own).' },
1293
1293
  offset: { type: 'string', value: '<bytes>', description: 'Read from this output byte offset (default last 64 KiB).' },
1294
1294
  follow: { type: 'boolean', description: 'Follow output until interrupted.' },
1295
1295
  } },
@@ -11,9 +11,11 @@ import { agentName as gitAgentName } from "./agent-git.js";
11
11
  import { randomUUID } from 'node:crypto';
12
12
  import { prepareAgentMachine } from "./agent-machine.js";
13
13
  import { OperationFailedError } from '@shardflux/sdk';
14
+ import { conversationPtyId, getConversation } from "./conversations.js";
14
15
  import { launchConversationProject, launchProjectCommand } from "./conversation-terminal.js";
15
16
  import { startThreadOn } from "./thread-on-computer.js";
16
17
  import { launchT3 } from "./t3-launch.js";
18
+ const CONVERSATION_ID = /^conv_[a-z0-9]{26}$/;
17
19
  /** L9 can register target-specific handlers after detecting the installed applications. */
18
20
  export async function availableTargets(ctx) {
19
21
  const { HANDLERS } = await import("./handlers.js");
@@ -124,14 +126,38 @@ export const CENTER_HANDLERS = {
124
126
  },
125
127
  async 'agents logs'(ctx, values, [ref]) {
126
128
  if (!ref)
127
- throw new UsageError('agents logs requires an agent workspace key');
129
+ throw new UsageError('agents logs requires a conversation id or an agent workspace key');
128
130
  if (values.app !== undefined && values.app !== 'claude' && values.app !== 'codex')
129
131
  throw new UsageError('--app must be claude or codex');
130
- const ws = await resolveWorkspace(ctx, ref);
132
+ // A conversation (0.18.1+): its id, or its VM's key. Its PTY is the guest's record, never derived from the VM key.
133
+ let conversation;
134
+ let ws;
135
+ if (CONVERSATION_ID.test(ref)) {
136
+ conversation = await getConversation(ctx, ref);
137
+ ws = await ctx.cloud().workspaces.get(conversation.workspace_id);
138
+ }
139
+ else {
140
+ ws = await resolveWorkspace(ctx, ref);
141
+ if (ws.labels.shard_conversation && CONVERSATION_ID.test(ws.labels.shard_conversation))
142
+ conversation = await getConversation(ctx, ws.labels.shard_conversation);
143
+ }
144
+ if (conversation?.route === 'codex-cloud')
145
+ throw new UsageError(`Codex Cloud runs this conversation; follow it with shard agents open ${conversation.id}.`);
146
+ if (conversation?.route === 'acp')
147
+ throw new UsageError('This conversation runs through its ACP client, which shows its output; it has no terminal to follow.');
148
+ if (conversation && values.app !== undefined && values.app !== conversation.harness)
149
+ throw new UsageError(`This conversation runs ${conversation.harness}; omit --app.`);
131
150
  const project = ws.labels.repo_remote || ws.labels.shard_project || ws.key;
132
- const app = str(values.app) ?? new AgentStateStore(ctx.store.dir).app(project, ws.labels.app === 'codex' ? 'codex' : 'claude');
133
- const id = `shard-${ws.labels.agent_pty_name || agentName(ws.key, ws.labels)}-${app}`;
151
+ const app = conversation?.harness ?? str(values.app) ?? new AgentStateStore(ctx.store.dir).app(project, ws.labels.app === 'codex' ? 'codex' : 'claude');
134
152
  const cell = ws.cell({ agentLabel: ctx.agentLabel, ...(ctx.wake ? {} : { wake: null }), transitionTimeoutMs: ctx.wakeTimeoutMs });
153
+ let id;
154
+ try {
155
+ id = conversation ? await conversationPtyId(cell, conversation, ctx.signal) : `shard-${ws.labels.agent_pty_name || agentName(ws.key, ws.labels)}-${app}`;
156
+ }
157
+ catch (error) {
158
+ cell.close();
159
+ throw error;
160
+ }
135
161
  const offsetText = str(values.offset);
136
162
  if (offsetText !== undefined && (!/^\d+$/.test(offsetText) || !Number.isSafeInteger(Number(offsetText))))
137
163
  throw new UsageError('--offset must be a nonnegative safe integer');
@@ -175,7 +201,7 @@ export const CENTER_HANDLERS = {
175
201
  }
176
202
  const remainder = decoder.decode();
177
203
  if (ctx.json)
178
- ctx.io.stdout.write(json({ pty_id: id, output: output + remainder, nextOffset: cursor.offset, session, exited }));
204
+ ctx.io.stdout.write(json({ pty_id: id, ...(conversation ? { conversation: { id: conversation.id } } : {}), output: output + remainder, nextOffset: cursor.offset, session, exited }));
179
205
  else
180
206
  ctx.io.stdout.write(remainder);
181
207
  return 0;
@@ -22,7 +22,12 @@ export const CONVERSATION_HANDLERS = {
22
22
  return 0;
23
23
  },
24
24
  async 'conversations get'(ctx, _values, [id]) { const c = await conversationClient(ctx).conversations.get(id); const q = questionLine(c, 200); out(ctx, `${c.title}\n${c.state}\n${c.branch}\n${q ? `${q}\n` : ''}`, c); return 0; },
25
- async 'conversations send'(ctx, _values, [id, text]) { const r = await conversationClient(ctx).conversations.send(id, text, { signal: ctx.signal }); out(ctx, `Message sent (${r.seq}).\n`, r); return 0; },
25
+ // A finished conversation whose VM was removed restores into a fresh one, then takes the message (APIs that do; 0.18.1+).
26
+ async 'conversations send'(ctx, _values, [id, text]) {
27
+ const r = await conversationClient(ctx).conversations.send(id, text, { signal: ctx.signal });
28
+ out(ctx, `${r.restored ? 'Restored\n' : ''}Message sent (${r.seq}).\n`, r);
29
+ return 0;
30
+ },
26
31
  async 'conversations wait'(ctx, values, [id]) {
27
32
  const seconds = values.timeout === undefined ? 900 : Number(values.timeout);
28
33
  if (!Number.isInteger(seconds) || seconds < 0 || seconds > 900)
@@ -4,6 +4,16 @@ import type { Values } from './args.js';
4
4
  import type { Conversation } from './conversations.js';
5
5
  /** Every terminal entry point uses the durable conversation's native harness, worktree and session. */
6
6
  export declare function conversationTerminal(ctx: Ctx, ws: Workspace, c: Conversation, project: string, values: Values): Promise<number>;
7
+ /**
8
+ * A conversation and its VM, for an app to open (Claude Desktop, claude.ai, T3 Code, the menu bar). A conversation
9
+ * whose VM was removed after it finished restores first (0.18.1+): a fresh VM from its saved branch and transcript, the
10
+ * same conversation and its session resumed. A terminal conversation's session starts detached, as a new one does, so
11
+ * the app reaches it at once; the guest starts the others.
12
+ */
13
+ export declare function reopenConversation(ctx: Ctx, c: Conversation, values: Values): Promise<{
14
+ conversation: Conversation;
15
+ ws: Workspace;
16
+ }>;
7
17
  /**
8
18
  * `shard ./` (and `agents new --in auto`): new conversations open where `agents targets` says (open_in, 0.17.0+): T3
9
19
  * Code mode, Claude Desktop or claude.ai for Claude (the conversation's Remote Control session), else this terminal.
@@ -23,6 +23,7 @@ import { launchT3 } from "./t3-launch.js";
23
23
  import { openClaudeDesktopSession, remoteControlUrl } from "./agent-target-commands.js";
24
24
  import { openUrl } from "./browser.js";
25
25
  import { out } from "./commands.js";
26
+ import { progress } from "./progress.js";
26
27
  /** Every terminal entry point uses the durable conversation's native harness, worktree and session. */
27
28
  export async function conversationTerminal(ctx, ws, c, project, values) {
28
29
  if (c.route === 'codex-cloud')
@@ -32,6 +33,27 @@ export async function conversationTerminal(ctx, ws, c, project, values) {
32
33
  const pty = ws.labels.shard_conversation && ws.labels.shard_conversation !== c.id ? conversationName(c) : undefined;
33
34
  return terminal(ctx, ws, c.harness, conversationName(c), project, values, undefined, c.worktree_path, { id: c.id, session: c.harness_session_id, title: c.title, ...(pty ? { pty } : {}), choice: recordedChoice(c) });
34
35
  }
36
+ /**
37
+ * A conversation and its VM, for an app to open (Claude Desktop, claude.ai, T3 Code, the menu bar). A conversation
38
+ * whose VM was removed after it finished restores first (0.18.1+): a fresh VM from its saved branch and transcript, the
39
+ * same conversation and its session resumed. A terminal conversation's session starts detached, as a new one does, so
40
+ * the app reaches it at once; the guest starts the others.
41
+ */
42
+ export async function reopenConversation(ctx, c, values) {
43
+ if (!c.ended_at || c.route === 'codex-cloud')
44
+ return { conversation: c, ws: await conversationWorkspace(ctx, c) };
45
+ const restored = await resumeConversation(ctx, c);
46
+ const ws = await conversationWorkspace(ctx, restored);
47
+ if (restored.route !== 'terminal') {
48
+ progress(ctx, 'Restored');
49
+ return { conversation: restored, ws };
50
+ }
51
+ if (!(await savedMachine(ctx))?.template)
52
+ await syncAgentConfig(ctx, ws);
53
+ const quiet = { ...ctx, io: { ...ctx.io, stdout: { write: () => true } } };
54
+ await conversationTerminal(quiet, ws, restored, ws.labels.repo_remote || ws.labels.shard_project || ws.key, { ...values, detach: true });
55
+ return { conversation: restored, ws };
56
+ }
35
57
  /**
36
58
  * `shard ./` (and `agents new --in auto`): new conversations open where `agents targets` says (open_in, 0.17.0+): T3
37
59
  * Code mode, Claude Desktop or claude.ai for Claude (the conversation's Remote Control session), else this terminal.
@@ -1,4 +1,4 @@
1
- import type { Workspace, WorkspaceView } from '@shardflux/sdk';
1
+ import type { CellClient, Workspace, WorkspaceView } from '@shardflux/sdk';
2
2
  import type { Ctx } from './commands.js';
3
3
  import type { ModelChoice } from './agent-models.js';
4
4
  export type Harness = 'claude' | 'codex';
@@ -65,6 +65,13 @@ export interface LocalConversation {
65
65
  */
66
66
  export declare const conversationEnded: (c: Pick<Conversation, "state" | "ended_at" | "route" | "landed">) => boolean;
67
67
  export declare const conversationName: (c: Conversation) => string;
68
+ /**
69
+ * The managed PTY a conversation's harness runs in (0.18.1+). The guest records it per conversation
70
+ * (sf-agent's conversation runtime record), which also keeps the PTY a fork inherited from its parent; without a
71
+ * record it is the one the guest names after the conversation, `shard-<name>-<harness>`. Never the VM key's name: a
72
+ * conversation's VM is `<project>/conv-<name>`.
73
+ */
74
+ export declare function conversationPtyId(cell: Pick<CellClient, 'exec'>, c: Conversation, signal?: AbortSignal): Promise<string>;
68
75
  export declare function validateConversation(c: Conversation): Conversation;
69
76
  export declare function localConversations(ctx: Pick<Ctx, 'store' | 'apiUrl'>): Record<string, LocalConversation>;
70
77
  export declare function rememberConversation(ctx: Pick<Ctx, 'store' | 'apiUrl'>, c: Conversation, folder: string, handedOff?: boolean, baseBranch?: string): void;
@@ -9,6 +9,7 @@ import { UsageError } from "./errors.js";
9
9
  import { conversationClient } from "./conversation-commands.js";
10
10
  import { recordSeedSnapshot } from "./files-mirror.js";
11
11
  import { rememberTwinSeed, sendTwinSeed, twinCheckoutArgv, twinSeed } from "./worktree-twin.js";
12
+ import { progress } from "./progress.js";
12
13
  /**
13
14
  * Whether the conversation has ended: its VM was reclaimed, or it was archived or discarded. A finished turn is `done`
14
15
  * while its VM still takes the next turn; on Codex Cloud, `done` ends the conversation.
@@ -20,6 +21,37 @@ export const conversationName = (c) => {
20
21
  throw new Error('Conversation returned an invalid branch or name');
21
22
  return name;
22
23
  };
24
+ /** The guest's managed PTY id: `shard-<name>-<app>` (services/guest-agent/internal/agents PtyID). */
25
+ const MANAGED_PTY = /^shard-[a-z0-9][a-z0-9._-]{0,63}-(claude|codex)$/;
26
+ /**
27
+ * The managed PTY a conversation's harness runs in (0.18.1+). The guest records it per conversation
28
+ * (sf-agent's conversation runtime record), which also keeps the PTY a fork inherited from its parent; without a
29
+ * record it is the one the guest names after the conversation, `shard-<name>-<harness>`. Never the VM key's name: a
30
+ * conversation's VM is `<project>/conv-<name>`.
31
+ */
32
+ export async function conversationPtyId(cell, c, signal) {
33
+ const derived = `shard-${conversationName(c)}-${c.harness}`;
34
+ let stdout;
35
+ try {
36
+ const r = await cell.exec.run(['cat', `/home/user/.local/share/shardflux/conversation-runtime/${c.id}.json`], { timeoutMs: 10_000, maxOutputBytes: 4096, ...(signal ? { signal } : {}) });
37
+ if (r.exitCode !== 0)
38
+ return derived;
39
+ stdout = r.stdout;
40
+ }
41
+ catch (error) {
42
+ if (signal?.aborted)
43
+ throw error;
44
+ return derived;
45
+ }
46
+ let recorded;
47
+ try {
48
+ recorded = JSON.parse(stdout)?.pty_id;
49
+ }
50
+ catch {
51
+ return derived;
52
+ }
53
+ return typeof recorded === 'string' && MANAGED_PTY.test(recorded) && recorded.endsWith(`-${c.harness}`) ? recorded : derived;
54
+ }
23
55
  export function validateConversation(c) {
24
56
  if (!c || !/^conv_[a-z0-9]{26}$/.test(c.id) || !c.project_id || !c.workspace_id || !c.worktree_path?.startsWith('/home/user/') || /[\0\r\n]/.test(c.worktree_path) || !['claude', 'codex'].includes(c.harness))
25
57
  throw new Error('Conversation response omitted its identity or binding');
@@ -120,6 +152,9 @@ export async function restoreConversationBinding(ctx, c, ws) {
120
152
  const result = await cell.exec.run(['sf-agent', 'conversation', 'restore', '--args', '{"start":false}'], { cwd: c.worktree_path, env: { SF_CONVERSATION_ID: c.id }, timeoutMs: 120_000, signal: ctx.signal });
121
153
  if (result.exitCode !== 0)
122
154
  throw new Error(`Conversation restore failed: ${result.stderr.trim()}`);
155
+ // A conversation whose VM was removed now runs in a fresh one, from its saved branch and transcript (0.18.1+).
156
+ if (c.restore.kind === 'resume')
157
+ progress(ctx, 'Restored');
123
158
  }
124
159
  export async function createConversationRecord(ctx, folder, harness, route, name, allocated, idempotencyKey = randomUUID(), choice = {}) {
125
160
  const project = projectIdentity(folder);
package/dist/t3-launch.js CHANGED
@@ -125,7 +125,9 @@ export async function openT3Conversation(ctx, conversationId, values = {}) {
125
125
  return 0;
126
126
  }
127
127
  // Its VM as T3's SSH environment, with the checks every `agents open t3` makes: --reachable and the cloud Claude login.
128
- let ws = await conversationWorkspace(ctx, conversation);
128
+ // A conversation whose VM was removed restores into a fresh one first (0.18.1+).
129
+ const { reopenConversation } = await import("./conversation-terminal.js");
130
+ let { ws } = await reopenConversation(ctx, conversation, values);
129
131
  if (values.reachable !== undefined)
130
132
  ws = await ctx.cloud().workspaces.open({ key: ws.key, ...agentReachability(values), wait: false });
131
133
  // A pooled thread (0.18.0+) runs on the account hub, not on the account's cloud Claude login.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/cli",
3
- "version": "0.18.0",
3
+ "version": "0.18.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",
@@ -58,8 +58,8 @@
58
58
  "typescript-eslint": "8.70.1"
59
59
  },
60
60
  "optionalDependencies": {
61
- "@shardflux/desktop-linux-x64": "0.1.0",
62
- "@shardflux/desktop-linux-arm64": "0.1.0",
61
+ "@shardflux/desktop-linux-x64": "0.1.1",
62
+ "@shardflux/desktop-linux-arm64": "0.1.1",
63
63
  "@shardflux/menubar-darwin": "^0.6.0"
64
64
  },
65
65
  "scripts": {