@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.
Files changed (209) hide show
  1. package/CHANGELOG.md +989 -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 +31 -1
  13. package/dist/commands/exec.js +31 -3
  14. package/dist/commands/export.d.ts +11 -0
  15. package/dist/commands/export.js +215 -0
  16. package/dist/commands/feed.d.ts +7 -1
  17. package/dist/commands/feed.js +47 -4
  18. package/dist/commands/go.d.ts +9 -2
  19. package/dist/commands/go.js +16 -6
  20. package/dist/commands/harness.d.ts +14 -0
  21. package/dist/commands/harness.js +145 -0
  22. package/dist/commands/import.js +87 -9
  23. package/dist/commands/inspect.js +5 -2
  24. package/dist/commands/profiles.d.ts +20 -0
  25. package/dist/commands/profiles.js +68 -21
  26. package/dist/commands/repo.js +14 -0
  27. package/dist/commands/routines.js +15 -2
  28. package/dist/commands/secrets.js +44 -21
  29. package/dist/commands/sessions-browser.js +1 -1
  30. package/dist/commands/sessions-export.js +19 -5
  31. package/dist/commands/sessions-migrate.d.ts +29 -0
  32. package/dist/commands/sessions-migrate.js +596 -0
  33. package/dist/commands/sessions-picker.js +16 -1
  34. package/dist/commands/sessions.d.ts +36 -13
  35. package/dist/commands/sessions.js +210 -40
  36. package/dist/commands/setup-computer.js +2 -2
  37. package/dist/commands/setup.js +6 -1
  38. package/dist/commands/ssh.js +71 -35
  39. package/dist/commands/status.js +10 -2
  40. package/dist/commands/versions.js +51 -11
  41. package/dist/commands/view.d.ts +5 -0
  42. package/dist/commands/view.js +99 -31
  43. package/dist/commands/watchdog.d.ts +7 -2
  44. package/dist/commands/watchdog.js +73 -57
  45. package/dist/index.js +59 -7
  46. package/dist/lib/activity.d.ts +14 -4
  47. package/dist/lib/activity.js +17 -5
  48. package/dist/lib/actor.d.ts +50 -0
  49. package/dist/lib/actor.js +166 -0
  50. package/dist/lib/agent-spec/provider.js +2 -1
  51. package/dist/lib/agent-spec/resolve.js +19 -5
  52. package/dist/lib/agent-spec/types.d.ts +9 -1
  53. package/dist/lib/agents.d.ts +56 -0
  54. package/dist/lib/agents.js +133 -7
  55. package/dist/lib/cloud/codex.d.ts +2 -0
  56. package/dist/lib/cloud/codex.js +14 -3
  57. package/dist/lib/cloud/session-index.d.ts +32 -0
  58. package/dist/lib/cloud/session-index.js +58 -0
  59. package/dist/lib/cloud/store.d.ts +7 -0
  60. package/dist/lib/cloud/store.js +25 -0
  61. package/dist/lib/config-transfer.d.ts +16 -0
  62. package/dist/lib/config-transfer.js +60 -0
  63. package/dist/lib/daemon.js +18 -2
  64. package/dist/lib/devices/connect.d.ts +15 -1
  65. package/dist/lib/devices/connect.js +15 -1
  66. package/dist/lib/devices/fleet.d.ts +21 -0
  67. package/dist/lib/devices/fleet.js +26 -0
  68. package/dist/lib/devices/health-report.d.ts +38 -0
  69. package/dist/lib/devices/health-report.js +214 -0
  70. package/dist/lib/devices/health.js +4 -1
  71. package/dist/lib/devices/reachability.d.ts +31 -0
  72. package/dist/lib/devices/reachability.js +40 -0
  73. package/dist/lib/devices/registry.d.ts +33 -0
  74. package/dist/lib/devices/registry.js +37 -0
  75. package/dist/lib/devices/resolve-target.d.ts +15 -27
  76. package/dist/lib/devices/resolve-target.js +63 -102
  77. package/dist/lib/devices/sync.d.ts +18 -0
  78. package/dist/lib/devices/sync.js +23 -1
  79. package/dist/lib/devices/tailscale.d.ts +3 -0
  80. package/dist/lib/devices/tailscale.js +1 -0
  81. package/dist/lib/events.d.ts +1 -1
  82. package/dist/lib/exec.d.ts +66 -0
  83. package/dist/lib/exec.js +195 -6
  84. package/dist/lib/export.d.ts +72 -0
  85. package/dist/lib/export.js +269 -0
  86. package/dist/lib/feed-post.d.ts +63 -0
  87. package/dist/lib/feed-post.js +204 -0
  88. package/dist/lib/fleet/apply.d.ts +32 -2
  89. package/dist/lib/fleet/apply.js +97 -10
  90. package/dist/lib/fleet/types.d.ts +11 -0
  91. package/dist/lib/fs-walk.d.ts +13 -0
  92. package/dist/lib/fs-walk.js +16 -7
  93. package/dist/lib/hooks.d.ts +1 -0
  94. package/dist/lib/hooks.js +156 -0
  95. package/dist/lib/hosts/dispatch.d.ts +7 -0
  96. package/dist/lib/hosts/dispatch.js +2 -0
  97. package/dist/lib/hosts/passthrough.js +2 -0
  98. package/dist/lib/hosts/reconnect.d.ts +78 -0
  99. package/dist/lib/hosts/reconnect.js +127 -0
  100. package/dist/lib/hosts/registry.d.ts +75 -14
  101. package/dist/lib/hosts/registry.js +205 -30
  102. package/dist/lib/hosts/remote-cmd.js +1 -0
  103. package/dist/lib/hosts/run-target.d.ts +13 -5
  104. package/dist/lib/hosts/run-target.js +26 -10
  105. package/dist/lib/hosts/session-index.d.ts +18 -1
  106. package/dist/lib/hosts/session-index.js +37 -2
  107. package/dist/lib/hosts/session-marker.d.ts +33 -0
  108. package/dist/lib/hosts/session-marker.js +51 -0
  109. package/dist/lib/hosts/tasks.d.ts +8 -4
  110. package/dist/lib/import.d.ts +25 -0
  111. package/dist/lib/import.js +107 -2
  112. package/dist/lib/isolation-boundary-report.d.ts +10 -0
  113. package/dist/lib/isolation-boundary-report.js +35 -0
  114. package/dist/lib/menubar/install-menubar.d.ts +20 -0
  115. package/dist/lib/menubar/install-menubar.js +45 -1
  116. package/dist/lib/migrate.d.ts +16 -0
  117. package/dist/lib/migrate.js +36 -0
  118. package/dist/lib/models.d.ts +27 -0
  119. package/dist/lib/models.js +54 -1
  120. package/dist/lib/overdue.d.ts +7 -2
  121. package/dist/lib/overdue.js +18 -11
  122. package/dist/lib/picker.js +4 -1
  123. package/dist/lib/platform/process.d.ts +17 -0
  124. package/dist/lib/platform/process.js +70 -0
  125. package/dist/lib/profiles-presets.js +9 -7
  126. package/dist/lib/profiles.d.ts +31 -0
  127. package/dist/lib/profiles.js +70 -0
  128. package/dist/lib/project-resources.js +20 -5
  129. package/dist/lib/pty-server.d.ts +2 -10
  130. package/dist/lib/pty-server.js +4 -38
  131. package/dist/lib/registry.d.ts +1 -1
  132. package/dist/lib/registry.js +48 -8
  133. package/dist/lib/rotate.d.ts +18 -0
  134. package/dist/lib/rotate.js +28 -0
  135. package/dist/lib/routines.d.ts +26 -0
  136. package/dist/lib/routines.js +19 -2
  137. package/dist/lib/runner.js +104 -7
  138. package/dist/lib/secrets/agent.d.ts +66 -1
  139. package/dist/lib/secrets/agent.js +154 -90
  140. package/dist/lib/secrets/bundles.d.ts +34 -0
  141. package/dist/lib/secrets/bundles.js +179 -19
  142. package/dist/lib/secrets/index.d.ts +15 -1
  143. package/dist/lib/secrets/index.js +21 -3
  144. package/dist/lib/secrets/rc-hygiene.d.ts +55 -0
  145. package/dist/lib/secrets/rc-hygiene.js +154 -0
  146. package/dist/lib/secrets/remote.d.ts +7 -4
  147. package/dist/lib/secrets/remote.js +7 -4
  148. package/dist/lib/secrets/sync-commands.d.ts +21 -0
  149. package/dist/lib/secrets/sync-commands.js +21 -0
  150. package/dist/lib/self-update.d.ts +23 -2
  151. package/dist/lib/self-update.js +86 -5
  152. package/dist/lib/session/active.d.ts +114 -7
  153. package/dist/lib/session/active.js +323 -52
  154. package/dist/lib/session/cloud.js +2 -1
  155. package/dist/lib/session/db.d.ts +48 -0
  156. package/dist/lib/session/db.js +238 -14
  157. package/dist/lib/session/detached.d.ts +30 -0
  158. package/dist/lib/session/detached.js +92 -0
  159. package/dist/lib/session/discover.d.ts +465 -2
  160. package/dist/lib/session/discover.js +1648 -468
  161. package/dist/lib/session/fork.js +2 -1
  162. package/dist/lib/session/hook-sessions.d.ts +43 -0
  163. package/dist/lib/session/hook-sessions.js +135 -0
  164. package/dist/lib/session/linear.d.ts +6 -0
  165. package/dist/lib/session/linear.js +55 -0
  166. package/dist/lib/session/migrate-targets.d.ts +65 -0
  167. package/dist/lib/session/migrate-targets.js +94 -0
  168. package/dist/lib/session/migrations.d.ts +37 -0
  169. package/dist/lib/session/migrations.js +60 -0
  170. package/dist/lib/session/parse.d.ts +18 -0
  171. package/dist/lib/session/parse.js +130 -30
  172. package/dist/lib/session/pid-registry.d.ts +34 -2
  173. package/dist/lib/session/pid-registry.js +49 -2
  174. package/dist/lib/session/render.d.ts +7 -2
  175. package/dist/lib/session/render.js +22 -10
  176. package/dist/lib/session/short-id.d.ts +17 -0
  177. package/dist/lib/session/short-id.js +20 -0
  178. package/dist/lib/session/state.js +6 -2
  179. package/dist/lib/shims.d.ts +27 -1
  180. package/dist/lib/shims.js +88 -6
  181. package/dist/lib/sqlite.d.ts +3 -2
  182. package/dist/lib/sqlite.js +27 -4
  183. package/dist/lib/staleness/detectors/commands.js +1 -1
  184. package/dist/lib/staleness/detectors/workflows.js +13 -2
  185. package/dist/lib/staleness/writers/commands.js +7 -7
  186. package/dist/lib/staleness/writers/workflows.d.ts +4 -2
  187. package/dist/lib/startup/command-registry.d.ts +2 -0
  188. package/dist/lib/startup/command-registry.js +5 -0
  189. package/dist/lib/state.d.ts +1 -1
  190. package/dist/lib/state.js +15 -36
  191. package/dist/lib/teams/agents.d.ts +19 -10
  192. package/dist/lib/teams/agents.js +40 -39
  193. package/dist/lib/types.d.ts +48 -2
  194. package/dist/lib/uninstall.js +1 -45
  195. package/dist/lib/usage.d.ts +33 -1
  196. package/dist/lib/usage.js +172 -12
  197. package/dist/lib/versions.d.ts +22 -2
  198. package/dist/lib/versions.js +98 -10
  199. package/dist/lib/watchdog/log.d.ts +43 -0
  200. package/dist/lib/watchdog/log.js +69 -0
  201. package/dist/lib/watchdog/routine.d.ts +44 -0
  202. package/dist/lib/watchdog/routine.js +69 -0
  203. package/dist/lib/watchdog/runner.d.ts +51 -7
  204. package/dist/lib/watchdog/runner.js +239 -64
  205. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  206. package/dist/lib/watchdog/watchdog.js +31 -16
  207. package/dist/lib/workflows.d.ts +16 -0
  208. package/dist/lib/workflows.js +110 -1
  209. package/package.json +1 -1
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * Picks `bun:sqlite` under Bun and `node:sqlite` under Node (>=22.5). Avoids
5
5
  * the native `better-sqlite3` addon entirely so there is no prebuild compile
6
- * and no Node/Bun ABI mismatch when the same source runs in tests (Bun) and
7
- * production (Node).
6
+ * and no Node/Bun ABI mismatch. Both runtimes are production: `dist/index.js`
7
+ * runs under Node, while the signed standalone `dist/bin/agents` (what the
8
+ * shims exec) embeds Bun.
8
9
  *
9
10
  * Exposes the small better-sqlite3-shaped surface area the rest of the
10
11
  * codebase already uses: `prepare/exec/pragma/transaction/close` on the DB,
@@ -21,9 +22,31 @@ const sqliteMod = isBun
21
22
  // bun:sqlite exports `Database`; node:sqlite exports `DatabaseSync`.
22
23
  const NativeDatabase = sqliteMod.Database
23
24
  ?? sqliteMod.DatabaseSync;
25
+ /**
26
+ * bun:sqlite binds a named-parameter object ONLY when its keys carry the SQL
27
+ * sigil (`{ '@id': … }` for `VALUES (@id)`); bare keys (`{ id: … }`) match
28
+ * nothing and every parameter stays NULL, so the first NOT NULL column raises a
29
+ * constraint error and the write is lost. node:sqlite accepts the bare keys.
30
+ * `strict: true` makes bun accept them too, so the bare-key call shape this
31
+ * codebase uses works on both runtimes. node:sqlite has no such option and
32
+ * rejects a second argument that isn't an object, so the argument list is built
33
+ * per runtime.
34
+ *
35
+ * The two runtimes are still not interchangeable at the edges, and only bun's
36
+ * half is exercised by the shipped binary rather than by vitest — so keep binds
37
+ * inside the intersection:
38
+ * - omit a named key: bun throws `Missing parameter "x"`, node binds NULL.
39
+ * - pass an extra named key: bun accepts it, node throws `Unknown named
40
+ * parameter`.
41
+ * - use sigil keys (`{'@id': …}`): node accepts them, strict bun rejects them.
42
+ * - a single non-plain object positional arg (e.g. `run(new Date())`) reaches
43
+ * the named path via bindArgs below and throws under strict bun.
44
+ */
45
+ const NATIVE_ARGS = isBun ? [{ strict: true }] : [];
24
46
  function bindArgs(params) {
25
47
  // Both bindings accept positional `(a, b, c)` and named `({ a, b, c })`
26
- // forms. Pass an object through unchanged so callers using named binds work.
48
+ // forms bun only under `strict: true` (see NATIVE_ARGS). Pass an object
49
+ // through unchanged so callers using named binds work.
27
50
  if (params.length === 1 &&
28
51
  params[0] !== null &&
29
52
  typeof params[0] === 'object' &&
@@ -51,7 +74,7 @@ class StatementImpl {
51
74
  class Database {
52
75
  inner;
53
76
  constructor(filename) {
54
- this.inner = new NativeDatabase(filename);
77
+ this.inner = new NativeDatabase(filename, ...NATIVE_ARGS);
55
78
  }
56
79
  prepare(sql) {
57
80
  return new StatementImpl(this.inner.prepare(sql));
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Commands detector — mirrors versions.ts:343-357. Inspects the version home,
3
3
  * returns command names. Honors the commands-as-skills marker for skills-only
4
- * agents (grok, kimi, Codex >= 0.117.0, …); falls back to scanning
4
+ * agents (kimi, Codex >= 0.117.0, …); falls back to scanning
5
5
  * `{agentDir}/<commandsSubdir>/` for the native path.
6
6
  */
7
7
  import * as fs from 'fs';
@@ -1,12 +1,14 @@
1
1
  /**
2
- * Workflows detector — scans `{versionHome}/workflows/` for subdirectories
3
- * containing WORKFLOW.md. Mirrors versions.ts:551-558.
2
+ * Workflows detector — scans the agent-specific on-disk layout for synced
3
+ * workflow names (Claude WORKFLOW.md trees, Kimi flow skills, Antigravity
4
+ * global_workflows, Goose recipes, OpenClaw Lobster, Grok Rhai).
4
5
  */
5
6
  import * as fs from 'fs';
6
7
  import * as os from 'os';
7
8
  import * as path from 'path';
8
9
  import * as yaml from 'yaml';
9
10
  import { capableAgents } from '../../capabilities.js';
11
+ import { grokWorkflowMarker } from '../../workflows.js';
10
12
  import { lazyAgentMap } from '../writers/lazy-map.js';
11
13
  function buildWorkflowsDetector(agent) {
12
14
  return {
@@ -93,6 +95,15 @@ function buildWorkflowsDetector(agent) {
93
95
  })
94
96
  .map(d => d.name.slice(0, -'.lobster'.length));
95
97
  }
98
+ if (agent === 'grok') {
99
+ const workflowsDir = path.join(versionHome, '.grok', 'workflows');
100
+ if (!fs.existsSync(workflowsDir))
101
+ return [];
102
+ return fs.readdirSync(workflowsDir, { withFileTypes: true })
103
+ .filter(d => d.isFile() && d.name.endsWith('.rhai') && !d.name.startsWith('.'))
104
+ .filter(d => grokWorkflowMarker(path.join(workflowsDir, d.name)) === d.name.slice(0, -'.rhai'.length))
105
+ .map(d => d.name.slice(0, -'.rhai'.length));
106
+ }
96
107
  const workflowsDir = path.join(versionHome, 'workflows');
97
108
  if (!fs.existsSync(workflowsDir))
98
109
  return [];
@@ -4,10 +4,10 @@
4
4
  * Two physical formats, picked per-(agent, version) at write time:
5
5
  *
6
6
  * - command-as-skill — fires when `shouldInstallCommandAsSkill(agent, version)`
7
- * is true. Used for grok (no native commands, but skills/) and Codex
8
- * >= 0.117.0 (commands capability ends, skills capability remains).
9
- * Writes `{agentDir}/skills/<name>/SKILL.md` with the `agents_command`
10
- * marker; the agent picks it up as a slash-command equivalent.
7
+ * is true. Used for Codex >= 0.117.0 (commands capability ends, skills
8
+ * capability remains) and agents with skills but no native command-file dir
9
+ * such as kimi. Writes `{agentDir}/skills/<name>/SKILL.md` with the
10
+ * `agents_command` marker; the agent picks it up as a slash-command equivalent.
11
11
  *
12
12
  * - native command file — `{agentDir}/<commandsSubdir>/<name>.md` (or .toml
13
13
  * when the agent's format is toml). Standard path for Claude, Codex
@@ -83,8 +83,8 @@ function buildCommandsWriter(agent) {
83
83
  // Built lazily on first access — see lazy-map.ts for the cycle rationale.
84
84
  //
85
85
  // Registration covers two cases:
86
- // - native commands (claude, codex < 0.117.0, gemini, etc.) — `commands` cap
87
- // - commands-as-skills (grok, codex >= 0.117.0)
86
+ // - native commands (claude, codex < 0.117.0, gemini, grok, etc.) — `commands` cap
87
+ // - commands-as-skills (kimi, codex >= 0.117.0)
88
88
  //
89
89
  // Agents that have skills but use a NATIVE non-file slash-command system
90
90
  // (openclaw → Gateway-based commands) are NOT registered. They declare
@@ -97,7 +97,7 @@ export const commandsWriters = lazyAgentMap(() => {
97
97
  if (cfg.capabilities.commands === false && (!cfg.skillsDir || cfg.skillsDir === ''))
98
98
  continue;
99
99
  // Skills-capable agent with no native command-file dir: convert commands to
100
- // skills by default (grok, kimi, …). Opt out only agents with their own
100
+ // skills by default (kimi, …). Opt out only agents with their own
101
101
  // slash-command runtime (openclaw).
102
102
  if (cfg.capabilities.commands === false && (!cfg.commandsSubdir || cfg.commandsSubdir === '')) {
103
103
  if (cfg.nativeCommandRuntime)
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Workflows writer — copies each workflow directory into
3
- * `<versionHome>/workflows/<name>/` via `syncWorkflowToVersion`.
2
+ * Workflows writer — copies each selected workflow into the agent-specific
3
+ * on-disk layout via `syncWorkflowToVersion` (Claude: WORKFLOW.md tree under
4
+ * `{versionHome}/workflows/`; Grok: native `.rhai` under
5
+ * `{versionHome}/.grok/workflows/`).
4
6
  */
5
7
  import type { AgentId } from '../../types.js';
6
8
  import type { ResourceWriter } from './types.js';
@@ -41,6 +41,7 @@ export declare const loadWorkflows: ModuleLoader;
41
41
  export declare const loadWorktree: ModuleLoader;
42
42
  export declare const loadVersions: ModuleLoader;
43
43
  export declare const loadImport: ModuleLoader;
44
+ export declare const loadExport: ModuleLoader;
44
45
  export declare const loadPackages: ModuleLoader;
45
46
  export declare const loadRoutines: ModuleLoader;
46
47
  export declare const loadMonitors: ModuleLoader;
@@ -56,6 +57,7 @@ export declare const loadApply: ModuleLoader;
56
57
  export declare const loadCheck: ModuleLoader;
57
58
  export declare const loadStatus: ModuleLoader;
58
59
  export declare const loadProfiles: ModuleLoader;
60
+ export declare const loadHarness: ModuleLoader;
59
61
  export declare const loadSecrets: ModuleLoader;
60
62
  export declare const loadLogin: ModuleLoader;
61
63
  export declare const loadWallet: ModuleLoader;
@@ -19,6 +19,7 @@ export const loadWorkflows = async () => (await import('../../commands/workflows
19
19
  export const loadWorktree = async () => (await import('../../commands/worktree.js')).registerWorktreeCommands;
20
20
  export const loadVersions = async () => (await import('../../commands/versions.js')).registerVersionsCommands;
21
21
  export const loadImport = async () => (await import('../../commands/import.js')).registerImportCommand;
22
+ export const loadExport = async () => (await import('../../commands/export.js')).registerExportCommand;
22
23
  export const loadPackages = async () => (await import('../../commands/packages.js')).registerPackagesCommands;
23
24
  export const loadRoutines = async () => (await import('../../commands/routines.js')).registerRoutinesCommands;
24
25
  export const loadMonitors = async () => (await import('../../commands/monitors.js')).registerMonitorsCommands;
@@ -34,6 +35,7 @@ export const loadApply = async () => (await import('../../commands/apply.js')).r
34
35
  export const loadCheck = async () => (await import('../../commands/check.js')).registerCheckCommand;
35
36
  export const loadStatus = async () => (await import('../../commands/status.js')).registerStatusCommand;
36
37
  export const loadProfiles = async () => (await import('../../commands/profiles.js')).registerProfilesCommands;
38
+ export const loadHarness = async () => (await import('../../commands/harness.js')).registerHarnessCommands;
37
39
  export const loadSecrets = async () => (await import('../../commands/secrets.js')).registerSecretsCommands;
38
40
  export const loadLogin = async () => (await import('../../commands/login.js')).registerLoginCommands;
39
41
  export const loadWallet = async () => (await import('../../commands/wallet.js')).registerWalletCommands;
@@ -127,6 +129,7 @@ export const COMMAND_LOADERS = {
127
129
  purge: [loadVersions],
128
130
  prune: [loadVersions, loadPrune],
129
131
  import: [loadImport],
132
+ export: [loadExport],
130
133
  registry: [loadPackages],
131
134
  search: [loadPackages],
132
135
  install: [loadPackages],
@@ -144,6 +147,8 @@ export const COMMAND_LOADERS = {
144
147
  status: [loadStatus],
145
148
  profile: [loadProfiles],
146
149
  profiles: [loadProfiles],
150
+ harness: [loadHarness],
151
+ harnesses: [loadHarness],
147
152
  secrets: [loadSecrets],
148
153
  login: [loadLogin],
149
154
  logout: [loadLogin],
@@ -165,7 +165,7 @@ export declare function getDriveDir(): string;
165
165
  export declare function getTrashDir(): string;
166
166
  /** Path to local session indexer storage (~/.agents/.history/sessions/). */
167
167
  export declare function getSessionsDir(): string;
168
- /** Path to the session index database (~/.agents/.history/sessions.db). */
168
+ /** Path to the session index database (~/.agents/.history/sessions/sessions.db). */
169
169
  export declare function getSessionsDbPath(): string;
170
170
  /** Path to teams config + registry (~/.agents/teams/). */
171
171
  export declare function getTeamsDir(): string;
package/dist/lib/state.js CHANGED
@@ -27,7 +27,6 @@ import * as path from 'path';
27
27
  import * as os from 'os';
28
28
  import * as yaml from 'yaml';
29
29
  import { ensureLockTarget, atomicWriteFileSync, withFileLock } from './fs-atomic.js';
30
- import { SEEDED_REGISTRIES } from './types.js';
31
30
  import { machineId } from './machine-id.js';
32
31
  const HOME = process.env.HOME ?? os.homedir();
33
32
  /**
@@ -376,7 +375,7 @@ export function getDriveDir() { return DRIVE_DIR; }
376
375
  export function getTrashDir() { return TRASH_DIR; }
377
376
  /** Path to local session indexer storage (~/.agents/.history/sessions/). */
378
377
  export function getSessionsDir() { return SESSIONS_DIR; }
379
- /** Path to the session index database (~/.agents/.history/sessions.db). */
378
+ /** Path to the session index database (~/.agents/.history/sessions/sessions.db). */
380
379
  export function getSessionsDbPath() { return SESSIONS_DB_PATH; }
381
380
  /** Path to teams config + registry (~/.agents/teams/). */
382
381
  export function getTeamsDir() { return TEAMS_DIR; }
@@ -606,18 +605,25 @@ function writeIfChanged(filePath, content) {
606
605
  * `agents:` / `versions:` are not written (no empty committed files).
607
606
  */
608
607
  function writeMetaUnlocked(meta) {
609
- const { agents, versions, defaultBrowserProfile, ...central } = meta;
608
+ const { agents, isolatedAgents, versions, defaultBrowserProfile, ...central } = meta;
610
609
  // Write the machine-local files FIRST, then strip central — so a crash mid-write
611
610
  // never removes pins/versions from central before they're persisted elsewhere.
612
611
  const devicePath = getDeviceMetaPath();
613
612
  const hasAgents = !!agents && Object.keys(agents).length > 0;
613
+ // The isolated pointer names a version installed on THIS machine, exactly like a
614
+ // global pin, so it belongs beside `agents:` in the device file rather than in the
615
+ // central doc that syncs — otherwise another machine inherits a pointer to a copy
616
+ // it does not have.
617
+ const hasIsolatedAgents = !!isolatedAgents && Object.keys(isolatedAgents).length > 0;
614
618
  const hasDefaultBrowser = !!defaultBrowserProfile;
615
- if (hasAgents || hasDefaultBrowser) {
619
+ if (hasAgents || hasIsolatedAgents || hasDefaultBrowser) {
616
620
  // Device-local doc carries `agents:` pins and `defaultBrowserProfile:` — both
617
621
  // are per-machine and must never land in central agents.yaml (which syncs).
618
622
  const deviceDoc = {};
619
623
  if (hasAgents)
620
624
  deviceDoc.agents = agents;
625
+ if (hasIsolatedAgents)
626
+ deviceDoc.isolatedAgents = isolatedAgents;
621
627
  if (hasDefaultBrowser)
622
628
  deviceDoc.defaultBrowserProfile = defaultBrowserProfile;
623
629
  fs.mkdirSync(path.dirname(devicePath), { recursive: true });
@@ -640,8 +646,9 @@ function writeMetaUnlocked(meta) {
640
646
  }
641
647
  /**
642
648
  * Overlay this machine's local state onto a central-portable Meta:
643
- * - `agents:` from the device file (device wins; the union both preserves the
644
- * one-level merge and self-heals a pre-migration central that still has pins)
649
+ * - `agents:` and `isolatedAgents:` from the device file (device wins; the union
650
+ * both preserves the one-level merge and self-heals a pre-migration central that
651
+ * still has pins)
645
652
  * - `defaultBrowserProfile:` from the device file (device is the sole source;
646
653
  * the field is stripped from central on write, so nothing to merge against)
647
654
  * - `versions:` from the history JSON (wholesale replace; falls back to
@@ -654,6 +661,8 @@ function overlayMachineLocal(meta) {
654
661
  const dm = yaml.parse(fs.readFileSync(devicePath, 'utf-8'));
655
662
  if (dm?.agents)
656
663
  meta.agents = { ...meta.agents, ...dm.agents };
664
+ if (dm?.isolatedAgents)
665
+ meta.isolatedAgents = { ...meta.isolatedAgents, ...dm.isolatedAgents };
657
666
  if (dm?.defaultBrowserProfile)
658
667
  meta.defaultBrowserProfile = dm.defaultBrowserProfile;
659
668
  }
@@ -670,29 +679,6 @@ function overlayMachineLocal(meta) {
670
679
  }
671
680
  return meta;
672
681
  }
673
- function applyRegistrySeeds(meta) {
674
- const seeded = new Set(meta.seededPresets || []);
675
- let changed = false;
676
- for (const [type, presets] of Object.entries(SEEDED_REGISTRIES)) {
677
- for (const [name, config] of Object.entries(presets)) {
678
- const key = `${type}.${name}`;
679
- if (seeded.has(key))
680
- continue;
681
- if (!meta.registries)
682
- meta.registries = { mcp: {}, skill: {} };
683
- if (!meta.registries[type])
684
- meta.registries[type] = {};
685
- if (!meta.registries[type][name]) {
686
- meta.registries[type][name] = { ...config };
687
- }
688
- seeded.add(key);
689
- changed = true;
690
- }
691
- }
692
- if (changed)
693
- meta.seededPresets = [...seeded];
694
- return changed;
695
- }
696
682
  /**
697
683
  * One-shot migration: move agents.yaml from system repo to user repo.
698
684
  * Idempotent — no-ops if user file already exists or system file absent.
@@ -803,17 +789,10 @@ export function readMeta() {
803
789
  };
804
790
  }
805
791
  overlayMachineLocal(meta);
806
- if (applyRegistrySeeds(meta)) {
807
- writeMeta(meta);
808
- return rememberMeta(meta);
809
- }
810
792
  return rememberMeta(meta);
811
793
  }
812
794
  const meta = createDefaultMeta();
813
795
  overlayMachineLocal(meta);
814
- if (applyRegistrySeeds(meta)) {
815
- writeMeta(meta);
816
- }
817
796
  return rememberMeta(meta);
818
797
  }
819
798
  /** Serialize and write agents.yaml to the user repo, invalidating the in-memory cache. */
@@ -1,3 +1,4 @@
1
+ import { captureProcessStartTime } from '../platform/index.js';
1
2
  import { AgentType } from './parsers.js';
2
3
  /**
3
4
  * Compute the Lowest Common Ancestor (LCA) of multiple file paths.
@@ -26,18 +27,25 @@ export type { AgentType } from './parsers.js';
26
27
  */
27
28
  export declare function buildSentinelCommand(cmd: string[], exitCodePath: string): string;
28
29
  /**
29
- * Capture a stable identifier for a process at the moment it was started.
30
- * Used to defeat PID reuse: a kill(pid, ...) is only safe when the process
31
- * still occupies the PID we observed at spawn time. A bare kill(pid, 0)
32
- * probe cannot tell whether the OS has recycled the slot to an unrelated
33
- * process combined with detached spawns and unref(), that's exactly how
34
- * `agents teams stop` ends up SIGKILLing random process groups.
30
+ * Env for a locally-spawned teammate. Freezes ONE actor for the whole spawn
31
+ * tree: actorEnv(resolveActor()) stamps AGENTS_ACTOR* onto the child env, so the
32
+ * teammate's inner `agents run` reads it via inheritedActor and short-circuits
33
+ * computeActor instead of re-resolving every teammate under one orchestrator
34
+ * shares the orchestrator's single frozen actor (see actor.ts). Precedence:
35
+ * process env < actor < the teammate's --env overrides, so an explicit override
36
+ * still wins. Single source of truth shared by launchProcess() and its test.
37
+ */
38
+ export declare function buildTeammateSpawnEnv(envOverrides: Record<string, string> | null): NodeJS.ProcessEnv;
39
+ /**
40
+ * Re-exported from `platform/process.ts`, which owns the one implementation.
35
41
  *
36
- * Linux: field 22 of /proc/<pid>/stat (starttime in clock ticks since boot).
37
- * macOS: output of `ps -o lstart= -p <pid>` (start time in human format).
38
- * Returns null on any error so callers can skip the guard rather than crash.
42
+ * This module used to carry its own near-identical copy, and that copy had no
43
+ * Windows branch it fell through to `ps`, which does not exist there, so
44
+ * `captureProcessStartTime` always returned null and the pid-reuse guard at
45
+ * `stop()` was silently inert on Windows. That is exactly how `agents teams stop`
46
+ * ends up SIGKILLing an unrelated process group once the OS recycles a pid.
39
47
  */
40
- export declare function captureProcessStartTime(pid: number): string | null;
48
+ export { captureProcessStartTime };
41
49
  /**
42
50
  * Reasoning-intensity knob. Passed through to `agents run --effort`, which
43
51
  * translates it into per-agent reasoning flags (claude --effort, codex
@@ -130,6 +138,7 @@ export declare class AgentProcess {
130
138
  startedAt: Date;
131
139
  completedAt: Date | null;
132
140
  parentSessionId: string | null;
141
+ actor: string | null;
133
142
  cloudSessionId: string | null;
134
143
  cloudProvider: string | null;
135
144
  prUrl: string | null;
@@ -7,7 +7,7 @@
7
7
  * dependency scheduling via --after, per-teammate model/effort overrides, and
8
8
  * multiple permission modes (plan, edit, full).
9
9
  */
10
- import { spawn, execSync, execFileSync } from 'child_process';
10
+ import { spawn, execSync } from 'child_process';
11
11
  import { getAgentsInvocation } from '../daemon.js';
12
12
  import * as fs from 'fs/promises';
13
13
  import * as fsSync from 'fs';
@@ -15,7 +15,7 @@ import * as path from 'path';
15
15
  import * as os from 'os';
16
16
  import { randomUUID } from 'crypto';
17
17
  import { resolveAgentsDir } from './persistence.js';
18
- import { findExecutable } from '../platform/index.js';
18
+ import { findExecutable, captureProcessStartTime } from '../platform/index.js';
19
19
  import { normalizeEvents } from './parsers.js';
20
20
  import { debug } from './debug.js';
21
21
  import { setGeminiAutoUpdateDisabled, updateGeminiSettings } from '../gemini-settings.js';
@@ -23,6 +23,7 @@ import { getAgentsDir as getSystemAgentsDir, getShimsDir } from '../state.js';
23
23
  import { AGENTS, getAccountInfo } from '../agents.js';
24
24
  import { resolveVersion, isVersionInstalled, verifyInstalledBinaryLaunches } from '../versions.js';
25
25
  import { sanitizeProcessEnv } from '../secrets/bundles.js';
26
+ import { resolveActor, actorEnv } from '../actor.js';
26
27
  import { recordRunName } from '../session/run-names.js';
27
28
  import { sshExec, shellQuote } from '../ssh-exec.js';
28
29
  import { resolveHost } from '../hosts/registry.js';
@@ -169,42 +170,31 @@ export function buildSentinelCommand(cmd, exitCodePath) {
169
170
  return `${cmd.map(shSingleQuote).join(' ')}; echo $? > ${shSingleQuote(exitCodePath)}`;
170
171
  }
171
172
  /**
172
- * Capture a stable identifier for a process at the moment it was started.
173
- * Used to defeat PID reuse: a kill(pid, ...) is only safe when the process
174
- * still occupies the PID we observed at spawn time. A bare kill(pid, 0)
175
- * probe cannot tell whether the OS has recycled the slot to an unrelated
176
- * process combined with detached spawns and unref(), that's exactly how
177
- * `agents teams stop` ends up SIGKILLing random process groups.
178
- *
179
- * Linux: field 22 of /proc/<pid>/stat (starttime in clock ticks since boot).
180
- * macOS: output of `ps -o lstart= -p <pid>` (start time in human format).
181
- * Returns null on any error so callers can skip the guard rather than crash.
173
+ * Env for a locally-spawned teammate. Freezes ONE actor for the whole spawn
174
+ * tree: actorEnv(resolveActor()) stamps AGENTS_ACTOR* onto the child env, so the
175
+ * teammate's inner `agents run` reads it via inheritedActor and short-circuits
176
+ * computeActor instead of re-resolving every teammate under one orchestrator
177
+ * shares the orchestrator's single frozen actor (see actor.ts). Precedence:
178
+ * process env < actor < the teammate's --env overrides, so an explicit override
179
+ * still wins. Single source of truth shared by launchProcess() and its test.
182
180
  */
183
- export function captureProcessStartTime(pid) {
184
- if (!pid || pid <= 0)
185
- return null;
186
- try {
187
- if (process.platform === 'linux') {
188
- const stat = fsSync.readFileSync(`/proc/${pid}/stat`, 'utf-8');
189
- const lastParen = stat.lastIndexOf(')');
190
- if (lastParen < 0)
191
- return null;
192
- const tail = stat.slice(lastParen + 2);
193
- const fields = tail.split(' ');
194
- // After comm we are at field 3; starttime is field 22, so index 19 here.
195
- return fields[19] || null;
196
- }
197
- const out = execFileSync('ps', ['-o', 'lstart=', '-p', String(pid)], {
198
- encoding: 'utf-8',
199
- stdio: ['ignore', 'pipe', 'ignore'],
200
- });
201
- const trimmed = out.trim();
202
- return trimmed.length > 0 ? trimmed : null;
203
- }
204
- catch {
205
- return null;
206
- }
181
+ export function buildTeammateSpawnEnv(envOverrides) {
182
+ return {
183
+ ...sanitizeProcessEnv(process.env),
184
+ ...actorEnv(resolveActor()),
185
+ ...(envOverrides ?? {}),
186
+ };
207
187
  }
188
+ /**
189
+ * Re-exported from `platform/process.ts`, which owns the one implementation.
190
+ *
191
+ * This module used to carry its own near-identical copy, and that copy had no
192
+ * Windows branch — it fell through to `ps`, which does not exist there, so
193
+ * `captureProcessStartTime` always returned null and the pid-reuse guard at
194
+ * `stop()` was silently inert on Windows. That is exactly how `agents teams stop`
195
+ * ends up SIGKILLing an unrelated process group once the OS recycles a pid.
196
+ */
197
+ export { captureProcessStartTime };
208
198
  /** Agent types the team runner supports. */
209
199
  const TEAM_AGENT_TYPES = ['codex', 'cursor', 'gemini', 'claude', 'opencode', 'grok', 'antigravity', 'kimi', 'droid'];
210
200
  // Suffix appended to all prompts to ensure agents provide a summary
@@ -479,6 +469,12 @@ export class AgentProcess {
479
469
  startedAt = new Date();
480
470
  completedAt = null;
481
471
  parentSessionId = null;
472
+ // Frozen actor (resolveActor().id) this teammate runs under. Stamped onto the
473
+ // local spawn env via actorEnv (buildTeammateSpawnEnv) so the teammate's inner
474
+ // `agents run` inherits one actor for the whole tree instead of re-resolving,
475
+ // and persisted so the record shows who ran it. Set from the resolved actor at
476
+ // construction; loadFromDisk restores the persisted value.
477
+ actor = null;
482
478
  cloudSessionId = null;
483
479
  cloudProvider = null;
484
480
  prUrl = null;
@@ -564,6 +560,7 @@ export class AgentProcess {
564
560
  this.completedAt = completedAt;
565
561
  this.baseDir = baseDir;
566
562
  this.parentSessionId = parentSessionId;
563
+ this.actor = resolveActor().id;
567
564
  this.cloudSessionId = cloudSessionId;
568
565
  this.cloudProvider = cloudProvider;
569
566
  this.prUrl = prUrl;
@@ -628,6 +625,7 @@ export class AgentProcess {
628
625
  duration: this.duration(),
629
626
  mode: this.mode,
630
627
  parent_session_id: this.parentSessionId,
628
+ actor: this.actor,
631
629
  workspace_dir: this.workspaceDir,
632
630
  cloud_session_id: this.cloudSessionId,
633
631
  cloud_provider: this.cloudProvider,
@@ -884,6 +882,7 @@ export class AgentProcess {
884
882
  started_at: this.startedAt.toISOString(),
885
883
  completed_at: this.completedAt?.toISOString() || null,
886
884
  parent_session_id: this.parentSessionId,
885
+ actor: this.actor,
887
886
  cloud_session_id: this.cloudSessionId,
888
887
  cloud_provider: this.cloudProvider,
889
888
  pr_url: this.prUrl,
@@ -948,6 +947,10 @@ export class AgentProcess {
948
947
  ? meta.task_type
949
948
  : null, meta.cloud_repo || null, meta.cloud_branch || null, meta.worktree_name || null, meta.worktree_path || null, meta.profile_name || null);
950
949
  agent.startTime = typeof meta.start_time === 'string' ? meta.start_time : null;
950
+ // The persisted actor is the truth for a reload; the constructor set it to
951
+ // THIS process's resolved actor, which is wrong for a teammate someone else
952
+ // ran. Legacy teammates predating the field carry no actor -> null.
953
+ agent.actor = meta.actor ?? null;
951
954
  // Distributed-team fields: set post-construction (like startTime) so the
952
955
  // constructor signature stays fixed. Null on every pre-existing teammate.
953
956
  agent.hostName = meta.host_name || null;
@@ -1556,9 +1559,7 @@ export class AgentManager {
1556
1559
  stdio: ['ignore', stdoutFd, stdoutFd],
1557
1560
  cwd: agent.cwd || undefined,
1558
1561
  detached: true,
1559
- env: agent.envOverrides
1560
- ? { ...sanitizeProcessEnv(process.env), ...agent.envOverrides }
1561
- : sanitizeProcessEnv(process.env),
1562
+ env: buildTeammateSpawnEnv(agent.envOverrides),
1562
1563
  });
1563
1564
  await new Promise((resolve, reject) => {
1564
1565
  childProcess.once('spawn', resolve);
@@ -689,9 +689,40 @@ export interface BrandConfig {
689
689
  /** False to keep the config but stop minting/using the brand. */
690
690
  enabled: boolean;
691
691
  }
692
+ /**
693
+ * An actor -- a responsible entity behind a run (a human today, a top-level
694
+ * agent later). Keyed in the `actors:` map by a short slug. Every field is
695
+ * optional enrichment over what `tailscale whois` already resolves: pin a
696
+ * preferred git email, add a github handle, or override the display name.
697
+ * `login` is the tailnet login-name to match against (defaults to the map key).
698
+ * See lib/actor.ts.
699
+ */
700
+ export interface ActorConfig {
701
+ /** 'human' (default) or 'agent'. Only humans get personal git credit. */
702
+ kind?: 'human' | 'agent';
703
+ /** Display + git author name. Overrides the tailnet DisplayName. */
704
+ name?: string;
705
+ /** Git author email. Overrides the tailnet login email. */
706
+ email?: string;
707
+ /** GitHub handle, for PR attribution. */
708
+ github?: string;
709
+ /** Tailnet login-name this entry matches. Defaults to the map key. */
710
+ login?: string;
711
+ }
692
712
  /** Top-level structure of ~/.agents/.system/agents.yaml -- the CLI's persistent state. */
693
713
  export interface Meta {
694
714
  agents?: Partial<Record<AgentId, string>>;
715
+ /**
716
+ * Per-agent preferred ISOLATED version — which copy a bare `agents run <agent>`
717
+ * resolves to when the agent has no global default.
718
+ *
719
+ * Kept separate from `agents` on purpose. An entry there is the global default,
720
+ * which owns the launcher, the bare shim and the real `~/.<agent>` config
721
+ * symlink, and arms the self-heal `shadowing` check. An isolated copy must never
722
+ * acquire any of that, so it cannot be recorded in the same place — the
723
+ * separation is what keeps `getGlobalDefault` incapable of returning one.
724
+ */
725
+ isolatedAgents?: Partial<Record<AgentId, string>>;
695
726
  run?: RunConfig;
696
727
  /**
697
728
  * `agents run --lease` config. `secretsBundle` names the keychain secrets bundle
@@ -754,8 +785,23 @@ export interface Meta {
754
785
  */
755
786
  brands?: Record<string, BrandConfig>;
756
787
  /**
757
- * Keys like `skill.hermes` registries seeded from SEEDED_REGISTRIES exactly
758
- * once. Tracked so a user `registry remove` won't silently re-seed.
788
+ * Actors keyed by slug -- who is behind a run. Enriches or overrides the
789
+ * identity `tailscale whois` resolves (git email, github handle, display
790
+ * name, human vs agent). See lib/actor.ts.
791
+ */
792
+ actors?: Record<string, ActorConfig>;
793
+ /**
794
+ * Removal tombstones for SEEDED_REGISTRIES presets, keyed like `skill.hermes`.
795
+ *
796
+ * Seeded presets are resolved in memory by `getRegistries` (see
797
+ * `offeredSeeds`) rather than written into agents.yaml — persisting them from
798
+ * the read path dirtied this git-tracked file and deadlocked
799
+ * `agents repo pull` (RUSH-1925). A key listed here means the user ran
800
+ * `registry remove` on that preset, so it is no longer offered.
801
+ *
802
+ * Entries written by the pre-RUSH-1925 seeding also appear here; those files
803
+ * carry the registry in their own `registries:` block too, which takes
804
+ * precedence, so the preset stays exactly as configured.
759
805
  */
760
806
  seededPresets?: string[];
761
807
  /**
@@ -20,6 +20,7 @@ import * as os from 'os';
20
20
  import * as path from 'path';
21
21
  import { AGENTS, ALL_AGENT_IDS } from './agents.js';
22
22
  import { getAgentConfigPath, getConfigSymlinkVersion, stripShimPathLines, releaseAdoptedLauncher, } from './shims.js';
23
+ import { moveDirCrossDevice, copyDirStrippingAgentsSymlinks } from './config-transfer.js';
23
24
  import { getUserAgentsDir, getBackupsDir, getHistoryDir, getShimsDir, getLegacySystemAgentsDir, } from './state.js';
24
25
  /** Home dir honoring the AGENTS_REAL_HOME test/override, mirroring shims.ts. */
25
26
  function realHome() {
@@ -60,51 +61,6 @@ function symlinkTarget(p) {
60
61
  function removeLink(p) {
61
62
  fs.unlinkSync(p);
62
63
  }
63
- /**
64
- * Move `source` onto `dest` across possibly-different volumes. `renameSync` is
65
- * atomic but throws EXDEV when `~/.agents` lives on a different filesystem than
66
- * `$HOME`; fall back to copy-then-remove so the restore still completes. The
67
- * source (a backup inside `~/.agents`) is removed only after the copy succeeds,
68
- * so a mid-copy failure never destroys the sole surviving copy.
69
- */
70
- function moveDirCrossDevice(source, dest) {
71
- try {
72
- fs.renameSync(source, dest);
73
- }
74
- catch (err) {
75
- if (err.code !== 'EXDEV')
76
- throw err;
77
- fs.cpSync(source, dest, { recursive: true });
78
- fs.rmSync(source, { recursive: true, force: true });
79
- }
80
- }
81
- /**
82
- * Copy `source` to `dest`, dropping any symlink whose target resolves back into
83
- * `~/.agents`. Adoption syncs managed resources (skills/commands) into the
84
- * version home as symlinks into `~/.agents`; copying them verbatim would leave
85
- * the restored config full of links that dangle the moment `~/.agents` is
86
- * disposed. Stripping them yields a clean, self-contained restore.
87
- */
88
- function copyDirStrippingAgentsSymlinks(source, dest, agentsDir) {
89
- const inside = agentsDir + path.sep;
90
- fs.cpSync(source, dest, {
91
- recursive: true,
92
- filter: (src) => {
93
- try {
94
- const st = fs.lstatSync(src);
95
- if (st.isSymbolicLink()) {
96
- const tgt = path.resolve(path.dirname(src), fs.readlinkSync(src));
97
- if (tgt === agentsDir || tgt.startsWith(inside))
98
- return false;
99
- }
100
- }
101
- catch {
102
- /* unreadable entry — let cpSync surface it on the real copy */
103
- }
104
- return true;
105
- },
106
- });
107
- }
108
64
  /** Classify one agent's config dir without mutating anything. */
109
65
  function planConfig(agent) {
110
66
  const realPath = getAgentConfigPath(agent);