@phnx-labs/agents-cli 1.20.87 → 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.
- package/CHANGELOG.md +323 -0
- package/README.md +12 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/commands.js +7 -7
- package/dist/commands/doctor.d.ts +0 -19
- package/dist/commands/doctor.js +219 -305
- package/dist/commands/exec.js +7 -19
- package/dist/commands/factory.js +26 -2
- package/dist/commands/funnel.js +16 -1
- package/dist/commands/inspect.js +3 -5
- package/dist/commands/menubar.js +117 -34
- package/dist/commands/routines.js +25 -3
- package/dist/commands/secrets-rotate-passphrase.d.ts +17 -0
- package/dist/commands/secrets-rotate-passphrase.js +96 -0
- package/dist/commands/secrets.js +2 -0
- package/dist/commands/sessions.d.ts +7 -1
- package/dist/commands/sessions.js +40 -12
- package/dist/commands/ssh.js +3 -3
- package/dist/commands/usage.d.ts +3 -2
- package/dist/commands/usage.js +2 -9
- package/dist/commands/webhook.js +7 -2
- package/dist/lib/agents.d.ts +31 -1
- package/dist/lib/agents.js +55 -0
- package/dist/lib/command-skills.d.ts +10 -0
- package/dist/lib/command-skills.js +14 -0
- package/dist/lib/commands.js +28 -2
- package/dist/lib/daemon.d.ts +29 -0
- package/dist/lib/daemon.js +75 -6
- package/dist/lib/devices/doctor-findings.d.ts +167 -0
- package/dist/lib/devices/doctor-findings.js +893 -0
- package/dist/lib/devices/fleet-divergence.d.ts +22 -0
- package/dist/lib/devices/fleet-divergence.js +34 -10
- package/dist/lib/devices/fleet-inventory.d.ts +17 -6
- package/dist/lib/devices/fleet-inventory.js +56 -8
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +14 -3
- package/dist/lib/exec.js +41 -8
- package/dist/lib/factory/snapshot.d.ts +78 -0
- package/dist/lib/factory/snapshot.js +209 -0
- package/dist/lib/fs-atomic.d.ts +14 -1
- package/dist/lib/fs-atomic.js +35 -3
- package/dist/lib/funnel.d.ts +1 -0
- package/dist/lib/funnel.js +8 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -2
- package/dist/lib/menubar/install-menubar.d.ts +53 -2
- package/dist/lib/menubar/install-menubar.js +183 -28
- package/dist/lib/platform/process.d.ts +2 -0
- package/dist/lib/platform/process.js +5 -3
- package/dist/lib/project-resources.js +34 -20
- package/dist/lib/resources.d.ts +8 -0
- package/dist/lib/resources.js +34 -1
- package/dist/lib/routines-placement.d.ts +2 -1
- package/dist/lib/routines-placement.js +8 -4
- package/dist/lib/routines.d.ts +57 -1
- package/dist/lib/routines.js +74 -1
- package/dist/lib/runner.d.ts +16 -1
- package/dist/lib/runner.js +58 -16
- package/dist/lib/sandbox.d.ts +2 -0
- package/dist/lib/sandbox.js +38 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/bundles.js +9 -34
- package/dist/lib/secrets/filestore.d.ts +152 -34
- package/dist/lib/secrets/filestore.js +676 -123
- package/dist/lib/secrets/rc-hygiene.d.ts +0 -6
- package/dist/lib/secrets/rc-hygiene.js +0 -24
- package/dist/lib/session/active.d.ts +6 -6
- package/dist/lib/session/active.js +6 -6
- package/dist/lib/session/discover.d.ts +5 -0
- package/dist/lib/session/discover.js +137 -1
- package/dist/lib/session/parse.d.ts +2 -0
- package/dist/lib/session/parse.js +76 -37
- package/dist/lib/session/remote-active.d.ts +4 -1
- package/dist/lib/session/remote-active.js +8 -2
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/session/viewing-in.d.ts +31 -0
- package/dist/lib/session/viewing-in.js +47 -0
- package/dist/lib/staleness/detectors/commands.js +14 -5
- package/dist/lib/staleness/types.d.ts +2 -0
- package/dist/lib/staleness/writers/commands.js +13 -7
- package/dist/lib/state.d.ts +17 -0
- package/dist/lib/state.js +30 -2
- package/dist/lib/triggers/handlers.d.ts +95 -0
- package/dist/lib/triggers/handlers.js +384 -0
- package/dist/lib/triggers/webhook.d.ts +10 -2
- package/dist/lib/triggers/webhook.js +65 -11
- package/dist/lib/usage.d.ts +72 -1
- package/dist/lib/usage.js +21 -27
- package/dist/lib/versions.js +30 -13
- package/package.json +1 -1
|
@@ -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`. */
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Commands detector — mirrors versions.ts
|
|
2
|
+
* Commands detector — mirrors the command dispatch in versions.ts. Inspects the version home,
|
|
3
3
|
* returns command names. Honors the commands-as-skills marker for skills-only
|
|
4
|
-
* agents (
|
|
5
|
-
*
|
|
4
|
+
* agents (Kimi, Codex >= 0.117.0, …), treats the native file as authoritative
|
|
5
|
+
* for dual-write targets (the skill copy is deliberately absent on a name
|
|
6
|
+
* collision), and scans `{agentDir}/<commandsSubdir>/` for native-only targets.
|
|
6
7
|
*/
|
|
7
8
|
import * as fs from 'fs';
|
|
8
9
|
import * as path from 'path';
|
|
9
10
|
import { AGENTS, MANAGED_AGENT_IDS, agentConfigDirName } from '../../agents.js';
|
|
10
|
-
import { shouldInstallCommandAsSkill,
|
|
11
|
+
import { listCommandSkillsInVersion, shouldInstallCommandAsSkill, } from '../../command-skills.js';
|
|
11
12
|
import { lazyAgentMap } from '../writers/lazy-map.js';
|
|
12
13
|
function buildCommandsDetector(agent) {
|
|
13
14
|
return {
|
|
@@ -23,9 +24,17 @@ function buildCommandsDetector(agent) {
|
|
|
23
24
|
if (!fs.existsSync(commandsDir))
|
|
24
25
|
return [];
|
|
25
26
|
const ext = agentConfig.format === 'toml' ? '.toml' : '.md';
|
|
26
|
-
|
|
27
|
+
const nativeCommands = fs.readdirSync(commandsDir)
|
|
27
28
|
.filter(f => f.endsWith(ext))
|
|
28
29
|
.map(f => f.replace(new RegExp(`\\${ext}$`), ''));
|
|
30
|
+
// For a dual-write target the native file is the authoritative record that
|
|
31
|
+
// the command synced. The skill copy is derived, and
|
|
32
|
+
// installCommandSkillToVersion deliberately writes none when a real skill
|
|
33
|
+
// source already owns the name -- requiring both copies reported those
|
|
34
|
+
// commands missing forever and drove an `agents refresh` loop no sync could
|
|
35
|
+
// clear. This also matches `agents doctor`/`prune`, which read the
|
|
36
|
+
// unfiltered listCommandsInVersionHome.
|
|
37
|
+
return nativeCommands;
|
|
29
38
|
},
|
|
30
39
|
};
|
|
31
40
|
}
|
|
@@ -44,6 +44,8 @@ export interface SyncManifest {
|
|
|
44
44
|
v: typeof MANIFEST_VERSION;
|
|
45
45
|
syncedAt: string;
|
|
46
46
|
commands: Record<string, FileEntry>;
|
|
47
|
+
/** Command names the version writer emitted during the preceding full sync. */
|
|
48
|
+
writtenCommands?: string[];
|
|
47
49
|
skills: Record<string, DirEntry>;
|
|
48
50
|
hooks: Record<string, FileEntry>;
|
|
49
51
|
rules: RulesEntry;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Commands writer.
|
|
3
3
|
*
|
|
4
|
-
* Two physical formats,
|
|
4
|
+
* Two physical formats, selected per-(agent, version) at write time. Most
|
|
5
|
+
* agents receive one format; dual-write registry targets receive both:
|
|
5
6
|
*
|
|
6
7
|
* - command-as-skill — fires when `shouldInstallCommandAsSkill(agent, version)`
|
|
7
8
|
* is true. Used for Codex >= 0.117.0 (commands capability ends, skills
|
|
8
9
|
* capability remains) and agents with skills but no native command-file dir
|
|
9
|
-
* such as
|
|
10
|
+
* such as Kimi. Cursor also receives this format in addition to its IDE
|
|
11
|
+
* command file. Writes `{agentDir}/skills/<name>/SKILL.md` with the
|
|
10
12
|
* `agents_command` marker; the agent picks it up as a slash-command equivalent.
|
|
11
13
|
*
|
|
12
14
|
* - native command file — `{agentDir}/<commandsSubdir>/<name>.md` (or .toml
|
|
@@ -23,7 +25,7 @@ import { supports } from '../../capabilities.js';
|
|
|
23
25
|
import { safeJoin } from '../../paths.js';
|
|
24
26
|
import { markdownToToml } from '../../convert.js';
|
|
25
27
|
import { commandAppliesTo, parseCommandMetadata } from '../../commands.js';
|
|
26
|
-
import { installCommandSkillToVersion, shouldInstallCommandAsSkill } from '../../command-skills.js';
|
|
28
|
+
import { installCommandSkillToVersion, shouldAlsoInstallCommandAsSkill, shouldInstallCommandAsSkill, } from '../../command-skills.js';
|
|
27
29
|
import { installGooseCommandToVersion } from '../../goose-commands.js';
|
|
28
30
|
import { resolveCommandSource, trustedSkillRoots } from './sources.js';
|
|
29
31
|
import { lazyAgentMap } from './lazy-map.js';
|
|
@@ -35,6 +37,7 @@ function buildCommandsWriter(agent) {
|
|
|
35
37
|
const agentConfig = AGENTS[agent];
|
|
36
38
|
const agentDir = path.join(versionHome, agentConfigDirName(agent));
|
|
37
39
|
const commandsAsSkills = shouldInstallCommandAsSkill(agent, version);
|
|
40
|
+
const commandsAlsoAsSkills = shouldAlsoInstallCommandAsSkill(agent, version);
|
|
38
41
|
const supportsCommands = supports(agent, 'commands', version).ok;
|
|
39
42
|
// Version-gated agents (e.g. goose skills >= 1.25.0) are registered but
|
|
40
43
|
// may be called at a version too old for both paths — skip gracefully.
|
|
@@ -54,23 +57,25 @@ function buildCommandsWriter(agent) {
|
|
|
54
57
|
const metadata = parseCommandMetadata(srcFile);
|
|
55
58
|
if (!commandAppliesTo(agent, version, metadata).ok)
|
|
56
59
|
continue;
|
|
57
|
-
if (commandsAsSkills) {
|
|
60
|
+
if (commandsAsSkills || commandsAlsoAsSkills) {
|
|
58
61
|
const installed = installCommandSkillToVersion(agentDir, cmd, srcFile, skillRoots);
|
|
62
|
+
// installed.skipped means a real skill source already owns this name,
|
|
63
|
+
// which is a deliberate no-op, not a failure — the native file is still written.
|
|
59
64
|
if (!installed.success)
|
|
60
65
|
continue;
|
|
61
66
|
}
|
|
62
|
-
|
|
67
|
+
if (supportsCommands && agent === 'goose') {
|
|
63
68
|
// Goose: recipe YAML + config.yaml slash_commands entry, not a file copy.
|
|
64
69
|
const installed = installGooseCommandToVersion(versionHome, cmd, srcFile);
|
|
65
70
|
if (!installed.success)
|
|
66
71
|
continue;
|
|
67
72
|
}
|
|
68
|
-
else if (agentConfig.format === 'toml') {
|
|
73
|
+
else if (supportsCommands && agentConfig.format === 'toml') {
|
|
69
74
|
const content = fs.readFileSync(srcFile, 'utf-8');
|
|
70
75
|
const tomlContent = markdownToToml(cmd, content);
|
|
71
76
|
fs.writeFileSync(safeJoin(commandsTarget, `${cmd}.toml`), tomlContent);
|
|
72
77
|
}
|
|
73
|
-
else {
|
|
78
|
+
else if (supportsCommands) {
|
|
74
79
|
fs.copyFileSync(srcFile, safeJoin(commandsTarget, `${cmd}.md`));
|
|
75
80
|
}
|
|
76
81
|
synced.push(cmd);
|
|
@@ -84,6 +89,7 @@ function buildCommandsWriter(agent) {
|
|
|
84
89
|
// Registration covers two cases:
|
|
85
90
|
// - native commands (claude, codex < 0.117.0, grok, etc.) — `commands` cap
|
|
86
91
|
// - commands-as-skills (kimi, codex >= 0.117.0)
|
|
92
|
+
// - dual-write commands plus command-skills (cursor)
|
|
87
93
|
//
|
|
88
94
|
// Agents that have skills but use a NATIVE non-file slash-command system
|
|
89
95
|
// (openclaw → Gateway-based commands) are NOT registered. They declare
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -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>;
|