@phnx-labs/agents-cli 1.22.40 → 1.22.42

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 (201) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +14 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.js +0 -1
  5. package/dist/cli/command-registry.d.ts +2 -0
  6. package/dist/cli/command-registry.js +5 -2
  7. package/dist/commands/accounts.d.ts +29 -0
  8. package/dist/commands/accounts.js +220 -29
  9. package/dist/commands/alias.js +1 -1
  10. package/dist/commands/auth.d.ts +9 -0
  11. package/dist/commands/auth.js +104 -0
  12. package/dist/commands/browser-sessions-picker.js +42 -62
  13. package/dist/commands/browser.d.ts +15 -0
  14. package/dist/commands/browser.js +145 -78
  15. package/dist/commands/computer-actions.d.ts +1 -1
  16. package/dist/commands/computer-actions.js +1 -1
  17. package/dist/commands/computer-sessions-picker.js +32 -55
  18. package/dist/commands/computer.d.ts +1 -1
  19. package/dist/commands/computer.js +2 -2
  20. package/dist/commands/doctor.d.ts +1 -1
  21. package/dist/commands/doctor.js +3 -3
  22. package/dist/commands/feed.js +4 -12
  23. package/dist/commands/hooks.js +1 -1
  24. package/dist/commands/import.js +1 -1
  25. package/dist/commands/insights.js +163 -85
  26. package/dist/commands/inspect.js +2 -2
  27. package/dist/commands/mine.js +1 -1
  28. package/dist/commands/org.d.ts +6 -0
  29. package/dist/commands/org.js +174 -0
  30. package/dist/commands/packages.js +1 -1
  31. package/dist/commands/prune.js +1 -1
  32. package/dist/commands/routines.js +1 -1
  33. package/dist/commands/run-account-picker.d.ts +19 -0
  34. package/dist/commands/run-account-picker.js +81 -0
  35. package/dist/commands/secrets.js +87 -97
  36. package/dist/commands/sessions-picker-factory.d.ts +40 -0
  37. package/dist/commands/sessions-picker-factory.js +67 -0
  38. package/dist/commands/sessions-picker.d.ts +1 -0
  39. package/dist/commands/sessions-picker.js +27 -5
  40. package/dist/commands/sessions-share.d.ts +25 -0
  41. package/dist/commands/sessions-share.js +166 -0
  42. package/dist/commands/sessions.js +2 -0
  43. package/dist/commands/setup-browser.js +5 -5
  44. package/dist/commands/setup-mine.js +1 -1
  45. package/dist/commands/setup-preferences.js +4 -4
  46. package/dist/commands/setup.js +5 -3
  47. package/dist/commands/sync.js +1 -1
  48. package/dist/commands/versions.js +1 -1
  49. package/dist/commands/view.js +1 -1
  50. package/dist/commands/webhook.js +14 -9
  51. package/dist/lib/account-capabilities.d.ts +10 -0
  52. package/dist/lib/account-capabilities.js +34 -0
  53. package/dist/lib/agent-spec/agents.d.ts +584 -0
  54. package/dist/lib/agent-spec/agents.js +3127 -0
  55. package/dist/lib/agent-spec/provider.js +1 -1
  56. package/dist/lib/agents.d.ts +2 -584
  57. package/dist/lib/agents.js +2 -3127
  58. package/dist/lib/browser/cdp.js +4 -0
  59. package/dist/lib/browser/chrome.js +12 -1
  60. package/dist/lib/browser/drivers/local.d.ts +8 -2
  61. package/dist/lib/browser/drivers/local.js +8 -2
  62. package/dist/lib/browser/drivers/ssh.d.ts +8 -2
  63. package/dist/lib/browser/drivers/ssh.js +11 -4
  64. package/dist/lib/browser/hygiene.d.ts +1 -1
  65. package/dist/lib/browser/hygiene.js +5 -1
  66. package/dist/lib/browser/profiles.d.ts +77 -4
  67. package/dist/lib/browser/profiles.js +121 -12
  68. package/dist/lib/browser/runtime-state.d.ts +11 -5
  69. package/dist/lib/browser/runtime-state.js +13 -9
  70. package/dist/lib/browser/service.d.ts +91 -30
  71. package/dist/lib/browser/service.js +342 -207
  72. package/dist/lib/browser/types.d.ts +96 -4
  73. package/dist/lib/browser/types.js +51 -0
  74. package/dist/lib/catchup.js +1 -1
  75. package/dist/lib/computer/actions.d.ts +1 -1
  76. package/dist/lib/computer/actions.js +1 -1
  77. package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
  78. package/dist/lib/computer/dispatch.d.ts +1 -1
  79. package/dist/lib/computer/download.d.ts +1 -1
  80. package/dist/lib/computer/download.js +3 -3
  81. package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
  82. package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
  83. package/dist/lib/daemon/daemon.js +1 -1
  84. package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
  85. package/dist/lib/{runner.js → daemon/runner.js} +59 -118
  86. package/dist/lib/daemon-webhooks.d.ts +3 -1
  87. package/dist/lib/daemon-webhooks.js +12 -7
  88. package/dist/lib/device-config.js +1 -1
  89. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  90. package/dist/lib/devices/doctor-findings.js +3 -3
  91. package/dist/lib/devices/fleet-inventory.js +1 -1
  92. package/dist/lib/devices/harness-inventory.js +1 -1
  93. package/dist/lib/devices/registry.js +4 -22
  94. package/dist/lib/doctor-diff.d.ts +1 -1
  95. package/dist/lib/doctor-diff.js +1 -1
  96. package/dist/lib/drift.js +1 -1
  97. package/dist/lib/entitlement.d.ts +31 -0
  98. package/dist/lib/entitlement.js +137 -0
  99. package/dist/lib/exec.js +39 -202
  100. package/dist/lib/feed/feed.js +2 -8
  101. package/dist/lib/fs-atomic.d.ts +7 -0
  102. package/dist/lib/fs-atomic.js +9 -0
  103. package/dist/lib/harness/adapter.d.ts +160 -0
  104. package/dist/lib/harness/adapter.js +47 -0
  105. package/dist/lib/harness/adapters/claude.d.ts +2 -0
  106. package/dist/lib/harness/adapters/claude.js +110 -0
  107. package/dist/lib/harness/adapters/codex.d.ts +2 -0
  108. package/dist/lib/harness/adapters/codex.js +83 -0
  109. package/dist/lib/harness/adapters/copilot.d.ts +2 -0
  110. package/dist/lib/harness/adapters/copilot.js +23 -0
  111. package/dist/lib/harness/adapters/cursor.d.ts +2 -0
  112. package/dist/lib/harness/adapters/cursor.js +38 -0
  113. package/dist/lib/harness/adapters/droid.d.ts +2 -0
  114. package/dist/lib/harness/adapters/droid.js +17 -0
  115. package/dist/lib/harness/adapters/grok.d.ts +2 -0
  116. package/dist/lib/harness/adapters/grok.js +14 -0
  117. package/dist/lib/harness/adapters/kimi.d.ts +2 -0
  118. package/dist/lib/harness/adapters/kimi.js +49 -0
  119. package/dist/lib/harness/adapters/muse.d.ts +2 -0
  120. package/dist/lib/harness/adapters/muse.js +44 -0
  121. package/dist/lib/harness/adapters/opencode.d.ts +2 -0
  122. package/dist/lib/harness/adapters/opencode.js +14 -0
  123. package/dist/lib/harness/exec-config-version.d.ts +8 -0
  124. package/dist/lib/harness/exec-config-version.js +22 -0
  125. package/dist/lib/harness/index.d.ts +1 -0
  126. package/dist/lib/harness/index.js +34 -0
  127. package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
  128. package/dist/lib/{hooks.js → hooks/install.js} +11 -11
  129. package/dist/lib/import.js +1 -1
  130. package/dist/lib/installations/index.d.ts +4 -4
  131. package/dist/lib/installations/index.js +3 -4
  132. package/dist/lib/installations/migrate.js +6 -6
  133. package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
  134. package/dist/lib/{shims.js → installations/shims.js} +12 -114
  135. package/dist/lib/installations/store.d.ts +141 -3
  136. package/dist/lib/installations/store.js +475 -5
  137. package/dist/lib/installations/versions.d.ts +1 -126
  138. package/dist/lib/installations/versions.js +5 -465
  139. package/dist/lib/isolation-boundary-report.d.ts +1 -1
  140. package/dist/lib/isolation-boundary-report.js +1 -1
  141. package/dist/lib/mailbox-gc.js +2 -3
  142. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  143. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  144. package/dist/lib/monitors/dispatch.d.ts +1 -1
  145. package/dist/lib/monitors/dispatch.js +2 -2
  146. package/dist/lib/monitors/state.d.ts +1 -1
  147. package/dist/lib/observe-aliases.d.ts +2 -2
  148. package/dist/lib/observe-aliases.js +2 -11
  149. package/dist/lib/plugins/plugins.js +1 -1
  150. package/dist/lib/plugins/skills.js +1 -1
  151. package/dist/lib/prix-account.d.ts +158 -0
  152. package/dist/lib/prix-account.js +214 -0
  153. package/dist/lib/project-key.js +7 -0
  154. package/dist/lib/project-launch.js +1 -1
  155. package/dist/lib/refresh.js +2 -2
  156. package/dist/lib/resource-inventory.d.ts +1 -1
  157. package/dist/lib/resource-inventory.js +1 -1
  158. package/dist/lib/rules/rules.js +1 -1
  159. package/dist/lib/scheduling/routines.js +1 -1
  160. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  161. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  162. package/dist/lib/secrets/bundles.d.ts +1 -1
  163. package/dist/lib/secrets/bundles.js +1 -1
  164. package/dist/lib/secrets/headless.d.ts +16 -0
  165. package/dist/lib/secrets/headless.js +21 -0
  166. package/dist/lib/secrets/remote.d.ts +9 -7
  167. package/dist/lib/secrets/remote.js +18 -9
  168. package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
  169. package/dist/lib/self-heal/checks/path.js +1 -1
  170. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  171. package/dist/lib/self-heal/checks/shims.js +1 -1
  172. package/dist/lib/session/discover.d.ts +39 -0
  173. package/dist/lib/session/discover.js +3 -3
  174. package/dist/lib/session/parse.js +24 -42
  175. package/dist/lib/session/recovery.js +1 -1
  176. package/dist/lib/session/share-html.d.ts +55 -0
  177. package/dist/lib/session/share-html.js +319 -0
  178. package/dist/lib/settings-manifest.d.ts +2 -0
  179. package/dist/lib/settings-manifest.js +81 -3
  180. package/dist/lib/share/publish.d.ts +38 -0
  181. package/dist/lib/share/publish.js +81 -8
  182. package/dist/lib/staleness/prune.d.ts +1 -1
  183. package/dist/lib/staleness/prune.js +1 -1
  184. package/dist/lib/staleness/writers/hooks.js +1 -1
  185. package/dist/lib/startup/command-registry.d.ts +2 -1
  186. package/dist/lib/startup/command-registry.js +5 -3
  187. package/dist/lib/teams/agents.js +2 -19
  188. package/dist/lib/teams/parsers.js +20 -26
  189. package/dist/lib/teams/registry.js +2 -21
  190. package/dist/lib/triggers/handlers.d.ts +37 -2
  191. package/dist/lib/triggers/handlers.js +57 -6
  192. package/dist/lib/triggers/webhook.d.ts +80 -6
  193. package/dist/lib/triggers/webhook.js +128 -4
  194. package/dist/lib/types.d.ts +1 -1
  195. package/dist/lib/uninstall.js +1 -1
  196. package/dist/lib/version.d.ts +9 -2
  197. package/dist/lib/version.js +11 -9
  198. package/dist/lib/wrap.d.ts +33 -0
  199. package/dist/lib/wrap.js +70 -0
  200. package/package.json +1 -1
  201. /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
@@ -0,0 +1,40 @@
1
+ /** Flag subset both twins share for the interactive-routing gate. */
2
+ export interface SessionsPickerGateOpts {
3
+ interactive?: boolean;
4
+ json?: boolean;
5
+ /** Present only on the browser twin; computer never sets it. */
6
+ open?: string | boolean;
7
+ }
8
+ export interface SessionsPickerCommandSpec<TRow, TOpts extends SessionsPickerGateOpts> {
9
+ /**
10
+ * Browser requires `opts.open === undefined` so `--open` (bare or with a
11
+ * selector) falls through to the flat printer. Computer has no `--open`.
12
+ */
13
+ requireOpenUndefined?: boolean;
14
+ runFlat: (opts: TOpts) => void;
15
+ buildRows: (opts: TOpts) => TRow[];
16
+ emptyMessage: (opts: TOpts) => string;
17
+ message: string;
18
+ matches: (row: TRow, query: string) => boolean;
19
+ labelFor: (row: TRow) => string;
20
+ buildPreview: (row: TRow) => string;
21
+ emptyFilterMessage: string;
22
+ enterHint: string;
23
+ onOpen: (row: TRow) => void | Promise<void>;
24
+ }
25
+ export interface SessionsPickerCommand<TOpts extends SessionsPickerGateOpts> {
26
+ shouldOpen: (opts: TOpts, isTTY: boolean) => boolean;
27
+ run: (opts: TOpts) => Promise<void>;
28
+ }
29
+ export declare function shouldOpenInteractiveSessions(opts: SessionsPickerGateOpts, isTTY: boolean, requireOpenUndefined?: boolean): boolean;
30
+ export declare function browseSessionsUntilQuit<TRow>(spec: {
31
+ message: string;
32
+ rows: TRow[];
33
+ matches: (row: TRow, query: string) => boolean;
34
+ labelFor: (row: TRow) => string;
35
+ buildPreview: (row: TRow) => string;
36
+ emptyFilterMessage: string;
37
+ enterHint: string;
38
+ onOpen: (row: TRow) => void | Promise<void>;
39
+ }): Promise<void>;
40
+ export declare function createSessionsPickerCommand<TRow, TOpts extends SessionsPickerGateOpts>(spec: SessionsPickerCommandSpec<TRow, TOpts>): SessionsPickerCommand<TOpts>;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared interactive-routing + browse loop for the task-first session
3
+ * pickers (`browser-sessions-picker.ts`, `computer-sessions-picker.ts`).
4
+ *
5
+ * Each twin keeps its own row formatter, matcher, and enter-handler; this
6
+ * factory owns the TTY/`--json`/`--no-interactive` (and optional `--open`)
7
+ * gate, the empty-list message, and the cancel-aware `itemPicker` loop.
8
+ */
9
+ import { itemPicker } from '../lib/picker.js';
10
+ import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
11
+ export function shouldOpenInteractiveSessions(opts, isTTY, requireOpenUndefined = false) {
12
+ return (opts.interactive !== false &&
13
+ !opts.json &&
14
+ (!requireOpenUndefined || opts.open === undefined) &&
15
+ isTTY);
16
+ }
17
+ export async function browseSessionsUntilQuit(spec) {
18
+ for (;;) {
19
+ let picked;
20
+ try {
21
+ picked = await itemPicker({
22
+ message: spec.message,
23
+ items: spec.rows,
24
+ filter: (query) => (query.trim() ? spec.rows.filter((r) => spec.matches(r, query)) : spec.rows),
25
+ labelFor: spec.labelFor,
26
+ buildPreview: spec.buildPreview,
27
+ emptyMessage: spec.emptyFilterMessage,
28
+ enterHint: spec.enterHint,
29
+ });
30
+ }
31
+ catch (err) {
32
+ if (isPromptCancelled(err))
33
+ return;
34
+ throw err;
35
+ }
36
+ if (!picked)
37
+ return;
38
+ await spec.onOpen(picked.item);
39
+ }
40
+ }
41
+ export function createSessionsPickerCommand(spec) {
42
+ const shouldOpen = (opts, isTTY) => shouldOpenInteractiveSessions(opts, isTTY, spec.requireOpenUndefined === true);
43
+ return {
44
+ shouldOpen,
45
+ run: async (opts) => {
46
+ if (!shouldOpen(opts, isInteractiveTerminal())) {
47
+ spec.runFlat(opts);
48
+ return;
49
+ }
50
+ const rows = spec.buildRows(opts);
51
+ if (rows.length === 0) {
52
+ console.log(spec.emptyMessage(opts));
53
+ return;
54
+ }
55
+ await browseSessionsUntilQuit({
56
+ message: spec.message,
57
+ rows,
58
+ matches: spec.matches,
59
+ labelFor: spec.labelFor,
60
+ buildPreview: spec.buildPreview,
61
+ emptyFilterMessage: spec.emptyFilterMessage,
62
+ enterHint: spec.enterHint,
63
+ onOpen: spec.onOpen,
64
+ });
65
+ },
66
+ };
67
+ }
@@ -119,5 +119,6 @@ export declare function buildSessionPreviewDigest(events: SessionEvent[], sessio
119
119
  export declare function directoriesTouched(session: SessionMeta, events: SessionEvent[], changes: ReturnType<typeof classifyFileChanges>): string[];
120
120
  /** Relativize a file path to its parent dir, short enough for one preview line. */
121
121
  export declare function relativizeDir(filePath: string, cwd?: string): string | undefined;
122
+ export declare function renderLastResponse(content: string, maxLines?: number, width?: number): string[];
122
123
  /** Show an interactive session picker and return the selected session with its action (resume or view). */
123
124
  export declare function sessionPicker(config: SessionPickerConfig): Promise<PickedSession | null>;
@@ -17,6 +17,8 @@ import { linkPath, linkUrl, relativeToCwd, shortenModel } from '../lib/session/r
17
17
  import { linearIssueUrl } from '../lib/session/linear.js';
18
18
  import { extractTodoProgress, WORKTREE_RE } from '../lib/session/state.js';
19
19
  import { renderMarkdown } from '../lib/markdown.js';
20
+ import { wrapToWidth } from '../lib/wrap.js';
21
+ import { terminalWidth, stringWidth } from '../lib/session/width.js';
20
22
  import { itemPicker } from '../lib/picker.js';
21
23
  import { createMemoryCache } from '../lib/memory-cache.js';
22
24
  import { classifyFileChanges, changeCounts, toolHistogram, detectTestResult } from '../lib/session/digest.js';
@@ -435,8 +437,6 @@ function formatHeader(session, events) {
435
437
  if (totalTokens !== undefined) {
436
438
  line3.push(chalk.bold.white(formatTokens(totalTokens)) + chalk.gray(' tokens'));
437
439
  }
438
- if (session.label)
439
- line3.push(chalk.white(session.label));
440
440
  if (session.filePath)
441
441
  line3.push(chalk.gray(linkPath(session.filePath, session.id)));
442
442
  else
@@ -452,7 +452,19 @@ function formatHeader(session, events) {
452
452
  const label = session.prNumber ? `PR#${session.prNumber}` : 'PR';
453
453
  line4.push(chalk.blue(linkUrl(session.prUrl, label)));
454
454
  }
455
+ // Lead with the session's human title: `session.label` — an agent-generated
456
+ // name / `/rename`, else the `--name` launch handle. NOT `session.topic`: the
457
+ // topic is the derived first-prompt, already shown on the `Prompt:` line, so
458
+ // using it here too would print the same text twice. Unlabelled sessions keep
459
+ // that `Prompt:` line as their topic indicator and simply lead with the agent
460
+ // line. Wrapped to the pane (the header sits at column 0, full terminal width);
461
+ // nothing renders when there is no label.
462
+ const title = (session.label || '').trim();
463
+ const titleLines = title
464
+ ? wrapToWidth(title, terminalWidth()).map(l => chalk.bold.white(l))
465
+ : [];
455
466
  return [
467
+ ...titleLines,
456
468
  line1.join(DOT),
457
469
  line2.join(DOT),
458
470
  line3.join(DOT),
@@ -821,7 +833,9 @@ function formatCompactPreview(digest, session) {
821
833
  }
822
834
  if (lastAssistant) {
823
835
  const maxLines = todosRendered.length > 0 || compact ? LAST_RESPONSE_MAX_LINES_WITH_TODOS : LAST_RESPONSE_MAX_LINES;
824
- const rendered = renderLastResponse(lastAssistant, maxLines);
836
+ // Content sits at column 4 (outer body indent + the ' ' prefix below), so
837
+ // wrap it to the pane minus that indent.
838
+ const rendered = renderLastResponse(lastAssistant, maxLines, terminalWidth() - 4);
825
839
  if (rendered.length > 0) {
826
840
  lines.push('');
827
841
  lines.push(chalk.cyan('Last response:'));
@@ -999,7 +1013,7 @@ export function relativizeDir(filePath, cwd) {
999
1013
  dir = parts.slice(-3).join('/');
1000
1014
  return dir || undefined;
1001
1015
  }
1002
- function renderLastResponse(content, maxLines = LAST_RESPONSE_MAX_LINES) {
1016
+ export function renderLastResponse(content, maxLines = LAST_RESPONSE_MAX_LINES, width = terminalWidth()) {
1003
1017
  const cleaned = stripTags(content).trim();
1004
1018
  if (!cleaned)
1005
1019
  return [];
@@ -1010,7 +1024,15 @@ function renderLastResponse(content, maxLines = LAST_RESPONSE_MAX_LINES) {
1010
1024
  catch {
1011
1025
  rendered = cleaned;
1012
1026
  }
1013
- const all = rendered.replace(/\s+$/, '').split('\n');
1027
+ // `marked-terminal` runs with `reflowText` off, so a paragraph with no hard
1028
+ // breaks renders as one long line that overflows the pane. Wrap any line whose
1029
+ // VISIBLE width exceeds the budget; leave lines that already fit untouched so
1030
+ // rendered-markdown indentation (lists, code blocks) is preserved. The text is
1031
+ // ANSI-coloured, so width is measured with `stringWidth`, never `String.length`.
1032
+ const all = rendered
1033
+ .replace(/\s+$/, '')
1034
+ .split('\n')
1035
+ .flatMap(line => (stringWidth(line) <= width ? [line] : wrapToWidth(line, width)));
1014
1036
  // Drop leading/trailing empty lines
1015
1037
  while (all.length && !all[0].trim())
1016
1038
  all.shift();
@@ -0,0 +1,25 @@
1
+ import type { Command } from 'commander';
2
+ import type { SessionMeta } from '../lib/session/types.js';
3
+ import { type PublishOptions } from '../lib/share/publish.js';
4
+ interface ShareOptions {
5
+ public?: boolean;
6
+ slug?: string;
7
+ label?: string;
8
+ expire?: string;
9
+ reasoning: string;
10
+ force?: boolean;
11
+ cover?: boolean;
12
+ }
13
+ /** `session-<shortId>` — stable per session, so re-sharing updates the same URL. */
14
+ export declare function defaultSessionSlug(session: SessionMeta): string;
15
+ /**
16
+ * Map the command's flags onto {@link PublishOptions}.
17
+ *
18
+ * Extracted so the defaults are testable directly. `unlisted` in particular is the
19
+ * security-relevant one — it inverts the parent command's public-by-default
20
+ * behavior, and a test that re-implements this mapping would still pass with it
21
+ * flipped.
22
+ */
23
+ export declare function buildSharePublishOptions(session: SessionMeta, options: Pick<ShareOptions, 'public' | 'slug' | 'label' | 'expire' | 'force' | 'cover'>): PublishOptions;
24
+ export declare function registerSessionsShareCommand(sessionsCmd: Command): void;
25
+ export {};
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `agents sessions share <id>` — publish one session transcript as a link.
3
+ *
4
+ * Composes three pieces that already existed but were never wired together:
5
+ * `renderSessionMarkdownDocument()` (redacted transcript),
6
+ * `renderSessionHtmlDocument()` (self-contained branded page), and
7
+ * `publishFile()` (the R2-backed share Worker). Before this, sharing a session
8
+ * meant three commands and a detour through the external artifacts-cli.
9
+ *
10
+ * Unlisted by default, unlike `agents artifacts share`. A transcript carries file
11
+ * paths, command output, error text, and whatever a tool printed — strictly more
12
+ * than a plan does — so it does not belong in the public `/<user>` gallery unless
13
+ * the operator asks for it with `--public`. The URL itself stays world-readable:
14
+ * unlisted is a capability URL, not a secret.
15
+ */
16
+ import * as fs from 'fs';
17
+ import * as os from 'os';
18
+ import * as path from 'path';
19
+ import chalk from 'chalk';
20
+ import { setHelpSections } from '../lib/help.js';
21
+ import { knownSecretValuesFromEnv } from '../lib/redact.js';
22
+ import { discoverSessions } from '../lib/session/discover.js';
23
+ import { renderSessionHtmlDocument } from '../lib/session/share-html.js';
24
+ import { publishFile, redactEmails } from '../lib/share/publish.js';
25
+ import { renderSessionMarkdownDocument } from './sessions-render.js';
26
+ import { selectSessions } from './sessions-export.js';
27
+ import { parseAgentFilter } from './sessions.js';
28
+ function parseReasoning(value) {
29
+ if (value === 'omit' || value === 'fold' || value === 'include')
30
+ return value;
31
+ throw new Error(`Unknown reasoning mode "${value}". Expected omit, fold, or include.`);
32
+ }
33
+ /** `session-<shortId>` — stable per session, so re-sharing updates the same URL. */
34
+ export function defaultSessionSlug(session) {
35
+ return `session-${session.shortId || session.id}`;
36
+ }
37
+ /**
38
+ * Map the command's flags onto {@link PublishOptions}.
39
+ *
40
+ * Extracted so the defaults are testable directly. `unlisted` in particular is the
41
+ * security-relevant one — it inverts the parent command's public-by-default
42
+ * behavior, and a test that re-implements this mapping would still pass with it
43
+ * flipped.
44
+ */
45
+ export function buildSharePublishOptions(session, options) {
46
+ return {
47
+ slug: options.slug ?? defaultSessionSlug(session),
48
+ unlisted: options.public !== true,
49
+ expire: options.expire,
50
+ force: options.force === true,
51
+ cover: options.cover !== false,
52
+ label: options.label,
53
+ meta: { kind: 'session' },
54
+ };
55
+ }
56
+ export function registerSessionsShareCommand(sessionsCmd) {
57
+ const cmd = sessionsCmd
58
+ .command('share <session>')
59
+ .description('Publish one session as a redacted, self-contained web page and print the link.')
60
+ .option('--public', 'List the page in your public share gallery (default: unlisted capability URL)')
61
+ .option('--slug <slug>', 'URL slug under your namespace (default: session-<shortId>)')
62
+ .option('--label <text>', 'Display title in the gallery and `agents artifacts share list`')
63
+ .option('--expire <spec>', 'Auto-expire window: 30d, 12h, a date, or never (default: 30d)')
64
+ .option('--reasoning <mode>', 'Reasoning visibility: omit, fold, or include', 'omit')
65
+ .option('--force', 'Publish despite the sensitive-content scan flagging the transcript')
66
+ .option('--no-cover', 'Skip generating the Open Graph preview image');
67
+ setHelpSections(cmd, {
68
+ examples: `# Share a session — prints an unlisted, redacted link
69
+ agents sessions share a1b2c3d4
70
+
71
+ # Put it in your public gallery at share.agents-cli.sh/<you>
72
+ agents sessions share a1b2c3d4 --public --label "How the retry bug got fixed"
73
+
74
+ # Keep the model's reasoning in collapsible sections
75
+ agents sessions share a1b2c3d4 --reasoning fold
76
+
77
+ # A link that does not decay
78
+ agents sessions share a1b2c3d4 --expire never`,
79
+ notes: `Requires a share endpoint: 'agents artifacts share status' shows it, 'agents artifacts setup'
80
+ provisions one, 'agents artifacts share join <baseUrl>' uses an existing one.
81
+
82
+ Unlisted by default — the URL is world-readable but the page stays out of your public
83
+ gallery and out of 'agents artifacts share list'. Pass --public to list it.
84
+
85
+ Secrets are redacted and the same pre-publish scan as 'agents artifacts share' runs on the
86
+ page, so a transcript carrying emails or credential-shaped strings is refused
87
+ unless you pass --force. --no-redact (the sessions-level flag) disables redaction
88
+ and is a bad idea for anything you publish.
89
+
90
+ Re-running with the same session updates the same URL and keeps the prior version
91
+ as a revision ('agents artifacts share revisions <slug>').
92
+
93
+ Manage published sessions with 'agents artifacts share list' and
94
+ 'agents artifacts share delete <slug>'.`,
95
+ });
96
+ cmd.action(async (selector, options, command) => {
97
+ const globals = command.optsWithGlobals();
98
+ const reasoning = parseReasoning(options.reasoning);
99
+ const limit = Math.max(1, Number.parseInt(globals.limit || '100', 10) || 100);
100
+ const agent = parseAgentFilter(globals.agent).agent;
101
+ const sessions = selectSessions(await discoverSessions({
102
+ all: globals.all !== false,
103
+ since: globals.since,
104
+ limit,
105
+ agent: agent ?? undefined,
106
+ }), [selector]);
107
+ if (sessions.length === 0) {
108
+ process.stderr.write(chalk.yellow(`No session matched "${selector}".\n`));
109
+ process.exitCode = 1;
110
+ return;
111
+ }
112
+ // One link per share: several sessions in one page would give the reader no
113
+ // way to reference just the one that matters, and the slug could only name one.
114
+ if (sessions.length > 1) {
115
+ process.stderr.write(chalk.yellow(`"${selector}" matched ${sessions.length} sessions. Share one at a time — pass a full or unique session id.\n`));
116
+ process.exitCode = 1;
117
+ return;
118
+ }
119
+ const session = sessions[0];
120
+ const redact = globals.redact !== false;
121
+ const markdown = renderSessionMarkdownDocument(session, {
122
+ redact,
123
+ reasoning,
124
+ knownSecrets: redact ? knownSecretValuesFromEnv() : undefined,
125
+ });
126
+ // Emails on top of what the renderer masks. Almost every real transcript
127
+ // carries a few — git author addresses, `gh api user`, a pasted log — and
128
+ // publishToEndpoint refuses a body containing any (RUSH-2428). Masking them
129
+ // means the published page genuinely does not carry them; the alternative,
130
+ // telling people to pass --force, would train everyone to bypass the gate
131
+ // that also catches real credentials.
132
+ //
133
+ // Applied to the RENDERED PAGE, not the Markdown, so the text that is masked
134
+ // is exactly the text `scanShareContent` will scan. Markdown escaping stands
135
+ // between the two: `foo\@example.com` hides from the pattern in the Markdown
136
+ // and reappears as a live address once marked drops the backslash.
137
+ const page = renderSessionHtmlDocument(session, markdown, { redacted: redact });
138
+ const html = redact ? redactEmails(page) : page;
139
+ // A real file on disk is what publishFile() takes, and the OG capturer opens
140
+ // it in a browser. 0600 + a per-run directory keeps the intermediate off a
141
+ // world-readable /tmp path while it exists.
142
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-session-share-'));
143
+ const file = path.join(dir, `${defaultSessionSlug(session)}.html`);
144
+ try {
145
+ fs.writeFileSync(file, html, { mode: 0o600 });
146
+ const result = await publishFile(file, buildSharePublishOptions(session, options));
147
+ if (globals.json) {
148
+ process.stdout.write(JSON.stringify({
149
+ session: session.id,
150
+ agent: session.agent,
151
+ redacted: redact,
152
+ ...result,
153
+ }, null, 2) + '\n');
154
+ return;
155
+ }
156
+ process.stdout.write(`${result.url}\n`);
157
+ const bits = [result.unlisted ? 'unlisted' : 'public', redact ? 'redacted' : chalk.red('NOT redacted')];
158
+ if (result.expiresAt)
159
+ bits.push(`expires ${result.expiresAt.slice(0, 10)}`);
160
+ process.stderr.write(chalk.dim(`${bits.join(' · ')}\n`));
161
+ }
162
+ finally {
163
+ fs.rmSync(dir, { recursive: true, force: true });
164
+ }
165
+ });
166
+ }
@@ -70,6 +70,7 @@ import { registerAttachCommand } from './attach.js';
70
70
  import { registerSessionsInjectCommand } from './sessions-inject.js';
71
71
  import { registerSessionsExportCommand } from './sessions-export.js';
72
72
  import { registerSessionsRenderCommand } from './sessions-render.js';
73
+ import { registerSessionsShareCommand } from './sessions-share.js';
73
74
  import { registerSessionsImportCommand } from './sessions-import.js';
74
75
  import { registerSessionsMigrateCommand, registerSessionsMigrationsCommand } from './sessions-migrate.js';
75
76
  import { registerSessionsBackfillCommand } from './sessions-backfill.js';
@@ -5173,6 +5174,7 @@ export function registerSessionsCommands(program) {
5173
5174
  registerSessionsInjectCommand(sessionsCmd);
5174
5175
  registerSessionsExportCommand(sessionsCmd);
5175
5176
  registerSessionsRenderCommand(sessionsCmd);
5177
+ registerSessionsShareCommand(sessionsCmd);
5176
5178
  registerSessionsImportCommand(sessionsCmd);
5177
5179
  registerSessionsMigrateCommand(sessionsCmd);
5178
5180
  registerSessionsMigrationsCommand(sessionsCmd);
@@ -10,11 +10,11 @@
10
10
  import chalk from 'chalk';
11
11
  import { setConfigValue } from '../lib/device-config.js';
12
12
  import { findFirstInstalledBrowser, listInstalledBrowsers } from '../lib/browser/chrome.js';
13
- import { DEFAULT_BROWSER_PROFILE_NAME, createProfile, findFreeProfilePort, getConfiguredDefaultProfileName, getProfile, } from '../lib/browser/profiles.js';
13
+ import { DEFAULT_BROWSER_PROFILE_NAME, getAutoDetectedProfile, createProfile, findFreeProfilePort, getConfiguredDefaultProfileName, } from '../lib/browser/profiles.js';
14
14
  import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
15
15
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
16
16
  import { defaultBrowserChoice } from './setup-preferences.js';
17
- const INSTALL_HINT = 'Install one of: Google Chrome, Brave, Microsoft Edge, Chromium, or Comet, then re-run `agents setup browser`.\n' +
17
+ const INSTALL_HINT = 'Install one of: Google Chrome, Brave, Microsoft Edge, Chromium, Comet, or Arc, then re-run `agents setup browser`.\n' +
18
18
  '(Safari and Firefox are not supported — agents browser drives over the Chrome DevTools Protocol.)';
19
19
  /**
20
20
  * Interactive browser setup. Returns true if a usable default profile exists
@@ -29,9 +29,9 @@ export async function runBrowserWizard() {
29
29
  console.error(chalk.red('No supported browser found.\n') + chalk.dim(INSTALL_HINT));
30
30
  return false;
31
31
  }
32
- const existing = await getProfile(DEFAULT_BROWSER_PROFILE_NAME);
32
+ const existing = await getAutoDetectedProfile();
33
33
  if (existing) {
34
- console.log(chalk.dim(`Browser profile "${DEFAULT_BROWSER_PROFILE_NAME}" already exists.`));
34
+ console.log(chalk.dim(`Browser profile "${existing.name}" already exists.`));
35
35
  return true;
36
36
  }
37
37
  const created = await createAutoDefault();
@@ -46,7 +46,7 @@ export async function runBrowserWizard() {
46
46
  }
47
47
  const { confirm, select } = await import('@inquirer/prompts');
48
48
  // If a default profile already exists, this is a reconfigure.
49
- const existing = await getProfile(DEFAULT_BROWSER_PROFILE_NAME);
49
+ const existing = await getAutoDetectedProfile();
50
50
  if (existing) {
51
51
  console.log(chalk.dim(`Browser profile "${existing.name}" already exists (${existing.browser}${existing.binary ? ` · ${existing.binary}` : ''}).`));
52
52
  const change = await confirm({ message: 'Re-create it (e.g. to pin a different browser)?', default: false });
@@ -11,7 +11,7 @@
11
11
  import chalk from 'chalk';
12
12
  import { initBrand, registerMineManageCommands } from './mine.js';
13
13
  import { validateBrandName, getBrandConfig } from '../lib/brand.js';
14
- import { isShimsInPath } from '../lib/shims.js';
14
+ import { isShimsInPath } from '../lib/installations/shims.js';
15
15
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
16
16
  /**
17
17
  * Optional/heavier top-level commands a brand commonly turns off. Kept short so
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import chalk from 'chalk';
16
16
  import { listInstalledBrowsers } from '../lib/browser/chrome.js';
17
- import { DEFAULT_BROWSER_PROFILE_NAME, createProfile, findFreeProfilePort, getConfiguredDefaultProfileName, getProfile, } from '../lib/browser/profiles.js';
17
+ import { DEFAULT_BROWSER_PROFILE_NAME, getAutoDetectedProfile, createProfile, findFreeProfilePort, getConfiguredDefaultProfileName, } from '../lib/browser/profiles.js';
18
18
  import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
19
19
  import { getConfigValue, setConfigValue } from '../lib/device-config.js';
20
20
  import { loadDevices } from '../lib/devices/registry.js';
@@ -88,7 +88,7 @@ export async function maybePickBrowserProfile() {
88
88
  return false;
89
89
  if (getConfiguredDefaultProfileName())
90
90
  return false;
91
- if (await getProfile(DEFAULT_BROWSER_PROFILE_NAME))
91
+ if (await getAutoDetectedProfile())
92
92
  return false;
93
93
  const installed = listInstalledBrowsers();
94
94
  if (installed.length === 0)
@@ -115,10 +115,10 @@ export async function maybePickBrowserProfile() {
115
115
  viewport: { width: DEFAULT_VIEWPORT.width, height: DEFAULT_VIEWPORT.height },
116
116
  };
117
117
  await createProfile(profile);
118
- // The same key `agents browser profiles set-default` writes (device-local).
118
+ // The same key `agents browser use` writes (device-local).
119
119
  setConfigValue('browser.profile', profile.name);
120
120
  console.log(chalk.green(`Browser: '${chosen.browserType}'`) +
121
- chalk.dim(` — profile "${profile.name}" is this machine's default (agents browser profiles set-default to change).`));
121
+ chalk.dim(` — profile "${profile.name}" is this machine's default (agents browser use to change).`));
122
122
  return true;
123
123
  }
124
124
  /**
@@ -16,7 +16,7 @@ import { isGitRepo, cloneIntoExisting, pullRepo } from '../lib/git.js';
16
16
  import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
17
17
  import { AGENTS, agentConfigDirName, getUnmanagedAgentInstalls, countSessionFiles, agentLabel } from '../lib/agents.js';
18
18
  import { setGlobalDefault } from '../lib/installations/versions.js';
19
- import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPath, getPathSetupInstructions, assertIsolationBoundary } from '../lib/shims.js';
19
+ import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPath, getPathSetupInstructions, assertIsolationBoundary } from '../lib/installations/shims.js';
20
20
  import { setHelpSections } from '../lib/help.js';
21
21
  import { registerSetupBrowserCommand, runBrowserWizard } from './setup-browser.js';
22
22
  import { registerSetupComputerCommand, runComputerWizard } from './setup-computer.js';
@@ -26,7 +26,7 @@ import { registerSetupSecretsCommand } from './setup-secrets.js';
26
26
  import { registerSetupFleetCommand } from './setup-fleet.js';
27
27
  import { registerSetupWatchdogCommand, runWatchdogSetupWizard } from './setup-watchdog.js';
28
28
  import { runPreferencesStep } from './setup-preferences.js';
29
- import { getConfiguredDefaultProfileName, getProfile, isProfileLaunchableHere, DEFAULT_BROWSER_PROFILE_NAME } from '../lib/browser/profiles.js';
29
+ import { getConfiguredDefaultProfileName, getProfile, getAutoDetectedProfile, isProfileLaunchableHere } from '../lib/browser/profiles.js';
30
30
  import { listInstalledBrowsers } from '../lib/browser/chrome.js';
31
31
  import { probeComputerTrust } from './computer.js';
32
32
  import { readShareConfig } from '../lib/share/config.js';
@@ -265,7 +265,9 @@ export async function ensureInitialized(program) {
265
265
  }
266
266
  export async function getSetupStatus() {
267
267
  const configuredBrowserProfile = getConfiguredDefaultProfileName();
268
- const browserProfile = await getProfile(configuredBrowserProfile ?? DEFAULT_BROWSER_PROFILE_NAME);
268
+ const browserProfile = configuredBrowserProfile
269
+ ? await getProfile(configuredBrowserProfile)
270
+ : await getAutoDetectedProfile();
269
271
  const browserReady = browserProfile !== null && isProfileLaunchableHere(browserProfile);
270
272
  const installedBrowsers = listInstalledBrowsers();
271
273
  const computerState = process.platform === 'darwin' ? (await probeComputerTrust() ? 'ready' : 'missing') : 'n/a';
@@ -36,7 +36,7 @@ import { resolveSyncPassphraseFromEnv } from '../lib/secrets/sync-passphrase.js'
36
36
  import { agentLabel, resolveAgentName, MANAGED_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
37
37
  import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildSelection, mergeRepoScopedSelections, listRepoNames, getVersionHomePath, } from '../lib/installations/versions.js';
38
38
  import { capableAgents } from '../lib/capabilities.js';
39
- import { parseHookManifest, registerHooksToSettings } from '../lib/hooks.js';
39
+ import { parseHookManifest, registerHooksToSettings } from '../lib/hooks/install.js';
40
40
  import { compileRulesForProject } from '../lib/rules/compile.js';
41
41
  import { runLaunchSync } from '../lib/project-launch.js';
42
42
  import { formatKeptProjectResources } from '../lib/project-resources.js';
@@ -11,7 +11,7 @@ import { resolveConfiguredModel, formatAgentIdentity } from '../lib/models.js';
11
11
  import { readManifest, writeManifest, createDefaultManifest } from '../lib/manifest.js';
12
12
  import { installVersion, removeVersion, listInstalledVersions, isVersionInstalled, isLatestInstalled, isOldestInstalled, getGlobalDefault, setGlobalDefault, markVersionIsolated, setIsolatedDefault, isVersionIsolated, getVersionHomePath, getVersionDir, syncResourcesToVersion, parseAgentSpec, promptResourceSelection, promptNewResourceSelection, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, printTrashFooter, } from '../lib/installations/versions.js';
13
13
  import { carryForwardSettings } from '../lib/settings-manifest.js';
14
- import { createShim, createVersionedAlias, supportsIsolatedInstall, isIsolationProtected, CONFIG_ENV_ISOLATED_AGENTS, removeShim, shimExists, getShimsDir, getShimPath, getPathShadowingExecutable, isShimsInPath, getPathSetupInstructions, addShimsToPath, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/shims.js';
14
+ import { createShim, createVersionedAlias, supportsIsolatedInstall, isIsolationProtected, CONFIG_ENV_ISOLATED_AGENTS, removeShim, shimExists, getShimsDir, getShimPath, getPathShadowingExecutable, isShimsInPath, getPathSetupInstructions, addShimsToPath, switchConfigSymlink, switchHomeFileSymlinks, } from '../lib/installations/shims.js';
15
15
  import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
16
16
  import { tryAutoPull } from '../lib/git.js';
17
17
  import { getAgentsDir, getTrashVersionsDir } from '../lib/state.js';
@@ -11,7 +11,7 @@ import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth
11
11
  import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, } from '../lib/accounting/usage.js';
12
12
  import { readManifest } from '../lib/manifest.js';
13
13
  import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
14
- import { ensureVersionedAliasCurrent, removeShim, } from '../lib/shims.js';
14
+ import { ensureVersionedAliasCurrent, removeShim, } from '../lib/installations/shims.js';
15
15
  import { getAgentResources } from '../lib/resources.js';
16
16
  import { renderMergedResources } from '../lib/merged-resources.js';
17
17
  import { resolveVersionFilter, AgentSpecError } from '../lib/agent-spec/index.js';
@@ -25,8 +25,10 @@ function readWebhookSecrets(bundleName) {
25
25
  secrets.github = env.GITHUB_WEBHOOK_SECRET;
26
26
  if (env.LINEAR_WEBHOOK_SECRET)
27
27
  secrets.linear = env.LINEAR_WEBHOOK_SECRET;
28
- if (!secrets.github && !secrets.linear) {
29
- throw new Error(`Bundle '${bundleName}' must contain GITHUB_WEBHOOK_SECRET or LINEAR_WEBHOOK_SECRET.`);
28
+ if (env.SLACK_SIGNING_SECRET)
29
+ secrets.slack = env.SLACK_SIGNING_SECRET;
30
+ if (!secrets.github && !secrets.linear && !secrets.slack) {
31
+ throw new Error(`Bundle '${bundleName}' must contain GITHUB_WEBHOOK_SECRET, LINEAR_WEBHOOK_SECRET, or SLACK_SIGNING_SECRET.`);
30
32
  }
31
33
  return secrets;
32
34
  }
@@ -36,8 +38,8 @@ export function registerWebhooksCommand(program) {
36
38
  .description('Run a localhost signed webhook receiver for routine triggers.');
37
39
  webhooks
38
40
  .command('serve')
39
- .description('Receive signed GitHub/Linear webhooks on /hooks/<source> and fire matching routines.')
40
- .requiredOption('--secrets-bundle <name>', 'agents secrets bundle containing GITHUB_WEBHOOK_SECRET and/or LINEAR_WEBHOOK_SECRET')
41
+ .description('Receive signed GitHub/Linear/Slack webhooks on /hooks/<source> and fire matching routines and handlers.')
42
+ .requiredOption('--secrets-bundle <name>', 'agents secrets bundle containing GITHUB_WEBHOOK_SECRET, LINEAR_WEBHOOK_SECRET, and/or SLACK_SIGNING_SECRET')
41
43
  .option('--bind <addr>', `Bind address (default ${DEFAULT_HOST})`, DEFAULT_HOST)
42
44
  .option('-p, --port <n>', `Local port (default ${DEFAULT_PORT})`, String(DEFAULT_PORT))
43
45
  .option('--rate-limit <n>', 'Accepted deliveries per source per minute', '60')
@@ -61,12 +63,15 @@ export function registerWebhooksCommand(program) {
61
63
  // Durable delivery dedup: replays survive a receiver restart (an
62
64
  // in-memory store would forget every seen delivery on restart).
63
65
  deliveryStore: createFileDeliveryStore(path.join(getRuntimeStateDir(), 'webhook', 'deliveries.json')),
64
- onDelivery: (webhook, fired, handlers) => {
66
+ // Logged at MATCH time, before dispatch — a `run.command` handler can
67
+ // block on a shelled-out agent run for minutes, and that must never
68
+ // delay the log that says a delivery fired (RUSH-2722).
69
+ onMatch: (webhook, matchedJobNames, matchedHandlerNames) => {
65
70
  const parts = [];
66
- if (fired.length)
67
- parts.push(`routines ${fired.map((f) => f.jobName).join(', ')}`);
68
- if (handlers.length)
69
- parts.push(`handlers ${handlers.map((h) => h.handlerName).join(', ')}`);
71
+ if (matchedJobNames.length)
72
+ parts.push(`routines ${matchedJobNames.join(', ')}`);
73
+ if (matchedHandlerNames.length)
74
+ parts.push(`handlers ${matchedHandlerNames.join(', ')}`);
70
75
  console.log(`${new Date().toISOString()} ${webhook.source}:${webhook.event} ` +
71
76
  (parts.length ? `fired ${parts.join('; ')}` : 'no match'));
72
77
  },
@@ -24,6 +24,16 @@ export declare const NATIVE_ACCOUNT_CAPABILITIES: Record<AgentId, NativeAccountC
24
24
  export declare function nativeAccountCapability(agent: AgentId): NativeAccountCapability;
25
25
  /** Whether a harness's native login can be named/attached (supported or conditional). */
26
26
  export declare function nativeAccountNameable(agent: AgentId): boolean;
27
+ /** Harnesses whose native login is fully nameable/attachable today. */
28
+ export declare function supportedNativeHarnesses(): AgentId[];
29
+ /**
30
+ * Named reason to refuse native name/attach, or null when the harness is
31
+ * nameable. Provider-credential `accounts add` is a different path and MUST
32
+ * not consult this — that surface is supported for every registered provider.
33
+ */
34
+ export declare function nativeAccountNamingRefusal(agent: AgentId): string | null;
35
+ /** Fail loud on the native name/attach path for an unsupported harness. */
36
+ export declare function assertNativeAccountNameable(agent: AgentId): void;
27
37
  /**
28
38
  * The single identity key used at `accounts name`, attach, exec validation,
29
39
  * view, and inventory. Prefer the harness's stable `accountKey`. For an