@phnx-labs/agents-cli 1.20.74 → 1.20.77

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 (231) hide show
  1. package/CHANGELOG.md +1055 -0
  2. package/README.md +36 -10
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/apply.js +21 -3
  5. package/dist/commands/attach.d.ts +10 -0
  6. package/dist/commands/attach.js +41 -0
  7. package/dist/commands/computer.js +3 -3
  8. package/dist/commands/detach-core.d.ts +46 -0
  9. package/dist/commands/detach-core.js +61 -0
  10. package/dist/commands/detach.d.ts +5 -0
  11. package/dist/commands/detach.js +171 -0
  12. package/dist/commands/doctor.js +18 -0
  13. package/dist/commands/exec.js +83 -13
  14. package/dist/commands/feed.d.ts +7 -1
  15. package/dist/commands/feed.js +47 -4
  16. package/dist/commands/go.d.ts +9 -2
  17. package/dist/commands/go.js +16 -6
  18. package/dist/commands/harness.d.ts +14 -0
  19. package/dist/commands/harness.js +145 -0
  20. package/dist/commands/import.js +38 -8
  21. package/dist/commands/inspect.js +5 -2
  22. package/dist/commands/profiles.d.ts +20 -0
  23. package/dist/commands/profiles.js +68 -21
  24. package/dist/commands/repo.js +14 -0
  25. package/dist/commands/routines.js +286 -15
  26. package/dist/commands/secrets.js +102 -37
  27. package/dist/commands/sessions-browser.js +12 -2
  28. package/dist/commands/sessions-export.js +19 -5
  29. package/dist/commands/sessions-migrate.d.ts +29 -0
  30. package/dist/commands/sessions-migrate.js +596 -0
  31. package/dist/commands/sessions-picker.d.ts +14 -1
  32. package/dist/commands/sessions-picker.js +184 -16
  33. package/dist/commands/sessions.d.ts +56 -14
  34. package/dist/commands/sessions.js +322 -62
  35. package/dist/commands/setup-computer.js +2 -2
  36. package/dist/commands/ssh.d.ts +13 -0
  37. package/dist/commands/ssh.js +110 -37
  38. package/dist/commands/status.js +10 -2
  39. package/dist/commands/versions.js +15 -6
  40. package/dist/commands/view.d.ts +5 -0
  41. package/dist/commands/view.js +24 -4
  42. package/dist/commands/watchdog.d.ts +7 -2
  43. package/dist/commands/watchdog.js +73 -57
  44. package/dist/index.js +59 -7
  45. package/dist/lib/activity.d.ts +17 -6
  46. package/dist/lib/activity.js +466 -40
  47. package/dist/lib/actor.d.ts +50 -0
  48. package/dist/lib/actor.js +166 -0
  49. package/dist/lib/agent-spec/provider.js +2 -1
  50. package/dist/lib/agent-spec/resolve.js +19 -5
  51. package/dist/lib/agent-spec/types.d.ts +9 -1
  52. package/dist/lib/agents.d.ts +21 -0
  53. package/dist/lib/agents.js +70 -6
  54. package/dist/lib/cloud/codex.d.ts +2 -0
  55. package/dist/lib/cloud/codex.js +14 -3
  56. package/dist/lib/cloud/session-index.d.ts +32 -0
  57. package/dist/lib/cloud/session-index.js +58 -0
  58. package/dist/lib/cloud/store.d.ts +7 -0
  59. package/dist/lib/cloud/store.js +25 -0
  60. package/dist/lib/config-transfer.js +4 -0
  61. package/dist/lib/daemon.d.ts +10 -1
  62. package/dist/lib/daemon.js +114 -11
  63. package/dist/lib/devices/connect.d.ts +15 -1
  64. package/dist/lib/devices/connect.js +15 -1
  65. package/dist/lib/devices/fleet.d.ts +37 -1
  66. package/dist/lib/devices/fleet.js +36 -2
  67. package/dist/lib/devices/health-report.d.ts +38 -0
  68. package/dist/lib/devices/health-report.js +214 -0
  69. package/dist/lib/devices/health.js +4 -1
  70. package/dist/lib/devices/reachability.d.ts +31 -0
  71. package/dist/lib/devices/reachability.js +40 -0
  72. package/dist/lib/devices/registry.d.ts +33 -0
  73. package/dist/lib/devices/registry.js +37 -0
  74. package/dist/lib/devices/resolve-target.d.ts +15 -27
  75. package/dist/lib/devices/resolve-target.js +63 -102
  76. package/dist/lib/devices/sync.d.ts +18 -0
  77. package/dist/lib/devices/sync.js +23 -1
  78. package/dist/lib/devices/tailscale.d.ts +3 -0
  79. package/dist/lib/devices/tailscale.js +1 -0
  80. package/dist/lib/events.d.ts +1 -1
  81. package/dist/lib/exec.d.ts +82 -0
  82. package/dist/lib/exec.js +218 -6
  83. package/dist/lib/feed-post.d.ts +63 -0
  84. package/dist/lib/feed-post.js +204 -0
  85. package/dist/lib/feed.d.ts +7 -1
  86. package/dist/lib/feed.js +96 -6
  87. package/dist/lib/fleet/apply.d.ts +32 -2
  88. package/dist/lib/fleet/apply.js +97 -10
  89. package/dist/lib/fleet/types.d.ts +11 -0
  90. package/dist/lib/fs-walk.d.ts +13 -0
  91. package/dist/lib/fs-walk.js +16 -7
  92. package/dist/lib/heal.d.ts +7 -4
  93. package/dist/lib/heal.js +10 -22
  94. package/dist/lib/hooks.d.ts +1 -0
  95. package/dist/lib/hooks.js +156 -0
  96. package/dist/lib/hosts/dispatch.d.ts +16 -0
  97. package/dist/lib/hosts/dispatch.js +26 -4
  98. package/dist/lib/hosts/passthrough.js +9 -2
  99. package/dist/lib/hosts/reconnect.d.ts +78 -0
  100. package/dist/lib/hosts/reconnect.js +127 -0
  101. package/dist/lib/hosts/registry.d.ts +75 -14
  102. package/dist/lib/hosts/registry.js +205 -30
  103. package/dist/lib/hosts/remote-cmd.d.ts +11 -0
  104. package/dist/lib/hosts/remote-cmd.js +32 -8
  105. package/dist/lib/hosts/remote-session-id.d.ts +45 -0
  106. package/dist/lib/hosts/remote-session-id.js +84 -0
  107. package/dist/lib/hosts/run-target.d.ts +17 -5
  108. package/dist/lib/hosts/run-target.js +30 -10
  109. package/dist/lib/hosts/session-index.d.ts +18 -1
  110. package/dist/lib/hosts/session-index.js +40 -5
  111. package/dist/lib/hosts/session-marker.d.ts +33 -0
  112. package/dist/lib/hosts/session-marker.js +51 -0
  113. package/dist/lib/hosts/tasks.d.ts +8 -4
  114. package/dist/lib/import.d.ts +2 -0
  115. package/dist/lib/import.js +35 -2
  116. package/dist/lib/menubar/install-menubar.d.ts +29 -0
  117. package/dist/lib/menubar/install-menubar.js +59 -1
  118. package/dist/lib/menubar/notify-desktop.d.ts +44 -0
  119. package/dist/lib/menubar/notify-desktop.js +78 -0
  120. package/dist/lib/migrate.d.ts +16 -0
  121. package/dist/lib/migrate.js +36 -0
  122. package/dist/lib/models.d.ts +27 -0
  123. package/dist/lib/models.js +54 -1
  124. package/dist/lib/overdue.d.ts +6 -2
  125. package/dist/lib/overdue.js +10 -36
  126. package/dist/lib/picker.js +4 -1
  127. package/dist/lib/platform/process.d.ts +17 -0
  128. package/dist/lib/platform/process.js +70 -0
  129. package/dist/lib/profiles-presets.js +9 -7
  130. package/dist/lib/profiles.d.ts +31 -0
  131. package/dist/lib/profiles.js +70 -0
  132. package/dist/lib/pty-server.d.ts +2 -10
  133. package/dist/lib/pty-server.js +4 -38
  134. package/dist/lib/registry.d.ts +1 -1
  135. package/dist/lib/registry.js +48 -8
  136. package/dist/lib/rotate.d.ts +18 -0
  137. package/dist/lib/rotate.js +28 -0
  138. package/dist/lib/routine-notify.d.ts +76 -0
  139. package/dist/lib/routine-notify.js +190 -0
  140. package/dist/lib/routines-placement.d.ts +38 -0
  141. package/dist/lib/routines-placement.js +79 -0
  142. package/dist/lib/routines-project.d.ts +97 -0
  143. package/dist/lib/routines-project.js +349 -0
  144. package/dist/lib/routines.d.ts +94 -0
  145. package/dist/lib/routines.js +93 -9
  146. package/dist/lib/runner.d.ts +12 -2
  147. package/dist/lib/runner.js +242 -20
  148. package/dist/lib/sandbox.js +10 -0
  149. package/dist/lib/secrets/account-token.d.ts +20 -0
  150. package/dist/lib/secrets/account-token.js +64 -0
  151. package/dist/lib/secrets/agent.d.ts +74 -4
  152. package/dist/lib/secrets/agent.js +181 -107
  153. package/dist/lib/secrets/bundles.d.ts +54 -6
  154. package/dist/lib/secrets/bundles.js +230 -34
  155. package/dist/lib/secrets/index.d.ts +26 -3
  156. package/dist/lib/secrets/index.js +42 -12
  157. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  158. package/dist/lib/secrets/rc-hygiene.js +154 -0
  159. package/dist/lib/secrets/remote.d.ts +7 -4
  160. package/dist/lib/secrets/remote.js +7 -4
  161. package/dist/lib/secrets/session-store.d.ts +6 -2
  162. package/dist/lib/secrets/session-store.js +65 -15
  163. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  164. package/dist/lib/secrets/sync-commands.js +21 -0
  165. package/dist/lib/secrets/unlock-hints.d.ts +27 -0
  166. package/dist/lib/secrets/unlock-hints.js +36 -0
  167. package/dist/lib/self-update.d.ts +23 -2
  168. package/dist/lib/self-update.js +86 -5
  169. package/dist/lib/session/active.d.ts +124 -7
  170. package/dist/lib/session/active.js +390 -56
  171. package/dist/lib/session/cloud.js +2 -1
  172. package/dist/lib/session/db.d.ts +54 -0
  173. package/dist/lib/session/db.js +320 -19
  174. package/dist/lib/session/detached.d.ts +30 -0
  175. package/dist/lib/session/detached.js +92 -0
  176. package/dist/lib/session/discover.d.ts +478 -3
  177. package/dist/lib/session/discover.js +1740 -472
  178. package/dist/lib/session/fork.js +2 -1
  179. package/dist/lib/session/hook-sessions.d.ts +43 -0
  180. package/dist/lib/session/hook-sessions.js +135 -0
  181. package/dist/lib/session/linear.d.ts +6 -0
  182. package/dist/lib/session/linear.js +55 -0
  183. package/dist/lib/session/migrate-targets.d.ts +65 -0
  184. package/dist/lib/session/migrate-targets.js +94 -0
  185. package/dist/lib/session/migrations.d.ts +37 -0
  186. package/dist/lib/session/migrations.js +60 -0
  187. package/dist/lib/session/parse.d.ts +18 -0
  188. package/dist/lib/session/parse.js +141 -32
  189. package/dist/lib/session/pid-registry.d.ts +34 -2
  190. package/dist/lib/session/pid-registry.js +49 -2
  191. package/dist/lib/session/prompt.d.ts +7 -0
  192. package/dist/lib/session/prompt.js +37 -0
  193. package/dist/lib/session/provenance.d.ts +7 -0
  194. package/dist/lib/session/render.d.ts +7 -2
  195. package/dist/lib/session/render.js +40 -26
  196. package/dist/lib/session/short-id.d.ts +17 -0
  197. package/dist/lib/session/short-id.js +20 -0
  198. package/dist/lib/session/state.d.ts +4 -0
  199. package/dist/lib/session/state.js +99 -13
  200. package/dist/lib/session/types.d.ts +25 -1
  201. package/dist/lib/session/types.js +8 -0
  202. package/dist/lib/shims.d.ts +1 -1
  203. package/dist/lib/shims.js +20 -6
  204. package/dist/lib/sqlite.d.ts +3 -2
  205. package/dist/lib/sqlite.js +27 -4
  206. package/dist/lib/staleness/detectors/commands.js +1 -1
  207. package/dist/lib/staleness/detectors/workflows.js +13 -2
  208. package/dist/lib/staleness/writers/commands.js +7 -7
  209. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  210. package/dist/lib/startup/command-registry.d.ts +1 -0
  211. package/dist/lib/startup/command-registry.js +3 -0
  212. package/dist/lib/state.d.ts +8 -4
  213. package/dist/lib/state.js +21 -37
  214. package/dist/lib/teams/agents.d.ts +19 -10
  215. package/dist/lib/teams/agents.js +40 -39
  216. package/dist/lib/types.d.ts +47 -2
  217. package/dist/lib/usage.d.ts +33 -1
  218. package/dist/lib/usage.js +172 -12
  219. package/dist/lib/versions.d.ts +9 -2
  220. package/dist/lib/versions.js +37 -8
  221. package/dist/lib/watchdog/log.d.ts +43 -0
  222. package/dist/lib/watchdog/log.js +69 -0
  223. package/dist/lib/watchdog/routine.d.ts +44 -0
  224. package/dist/lib/watchdog/routine.js +69 -0
  225. package/dist/lib/watchdog/runner.d.ts +51 -7
  226. package/dist/lib/watchdog/runner.js +239 -64
  227. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  228. package/dist/lib/watchdog/watchdog.js +31 -16
  229. package/dist/lib/workflows.d.ts +16 -0
  230. package/dist/lib/workflows.js +110 -1
  231. package/package.json +1 -1
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Actor provenance -- who initiated a run.
3
+ *
4
+ * One shared account means every session, commit, and event otherwise shows up
5
+ * as the same person. `resolveActor()` answers "which human is behind this run"
6
+ * by looking at how the process was reached:
7
+ *
8
+ * - Over SSH (the shared-fleet case): `tailscale whois` the SSH client IP to
9
+ * the connecting tailnet identity -- a real name + login email.
10
+ * - Locally (non-SSH): we can't honestly say who is at the box, so the id is
11
+ * `UNRESOLVED@<host>` and no personal git identity is claimed.
12
+ * - Inherited: a child spawn trusts the `AGENTS_ACTOR*` env its parent
13
+ * stamped rather than re-resolving, so the whole spawn tree shares one actor.
14
+ *
15
+ * The resolved actor rides the child-process env (`actorEnv()`, merged into
16
+ * `buildExecEnv`). When the actor is a resolved human, that env also carries
17
+ * `GIT_AUTHOR_*` / `GIT_COMMITTER_*`, so the agent's own `git commit` is credited
18
+ * to the person instead of the shared account.
19
+ *
20
+ * The optional `actors:` map in agents.yaml enriches or overrides a resolved
21
+ * identity (pin a work email, add a github handle, mark an entry as an agent).
22
+ */
23
+ import { spawnSync } from 'child_process';
24
+ import { machineId } from './machine-id.js';
25
+ import { parseSshConnection } from './session/provenance.js';
26
+ import { readMeta } from './state.js';
27
+ /** Hard cap on the whois shell-out — it runs on the spawn hot path. */
28
+ const WHOIS_TIMEOUT_MS = 2000;
29
+ /**
30
+ * Resolve the tailnet identity behind an IP via `tailscale whois`. Returns
31
+ * undefined when tailscale is absent, the peer is unknown, the call fails, or it
32
+ * exceeds WHOIS_TIMEOUT_MS -- every one of those falls back to an unresolved
33
+ * actor, never an error and never a hang (a wedged tailscaled is timed out, not
34
+ * waited on, since this sits in buildExecEnv on every agent spawn).
35
+ */
36
+ function tailscaleWhois(ip) {
37
+ try {
38
+ const res = spawnSync('tailscale', ['whois', '--json', ip], {
39
+ encoding: 'utf-8',
40
+ windowsHide: true,
41
+ timeout: WHOIS_TIMEOUT_MS,
42
+ });
43
+ // A timeout leaves status null (child killed by SIGTERM) -- the status guard
44
+ // below treats it as an unresolved actor, same as any other failure.
45
+ if (res.status !== 0 || !res.stdout)
46
+ return undefined;
47
+ const data = JSON.parse(res.stdout);
48
+ const up = data.UserProfile;
49
+ if (!up)
50
+ return undefined;
51
+ return { login: up.LoginName, displayName: up.DisplayName };
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ }
57
+ /** Read the actors map from config, tolerant of a missing/unreadable config. */
58
+ function readActors() {
59
+ try {
60
+ return readMeta().actors ?? {};
61
+ }
62
+ catch {
63
+ return {};
64
+ }
65
+ }
66
+ /**
67
+ * Find the actors-map entry for a resolved tailnet login. Matches on an entry's
68
+ * explicit `login`, its map key, or its `email` (all case-insensitive).
69
+ */
70
+ function findActorConfig(login, actors) {
71
+ const needle = login.toLowerCase();
72
+ for (const [key, cfg] of Object.entries(actors)) {
73
+ const candidates = [cfg.login, key, cfg.email].filter((v) => !!v);
74
+ if (candidates.some((c) => c.toLowerCase() === needle))
75
+ return cfg;
76
+ }
77
+ return undefined;
78
+ }
79
+ /**
80
+ * Map a resolved tailnet identity (+ the actors map) to a ResolvedActor. Pure --
81
+ * the impure whois/config reads happen in computeActor -- so the enrich/override
82
+ * path is fully testable. No login (local, or an unnameable peer) yields the
83
+ * honest `UNRESOLVED@<host>`; a login without a config entry still credits git
84
+ * from the tailnet DisplayName + login email.
85
+ */
86
+ export function actorFromIdentity(who, host, actors) {
87
+ const login = who?.login;
88
+ if (!login) {
89
+ return { id: `UNRESOLVED@${host}`, kind: 'human' };
90
+ }
91
+ const cfg = findActorConfig(login, actors);
92
+ const emailFromLogin = login.includes('@') ? login : undefined;
93
+ return {
94
+ id: login,
95
+ kind: cfg?.kind ?? 'human',
96
+ name: cfg?.name ?? who?.displayName,
97
+ email: cfg?.email ?? emailFromLogin,
98
+ github: cfg?.github,
99
+ };
100
+ }
101
+ /** Reconstruct an actor an ancestor process already resolved into the env. */
102
+ function inheritedActor(env) {
103
+ const id = env.AGENTS_ACTOR;
104
+ if (!id)
105
+ return undefined;
106
+ return {
107
+ id,
108
+ kind: env.AGENTS_ACTOR_KIND === 'agent' ? 'agent' : 'human',
109
+ name: env.AGENTS_ACTOR_NAME || undefined,
110
+ email: env.AGENTS_ACTOR_EMAIL || undefined,
111
+ github: env.AGENTS_ACTOR_GITHUB || undefined,
112
+ };
113
+ }
114
+ /**
115
+ * Compute the actor for a given environment. Pure with respect to `env` (the
116
+ * only impurity is the `tailscale whois` / config read on the fresh-SSH path),
117
+ * so tests can drive every branch by passing an env explicitly.
118
+ */
119
+ export function computeActor(env = process.env) {
120
+ const inherited = inheritedActor(env);
121
+ if (inherited)
122
+ return inherited;
123
+ const ssh = env.SSH_CONNECTION ? parseSshConnection(env.SSH_CONNECTION) : undefined;
124
+ const who = ssh?.clientIp ? tailscaleWhois(ssh.clientIp) : undefined;
125
+ return actorFromIdentity(who, machineId(), readActors());
126
+ }
127
+ let cached;
128
+ /**
129
+ * Resolve the actor for the current process, cached for the process lifetime
130
+ * (the SSH `whois` shell-out runs at most once).
131
+ */
132
+ export function resolveActor() {
133
+ if (!cached)
134
+ cached = computeActor(process.env);
135
+ return cached;
136
+ }
137
+ /** Clear the per-process cache. For tests, and for env changes within a run. */
138
+ export function resetActorCache() {
139
+ cached = undefined;
140
+ }
141
+ /**
142
+ * The env an actor propagates to child processes. Always carries the actor id +
143
+ * kind (so children inherit and don't re-resolve). For a resolved human with a
144
+ * real name and email, it also carries `GIT_AUTHOR_*` / `GIT_COMMITTER_*` so the
145
+ * agent's own commits are credited to the person, not the shared account. An
146
+ * unresolved actor sets no git identity -- git keeps its ambient config.
147
+ */
148
+ export function actorEnv(actor) {
149
+ const env = {
150
+ AGENTS_ACTOR: actor.id,
151
+ AGENTS_ACTOR_KIND: actor.kind,
152
+ };
153
+ if (actor.name)
154
+ env.AGENTS_ACTOR_NAME = actor.name;
155
+ if (actor.email)
156
+ env.AGENTS_ACTOR_EMAIL = actor.email;
157
+ if (actor.github)
158
+ env.AGENTS_ACTOR_GITHUB = actor.github;
159
+ if (actor.kind === 'human' && actor.name && actor.email) {
160
+ env.GIT_AUTHOR_NAME = actor.name;
161
+ env.GIT_AUTHOR_EMAIL = actor.email;
162
+ env.GIT_COMMITTER_NAME = actor.name;
163
+ env.GIT_COMMITTER_EMAIL = actor.email;
164
+ }
165
+ return env;
166
+ }
@@ -1,9 +1,10 @@
1
1
  // The production VersionProvider: the only fs/meta-coupled file in the engine.
2
2
  // Everything else in this module is pure and testable without it.
3
- import { listInstalledVersions, getGlobalDefault, getProjectVersion, isVersionInstalled, } from '../versions.js';
3
+ import { listInstalledVersions, getGlobalDefault, getIsolatedDefault, getProjectVersion, isVersionInstalled, } from '../versions.js';
4
4
  export const defaultVersionProvider = {
5
5
  listInstalled: (agent) => listInstalledVersions(agent),
6
6
  getProjectVersion: (agent, cwd) => getProjectVersion(agent, cwd),
7
7
  getGlobalDefault: (agent) => getGlobalDefault(agent),
8
+ getIsolatedDefault: (agent) => getIsolatedDefault(agent),
8
9
  isInstalled: (agent, version) => isVersionInstalled(agent, version),
9
10
  };
@@ -66,6 +66,15 @@ export function resolveAgentTargets(spec, provider, opts = {}) {
66
66
  push(agent, glob, 'global-default');
67
67
  continue;
68
68
  }
69
+ // An isolated-only agent never has a global default — that is the point — so
70
+ // without this step `--agents codex` threw "No default version set" at a user
71
+ // who had explicitly run `agents use codex@<v>`. resolveVersion already had
72
+ // this fallback; the two resolvers had drifted apart.
73
+ const iso = provider.getIsolatedDefault(agent);
74
+ if (iso) {
75
+ push(agent, iso, 'isolated-default');
76
+ continue;
77
+ }
69
78
  const installed = provider.listInstalled(agent);
70
79
  if (installed.length === 0) {
71
80
  push(agent, null, 'none');
@@ -81,13 +90,17 @@ export function resolveAgentTargets(spec, provider, opts = {}) {
81
90
  }
82
91
  throw new AgentSpecError(`No default version set for ${name}. Specify one (${agent}@<version>) or set it: agents use ${agent}@<version>.`, 'no-default', agent, installed);
83
92
  }
84
- // ----- @pinned / @default: the configured global default -----
93
+ // ----- @pinned / @default: the configured default (global, else isolated) -----
85
94
  if (qualifier === 'pinned' || qualifier === 'default') {
86
- const def = provider.getGlobalDefault(agent);
95
+ const glob = provider.getGlobalDefault(agent);
96
+ // Report which kind it was: an isolated default owns none of the launcher /
97
+ // shim / config-symlink machinery a global default does, and callers that log
98
+ // the source shouldn't claim otherwise.
99
+ const def = glob ?? provider.getIsolatedDefault(agent);
87
100
  if (!def) {
88
101
  throw new AgentSpecError(`No default version set for ${name}. Run: agents use ${agent}@<version>`, 'no-default', agent, provider.listInstalled(agent));
89
102
  }
90
- push(agent, def, 'global-default(@pinned)');
103
+ push(agent, def, glob ? 'global-default(@pinned)' : 'isolated-default');
91
104
  continue;
92
105
  }
93
106
  // ----- @all: every installed version -----
@@ -168,7 +181,8 @@ export function resolveListFilter(agent, qualifier, provider, opts = {}) {
168
181
  const q = qualifier?.trim();
169
182
  if (!q || q === 'any')
170
183
  return undefined;
171
- if (q === 'default' || q === 'pinned')
172
- return provider.getGlobalDefault(agent) ?? undefined;
184
+ if (q === 'default' || q === 'pinned') {
185
+ return provider.getGlobalDefault(agent) ?? provider.getIsolatedDefault(agent) ?? undefined;
186
+ }
173
187
  return resolveSingleAgentTarget(`${agent}@${q}`, provider, { ...opts, availableAgents: [agent] }).version;
174
188
  }
@@ -1,6 +1,6 @@
1
1
  import type { AgentId } from '../types.js';
2
2
  /** How a resolved version was arrived at — for provenance, display, and error rendering. */
3
- export type VersionSource = 'explicit' | 'project-pin' | 'global-default' | 'global-default(@pinned)' | 'sole-installed' | 'newest-installed' | 'alias-latest' | 'alias-oldest' | 'none';
3
+ export type VersionSource = 'explicit' | 'project-pin' | 'global-default' | 'isolated-default' | 'global-default(@pinned)' | 'sole-installed' | 'newest-installed' | 'alias-latest' | 'alias-oldest' | 'none';
4
4
  export interface AgentTarget {
5
5
  agent: AgentId;
6
6
  /** Resolved exact version, or null when the agent has no installed versions yet. */
@@ -32,6 +32,14 @@ export interface VersionProvider {
32
32
  getProjectVersion(agent: AgentId, cwd: string): string | null;
33
33
  /** The configured global default version, or null. */
34
34
  getGlobalDefault(agent: AgentId): string | null;
35
+ /**
36
+ * The preferred isolated copy. Kept separate from getGlobalDefault on purpose: a
37
+ * global default owns the launcher, the bare shim and the real ~/.<agent> config
38
+ * symlink, and an isolated version must never acquire any of those. Folding the
39
+ * two together here would hand an isolated version back to every caller that
40
+ * reasonably assumes a global default means "the one that owns the launcher".
41
+ */
42
+ getIsolatedDefault(agent: AgentId): string | null;
35
43
  /** Whether an exact version is installed. */
36
44
  isInstalled(agent: AgentId, version: string): boolean;
37
45
  }
@@ -265,6 +265,27 @@ export declare function __resetAntigravityKeychainCacheForTest(): void;
265
265
  * Sync, no Keychain, no network — safe on the `agents run` hot path.
266
266
  */
267
267
  export declare function isClaudeCredentialFileBlank(base: string, platform?: NodeJS.Platform): boolean;
268
+ /**
269
+ * Whether a Claude version home holds its OWN on-disk credential — a non-blank
270
+ * `.claude/.credentials.json` (the store Claude Code writes on login off macOS)
271
+ * or a `.claude/.oauth_token` file (the keychain-less shim fallback, shims.ts).
272
+ *
273
+ * RUSH-1979: the routines daemon injects one ambient `CLAUDE_CODE_OAUTH_TOKEN`
274
+ * (RUSH-1759) so a token-less default account still authenticates. But Claude —
275
+ * and the Linux shim's own `-z CLAUDE_CODE_OAUTH_TOKEN` guard — both prefer that
276
+ * env var over a pinned account's config dir, so when balanced rotation pins a
277
+ * specific account's `CLAUDE_CONFIG_DIR` the ambient token shadows it: the pool
278
+ * is inert and every fire 401s on the one stale token. A routine spawn drops the
279
+ * ambient token when the pinned account has its own credential (this returns
280
+ * true) so the account authenticates itself; when it does not (the RUSH-1759
281
+ * default), the injected token is kept.
282
+ *
283
+ * Off-darwin only, mirroring {@link isClaudeCredentialFileBlank}: on macOS the
284
+ * login Keychain is the canonical store and probing it raises an auth sheet, and
285
+ * there the daemon injects no token to shadow (broker-only, usually absent), so
286
+ * returning false changes nothing. Sync, no Keychain, no network.
287
+ */
288
+ export declare function claudeHomeHasOwnCredential(base: string, platform?: NodeJS.Platform): boolean;
268
289
  export declare function getAccountInfo(agentId: AgentId, home?: string): Promise<AccountInfo>;
269
290
  /**
270
291
  * Determine when the agent was last used by checking session file mtimes,
@@ -320,8 +320,8 @@ export const AGENTS = {
320
320
  instructionsFile: 'AGENTS.md',
321
321
  format: 'markdown',
322
322
  variableSyntax: '$ARGUMENTS',
323
- supportsHooks: false,
324
- capabilities: { hooks: false, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: { since: '1.1.1' }, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'] },
323
+ supportsHooks: true,
324
+ capabilities: { hooks: { since: '0.3.130' }, mcp: true, mcpHttp: false, mcpHeaders: false, allowlist: { since: '1.1.1' }, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'AGENTS.md' }, workflows: false, memory: false, modes: ['plan', 'edit'] },
325
325
  },
326
326
  openclaw: {
327
327
  id: 'openclaw',
@@ -479,6 +479,8 @@ export const AGENTS = {
479
479
  // ~/.grok/hooks/ (+ project .grok/hooks/) — events PreToolUse, PostToolUse, etc.
480
480
  // Plugins live in ~/.grok/plugins/ with marketplaces. Permissions: --allow/--deny
481
481
  // CLI flags or [permission] TOML block in ~/.grok/config.toml.
482
+ // Workflows (native Rhai orchestration) enabled by default as of v0.2.111 —
483
+ // projected into ~/.grok/workflows/<name>.rhai (under GROK_HOME isolation).
482
484
  grok: {
483
485
  id: 'grok',
484
486
  name: 'Grok',
@@ -487,8 +489,12 @@ export const AGENTS = {
487
489
  npmPackage: '',
488
490
  installScript: 'curl -fsSL https://x.ai/cli/install.sh | bash',
489
491
  configDir: path.join(HOME, '.grok'),
490
- commandsDir: '', // Grok primarily uses skills + slash commands from skills
491
- commandsSubdir: '',
492
+ // Grok discovers file-based slash commands from ~/.agents/commands/ (the
493
+ // cross-agent compat dir it mirrors from Claude Code) as well as the legacy
494
+ // ~/.claude/commands/ symlink. We write there directly so the per-agent
495
+ // install path and the central user repo stay in sync.
496
+ commandsDir: path.join(HOME, '.agents', 'commands'),
497
+ commandsSubdir: path.join('..', '.agents', 'commands'),
492
498
  skillsDir: path.join(HOME, '.grok', 'skills'),
493
499
  hooksDir: path.join(HOME, '.grok', 'hooks'),
494
500
  instructionsFile: 'AGENTS.md',
@@ -502,11 +508,13 @@ export const AGENTS = {
502
508
  mcpHeaders: false,
503
509
  allowlist: true, // maps to Grok's granular Bash/Edit/Write/Read/Grep/WebFetch/MCPTool rules
504
510
  skills: true,
505
- commands: false, // covered by skills
511
+ commands: true, // Grok >= 0.2.111: file-based slash commands from ~/.agents/commands/ (docs) + ~/.claude/commands/ (Claude Code compat)
506
512
  plugins: true,
507
513
  subagents: true, // ~/.grok/agents/*.md (Claude-compatible agent defs)
508
514
  rules: { file: 'AGENTS.md' },
509
- workflows: false,
515
+ // Native workflows (`.rhai` under ~/.grok/workflows/) shipped on-by-default
516
+ // in v0.2.111 (2026-07-22). See transformWorkflowForGrok in workflows.ts.
517
+ workflows: { since: '0.2.111' },
510
518
  memory: true,
511
519
  modes: ['plan', 'edit', 'skip'],
512
520
  // grok's `--permission-mode plan` silently stalls a headless `-p` run at
@@ -583,6 +591,24 @@ export const AGENTS = {
583
591
  // `.factory-plugin/` (it also reads `.claude-plugin/` for compatibility) — set
584
592
  // pluginManifestDir so syncPluginToVersion mirrors the manifest into it, the
585
593
  // same pattern codex uses with `.codex-plugin`.
594
+ //
595
+ // Workflows / Factory Missions (RUSH-1864, probed droid v0.177.0; ticket cited
596
+ // v0.161.0 — self-updating, no pinnable semver): Factory ships genuine multi-
597
+ // step orchestration ("Missions") via `/missions` (interactive) and
598
+ // `droid exec --mission` (headless; optional `-f <prompt.md>` is a *prompt*
599
+ // file, not a named mission template). Docs:
600
+ // https://docs.factory.ai/cli/features/missions and
601
+ // https://docs.factory.ai/docs/droid-exec/overview (Mission Mode).
602
+ //
603
+ // Mission state may land under `~/.factory/missions/<sessionId>/` at runtime
604
+ // (`getMissionsDir` = join(home, ".factory", "missions") + per-session
605
+ // mission.md / features.json / progress_log.jsonl) — that is **session
606
+ // runtime state**, not a discovery directory of installable named workflows.
607
+ // There is no discoverMission / template registry, and a cold install has no
608
+ // `~/.factory/missions/` until a mission is run. agents-cli's workflows
609
+ // capability means "sync WORKFLOW.md into an auto-discovered dir" — Droid
610
+ // has no such dir, so workflows stays false (invoke-only; do not fabricate a
611
+ // writer target).
586
612
  droid: {
587
613
  id: 'droid',
588
614
  name: 'Droid',
@@ -615,6 +641,7 @@ export const AGENTS = {
615
641
  plugins: true,
616
642
  subagents: true,
617
643
  rules: { file: 'AGENTS.md' },
644
+ // Factory Missions are invoke-only — no installable discovery dir (RUSH-1864).
618
645
  workflows: false,
619
646
  memory: false,
620
647
  modes: ['plan', 'edit', 'auto', 'skip'],
@@ -1377,6 +1404,43 @@ export function isClaudeCredentialFileBlank(base, platform = process.platform) {
1377
1404
  return false;
1378
1405
  }
1379
1406
  }
1407
+ /**
1408
+ * Whether a Claude version home holds its OWN on-disk credential — a non-blank
1409
+ * `.claude/.credentials.json` (the store Claude Code writes on login off macOS)
1410
+ * or a `.claude/.oauth_token` file (the keychain-less shim fallback, shims.ts).
1411
+ *
1412
+ * RUSH-1979: the routines daemon injects one ambient `CLAUDE_CODE_OAUTH_TOKEN`
1413
+ * (RUSH-1759) so a token-less default account still authenticates. But Claude —
1414
+ * and the Linux shim's own `-z CLAUDE_CODE_OAUTH_TOKEN` guard — both prefer that
1415
+ * env var over a pinned account's config dir, so when balanced rotation pins a
1416
+ * specific account's `CLAUDE_CONFIG_DIR` the ambient token shadows it: the pool
1417
+ * is inert and every fire 401s on the one stale token. A routine spawn drops the
1418
+ * ambient token when the pinned account has its own credential (this returns
1419
+ * true) so the account authenticates itself; when it does not (the RUSH-1759
1420
+ * default), the injected token is kept.
1421
+ *
1422
+ * Off-darwin only, mirroring {@link isClaudeCredentialFileBlank}: on macOS the
1423
+ * login Keychain is the canonical store and probing it raises an auth sheet, and
1424
+ * there the daemon injects no token to shadow (broker-only, usually absent), so
1425
+ * returning false changes nothing. Sync, no Keychain, no network.
1426
+ */
1427
+ export function claudeHomeHasOwnCredential(base, platform = process.platform) {
1428
+ if (platform === 'darwin')
1429
+ return false;
1430
+ if (fs.existsSync(path.join(base, '.claude', '.oauth_token')))
1431
+ return true;
1432
+ try {
1433
+ const raw = fs.readFileSync(path.join(base, '.claude', '.credentials.json'), 'utf-8');
1434
+ const oauth = JSON.parse(raw).claudeAiOauth;
1435
+ if (!oauth)
1436
+ return false;
1437
+ const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0;
1438
+ return nonEmpty(oauth.accessToken) || nonEmpty(oauth.refreshToken);
1439
+ }
1440
+ catch {
1441
+ return false;
1442
+ }
1443
+ }
1380
1444
  export async function getAccountInfo(agentId, home) {
1381
1445
  const base = home || os.homedir();
1382
1446
  const empty = {
@@ -24,3 +24,5 @@ export declare class CodexCloudProvider implements CloudProvider {
24
24
  cancel(_taskId: string): Promise<void>;
25
25
  message(_taskId: string, _content: string): Promise<void>;
26
26
  }
27
+ /** Extract a task ID from codex CLI output (JSON field, key:value line, or UUID pattern). */
28
+ export declare function extractTaskId(output: string): string | undefined;
@@ -127,8 +127,19 @@ export class CodexCloudProvider {
127
127
  if (code !== 0) {
128
128
  throw new Error(`codex cloud exec failed: ${stderr || stdout}`);
129
129
  }
130
- // Parse task ID from output. Codex typically prints the task ID.
131
- const taskId = extractTaskId(stdout) ?? `codex-${Date.now()}`;
130
+ // The task id is the ONLY handle to the just-created execution — status,
131
+ // list, and the session-index reconcile all key on it. Codex prints it to
132
+ // stdout (JSON or a task_/id: line); some builds route it to stderr, so scan
133
+ // both. A synthetic `codex-<ts>` id was the old fallback here — it can never
134
+ // match the real execution, so it silently broke every follow-up. If the id
135
+ // genuinely can't be parsed, fail loudly and point at `agents cloud list`
136
+ // (which recovers the newest execution) rather than persist a bogus id.
137
+ const taskId = extractTaskId(stdout) ?? extractTaskId(stderr);
138
+ if (!taskId) {
139
+ throw new Error('codex cloud exec did not report a task id — the run may have dispatched, but ' +
140
+ 'without the id it cannot be tracked. Find it with `agents cloud list --provider codex`, ' +
141
+ `then \`agents cloud status <id>\`.\nRaw output: ${(stdout || stderr).trim().slice(0, 400)}`);
142
+ }
132
143
  const now = new Date().toISOString();
133
144
  return {
134
145
  id: taskId,
@@ -232,7 +243,7 @@ export class CodexCloudProvider {
232
243
  }
233
244
  }
234
245
  /** Extract a task ID from codex CLI output (JSON field, key:value line, or UUID pattern). */
235
- function extractTaskId(output) {
246
+ export function extractTaskId(output) {
236
247
  // Try JSON first
237
248
  try {
238
249
  const data = JSON.parse(output);
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Register cloud-dispatched tasks into the LOCAL session index, keyed by the
3
+ * real execution id, so a launch is mappable to a session immediately.
4
+ *
5
+ * The cloud store (`tasks.db`) and the session index (`sessions` table) were two
6
+ * disjoint stores: a `agents cloud run` wrote only the store, and the session
7
+ * index learned about the run only later, via `discoverCloudSessions` hitting the
8
+ * proxy. Until that discovery ran there was no launch→session mapping at all —
9
+ * the run was orphaned in `agents sessions` exactly like the non-Claude host runs.
10
+ *
11
+ * This mirrors hosts/session-index.ts: the transcript is remote (fetched on
12
+ * demand by session/cloud.ts), so the row is registered with an EMPTY `file_path`
13
+ * — the sentinel db.js's stale-row filter treats as "always live" — and a
14
+ * `[cloud/<status>]` label. The row is written at dispatch and refreshed on every
15
+ * status poll so the label tracks the task's lifecycle. Non-session-agent cloud
16
+ * providers (a task whose agent has no transcript format) are skipped: there is
17
+ * nothing to resolve or resume by id.
18
+ */
19
+ import type { CloudTask } from './types.js';
20
+ /** Context a cloud task needs to become a session row — the LOCAL dir it was launched from. */
21
+ export interface CloudSessionContext {
22
+ /** Local directory `agents cloud run` was invoked from. Defaults to process.cwd(). */
23
+ cwd?: string;
24
+ }
25
+ /**
26
+ * Register (or refresh) a cloud task in the local session index. No-op when the
27
+ * task's agent isn't a known session agent, or its id isn't a usable execution id
28
+ * (never a fabricated `codex-<ts>` — codex.ts fails loud instead of minting one,
29
+ * but guard here too so a bad id can never seed a bogus row). Best-effort: a
30
+ * failed index write must never break the dispatch/poll it rides on.
31
+ */
32
+ export declare function registerCloudSession(task: CloudTask, ctx?: CloudSessionContext): void;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Register cloud-dispatched tasks into the LOCAL session index, keyed by the
3
+ * real execution id, so a launch is mappable to a session immediately.
4
+ *
5
+ * The cloud store (`tasks.db`) and the session index (`sessions` table) were two
6
+ * disjoint stores: a `agents cloud run` wrote only the store, and the session
7
+ * index learned about the run only later, via `discoverCloudSessions` hitting the
8
+ * proxy. Until that discovery ran there was no launch→session mapping at all —
9
+ * the run was orphaned in `agents sessions` exactly like the non-Claude host runs.
10
+ *
11
+ * This mirrors hosts/session-index.ts: the transcript is remote (fetched on
12
+ * demand by session/cloud.ts), so the row is registered with an EMPTY `file_path`
13
+ * — the sentinel db.js's stale-row filter treats as "always live" — and a
14
+ * `[cloud/<status>]` label. The row is written at dispatch and refreshed on every
15
+ * status poll so the label tracks the task's lifecycle. Non-session-agent cloud
16
+ * providers (a task whose agent has no transcript format) are skipped: there is
17
+ * nothing to resolve or resume by id.
18
+ */
19
+ import { upsertSession } from '../session/db.js';
20
+ import { isSessionTrackedAgent } from '../session/types.js';
21
+ import { deriveShortId } from '../session/short-id.js';
22
+ /** The execution-id charset the session index will accept as a row id. */
23
+ const EXECUTION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
24
+ /**
25
+ * Register (or refresh) a cloud task in the local session index. No-op when the
26
+ * task's agent isn't a known session agent, or its id isn't a usable execution id
27
+ * (never a fabricated `codex-<ts>` — codex.ts fails loud instead of minting one,
28
+ * but guard here too so a bad id can never seed a bogus row). Best-effort: a
29
+ * failed index write must never break the dispatch/poll it rides on.
30
+ */
31
+ export function registerCloudSession(task, ctx = {}) {
32
+ if (!task.agent || !isSessionTrackedAgent(task.agent))
33
+ return;
34
+ if (!task.id || !EXECUTION_ID_RE.test(task.id))
35
+ return;
36
+ try {
37
+ upsertSession({
38
+ id: task.id,
39
+ shortId: deriveShortId(task.id),
40
+ agent: task.agent,
41
+ timestamp: task.createdAt,
42
+ lastActivity: task.updatedAt,
43
+ cwd: ctx.cwd ?? process.cwd(),
44
+ project: task.repo ?? task.repos?.[0],
45
+ // Remote transcript — no local file yet. Empty file_path is the sentinel
46
+ // the DB stale-filter treats as "always live" (see hosts/session-index.ts).
47
+ filePath: '',
48
+ topic: task.prompt.split('\n')[0]?.slice(0, 120) || undefined,
49
+ // Mirrors session/cloud.ts's discovered label, so the dispatch-time row and
50
+ // a later proxy-discovered row read the same in `agents sessions`.
51
+ label: `[cloud/${task.status}]${task.branch ? ` ${task.branch}` : ''}`,
52
+ prUrl: task.prUrl,
53
+ }, '');
54
+ }
55
+ catch {
56
+ /* index write is best-effort; the task is already persisted in the cloud store */
57
+ }
58
+ }
@@ -6,6 +6,13 @@
6
6
  * remote provider each time.
7
7
  */
8
8
  import type { CloudTask, CloudProviderId, CloudTaskStatus } from './types.js';
9
+ /**
10
+ * Release the tasks.db connection so the file (and its WAL sidecars) can be
11
+ * removed. The mirror of `closeDB()` in `../session/db.ts`, which this module
12
+ * had no counterpart for — on Windows an open handle makes the file
13
+ * un-unlinkable, so anything tearing down a temp home must close this too.
14
+ */
15
+ export declare function closeStore(): void;
9
16
  /** Persist a task snapshot, replacing any existing row with the same ID. */
10
17
  export declare function insertTask(task: CloudTask): void;
11
18
  /** Update a task's status and optionally patch summary, PR URL, or branch. */
@@ -9,6 +9,7 @@ import * as fs from 'fs';
9
9
  import * as path from 'path';
10
10
  import Database from '../sqlite.js';
11
11
  import { getCloudDir } from '../state.js';
12
+ import { registerCloudSession } from './session-index.js';
12
13
  const CLOUD_DIR = getCloudDir();
13
14
  const DB_PATH = path.join(CLOUD_DIR, 'tasks.db');
14
15
  const SCHEMA = `
@@ -41,12 +42,29 @@ function db() {
41
42
  _db.exec(SCHEMA);
42
43
  return _db;
43
44
  }
45
+ /**
46
+ * Release the tasks.db connection so the file (and its WAL sidecars) can be
47
+ * removed. The mirror of `closeDB()` in `../session/db.ts`, which this module
48
+ * had no counterpart for — on Windows an open handle makes the file
49
+ * un-unlinkable, so anything tearing down a temp home must close this too.
50
+ */
51
+ export function closeStore() {
52
+ if (_db) {
53
+ _db.close();
54
+ _db = null;
55
+ }
56
+ }
44
57
  /** Persist a task snapshot, replacing any existing row with the same ID. */
45
58
  export function insertTask(task) {
46
59
  db().prepare(`
47
60
  INSERT OR REPLACE INTO tasks (id, provider, status, agent, prompt, repo, branch, pr_url, summary, created_at, updated_at)
48
61
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
49
62
  `).run(task.id, task.provider, task.status, task.agent ?? null, task.prompt, task.repo ?? null, task.branch ?? null, task.prUrl ?? null, task.summary ?? null, task.createdAt, task.updatedAt);
63
+ // Every cloud dispatch flows through here, so this is the one chokepoint that
64
+ // reconciles the cloud store with the session index: register a session row
65
+ // keyed by the real execution id so the launch is mappable to a session
66
+ // immediately, not only after a later proxy discovery (see session-index.ts).
67
+ registerCloudSession(task);
50
68
  }
51
69
  /** Update a task's status and optionally patch summary, PR URL, or branch. */
52
70
  export function updateTaskStatus(id, status, extra) {
@@ -67,6 +85,13 @@ export function updateTaskStatus(id, status, extra) {
67
85
  }
68
86
  params.push(id);
69
87
  db().prepare(`UPDATE tasks SET ${sets.join(', ')} WHERE id = ?`).run(...params);
88
+ // Keep the session index in lockstep with the store on every poll: refresh the
89
+ // row so its `[cloud/<status>]` label tracks the task's lifecycle (and picks up
90
+ // a newly-opened PR url). Reads the just-written row so the label reflects the
91
+ // update we just made, not a stale snapshot.
92
+ const updated = getTaskById(id);
93
+ if (updated)
94
+ registerCloudSession(updated);
70
95
  }
71
96
  /** Fetch a single task by its provider-assigned ID, or null if not found locally. */
72
97
  export function getTaskById(id) {
@@ -38,6 +38,10 @@ export function copyDirStrippingAgentsSymlinks(source, dest, agentsDir) {
38
38
  const inside = agentsDir + path.sep;
39
39
  fs.cpSync(source, dest, {
40
40
  recursive: true,
41
+ // `force: true` is Node's default, but Bun drops it when a `filter` is supplied —
42
+ // existing files are then silently left alone. `dist/bin/agents` is bun-compiled,
43
+ // so this is a production path, not just a test artifact. State it explicitly.
44
+ force: true,
41
45
  filter: (src) => {
42
46
  try {
43
47
  const st = fs.lstatSync(src);
@@ -81,6 +81,15 @@ export declare function runDaemon(): Promise<void>;
81
81
  export declare function readDaemonClaudeOAuthToken(opts?: {
82
82
  allowPrompt?: boolean;
83
83
  }): string | null;
84
+ /**
85
+ * Read the FULL `claude` bundle env — the main `CLAUDE_CODE_OAUTH_TOKEN` plus any
86
+ * per-account `CLAUDE_CODE_OAUTH_TOKEN_<slug>` setup-tokens. Same resolution and
87
+ * never-throws contract as {@link readDaemonClaudeOAuthToken}; returns `{}` when
88
+ * the bundle can't be read (broker-only headless miss, absent bundle, etc.).
89
+ */
90
+ export declare function readDaemonClaudeBundleEnv(opts?: {
91
+ allowPrompt?: boolean;
92
+ }): Record<string, string>;
84
93
  /**
85
94
  * Write a launchd plist or systemd unit with owner-only permissions atomically.
86
95
  *
@@ -139,7 +148,7 @@ export declare function ensureDaemonStarted(): {
139
148
  * the daemon then passes it to every routine run it spawns. An already-set
140
149
  * value (e.g. inherited from launchd) is left untouched.
141
150
  */
142
- export declare function buildDetachedDaemonEnv(baseEnv?: NodeJS.ProcessEnv, oauthToken?: string | null): NodeJS.ProcessEnv;
151
+ export declare function buildDetachedDaemonEnv(baseEnv?: NodeJS.ProcessEnv, bundleEnv?: Record<string, string>): NodeJS.ProcessEnv;
143
152
  /**
144
153
  * Resolve how to launch the daemon: `node <entry> __daemon-run`, matching the
145
154
  * exact form that works under a direct `__daemon-run`.