@phnx-labs/agents-cli 1.20.93 → 1.21.1

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 (128) hide show
  1. package/CHANGELOG.md +290 -0
  2. package/README.md +4 -1
  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/exec.js +13 -0
  7. package/dist/commands/feed.js +37 -23
  8. package/dist/commands/mailboxes.js +39 -1
  9. package/dist/commands/message.js +12 -1
  10. package/dist/commands/monitors.js +8 -6
  11. package/dist/commands/packages.js +14 -5
  12. package/dist/commands/projects.d.ts +28 -0
  13. package/dist/commands/projects.js +173 -47
  14. package/dist/commands/routines.js +27 -1
  15. package/dist/commands/rules.js +6 -0
  16. package/dist/commands/sessions.d.ts +17 -0
  17. package/dist/commands/sessions.js +16 -8
  18. package/dist/commands/setup-browser.js +5 -1
  19. package/dist/commands/setup-fleet.js +5 -0
  20. package/dist/commands/setup-preferences.d.ts +53 -0
  21. package/dist/commands/setup-preferences.js +142 -0
  22. package/dist/commands/setup.js +8 -9
  23. package/dist/commands/ssh.js +220 -10
  24. package/dist/commands/sync.d.ts +2 -2
  25. package/dist/commands/sync.js +4 -9
  26. package/dist/commands/watchdog.js +26 -3
  27. package/dist/index.js +22 -6
  28. package/dist/lib/activity.d.ts +13 -0
  29. package/dist/lib/activity.js +22 -2
  30. package/dist/lib/auto-dispatch.d.ts +6 -1
  31. package/dist/lib/auto-dispatch.js +7 -2
  32. package/dist/lib/beta.d.ts +1 -1
  33. package/dist/lib/beta.js +1 -1
  34. package/dist/lib/channels/providers/openclaw-telegram.js +1 -1
  35. package/dist/lib/channels/resolve.d.ts +18 -1
  36. package/dist/lib/channels/resolve.js +15 -5
  37. package/dist/lib/daemon.d.ts +13 -0
  38. package/dist/lib/daemon.js +80 -60
  39. package/dist/lib/device-config.d.ts +82 -0
  40. package/dist/lib/device-config.js +296 -0
  41. package/dist/lib/event-provenance.d.ts +19 -0
  42. package/dist/lib/event-provenance.js +48 -0
  43. package/dist/lib/events.d.ts +2 -2
  44. package/dist/lib/events.js +4 -53
  45. package/dist/lib/feed-broadcast.d.ts +41 -5
  46. package/dist/lib/feed-broadcast.js +110 -12
  47. package/dist/lib/feed-post.d.ts +16 -2
  48. package/dist/lib/feed-post.js +35 -7
  49. package/dist/lib/feed.d.ts +1 -1
  50. package/dist/lib/feed.js +3 -1
  51. package/dist/lib/git.d.ts +13 -2
  52. package/dist/lib/git.js +38 -6
  53. package/dist/lib/hooks.d.ts +16 -1
  54. package/dist/lib/hooks.js +61 -1
  55. package/dist/lib/linear-project-counts.d.ts +72 -5
  56. package/dist/lib/linear-project-counts.js +90 -6
  57. package/dist/lib/linear-projects.d.ts +12 -0
  58. package/dist/lib/linear-projects.js +30 -0
  59. package/dist/lib/mailbox-gc.js +30 -7
  60. package/dist/lib/mailbox.d.ts +14 -1
  61. package/dist/lib/mailbox.js +35 -3
  62. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  63. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  64. package/dist/lib/menubar/install-menubar.d.ts +5 -21
  65. package/dist/lib/menubar/install-menubar.js +35 -44
  66. package/dist/lib/migrate.js +1 -1
  67. package/dist/lib/models.js +21 -11
  68. package/dist/lib/monitors/config.d.ts +1 -1
  69. package/dist/lib/monitors/dispatch.d.ts +6 -2
  70. package/dist/lib/monitors/dispatch.js +10 -15
  71. package/dist/lib/notify.d.ts +42 -6
  72. package/dist/lib/notify.js +41 -32
  73. package/dist/lib/overdue.d.ts +1 -1
  74. package/dist/lib/overdue.js +54 -19
  75. package/dist/lib/project-import.d.ts +96 -0
  76. package/dist/lib/project-import.js +171 -0
  77. package/dist/lib/remote-agents-json.d.ts +14 -1
  78. package/dist/lib/remote-agents-json.js +21 -3
  79. package/dist/lib/routines.d.ts +53 -0
  80. package/dist/lib/routines.js +105 -4
  81. package/dist/lib/rules/run-sync.d.ts +18 -0
  82. package/dist/lib/rules/run-sync.js +92 -0
  83. package/dist/lib/scheduler.js +12 -2
  84. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  85. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  86. package/dist/lib/secrets/index.d.ts +4 -5
  87. package/dist/lib/secrets/index.js +4 -5
  88. package/dist/lib/session/active.d.ts +16 -2
  89. package/dist/lib/session/active.js +8 -4
  90. package/dist/lib/session/bundle.js +5 -1
  91. package/dist/lib/session/remote-list.d.ts +1 -15
  92. package/dist/lib/session/remote-list.js +22 -111
  93. package/dist/lib/session/sync/config.d.ts +13 -5
  94. package/dist/lib/session/sync/config.js +21 -13
  95. package/dist/lib/startup/command-registry.d.ts +0 -2
  96. package/dist/lib/startup/command-registry.js +1 -5
  97. package/dist/lib/state.d.ts +7 -2
  98. package/dist/lib/state.js +85 -10
  99. package/dist/lib/sync-umbrella.d.ts +11 -23
  100. package/dist/lib/sync-umbrella.js +14 -45
  101. package/dist/lib/teams/agents.d.ts +35 -3
  102. package/dist/lib/teams/agents.js +52 -8
  103. package/dist/lib/teams/scheduler.d.ts +29 -2
  104. package/dist/lib/teams/scheduler.js +60 -16
  105. package/dist/lib/types.d.ts +23 -1
  106. package/package.json +1 -2
  107. package/dist/commands/drive.d.ts +0 -10
  108. package/dist/commands/drive.js +0 -183
  109. package/dist/commands/hq.d.ts +0 -2
  110. package/dist/commands/hq.js +0 -58
  111. package/dist/commands/sessions-sync.d.ts +0 -17
  112. package/dist/commands/sessions-sync.js +0 -135
  113. package/dist/commands/sync-provision.d.ts +0 -23
  114. package/dist/commands/sync-provision.js +0 -107
  115. package/dist/lib/drive-sync.d.ts +0 -45
  116. package/dist/lib/drive-sync.js +0 -238
  117. package/dist/lib/hq/floor.d.ts +0 -87
  118. package/dist/lib/hq/floor.js +0 -243
  119. package/dist/lib/session/sync/crdt.d.ts +0 -44
  120. package/dist/lib/session/sync/crdt.js +0 -119
  121. package/dist/lib/session/sync/manifest.d.ts +0 -62
  122. package/dist/lib/session/sync/manifest.js +0 -100
  123. package/dist/lib/session/sync/provision.d.ts +0 -49
  124. package/dist/lib/session/sync/provision.js +0 -91
  125. package/dist/lib/session/sync/r2.d.ts +0 -32
  126. package/dist/lib/session/sync/r2.js +0 -121
  127. package/dist/lib/session/sync/sync.d.ts +0 -106
  128. 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)
@@ -47,7 +47,7 @@ import { getSystemAgentsDir, getUserAgentsDir, getEnabledExtraRepos } from '../l
47
47
  export function registerSyncCommand(program) {
48
48
  addHostOption(program.command('sync [agentSpec] [repo]'))
49
49
  .summary('Make this machine current, or sync resources into one agent')
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 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).')
51
51
  .option('--agent <agent>', 'Agent identifier (legacy form; prefer the positional spec)')
52
52
  .option('--agent-version <version>', 'Version to sync into (legacy form; prefer "agent@version")')
53
53
  .option('--repo <name>', 'Scope the sync to a single DotAgent repo: system / user / project / <alias> (also accepted as a positional)')
@@ -60,7 +60,6 @@ export function registerSyncCommand(program) {
60
60
  // Umbrella verb (no agent given): make this machine current.
61
61
  .option('--repos', 'Umbrella: git-pull ~/.agents + enabled ~/.agents-* extras', false)
62
62
  .option('--secrets', 'Umbrella: pull encrypted secret bundles from the remote', false)
63
- .option('--sessions', 'Umbrella: sync session transcripts across machines', false)
64
63
  .option('--cloud', 'Umbrella: fetch all remote state but skip the local reconcile', false)
65
64
  .option('--local', "Umbrella: reconcile resources into installed agents only (no fetch)", false)
66
65
  .action(async (agentSpec, repo, opts) => {
@@ -206,7 +205,7 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
206
205
  // Interactive bare `agents sync` (a TTY, no --yes, no scope flag) drops into
207
206
  // the two-checklist picker: which repos to sync from, which agents to sync
208
207
  // into. Any explicit flag or --yes keeps the non-interactive umbrella below.
209
- const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.sessions || opts.cloud || opts.local);
208
+ const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local);
210
209
  if (!quiet && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
211
210
  await runInteractiveReconcile(opts, outLog, errLog);
212
211
  return;
@@ -214,7 +213,6 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
214
213
  const flags = {
215
214
  repos: opts.repos,
216
215
  secrets: opts.secrets,
217
- sessions: opts.sessions,
218
216
  cloud: opts.cloud,
219
217
  local: opts.local,
220
218
  };
@@ -238,9 +236,6 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
238
236
  if (result.secrets) {
239
237
  parts.push(result.secrets.skipped ? 'secrets skipped' : `secrets ${result.secrets.pulled} pulled`);
240
238
  }
241
- if (result.sessions) {
242
- parts.push(result.sessions.ran ? `sessions ${result.sessions.merged} merged` : 'sessions off');
243
- }
244
239
  if (result.reconciled)
245
240
  parts.push('reconciled');
246
241
  outLog(chalk.green(`✓ sync: ${parts.join(' · ') || 'nothing to do'}`));
@@ -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
@@ -281,7 +282,6 @@ Run and dispatch:
281
282
  run <agent|profile> [prompt] Run an agent. Omit prompt for interactive mode.
282
283
  defaults Configure run defaults by agent/version selector
283
284
  teams Coordinate multiple agents on shared work
284
- hq JSON bridge for the interactive Agents HQ floor
285
285
  routines Run agents on a cron schedule (scheduler auto-starts)
286
286
  webhook Receive signed GitHub/Linear webhooks for trigger routines
287
287
  funnel Expose a webhook receiver through Tailscale Funnel
@@ -301,14 +301,13 @@ Diagnostics:
301
301
  perf Latency rollups (hooks, commands, runs) from the disposable perf warehouse
302
302
 
303
303
  Config sync:
304
- drive Sync session history across machines via rsync
305
304
  pull Clone or pull the system repo at ~/.agents/.system/
306
305
  repo init --path <dir> Scaffold your own editable repo from a template
307
306
  repo add <path|gh:user/repo> Merge an extra repo after the system repo
308
307
  lock [--frozen] Write/verify agents.lock (SHA-256 of resolved resources); --frozen fails on drift
309
308
 
310
309
  Beta features:
311
- beta Enable preview features (factory, drive, and more)
310
+ beta Enable preview features (factory and more)
312
311
 
313
312
  Automation tips:
314
313
  Pass explicit names/IDs Avoid pickers: agents sessions <id> --markdown
@@ -721,6 +720,21 @@ function registerResourcesTombstoneCommand(p) {
721
720
  await program.parseAsync(['node', 'agents', ...args]);
722
721
  });
723
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
+ }
724
738
  /**
725
739
  * Hidden `agents _internal <sub>` namespace for machine-to-machine calls that
726
740
  * are not user-facing. The first subcommand is `friction`, used by shell guard
@@ -840,6 +854,9 @@ async function registerEagerForRequest(name) {
840
854
  registerResourcesTombstoneCommand(program);
841
855
  await reg(loadView);
842
856
  return true;
857
+ case 'hq':
858
+ registerHqTombstoneCommand(program);
859
+ return true;
843
860
  case '_internal':
844
861
  registerInternalCommand(program);
845
862
  return true;
@@ -912,7 +929,6 @@ async function registerAllEagerCommands() {
912
929
  await reg(loadSync);
913
930
  await reg(loadLock);
914
931
  await reg(loadRefreshRules);
915
- await reg(loadDrive);
916
932
  await reg(loadFactory);
917
933
  await reg(loadUsage);
918
934
  await reg(loadCost);
@@ -932,7 +948,7 @@ async function registerAllEagerCommands() {
932
948
  await reg(loadAudit);
933
949
  await reg(loadWebhook);
934
950
  await reg(loadFunnel);
935
- await reg(loadHq);
951
+ registerHqTombstoneCommand(program);
936
952
  await reg(loadFeed);
937
953
  await reg(loadActivity);
938
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'
@@ -80,16 +81,28 @@ export interface ActivityEvent {
80
81
  project?: string;
81
82
  /** Agent that produced the event (claude, codex, ...). */
82
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';
83
88
  /** Tool that triggered the event (Bash, Task, ExitPlanMode, feed.post, ...). */
84
89
  tool?: string;
85
90
  /** One-line human summary (plan title, PR command, sub-agent role, status text). */
86
91
  detail?: string;
92
+ /**
93
+ * Short subject for deliberate status posts (`feed post --title`). Phone
94
+ * broadcasts put this on the first line; `detail` is the body. Optional on
95
+ * older events that only carried `detail`.
96
+ */
97
+ title?: string;
87
98
  /** Extracted URL when the event has one (e.g. the opened PR). */
88
99
  url?: string;
89
100
  /** Auto-stamped process identity for deliberate posts (from pid registry / env). */
90
101
  pid?: number;
91
102
  /** Spawn-time join key (`AGENT_LAUNCH_ID`) when known. */
92
103
  launchId?: string;
104
+ /** Session that spawned this session (`AGENTS_PARENT_SESSION_ID`) when known. */
105
+ parentSessionId?: string;
93
106
  /** Factory terminal id when the launch inherited one. */
94
107
  terminalId?: string;
95
108
  /** `$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',
@@ -109,6 +110,7 @@ export function appendActivityEvent(event, root) {
109
110
  const dir = root ?? getActivityDir();
110
111
  fs.mkdirSync(dir, { recursive: true });
111
112
  const record = {
113
+ ...stampProvenance(),
112
114
  v: event.v ?? 1,
113
115
  tier: event.tier ?? tierForEvent(event.event),
114
116
  ...event,
@@ -135,11 +137,15 @@ function parseLine(line) {
135
137
  cwd: parsed.cwd,
136
138
  project: parsed.project,
137
139
  agent: parsed.agent,
140
+ actor: parsed.actor,
141
+ kind: parsed.kind,
138
142
  tool: parsed.tool,
139
143
  detail: parsed.detail,
144
+ title: typeof parsed.title === 'string' ? parsed.title : undefined,
140
145
  url: parsed.url,
141
146
  pid: typeof parsed.pid === 'number' ? parsed.pid : undefined,
142
147
  launchId: parsed.launchId,
148
+ parentSessionId: parsed.parentSessionId,
143
149
  terminalId: parsed.terminalId,
144
150
  tmuxPane: parsed.tmuxPane,
145
151
  category: typeof parsed.category === 'string' ? parsed.category : undefined,
@@ -268,10 +274,12 @@ export function activityEventToRecord(ev) {
268
274
  level: 'info',
269
275
  caller: ev.tool === 'feed.post' ? 'agent' : 'hook',
270
276
  session: ev.sessionId,
271
- osUser: ev.agent ?? 'agent',
277
+ osUser: 'unknown',
272
278
  transport: 'local',
273
279
  // payload
274
280
  agent: ev.agent,
281
+ actor: ev.actor ?? 'unknown',
282
+ kind: ev.kind ?? 'unknown',
275
283
  sessionId: ev.sessionId,
276
284
  cwd: ev.cwd,
277
285
  module: 'activity',
@@ -281,6 +289,7 @@ export function activityEventToRecord(ev) {
281
289
  tier: ev.tier,
282
290
  ...(ev.project ? { project: ev.project } : {}),
283
291
  ...(ev.launchId ? { launchId: ev.launchId } : {}),
292
+ ...(ev.parentSessionId ? { parentSessionId: ev.parentSessionId } : {}),
284
293
  ...(ev.terminalId ? { terminalId: ev.terminalId } : {}),
285
294
  ...(ev.tmuxPane ? { tmuxPane: ev.tmuxPane } : {}),
286
295
  ...(ev.attachments?.length ? { attachments: ev.attachments } : {}),
@@ -390,6 +399,8 @@ export function formatProgressUpdate(ev, opts = {}) {
390
399
  ].filter((c) => Boolean(c));
391
400
  if (chips.length > 0)
392
401
  lines.push(` ${chalk.gray(chips.join(' · '))}`);
402
+ if (ev.title)
403
+ lines.push(` ${chalk.white.bold(ev.title)}`);
393
404
  if (ev.detail)
394
405
  lines.push(` ${chalk.white(`"${ev.detail}"`)}`);
395
406
  if (ev.attachments?.length) {
@@ -1521,8 +1532,17 @@ def main():
1521
1532
  cwd = payload.get("cwd") or os.environ.get("AGENTS_CWD")
1522
1533
  if cwd:
1523
1534
  record["cwd"] = cwd
1524
- agent = os.environ.get("AGENTS_AGENT_NAME") or "claude"
1535
+ agent = os.environ.get("AGENTS_AGENT_NAME") or "unknown"
1525
1536
  record["agent"] = agent
1537
+ record["actor"] = os.environ.get("AGENTS_ACTOR") or "unknown"
1538
+ actor_kind = os.environ.get("AGENTS_ACTOR_KIND")
1539
+ record["kind"] = actor_kind if actor_kind in ("human", "agent") else "unknown"
1540
+ launch_id = os.environ.get("AGENT_LAUNCH_ID")
1541
+ if launch_id:
1542
+ record["launchId"] = launch_id
1543
+ parent_session_id = os.environ.get("AGENTS_PARENT_SESSION_ID")
1544
+ if parent_session_id:
1545
+ record["parentSessionId"] = parent_session_id
1526
1546
 
1527
1547
  try:
1528
1548
  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
  }
@@ -24,7 +24,7 @@ export const openclawTelegramProvider = {
24
24
  return { ok: false, channel: name, id: opts.target, error: 'openclaw CLI not found on PATH' };
25
25
  }
26
26
  try {
27
- await execFileAsync('openclaw', buildOpenClawNotifyArgs(text, { channel: 'telegram', target: opts.target }));
27
+ await execFileAsync('openclaw', buildOpenClawNotifyArgs(text, { target: opts.target }));
28
28
  return { ok: true, channel: name, id: opts.target };
29
29
  }
30
30
  catch (err) {
@@ -4,8 +4,25 @@
4
4
  *
5
5
  * Default is name-identity (`--channel slack` -> `slack` provider) — NOT a
6
6
  * fallback to a different transport. Only telegram is dual-homed (rush vs
7
- * openclaw-telegram); config picks. An unmapped-to-unregistered name dies loud.
7
+ * openclaw-telegram); config picks.
8
+ *
9
+ * Two entry points, deliberately: `lookupTransport` *returns* the failure, for
10
+ * long-lived callers (the monitor daemon, the feed-dispatch loop) that must
11
+ * survive a bad channel name; `resolveTransport` `die()`s on it, for the
12
+ * interactive `agents send` / `agents notify` command path where exiting with a
13
+ * loud message is the right answer. Never give a daemon the dying one.
8
14
  */
9
15
  import type { Meta } from '../types.js';
10
16
  import { type ChannelProvider } from './registry.js';
17
+ export interface TransportLookup {
18
+ /** Provider name after applying the `notify.transports` mapping. */
19
+ providerName: string;
20
+ /** Registered provider, or undefined when `providerName` resolves to nothing. */
21
+ provider?: ChannelProvider;
22
+ /** Why resolution failed — set exactly when `provider` is undefined. */
23
+ error?: string;
24
+ }
25
+ /** Resolve a channel to its provider, returning the failure instead of exiting. */
26
+ export declare function lookupTransport(channel: string, meta: Meta): TransportLookup;
27
+ /** Interactive-command resolution: an unregistered provider dies loud. */
11
28
  export declare function resolveTransport(channel: string, meta: Meta): ChannelProvider;
@@ -1,12 +1,22 @@
1
1
  import { die } from '../format.js';
2
2
  import { resolveChannelProvider, listChannelProviders } from './registry.js';
3
- export function resolveTransport(channel, meta) {
3
+ /** Resolve a channel to its provider, returning the failure instead of exiting. */
4
+ export function lookupTransport(channel, meta) {
4
5
  const providerName = meta.notify?.transports?.[channel] ?? channel;
5
6
  const provider = resolveChannelProvider(providerName);
6
- if (!provider) {
7
- die(`No channel provider '${providerName}'` +
7
+ if (provider)
8
+ return { providerName, provider };
9
+ return {
10
+ providerName,
11
+ error: `No channel provider '${providerName}'` +
8
12
  (providerName === channel ? '' : ` (mapped from channel '${channel}' via notify.transports)`) +
9
- `. Registered: ${listChannelProviders().join(', ')}.`);
10
- }
13
+ `. Registered: ${listChannelProviders().join(', ')}.`,
14
+ };
15
+ }
16
+ /** Interactive-command resolution: an unregistered provider dies loud. */
17
+ export function resolveTransport(channel, meta) {
18
+ const { provider, error } = lookupTransport(channel, meta);
19
+ if (!provider)
20
+ die(error);
11
21
  return provider;
12
22
  }
@@ -16,6 +16,19 @@ import { getAgentsBinPath } from './cli-entry.js';
16
16
  * broker is hosting, and never clobber a reachable (healthy) broker.
17
17
  */
18
18
  export declare function shouldTakeOverBroker(isHosting: boolean, brokerReachable: boolean): boolean;
19
+ /**
20
+ * What a gate re-evaluation must do with the routines scheduler. The daemon
21
+ * re-evaluates `scheduler.enabled` on every SIGHUP reload so flipping the key
22
+ * takes effect without a daemon restart (and `routines add`'s reload signal on
23
+ * a re-enabled box boots the scheduler — the reload is truthful, not a no-op).
24
+ *
25
+ * running + enabled → reload (the normal SIGHUP path)
26
+ * running + !enabled → stop (gate flipped off since boot)
27
+ * !running + enabled → boot (gate flipped on since boot)
28
+ * !running + !enabled → none (stay dark)
29
+ */
30
+ export type SchedulerGateTransition = 'reload' | 'stop' | 'boot' | 'none';
31
+ export declare function schedulerGateTransition(running: boolean, enabled: boolean): SchedulerGateTransition;
19
32
  /** Read the stored daemon PID from disk. Returns null if not present or invalid. */
20
33
  export declare function readDaemonPid(): number | null;
21
34
  /** Write the daemon PID to the pid file. */