@phnx-labs/agents-cli 1.20.35 → 1.20.37
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 +91 -0
- package/LICENSE +185 -21
- package/README.md +8 -4
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer.js +81 -0
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.js +1 -89
- package/dist/commands/events.js +3 -3
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +277 -11
- package/dist/commands/hosts.js +10 -6
- package/dist/commands/inspect.js +8 -9
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +156 -75
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.js +20 -8
- package/dist/commands/sessions-sync.d.ts +3 -0
- package/dist/commands/sessions-sync.js +44 -4
- package/dist/commands/sessions.d.ts +78 -1
- package/dist/commands/sessions.js +506 -66
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +25 -2
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.js +2 -1
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.js +24 -3
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +112 -8
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +57 -0
- package/dist/lib/daemon.js +196 -18
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/resolve-target.d.ts +24 -0
- package/dist/lib/devices/resolve-target.js +80 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +31 -13
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +79 -13
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +127 -8
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +39 -6
- package/dist/lib/hosts/logs.js +30 -1
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/ready.d.ts +29 -6
- package/dist/lib/hosts/ready.js +66 -15
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +14 -0
- package/dist/lib/hosts/tasks.js +15 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugins.js +16 -6
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +73 -0
- package/dist/lib/session/active.js +199 -22
- package/dist/lib/session/db.d.ts +2 -1
- package/dist/lib/session/db.js +41 -5
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +127 -29
- package/dist/lib/session/ghostty-tabs.d.ts +33 -0
- package/dist/lib/session/ghostty-tabs.js +126 -0
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.js +23 -20
- package/dist/lib/session/pid-registry.d.ts +1 -0
- package/dist/lib/session/pid-registry.js +24 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/relative-time.js +6 -2
- package/dist/lib/session/remote-active.js +19 -17
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +205 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +20 -4
- package/dist/lib/session/sync/config.d.ts +14 -15
- package/dist/lib/session/sync/config.js +60 -20
- package/dist/lib/session/types.d.ts +23 -0
- package/dist/lib/shims.d.ts +36 -6
- package/dist/lib/shims.js +91 -29
- package/dist/lib/ssh-exec.js +2 -0
- package/dist/lib/ssh-tunnel.js +2 -1
- package/dist/lib/startup/command-registry.d.ts +6 -0
- package/dist/lib/startup/command-registry.js +13 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +21 -13
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/index.d.ts +2 -1
- package/dist/lib/terminal/backends/index.js +3 -1
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/index.d.ts +4 -1
- package/dist/lib/terminal/index.js +4 -1
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/tmux/session.d.ts +10 -0
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +119 -13
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
|
@@ -46,6 +46,12 @@ export interface SessionMeta {
|
|
|
46
46
|
shortId: string;
|
|
47
47
|
agent: SessionAgentId;
|
|
48
48
|
timestamp: string;
|
|
49
|
+
/**
|
|
50
|
+
* Last-activity time (ISO): the last message timestamp when a parser computed
|
|
51
|
+
* it, else file mtime, else `timestamp`. This is the recency signal the
|
|
52
|
+
* listing sorts and labels by; `timestamp` stays the creation time.
|
|
53
|
+
*/
|
|
54
|
+
lastActivity?: string;
|
|
49
55
|
project?: string;
|
|
50
56
|
cwd?: string;
|
|
51
57
|
filePath: string;
|
|
@@ -78,6 +84,23 @@ export interface SessionMeta {
|
|
|
78
84
|
* `entrypoint` field ('sdk-cli' for team spawns, 'cli' for real sessions).
|
|
79
85
|
*/
|
|
80
86
|
isTeamOrigin?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* The machine (normalized hostname) this session's transcript originated on:
|
|
89
|
+
* the local machine for live-home sessions, or the origin machine parsed from
|
|
90
|
+
* the cross-machine mirror path (backups/<agent>/<machine>/…, see
|
|
91
|
+
* sync/agents.ts). Populated by discoverSessions for the listing/picker;
|
|
92
|
+
* undefined for sessions obtained outside that path.
|
|
93
|
+
*/
|
|
94
|
+
machine?: string;
|
|
95
|
+
/**
|
|
96
|
+
* True only for rows pulled from another machine over the live cross-machine
|
|
97
|
+
* fan-out (`remote-list.ts`) — their transcript is on that peer's disk, so
|
|
98
|
+
* reading/resuming has to hop back over SSH. Distinct from `machine`, which is
|
|
99
|
+
* also set on locally-readable synced mirrors: a mirror is machine-tagged but
|
|
100
|
+
* its `filePath` is a local path, so it must NOT be treated as remote. Set by
|
|
101
|
+
* `parseRemoteList`; transient (never persisted, stripped from --json).
|
|
102
|
+
*/
|
|
103
|
+
_remote?: boolean;
|
|
81
104
|
/** Terms that matched the current search query */
|
|
82
105
|
_matchedTerms?: string[];
|
|
83
106
|
/** BM25 relevance score from the most recent content-index search */
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export interface ConflictInfo {
|
|
|
77
77
|
* top-level entry add/remove — deep edits to plugin contents won't
|
|
78
78
|
* trigger auto-resync, run `agents sync` for that.
|
|
79
79
|
*/
|
|
80
|
-
export declare const SHIM_SCHEMA_VERSION =
|
|
80
|
+
export declare const SHIM_SCHEMA_VERSION = 22;
|
|
81
81
|
/**
|
|
82
82
|
* Generate the full bash shim script for the given agent. The returned string
|
|
83
83
|
* is written to ~/.agents/shims/{cliCommand} and made executable.
|
|
@@ -134,8 +134,19 @@ export declare function removeShim(agent: AgentId): boolean;
|
|
|
134
134
|
* generic node_modules/.bin branch.
|
|
135
135
|
* v10 — guard grok's `command -v grok` fallback against resolving to our own
|
|
136
136
|
* shims dir (same infinite re-exec loop), mirroring droid.
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
* v11 — export DISABLE_AUTOUPDATER=1 for claude aliases so a pinned per-version
|
|
138
|
+
* install can't self-mutate via Claude Code's background auto-updater.
|
|
139
|
+
* Explicit user value wins.
|
|
140
|
+
* v12 — Windows: stop writing the extensionless bash alias next to the `.cmd`
|
|
141
|
+
* (and delete a lingering one). The version suffix contains dots, so
|
|
142
|
+
* cmd.exe and PowerShell treat `claude@2.1.201` as a complete filename
|
|
143
|
+
* with extension `.201`, exact-match the bash script AHEAD of PATHEXT
|
|
144
|
+
* probing, and ShellExecute it to the `.sh` editor association — the
|
|
145
|
+
* editor opens the script and the agent never launches. The `.cmd` is
|
|
146
|
+
* now the only Windows artifact and carries this alias marker so
|
|
147
|
+
* staleness checks read it directly.
|
|
148
|
+
*/
|
|
149
|
+
export declare const VERSIONED_ALIAS_SCHEMA_VERSION = 12;
|
|
139
150
|
/**
|
|
140
151
|
* Generate a versioned alias script that directly execs a specific version.
|
|
141
152
|
* e.g., claude@2.0.65 -> directly runs that version's binary
|
|
@@ -157,20 +168,39 @@ export declare function isVersionedAliasCurrent(agent: AgentId, version: string)
|
|
|
157
168
|
*/
|
|
158
169
|
export declare function ensureVersionedAliasCurrent(agent: AgentId, version: string): 'created' | 'updated' | 'current';
|
|
159
170
|
/**
|
|
160
|
-
* Get the filesystem path for a versioned alias script
|
|
171
|
+
* Get the filesystem path for a versioned alias script — the logical
|
|
172
|
+
* (extensionless) launch name. On Windows this is not a real file (see
|
|
173
|
+
* versionedAliasOnDiskFile); stat/read checks must use the on-disk path.
|
|
161
174
|
*/
|
|
162
175
|
export declare function getVersionedAliasPath(agent: AgentId, version: string): string;
|
|
176
|
+
/**
|
|
177
|
+
* The file createVersionedAlias actually materializes for a platform:
|
|
178
|
+
* `<cmd>@<version>.cmd` on Windows, the bare bash script on POSIX. Pure —
|
|
179
|
+
* testable on any host. Mirrors onDiskShimFile for the main shim.
|
|
180
|
+
*/
|
|
181
|
+
export declare function versionedAliasOnDiskFile(cliCommand: string, version: string, platform: NodeJS.Platform): string;
|
|
163
182
|
/**
|
|
164
183
|
* Create a versioned alias for a specific agent version.
|
|
165
184
|
* e.g., claude@2.0.65
|
|
185
|
+
*
|
|
186
|
+
* Same platform split as createShim (shimTargetsFor): POSIX writes the
|
|
187
|
+
* extensionless bash script; Windows writes ONLY the `.cmd`. Unlike the main
|
|
188
|
+
* shim — where the bash file was merely dead weight — a bash alias next to the
|
|
189
|
+
* versioned `.cmd` is actively harmful: the dotted version suffix makes
|
|
190
|
+
* cmd.exe/PowerShell treat `claude@2.1.201` as a complete filename (extension
|
|
191
|
+
* `.201`), so the exact match wins over PATHEXT probing and the shell
|
|
192
|
+
* ShellExecutes the bash script to the `.sh` editor association — the editor
|
|
193
|
+
* opens, the agent never launches. Any legacy bash alias is deleted here.
|
|
166
194
|
*/
|
|
167
195
|
export declare function createVersionedAlias(agent: AgentId, version: string): string;
|
|
168
196
|
/**
|
|
169
|
-
* Remove a versioned alias for a specific agent version.
|
|
197
|
+
* Remove a versioned alias for a specific agent version. Removes whichever
|
|
198
|
+
* companions exist — the extensionless script (POSIX, or a legacy Windows
|
|
199
|
+
* install that wrote it) AND the `.cmd` (Windows) — mirroring removeShim.
|
|
170
200
|
*/
|
|
171
201
|
export declare function removeVersionedAlias(agent: AgentId, version: string): boolean;
|
|
172
202
|
/**
|
|
173
|
-
* Check if a versioned alias exists.
|
|
203
|
+
* Check if a versioned alias exists (the on-disk artifact for this platform).
|
|
174
204
|
*/
|
|
175
205
|
export declare function versionedAliasExists(agent: AgentId, version: string): boolean;
|
|
176
206
|
/**
|
package/dist/lib/shims.js
CHANGED
|
@@ -208,7 +208,10 @@ async function promptConflictStrategy(conflictInfos) {
|
|
|
208
208
|
// re-exec-looped through `command -v kimi` (the dispatcher itself).
|
|
209
209
|
// v21 — guard grok's `command -v grok` fallback against resolving to our own
|
|
210
210
|
// shims dir (same infinite re-exec loop), mirroring droid.
|
|
211
|
-
|
|
211
|
+
// v22 — export DISABLE_AUTOUPDATER=1 for claude shims so a pinned per-version
|
|
212
|
+
// install can't self-mutate: Claude Code's background auto-updater would
|
|
213
|
+
// otherwise rewrite the pinned binary in place. Explicit user value wins.
|
|
214
|
+
export const SHIM_SCHEMA_VERSION = 22;
|
|
212
215
|
/** Internal marker string used to embed the schema version in shim scripts. */
|
|
213
216
|
const SHIM_VERSION_MARKER = 'agents-shim-version:';
|
|
214
217
|
function shellQuote(value) {
|
|
@@ -236,6 +239,10 @@ export function generateShimScript(agent) {
|
|
|
236
239
|
# selected version's config directory so switching versions also switches the
|
|
237
240
|
# live Claude account.
|
|
238
241
|
export CLAUDE_CONFIG_DIR="$VERSION_DIR/home/${configDirName}"
|
|
242
|
+
# Managed installs are pinned in a per-version dir; Claude Code's background
|
|
243
|
+
# auto-updater would rewrite the pinned binary in place. Disable it so a pin
|
|
244
|
+
# stays a pin. An explicit user value always wins.
|
|
245
|
+
export DISABLE_AUTOUPDATER="\${DISABLE_AUTOUPDATER:-1}"
|
|
239
246
|
# On Linux sandboxes (no keychain), fall back to a per-version token file.
|
|
240
247
|
# The env var always wins if already set; no-op on macOS.
|
|
241
248
|
if [ "\$(uname -s)" = "Linux" ] && [ -z "\${CLAUDE_CODE_OAUTH_TOKEN:-}" ] && [ -f "\$CLAUDE_CONFIG_DIR/.oauth_token" ]; then
|
|
@@ -580,12 +587,18 @@ export function createShim(agent) {
|
|
|
580
587
|
* `cliCommand@version` for a versioned alias. node + the dist entrypoint are
|
|
581
588
|
* resolved at generation time so the launcher does not depend on `agents`
|
|
582
589
|
* already being on PATH.
|
|
590
|
+
*
|
|
591
|
+
* `extraMarkerLines` lets callers stamp additional schema markers into the
|
|
592
|
+
* header — versioned aliases embed their own alias-schema marker so
|
|
593
|
+
* readVersionedAliasSchemaVersion can stat the `.cmd` (the only Windows
|
|
594
|
+
* artifact) the same way it reads the bash script on POSIX.
|
|
583
595
|
*/
|
|
584
|
-
function writeWindowsCmdShim(cmdPath, spec) {
|
|
596
|
+
function writeWindowsCmdShim(cmdPath, spec, extraMarkerLines = []) {
|
|
585
597
|
const indexJs = getAgentsBinForGeneratedShim();
|
|
586
598
|
const content = `@echo off\r\n` +
|
|
587
599
|
`rem Auto-generated by agents-cli - do not edit\r\n` +
|
|
588
600
|
`rem ${SHIM_VERSION_MARKER} ${SHIM_SCHEMA_VERSION}\r\n` +
|
|
601
|
+
extraMarkerLines.map((line) => `rem ${line}\r\n`).join('') +
|
|
589
602
|
`node "${indexJs}" __shim ${spec} %*\r\n`;
|
|
590
603
|
fs.writeFileSync(cmdPath, content);
|
|
591
604
|
}
|
|
@@ -640,8 +653,19 @@ export function removeShim(agent) {
|
|
|
640
653
|
* generic node_modules/.bin branch.
|
|
641
654
|
* v10 — guard grok's `command -v grok` fallback against resolving to our own
|
|
642
655
|
* shims dir (same infinite re-exec loop), mirroring droid.
|
|
656
|
+
* v11 — export DISABLE_AUTOUPDATER=1 for claude aliases so a pinned per-version
|
|
657
|
+
* install can't self-mutate via Claude Code's background auto-updater.
|
|
658
|
+
* Explicit user value wins.
|
|
659
|
+
* v12 — Windows: stop writing the extensionless bash alias next to the `.cmd`
|
|
660
|
+
* (and delete a lingering one). The version suffix contains dots, so
|
|
661
|
+
* cmd.exe and PowerShell treat `claude@2.1.201` as a complete filename
|
|
662
|
+
* with extension `.201`, exact-match the bash script AHEAD of PATHEXT
|
|
663
|
+
* probing, and ShellExecute it to the `.sh` editor association — the
|
|
664
|
+
* editor opens the script and the agent never launches. The `.cmd` is
|
|
665
|
+
* now the only Windows artifact and carries this alias marker so
|
|
666
|
+
* staleness checks read it directly.
|
|
643
667
|
*/
|
|
644
|
-
export const VERSIONED_ALIAS_SCHEMA_VERSION =
|
|
668
|
+
export const VERSIONED_ALIAS_SCHEMA_VERSION = 12;
|
|
645
669
|
/** Internal marker string used to embed the schema version in versioned alias scripts. */
|
|
646
670
|
const VERSIONED_ALIAS_VERSION_MARKER = 'agents-versioned-alias-version:';
|
|
647
671
|
// The version string is interpolated into a generated bash script and into
|
|
@@ -669,6 +693,10 @@ export function generateVersionedAliasScript(agent, version) {
|
|
|
669
693
|
# Claude stores OAuth credentials in the macOS keychain. Scope them to this
|
|
670
694
|
# version's config directory so direct aliases also switch the live account.
|
|
671
695
|
export CLAUDE_CONFIG_DIR="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
|
|
696
|
+
# Managed installs are pinned in a per-version dir; Claude Code's background
|
|
697
|
+
# auto-updater would rewrite the pinned binary in place. Disable it so a pin
|
|
698
|
+
# stays a pin. An explicit user value always wins.
|
|
699
|
+
export DISABLE_AUTOUPDATER="\${DISABLE_AUTOUPDATER:-1}"
|
|
672
700
|
`
|
|
673
701
|
: agent === 'codex'
|
|
674
702
|
? `
|
|
@@ -765,7 +793,7 @@ exec "$BINARY"${launchArgs} "$@"
|
|
|
765
793
|
* alias doesn't exist or is a pre-v2 alias (no marker — treated as stale).
|
|
766
794
|
*/
|
|
767
795
|
export function readVersionedAliasSchemaVersion(agent, version) {
|
|
768
|
-
const aliasPath =
|
|
796
|
+
const aliasPath = versionedAliasOnDiskPath(agent, version);
|
|
769
797
|
if (!fs.existsSync(aliasPath))
|
|
770
798
|
return null;
|
|
771
799
|
try {
|
|
@@ -792,11 +820,19 @@ export function isVersionedAliasCurrent(agent, version) {
|
|
|
792
820
|
* was upgraded.
|
|
793
821
|
*/
|
|
794
822
|
export function ensureVersionedAliasCurrent(agent, version) {
|
|
795
|
-
|
|
796
|
-
if (!fs.existsSync(aliasPath)) {
|
|
823
|
+
if (!fs.existsSync(versionedAliasOnDiskPath(agent, version))) {
|
|
797
824
|
createVersionedAlias(agent, version);
|
|
798
825
|
return 'created';
|
|
799
826
|
}
|
|
827
|
+
// A lingering extensionless bash alias on Windows shadows the `.cmd` in both
|
|
828
|
+
// cmd.exe and PowerShell (the dotted version reads as a file extension, and
|
|
829
|
+
// an exact filename match beats PATHEXT probing), ShellExecuting the bash
|
|
830
|
+
// script to the `.sh` editor association instead of launching the agent.
|
|
831
|
+
// Regenerate regardless of the `.cmd`'s stamp so the shadow gets deleted.
|
|
832
|
+
if (shimTargetsFor(process.platform).cmd && fs.existsSync(getVersionedAliasPath(agent, version))) {
|
|
833
|
+
createVersionedAlias(agent, version);
|
|
834
|
+
return 'updated';
|
|
835
|
+
}
|
|
800
836
|
// Upgrade-only (newest-wins), same rationale as ensureShimCurrent: never
|
|
801
837
|
// downgrade an alias stamped by a newer install sharing the shims dir.
|
|
802
838
|
const onDisk = readVersionedAliasSchemaVersion(agent, version);
|
|
@@ -807,14 +843,38 @@ export function ensureVersionedAliasCurrent(agent, version) {
|
|
|
807
843
|
return 'current';
|
|
808
844
|
}
|
|
809
845
|
/**
|
|
810
|
-
* Get the filesystem path for a versioned alias script
|
|
846
|
+
* Get the filesystem path for a versioned alias script — the logical
|
|
847
|
+
* (extensionless) launch name. On Windows this is not a real file (see
|
|
848
|
+
* versionedAliasOnDiskFile); stat/read checks must use the on-disk path.
|
|
811
849
|
*/
|
|
812
850
|
export function getVersionedAliasPath(agent, version) {
|
|
813
851
|
return path.join(getShimsDir(), `${AGENTS[agent].cliCommand}@${version}`);
|
|
814
852
|
}
|
|
853
|
+
/**
|
|
854
|
+
* The file createVersionedAlias actually materializes for a platform:
|
|
855
|
+
* `<cmd>@<version>.cmd` on Windows, the bare bash script on POSIX. Pure —
|
|
856
|
+
* testable on any host. Mirrors onDiskShimFile for the main shim.
|
|
857
|
+
*/
|
|
858
|
+
export function versionedAliasOnDiskFile(cliCommand, version, platform) {
|
|
859
|
+
const name = `${cliCommand}@${version}`;
|
|
860
|
+
return shimTargetsFor(platform).cmd ? `${name}.cmd` : name;
|
|
861
|
+
}
|
|
862
|
+
/** The on-disk versioned-alias path for the current platform. */
|
|
863
|
+
function versionedAliasOnDiskPath(agent, version) {
|
|
864
|
+
return path.join(getShimsDir(), versionedAliasOnDiskFile(AGENTS[agent].cliCommand, version, process.platform));
|
|
865
|
+
}
|
|
815
866
|
/**
|
|
816
867
|
* Create a versioned alias for a specific agent version.
|
|
817
868
|
* e.g., claude@2.0.65
|
|
869
|
+
*
|
|
870
|
+
* Same platform split as createShim (shimTargetsFor): POSIX writes the
|
|
871
|
+
* extensionless bash script; Windows writes ONLY the `.cmd`. Unlike the main
|
|
872
|
+
* shim — where the bash file was merely dead weight — a bash alias next to the
|
|
873
|
+
* versioned `.cmd` is actively harmful: the dotted version suffix makes
|
|
874
|
+
* cmd.exe/PowerShell treat `claude@2.1.201` as a complete filename (extension
|
|
875
|
+
* `.201`), so the exact match wins over PATHEXT probing and the shell
|
|
876
|
+
* ShellExecutes the bash script to the `.sh` editor association — the editor
|
|
877
|
+
* opens, the agent never launches. Any legacy bash alias is deleted here.
|
|
818
878
|
*/
|
|
819
879
|
export function createVersionedAlias(agent, version) {
|
|
820
880
|
assertSafeVersion(version);
|
|
@@ -822,40 +882,42 @@ export function createVersionedAlias(agent, version) {
|
|
|
822
882
|
const shimsDir = getShimsDir();
|
|
823
883
|
const agentConfig = AGENTS[agent];
|
|
824
884
|
const aliasPath = path.join(shimsDir, `${agentConfig.cliCommand}@${version}`);
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
885
|
+
const targets = shimTargetsFor(process.platform);
|
|
886
|
+
if (targets.bash) {
|
|
887
|
+
fs.writeFileSync(aliasPath, generateVersionedAliasScript(agent, version), { mode: 0o755 });
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
try {
|
|
891
|
+
fs.unlinkSync(aliasPath);
|
|
892
|
+
}
|
|
893
|
+
catch { }
|
|
894
|
+
}
|
|
895
|
+
if (targets.cmd) {
|
|
896
|
+
writeWindowsCmdShim(aliasPath + '.cmd', `${agentConfig.cliCommand}@${version}`, [`${VERSIONED_ALIAS_VERSION_MARKER} ${VERSIONED_ALIAS_SCHEMA_VERSION}`]);
|
|
829
897
|
}
|
|
830
898
|
return aliasPath;
|
|
831
899
|
}
|
|
832
900
|
/**
|
|
833
|
-
* Remove a versioned alias for a specific agent version.
|
|
901
|
+
* Remove a versioned alias for a specific agent version. Removes whichever
|
|
902
|
+
* companions exist — the extensionless script (POSIX, or a legacy Windows
|
|
903
|
+
* install that wrote it) AND the `.cmd` (Windows) — mirroring removeShim.
|
|
834
904
|
*/
|
|
835
905
|
export function removeVersionedAlias(agent, version) {
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
const aliasPath
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
try {
|
|
843
|
-
fs.unlinkSync(aliasPath + '.cmd');
|
|
844
|
-
}
|
|
845
|
-
catch { }
|
|
906
|
+
const aliasPath = getVersionedAliasPath(agent, version);
|
|
907
|
+
let removed = false;
|
|
908
|
+
for (const p of [aliasPath, aliasPath + '.cmd']) {
|
|
909
|
+
if (fs.existsSync(p)) {
|
|
910
|
+
fs.unlinkSync(p);
|
|
911
|
+
removed = true;
|
|
846
912
|
}
|
|
847
|
-
return true;
|
|
848
913
|
}
|
|
849
|
-
return
|
|
914
|
+
return removed;
|
|
850
915
|
}
|
|
851
916
|
/**
|
|
852
|
-
* Check if a versioned alias exists.
|
|
917
|
+
* Check if a versioned alias exists (the on-disk artifact for this platform).
|
|
853
918
|
*/
|
|
854
919
|
export function versionedAliasExists(agent, version) {
|
|
855
|
-
|
|
856
|
-
const agentConfig = AGENTS[agent];
|
|
857
|
-
const aliasPath = path.join(shimsDir, `${agentConfig.cliCommand}@${version}`);
|
|
858
|
-
return fs.existsSync(aliasPath);
|
|
920
|
+
return fs.existsSync(versionedAliasOnDiskPath(agent, version));
|
|
859
921
|
}
|
|
860
922
|
/**
|
|
861
923
|
* Get the path to the agent's config directory in HOME.
|
package/dist/lib/ssh-exec.js
CHANGED
|
@@ -103,6 +103,7 @@ export function sshExec(target, remoteCmd, opts = {}) {
|
|
|
103
103
|
encoding: 'utf-8',
|
|
104
104
|
timeout: opts.timeoutMs,
|
|
105
105
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
106
|
+
windowsHide: true,
|
|
106
107
|
});
|
|
107
108
|
const timedOut = !!(res.error && res.error.code === 'ETIMEDOUT');
|
|
108
109
|
return {
|
|
@@ -128,6 +129,7 @@ export function sshExecRaw(target, remoteCmd, opts = {}) {
|
|
|
128
129
|
// No `encoding` → spawnSync returns Buffers.
|
|
129
130
|
timeout: opts.timeoutMs,
|
|
130
131
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
132
|
+
windowsHide: true,
|
|
131
133
|
});
|
|
132
134
|
const timedOut = !!(res.error && res.error.code === 'ETIMEDOUT');
|
|
133
135
|
return {
|
package/dist/lib/ssh-tunnel.js
CHANGED
|
@@ -23,6 +23,7 @@ import * as path from 'path';
|
|
|
23
23
|
import { fileURLToPath } from 'url';
|
|
24
24
|
import { randomBytes } from 'crypto';
|
|
25
25
|
import { sshExec, SSH_OPTS } from './ssh-exec.js';
|
|
26
|
+
import { backgroundSpawnOptions } from './platform/process.js';
|
|
26
27
|
import { encodePowerShell } from './browser/drivers/ssh.js';
|
|
27
28
|
import { getDevice } from './devices/registry.js';
|
|
28
29
|
import { sshTargetFor } from './devices/connect.js';
|
|
@@ -56,7 +57,7 @@ export function startSSHTunnel(user, host, localPort, remotePort, opts = {}) {
|
|
|
56
57
|
const args = buildTunnelArgs(user, host, localPort, remotePort);
|
|
57
58
|
const tunnel = spawn('ssh', args, {
|
|
58
59
|
stdio: opts.detached ? 'ignore' : ['ignore', 'ignore', 'pipe'],
|
|
59
|
-
|
|
60
|
+
...(opts.detached ? backgroundSpawnOptions() : { detached: false, windowsHide: true }),
|
|
60
61
|
});
|
|
61
62
|
let stderr = '';
|
|
62
63
|
tunnel.stderr?.on('data', (data) => {
|
|
@@ -49,6 +49,7 @@ export declare const loadPrune: ModuleLoader;
|
|
|
49
49
|
export declare const loadTrash: ModuleLoader;
|
|
50
50
|
export declare const loadRestore: ModuleLoader;
|
|
51
51
|
export declare const loadDoctor: ModuleLoader;
|
|
52
|
+
export declare const loadCheck: ModuleLoader;
|
|
52
53
|
export declare const loadStatus: ModuleLoader;
|
|
53
54
|
export declare const loadProfiles: ModuleLoader;
|
|
54
55
|
export declare const loadSecrets: ModuleLoader;
|
|
@@ -57,6 +58,7 @@ export declare const loadHelper: ModuleLoader;
|
|
|
57
58
|
export declare const loadMenubar: ModuleLoader;
|
|
58
59
|
export declare const loadBeta: ModuleLoader;
|
|
59
60
|
export declare const loadSync: ModuleLoader;
|
|
61
|
+
export declare const loadLock: ModuleLoader;
|
|
60
62
|
export declare const loadRefreshRules: ModuleLoader;
|
|
61
63
|
export declare const loadDrive: ModuleLoader;
|
|
62
64
|
export declare const loadFactory: ModuleLoader;
|
|
@@ -66,6 +68,7 @@ export declare const loadBudget: ModuleLoader;
|
|
|
66
68
|
export declare const loadAlias: ModuleLoader;
|
|
67
69
|
export declare const loadPty: ModuleLoader;
|
|
68
70
|
export declare const loadTmux: ModuleLoader;
|
|
71
|
+
export declare const loadWatchdog: ModuleLoader;
|
|
69
72
|
export declare const loadBrowser: ModuleLoader;
|
|
70
73
|
export declare const loadComputer: ModuleLoader;
|
|
71
74
|
export declare const loadHosts: ModuleLoader;
|
|
@@ -79,6 +82,9 @@ export declare const loadSetup: ModuleLoader;
|
|
|
79
82
|
export declare const loadSessions: ModuleLoader;
|
|
80
83
|
export declare const loadTeams: ModuleLoader;
|
|
81
84
|
export declare const loadCloud: ModuleLoader;
|
|
85
|
+
export declare const loadMessage: ModuleLoader;
|
|
86
|
+
export declare const loadServe: ModuleLoader;
|
|
87
|
+
export declare const loadAudit: ModuleLoader;
|
|
82
88
|
/**
|
|
83
89
|
* Commands whose modules pull in the SQLite-backed session/cloud stack. They are
|
|
84
90
|
* registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
|
|
@@ -27,6 +27,7 @@ export const loadPrune = async () => (await import('../../commands/prune.js')).r
|
|
|
27
27
|
export const loadTrash = async () => (await import('../../commands/trash.js')).registerTrashCommands;
|
|
28
28
|
export const loadRestore = async () => (await import('../../commands/trash.js')).registerRestoreCommand;
|
|
29
29
|
export const loadDoctor = async () => (await import('../../commands/doctor.js')).registerDoctorCommand;
|
|
30
|
+
export const loadCheck = async () => (await import('../../commands/check.js')).registerCheckCommand;
|
|
30
31
|
export const loadStatus = async () => (await import('../../commands/status.js')).registerStatusCommand;
|
|
31
32
|
export const loadProfiles = async () => (await import('../../commands/profiles.js')).registerProfilesCommands;
|
|
32
33
|
export const loadSecrets = async () => (await import('../../commands/secrets.js')).registerSecretsCommands;
|
|
@@ -35,6 +36,7 @@ export const loadHelper = async () => (await import('../../commands/helper.js'))
|
|
|
35
36
|
export const loadMenubar = async () => (await import('../../commands/menubar.js')).registerMenubarCommands;
|
|
36
37
|
export const loadBeta = async () => (await import('../../commands/beta.js')).registerBetaCommands;
|
|
37
38
|
export const loadSync = async () => (await import('../../commands/sync.js')).registerSyncCommand;
|
|
39
|
+
export const loadLock = async () => (await import('../../commands/lock.js')).registerLockCommand;
|
|
38
40
|
export const loadRefreshRules = async () => (await import('../../commands/refresh-rules.js')).registerRefreshRulesCommand;
|
|
39
41
|
export const loadDrive = async () => (await import('../../commands/drive.js')).registerDriveCommands;
|
|
40
42
|
export const loadFactory = async () => (await import('../../commands/factory.js')).registerFactoryCommands;
|
|
@@ -44,6 +46,7 @@ export const loadBudget = async () => (await import('../../commands/budget.js'))
|
|
|
44
46
|
export const loadAlias = async () => (await import('../../commands/alias.js')).registerAliasCommand;
|
|
45
47
|
export const loadPty = async () => (await import('../../commands/pty.js')).registerPtyCommands;
|
|
46
48
|
export const loadTmux = async () => (await import('../../commands/tmux.js')).registerTmuxCommands;
|
|
49
|
+
export const loadWatchdog = async () => (await import('../../commands/watchdog.js')).registerWatchdogCommand;
|
|
47
50
|
export const loadBrowser = async () => (await import('../../commands/browser.js')).registerBrowserCommand;
|
|
48
51
|
export const loadComputer = async () => (await import('../../commands/computer.js')).registerComputerCommand;
|
|
49
52
|
export const loadHosts = async () => (await import('../../commands/hosts.js')).registerHostsCommand;
|
|
@@ -57,6 +60,9 @@ export const loadSetup = async () => (await import('../../commands/setup.js')).r
|
|
|
57
60
|
export const loadSessions = async () => (await import('../../commands/sessions.js')).registerSessionsCommands;
|
|
58
61
|
export const loadTeams = async () => (await import('../../commands/teams.js')).registerTeamsCommands;
|
|
59
62
|
export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
|
|
63
|
+
export const loadMessage = async () => (await import('../../commands/message.js')).registerMessageCommand;
|
|
64
|
+
export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
|
|
65
|
+
export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
|
|
60
66
|
/**
|
|
61
67
|
* Commands whose modules pull in the SQLite-backed session/cloud stack. They are
|
|
62
68
|
* registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
|
|
@@ -64,7 +70,7 @@ export const loadCloud = async () => (await import('../../commands/cloud.js')).r
|
|
|
64
70
|
* inherit the root's custom help formatter rather than getting the per-command
|
|
65
71
|
* recursive pass. Keeping that ordering preserves their `--help` output exactly.
|
|
66
72
|
*/
|
|
67
|
-
export const LAZY_COMMAND_NAMES = new Set(['sessions', 'teams', 'cloud']);
|
|
73
|
+
export const LAZY_COMMAND_NAMES = new Set(['sessions', 'teams', 'cloud', 'message', 'serve']);
|
|
68
74
|
/**
|
|
69
75
|
* User-typed top-level command name -> ordered list of module loaders to run.
|
|
70
76
|
*
|
|
@@ -113,6 +119,7 @@ export const COMMAND_LOADERS = {
|
|
|
113
119
|
trash: [loadTrash],
|
|
114
120
|
restore: [loadRestore],
|
|
115
121
|
doctor: [loadDoctor],
|
|
122
|
+
check: [loadCheck],
|
|
116
123
|
status: [loadStatus],
|
|
117
124
|
profiles: [loadProfiles],
|
|
118
125
|
secrets: [loadSecrets],
|
|
@@ -121,6 +128,7 @@ export const COMMAND_LOADERS = {
|
|
|
121
128
|
menubar: [loadMenubar],
|
|
122
129
|
beta: [loadBeta],
|
|
123
130
|
sync: [loadSync],
|
|
131
|
+
lock: [loadLock],
|
|
124
132
|
'refresh-rules': [loadRefreshRules],
|
|
125
133
|
drive: [loadDrive],
|
|
126
134
|
factory: [loadFactory],
|
|
@@ -130,6 +138,7 @@ export const COMMAND_LOADERS = {
|
|
|
130
138
|
alias: [loadAlias],
|
|
131
139
|
pty: [loadPty],
|
|
132
140
|
tmux: [loadTmux],
|
|
141
|
+
watchdog: [loadWatchdog],
|
|
133
142
|
browser: [loadBrowser],
|
|
134
143
|
computer: [loadComputer],
|
|
135
144
|
hosts: [loadHosts],
|
|
@@ -144,4 +153,7 @@ export const COMMAND_LOADERS = {
|
|
|
144
153
|
sessions: [loadSessions],
|
|
145
154
|
teams: [loadTeams],
|
|
146
155
|
cloud: [loadCloud],
|
|
156
|
+
message: [loadMessage],
|
|
157
|
+
serve: [loadServe],
|
|
158
|
+
audit: [loadAudit],
|
|
147
159
|
};
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -120,6 +120,8 @@ export declare function getRoutinesDir(): string;
|
|
|
120
120
|
export declare function getProjectRoutinesDir(cwd?: string): string | null;
|
|
121
121
|
/** Path to routine execution logs (~/.agents/.history/runs/). */
|
|
122
122
|
export declare function getRunsDir(): string;
|
|
123
|
+
/** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
|
|
124
|
+
export declare function getMailboxRootDir(): string;
|
|
123
125
|
/** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
|
|
124
126
|
export declare function getVersionsDir(): string;
|
|
125
127
|
/** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
|
|
@@ -226,6 +228,17 @@ export declare function getEnabledExtraRepos(): Array<{
|
|
|
226
228
|
export declare function ensureAgentsDir(): void;
|
|
227
229
|
/** Return an empty Meta object used when no agents.yaml exists yet. */
|
|
228
230
|
export declare function createDefaultMeta(): Meta;
|
|
231
|
+
/**
|
|
232
|
+
* Per-device machine-local version pins — `~/.agents/devices/<machine>/agents.yaml`.
|
|
233
|
+
* Committed and synced, but each machine only ever writes its OWN folder, so
|
|
234
|
+
* pulls never conflict. `<machine>` = machineId() (Tailscale-aligned short name).
|
|
235
|
+
*/
|
|
236
|
+
export declare function getDeviceMetaPath(): string;
|
|
237
|
+
/**
|
|
238
|
+
* Machine-local per-version resource tracking — `~/.agents/.history/version-resources.json`.
|
|
239
|
+
* Gitignored (under .history/) and regenerable; never synced.
|
|
240
|
+
*/
|
|
241
|
+
export declare function getVersionResourcesPath(): string;
|
|
229
242
|
/**
|
|
230
243
|
* Read and cache ~/.agents/agents.yaml, migrating from legacy locations if needed.
|
|
231
244
|
*
|
package/dist/lib/state.js
CHANGED
|
@@ -28,6 +28,7 @@ import * as os from 'os';
|
|
|
28
28
|
import * as yaml from 'yaml';
|
|
29
29
|
import { ensureLockTarget, atomicWriteFileSync, withFileLock } from './fs-atomic.js';
|
|
30
30
|
import { SEEDED_REGISTRIES } from './types.js';
|
|
31
|
+
import { machineId } from './machine-id.js';
|
|
31
32
|
const HOME = process.env.HOME ?? os.homedir();
|
|
32
33
|
/**
|
|
33
34
|
* Compare two filesystem paths for identity, resolving symlinks and (on
|
|
@@ -87,6 +88,7 @@ const RUNS_DIR = path.join(HISTORY_DIR, 'runs');
|
|
|
87
88
|
const TEAMS_AGENTS_DIR = path.join(HISTORY_DIR, 'teams', 'agents');
|
|
88
89
|
const BACKUPS_DIR = path.join(HISTORY_DIR, 'backups');
|
|
89
90
|
const TRASH_DIR = path.join(HISTORY_DIR, 'trash');
|
|
91
|
+
const MAILBOX_DIR = path.join(HISTORY_DIR, 'mailbox');
|
|
90
92
|
// Cache bucket (regenerable).
|
|
91
93
|
const SHIMS_DIR = path.join(CACHE_DIR, 'shims');
|
|
92
94
|
const HOOK_SHIMS_DIR = path.join(SHIMS_DIR, 'hooks');
|
|
@@ -309,6 +311,8 @@ export function getProjectRoutinesDir(cwd = process.cwd()) {
|
|
|
309
311
|
}
|
|
310
312
|
/** Path to routine execution logs (~/.agents/.history/runs/). */
|
|
311
313
|
export function getRunsDir() { return RUNS_DIR; }
|
|
314
|
+
/** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
|
|
315
|
+
export function getMailboxRootDir() { return MAILBOX_DIR; }
|
|
312
316
|
/** Path to installed agent CLI binaries (~/.agents/.history/versions/). */
|
|
313
317
|
export function getVersionsDir() { return VERSIONS_DIR; }
|
|
314
318
|
/** Path to version-switching shim scripts (~/.agents/.cache/shims/). */
|
|
@@ -495,13 +499,37 @@ function safeMtimeMs(filePath) {
|
|
|
495
499
|
return 0;
|
|
496
500
|
}
|
|
497
501
|
}
|
|
498
|
-
/**
|
|
502
|
+
/**
|
|
503
|
+
* Per-device machine-local version pins — `~/.agents/devices/<machine>/agents.yaml`.
|
|
504
|
+
* Committed and synced, but each machine only ever writes its OWN folder, so
|
|
505
|
+
* pulls never conflict. `<machine>` = machineId() (Tailscale-aligned short name).
|
|
506
|
+
*/
|
|
507
|
+
export function getDeviceMetaPath() {
|
|
508
|
+
return path.join(USER_AGENTS_DIR, 'devices', machineId(), 'agents.yaml');
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Machine-local per-version resource tracking — `~/.agents/.history/version-resources.json`.
|
|
512
|
+
* Gitignored (under .history/) and regenerable; never synced.
|
|
513
|
+
*/
|
|
514
|
+
export function getVersionResourcesPath() {
|
|
515
|
+
return path.join(HISTORY_DIR, 'version-resources.json');
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Combined cache stamp across all four Meta sources: central + system
|
|
519
|
+
* agents.yaml, this machine's device pins, and the version-resources tracking.
|
|
520
|
+
* A delimited string, NOT a numeric sum — summing down-scaled epoch-ms values
|
|
521
|
+
* loses precision (float64 rounds sub-unit terms away at ~1.75e12), so a change
|
|
522
|
+
* in any one file must contribute at full resolution.
|
|
523
|
+
*/
|
|
499
524
|
function currentMetaStamp() {
|
|
500
|
-
return safeMtimeMs(META_FILE)
|
|
525
|
+
return safeMtimeMs(META_FILE)
|
|
526
|
+
+ '|' + safeMtimeMs(SYSTEM_META_FILE)
|
|
527
|
+
+ '|' + safeMtimeMs(getDeviceMetaPath())
|
|
528
|
+
+ '|' + safeMtimeMs(getVersionResourcesPath());
|
|
501
529
|
}
|
|
502
530
|
/** Memoize a parsed Meta against the current file mtimes. */
|
|
503
531
|
function rememberMeta(meta) {
|
|
504
|
-
metaCache = {
|
|
532
|
+
metaCache = { stamp: currentMetaStamp(), meta };
|
|
505
533
|
return meta;
|
|
506
534
|
}
|
|
507
535
|
function withMetaLock(fn) {
|
|
@@ -526,11 +554,78 @@ function withMetaLock(fn) {
|
|
|
526
554
|
}
|
|
527
555
|
});
|
|
528
556
|
}
|
|
557
|
+
/** Atomic write only when the on-disk content differs — avoids needless mtime
|
|
558
|
+
* bumps (which would thrash the meta cache) on no-op field routing. */
|
|
559
|
+
function writeIfChanged(filePath, content) {
|
|
560
|
+
let current = null;
|
|
561
|
+
try {
|
|
562
|
+
current = fs.readFileSync(filePath, 'utf-8');
|
|
563
|
+
}
|
|
564
|
+
catch { /* absent */ }
|
|
565
|
+
if (current === content)
|
|
566
|
+
return;
|
|
567
|
+
atomicWriteFileSync(filePath, content);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Partition the in-memory Meta across three files by sync-domain:
|
|
571
|
+
* - central `~/.agents/agents.yaml` — portable, everything else
|
|
572
|
+
* - device `~/.agents/devices/<machine>/agents.yaml` — `agents:` pins (per-device)
|
|
573
|
+
* - history `~/.agents/.history/version-resources.json` — `versions:` (machine-local)
|
|
574
|
+
* All callers funnel through writeMeta → here, so nothing else changes. Empty
|
|
575
|
+
* `agents:` / `versions:` are not written (no empty committed files).
|
|
576
|
+
*/
|
|
529
577
|
function writeMetaUnlocked(meta) {
|
|
530
|
-
const
|
|
531
|
-
|
|
578
|
+
const { agents, versions, ...central } = meta;
|
|
579
|
+
// Write the machine-local files FIRST, then strip central — so a crash mid-write
|
|
580
|
+
// never removes pins/versions from central before they're persisted elsewhere.
|
|
581
|
+
const devicePath = getDeviceMetaPath();
|
|
582
|
+
if (agents && Object.keys(agents).length > 0) {
|
|
583
|
+
fs.mkdirSync(path.dirname(devicePath), { recursive: true });
|
|
584
|
+
writeIfChanged(devicePath, META_HEADER + yaml.stringify({ agents }));
|
|
585
|
+
}
|
|
586
|
+
else if (fs.existsSync(devicePath)) {
|
|
587
|
+
// Every pin was cleared. Persist the emptied map instead of skipping the
|
|
588
|
+
// write — otherwise the stale device file survives and overlayMachineLocal
|
|
589
|
+
// re-applies the removed pin on the next read, leaving a dangling default
|
|
590
|
+
// (e.g. a launcher pointing at a version that was just uninstalled).
|
|
591
|
+
writeIfChanged(devicePath, META_HEADER + yaml.stringify({ agents: {} }));
|
|
592
|
+
}
|
|
593
|
+
if (versions && Object.keys(versions).length > 0) {
|
|
594
|
+
const vrPath = getVersionResourcesPath();
|
|
595
|
+
fs.mkdirSync(path.dirname(vrPath), { recursive: true });
|
|
596
|
+
writeIfChanged(vrPath, JSON.stringify(versions, null, 2) + '\n');
|
|
597
|
+
}
|
|
598
|
+
writeIfChanged(META_FILE, META_HEADER + yaml.stringify(central));
|
|
532
599
|
metaCache = null;
|
|
533
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Overlay this machine's local state onto a central-portable Meta:
|
|
603
|
+
* - `agents:` from the device file (device wins; the union both preserves the
|
|
604
|
+
* one-level merge and self-heals a pre-migration central that still has pins)
|
|
605
|
+
* - `versions:` from the history JSON (wholesale replace; falls back to
|
|
606
|
+
* whatever central carried when the history file doesn't exist yet)
|
|
607
|
+
*/
|
|
608
|
+
function overlayMachineLocal(meta) {
|
|
609
|
+
const devicePath = getDeviceMetaPath();
|
|
610
|
+
if (fs.existsSync(devicePath)) {
|
|
611
|
+
try {
|
|
612
|
+
const dm = yaml.parse(fs.readFileSync(devicePath, 'utf-8'));
|
|
613
|
+
if (dm?.agents)
|
|
614
|
+
meta.agents = { ...meta.agents, ...dm.agents };
|
|
615
|
+
}
|
|
616
|
+
catch { /* ignore malformed device file */ }
|
|
617
|
+
}
|
|
618
|
+
const vrPath = getVersionResourcesPath();
|
|
619
|
+
if (fs.existsSync(vrPath)) {
|
|
620
|
+
try {
|
|
621
|
+
const vr = JSON.parse(fs.readFileSync(vrPath, 'utf-8'));
|
|
622
|
+
if (vr)
|
|
623
|
+
meta.versions = vr;
|
|
624
|
+
}
|
|
625
|
+
catch { /* ignore malformed history file */ }
|
|
626
|
+
}
|
|
627
|
+
return meta;
|
|
628
|
+
}
|
|
534
629
|
function applyRegistrySeeds(meta) {
|
|
535
630
|
const seeded = new Set(meta.seededPresets || []);
|
|
536
631
|
let changed = false;
|
|
@@ -590,10 +685,7 @@ export function readMeta() {
|
|
|
590
685
|
// what we last parsed. Reduces N readMeta calls per CLI invocation to ~2 stat
|
|
591
686
|
// syscalls plus an in-memory object spread.
|
|
592
687
|
if (metaCache) {
|
|
593
|
-
|
|
594
|
-
const systemMtime = safeMtimeMs(SYSTEM_META_FILE);
|
|
595
|
-
const stamp = userMtime + systemMtime * 1e-3;
|
|
596
|
-
if (stamp === metaCache.mtime) {
|
|
688
|
+
if (currentMetaStamp() === metaCache.stamp) {
|
|
597
689
|
return metaCache.meta;
|
|
598
690
|
}
|
|
599
691
|
}
|
|
@@ -666,6 +758,7 @@ export function readMeta() {
|
|
|
666
758
|
...userMeta?.registries,
|
|
667
759
|
};
|
|
668
760
|
}
|
|
761
|
+
overlayMachineLocal(meta);
|
|
669
762
|
if (applyRegistrySeeds(meta)) {
|
|
670
763
|
writeMeta(meta);
|
|
671
764
|
return rememberMeta(meta);
|
|
@@ -673,6 +766,7 @@ export function readMeta() {
|
|
|
673
766
|
return rememberMeta(meta);
|
|
674
767
|
}
|
|
675
768
|
const meta = createDefaultMeta();
|
|
769
|
+
overlayMachineLocal(meta);
|
|
676
770
|
if (applyRegistrySeeds(meta)) {
|
|
677
771
|
writeMeta(meta);
|
|
678
772
|
}
|