@phnx-labs/agents-cli 1.20.88 → 1.20.89

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 (55) hide show
  1. package/CHANGELOG.md +263 -0
  2. package/README.md +9 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/commands.js +7 -7
  5. package/dist/commands/factory.js +26 -2
  6. package/dist/commands/funnel.js +16 -1
  7. package/dist/commands/menubar.js +117 -34
  8. package/dist/commands/routines.js +23 -1
  9. package/dist/commands/secrets-rotate-passphrase.d.ts +17 -0
  10. package/dist/commands/secrets-rotate-passphrase.js +96 -0
  11. package/dist/commands/secrets.js +2 -0
  12. package/dist/commands/sessions.d.ts +7 -1
  13. package/dist/commands/sessions.js +39 -12
  14. package/dist/commands/webhook.js +7 -2
  15. package/dist/lib/commands.js +9 -1
  16. package/dist/lib/daemon.d.ts +29 -0
  17. package/dist/lib/daemon.js +58 -4
  18. package/dist/lib/events.d.ts +1 -1
  19. package/dist/lib/factory/snapshot.d.ts +78 -0
  20. package/dist/lib/factory/snapshot.js +209 -0
  21. package/dist/lib/fs-atomic.d.ts +14 -1
  22. package/dist/lib/fs-atomic.js +35 -3
  23. package/dist/lib/funnel.d.ts +1 -0
  24. package/dist/lib/funnel.js +8 -0
  25. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  26. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  27. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -2
  28. package/dist/lib/menubar/install-menubar.d.ts +53 -2
  29. package/dist/lib/menubar/install-menubar.js +183 -28
  30. package/dist/lib/platform/process.d.ts +2 -0
  31. package/dist/lib/platform/process.js +5 -3
  32. package/dist/lib/resources.d.ts +8 -0
  33. package/dist/lib/resources.js +34 -1
  34. package/dist/lib/routines-placement.d.ts +2 -1
  35. package/dist/lib/routines-placement.js +8 -4
  36. package/dist/lib/routines.d.ts +57 -1
  37. package/dist/lib/routines.js +74 -1
  38. package/dist/lib/runner.d.ts +2 -0
  39. package/dist/lib/runner.js +21 -8
  40. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  41. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  42. package/dist/lib/secrets/bundles.js +9 -34
  43. package/dist/lib/secrets/filestore.d.ts +152 -34
  44. package/dist/lib/secrets/filestore.js +676 -123
  45. package/dist/lib/session/remote-active.d.ts +4 -1
  46. package/dist/lib/session/remote-active.js +8 -2
  47. package/dist/lib/session/viewing-in.d.ts +31 -0
  48. package/dist/lib/session/viewing-in.js +47 -0
  49. package/dist/lib/state.d.ts +17 -0
  50. package/dist/lib/state.js +30 -2
  51. package/dist/lib/triggers/handlers.d.ts +95 -0
  52. package/dist/lib/triggers/handlers.js +384 -0
  53. package/dist/lib/triggers/webhook.d.ts +10 -2
  54. package/dist/lib/triggers/webhook.js +65 -11
  55. package/package.json +1 -1
@@ -9,7 +9,10 @@ export declare const NO_FANOUT_ENV = "AGENTS_SESSIONS_LOCAL";
9
9
  * Parse a peer's `--active --json` stdout into active sessions, tagging each
10
10
  * with `machine`. Defensive against version skew / partial output: non-JSON or
11
11
  * a non-array yields `[]`, and non-object entries are dropped rather than
12
- * throwing. Exported for unit testing without a live tailnet.
12
+ * throwing. `viewingIn` arrives as a display string from a current peer and as
13
+ * an `{app, tab}` object from one that predates the flattening, so it is
14
+ * normalized here — the single boundary where a foreign row becomes internal.
15
+ * Exported for unit testing without a live tailnet.
13
16
  */
14
17
  export declare function parseRemoteActive(stdout: string, machine: string): ActiveSession[];
15
18
  export interface RemoteActiveResult {
@@ -15,6 +15,7 @@
15
15
  * `spawn`, not the sync `sshExec`) so N peers cost one round-trip, not N.
16
16
  */
17
17
  import { gatherRemoteAgentsJson } from '../remote-agents-json.js';
18
+ import { parseViewingIn } from './viewing-in.js';
18
19
  /**
19
20
  * Recursion guard, passed as an env var (not a CLI flag) so an OLDER remote
20
21
  * `agents` that predates this feature ignores it harmlessly instead of erroring
@@ -25,7 +26,10 @@ export const NO_FANOUT_ENV = 'AGENTS_SESSIONS_LOCAL';
25
26
  * Parse a peer's `--active --json` stdout into active sessions, tagging each
26
27
  * with `machine`. Defensive against version skew / partial output: non-JSON or
27
28
  * a non-array yields `[]`, and non-object entries are dropped rather than
28
- * throwing. Exported for unit testing without a live tailnet.
29
+ * throwing. `viewingIn` arrives as a display string from a current peer and as
30
+ * an `{app, tab}` object from one that predates the flattening, so it is
31
+ * normalized here — the single boundary where a foreign row becomes internal.
32
+ * Exported for unit testing without a live tailnet.
29
33
  */
30
34
  export function parseRemoteActive(stdout, machine) {
31
35
  let parsed;
@@ -40,7 +44,9 @@ export function parseRemoteActive(stdout, machine) {
40
44
  const out = [];
41
45
  for (const x of parsed) {
42
46
  if (x && typeof x === 'object' && !Array.isArray(x)) {
43
- out.push({ ...x, machine });
47
+ const row = { ...x, machine };
48
+ row.viewingIn = parseViewingIn(x.viewingIn);
49
+ out.push(row);
44
50
  }
45
51
  }
46
52
  return out;
@@ -27,6 +27,37 @@ export interface ViewingIn {
27
27
  /** 1-based tab number within that app, when it can be resolved. */
28
28
  tab?: number;
29
29
  }
30
+ /**
31
+ * The one display form of "where is this session being watched" — `'codium tab 3'`
32
+ * when a client is attached, the bare app name when the tab can't be resolved, and
33
+ * `'detached'` when the pane is live but nobody is looking at it (the terminal that
34
+ * displayed it was closed or crashed).
35
+ *
36
+ * `undefined` means **we do not know**, and it covers two different situations that
37
+ * must not be confused with `'detached'`: a session that isn't tmux-hosted (no pane
38
+ * to attach to, so it isn't on this axis at all), and a tmux session whose pane the
39
+ * locator could not resolve (`mapPanesToTargets` returned nothing for this socket,
40
+ * or the row was never enriched). {@link resolveViewingIn} answers `undefined` for
41
+ * BOTH "no client attached" and "could not locate the pane", so the pane's resolved
42
+ * `tmuxTarget` is what separates them: without it there is no evidence of absence,
43
+ * only absence of evidence. Claiming `'detached'` there would invent an orphaned
44
+ * session — and a consumer acts on that claim (Factory's picker pre-ticks every
45
+ * detached row for rescue), so the wrong answer resumes a session nobody asked for.
46
+ *
47
+ * Shared by the `--active` row renderer and the `--json` serializer so a machine
48
+ * consumer reads exactly the string a human sees, instead of re-deriving "detached"
49
+ * from an absent field.
50
+ */
51
+ export declare function viewingInLabel(s: Pick<ActiveSession, 'provenance' | 'viewingIn' | 'tmuxTarget'>): string | undefined;
52
+ /**
53
+ * Wire form -> internal form for a row arriving from another machine's
54
+ * `--active --json`. The fan-out reaches peers whose CLI may predate
55
+ * {@link viewingInLabel} and still emit the `{app, tab}` object, so this boundary
56
+ * normalizes both shapes into one — the ONLY place either shape is accepted.
57
+ * `'detached'` maps to undefined, which is what "no attached client" means
58
+ * internally; {@link viewingInLabel} regenerates the word from the pane.
59
+ */
60
+ export declare function parseViewingIn(raw: unknown): ViewingIn | undefined;
30
61
  /** Injection seams so `resolveViewingIn` is unit-testable without a live tmux/ps/osascript. */
31
62
  export interface ViewingInDeps {
32
63
  /** Ghostty surfaces (window/tab/cwd/title). Enumerated once by the caller and shared. */
@@ -26,6 +26,53 @@ import { hostFromPid } from './active.js';
26
26
  import { enumerateGhosttyTabs, assignGhosttyTabs } from './ghostty-tabs.js';
27
27
  import { getTerminalsDir } from '../state.js';
28
28
  const execFileAsync = promisify(execFile);
29
+ /**
30
+ * The one display form of "where is this session being watched" — `'codium tab 3'`
31
+ * when a client is attached, the bare app name when the tab can't be resolved, and
32
+ * `'detached'` when the pane is live but nobody is looking at it (the terminal that
33
+ * displayed it was closed or crashed).
34
+ *
35
+ * `undefined` means **we do not know**, and it covers two different situations that
36
+ * must not be confused with `'detached'`: a session that isn't tmux-hosted (no pane
37
+ * to attach to, so it isn't on this axis at all), and a tmux session whose pane the
38
+ * locator could not resolve (`mapPanesToTargets` returned nothing for this socket,
39
+ * or the row was never enriched). {@link resolveViewingIn} answers `undefined` for
40
+ * BOTH "no client attached" and "could not locate the pane", so the pane's resolved
41
+ * `tmuxTarget` is what separates them: without it there is no evidence of absence,
42
+ * only absence of evidence. Claiming `'detached'` there would invent an orphaned
43
+ * session — and a consumer acts on that claim (Factory's picker pre-ticks every
44
+ * detached row for rescue), so the wrong answer resumes a session nobody asked for.
45
+ *
46
+ * Shared by the `--active` row renderer and the `--json` serializer so a machine
47
+ * consumer reads exactly the string a human sees, instead of re-deriving "detached"
48
+ * from an absent field.
49
+ */
50
+ export function viewingInLabel(s) {
51
+ if (s.provenance?.mux?.kind !== 'tmux' || !s.provenance.mux.pane)
52
+ return undefined;
53
+ if (!s.viewingIn)
54
+ return s.tmuxTarget ? 'detached' : undefined;
55
+ return s.viewingIn.tab != null ? `${s.viewingIn.app} tab ${s.viewingIn.tab}` : s.viewingIn.app;
56
+ }
57
+ /**
58
+ * Wire form -> internal form for a row arriving from another machine's
59
+ * `--active --json`. The fan-out reaches peers whose CLI may predate
60
+ * {@link viewingInLabel} and still emit the `{app, tab}` object, so this boundary
61
+ * normalizes both shapes into one — the ONLY place either shape is accepted.
62
+ * `'detached'` maps to undefined, which is what "no attached client" means
63
+ * internally; {@link viewingInLabel} regenerates the word from the pane.
64
+ */
65
+ export function parseViewingIn(raw) {
66
+ if (raw && typeof raw === 'object') {
67
+ const app = raw.app;
68
+ const tab = raw.tab;
69
+ return typeof app === 'string' ? { app, tab: typeof tab === 'number' ? tab : undefined } : undefined;
70
+ }
71
+ if (typeof raw !== 'string' || !raw || raw === 'detached')
72
+ return undefined;
73
+ const m = raw.match(/^(.+?) tab (\d+)$/);
74
+ return m ? { app: m[1], tab: parseInt(m[2], 10) } : { app: raw };
75
+ }
29
76
  /** Apps whose tab index is published by the extension via live-terminals.json. */
30
77
  const EDITOR_APPS = new Set(['code', 'cursor', 'codium', 'windsurf']);
31
78
  /** The tmux session name a session's pane belongs to, from `session:window.pane`. */
@@ -109,6 +109,12 @@ export declare function getCacheDir(): string;
109
109
  export declare function getPackagesDir(): string;
110
110
  /** Path to routine YAML definitions (~/.agents/routines/). */
111
111
  export declare function getRoutinesDir(): string;
112
+ /**
113
+ * Path to webhook handler YAML definitions (~/.agents/webhooks/). Handlers are
114
+ * one-off triggers for agents/workflows/commands/routines, layered the same way
115
+ * as routines (project > user > system).
116
+ */
117
+ export declare function getWebhooksDir(): string;
112
118
  /**
113
119
  * Path to built-in routine definitions shipped in the system repo
114
120
  * (`~/.agents/.system/routines/`). Unioned under the user routines dir by
@@ -117,6 +123,11 @@ export declare function getRoutinesDir(): string;
117
123
  * disables the built-in). The daemon fires these; the directory need not exist.
118
124
  */
119
125
  export declare function getSystemRoutinesDir(): string;
126
+ /**
127
+ * Path to built-in webhook handler definitions shipped in the system repo
128
+ * (`~/.agents/.system/webhooks/`). Layered under user handlers by `listHandlers()`.
129
+ */
130
+ export declare function getSystemWebhooksDir(): string;
120
131
  /**
121
132
  * Path to a project-scoped routines directory (`<project>/.agents/routines/`),
122
133
  * or null when no project `.agents/` is found by walking up from cwd.
@@ -130,6 +141,12 @@ export declare function getSystemRoutinesDir(): string;
130
141
  * it does not auto-enable firing. See `lib/routines-project.ts`.
131
142
  */
132
143
  export declare function getProjectRoutinesDir(cwd?: string): string | null;
144
+ /**
145
+ * Path to a project-scoped webhook handlers directory
146
+ * (`<project>/.agents/webhooks/`), or null when no project `.agents/` is found
147
+ * by walking up from cwd.
148
+ */
149
+ export declare function getProjectWebhooksDir(cwd?: string): string | null;
133
150
  /** Path to routine execution logs (~/.agents/.history/runs/). */
134
151
  export declare function getRunsDir(): string;
135
152
  /** Path to monitor YAML definitions (~/.agents/monitors/). */
package/dist/lib/state.js CHANGED
@@ -72,6 +72,7 @@ const SYSTEM_PLUGINS_DIR = path.join(SYSTEM_AGENTS_DIR, 'plugins');
72
72
  // Unioned under user routines by listJobs()/readJob() so a routine shipped here
73
73
  // fires for every install, while a user routine of the same name overrides it.
74
74
  const SYSTEM_ROUTINES_DIR = path.join(SYSTEM_AGENTS_DIR, 'routines');
75
+ const SYSTEM_WEBHOOKS_DIR = path.join(SYSTEM_AGENTS_DIR, 'webhooks');
75
76
  const SYSTEM_PROMPTCUTS_FILE = path.join(SYSTEM_AGENTS_DIR, 'hooks', 'promptcuts.yaml');
76
77
  const SYSTEM_MCP_CONFIG_FILE = path.join(SYSTEM_AGENTS_DIR, 'mcp.json');
77
78
  const SYSTEM_INSTRUCTIONS_FILE = path.join(SYSTEM_AGENTS_DIR, 'instructions.md');
@@ -82,6 +83,7 @@ const HISTORY_DIR = path.join(USER_AGENTS_DIR, '.history');
82
83
  const CACHE_DIR = path.join(USER_AGENTS_DIR, '.cache');
83
84
  // Top-level user dirs (config/definitions only — runtime moves into .history/.cache).
84
85
  const ROUTINES_DIR = path.join(USER_AGENTS_DIR, 'routines');
86
+ const WEBHOOKS_DIR = path.join(USER_AGENTS_DIR, 'webhooks');
85
87
  // Monitor definitions (event-triggered watchers). Sibling of ROUTINES_DIR: a
86
88
  // monitor is a routine whose trigger is a watched source instead of a clock.
87
89
  const MONITORS_DIR = path.join(USER_AGENTS_DIR, 'monitors');
@@ -306,7 +308,13 @@ export function getCacheDir() { return CACHE_DIR; }
306
308
  /** Path to cloned packages (~/.agents/.cache/packages/). */
307
309
  export function getPackagesDir() { return PACKAGES_DIR; }
308
310
  /** Path to routine YAML definitions (~/.agents/routines/). */
309
- export function getRoutinesDir() { return ROUTINES_DIR; }
311
+ export function getRoutinesDir() { return process.env.AGENTS_ROUTINES_DIR ?? ROUTINES_DIR; }
312
+ /**
313
+ * Path to webhook handler YAML definitions (~/.agents/webhooks/). Handlers are
314
+ * one-off triggers for agents/workflows/commands/routines, layered the same way
315
+ * as routines (project > user > system).
316
+ */
317
+ export function getWebhooksDir() { return process.env.AGENTS_WEBHOOKS_DIR ?? WEBHOOKS_DIR; }
310
318
  /**
311
319
  * Path to built-in routine definitions shipped in the system repo
312
320
  * (`~/.agents/.system/routines/`). Unioned under the user routines dir by
@@ -314,7 +322,12 @@ export function getRoutinesDir() { return ROUTINES_DIR; }
314
322
  * user routine of the same name overrides it (a user copy with `enabled: false`
315
323
  * disables the built-in). The daemon fires these; the directory need not exist.
316
324
  */
317
- export function getSystemRoutinesDir() { return SYSTEM_ROUTINES_DIR; }
325
+ export function getSystemRoutinesDir() { return process.env.AGENTS_SYSTEM_ROUTINES_DIR ?? SYSTEM_ROUTINES_DIR; }
326
+ /**
327
+ * Path to built-in webhook handler definitions shipped in the system repo
328
+ * (`~/.agents/.system/webhooks/`). Layered under user handlers by `listHandlers()`.
329
+ */
330
+ export function getSystemWebhooksDir() { return process.env.AGENTS_SYSTEM_WEBHOOKS_DIR ?? SYSTEM_WEBHOOKS_DIR; }
318
331
  /**
319
332
  * Path to a project-scoped routines directory (`<project>/.agents/routines/`),
320
333
  * or null when no project `.agents/` is found by walking up from cwd.
@@ -333,6 +346,17 @@ export function getProjectRoutinesDir(cwd = process.cwd()) {
333
346
  return null;
334
347
  return path.join(projectAgentsDir, 'routines');
335
348
  }
349
+ /**
350
+ * Path to a project-scoped webhook handlers directory
351
+ * (`<project>/.agents/webhooks/`), or null when no project `.agents/` is found
352
+ * by walking up from cwd.
353
+ */
354
+ export function getProjectWebhooksDir(cwd = process.cwd()) {
355
+ const projectAgentsDir = getProjectAgentsDir(cwd);
356
+ if (!projectAgentsDir)
357
+ return null;
358
+ return path.join(projectAgentsDir, 'webhooks');
359
+ }
336
360
  /** Path to routine execution logs (~/.agents/.history/runs/). */
337
361
  export function getRunsDir() { return RUNS_DIR; }
338
362
  /** Path to monitor YAML definitions (~/.agents/monitors/). */
@@ -506,6 +530,8 @@ export function ensureAgentsDir() {
506
530
  fs.mkdirSync(PACKAGES_DIR, opts);
507
531
  if (!fs.existsSync(ROUTINES_DIR))
508
532
  fs.mkdirSync(ROUTINES_DIR, opts);
533
+ if (!fs.existsSync(WEBHOOKS_DIR))
534
+ fs.mkdirSync(WEBHOOKS_DIR, opts);
509
535
  if (!fs.existsSync(RUNS_DIR))
510
536
  fs.mkdirSync(RUNS_DIR, opts);
511
537
  if (!fs.existsSync(VERSIONS_DIR))
@@ -524,6 +550,8 @@ export function ensureAgentsDir() {
524
550
  fs.mkdirSync(SYSTEM_PERMISSIONS_DIR, opts);
525
551
  if (!fs.existsSync(SYSTEM_SUBAGENTS_DIR))
526
552
  fs.mkdirSync(SYSTEM_SUBAGENTS_DIR, opts);
553
+ if (!fs.existsSync(SYSTEM_WEBHOOKS_DIR))
554
+ fs.mkdirSync(SYSTEM_WEBHOOKS_DIR, opts);
527
555
  try {
528
556
  fs.chmodSync(SYSTEM_AGENTS_DIR, 0o700);
529
557
  }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Webhook handler config layer.
3
+ *
4
+ * Handlers are one-off triggers stored in `~/.agents/webhooks/*.yml` (plus
5
+ * project/system layers). They complement routine triggers: a matching webhook
6
+ * can run an agent, workflow, shell command, or delegate to a routine.
7
+ */
8
+ import type { JobConfig, RunMeta, WebhookContext } from '../routines.js';
9
+ import type { AgentId } from '../types.js';
10
+ import type { IncomingWebhook, WebhookSource } from './webhook.js';
11
+ export interface WebhookHandler {
12
+ name: string;
13
+ enabled?: boolean;
14
+ devices?: string[];
15
+ source: WebhookSource;
16
+ event?: string;
17
+ action?: string;
18
+ stateTo?: string;
19
+ stateFrom?: string;
20
+ teamKey?: string;
21
+ label?: string;
22
+ repo?: string;
23
+ branch?: string;
24
+ /**
25
+ * Where to run the action. A device name (`yosemite-s0`) runs there over SSH;
26
+ * `fleet` picks any eligible online worker; `fleet/<platform>` (or
27
+ * `<platform>/fleet`, or a bare `linux`/`macos`/`windows`) restricts that pick
28
+ * to one platform. Omitted runs locally.
29
+ */
30
+ host?: string;
31
+ run?: {
32
+ agent?: AgentId;
33
+ workflow?: string;
34
+ command?: string;
35
+ prompt?: string;
36
+ /** Environment variables injected into the spawned process. */
37
+ env?: Record<string, string>;
38
+ };
39
+ routine?: string;
40
+ }
41
+ export interface FiredHandler {
42
+ handlerName: string;
43
+ runId?: string;
44
+ exitCode?: number;
45
+ output?: string;
46
+ }
47
+ export interface HandlerHostResolution {
48
+ /** Resolved execution host, or undefined to run locally. */
49
+ host?: string;
50
+ /** Strategy that should be set on the JobConfig. */
51
+ hostStrategy?: 'host' | 'fleet';
52
+ }
53
+ /**
54
+ * Resolve a handler `host` expression to a concrete host or local execution.
55
+ *
56
+ * - Specific device name (e.g. `yosemite-s0`) → run there over SSH, or locally
57
+ * if it names this machine.
58
+ * - `fleet` → pick any online worker device.
59
+ * - `fleet/<platform>` or `<platform>/fleet` (e.g. `fleet/linux`, `linux/fleet`)
60
+ * → pick any online worker on that platform. `linux` alone is accepted as a
61
+ * shorthand for `fleet/linux`.
62
+ *
63
+ * Throws when a fleet expression matches no eligible device, rather than
64
+ * silently falling back to this machine — `fleet/linux` must never land on a
65
+ * macOS box.
66
+ */
67
+ export declare function resolveHandlerHost(host: string | undefined): HandlerHostResolution;
68
+ /**
69
+ * List all webhook handlers, scanning project > user > system webhook dirs.
70
+ * Higher layers shadow lower ones of the same name (first-seen wins).
71
+ */
72
+ export declare function listHandlers(cwd?: string): WebhookHandler[];
73
+ /** Pure matcher: does this handler match the incoming webhook? */
74
+ export declare function handlerMatchesWebhook(handler: WebhookHandler, webhook: IncomingWebhook): boolean;
75
+ /**
76
+ * Build the variable-substitution context for a webhook. Linear events expose
77
+ * `issue` and `updatedFrom`; GitHub events expose `repository`, `pull_request`,
78
+ * and `issue`.
79
+ */
80
+ export declare function buildWebhookContext(webhook: IncomingWebhook): WebhookContext;
81
+ export interface ExecuteHandlerOptions {
82
+ dispatchAgent?: (config: JobConfig) => Promise<RunMeta>;
83
+ dispatchWorkflow?: (config: JobConfig) => Promise<RunMeta>;
84
+ execCommand?: (command: string) => Promise<{
85
+ exitCode: number;
86
+ output: string;
87
+ }>;
88
+ dispatchRoutine?: (config: JobConfig) => Promise<RunMeta>;
89
+ }
90
+ /**
91
+ * Execute a handler's action. Runs the configured agent/workflow/command or
92
+ * delegates to a routine, substituting `{{...}}` placeholders from the webhook
93
+ * context.
94
+ */
95
+ export declare function executeHandler(handler: WebhookHandler, webhook: IncomingWebhook, opts?: ExecuteHandlerOptions): Promise<FiredHandler>;