@phnx-labs/agents-cli 1.20.92 → 1.21.0

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 (146) hide show
  1. package/CHANGELOG.md +385 -0
  2. package/README.md +3 -0
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/beta.js +3 -5
  5. package/dist/commands/doctor.js +23 -0
  6. package/dist/commands/events.js +91 -1
  7. package/dist/commands/exec.js +13 -0
  8. package/dist/commands/mailboxes.js +39 -1
  9. package/dist/commands/message.js +12 -1
  10. package/dist/commands/packages.js +14 -5
  11. package/dist/commands/projects.d.ts +38 -0
  12. package/dist/commands/projects.js +355 -48
  13. package/dist/commands/routines.js +27 -1
  14. package/dist/commands/rules.js +6 -0
  15. package/dist/commands/secrets.d.ts +17 -0
  16. package/dist/commands/secrets.js +198 -7
  17. package/dist/commands/send.d.ts +14 -12
  18. package/dist/commands/send.js +105 -35
  19. package/dist/commands/sessions.d.ts +17 -0
  20. package/dist/commands/sessions.js +16 -8
  21. package/dist/commands/setup-browser.js +5 -1
  22. package/dist/commands/setup-fleet.js +5 -0
  23. package/dist/commands/setup-preferences.d.ts +53 -0
  24. package/dist/commands/setup-preferences.js +142 -0
  25. package/dist/commands/setup.js +8 -9
  26. package/dist/commands/ssh.js +220 -10
  27. package/dist/commands/sync.d.ts +2 -2
  28. package/dist/commands/sync.js +13 -12
  29. package/dist/commands/view.js +4 -0
  30. package/dist/commands/watchdog.js +26 -3
  31. package/dist/index.js +38 -6
  32. package/dist/lib/activity.d.ts +15 -0
  33. package/dist/lib/activity.js +26 -2
  34. package/dist/lib/auto-dispatch.d.ts +6 -1
  35. package/dist/lib/auto-dispatch.js +7 -2
  36. package/dist/lib/beta.d.ts +1 -1
  37. package/dist/lib/beta.js +1 -1
  38. package/dist/lib/channels/send.d.ts +83 -0
  39. package/dist/lib/channels/send.js +112 -0
  40. package/dist/lib/daemon.d.ts +13 -0
  41. package/dist/lib/daemon.js +80 -60
  42. package/dist/lib/device-config.d.ts +82 -0
  43. package/dist/lib/device-config.js +296 -0
  44. package/dist/lib/event-provenance.d.ts +19 -0
  45. package/dist/lib/event-provenance.js +48 -0
  46. package/dist/lib/events-ingest.d.ts +46 -0
  47. package/dist/lib/events-ingest.js +182 -0
  48. package/dist/lib/events.d.ts +17 -5
  49. package/dist/lib/events.js +59 -56
  50. package/dist/lib/feed.d.ts +1 -1
  51. package/dist/lib/feed.js +2 -0
  52. package/dist/lib/git.d.ts +13 -2
  53. package/dist/lib/git.js +38 -6
  54. package/dist/lib/linear-project-counts.d.ts +129 -0
  55. package/dist/lib/linear-project-counts.js +206 -0
  56. package/dist/lib/linear-projects.d.ts +62 -0
  57. package/dist/lib/linear-projects.js +144 -0
  58. package/dist/lib/mailbox-gc.js +30 -7
  59. package/dist/lib/mailbox.d.ts +14 -1
  60. package/dist/lib/mailbox.js +35 -3
  61. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  62. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  63. package/dist/lib/menubar/install-menubar.d.ts +5 -21
  64. package/dist/lib/menubar/install-menubar.js +35 -44
  65. package/dist/lib/menubar/notify-desktop.d.ts +17 -2
  66. package/dist/lib/menubar/notify-desktop.js +8 -2
  67. package/dist/lib/migrate.js +1 -1
  68. package/dist/lib/models.js +21 -11
  69. package/dist/lib/overdue.d.ts +1 -1
  70. package/dist/lib/overdue.js +54 -19
  71. package/dist/lib/project-import.d.ts +96 -0
  72. package/dist/lib/project-import.js +171 -0
  73. package/dist/lib/project-probe.d.ts +75 -0
  74. package/dist/lib/project-probe.js +160 -0
  75. package/dist/lib/project-resources.d.ts +8 -0
  76. package/dist/lib/project-resources.js +31 -3
  77. package/dist/lib/project-status.d.ts +32 -1
  78. package/dist/lib/project-status.js +82 -1
  79. package/dist/lib/projects.d.ts +6 -0
  80. package/dist/lib/projects.js +12 -0
  81. package/dist/lib/remote-agents-json.d.ts +14 -1
  82. package/dist/lib/remote-agents-json.js +21 -3
  83. package/dist/lib/routine-notify.d.ts +11 -0
  84. package/dist/lib/routine-notify.js +22 -0
  85. package/dist/lib/routines.d.ts +53 -0
  86. package/dist/lib/routines.js +105 -4
  87. package/dist/lib/rules/run-sync.d.ts +18 -0
  88. package/dist/lib/rules/run-sync.js +92 -0
  89. package/dist/lib/run-notify.js +3 -0
  90. package/dist/lib/scheduler.js +12 -2
  91. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  92. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  93. package/dist/lib/secrets/audit.d.ts +1 -1
  94. package/dist/lib/secrets/audit.js +53 -10
  95. package/dist/lib/secrets/index.d.ts +4 -5
  96. package/dist/lib/secrets/index.js +4 -5
  97. package/dist/lib/secrets/list-filter.d.ts +20 -5
  98. package/dist/lib/secrets/list-filter.js +22 -6
  99. package/dist/lib/secrets/usage-db.d.ts +106 -0
  100. package/dist/lib/secrets/usage-db.js +236 -0
  101. package/dist/lib/session/active.d.ts +16 -2
  102. package/dist/lib/session/active.js +8 -4
  103. package/dist/lib/session/bundle.js +5 -1
  104. package/dist/lib/session/remote-active.d.ts +5 -1
  105. package/dist/lib/session/remote-active.js +4 -1
  106. package/dist/lib/session/remote-list.d.ts +1 -15
  107. package/dist/lib/session/remote-list.js +22 -111
  108. package/dist/lib/session/sync/config.d.ts +13 -5
  109. package/dist/lib/session/sync/config.js +21 -13
  110. package/dist/lib/sqlite.js +28 -1
  111. package/dist/lib/startup/command-registry.d.ts +0 -2
  112. package/dist/lib/startup/command-registry.js +1 -5
  113. package/dist/lib/state.d.ts +19 -2
  114. package/dist/lib/state.js +99 -10
  115. package/dist/lib/sync-umbrella.d.ts +11 -23
  116. package/dist/lib/sync-umbrella.js +14 -45
  117. package/dist/lib/teams/agents.d.ts +35 -3
  118. package/dist/lib/teams/agents.js +52 -8
  119. package/dist/lib/teams/scheduler.d.ts +29 -2
  120. package/dist/lib/teams/scheduler.js +60 -16
  121. package/dist/lib/types.d.ts +22 -5
  122. package/dist/lib/versions.d.ts +6 -0
  123. package/dist/lib/versions.js +6 -4
  124. package/package.json +1 -2
  125. package/dist/commands/drive.d.ts +0 -10
  126. package/dist/commands/drive.js +0 -183
  127. package/dist/commands/hq.d.ts +0 -2
  128. package/dist/commands/hq.js +0 -58
  129. package/dist/commands/sessions-sync.d.ts +0 -17
  130. package/dist/commands/sessions-sync.js +0 -135
  131. package/dist/commands/sync-provision.d.ts +0 -23
  132. package/dist/commands/sync-provision.js +0 -107
  133. package/dist/lib/drive-sync.d.ts +0 -45
  134. package/dist/lib/drive-sync.js +0 -238
  135. package/dist/lib/hq/floor.d.ts +0 -87
  136. package/dist/lib/hq/floor.js +0 -243
  137. package/dist/lib/session/sync/crdt.d.ts +0 -44
  138. package/dist/lib/session/sync/crdt.js +0 -119
  139. package/dist/lib/session/sync/manifest.d.ts +0 -62
  140. package/dist/lib/session/sync/manifest.js +0 -100
  141. package/dist/lib/session/sync/provision.d.ts +0 -49
  142. package/dist/lib/session/sync/provision.js +0 -91
  143. package/dist/lib/session/sync/r2.d.ts +0 -32
  144. package/dist/lib/session/sync/r2.js +0 -121
  145. package/dist/lib/session/sync/sync.d.ts +0 -106
  146. package/dist/lib/session/sync/sync.js +0 -374
@@ -2,8 +2,8 @@
2
2
  * `agents sync` — synchronize central resources into an installed agent version.
3
3
  *
4
4
  * Forms:
5
- * agents sync # umbrella: fetch config repos -> reconcile all (secrets/sessions opt-in)
6
- * agents sync --repos|--secrets|--sessions # umbrella: fetch only those, then reconcile
5
+ * agents sync # umbrella: fetch config repos -> reconcile all (secrets opt-in)
6
+ * agents sync --repos|--secrets # umbrella: fetch only those, then reconcile
7
7
  * agents sync --cloud # umbrella: fetch all, skip reconcile
8
8
  * agents sync --local # umbrella: reconcile all, no fetch
9
9
  * agents sync system # one repo: git pull --rebase (pull-only mirror)
@@ -2,8 +2,8 @@
2
2
  * `agents sync` — synchronize central resources into an installed agent version.
3
3
  *
4
4
  * Forms:
5
- * agents sync # umbrella: fetch config repos -> reconcile all (secrets/sessions opt-in)
6
- * agents sync --repos|--secrets|--sessions # umbrella: fetch only those, then reconcile
5
+ * agents sync # umbrella: fetch config repos -> reconcile all (secrets opt-in)
6
+ * agents sync --repos|--secrets # umbrella: fetch only those, then reconcile
7
7
  * agents sync --cloud # umbrella: fetch all, skip reconcile
8
8
  * agents sync --local # umbrella: reconcile all, no fetch
9
9
  * agents sync system # one repo: git pull --rebase (pull-only mirror)
@@ -37,6 +37,7 @@ import { capableAgents } from '../lib/capabilities.js';
37
37
  import { parseHookManifest, registerHooksToSettings } from '../lib/hooks.js';
38
38
  import { compileRulesForProject } from '../lib/rules/compile.js';
39
39
  import { runLaunchSync } from '../lib/project-launch.js';
40
+ import { formatKeptProjectResources } from '../lib/project-resources.js';
40
41
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
41
42
  import { runUmbrellaSync } from '../lib/sync-umbrella.js';
42
43
  import { addHostOption } from '../lib/hosts/option.js';
@@ -46,7 +47,7 @@ import { getSystemAgentsDir, getUserAgentsDir, getEnabledExtraRepos } from '../l
46
47
  export function registerSyncCommand(program) {
47
48
  addHostOption(program.command('sync [agentSpec] [repo]'))
48
49
  .summary('Make this machine current, or sync resources into one agent')
49
- .description('With an [agentSpec], syncs resources (commands, skills, hooks, rules, MCPs, plugins, etc.) into that installed agent version — previews changes and lets you pick. e.g. "claude", "claude@2.1.142", a selector: @latest / @oldest / @pinned (= @default), or @all for every installed version.\n\nAppend a [repo] (or pass --repo) to scope the sync to a single DotAgent repo — system / user / project / <alias>. e.g. "agents sync claude@all system" reconciles only the system repo\'s resources into every installed Claude.\n\nGive a DotAgent repo name ALONE — "agents sync system" / "agents sync user" / "agents sync <alias>" — to git-sync that one repo: refuse if the tree is dirty, else git pull --rebase against origin. The user repo and extra aliases also push local commits up; the system repo is a pull-only mirror.\n\nWith NO agent, runs the umbrella verb: fetch the config repos then reconcile them into every installed agent. Secrets and sessions are opt-in — add --secrets to pull secret bundles or --sessions to sync transcripts (sessions are also queryable live via "agents sessions --host <machine>"). Also: --cloud (fetch only), --local (reconcile only).')
50
+ .description('With an [agentSpec], syncs resources (commands, skills, hooks, rules, MCPs, plugins, etc.) into that installed agent version — previews changes and lets you pick. e.g. "claude", "claude@2.1.142", a selector: @latest / @oldest / @pinned (= @default), or @all for every installed version.\n\nAppend a [repo] (or pass --repo) to scope the sync to a single DotAgent repo — system / user / project / <alias>. e.g. "agents sync claude@all system" reconciles only the system repo\'s resources into every installed Claude.\n\nGive a DotAgent repo name ALONE — "agents sync system" / "agents sync user" / "agents sync <alias>" — to git-sync that one repo: refuse if the tree is dirty, else git pull --rebase against origin. The user repo and extra aliases also push local commits up; the system repo is a pull-only mirror.\n\nWith NO agent, runs the umbrella verb: fetch the config repos then reconcile them into every installed agent. Secrets are opt-in — add --secrets to pull secret bundles. Session transcripts are queryable live via "agents sessions --host <machine>", or moved with "agents sessions export/import". Also: --cloud (fetch only), --local (reconcile only).')
50
51
  .option('--agent <agent>', 'Agent identifier (legacy form; prefer the positional spec)')
51
52
  .option('--agent-version <version>', 'Version to sync into (legacy form; prefer "agent@version")')
52
53
  .option('--repo <name>', 'Scope the sync to a single DotAgent repo: system / user / project / <alias> (also accepted as a positional)')
@@ -59,7 +60,6 @@ export function registerSyncCommand(program) {
59
60
  // Umbrella verb (no agent given): make this machine current.
60
61
  .option('--repos', 'Umbrella: git-pull ~/.agents + enabled ~/.agents-* extras', false)
61
62
  .option('--secrets', 'Umbrella: pull encrypted secret bundles from the remote', false)
62
- .option('--sessions', 'Umbrella: sync session transcripts across machines', false)
63
63
  .option('--cloud', 'Umbrella: fetch all remote state but skip the local reconcile', false)
64
64
  .option('--local', "Umbrella: reconcile resources into installed agents only (no fetch)", false)
65
65
  .action(async (agentSpec, repo, opts) => {
@@ -205,7 +205,7 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
205
205
  // Interactive bare `agents sync` (a TTY, no --yes, no scope flag) drops into
206
206
  // the two-checklist picker: which repos to sync from, which agents to sync
207
207
  // into. Any explicit flag or --yes keeps the non-interactive umbrella below.
208
- const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.sessions || opts.cloud || opts.local);
208
+ const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local);
209
209
  if (!quiet && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
210
210
  await runInteractiveReconcile(opts, outLog, errLog);
211
211
  return;
@@ -213,7 +213,6 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
213
213
  const flags = {
214
214
  repos: opts.repos,
215
215
  secrets: opts.secrets,
216
- sessions: opts.sessions,
217
216
  cloud: opts.cloud,
218
217
  local: opts.local,
219
218
  };
@@ -237,9 +236,6 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
237
236
  if (result.secrets) {
238
237
  parts.push(result.secrets.skipped ? 'secrets skipped' : `secrets ${result.secrets.pulled} pulled`);
239
238
  }
240
- if (result.sessions) {
241
- parts.push(result.sessions.ran ? `sessions ${result.sessions.merged} merged` : 'sessions off');
242
- }
243
239
  if (result.reconciled)
244
240
  parts.push('reconciled');
245
241
  outLog(chalk.green(`✓ sync: ${parts.join(' · ') || 'nothing to do'}`));
@@ -516,8 +512,11 @@ function printSyncDetail(result, agent, version, cwd) {
516
512
  lines.push({ kind: 'plugins', items: result.plugins });
517
513
  if (result.workflows.length > 0)
518
514
  lines.push({ kind: 'workflows', items: result.workflows });
515
+ const kept = formatKeptProjectResources(result.projectSkipped);
519
516
  if (lines.length === 0) {
520
517
  console.log(chalk.gray(`Already in sync — ${agentLabel(agent)}@${version}`));
518
+ if (kept)
519
+ console.log(chalk.gray(kept));
521
520
  return;
522
521
  }
523
522
  console.log(chalk.green(`Synced to ${agentLabel(agent)}@${version}:`));
@@ -531,6 +530,8 @@ function printSyncDetail(result, agent, version, cwd) {
531
530
  const count = chalk.cyan(`(${sorted.length})`.padStart(5));
532
531
  console.log(` ${chalk.bold(padded)} ${count} ${chalk.gray(preview)}${more}`);
533
532
  }
533
+ if (kept)
534
+ console.log(chalk.gray(kept));
534
535
  }
535
536
  function runLaunchMode(agent, version, cwd, quiet) {
536
537
  let result;
@@ -561,7 +562,7 @@ function runLaunchMode(agent, version, cwd, quiet) {
561
562
  else {
562
563
  console.log(chalk.green(`Launch sync: ${bits.join(', ')}`));
563
564
  }
564
- if (result.workspaceSkipped.length > 0) {
565
- console.log(chalk.yellow(`Skipped (user-owned, not overwritten): ${result.workspaceSkipped.join(', ')}`));
566
- }
565
+ const kept = formatKeptProjectResources(result.workspaceSkipped);
566
+ if (kept)
567
+ console.log(chalk.gray(kept));
567
568
  }
@@ -11,6 +11,7 @@ import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth
11
11
  import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
12
12
  import { readManifest } from '../lib/manifest.js';
13
13
  import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
14
+ import { formatKeptProjectResources } from '../lib/project-resources.js';
14
15
  import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
15
16
  import { getAgentResources } from '../lib/resources.js';
16
17
  import { renderMergedResources } from '../lib/merged-resources.js';
@@ -722,6 +723,9 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
722
723
  if (synced.length > 0) {
723
724
  console.log(chalk.green(`\nSynced to ${agentLabel(filterAgentId)}@${defaultVersion}: ${synced.join(', ')}`));
724
725
  }
726
+ const kept = formatKeptProjectResources(result.projectSkipped);
727
+ if (kept)
728
+ console.log(chalk.gray(kept));
725
729
  }
726
730
  }
727
731
  catch (err) {
@@ -24,6 +24,9 @@ import { setHelpSections } from '../lib/help.js';
24
24
  import { parseDuration } from '../lib/hooks/cache.js';
25
25
  import { getRuntimeStateDir } from '../lib/state.js';
26
26
  import { setJobEnabled } from '../lib/routines.js';
27
+ import { getActiveSessions } from '../lib/session/active.js';
28
+ import { mailboxIdForActiveSession } from '../lib/mailbox-target.js';
29
+ import { gcMailbox } from '../lib/mailbox-gc.js';
27
30
  import { ensureWatchdogRoutine, isWatchdogRoutineEnabled, watchdogRoutineExists, WATCHDOG_ROUTINE_NAME, WATCHDOG_ROUTINE_SCHEDULE, } from '../lib/watchdog/routine.js';
28
31
  import { runWatchdogTick, writePolicySentinel, DEFAULT_THRESHOLDS, } from '../lib/watchdog/runner.js';
29
32
  /** Default state dir the runner and these subcommands share. */
@@ -58,6 +61,21 @@ function humanMs(ms) {
58
61
  return `${Math.round(ms / 60_000)}m`;
59
62
  return `${Math.round(ms / 1000)}s`;
60
63
  }
64
+ /**
65
+ * Run the mailbox liveness sweep against the same session set the tick just used.
66
+ * Idempotent and cheap: archiving a message twice is a no-op, and GC only scans
67
+ * directories. Failures are swallowed so a mailbox-root problem cannot break the
68
+ * watchdog tick.
69
+ */
70
+ async function runMailboxGc(sessions) {
71
+ const activeBoxIds = new Set(sessions.map(mailboxIdForActiveSession).filter((id) => !!id));
72
+ try {
73
+ gcMailbox(activeBoxIds);
74
+ }
75
+ catch {
76
+ // GC is best-effort housekeeping; the next tick will retry.
77
+ }
78
+ }
61
79
  function colorForOutcome(o) {
62
80
  if (o.injected)
63
81
  return chalk.green;
@@ -118,7 +136,7 @@ export function registerWatchdogCommand(program) {
118
136
  // path is the daemon routine, which runs `agents watchdog --nudge` — so the
119
137
  // routine's enabled state IS the on/off switch, not a flag read here.
120
138
  const computeWillInject = () => opts.nudge === true;
121
- const tickOnce = async (willInject) => runWatchdogTick({
139
+ const tickOnce = async (willInject, sessions) => runWatchdogTick({
122
140
  nudge: willInject,
123
141
  nudgeText: opts.text,
124
142
  smart: opts.smart === true,
@@ -126,10 +144,13 @@ export function registerWatchdogCommand(program) {
126
144
  thresholds,
127
145
  allowGhosttyFocus: opts.allowGhosttyFocus === true,
128
146
  stateDir: stateDir(),
147
+ sessions,
129
148
  });
130
149
  if (!opts.watch) {
131
150
  const willInject = computeWillInject();
132
- const result = await tickOnce(willInject);
151
+ const sessions = await getActiveSessions();
152
+ const result = await tickOnce(willInject, sessions);
153
+ await runMailboxGc(sessions);
133
154
  if (opts.json)
134
155
  console.log(JSON.stringify(result, null, 2));
135
156
  else
@@ -146,7 +167,9 @@ export function registerWatchdogCommand(program) {
146
167
  while (true) {
147
168
  // Re-evaluated each tick: picks up enable/disable flips mid-run.
148
169
  const willInject = computeWillInject();
149
- const result = await tickOnce(willInject);
170
+ const sessions = await getActiveSessions();
171
+ const result = await tickOnce(willInject, sessions);
172
+ await runMailboxGc(sessions);
150
173
  if (opts.json)
151
174
  console.log(JSON.stringify(result));
152
175
  else
package/dist/index.js CHANGED
@@ -94,11 +94,12 @@ if (IS_DEV_BUILD) {
94
94
  // module on each invocation (which loaded the whole ~50-module tree before the
95
95
  // first byte of output), the registry maps a command name to a thunk that
96
96
  // imports only what that command needs. See src/lib/startup/command-registry.ts.
97
- import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadPerf, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadHq, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
97
+ import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadPerf, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadFeed, loadActivity, loadMailboxes, } from './lib/startup/command-registry.js';
98
98
  import { applyGlobalHelpConventions } from './lib/help.js';
99
99
  import { renderWhatsNew } from './lib/whats-new.js';
100
100
  import { getCliLaunch } from './lib/cli-entry.js';
101
101
  import { emit, emitFriction, redactArgs } from './lib/events.js';
102
+ import { die } from './lib/format.js';
102
103
  // Transparent shim delegate: the generated Windows `.cmd` shims invoke
103
104
  // `agents __shim <agent>[@version] <raw args>`. Intercept here, before commander
104
105
  // parses anything, so the agent's own flags (`--help`, `--version`, etc.) pass
@@ -147,11 +148,25 @@ function auditCommandPath(cmd) {
147
148
  return parts;
148
149
  }
149
150
  const auditStarts = new WeakMap();
151
+ /**
152
+ * Commands that WRITE the event stream, so recording their own invocation would
153
+ * add records to the log they are writing into. `events emit` is batched — one
154
+ * flush every few seconds per open editor window — so auditing it would bury the
155
+ * real events under two `command.*` records per flush. `_internal friction`
156
+ * exists for the same reason (shell guards fire before any `agents` process
157
+ * exists, so they cannot emit in-process) and had the same self-logging bug.
158
+ */
159
+ const AUDIT_EXEMPT_COMMANDS = new Set([
160
+ 'events emit',
161
+ '_internal friction',
162
+ ]);
150
163
  program.hook('preAction', (_thisCommand, actionCommand) => {
151
164
  try {
152
165
  const parts = auditCommandPath(actionCommand);
153
166
  if (parts.length === 0)
154
167
  return;
168
+ if (AUDIT_EXEMPT_COMMANDS.has(parts.join(' ')))
169
+ return;
155
170
  auditStarts.set(actionCommand, Date.now());
156
171
  emit('command.start', {
157
172
  module: parts[0],
@@ -172,6 +187,8 @@ program.hook('postAction', (_thisCommand, actionCommand) => {
172
187
  const parts = auditCommandPath(actionCommand);
173
188
  if (parts.length === 0)
174
189
  return;
190
+ if (AUDIT_EXEMPT_COMMANDS.has(parts.join(' ')))
191
+ return;
175
192
  const started = auditStarts.get(actionCommand);
176
193
  const durationMs = started !== undefined ? Date.now() - started : undefined;
177
194
  const command = parts.join(' ');
@@ -265,7 +282,6 @@ Run and dispatch:
265
282
  run <agent|profile> [prompt] Run an agent. Omit prompt for interactive mode.
266
283
  defaults Configure run defaults by agent/version selector
267
284
  teams Coordinate multiple agents on shared work
268
- hq JSON bridge for the interactive Agents HQ floor
269
285
  routines Run agents on a cron schedule (scheduler auto-starts)
270
286
  webhook Receive signed GitHub/Linear webhooks for trigger routines
271
287
  funnel Expose a webhook receiver through Tailscale Funnel
@@ -285,14 +301,13 @@ Diagnostics:
285
301
  perf Latency rollups (hooks, commands, runs) from the disposable perf warehouse
286
302
 
287
303
  Config sync:
288
- drive Sync session history across machines via rsync
289
304
  pull Clone or pull the system repo at ~/.agents/.system/
290
305
  repo init --path <dir> Scaffold your own editable repo from a template
291
306
  repo add <path|gh:user/repo> Merge an extra repo after the system repo
292
307
  lock [--frozen] Write/verify agents.lock (SHA-256 of resolved resources); --frozen fails on drift
293
308
 
294
309
  Beta features:
295
- beta Enable preview features (factory, drive, and more)
310
+ beta Enable preview features (factory and more)
296
311
 
297
312
  Automation tips:
298
313
  Pass explicit names/IDs Avoid pickers: agents sessions <id> --markdown
@@ -705,6 +720,21 @@ function registerResourcesTombstoneCommand(p) {
705
720
  await program.parseAsync(['node', 'agents', ...args]);
706
721
  });
707
722
  }
723
+ /**
724
+ * Removed `hq` command — the JSON bridge for the interactive Agents HQ floor
725
+ * (`agents hq floor --json`). No UI ever consumed it (apps/factory has zero
726
+ * references) and it had no external users, so it is gone with no replacement.
727
+ * Kept as a hidden tombstone so a stale invocation gets a clear message and a
728
+ * non-zero exit instead of commander's raw "unknown command".
729
+ */
730
+ function registerHqTombstoneCommand(p) {
731
+ p.command('hq', { hidden: true })
732
+ .allowUnknownOption()
733
+ .allowExcessArguments()
734
+ .action(() => {
735
+ die('"agents hq" was removed (internal Agents HQ floor bridge, no longer used).');
736
+ });
737
+ }
708
738
  /**
709
739
  * Hidden `agents _internal <sub>` namespace for machine-to-machine calls that
710
740
  * are not user-facing. The first subcommand is `friction`, used by shell guard
@@ -824,6 +854,9 @@ async function registerEagerForRequest(name) {
824
854
  registerResourcesTombstoneCommand(program);
825
855
  await reg(loadView);
826
856
  return true;
857
+ case 'hq':
858
+ registerHqTombstoneCommand(program);
859
+ return true;
827
860
  case '_internal':
828
861
  registerInternalCommand(program);
829
862
  return true;
@@ -896,7 +929,6 @@ async function registerAllEagerCommands() {
896
929
  await reg(loadSync);
897
930
  await reg(loadLock);
898
931
  await reg(loadRefreshRules);
899
- await reg(loadDrive);
900
932
  await reg(loadFactory);
901
933
  await reg(loadUsage);
902
934
  await reg(loadCost);
@@ -916,7 +948,7 @@ async function registerAllEagerCommands() {
916
948
  await reg(loadAudit);
917
949
  await reg(loadWebhook);
918
950
  await reg(loadFunnel);
919
- await reg(loadHq);
951
+ registerHqTombstoneCommand(program);
920
952
  await reg(loadFeed);
921
953
  await reg(loadActivity);
922
954
  await reg(loadMailboxes);
@@ -1,3 +1,4 @@
1
+ import type { ActorKind } from './actor.js';
1
2
  import type { EventRecord } from './events.js';
2
3
  /** Recognizable milestone events, ordered first in any activity lane. */
3
4
  export type MilestoneEvent = 'plan.created' | 'pr.opened' | 'pr.merged' | 'worktree.created' | 'worktree.removed' | 'commit.created' | 'pushed' | 'subagent.spawned' | 'artifact.created' | 'task.completed' | 'checklist.created'
@@ -5,6 +6,14 @@ export type MilestoneEvent = 'plan.created' | 'pr.opened' | 'pr.merged' | 'workt
5
6
  | 'video.rendered' | 'video.converted' | 'image.upscaled' | 'metadata.edited'
6
7
  /** Deliberate agent-authored progress post (`agents feed post`). */
7
8
  | 'status.posted'
9
+ /**
10
+ * An agent terminal spawned from the Factory VS Code extension. A milestone
11
+ * because it is the BIRTH of a session — it carries the sessionId and the
12
+ * terminalId that every later event on that session joins through, the same
13
+ * way `subagent.spawned` roots a sub-agent. Written out of process by
14
+ * `agents events emit`; the other factory.* kinds are operational-only.
15
+ */
16
+ | 'factory.launch'
8
17
  /**
9
18
  * The same post, but the agent is STUCK (`agents feed post --blocked`).
10
19
  *
@@ -72,6 +81,10 @@ export interface ActivityEvent {
72
81
  project?: string;
73
82
  /** Agent that produced the event (claude, codex, ...). */
74
83
  agent?: string;
84
+ /** Resolved actor id shared with the operational event stream. */
85
+ actor?: string;
86
+ /** Resolved actor kind shared with the operational event stream. */
87
+ kind?: ActorKind | 'unknown';
75
88
  /** Tool that triggered the event (Bash, Task, ExitPlanMode, feed.post, ...). */
76
89
  tool?: string;
77
90
  /** One-line human summary (plan title, PR command, sub-agent role, status text). */
@@ -82,6 +95,8 @@ export interface ActivityEvent {
82
95
  pid?: number;
83
96
  /** Spawn-time join key (`AGENT_LAUNCH_ID`) when known. */
84
97
  launchId?: string;
98
+ /** Session that spawned this session (`AGENTS_PARENT_SESSION_ID`) when known. */
99
+ parentSessionId?: string;
85
100
  /** Factory terminal id when the launch inherited one. */
86
101
  terminalId?: string;
87
102
  /** `$TMUX_PANE` at launch when recorded. */
@@ -25,6 +25,7 @@ import { relTime, truncate } from './format.js';
25
25
  import { getActivityDir, getUserAgentsDir } from './state.js';
26
26
  import { normalizeHost } from './machine-id.js';
27
27
  import { projectKeyFromCwd } from './project-key.js';
28
+ import { stampProvenance } from './event-provenance.js';
28
29
  /** The set of milestone events, for tier classification and ordering. */
29
30
  export const MILESTONE_EVENTS = [
30
31
  'plan.created',
@@ -43,6 +44,12 @@ export const MILESTONE_EVENTS = [
43
44
  'image.upscaled',
44
45
  'metadata.edited',
45
46
  'status.posted',
47
+ // NOTE: intentionally absent from the Python hook's own MILESTONE_EVENTS copy
48
+ // (see ACTIVITY_LOG_HOOK_SCRIPT below) — that set only classifies events the
49
+ // hook itself writes from PreToolUse/PostToolUse, and the hook never writes
50
+ // this one. activity.test.ts pins the difference so the divergence stays
51
+ // deliberate rather than becoming drift.
52
+ 'factory.launch',
46
53
  'status.blocked',
47
54
  ];
48
55
  const MILESTONE_SET = new Set(MILESTONE_EVENTS);
@@ -103,6 +110,7 @@ export function appendActivityEvent(event, root) {
103
110
  const dir = root ?? getActivityDir();
104
111
  fs.mkdirSync(dir, { recursive: true });
105
112
  const record = {
113
+ ...stampProvenance(),
106
114
  v: event.v ?? 1,
107
115
  tier: event.tier ?? tierForEvent(event.event),
108
116
  ...event,
@@ -129,11 +137,14 @@ function parseLine(line) {
129
137
  cwd: parsed.cwd,
130
138
  project: parsed.project,
131
139
  agent: parsed.agent,
140
+ actor: parsed.actor,
141
+ kind: parsed.kind,
132
142
  tool: parsed.tool,
133
143
  detail: parsed.detail,
134
144
  url: parsed.url,
135
145
  pid: typeof parsed.pid === 'number' ? parsed.pid : undefined,
136
146
  launchId: parsed.launchId,
147
+ parentSessionId: parsed.parentSessionId,
137
148
  terminalId: parsed.terminalId,
138
149
  tmuxPane: parsed.tmuxPane,
139
150
  category: typeof parsed.category === 'string' ? parsed.category : undefined,
@@ -262,10 +273,12 @@ export function activityEventToRecord(ev) {
262
273
  level: 'info',
263
274
  caller: ev.tool === 'feed.post' ? 'agent' : 'hook',
264
275
  session: ev.sessionId,
265
- osUser: ev.agent ?? 'agent',
276
+ osUser: 'unknown',
266
277
  transport: 'local',
267
278
  // payload
268
279
  agent: ev.agent,
280
+ actor: ev.actor ?? 'unknown',
281
+ kind: ev.kind ?? 'unknown',
269
282
  sessionId: ev.sessionId,
270
283
  cwd: ev.cwd,
271
284
  module: 'activity',
@@ -275,6 +288,7 @@ export function activityEventToRecord(ev) {
275
288
  tier: ev.tier,
276
289
  ...(ev.project ? { project: ev.project } : {}),
277
290
  ...(ev.launchId ? { launchId: ev.launchId } : {}),
291
+ ...(ev.parentSessionId ? { parentSessionId: ev.parentSessionId } : {}),
278
292
  ...(ev.terminalId ? { terminalId: ev.terminalId } : {}),
279
293
  ...(ev.tmuxPane ? { tmuxPane: ev.tmuxPane } : {}),
280
294
  ...(ev.attachments?.length ? { attachments: ev.attachments } : {}),
@@ -302,6 +316,7 @@ export const EVENT_STYLE = {
302
316
  'checklist.created': { glyph: '☐', color: chalk.cyan, label: 'checklist created' },
303
317
  'status.posted': { glyph: '▸', color: chalk.white, label: 'status' },
304
318
  'file.edited': { glyph: '·', color: chalk.gray, label: 'file edited' },
319
+ 'factory.launch': { glyph: '⌁', color: chalk.cyan, label: 'factory launch' },
305
320
  'bash.executed': { glyph: '$', color: chalk.gray, label: 'command run' },
306
321
  };
307
322
  export function styleForEvent(event) {
@@ -1514,8 +1529,17 @@ def main():
1514
1529
  cwd = payload.get("cwd") or os.environ.get("AGENTS_CWD")
1515
1530
  if cwd:
1516
1531
  record["cwd"] = cwd
1517
- agent = os.environ.get("AGENTS_AGENT_NAME") or "claude"
1532
+ agent = os.environ.get("AGENTS_AGENT_NAME") or "unknown"
1518
1533
  record["agent"] = agent
1534
+ record["actor"] = os.environ.get("AGENTS_ACTOR") or "unknown"
1535
+ actor_kind = os.environ.get("AGENTS_ACTOR_KIND")
1536
+ record["kind"] = actor_kind if actor_kind in ("human", "agent") else "unknown"
1537
+ launch_id = os.environ.get("AGENT_LAUNCH_ID")
1538
+ if launch_id:
1539
+ record["launchId"] = launch_id
1540
+ parent_session_id = os.environ.get("AGENTS_PARENT_SESSION_ID")
1541
+ if parent_session_id:
1542
+ record["parentSessionId"] = parent_session_id
1519
1543
 
1520
1544
  try:
1521
1545
  os.makedirs(activity_dir, exist_ok=True)
@@ -43,7 +43,12 @@ export interface PlannedDispatch {
43
43
  title: string;
44
44
  delegateName: string;
45
45
  }
46
- /** Path to the shared factory project registry (written by the factory UI). */
46
+ /**
47
+ * Path to the shared factory project registry (written by the factory UI).
48
+ * `AGENTS_FACTORY_PROJECTS_PATH` overrides it, mirroring `AGENTS_PROJECTS_DIR`
49
+ * (`state.ts`) — that seam is what lets `projects import --from-factory` be
50
+ * tested end-to-end against a real fixture file instead of a mock.
51
+ */
47
52
  export declare function factoryProjectsPath(): string;
48
53
  /** Read the project registry rows this module cares about. */
49
54
  export declare function readAutoDispatchProjects(): AutoDispatchProject[];
@@ -16,9 +16,14 @@
16
16
  import * as fs from 'fs';
17
17
  import * as path from 'path';
18
18
  import { homedir } from 'os';
19
- /** Path to the shared factory project registry (written by the factory UI). */
19
+ /**
20
+ * Path to the shared factory project registry (written by the factory UI).
21
+ * `AGENTS_FACTORY_PROJECTS_PATH` overrides it, mirroring `AGENTS_PROJECTS_DIR`
22
+ * (`state.ts`) — that seam is what lets `projects import --from-factory` be
23
+ * tested end-to-end against a real fixture file instead of a mock.
24
+ */
20
25
  export function factoryProjectsPath() {
21
- return path.join(homedir(), '.agents', 'factory', 'projects.json');
26
+ return process.env.AGENTS_FACTORY_PROJECTS_PATH ?? path.join(homedir(), '.agents', 'factory', 'projects.json');
22
27
  }
23
28
  /** Read the project registry rows this module cares about. */
24
29
  export function readAutoDispatchProjects() {
@@ -8,7 +8,7 @@
8
8
  * checks.
9
9
  */
10
10
  import type { BetaFeatureName } from './types.js';
11
- export declare const ALL_BETA_FEATURES: readonly ["drive", "factory", "session-sync", "projects"];
11
+ export declare const ALL_BETA_FEATURES: readonly ["factory", "projects"];
12
12
  export declare function getEnabledBetaFeatures(): BetaFeatureName[];
13
13
  export declare function isBetaEnabled(feature: BetaFeatureName): boolean;
14
14
  export declare function getBetaConfigLocation(): {
package/dist/lib/beta.js CHANGED
@@ -10,7 +10,7 @@
10
10
  import * as path from 'path';
11
11
  import { getAgentsDir, getOptionalUserAgentsDir, readMeta, writeMeta } from './state.js';
12
12
  import { readManifest, writeManifest } from './manifest.js';
13
- export const ALL_BETA_FEATURES = ['drive', 'factory', 'session-sync', 'projects'];
13
+ export const ALL_BETA_FEATURES = ['factory', 'projects'];
14
14
  function isBetaFeatureName(value) {
15
15
  return typeof value === 'string' && ALL_BETA_FEATURES.includes(value);
16
16
  }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Delivery envelope for `agents send` / `agents notify`.
3
+ *
4
+ * One primitive: resolve a destination (channel + target), compose text + urls +
5
+ * attachments, hand off to a channel provider. `notify` is the same path with
6
+ * destination defaulted to `notify.owner` in agents.yaml — owner is an address
7
+ * alias (`--to owner`), not a separate stack.
8
+ *
9
+ * Agent control (`agents message`, `sessions inject`) stays outside this module.
10
+ */
11
+ import type { Meta } from '../types.js';
12
+ import type { SendResult } from './registry.js';
13
+ /** Normalized delivery request after CLI/config resolution. */
14
+ export interface SendEnvelope {
15
+ text: string;
16
+ channel: string;
17
+ to: string;
18
+ thread?: string;
19
+ attachments?: string[];
20
+ from?: string;
21
+ dryRun?: boolean;
22
+ }
23
+ export interface ResolveSendInput {
24
+ /**
25
+ * Body text. Prefer `--text`; positional `[text]` is accepted for compat and
26
+ * folded in when `--text` is omitted.
27
+ */
28
+ text?: string;
29
+ /** Positional `[text]` from commander (legacy). */
30
+ positionalText?: string;
31
+ /**
32
+ * Recipient. Channel-specific id, or the alias `owner` which expands to
33
+ * `notify.owner.{channel,to}`.
34
+ */
35
+ to?: string;
36
+ /** Provider/channel name. Required unless `to` is `owner` (or ownerMode). */
37
+ channel?: string;
38
+ thread?: string;
39
+ /** Local file paths. */
40
+ attachments?: string[];
41
+ /** Links / remote media refs — appended to the body so every provider sees them. */
42
+ urls?: string[];
43
+ from?: string;
44
+ dryRun?: boolean;
45
+ /**
46
+ * When true (`agents notify`), missing channel/to default to `notify.owner`.
47
+ * Explicit flags still win.
48
+ */
49
+ ownerMode?: boolean;
50
+ }
51
+ export type ResolveSendResult = {
52
+ ok: true;
53
+ envelope: SendEnvelope;
54
+ } | {
55
+ ok: false;
56
+ error: string;
57
+ };
58
+ /** True when the destination token means “the configured owner”. */
59
+ export declare function isOwnerAlias(to: string | undefined): boolean;
60
+ /** Compose body + optional URL lines (skip urls already present in the body). */
61
+ export declare function composeSendText(text: string, urls?: string[]): string;
62
+ /** Read notify.owner; null when either field is missing. */
63
+ export declare function readOwnerDest(meta: Meta): {
64
+ channel: string;
65
+ to: string;
66
+ } | null;
67
+ /**
68
+ * Resolve CLI/config into a send envelope. Pure except for reading `meta` —
69
+ * no I/O, no provider registration — so unit tests do not need a real PATH.
70
+ */
71
+ export declare function resolveSendEnvelope(input: ResolveSendInput, meta: Meta): ResolveSendResult;
72
+ /**
73
+ * Register providers, resolve transport, deliver. Used by the CLI and by any
74
+ * internal caller that already has a resolved envelope.
75
+ */
76
+ export declare function deliverEnvelope(envelope: SendEnvelope, meta: Meta): Promise<SendResult>;
77
+ /** Resolve + deliver in one step (CLI happy path). */
78
+ export declare function sendMessage(input: ResolveSendInput, meta: Meta): Promise<{
79
+ result: SendResult;
80
+ envelope: SendEnvelope;
81
+ } | {
82
+ error: string;
83
+ }>;