@phnx-labs/agents-cli 1.20.73 → 1.20.76
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 +989 -0
- package/README.md +36 -10
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.js +21 -3
- package/dist/commands/attach.d.ts +10 -0
- package/dist/commands/attach.js +41 -0
- package/dist/commands/computer.js +3 -3
- package/dist/commands/detach-core.d.ts +46 -0
- package/dist/commands/detach-core.js +61 -0
- package/dist/commands/detach.d.ts +5 -0
- package/dist/commands/detach.js +171 -0
- package/dist/commands/doctor.js +31 -1
- package/dist/commands/exec.js +31 -3
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +7 -1
- package/dist/commands/feed.js +47 -4
- package/dist/commands/go.d.ts +9 -2
- package/dist/commands/go.js +16 -6
- package/dist/commands/harness.d.ts +14 -0
- package/dist/commands/harness.js +145 -0
- package/dist/commands/import.js +87 -9
- package/dist/commands/inspect.js +5 -2
- package/dist/commands/profiles.d.ts +20 -0
- package/dist/commands/profiles.js +68 -21
- package/dist/commands/repo.js +14 -0
- package/dist/commands/routines.js +15 -2
- package/dist/commands/secrets.js +44 -21
- package/dist/commands/sessions-browser.js +1 -1
- package/dist/commands/sessions-export.js +19 -5
- package/dist/commands/sessions-migrate.d.ts +29 -0
- package/dist/commands/sessions-migrate.js +596 -0
- package/dist/commands/sessions-picker.js +16 -1
- package/dist/commands/sessions.d.ts +36 -13
- package/dist/commands/sessions.js +210 -40
- package/dist/commands/setup-computer.js +2 -2
- package/dist/commands/setup.js +6 -1
- package/dist/commands/ssh.js +71 -35
- package/dist/commands/status.js +10 -2
- package/dist/commands/versions.js +51 -11
- package/dist/commands/view.d.ts +5 -0
- package/dist/commands/view.js +99 -31
- package/dist/commands/watchdog.d.ts +7 -2
- package/dist/commands/watchdog.js +73 -57
- package/dist/index.js +59 -7
- package/dist/lib/activity.d.ts +14 -4
- package/dist/lib/activity.js +17 -5
- package/dist/lib/actor.d.ts +50 -0
- package/dist/lib/actor.js +166 -0
- package/dist/lib/agent-spec/provider.js +2 -1
- package/dist/lib/agent-spec/resolve.js +19 -5
- package/dist/lib/agent-spec/types.d.ts +9 -1
- package/dist/lib/agents.d.ts +56 -0
- package/dist/lib/agents.js +133 -7
- package/dist/lib/cloud/codex.d.ts +2 -0
- package/dist/lib/cloud/codex.js +14 -3
- package/dist/lib/cloud/session-index.d.ts +32 -0
- package/dist/lib/cloud/session-index.js +58 -0
- package/dist/lib/cloud/store.d.ts +7 -0
- package/dist/lib/cloud/store.js +25 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +60 -0
- package/dist/lib/daemon.js +18 -2
- package/dist/lib/devices/connect.d.ts +15 -1
- package/dist/lib/devices/connect.js +15 -1
- package/dist/lib/devices/fleet.d.ts +21 -0
- package/dist/lib/devices/fleet.js +26 -0
- package/dist/lib/devices/health-report.d.ts +38 -0
- package/dist/lib/devices/health-report.js +214 -0
- package/dist/lib/devices/health.js +4 -1
- package/dist/lib/devices/reachability.d.ts +31 -0
- package/dist/lib/devices/reachability.js +40 -0
- package/dist/lib/devices/registry.d.ts +33 -0
- package/dist/lib/devices/registry.js +37 -0
- package/dist/lib/devices/resolve-target.d.ts +15 -27
- package/dist/lib/devices/resolve-target.js +63 -102
- package/dist/lib/devices/sync.d.ts +18 -0
- package/dist/lib/devices/sync.js +23 -1
- package/dist/lib/devices/tailscale.d.ts +3 -0
- package/dist/lib/devices/tailscale.js +1 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +66 -0
- package/dist/lib/exec.js +195 -6
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-post.d.ts +63 -0
- package/dist/lib/feed-post.js +204 -0
- package/dist/lib/fleet/apply.d.ts +32 -2
- package/dist/lib/fleet/apply.js +97 -10
- package/dist/lib/fleet/types.d.ts +11 -0
- package/dist/lib/fs-walk.d.ts +13 -0
- package/dist/lib/fs-walk.js +16 -7
- package/dist/lib/hooks.d.ts +1 -0
- package/dist/lib/hooks.js +156 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +2 -0
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/reconnect.d.ts +78 -0
- package/dist/lib/hosts/reconnect.js +127 -0
- package/dist/lib/hosts/registry.d.ts +75 -14
- package/dist/lib/hosts/registry.js +205 -30
- package/dist/lib/hosts/remote-cmd.js +1 -0
- package/dist/lib/hosts/run-target.d.ts +13 -5
- package/dist/lib/hosts/run-target.js +26 -10
- package/dist/lib/hosts/session-index.d.ts +18 -1
- package/dist/lib/hosts/session-index.js +37 -2
- package/dist/lib/hosts/session-marker.d.ts +33 -0
- package/dist/lib/hosts/session-marker.js +51 -0
- package/dist/lib/hosts/tasks.d.ts +8 -4
- package/dist/lib/import.d.ts +25 -0
- package/dist/lib/import.js +107 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.d.ts +20 -0
- package/dist/lib/menubar/install-menubar.js +45 -1
- package/dist/lib/migrate.d.ts +16 -0
- package/dist/lib/migrate.js +36 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +54 -1
- package/dist/lib/overdue.d.ts +7 -2
- package/dist/lib/overdue.js +18 -11
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/process.d.ts +17 -0
- package/dist/lib/platform/process.js +70 -0
- package/dist/lib/profiles-presets.js +9 -7
- package/dist/lib/profiles.d.ts +31 -0
- package/dist/lib/profiles.js +70 -0
- package/dist/lib/project-resources.js +20 -5
- package/dist/lib/pty-server.d.ts +2 -10
- package/dist/lib/pty-server.js +4 -38
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +48 -8
- package/dist/lib/rotate.d.ts +18 -0
- package/dist/lib/rotate.js +28 -0
- package/dist/lib/routines.d.ts +26 -0
- package/dist/lib/routines.js +19 -2
- package/dist/lib/runner.js +104 -7
- package/dist/lib/secrets/agent.d.ts +66 -1
- package/dist/lib/secrets/agent.js +154 -90
- package/dist/lib/secrets/bundles.d.ts +34 -0
- package/dist/lib/secrets/bundles.js +179 -19
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +21 -3
- package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
- package/dist/lib/secrets/rc-hygiene.js +154 -0
- package/dist/lib/secrets/remote.d.ts +7 -4
- package/dist/lib/secrets/remote.js +7 -4
- package/dist/lib/secrets/sync-commands.d.ts +21 -0
- package/dist/lib/secrets/sync-commands.js +21 -0
- package/dist/lib/self-update.d.ts +23 -2
- package/dist/lib/self-update.js +86 -5
- package/dist/lib/session/active.d.ts +114 -7
- package/dist/lib/session/active.js +323 -52
- package/dist/lib/session/cloud.js +2 -1
- package/dist/lib/session/db.d.ts +48 -0
- package/dist/lib/session/db.js +238 -14
- package/dist/lib/session/detached.d.ts +30 -0
- package/dist/lib/session/detached.js +92 -0
- package/dist/lib/session/discover.d.ts +465 -2
- package/dist/lib/session/discover.js +1648 -468
- package/dist/lib/session/fork.js +2 -1
- package/dist/lib/session/hook-sessions.d.ts +43 -0
- package/dist/lib/session/hook-sessions.js +135 -0
- package/dist/lib/session/linear.d.ts +6 -0
- package/dist/lib/session/linear.js +55 -0
- package/dist/lib/session/migrate-targets.d.ts +65 -0
- package/dist/lib/session/migrate-targets.js +94 -0
- package/dist/lib/session/migrations.d.ts +37 -0
- package/dist/lib/session/migrations.js +60 -0
- package/dist/lib/session/parse.d.ts +18 -0
- package/dist/lib/session/parse.js +130 -30
- package/dist/lib/session/pid-registry.d.ts +34 -2
- package/dist/lib/session/pid-registry.js +49 -2
- package/dist/lib/session/render.d.ts +7 -2
- package/dist/lib/session/render.js +22 -10
- package/dist/lib/session/short-id.d.ts +17 -0
- package/dist/lib/session/short-id.js +20 -0
- package/dist/lib/session/state.js +6 -2
- package/dist/lib/shims.d.ts +27 -1
- package/dist/lib/shims.js +88 -6
- package/dist/lib/sqlite.d.ts +3 -2
- package/dist/lib/sqlite.js +27 -4
- package/dist/lib/staleness/detectors/commands.js +1 -1
- package/dist/lib/staleness/detectors/workflows.js +13 -2
- package/dist/lib/staleness/writers/commands.js +7 -7
- package/dist/lib/staleness/writers/workflows.d.ts +4 -2
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +5 -0
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +15 -36
- package/dist/lib/teams/agents.d.ts +19 -10
- package/dist/lib/teams/agents.js +40 -39
- package/dist/lib/types.d.ts +48 -2
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/usage.d.ts +33 -1
- package/dist/lib/usage.js +172 -12
- package/dist/lib/versions.d.ts +22 -2
- package/dist/lib/versions.js +98 -10
- package/dist/lib/watchdog/log.d.ts +43 -0
- package/dist/lib/watchdog/log.js +69 -0
- package/dist/lib/watchdog/routine.d.ts +44 -0
- package/dist/lib/watchdog/routine.js +69 -0
- package/dist/lib/watchdog/runner.d.ts +51 -7
- package/dist/lib/watchdog/runner.js +239 -64
- package/dist/lib/watchdog/watchdog.d.ts +1 -1
- package/dist/lib/watchdog/watchdog.js +31 -16
- package/dist/lib/workflows.d.ts +16 -0
- package/dist/lib/workflows.js +110 -1
- package/package.json +1 -1
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Each preset bundles a host CLI, API base URL, default model, and provider
|
|
5
5
|
* name so users can `agents profiles add kimi` without manual configuration.
|
|
6
6
|
*/
|
|
7
|
-
// Model IDs verified against openrouter.ai/api/v1/models on 2026-
|
|
7
|
+
// Model IDs verified against openrouter.ai/api/v1/models on 2026-07-31
|
|
8
|
+
// (spark presets corrected from the never-served meta/claude-spark-1.1 to the
|
|
9
|
+
// live meta/muse-spark-1.1, confirmed on both OpenRouter and OpenCode id:meta).
|
|
8
10
|
// Presets target the top-ranked open-source model per provider based on
|
|
9
11
|
// SWE-bench Verified, LiveCodeBench, HumanEval, and Chatbot Arena rankings.
|
|
10
12
|
//
|
|
@@ -100,18 +102,18 @@ export const PRESETS = [
|
|
|
100
102
|
},
|
|
101
103
|
{
|
|
102
104
|
name: 'claude-spark',
|
|
103
|
-
description: 'Meta
|
|
105
|
+
description: 'Meta Muse Spark 1.1 via OpenRouter inside Claude Code (open alternative). Model: meta/muse-spark-1.1 — now usable in the Claude Code UI. HEADLESS-SAFE. For open-claude spark usage.',
|
|
104
106
|
...OPENROUTER_AUTH,
|
|
105
107
|
env: {
|
|
106
108
|
ANTHROPIC_BASE_URL: OPENROUTER_BASE,
|
|
107
|
-
ANTHROPIC_MODEL: 'meta/
|
|
108
|
-
ANTHROPIC_SMALL_FAST_MODEL: 'meta/
|
|
109
|
+
ANTHROPIC_MODEL: 'meta/muse-spark-1.1',
|
|
110
|
+
ANTHROPIC_SMALL_FAST_MODEL: 'meta/muse-spark-1.1',
|
|
109
111
|
},
|
|
110
112
|
},
|
|
111
113
|
// ----- OpenCode CLI (open-claude harness) -----
|
|
112
114
|
{
|
|
113
115
|
name: 'opencode',
|
|
114
|
-
description: 'OpenCode default — uses your configured model via opencode auth. Run `opencode auth` to login, then `agents run opencode --model meta/
|
|
116
|
+
description: 'OpenCode default — uses your configured model via opencode auth. Run `opencode auth` to login, then `agents run opencode --model meta/muse-spark-1.1 "prompt"` for spark usage.',
|
|
115
117
|
provider: 'opencode',
|
|
116
118
|
host: 'opencode',
|
|
117
119
|
authEnvVar: 'OPENCODE_API_KEY',
|
|
@@ -120,13 +122,13 @@ export const PRESETS = [
|
|
|
120
122
|
},
|
|
121
123
|
{
|
|
122
124
|
name: 'opencode-spark',
|
|
123
|
-
description: 'Meta
|
|
125
|
+
description: 'Meta Muse Spark 1.1 via OpenCode (free, headless-safe). Pinned to meta/muse-spark-1.1 — best for open-claude usage with opencode harness.',
|
|
124
126
|
provider: 'opencode',
|
|
125
127
|
host: 'opencode',
|
|
126
128
|
authEnvVar: 'OPENCODE_API_KEY',
|
|
127
129
|
authOptional: true,
|
|
128
130
|
env: {
|
|
129
|
-
OPENCODE_MODEL: 'meta/
|
|
131
|
+
OPENCODE_MODEL: 'meta/muse-spark-1.1',
|
|
130
132
|
},
|
|
131
133
|
},
|
|
132
134
|
{
|
package/dist/lib/profiles.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ export interface Profile {
|
|
|
19
19
|
envVar: string;
|
|
20
20
|
keychainItem: string;
|
|
21
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* When true, the host manages its own login and the keychain token named by
|
|
24
|
+
* `auth` is optional: if it is not stored, no auth env var is injected and the
|
|
25
|
+
* run proceeds on the host's own credentials (e.g. `opencode auth`). Without
|
|
26
|
+
* this flag a missing keychain item is a hard error at exec time.
|
|
27
|
+
*/
|
|
28
|
+
authOptional?: boolean;
|
|
22
29
|
description?: string;
|
|
23
30
|
preset?: string;
|
|
24
31
|
provider?: string;
|
|
@@ -92,6 +99,30 @@ export declare function profileSummary(profile: Profile): ProfileSummary;
|
|
|
92
99
|
* for the OpenRouter key exactly once.
|
|
93
100
|
*/
|
|
94
101
|
export declare function profileFromPreset(profileName: string, preset: Preset, version?: string): Profile;
|
|
102
|
+
/** Return the model-override env var for a host (known hosts mapped; else `<HOST>_MODEL`). */
|
|
103
|
+
export declare function modelEnvKeyForHost(host: AgentId): string;
|
|
104
|
+
/** Return the base-URL env var for a host, or null when the host has no known override var. */
|
|
105
|
+
export declare function baseUrlEnvKeyForHost(host: AgentId): string | null;
|
|
106
|
+
/** Return the auth-token env var for a host, or null when unknown. */
|
|
107
|
+
export declare function authEnvKeyForHost(host: AgentId): string | null;
|
|
108
|
+
/** Options for {@link profileFromHostModel}. */
|
|
109
|
+
export interface HostModelOptions {
|
|
110
|
+
version?: string;
|
|
111
|
+
/** Base URL for OpenAI/Anthropic-compatible hosts (claude, codex). Ignored for hosts without a known var. */
|
|
112
|
+
baseUrl?: string;
|
|
113
|
+
/** Provider label + keychain namespace; only needed when the host requires a token. */
|
|
114
|
+
provider?: string;
|
|
115
|
+
/** Env var the host reads its auth token from; pair with `provider` to attach keychain auth. */
|
|
116
|
+
authEnvVar?: string;
|
|
117
|
+
description?: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build a custom-harness profile from a host CLI + model in one shot, without a
|
|
121
|
+
* preset. The model lands on the host's model env var ({@link modelEnvKeyForHost});
|
|
122
|
+
* auth is attached only when both `provider` and `authEnvVar` are supplied
|
|
123
|
+
* (hosts that manage their own login — e.g. opencode — need neither).
|
|
124
|
+
*/
|
|
125
|
+
export declare function profileFromHostModel(name: string, host: AgentId, model: string, opts?: HostModelOptions): Profile;
|
|
95
126
|
/**
|
|
96
127
|
* Resolve a profile into the env block that should be injected into the
|
|
97
128
|
* spawned agent process. Reads the token from keychain at exec time so the
|
package/dist/lib/profiles.js
CHANGED
|
@@ -223,11 +223,75 @@ export function profileFromPreset(profileName, preset, version) {
|
|
|
223
223
|
envVar: preset.authEnvVar,
|
|
224
224
|
keychainItem: keychainItemName(preset.provider),
|
|
225
225
|
},
|
|
226
|
+
authOptional: preset.authOptional,
|
|
226
227
|
description: preset.description,
|
|
227
228
|
preset: preset.name,
|
|
228
229
|
provider: preset.provider,
|
|
229
230
|
};
|
|
230
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Env var each host CLI reads to override its model. Mirror of the read-side
|
|
234
|
+
* `MODEL_ENV_KEYS` above, keyed by host so a one-shot `--host <agent> --model
|
|
235
|
+
* <id>` writes the model onto the var that host actually honors.
|
|
236
|
+
*/
|
|
237
|
+
const MODEL_ENV_KEY_BY_HOST = {
|
|
238
|
+
claude: 'ANTHROPIC_MODEL',
|
|
239
|
+
opencode: 'OPENCODE_MODEL',
|
|
240
|
+
grok: 'GROK_MODEL',
|
|
241
|
+
gemini: 'GEMINI_MODEL',
|
|
242
|
+
codex: 'OPENAI_MODEL',
|
|
243
|
+
};
|
|
244
|
+
/** Base-URL env var per host, for the OpenAI/Anthropic-compatible hosts where it applies. */
|
|
245
|
+
const BASE_URL_ENV_KEY_BY_HOST = {
|
|
246
|
+
claude: 'ANTHROPIC_BASE_URL',
|
|
247
|
+
codex: 'OPENAI_BASE_URL',
|
|
248
|
+
};
|
|
249
|
+
/** Return the model-override env var for a host (known hosts mapped; else `<HOST>_MODEL`). */
|
|
250
|
+
export function modelEnvKeyForHost(host) {
|
|
251
|
+
return MODEL_ENV_KEY_BY_HOST[host] ?? `${host.toUpperCase()}_MODEL`;
|
|
252
|
+
}
|
|
253
|
+
/** Return the base-URL env var for a host, or null when the host has no known override var. */
|
|
254
|
+
export function baseUrlEnvKeyForHost(host) {
|
|
255
|
+
return BASE_URL_ENV_KEY_BY_HOST[host] ?? null;
|
|
256
|
+
}
|
|
257
|
+
/** Env var each host reads its auth token from, for custom-endpoint (`--auth-provider`) harnesses. */
|
|
258
|
+
const AUTH_ENV_KEY_BY_HOST = {
|
|
259
|
+
claude: 'ANTHROPIC_AUTH_TOKEN',
|
|
260
|
+
codex: 'OPENAI_API_KEY',
|
|
261
|
+
gemini: 'GEMINI_API_KEY',
|
|
262
|
+
grok: 'XAI_API_KEY',
|
|
263
|
+
opencode: 'OPENCODE_API_KEY',
|
|
264
|
+
};
|
|
265
|
+
/** Return the auth-token env var for a host, or null when unknown. */
|
|
266
|
+
export function authEnvKeyForHost(host) {
|
|
267
|
+
return AUTH_ENV_KEY_BY_HOST[host] ?? null;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Build a custom-harness profile from a host CLI + model in one shot, without a
|
|
271
|
+
* preset. The model lands on the host's model env var ({@link modelEnvKeyForHost});
|
|
272
|
+
* auth is attached only when both `provider` and `authEnvVar` are supplied
|
|
273
|
+
* (hosts that manage their own login — e.g. opencode — need neither).
|
|
274
|
+
*/
|
|
275
|
+
export function profileFromHostModel(name, host, model, opts = {}) {
|
|
276
|
+
const env = { [modelEnvKeyForHost(host)]: model };
|
|
277
|
+
if (opts.baseUrl) {
|
|
278
|
+
const key = baseUrlEnvKeyForHost(host);
|
|
279
|
+
if (key)
|
|
280
|
+
env[key] = opts.baseUrl;
|
|
281
|
+
}
|
|
282
|
+
const profile = {
|
|
283
|
+
name,
|
|
284
|
+
host: { agent: host, version: opts.version },
|
|
285
|
+
env,
|
|
286
|
+
description: opts.description ?? `Custom harness: ${host} + ${model}`,
|
|
287
|
+
provider: opts.provider ?? host,
|
|
288
|
+
};
|
|
289
|
+
if (opts.provider && opts.authEnvVar) {
|
|
290
|
+
profile.auth = { envVar: opts.authEnvVar, keychainItem: keychainItemName(opts.provider) };
|
|
291
|
+
profile.authOptional = false;
|
|
292
|
+
}
|
|
293
|
+
return profile;
|
|
294
|
+
}
|
|
231
295
|
/**
|
|
232
296
|
* Resolve a profile into the env block that should be injected into the
|
|
233
297
|
* spawned agent process. Reads the token from keychain at exec time so the
|
|
@@ -236,6 +300,12 @@ export function profileFromPreset(profileName, preset, version) {
|
|
|
236
300
|
export function resolveProfileEnv(profile) {
|
|
237
301
|
const env = { ...profile.env };
|
|
238
302
|
if (profile.auth) {
|
|
303
|
+
// Optional auth (host manages its own login) with no stored token: inject
|
|
304
|
+
// nothing and let the host use its own credentials. Only required auth
|
|
305
|
+
// hard-fails on a missing keychain item.
|
|
306
|
+
if (profile.authOptional && !hasKeychainToken(profile.auth.keychainItem)) {
|
|
307
|
+
return env;
|
|
308
|
+
}
|
|
239
309
|
const token = getKeychainToken(profile.auth.keychainItem);
|
|
240
310
|
env[profile.auth.envVar] = token;
|
|
241
311
|
}
|
|
@@ -41,7 +41,7 @@ function loadProjectManifest(agentRoot) {
|
|
|
41
41
|
return null;
|
|
42
42
|
if (!raw.paths.every((p) => typeof p === 'string' && p.length > 0))
|
|
43
43
|
return null;
|
|
44
|
-
return raw;
|
|
44
|
+
return { ...raw, paths: raw.paths.map(toPosixRel) };
|
|
45
45
|
}
|
|
46
46
|
catch {
|
|
47
47
|
return null;
|
|
@@ -106,10 +106,22 @@ function projectEntries(projectAgentsDir, kind) {
|
|
|
106
106
|
return [];
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Manifest paths are persisted to `.agents-managed.json`, which lives in the
|
|
111
|
+
* version-controlled project dir and therefore travels between machines. Store
|
|
112
|
+
* them POSIX-style: `path.join` yields `skills\myskill` on Windows, and a
|
|
113
|
+
* manifest carrying that would silently fail to match — and so fail to clean up
|
|
114
|
+
* its managed files — when the same project is synced on macOS or Linux.
|
|
115
|
+
* Normalizing on both write and read also repairs manifests written by earlier
|
|
116
|
+
* Windows builds.
|
|
117
|
+
*/
|
|
118
|
+
function toPosixRel(rel) {
|
|
119
|
+
return rel.replace(/\\/g, '/');
|
|
120
|
+
}
|
|
109
121
|
function record(kind, name, relPaths, result, manifestPaths) {
|
|
110
122
|
result.synced.push(`${kind}/${name}`);
|
|
111
123
|
for (const rel of relPaths)
|
|
112
|
-
manifestPaths.add(rel);
|
|
124
|
+
manifestPaths.add(toPosixRel(rel));
|
|
113
125
|
}
|
|
114
126
|
function skip(dest, projectRoot, result) {
|
|
115
127
|
const rel = path.relative(projectRoot, dest);
|
|
@@ -209,8 +221,8 @@ function syncProjectSubagents(agent, version, projectAgentsDir, projectRoot, age
|
|
|
209
221
|
return;
|
|
210
222
|
const all = readProjectSubagents(projectAgentsDir);
|
|
211
223
|
const dir = target.dir(projectRoot);
|
|
212
|
-
const targetDirRel = path.relative(agentRoot, dir);
|
|
213
|
-
const hadManagedTarget = manifest?.paths.some((rel) => rel === targetDirRel || rel.startsWith(targetDirRel +
|
|
224
|
+
const targetDirRel = toPosixRel(path.relative(agentRoot, dir));
|
|
225
|
+
const hadManagedTarget = manifest?.paths.some((rel) => rel === targetDirRel || rel.startsWith(targetDirRel + '/')) ?? false;
|
|
214
226
|
for (const sub of all.values()) {
|
|
215
227
|
const occupied = target.occupied(dir, sub.name);
|
|
216
228
|
const existing = occupied.find((entry) => pathExists(entry.path));
|
|
@@ -234,7 +246,10 @@ function syncProjectSubagents(agent, version, projectAgentsDir, projectRoot, age
|
|
|
234
246
|
if (target.finalize && (syncedNames.length > 0 || hadManagedTarget)) {
|
|
235
247
|
target.finalize(dir, syncedNames);
|
|
236
248
|
for (const entry of target.finalizeOccupied?.(dir) ?? []) {
|
|
237
|
-
|
|
249
|
+
// Same normalization as record(): this is the one manifest write that
|
|
250
|
+
// doesn't funnel through it (the parent subagent index, e.g. Kimi's
|
|
251
|
+
// agents/_agents-cli.yaml).
|
|
252
|
+
manifestPaths.add(toPosixRel(path.relative(agentRoot, entry.path)));
|
|
238
253
|
}
|
|
239
254
|
}
|
|
240
255
|
}
|
package/dist/lib/pty-server.d.ts
CHANGED
|
@@ -8,16 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Protocol: newline-delimited JSON over ~/.agents/.system/pty.sock
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* Used to defeat PID reuse: a kill(pid, ...) is only safe when the process
|
|
14
|
-
* still occupies the PID we observed at spawn time.
|
|
15
|
-
*
|
|
16
|
-
* Linux: field 22 of /proc/<pid>/stat (starttime in clock ticks since boot).
|
|
17
|
-
* macOS: output of `ps -o lstart= -p <pid>` (start time in human format).
|
|
18
|
-
* Returns null on any error so callers can skip the guard rather than crash.
|
|
19
|
-
*/
|
|
20
|
-
export declare function captureProcessStartTime(pid: number): string | null;
|
|
11
|
+
import { captureProcessStartTime } from './platform/index.js';
|
|
12
|
+
export { captureProcessStartTime };
|
|
21
13
|
/**
|
|
22
14
|
* Wrap a user command so a `__SENTINEL__:<exit>` line is printed after it
|
|
23
15
|
* finishes — that line drives completion detection in the exec/read flow.
|
package/dist/lib/pty-server.js
CHANGED
|
@@ -12,46 +12,12 @@ import * as net from 'net';
|
|
|
12
12
|
import * as fs from 'fs';
|
|
13
13
|
import * as path from 'path';
|
|
14
14
|
import * as crypto from 'crypto';
|
|
15
|
-
import { execFileSync } from 'child_process';
|
|
16
15
|
import { fileURLToPath } from 'url';
|
|
17
16
|
import { getPtyDir as getPtyDirRoot } from './state.js';
|
|
18
|
-
import { isAlive } from './platform/index.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* still occupies the PID we observed at spawn time.
|
|
23
|
-
*
|
|
24
|
-
* Linux: field 22 of /proc/<pid>/stat (starttime in clock ticks since boot).
|
|
25
|
-
* macOS: output of `ps -o lstart= -p <pid>` (start time in human format).
|
|
26
|
-
* Returns null on any error so callers can skip the guard rather than crash.
|
|
27
|
-
*/
|
|
28
|
-
export function captureProcessStartTime(pid) {
|
|
29
|
-
if (!pid || pid <= 0)
|
|
30
|
-
return null;
|
|
31
|
-
try {
|
|
32
|
-
if (process.platform === 'linux') {
|
|
33
|
-
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
|
34
|
-
// The comm field (#2) is wrapped in parens and may contain spaces, so
|
|
35
|
-
// split off everything after the last `)` to get a clean field list.
|
|
36
|
-
const lastParen = stat.lastIndexOf(')');
|
|
37
|
-
if (lastParen < 0)
|
|
38
|
-
return null;
|
|
39
|
-
const tail = stat.slice(lastParen + 2);
|
|
40
|
-
const fields = tail.split(' ');
|
|
41
|
-
// After comm we are at field 3; starttime is field 22, so index 19 here.
|
|
42
|
-
return fields[19] || null;
|
|
43
|
-
}
|
|
44
|
-
const out = execFileSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
|
|
45
|
-
encoding: 'utf-8',
|
|
46
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
47
|
-
});
|
|
48
|
-
const trimmed = out.trim();
|
|
49
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
17
|
+
import { isAlive, captureProcessStartTime } from './platform/index.js';
|
|
18
|
+
// Re-exported for the existing importers of this module; the implementation is
|
|
19
|
+
// in platform/process.ts, shared with teams/agents.ts so the two cannot drift.
|
|
20
|
+
export { captureProcessStartTime };
|
|
55
21
|
// --- Constants ---
|
|
56
22
|
const IS_WINDOWS = process.platform === 'win32';
|
|
57
23
|
const SENTINEL = '__AGENTS_PTY_DONE__';
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare function getEnabledRegistries(type: RegistryType): Array<{
|
|
|
17
17
|
}>;
|
|
18
18
|
/** Add or update a registry configuration in agents.yaml. */
|
|
19
19
|
export declare function setRegistry(type: RegistryType, name: string, config: Partial<RegistryConfig>): void;
|
|
20
|
-
/** Remove a user-configured registry. Returns false if it did not exist. */
|
|
20
|
+
/** Remove a user-configured or seeded registry. Returns false if it did not exist. */
|
|
21
21
|
export declare function removeRegistry(type: RegistryType, name: string): boolean;
|
|
22
22
|
/** Search MCP registries for servers matching a query string. */
|
|
23
23
|
export declare function searchMcpRegistries(query: string, options?: {
|
package/dist/lib/registry.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { createHash } from 'crypto';
|
|
11
|
-
import { DEFAULT_REGISTRIES } from './types.js';
|
|
11
|
+
import { DEFAULT_REGISTRIES, SEEDED_REGISTRIES } from './types.js';
|
|
12
12
|
import { readMeta, writeMeta } from './state.js';
|
|
13
13
|
import { discoverSkillsFromRepo } from './skills.js';
|
|
14
14
|
const UNSAFE_PACKAGE_SPEC_CHARS = /[;&|`$\s\x00-\x1f\x7f]/;
|
|
@@ -26,13 +26,38 @@ export function validatedPyPISpec(spec) {
|
|
|
26
26
|
}
|
|
27
27
|
return spec;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Seeded presets offered for `type`, minus any the user has removed.
|
|
31
|
+
*
|
|
32
|
+
* These are resolved here rather than written into agents.yaml. Seeding used to
|
|
33
|
+
* happen on the state READ path and persisted the entry — but agents.yaml is
|
|
34
|
+
* git-tracked in the user's DotAgents repo, so that write left the working tree
|
|
35
|
+
* dirty and every `agents repo pull` aborted with "Working tree has uncommitted
|
|
36
|
+
* changes", naming neither the file nor the cause. Since every `agents`
|
|
37
|
+
* invocation reads state, the dirt returned the instant it was cleared, and the
|
|
38
|
+
* loop could not be escaped through the CLI at all (RUSH-1925).
|
|
39
|
+
*
|
|
40
|
+
* `seededPresets` is the removal tombstone: a key listed there means the user
|
|
41
|
+
* removed that preset, so it is not offered again. Users who were seeded under
|
|
42
|
+
* the old behaviour have the key listed *and* the entry in their own
|
|
43
|
+
* `registries:` block, which still wins below — so nothing disappears for them.
|
|
44
|
+
*/
|
|
45
|
+
function offeredSeeds(type, meta) {
|
|
46
|
+
const removed = new Set(meta.seededPresets || []);
|
|
47
|
+
const offered = {};
|
|
48
|
+
for (const [name, config] of Object.entries(SEEDED_REGISTRIES[type] || {})) {
|
|
49
|
+
if (!removed.has(`${type}.${name}`))
|
|
50
|
+
offered[name] = { ...config };
|
|
51
|
+
}
|
|
52
|
+
return offered;
|
|
53
|
+
}
|
|
29
54
|
/** Get all registries of a given type, merging defaults with user overrides. */
|
|
30
55
|
export function getRegistries(type) {
|
|
31
56
|
const meta = readMeta();
|
|
32
57
|
const defaultRegs = DEFAULT_REGISTRIES[type] || {};
|
|
33
58
|
const userRegs = meta.registries?.[type] || {};
|
|
34
59
|
// Merge defaults with user config (user overrides defaults)
|
|
35
|
-
return { ...defaultRegs, ...userRegs };
|
|
60
|
+
return { ...defaultRegs, ...offeredSeeds(type, meta), ...userRegs };
|
|
36
61
|
}
|
|
37
62
|
/** Get only the enabled registries of a given type. */
|
|
38
63
|
export function getEnabledRegistries(type) {
|
|
@@ -50,19 +75,34 @@ export function setRegistry(type, name, config) {
|
|
|
50
75
|
if (!meta.registries[type]) {
|
|
51
76
|
meta.registries[type] = {};
|
|
52
77
|
}
|
|
53
|
-
|
|
78
|
+
// Seeded presets are resolved in memory and never materialized in agents.yaml
|
|
79
|
+
// (see offeredSeeds), so a partial update — `registry disable`, `enable`, or
|
|
80
|
+
// `config --api-key` — has nothing stored to merge with. Without the
|
|
81
|
+
// SEEDED_REGISTRIES fallback it would persist only the changed fields and drop
|
|
82
|
+
// `url`, and because userRegs wins over the in-memory seed in getRegistries the
|
|
83
|
+
// preset would stay broken even after re-enabling.
|
|
84
|
+
const existing = meta.registries[type][name]
|
|
85
|
+
|| DEFAULT_REGISTRIES[type]?.[name]
|
|
86
|
+
|| SEEDED_REGISTRIES[type]?.[name];
|
|
54
87
|
meta.registries[type][name] = { ...existing, ...config };
|
|
55
88
|
writeMeta(meta);
|
|
56
89
|
}
|
|
57
|
-
/** Remove a user-configured registry. Returns false if it did not exist. */
|
|
90
|
+
/** Remove a user-configured or seeded registry. Returns false if it did not exist. */
|
|
58
91
|
export function removeRegistry(type, name) {
|
|
59
92
|
const meta = readMeta();
|
|
60
|
-
|
|
93
|
+
const inUserConfig = !!meta.registries?.[type]?.[name];
|
|
94
|
+
// A seeded preset has no entry in agents.yaml until the user edits it, so
|
|
95
|
+
// removal is recorded as a tombstone in seededPresets instead of a deletion.
|
|
96
|
+
const isOfferedSeed = !!offeredSeeds(type, meta)[name];
|
|
97
|
+
if (!inUserConfig && !isOfferedSeed)
|
|
98
|
+
return false;
|
|
99
|
+
if (inUserConfig)
|
|
61
100
|
delete meta.registries[type][name];
|
|
62
|
-
|
|
63
|
-
|
|
101
|
+
if (SEEDED_REGISTRIES[type]?.[name]) {
|
|
102
|
+
meta.seededPresets = [...new Set([...(meta.seededPresets || []), `${type}.${name}`])];
|
|
64
103
|
}
|
|
65
|
-
|
|
104
|
+
writeMeta(meta);
|
|
105
|
+
return true;
|
|
66
106
|
}
|
|
67
107
|
/**
|
|
68
108
|
* Cap every registry network call. Without this a slow or unreachable registry
|
package/dist/lib/rotate.d.ts
CHANGED
|
@@ -126,6 +126,24 @@ export declare function pickBalancedCandidate(candidates: RotateCandidate[]): Ro
|
|
|
126
126
|
*/
|
|
127
127
|
export declare function pickAvailableCandidate(candidates: RotateCandidate[], preferredVersion?: string | null): RotateResult | null;
|
|
128
128
|
export declare function collectRunCandidates(agent: AgentId): Promise<RotateCandidate[]>;
|
|
129
|
+
/**
|
|
130
|
+
* Resolve an account identity to the installed version slot that holds it, over
|
|
131
|
+
* an already-collected candidate list. Pure — no I/O — so it is unit-tested
|
|
132
|
+
* directly. Matches, case-insensitively, against a candidate's login `email`
|
|
133
|
+
* (the usual form) or its `accountKey`, and only ever returns a signed-in slot.
|
|
134
|
+
* Returns null when nothing matches, so the caller can fall back and warn.
|
|
135
|
+
*/
|
|
136
|
+
export declare function matchAccountVersion(candidates: RotateCandidate[], account: string): string | null;
|
|
137
|
+
/**
|
|
138
|
+
* Resolve a routine's `account:` pin (login email or account key) to the
|
|
139
|
+
* installed version currently holding that account. Thin I/O wrapper over
|
|
140
|
+
* {@link collectRunCandidates} + {@link matchAccountVersion}; returns null when
|
|
141
|
+
* no signed-in version matches. Pinning a routine to a distinct account is the
|
|
142
|
+
* durable cure for the shared-single-use-refresh-token revocation storm
|
|
143
|
+
* (RUSH-1957): the pinned run never rotates and never lands on another
|
|
144
|
+
* routine's credential.
|
|
145
|
+
*/
|
|
146
|
+
export declare function resolveAccountVersion(agent: AgentId, account: string): Promise<string | null>;
|
|
129
147
|
/**
|
|
130
148
|
* Pick a healthy version for `agent` using weighted random by remaining
|
|
131
149
|
* capacity. See `pickBalancedCandidate` for algorithm details.
|
package/dist/lib/rotate.js
CHANGED
|
@@ -316,6 +316,34 @@ export async function collectRunCandidates(agent) {
|
|
|
316
316
|
};
|
|
317
317
|
});
|
|
318
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Resolve an account identity to the installed version slot that holds it, over
|
|
321
|
+
* an already-collected candidate list. Pure — no I/O — so it is unit-tested
|
|
322
|
+
* directly. Matches, case-insensitively, against a candidate's login `email`
|
|
323
|
+
* (the usual form) or its `accountKey`, and only ever returns a signed-in slot.
|
|
324
|
+
* Returns null when nothing matches, so the caller can fall back and warn.
|
|
325
|
+
*/
|
|
326
|
+
export function matchAccountVersion(candidates, account) {
|
|
327
|
+
const needle = account.trim().toLowerCase();
|
|
328
|
+
if (!needle)
|
|
329
|
+
return null;
|
|
330
|
+
const match = candidates.find((c) => c.signedIn &&
|
|
331
|
+
(c.email?.toLowerCase() === needle || c.accountKey?.toLowerCase() === needle));
|
|
332
|
+
return match?.version ?? null;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Resolve a routine's `account:` pin (login email or account key) to the
|
|
336
|
+
* installed version currently holding that account. Thin I/O wrapper over
|
|
337
|
+
* {@link collectRunCandidates} + {@link matchAccountVersion}; returns null when
|
|
338
|
+
* no signed-in version matches. Pinning a routine to a distinct account is the
|
|
339
|
+
* durable cure for the shared-single-use-refresh-token revocation storm
|
|
340
|
+
* (RUSH-1957): the pinned run never rotates and never lands on another
|
|
341
|
+
* routine's credential.
|
|
342
|
+
*/
|
|
343
|
+
export async function resolveAccountVersion(agent, account) {
|
|
344
|
+
const candidates = await collectRunCandidates(agent);
|
|
345
|
+
return matchAccountVersion(candidates, account);
|
|
346
|
+
}
|
|
319
347
|
/**
|
|
320
348
|
* Pick a healthy version for `agent` using weighted random by remaining
|
|
321
349
|
* capacity. See `pickBalancedCandidate` for algorithm details.
|
package/dist/lib/routines.d.ts
CHANGED
|
@@ -111,6 +111,25 @@ export interface JobConfig {
|
|
|
111
111
|
allow?: JobAllowConfig;
|
|
112
112
|
config?: Record<string, unknown>;
|
|
113
113
|
version?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Pin this routine to a signed-in account by identity (its login email, or its
|
|
116
|
+
* account key) instead of rotating. At launch it resolves to whichever
|
|
117
|
+
* installed version currently holds that account and runs pinned — no
|
|
118
|
+
* `balanced` rotation, no usage-read refresh, no failover onto other accounts.
|
|
119
|
+
*
|
|
120
|
+
* This is the durable way to keep concurrent unattended routines off a *shared*
|
|
121
|
+
* Claude OAuth credential: the refresh token is single-use and rotates
|
|
122
|
+
* server-side on every refresh, so two routines running the same account
|
|
123
|
+
* concurrently (on one box or across the fleet) mutually revoke each other —
|
|
124
|
+
* the `401 OAuth access token has been revoked` storm (RUSH-1957). Give each
|
|
125
|
+
* routine (or each device's routines) a distinct account and no run rotates a
|
|
126
|
+
* credential out from under another.
|
|
127
|
+
*
|
|
128
|
+
* Prefer this over `version:`, which pins a version *number* that is GC'd on
|
|
129
|
+
* the next agent upgrade — when the pinned version disappears the routine
|
|
130
|
+
* silently falls back to `balanced` and the stampede returns.
|
|
131
|
+
*/
|
|
132
|
+
account?: string;
|
|
114
133
|
runOnce?: boolean;
|
|
115
134
|
endAt?: string;
|
|
116
135
|
/**
|
|
@@ -251,6 +270,13 @@ export declare function parseTimeout(timeout: string): number | null;
|
|
|
251
270
|
export declare function listRuns(jobName: string): RunMeta[];
|
|
252
271
|
/** Get the most recent run for a job, or null if never run. */
|
|
253
272
|
export declare function getLatestRun(jobName: string): RunMeta | null;
|
|
273
|
+
/**
|
|
274
|
+
* Get the most recent COMPLETED run for a job, or null if none has completed.
|
|
275
|
+
* Used to resolve `{last_report}` so a failed run's error text (e.g. an auth
|
|
276
|
+
* login prompt written into report.md) can never be injected into the next
|
|
277
|
+
* run's prompt.
|
|
278
|
+
*/
|
|
279
|
+
export declare function getLatestCompletedRun(jobName: string): RunMeta | null;
|
|
254
280
|
/** Persist run metadata to its run directory as meta.json. */
|
|
255
281
|
export declare function writeRunMeta(meta: RunMeta): void;
|
|
256
282
|
/** Read run metadata from disk. Returns null if missing or corrupt. */
|
package/dist/lib/routines.js
CHANGED
|
@@ -536,8 +536,11 @@ export function resolveJobPrompt(config) {
|
|
|
536
536
|
prompt = prompt.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
|
|
537
537
|
}
|
|
538
538
|
}
|
|
539
|
-
// Last report (special handling)
|
|
540
|
-
|
|
539
|
+
// Last report (special handling). Only a COMPLETED run's report is injected —
|
|
540
|
+
// a failed run's report.md is the agent's error text (e.g. a login prompt on
|
|
541
|
+
// an auth failure), and feeding that into the next prompt poisons every
|
|
542
|
+
// subsequent run until a human intervenes.
|
|
543
|
+
const latestRun = getLatestCompletedRun(config.name);
|
|
541
544
|
if (latestRun) {
|
|
542
545
|
const reportPath = path.join(getJobRunsDir(config.name), latestRun.runId, 'report.md');
|
|
543
546
|
if (fs.existsSync(reportPath)) {
|
|
@@ -595,6 +598,20 @@ export function getLatestRun(jobName) {
|
|
|
595
598
|
const runs = listRuns(jobName);
|
|
596
599
|
return runs.length > 0 ? runs[runs.length - 1] : null;
|
|
597
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Get the most recent COMPLETED run for a job, or null if none has completed.
|
|
603
|
+
* Used to resolve `{last_report}` so a failed run's error text (e.g. an auth
|
|
604
|
+
* login prompt written into report.md) can never be injected into the next
|
|
605
|
+
* run's prompt.
|
|
606
|
+
*/
|
|
607
|
+
export function getLatestCompletedRun(jobName) {
|
|
608
|
+
const runs = listRuns(jobName);
|
|
609
|
+
for (let i = runs.length - 1; i >= 0; i--) {
|
|
610
|
+
if (runs[i].status === 'completed')
|
|
611
|
+
return runs[i];
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
598
615
|
/** Persist run metadata to its run directory as meta.json. */
|
|
599
616
|
export function writeRunMeta(meta) {
|
|
600
617
|
ensureAgentsDir();
|