@shardflux/cli 0.25.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 +154 -1
- package/README.md +212 -36
- package/dist/account-commands.d.ts +2 -4
- package/dist/account-commands.js +2 -6
- package/dist/account-pool.d.ts +11 -1
- package/dist/account-pool.js +48 -0
- package/dist/agent-commands.js +4 -3
- package/dist/agent-git-commands.d.ts +1 -2
- package/dist/agent-git-commands.js +2 -21
- package/dist/agent-models.d.ts +31 -1
- package/dist/agent-models.js +27 -4
- 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 +67 -12
- package/dist/board-args.js +8 -3
- package/dist/board-commands.d.ts +7 -3
- package/dist/board-commands.js +22 -12
- package/dist/brain-commands.d.ts +2 -0
- package/dist/brain-commands.js +78 -0
- package/dist/cli-proxy.d.ts +1 -13
- package/dist/cli-proxy.js +2 -27
- package/dist/client-settings.d.ts +2 -1
- package/dist/client-settings.js +14 -2
- package/dist/codex-cloud.d.ts +2 -0
- package/dist/codex-cloud.js +17 -12
- package/dist/commands.js +8 -0
- package/dist/conversation-commands.d.ts +5 -1
- package/dist/conversation-commands.js +58 -6
- package/dist/conversation-terminal.js +2 -2
- package/dist/desktop-linux.d.ts +12 -1
- package/dist/desktop-linux.js +47 -18
- package/dist/device-approvals.js +24 -2
- package/dist/device-bridge.d.ts +24 -4
- package/dist/device-bridge.js +181 -8
- package/dist/device-folders.d.ts +6 -1
- package/dist/device-folders.js +6 -2
- package/dist/device-helper.d.ts +8 -0
- package/dist/device-helper.js +71 -12
- package/dist/device-mode.d.ts +44 -0
- package/dist/device-mode.js +119 -0
- package/dist/drive-commands.d.ts +2 -2
- package/dist/drive-commands.js +1 -1
- 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/handlers.js +4 -2
- package/dist/land.d.ts +10 -5
- package/dist/land.js +56 -9
- package/dist/linux-helper.js +1 -2
- package/dist/mac-app.d.ts +11 -0
- package/dist/mac-app.js +11 -0
- 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 +19 -16
- package/dist/menubar.d.ts +18 -13
- package/dist/menubar.js +60 -50
- package/dist/orchestrator-args.d.ts +7 -0
- package/dist/orchestrator-args.js +64 -0
- package/dist/orchestrator-commands.d.ts +43 -0
- package/dist/orchestrator-commands.js +260 -0
- package/dist/person-scope.d.ts +20 -0
- package/dist/person-scope.js +61 -0
- package/dist/project-hub-seed.d.ts +20 -0
- package/dist/project-hub-seed.js +59 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +2 -1
- package/dist/router-commands.d.ts +12 -21
- package/dist/router-commands.js +5 -59
- package/dist/startup-housekeeping.d.ts +5 -0
- package/dist/startup-housekeeping.js +30 -0
- package/dist/t3-launch.js +2 -2
- package/dist/t3-provider.js +4 -2
- package/dist/task-land-hub.py +16 -1
- package/dist/this-computer.d.ts +4 -2
- package/dist/this-computer.js +20 -2
- package/dist/thread-on-computer.js +2 -2
- package/dist/timing.js +3 -1
- package/dist/transcribe-commands.js +6 -2
- package/dist/update.js +4 -2
- package/dist/wait-status.d.ts +1 -1
- package/dist/wait-status.js +2 -0
- package/dist/worktree-twin-engine.d.ts +1 -1
- package/dist/worktree-twin-engine.js +10 -7
- package/dist/worktree-twin.d.ts +6 -0
- package/dist/worktree-twin.js +8 -1
- package/package.json +12 -6
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);
|
package/dist/task-land-hub.py
CHANGED
|
@@ -11,7 +11,10 @@ Input: one JSON object on stdin. Output: one JSON object on stdout (exit 0; "sta
|
|
|
11
11
|
title for the merge commit's message
|
|
12
12
|
|
|
13
13
|
The result commit T lands in the default branch D: a fast-forward when D is an ancestor of T, nothing when T is already
|
|
14
|
-
in D, else a merge commit when the merge is clean (a conflict: state "conflict", stage "default"; nothing changes).
|
|
14
|
+
in D, else a merge commit when the merge is clean (a conflict: state "conflict", stage "default"; nothing changes).
|
|
15
|
+
Commits with no history in common (a result started from another repository than the one D holds, or two workers from
|
|
16
|
+
different repositories) never merge: state "unrelated" with its stage, nothing changes. Unlike a conflict it does not
|
|
17
|
+
clear by trying again. The
|
|
15
18
|
hub's branches move only by `git push` from a temporary repository, never by force, so a branch someone else moved
|
|
16
19
|
meanwhile is refused (stage "raced") instead of overwritten. Before answering "landed" it checks on the hub itself that
|
|
17
20
|
T is contained in D, and which sources are contained in T.
|
|
@@ -55,6 +58,11 @@ def ancestor(cwd, a, b):
|
|
|
55
58
|
return git(cwd, 'merge-base', '--is-ancestor', a, b, ok=(0, 1)).returncode == 0
|
|
56
59
|
|
|
57
60
|
|
|
61
|
+
def related(cwd, a, b):
|
|
62
|
+
"""Whether a and b have any history in common (git finds a merge base)."""
|
|
63
|
+
return git(cwd, 'merge-base', a, b, ok=(0, 1)).returncode == 0
|
|
64
|
+
|
|
65
|
+
|
|
58
66
|
def merge(repo, ours, theirs, message):
|
|
59
67
|
"""A merge commit of theirs into ours when the merge is clean, else (None, conflicting paths)."""
|
|
60
68
|
r = git(repo, 'merge-tree', '--write-tree', '--name-only', '--no-messages', '-z', ours, theirs, ok=(0, 1))
|
|
@@ -107,6 +115,8 @@ def land(a):
|
|
|
107
115
|
if not t or ancestor(work, t, st):
|
|
108
116
|
t = st
|
|
109
117
|
elif not ancestor(work, st, t):
|
|
118
|
+
if not related(work, t, st):
|
|
119
|
+
return {'state': 'unrelated', 'stage': 'integrate', 'into': into, 'source': s}
|
|
110
120
|
t, conflicts = merge(work, t, st, 'Merge %s into %s' % (s, result))
|
|
111
121
|
if not t:
|
|
112
122
|
return {'state': 'conflict', 'stage': 'integrate', 'into': into, 'source': s, 'conflicts': conflicts}
|
|
@@ -120,6 +130,11 @@ def land(a):
|
|
|
120
130
|
elif not before or ancestor(work, before, t):
|
|
121
131
|
method, after = ('created' if not before else 'ff'), t
|
|
122
132
|
else:
|
|
133
|
+
if not related(work, before, t):
|
|
134
|
+
# Another repository's branch (a project's hub holds every folder its people linked): the result still
|
|
135
|
+
# lands in its own folder; the sources it holds say which workers' work that is.
|
|
136
|
+
contained = [s for s in sources if have.get(s) and ancestor(work, have[s], t)]
|
|
137
|
+
return {'state': 'unrelated', 'stage': 'default', 'into': into, 'result': t, 'before': before, 'contained': contained}
|
|
123
138
|
after, conflicts = merge(work, before, t, 'Land %s (%s)' % (title, result))
|
|
124
139
|
if not after:
|
|
125
140
|
return {'state': 'conflict', 'stage': 'default', 'into': into, 'result': t, 'before': before, 'conflicts': conflicts}
|
package/dist/this-computer.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ export declare const yourComputer: (platform?: NodeJS.Platform) => string;
|
|
|
4
4
|
export declare const LINUX_DEFAULT_SHORTCUT = "<Alt><Shift>space";
|
|
5
5
|
/** A GTK accelerator ("<Control><Shift>space") as the Linux app shows it ("Ctrl+Shift+Space"); shortcutLabel in its core.mjs. */
|
|
6
6
|
export declare function acceleratorLabel(accelerator: string): string;
|
|
7
|
+
/** An Electron accelerator ("Alt+Shift+Space", the one app's desktop-app.json) as the person reads it. */
|
|
8
|
+
export declare function electronAcceleratorLabel(accelerator: string, platform: NodeJS.Platform): string;
|
|
7
9
|
/**
|
|
8
|
-
* The launcher's shortcut as the person presses it:
|
|
9
|
-
* on Linux
|
|
10
|
+
* The launcher's shortcut as the person presses it: the one app's (desktop-app.json in the config directory, 0.27.0+),
|
|
11
|
+
* else ⌥⇧Space on macOS (the menu bar app's) and the Linux app's configured one on Linux; undefined elsewhere.
|
|
10
12
|
*/
|
|
11
13
|
export declare function launcherShortcut(configDir: string, platform?: NodeJS.Platform): string | undefined;
|
package/dist/this-computer.js
CHANGED
|
@@ -16,11 +16,29 @@ export function acceleratorLabel(accelerator) {
|
|
|
16
16
|
const key = accelerator.replace(/<\w+>/g, '');
|
|
17
17
|
return [...mods, key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1)].join('+');
|
|
18
18
|
}
|
|
19
|
+
/** An Electron accelerator ("Alt+Shift+Space", the one app's desktop-app.json) as the person reads it. */
|
|
20
|
+
export function electronAcceleratorLabel(accelerator, platform) {
|
|
21
|
+
const parts = accelerator.split('+');
|
|
22
|
+
const key = parts.pop() ?? '';
|
|
23
|
+
if (platform === 'darwin') {
|
|
24
|
+
const symbols = { Control: '⌃', Ctrl: '⌃', Alt: '⌥', Option: '⌥', Shift: '⇧', Command: '⌘', Cmd: '⌘', CommandOrControl: '⌘', CmdOrCtrl: '⌘' };
|
|
25
|
+
const order = ['⌃', '⌥', '⇧', '⌘'];
|
|
26
|
+
return parts.map(p => symbols[p] ?? p).sort((a, b) => order.indexOf(a) - order.indexOf(b)).join('') + key;
|
|
27
|
+
}
|
|
28
|
+
const names = { Control: 'Ctrl', CommandOrControl: 'Ctrl', CmdOrCtrl: 'Ctrl', Command: 'Super', Cmd: 'Super', Super: 'Super', Meta: 'Super' };
|
|
29
|
+
return [...parts.map(p => names[p] ?? p), key].join('+');
|
|
30
|
+
}
|
|
19
31
|
/**
|
|
20
|
-
* The launcher's shortcut as the person presses it:
|
|
21
|
-
* on Linux
|
|
32
|
+
* The launcher's shortcut as the person presses it: the one app's (desktop-app.json in the config directory, 0.27.0+),
|
|
33
|
+
* else ⌥⇧Space on macOS (the menu bar app's) and the Linux app's configured one on Linux; undefined elsewhere.
|
|
22
34
|
*/
|
|
23
35
|
export function launcherShortcut(configDir, platform = process.platform) {
|
|
36
|
+
try {
|
|
37
|
+
const app = JSON.parse(readFileSync(join(configDir, 'desktop-app.json'), 'utf8'));
|
|
38
|
+
if (typeof app.launcher_shortcut === 'string' && /^([A-Za-z]+\+)+\S+$/.test(app.launcher_shortcut) && (platform === 'darwin' || platform === 'linux'))
|
|
39
|
+
return electronAcceleratorLabel(app.launcher_shortcut, platform);
|
|
40
|
+
}
|
|
41
|
+
catch { /* the app has not written it (an older app, or none) */ }
|
|
24
42
|
if (platform === 'darwin')
|
|
25
43
|
return '⌥⇧Space';
|
|
26
44
|
if (platform !== 'linux')
|
|
@@ -19,7 +19,7 @@ import { cloudClaudeLogin, loginClaudeInWorkspace, requireClaudeTerminal } from
|
|
|
19
19
|
import { promptWithImages, readInitialImages, readInitialPrompt, uploadInitialImages } from "./initial-prompt.js";
|
|
20
20
|
import { recordSeedSnapshot } from "./files-mirror.js";
|
|
21
21
|
import { ensureCodexCloud, printCloudThread, submitCloudInitialPrompt } from "./codex-cloud.js";
|
|
22
|
-
import { modelChoice } from "./agent-models.js";
|
|
22
|
+
import { liveModels, modelChoice } from "./agent-models.js";
|
|
23
23
|
import { storedTitlePrefix } from "./client-settings.js";
|
|
24
24
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
25
25
|
const SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
|
|
@@ -143,7 +143,7 @@ export async function startThreadOn(ctx, values, path, explicitApp) {
|
|
|
143
143
|
const review = values.review === true;
|
|
144
144
|
if (review && app === 'codex')
|
|
145
145
|
throw new UsageError('--review starts a read-only Claude reviewer. A Codex thread on a computer works on its own branch; omit --review.');
|
|
146
|
-
const choice = modelChoice(values, app);
|
|
146
|
+
const choice = modelChoice(values, app, await liveModels(ctx, values, app));
|
|
147
147
|
const name = str(values.name);
|
|
148
148
|
if (name !== undefined && !/^[a-z0-9][a-z0-9-]{0,47}$/.test(name))
|
|
149
149
|
throw new UsageError('--name must match ^[a-z0-9][a-z0-9-]{0,47}$');
|
package/dist/timing.js
CHANGED
|
@@ -53,13 +53,15 @@ export class TimingRecorder {
|
|
|
53
53
|
return own && own.action !== 'token' ? own : this.last;
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
/** A resume that booted
|
|
56
|
+
/** A resume that booted its disk: files kept, every process restarted (never a null memory_restored). */
|
|
57
57
|
function restartedProcesses(t) {
|
|
58
58
|
return t.server?.resumePath === 'cold_boot' && t.server.memoryRestored === false;
|
|
59
59
|
}
|
|
60
60
|
/** Cold boot reasons the notice says in words (0.8.1+); any other reason is printed as the API sent it. */
|
|
61
61
|
const COLD_BOOT_WORDS = {
|
|
62
62
|
host_lost: 'the machine it ran on failed',
|
|
63
|
+
guest_unresponsive: 'the guest did not become ready after bounded restore attempts',
|
|
64
|
+
guest_filesystem: 'the filesystem was repaired offline',
|
|
63
65
|
};
|
|
64
66
|
/**
|
|
65
67
|
* The one-line stderr notice after a command whose resume booted the workspace's saved disk (0.6.0+), with or without
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { readFile } from 'node:fs/promises';
|
|
7
7
|
import { extname } from 'node:path';
|
|
8
|
-
import { audioFormatOf } from '@shardflux/sdk';
|
|
8
|
+
import { ShardfluxApiError, audioFormatOf } from '@shardflux/sdk';
|
|
9
9
|
import { out, str } from "./commands.js";
|
|
10
10
|
import { EXIT, UsageError } from "./errors.js";
|
|
11
|
-
import { inScope } from "./
|
|
11
|
+
import { inScope } from "./person-scope.js";
|
|
12
12
|
/** What one transcription holds (the API's bound; past it the rest is not transcribed and `truncated` says so). */
|
|
13
13
|
export const TRANSCRIBE_MAX_BYTES = 6 * 1024 * 1024;
|
|
14
14
|
const FORMATS = ['webm', 'wav', 'pcm16'];
|
|
@@ -74,6 +74,10 @@ export const TRANSCRIBE_HANDLERS = {
|
|
|
74
74
|
catch (err) {
|
|
75
75
|
if (ctx.signal.aborted)
|
|
76
76
|
return EXIT.interrupted;
|
|
77
|
+
if (err instanceof ShardfluxApiError && err.reason === 'cloud_transcription_off') {
|
|
78
|
+
ctx.io.stderr.write('Cloud transcription is not on for this organization (cloud_transcription_off).\n');
|
|
79
|
+
return EXIT.error;
|
|
80
|
+
}
|
|
77
81
|
throw err;
|
|
78
82
|
}
|
|
79
83
|
},
|
package/dist/update.js
CHANGED
|
@@ -18,12 +18,14 @@ import { AgentStateStore } from "./agent-state.js";
|
|
|
18
18
|
import { writePrivateFile } from "./credentials.js";
|
|
19
19
|
import { CodedError } from "./errors.js";
|
|
20
20
|
import { json } from "./format.js";
|
|
21
|
+
import { macAppPackage, ONE_APP_ON_MAC } from "./mac-app.js";
|
|
21
22
|
import { compareAppVersions, MenubarManager, packagedAppPath } from "./menubar.js";
|
|
22
23
|
export const INSTALL_FILE = 'install.json';
|
|
23
24
|
export const LAST_UPDATE_FILE = 'last-update.json';
|
|
24
25
|
const LOCK_FILE = 'update.lock';
|
|
25
26
|
const MCP = '@shardflux/mcp';
|
|
26
|
-
|
|
27
|
+
/** The npm package of the macOS app on this Mac: the menu bar app, or the one Shardflux app once it takes over. */
|
|
28
|
+
const MENUBAR = macAppPackage();
|
|
27
29
|
/** Finds the package directory of `pkg` above `script` and how it was installed. */
|
|
28
30
|
export function detectInstall(script, pkg, platform = process.platform) {
|
|
29
31
|
let real = script;
|
|
@@ -100,7 +102,7 @@ export function displayCommand(install, specs) {
|
|
|
100
102
|
}
|
|
101
103
|
/** Prefixes can contain spaces, quotes or shell substitutions. The actual install still uses argv. */
|
|
102
104
|
const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
103
|
-
const LABELS = { shardflux: 'shardflux', '@shardflux/cli': 'shard', '@shardflux/sdk': 'SDK', [MENUBAR]: 'menu bar', [MCP]: 'MCP server' };
|
|
105
|
+
const LABELS = { shardflux: 'shardflux', '@shardflux/cli': 'shard', '@shardflux/sdk': 'SDK', [MENUBAR]: ONE_APP_ON_MAC ? 'Shardflux app' : 'menu bar', [MCP]: 'MCP server' };
|
|
104
106
|
/** Every local piece and its status against the catalog. */
|
|
105
107
|
export async function components(ctx, install, catalog, menubar = menubarManager(ctx)) {
|
|
106
108
|
const local = [[ctx.distribution.package, ctx.distribution.version]];
|
package/dist/wait-status.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function queueLine(e: Pick<Extract<ProgressEvent, {
|
|
|
14
14
|
type: 'phase';
|
|
15
15
|
}>, 'queuePosition' | 'scaleOut' | 'expectedReadyBy' | 'deadlineAt'>): string;
|
|
16
16
|
/** A call waiting to be admitted, in words. */
|
|
17
|
-
export declare function admissionLine(r: Pick<RetryRecord, 'reason' | 'queuePosition'>): string;
|
|
17
|
+
export declare function admissionLine(r: Pick<RetryRecord, 'reason' | 'queuePosition' | 'retryAfterSeconds'>): string;
|
|
18
18
|
/**
|
|
19
19
|
* The client listener that prints where a wait stands. Silent with `json`. A phase other than capacity_pending ends
|
|
20
20
|
* the queue, so a later queue prints again.
|
package/dist/wait-status.js
CHANGED
|
@@ -17,6 +17,8 @@ export function queueLine(e) {
|
|
|
17
17
|
/** A call waiting to be admitted, in words. */
|
|
18
18
|
export function admissionLine(r) {
|
|
19
19
|
const position = r.queuePosition ? ` (position ${r.queuePosition})` : '';
|
|
20
|
+
if (r.reason === 'admission_queued')
|
|
21
|
+
return `queued for admission${position}${r.retryAfterSeconds != null ? `; retrying in ${r.retryAfterSeconds} s` : ''}`;
|
|
20
22
|
if (r.reason === 'host_capacity')
|
|
21
23
|
return `waiting for room on the workspace's host${position}`;
|
|
22
24
|
if (r.reason === 'concurrent_workspaces')
|
|
@@ -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 */ }
|
|
@@ -624,7 +627,7 @@ export const TWIN_HANDLERS = {
|
|
|
624
627
|
ledger.setMode(project, action);
|
|
625
628
|
}
|
|
626
629
|
const mode = ledger.mode(project);
|
|
627
|
-
const text = mode === 'auto' ? 'Finished conversations land in your local branch automatically.' : mode === 'pr' ? 'Finished conversations open a pull request instead of landing locally.' : 'Automatic landing is off for this project.';
|
|
630
|
+
const text = !ledger.autoLand() ? 'Automatic landing is off on this computer: shard settings --auto-land on turns it back on.' : mode === 'auto' ? 'Finished conversations land in your local branch automatically.' : mode === 'pr' ? 'Finished conversations open a pull request instead of landing locally.' : 'Automatic landing is off for this project.';
|
|
628
631
|
out(ctx, `${text}\n`, { project_id: project, landing: mode });
|
|
629
632
|
return 0;
|
|
630
633
|
},
|
package/dist/worktree-twin.d.ts
CHANGED
|
@@ -265,7 +265,9 @@ export interface TwinConversationState {
|
|
|
265
265
|
last_error?: string;
|
|
266
266
|
latency_ms?: number;
|
|
267
267
|
}
|
|
268
|
+
/** `auto_land: false` (`shard settings --auto-land off`, the desktop app's Settings switch): nothing lands on this computer. */
|
|
268
269
|
interface TwinDoc {
|
|
270
|
+
auto_land?: boolean;
|
|
269
271
|
landing?: Record<string, LandingMode>;
|
|
270
272
|
conversations?: Record<string, TwinConversationState>;
|
|
271
273
|
}
|
|
@@ -273,8 +275,12 @@ export declare class TwinLedger {
|
|
|
273
275
|
readonly store: AgentStateStore;
|
|
274
276
|
constructor(dir: string);
|
|
275
277
|
read(): TwinDoc;
|
|
278
|
+
/** The project's landing, 'off' everywhere while this computer's switch is off. */
|
|
276
279
|
mode(project: string): LandingMode;
|
|
277
280
|
setMode(project: string, mode: LandingMode): void;
|
|
281
|
+
/** This computer's switch for every project (on unless turned off). */
|
|
282
|
+
autoLand(): boolean;
|
|
283
|
+
setAutoLand(on: boolean): void;
|
|
278
284
|
conversation(id: string): TwinConversationState;
|
|
279
285
|
patch(id: string, change: Partial<TwinConversationState>): void;
|
|
280
286
|
forget(id: string): void;
|
package/dist/worktree-twin.js
CHANGED
|
@@ -726,8 +726,15 @@ export class TwinLedger {
|
|
|
726
726
|
store;
|
|
727
727
|
constructor(dir) { this.store = new AgentStateStore(dir); }
|
|
728
728
|
read() { return this.store.read().worktree_twin ?? {}; }
|
|
729
|
-
|
|
729
|
+
/** The project's landing, 'off' everywhere while this computer's switch is off. */
|
|
730
|
+
mode(project) { const doc = this.read(); return doc.auto_land === false ? 'off' : doc.landing?.[project] ?? 'auto'; }
|
|
730
731
|
setMode(project, mode) { this.update(doc => { doc.landing = { ...doc.landing, [project]: mode }; }); }
|
|
732
|
+
/** This computer's switch for every project (on unless turned off). */
|
|
733
|
+
autoLand() { return this.read().auto_land !== false; }
|
|
734
|
+
setAutoLand(on) { this.update(doc => { if (on)
|
|
735
|
+
delete doc.auto_land;
|
|
736
|
+
else
|
|
737
|
+
doc.auto_land = false; }); }
|
|
731
738
|
conversation(id) { return this.read().conversations?.[id] ?? {}; }
|
|
732
739
|
patch(id, change) {
|
|
733
740
|
this.update(doc => { doc.conversations = { ...doc.conversations, [id]: { ...doc.conversations?.[id], ...change } }; });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
56
|
+
"@shardflux/sdk": "^0.30.1"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
59
|
"@eslint/js": "10.0.1",
|
|
@@ -60,9 +65,10 @@
|
|
|
60
65
|
"typescript-eslint": "8.70.1"
|
|
61
66
|
},
|
|
62
67
|
"optionalDependencies": {
|
|
63
|
-
"@shardflux/desktop-
|
|
64
|
-
"@shardflux/desktop-
|
|
65
|
-
"@shardflux/
|
|
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"
|
|
66
72
|
},
|
|
67
73
|
"scripts": {
|
|
68
74
|
"build": "node scripts/build.mjs",
|