@phnx-labs/agents-cli 1.22.23 → 1.22.24

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +5 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/attach.d.ts +2 -0
  5. package/dist/commands/attach.js +32 -7
  6. package/dist/commands/defaults.js +2 -0
  7. package/dist/commands/doctor.js +5 -0
  8. package/dist/commands/exec.js +80 -28
  9. package/dist/commands/feed.d.ts +18 -0
  10. package/dist/commands/feed.js +44 -1
  11. package/dist/commands/focus.d.ts +34 -3
  12. package/dist/commands/focus.js +300 -56
  13. package/dist/commands/go.d.ts +14 -1
  14. package/dist/commands/go.js +49 -5
  15. package/dist/commands/insights.js +6 -2
  16. package/dist/commands/inspect.js +39 -5
  17. package/dist/commands/menubar.js +6 -1
  18. package/dist/commands/models.js +1 -0
  19. package/dist/commands/modes.d.ts +12 -0
  20. package/dist/commands/modes.js +147 -0
  21. package/dist/commands/secrets-sync.js +11 -13
  22. package/dist/commands/secrets.d.ts +2 -0
  23. package/dist/commands/secrets.js +54 -17
  24. package/dist/commands/sessions-browser.d.ts +35 -0
  25. package/dist/commands/sessions-browser.js +140 -14
  26. package/dist/commands/sessions-resume.d.ts +2 -0
  27. package/dist/commands/sessions-resume.js +26 -11
  28. package/dist/commands/sessions.d.ts +10 -0
  29. package/dist/commands/sessions.js +34 -59
  30. package/dist/commands/setup-secrets.js +1 -1
  31. package/dist/commands/sync.js +246 -42
  32. package/dist/commands/view.js +2 -0
  33. package/dist/index.js +2 -1
  34. package/dist/lib/agent-modes.d.ts +49 -0
  35. package/dist/lib/agent-modes.js +70 -0
  36. package/dist/lib/doctor-diff.d.ts +3 -0
  37. package/dist/lib/doctor-diff.js +15 -13
  38. package/dist/lib/event-stream.d.ts +3 -1
  39. package/dist/lib/event-stream.js +14 -1
  40. package/dist/lib/exec.d.ts +12 -0
  41. package/dist/lib/exec.js +88 -9
  42. package/dist/lib/hooks/cache.js +36 -3
  43. package/dist/lib/hooks.d.ts +19 -7
  44. package/dist/lib/hooks.js +88 -38
  45. package/dist/lib/hosts/session-index.d.ts +4 -0
  46. package/dist/lib/hosts/session-index.js +7 -0
  47. package/dist/lib/manifest.d.ts +12 -2
  48. package/dist/lib/manifest.js +60 -5
  49. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  50. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
  51. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  52. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  53. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
  54. package/dist/lib/menubar/install-menubar.d.ts +52 -2
  55. package/dist/lib/menubar/install-menubar.js +128 -6
  56. package/dist/lib/refresh.d.ts +5 -0
  57. package/dist/lib/refresh.js +37 -33
  58. package/dist/lib/resource-inventory.d.ts +79 -0
  59. package/dist/lib/resource-inventory.js +122 -0
  60. package/dist/lib/resources.js +8 -5
  61. package/dist/lib/run-defaults.d.ts +2 -0
  62. package/dist/lib/run-defaults.js +23 -2
  63. package/dist/lib/runner.js +22 -17
  64. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  65. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  66. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  67. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  68. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  69. package/dist/lib/secrets/bundles.js +157 -65
  70. package/dist/lib/secrets/filestore.d.ts +5 -3
  71. package/dist/lib/secrets/filestore.js +12 -8
  72. package/dist/lib/secrets/index.js +34 -8
  73. package/dist/lib/secrets/reaper.d.ts +10 -0
  74. package/dist/lib/secrets/reaper.js +50 -15
  75. package/dist/lib/secrets/sync-passphrase.d.ts +27 -0
  76. package/dist/lib/secrets/sync-passphrase.js +78 -0
  77. package/dist/lib/session/recovery.d.ts +37 -0
  78. package/dist/lib/session/recovery.js +95 -0
  79. package/dist/lib/shims.d.ts +2 -2
  80. package/dist/lib/shims.js +24 -5
  81. package/dist/lib/startup/command-registry.d.ts +1 -0
  82. package/dist/lib/startup/command-registry.js +2 -0
  83. package/dist/lib/sync-umbrella.d.ts +5 -0
  84. package/dist/lib/sync-umbrella.js +5 -4
  85. package/dist/lib/tmux/session.d.ts +4 -2
  86. package/dist/lib/tmux/session.js +5 -5
  87. package/dist/lib/types.d.ts +2 -0
  88. package/package.json +1 -1
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Resource inventory — the single chokepoint for "what does this agent@version
3
+ * have" (RUSH-2238, parent RUSH-2236; spec: openspec/specs/resource-inventory).
4
+ *
5
+ * One API answers four orthogonal questions per (agent, version, kind):
6
+ *
7
+ * capable — the agent+version feature flags allow the kind at all
8
+ * declared — resources the DotAgents layers (project/user/system/extra)
9
+ * declare for sync
10
+ * onDisk — resources actually installed in the version home filesystem
11
+ * (the installed-resource truth — NOT agents.yaml tracking data)
12
+ * wired — resources the harness native config references so they fire
13
+ * unmanaged — onDisk − declared (orphans no layer declares anymore)
14
+ *
15
+ * Callers (inspect, doctor, view, sync) MUST come here rather than
16
+ * re-implementing path joins or config parsing per command. Wiring parsing
17
+ * currently covers the settings.json family (claude, droid,
18
+ * muse), Grok hooks.json, and Kimi config.toml via checkVersionHookWiring.
19
+ */
20
+ import type { AgentId } from './types.js';
21
+ import { type HookWiringReport } from './hooks.js';
22
+ /** Resource kinds the inventory can report. Only 'hooks' is implemented so far
23
+ * (RUSH-2238); the remaining kinds land with their follow-up tickets. */
24
+ export type InventoryKind = 'hooks' | 'skills' | 'commands' | 'plugins' | 'mcp';
25
+ /** A reference to one resource in one state (declared / onDisk / wired). */
26
+ export interface ResourceRef {
27
+ /** Stable install name (script basename without extension for hooks). */
28
+ name: string;
29
+ /** Declared/onDisk: absolute file path. Wired: the command string the native
30
+ * config references. */
31
+ path: string;
32
+ /** Origin: a layer name for declared ('project' | 'user' | 'system' | extra
33
+ * repo alias), 'user'/'project' scope or 'version-home' for onDisk,
34
+ * 'native-config' for wired. */
35
+ source: string;
36
+ /** Optional context — e.g. the lifecycle events a wired hook fires on. */
37
+ detail?: string;
38
+ }
39
+ export interface ResourceInventory {
40
+ agent: AgentId;
41
+ version: string;
42
+ kind: InventoryKind;
43
+ /** Agent+version feature flags allow this kind (version-aware gate). */
44
+ capable: boolean;
45
+ /** Declared in the DotAgents layers. */
46
+ declared: ResourceRef[];
47
+ /** Installed on disk in the version home. */
48
+ onDisk: ResourceRef[];
49
+ /** Referenced by the harness native config (empty when wiringSupported is
50
+ * false — the format parser for that harness is not implemented yet). */
51
+ wired: ResourceRef[];
52
+ /** onDisk − declared: installed orphans no layer declares. */
53
+ unmanaged: ResourceRef[];
54
+ /** Whether this harness's native wiring was read successfully. When false,
55
+ * `wired: []` means "unknown" (unsupported family OR unparseable config),
56
+ * NOT "nothing wired". Format-supported but corrupt configs set this false. */
57
+ wiringSupported: boolean;
58
+ /** Native-format diagnostic retained for doctor; derived in this same pass. */
59
+ wiring?: HookWiringReport;
60
+ }
61
+ /**
62
+ * The single inventory API. Harness-scoped (claude/codex/grok/kimi/droid) —
63
+ * never a model or profile. Throws for kinds without an implementation rather
64
+ * than returning a half-empty report that looks like truth.
65
+ */
66
+ export declare function getResourceInventory(agent: AgentId, version: string, kind: InventoryKind, opts?: {
67
+ cwd?: string;
68
+ }): ResourceInventory;
69
+ /**
70
+ * On-disk hook listing for an explicit home (version home or effective home)
71
+ * plus the project overlay — the one listing `getAgentResources` routes
72
+ * through, so inspect/doctor/view share a single hooks source. Paths resolve
73
+ * through the absolute-hooksDir-safe join (RUSH-2237), so grok/kimi version
74
+ * homes list their real hooks.
75
+ */
76
+ export declare function listOnDiskHooks(agent: AgentId, opts?: {
77
+ home?: string;
78
+ cwd?: string;
79
+ }): ResourceRef[];
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Resource inventory — the single chokepoint for "what does this agent@version
3
+ * have" (RUSH-2238, parent RUSH-2236; spec: openspec/specs/resource-inventory).
4
+ *
5
+ * One API answers four orthogonal questions per (agent, version, kind):
6
+ *
7
+ * capable — the agent+version feature flags allow the kind at all
8
+ * declared — resources the DotAgents layers (project/user/system/extra)
9
+ * declare for sync
10
+ * onDisk — resources actually installed in the version home filesystem
11
+ * (the installed-resource truth — NOT agents.yaml tracking data)
12
+ * wired — resources the harness native config references so they fire
13
+ * unmanaged — onDisk − declared (orphans no layer declares anymore)
14
+ *
15
+ * Callers (inspect, doctor, view, sync) MUST come here rather than
16
+ * re-implementing path joins or config parsing per command. Wiring parsing
17
+ * currently covers the settings.json family (claude, droid,
18
+ * muse), Grok hooks.json, and Kimi config.toml via checkVersionHookWiring.
19
+ */
20
+ import { AGENTS } from './agents.js';
21
+ import { supports } from './capabilities.js';
22
+ import { listResources } from './resources.js';
23
+ import { checkVersionHookWiring, listHooksInVersionHome, listInstalledHooksWithScope, } from './hooks.js';
24
+ const IMPLEMENTED_KINDS = ['hooks'];
25
+ /**
26
+ * The single inventory API. Harness-scoped (claude/codex/grok/kimi/droid) —
27
+ * never a model or profile. Throws for kinds without an implementation rather
28
+ * than returning a half-empty report that looks like truth.
29
+ */
30
+ export function getResourceInventory(agent, version, kind, opts = {}) {
31
+ if (!IMPLEMENTED_KINDS.includes(kind)) {
32
+ // Fail loud at the boundary: the kind is part of the API shape but its
33
+ // inventory lands with a RUSH-2236 follow-up — a silent empty report would
34
+ // read as "nothing installed".
35
+ throw new Error(`getResourceInventory: kind '${kind}' is not implemented yet (implemented: ${IMPLEMENTED_KINDS.join(', ')}; tracked under RUSH-2236)`);
36
+ }
37
+ return hooksInventory(agent, version, opts.cwd);
38
+ }
39
+ /**
40
+ * On-disk hook listing for an explicit home (version home or effective home)
41
+ * plus the project overlay — the one listing `getAgentResources` routes
42
+ * through, so inspect/doctor/view share a single hooks source. Paths resolve
43
+ * through the absolute-hooksDir-safe join (RUSH-2237), so grok/kimi version
44
+ * homes list their real hooks.
45
+ */
46
+ export function listOnDiskHooks(agent, opts = {}) {
47
+ return listInstalledHooksWithScope(agent, opts.cwd ?? process.cwd(), { home: opts.home }).map((h) => ({
48
+ name: h.name,
49
+ path: h.path,
50
+ source: h.scope,
51
+ detail: h.dataFile,
52
+ }));
53
+ }
54
+ function hooksInventory(agent, version, cwd) {
55
+ const capable = supports(agent, 'hooks', version).ok;
56
+ // Declared: the layered DotAgents repos are the declaration surface. Hook
57
+ // script names carry their extension here ('git-guard.sh'); normalize to the
58
+ // extension-less install name so declared/onDisk sets compare.
59
+ const declared = listResources('hooks', cwd).map((r) => ({
60
+ name: r.name.replace(/\.[^./]+$/, ''),
61
+ path: r.path,
62
+ source: r.source,
63
+ }));
64
+ // onDisk / wired need the version home; both are empty for an agent whose
65
+ // hooks capability is gated off (unknown agents included — AGENTS lookup
66
+ // would otherwise throw).
67
+ let onDisk = [];
68
+ let wired = [];
69
+ let wiringSupported = false;
70
+ let wiring;
71
+ if (capable && AGENTS[agent]?.supportsHooks) {
72
+ onDisk = listHooksInVersionHome(agent, version).map((e) => ({
73
+ name: e.name,
74
+ path: e.scriptPath,
75
+ source: 'version-home',
76
+ detail: e.dataFile,
77
+ }));
78
+ const report = checkVersionHookWiring(agent, version);
79
+ wiring = report;
80
+ // supported=true means the harness format is known; settingsUnparseable means
81
+ // we could not trust the file. Only report wired counts when the parse succeeded —
82
+ // otherwise inspect/doctor would render "wired 0" for corrupt configs (PR #2140 review).
83
+ wiringSupported = report.supported && !report.settingsUnparseable;
84
+ if (wiringSupported) {
85
+ // One ref per hook, events folded into detail — a hook wired under
86
+ // PreToolUse AND Stop is one inventory entry, not two.
87
+ const eventsByName = new Map();
88
+ const commandByName = new Map();
89
+ for (const issue of report.wired) {
90
+ let events = eventsByName.get(issue.name);
91
+ if (!events) {
92
+ events = new Set();
93
+ eventsByName.set(issue.name, events);
94
+ commandByName.set(issue.name, issue.command);
95
+ }
96
+ events.add(issue.event);
97
+ }
98
+ wired = [...eventsByName.entries()]
99
+ .sort(([a], [b]) => a.localeCompare(b))
100
+ .map(([name, events]) => ({
101
+ name,
102
+ path: commandByName.get(name),
103
+ source: 'native-config',
104
+ detail: [...events].sort().join('/'),
105
+ }));
106
+ }
107
+ }
108
+ const declaredNames = new Set(declared.map((r) => r.name));
109
+ const unmanaged = onDisk.filter((r) => !declaredNames.has(r.name));
110
+ return {
111
+ agent,
112
+ version,
113
+ kind: 'hooks',
114
+ capable,
115
+ declared,
116
+ onDisk,
117
+ wired,
118
+ unmanaged,
119
+ wiringSupported,
120
+ wiring,
121
+ };
122
+ }
@@ -7,7 +7,7 @@ import * as path from 'path';
7
7
  import { AGENTS, listInstalledMcpsWithScope } from './agents.js';
8
8
  import { listInstalledCommandsWithScope } from './commands.js';
9
9
  import { listInstalledSkillsWithScope } from './skills.js';
10
- import { listInstalledHooksWithScope } from './hooks.js';
10
+ import { listOnDiskHooks } from './resource-inventory.js';
11
11
  import { listInstalledInstructionsWithScope } from './rules/rules.js';
12
12
  import { getEffectiveHome } from './versions.js';
13
13
  import { listMcpServerConfigs } from './mcp.js';
@@ -352,11 +352,14 @@ export function getAgentResources(agentId, options = {}) {
352
352
  });
353
353
  }
354
354
  }
355
- // Hooks
355
+ // Hooks — routed through the resource-inventory chokepoint (RUSH-2238) so
356
+ // inspect/doctor/view share one listing and one (absolute-hooksDir-safe)
357
+ // path resolution.
356
358
  const hooks = [];
357
- for (const hook of listInstalledHooksWithScope(agentId, cwd, { home })) {
358
- if (shouldInclude(hook.scope)) {
359
- hooks.push({ name: hook.name, path: hook.path, scope: hook.scope });
359
+ for (const ref of listOnDiskHooks(agentId, { cwd, home })) {
360
+ const hookScope = ref.source;
361
+ if (shouldInclude(hookScope)) {
362
+ hooks.push({ name: ref.name, path: ref.path, scope: hookScope });
360
363
  }
361
364
  }
362
365
  // Workflows
@@ -21,6 +21,7 @@ export interface ResolvedRunDefaults extends RunDefaults {
21
21
  sources: {
22
22
  mode?: string;
23
23
  model?: string;
24
+ effort?: string;
24
25
  };
25
26
  }
26
27
  export interface RunDefaultEntry {
@@ -30,6 +31,7 @@ export interface RunDefaultEntry {
30
31
  type RunDefaultsInput = {
31
32
  mode?: unknown;
32
33
  model?: unknown;
34
+ effort?: unknown;
33
35
  };
34
36
  export declare function normalizeRunDefaultMode(input: string): Mode;
35
37
  export declare function parseRunDefaultSelector(input: string): ParsedRunDefaultSelector;
@@ -17,6 +17,7 @@ import { readMeta, updateMeta } from './state.js';
17
17
  import { getProjectRunConfigs } from './run-config.js';
18
18
  import chalk from 'chalk';
19
19
  const VERSION_RE = /^(?:\*|latest|(?!.*\.\.)[A-Za-z0-9._+-]{1,64})$/;
20
+ const RUN_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'auto'];
20
21
  function isAgentId(value) {
21
22
  return value in AGENTS;
22
23
  }
@@ -42,6 +43,12 @@ function normalizeRunDefaults(defaults, selector) {
42
43
  }
43
44
  out.model = defaults.model.trim();
44
45
  }
46
+ if (defaults.effort !== undefined) {
47
+ if (typeof defaults.effort !== 'string' || !RUN_EFFORTS.includes(defaults.effort)) {
48
+ throw new Error('Invalid effort in run.defaults.' + selector + ': use one of: ' + RUN_EFFORTS.join(', ') + '.');
49
+ }
50
+ out.effort = defaults.effort;
51
+ }
45
52
  return out;
46
53
  }
47
54
  export function parseRunDefaultSelector(input) {
@@ -97,6 +104,10 @@ export function resolveRunDefaultsFromConfig(runConfig, agent, version) {
97
104
  resolved.model = wildcard.model;
98
105
  resolved.sources.model = wildcardSelector;
99
106
  }
107
+ if (wildcard?.effort) {
108
+ resolved.effort = wildcard.effort;
109
+ resolved.sources.effort = wildcardSelector;
110
+ }
100
111
  if (exactSelector && defaults[exactSelector]) {
101
112
  const exact = normalizeRunDefaults(defaults[exactSelector], exactSelector);
102
113
  if (exact.mode) {
@@ -107,6 +118,10 @@ export function resolveRunDefaultsFromConfig(runConfig, agent, version) {
107
118
  resolved.model = exact.model;
108
119
  resolved.sources.model = exactSelector;
109
120
  }
121
+ if (exact.effort) {
122
+ resolved.effort = exact.effort;
123
+ resolved.sources.effort = exactSelector;
124
+ }
110
125
  }
111
126
  return resolved;
112
127
  }
@@ -122,6 +137,10 @@ export function resolveRunDefaultsFromConfigs(runConfigs, agent, version) {
122
137
  resolved.model = next.model;
123
138
  resolved.sources.model = next.sources.model;
124
139
  }
140
+ if (next.effort) {
141
+ resolved.effort = next.effort;
142
+ resolved.sources.effort = next.sources.effort;
143
+ }
125
144
  }
126
145
  return resolved;
127
146
  }
@@ -135,6 +154,8 @@ export function formatRunDefaultEntry(entry) {
135
154
  parts.push(`mode ${chalk.white(entry.defaults.mode)}`);
136
155
  if (entry.defaults.model)
137
156
  parts.push(`model ${chalk.white(entry.defaults.model)}`);
157
+ if (entry.defaults.effort)
158
+ parts.push('effort ' + chalk.white(entry.defaults.effort));
138
159
  return `${chalk.cyan(entry.selector.padEnd(22))} ${parts.join(' ')}`;
139
160
  }
140
161
  export function listRunDefaults() {
@@ -149,8 +170,8 @@ export function listRunDefaults() {
149
170
  export function setRunDefault(selectorInput, defaultsInput) {
150
171
  const parsed = parseRunDefaultSelector(selectorInput);
151
172
  const defaults = normalizeRunDefaults(defaultsInput, parsed.selector);
152
- if (!defaults.mode && !defaults.model) {
153
- throw new Error('Set at least one default: --mode <mode> or --model <model>.');
173
+ if (!defaults.mode && !defaults.model && !defaults.effort) {
174
+ throw new Error('Set at least one default: --mode <mode>, --model <model>, or --effort <effort>.');
154
175
  }
155
176
  updateMeta((meta) => {
156
177
  const run = { ...(meta.run ?? {}) };
@@ -31,7 +31,8 @@ import { backgroundSpawnOptions, killTree } from './platform/process.js';
31
31
  import lockfile from 'proper-lockfile';
32
32
  import { ensureLockTarget } from './fs-atomic.js';
33
33
  import { walkForFiles } from './fs-walk.js';
34
- import { getBinaryPath, isVersionInstalled, resolveVersion } from './versions.js';
34
+ import { getBinaryPath, isVersionInstalled, resolveVersion, getVersionHomePath } from './versions.js';
35
+ import { resolveClaudeSetupToken } from './claude-account-token.js';
35
36
  import { getConfiguredRunStrategy, resolveRunVersion, resolveAccountVersion, rotationFailoverChain, readinessFromCandidate, formatNoHealthyAccountError, } from './rotate.js';
36
37
  import { readAuthHealth, isDeadVerdict } from './auth-health.js';
37
38
  import { machineId } from './machine-id.js';
@@ -537,22 +538,26 @@ export function buildRoutineSpawnEnv(baseEnv, agent, version, timezone, overlayH
537
538
  if (v !== undefined)
538
539
  out[k] = v;
539
540
  }
540
- // A routine authenticates through the pinned account's own CLAUDE_CONFIG_DIR
541
- // login on THIS box (buildExecEnv points it at the per-account version home).
542
- // Claude Code's interactive session refreshes itself per-device; keeping the
543
- // daemon out of the credential entirely is what avoids the fleet-wide rotation
544
- // logout a shared/rotating token was the cause, not the fix.
545
- //
546
- // Injecting a token was already ruled out, but INHERITING one was not:
547
- // buildExecEnv spreads the ambient process.env (exec.ts) and sanitizeProcessEnv
548
- // only strips loader/interpreter vars, never credentials. So on any box whose
549
- // daemon environment happens to carry CLAUDE_CODE_OAUTH_TOKEN, every routine
550
- // spawn silently ran on that one shared rotating token instead of the host's
551
- // own login the exact fleet-wide-logout path, arriving by inheritance rather
552
- // than injection. CI never caught it because CI has no token to inherit; a
553
- // provisioned box does. Drop it here so a routine always uses the login of the
554
- // machine it runs on.
555
- delete out.CLAUDE_CODE_OAUTH_TOKEN;
541
+ // CLAUDE_CODE_OAUTH_TOKEN comes in two flavours, and only one is safe for a
542
+ // routine. KEEP a per-account `claude setup-token` (long-lived, NON-rotating,
543
+ // keyed to this home's own account) that buildExecEnv injected from the reserved
544
+ // `auth` bundle (resolveClaudeSetupToken) that is the durable cure for the
545
+ // single-use-refresh-token revocation storm: a setup-token never rotates, so a
546
+ // scheduled routine can't land on a sibling home's just-rotated-out credential.
547
+ // STRIP an INHERITED ambient value instead: buildExecEnv spreads process.env
548
+ // (exec.ts) and sanitizeProcessEnv leaves credentials, so a daemon env that
549
+ // happens to carry a shared/rotating CLAUDE_CODE_OAUTH_TOKEN would otherwise make
550
+ // every routine run on that one token — the RUSH-1822 fleet-wide-logout path.
551
+ // Distinguish by value: only the resolved setup-token survives.
552
+ // Authoritative: buildExecEnv injects the setup-token but then spreads the caller
553
+ // env over it, so an ambient CLAUDE_CODE_OAUTH_TOKEN would win re-assert here.
554
+ const setupToken = agent === 'claude' && version
555
+ ? resolveClaudeSetupToken(getVersionHomePath('claude', version))
556
+ : null;
557
+ if (setupToken)
558
+ out.CLAUDE_CODE_OAUTH_TOKEN = setupToken;
559
+ else
560
+ delete out.CLAUDE_CODE_OAUTH_TOKEN;
556
561
  if (agent === 'cursor' && overlayHome) {
557
562
  // prepareJobHome links this host's Cursor auth file here. Pin XDG_CONFIG_HOME
558
563
  // to the overlay so an ambient value cannot bypass the routine sandbox.
@@ -8,6 +8,8 @@
8
8
  <string>Agents CLI</string>
9
9
  <key>CFBundleDisplayName</key>
10
10
  <string>Agents CLI</string>
11
+ <key>CFBundleIconFile</key>
12
+ <string>AppIcon</string>
11
13
  <key>CFBundleExecutable</key>
12
14
  <string>Agents CLI</string>
13
15
  <key>CFBundlePackageType</key>
@@ -3,9 +3,21 @@
3
3
  <plist version="1.0">
4
4
  <dict>
5
5
  <key>files</key>
6
- <dict/>
6
+ <dict>
7
+ <key>Resources/AppIcon.icns</key>
8
+ <data>
9
+ DFq5H08EkhgWIC3UvGMR9B58BZw=
10
+ </data>
11
+ </dict>
7
12
  <key>files2</key>
8
13
  <dict>
14
+ <key>Resources/AppIcon.icns</key>
15
+ <dict>
16
+ <key>hash2</key>
17
+ <data>
18
+ mBSjM6jlvN7J1jQowsvqTl7CHM0pur0e6qsHMuzk5k0=
19
+ </data>
20
+ </dict>
9
21
  <key>embedded.provisionprofile</key>
10
22
  <dict>
11
23
  <key>hash2</key>