@phnx-labs/agents-cli 1.20.62 → 1.20.64
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 +62 -0
- package/README.md +19 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +13 -3
- package/dist/commands/exec.js +96 -28
- package/dist/commands/feed.d.ts +4 -0
- package/dist/commands/feed.js +27 -8
- package/dist/commands/funnel.d.ts +5 -0
- package/dist/commands/funnel.js +62 -0
- package/dist/commands/hosts.js +42 -0
- package/dist/commands/lease.d.ts +23 -0
- package/dist/commands/lease.js +201 -0
- package/dist/commands/mailboxes.d.ts +20 -0
- package/dist/commands/mailboxes.js +390 -0
- package/dist/commands/repo.d.ts +4 -4
- package/dist/commands/repo.js +30 -19
- package/dist/commands/routines.js +92 -29
- package/dist/commands/sessions-export.d.ts +2 -0
- package/dist/commands/sessions-export.js +279 -0
- package/dist/commands/sessions-import.d.ts +2 -0
- package/dist/commands/sessions-import.js +230 -0
- package/dist/commands/sessions-sync.d.ts +1 -0
- package/dist/commands/sessions-sync.js +16 -2
- package/dist/commands/sessions.js +12 -1
- package/dist/commands/setup.js +9 -0
- package/dist/commands/ssh.js +170 -5
- package/dist/commands/sync-provision.d.ts +23 -0
- package/dist/commands/sync-provision.js +107 -0
- package/dist/commands/usage.d.ts +2 -0
- package/dist/commands/usage.js +7 -2
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/webhook.d.ts +9 -0
- package/dist/commands/webhook.js +93 -0
- package/dist/index.js +7 -2
- package/dist/lib/agents.d.ts +44 -0
- package/dist/lib/agents.js +85 -35
- package/dist/lib/browser/drivers/ssh.js +19 -2
- package/dist/lib/browser/ipc.js +5 -4
- package/dist/lib/browser/profiles.d.ts +13 -0
- package/dist/lib/browser/profiles.js +17 -0
- package/dist/lib/browser/service.d.ts +12 -1
- package/dist/lib/browser/service.js +48 -13
- package/dist/lib/browser/sessions-list.d.ts +40 -0
- package/dist/lib/browser/sessions-list.js +190 -0
- package/dist/lib/comms-render.d.ts +37 -0
- package/dist/lib/comms-render.js +89 -0
- package/dist/lib/crabbox/cli.d.ts +72 -0
- package/dist/lib/crabbox/cli.js +158 -9
- package/dist/lib/crabbox/runtimes.d.ts +13 -0
- package/dist/lib/crabbox/runtimes.js +24 -0
- package/dist/lib/daemon.js +8 -1
- package/dist/lib/devices/fleet.d.ts +62 -0
- package/dist/lib/devices/fleet.js +128 -0
- package/dist/lib/devices/health.d.ts +77 -0
- package/dist/lib/devices/health.js +186 -0
- package/dist/lib/funnel.d.ts +5 -0
- package/dist/lib/funnel.js +23 -0
- package/dist/lib/git.d.ts +21 -5
- package/dist/lib/git.js +64 -14
- package/dist/lib/hosts/credentials.d.ts +28 -0
- package/dist/lib/hosts/credentials.js +48 -0
- package/dist/lib/hosts/dispatch.d.ts +25 -0
- package/dist/lib/hosts/dispatch.js +68 -2
- package/dist/lib/hosts/passthrough.d.ts +13 -10
- package/dist/lib/hosts/passthrough.js +119 -29
- package/dist/lib/mailbox-gc.js +4 -16
- package/dist/lib/mailbox.d.ts +39 -0
- package/dist/lib/mailbox.js +112 -0
- package/dist/lib/migrate.d.ts +12 -0
- package/dist/lib/migrate.js +55 -1
- package/dist/lib/paths.d.ts +13 -0
- package/dist/lib/paths.js +26 -4
- package/dist/lib/routines.d.ts +50 -12
- package/dist/lib/routines.js +82 -27
- package/dist/lib/runner.js +255 -13
- package/dist/lib/sandbox.d.ts +9 -1
- package/dist/lib/sandbox.js +11 -2
- package/dist/lib/session/bundle.d.ts +150 -0
- package/dist/lib/session/bundle.js +189 -0
- package/dist/lib/session/remote-bundle.d.ts +12 -0
- package/dist/lib/session/remote-bundle.js +61 -0
- package/dist/lib/session/sync/agents.d.ts +56 -6
- package/dist/lib/session/sync/agents.js +0 -0
- package/dist/lib/session/sync/config.d.ts +8 -0
- package/dist/lib/session/sync/config.js +6 -1
- package/dist/lib/session/sync/manifest.d.ts +14 -3
- package/dist/lib/session/sync/manifest.js +4 -0
- package/dist/lib/session/sync/provision.d.ts +49 -0
- package/dist/lib/session/sync/provision.js +91 -0
- package/dist/lib/session/sync/sync.d.ts +26 -2
- package/dist/lib/session/sync/sync.js +192 -69
- package/dist/lib/session/sync/transcript-crypto.d.ts +77 -0
- package/dist/lib/session/sync/transcript-crypto.js +147 -0
- package/dist/lib/ssh-tunnel.js +13 -1
- package/dist/lib/staleness/detectors/subagents.d.ts +5 -0
- package/dist/lib/staleness/detectors/subagents.js +5 -192
- package/dist/lib/staleness/writers/subagents.d.ts +10 -0
- package/dist/lib/staleness/writers/subagents.js +11 -102
- package/dist/lib/startup/command-registry.d.ts +4 -0
- package/dist/lib/startup/command-registry.js +16 -0
- package/dist/lib/state.d.ts +10 -2
- package/dist/lib/state.js +14 -2
- package/dist/lib/subagents-registry.d.ts +85 -0
- package/dist/lib/subagents-registry.js +393 -0
- package/dist/lib/subagents.d.ts +8 -8
- package/dist/lib/subagents.js +32 -663
- package/dist/lib/sync-umbrella.d.ts +1 -0
- package/dist/lib/sync-umbrella.js +14 -3
- package/dist/lib/triggers/webhook.d.ts +70 -27
- package/dist/lib/triggers/webhook.js +264 -43
- package/dist/lib/types.d.ts +9 -0
- package/dist/lib/usage.d.ts +42 -3
- package/dist/lib/usage.js +162 -22
- package/package.json +1 -1
package/dist/lib/routines.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface JobAllowConfig {
|
|
|
18
18
|
export type GithubTriggerEvent = 'pull_request' | 'push' | 'issue_comment' | 'workflow_run';
|
|
19
19
|
/** Canonical set of accepted GitHub trigger events — single source for validation. */
|
|
20
20
|
export declare const GITHUB_TRIGGER_EVENTS: readonly GithubTriggerEvent[];
|
|
21
|
+
/** Linear webhook resource types a routine can be triggered by. */
|
|
22
|
+
export type LinearTriggerEvent = 'Issue' | 'IssueLabel' | 'Comment' | 'Project' | 'Cycle';
|
|
23
|
+
/** Canonical set of accepted Linear trigger events — single source for validation. */
|
|
24
|
+
export declare const LINEAR_TRIGGER_EVENTS: readonly LinearTriggerEvent[];
|
|
21
25
|
/**
|
|
22
26
|
* Map a user-facing `--on` alias to a canonical GitHub trigger event.
|
|
23
27
|
* Accepts the canonical names plus friendly shortcuts (e.g. `pr`, `pr_opened`
|
|
@@ -26,11 +30,11 @@ export declare const GITHUB_TRIGGER_EVENTS: readonly GithubTriggerEvent[];
|
|
|
26
30
|
export declare function normalizeTriggerEvent(input: string): GithubTriggerEvent | null;
|
|
27
31
|
/**
|
|
28
32
|
* Event-based fire condition for a routine — an alternative (or complement) to
|
|
29
|
-
* `schedule`.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
33
|
+
* `schedule`. Incoming webhooks whose source-specific filters match fire the job
|
|
34
|
+
* through the same dispatch path a cron fire uses. See
|
|
35
|
+
* `src/lib/triggers/webhook.ts`.
|
|
32
36
|
*/
|
|
33
|
-
export interface
|
|
37
|
+
export interface GithubJobTrigger {
|
|
34
38
|
type: 'github_event';
|
|
35
39
|
event: GithubTriggerEvent;
|
|
36
40
|
/** `owner/name` — when set, only payloads for this repo match. */
|
|
@@ -38,6 +42,17 @@ export interface JobTrigger {
|
|
|
38
42
|
/** git branch (ref short name) — when set, only payloads for this branch match. */
|
|
39
43
|
branch?: string;
|
|
40
44
|
}
|
|
45
|
+
export interface LinearJobTrigger {
|
|
46
|
+
type: 'linear_event';
|
|
47
|
+
event: LinearTriggerEvent;
|
|
48
|
+
/** Linear action, e.g. `create`, `update`, `remove`. */
|
|
49
|
+
action?: string;
|
|
50
|
+
/** Issue identifier prefix such as `RUSH`; useful when one webhook spans teams. */
|
|
51
|
+
teamKey?: string;
|
|
52
|
+
/** Required issue label name. */
|
|
53
|
+
label?: string;
|
|
54
|
+
}
|
|
55
|
+
export type JobTrigger = GithubJobTrigger | LinearJobTrigger;
|
|
41
56
|
/**
|
|
42
57
|
* Full configuration for a routine (persisted as YAML).
|
|
43
58
|
*
|
|
@@ -51,8 +66,16 @@ export interface JobConfig {
|
|
|
51
66
|
schedule?: string;
|
|
52
67
|
/** Event/webhook fire condition. Optional when `schedule` is set. */
|
|
53
68
|
trigger?: JobTrigger;
|
|
54
|
-
agent
|
|
69
|
+
/** Which agent runs the routine. Optional — omitted for `workflow`/`command` routines. Exactly one of agent/workflow/command must be set. */
|
|
70
|
+
agent?: AgentId;
|
|
55
71
|
workflow?: string;
|
|
72
|
+
/**
|
|
73
|
+
* A plain shell command run directly instead of an agent/workflow — no LLM,
|
|
74
|
+
* no auth, no rotation, no tokens, no sandbox overlay. For deterministic
|
|
75
|
+
* housekeeping routines (version-check, `npm i -g`, `git pull`, notify).
|
|
76
|
+
* Mutually exclusive with `agent` and `workflow`.
|
|
77
|
+
*/
|
|
78
|
+
command?: string;
|
|
56
79
|
mode: 'plan' | 'edit' | 'auto' | 'skip' | 'full';
|
|
57
80
|
effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
|
|
58
81
|
timeout: string;
|
|
@@ -89,8 +112,10 @@ export interface JobConfig {
|
|
|
89
112
|
export interface RunMeta {
|
|
90
113
|
jobName: string;
|
|
91
114
|
runId: string;
|
|
92
|
-
agent
|
|
115
|
+
agent?: AgentId;
|
|
93
116
|
workflow?: string;
|
|
117
|
+
/** The shell command that ran, for command-mode routines (no agent). */
|
|
118
|
+
command?: string;
|
|
94
119
|
pid: number | null;
|
|
95
120
|
/** Process birth time (epoch ms) recorded at spawn for pid-reuse detection. */
|
|
96
121
|
spawnedAt?: number;
|
|
@@ -126,16 +151,20 @@ export interface JobEligibilityResult {
|
|
|
126
151
|
*/
|
|
127
152
|
export declare function checkJobDeviceEligibility(config: Pick<JobConfig, 'name' | 'devices'>): JobEligibilityResult | null;
|
|
128
153
|
/**
|
|
129
|
-
* List all job configs, scanning project > user routine dirs.
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
154
|
+
* List all job configs, scanning project > user > system routine dirs.
|
|
155
|
+
* Higher layers shadow lower ones of the same name (first-seen wins): a project
|
|
156
|
+
* routine shadows a user routine, and a user routine shadows a built-in system
|
|
157
|
+
* routine (`~/.agents/.system/routines/`, shipped via gh:phnx-labs/.agents-system).
|
|
158
|
+
* Project discovery is opt-in via `cwd`; the daemon (which calls `listJobs()`
|
|
159
|
+
* with no argument) sees user + system routines, so a built-in routine fires for
|
|
160
|
+
* every install unless the user overrides or disables it by name.
|
|
133
161
|
*/
|
|
134
162
|
export declare function listJobs(cwd?: string): JobConfig[];
|
|
135
163
|
/**
|
|
136
|
-
* Read a single job config by name, checking project > user.
|
|
164
|
+
* Read a single job config by name, checking project > user > system.
|
|
137
165
|
* Project discovery is opt-in via `cwd`; daemon callers pass no argument and
|
|
138
|
-
*
|
|
166
|
+
* resolve user + system routines (a user routine of the same name shadows a
|
|
167
|
+
* built-in system routine).
|
|
139
168
|
*/
|
|
140
169
|
export declare function readJob(name: string, cwd?: string): JobConfig | null;
|
|
141
170
|
/** Write a job config to disk, omitting fields that match defaults.
|
|
@@ -186,6 +215,15 @@ export declare function getLatestRun(jobName: string): RunMeta | null;
|
|
|
186
215
|
export declare function writeRunMeta(meta: RunMeta): void;
|
|
187
216
|
/** Read run metadata from disk. Returns null if missing or corrupt. */
|
|
188
217
|
export declare function readRunMeta(jobName: string, runId: string): RunMeta | null;
|
|
218
|
+
/**
|
|
219
|
+
* Runs directory for a single job, with the (untrusted) job name contained to a
|
|
220
|
+
* single segment beneath the runs dir — same guard as `getJobHomePath`. The name
|
|
221
|
+
* comes from routine YAML and can arrive via a synced config repo; every runs-dir
|
|
222
|
+
* sink (run dir, meta read/write, last-report read) routes through here so a
|
|
223
|
+
* crafted `name` like `../../../../tmp/x` can't `mkdirSync`/write `stdout.log`,
|
|
224
|
+
* `meta.json`, or `report.md` outside `~/.agents/.history/runs`.
|
|
225
|
+
*/
|
|
226
|
+
export declare function getJobRunsDir(jobName: string): string;
|
|
189
227
|
/** Get the filesystem path for a specific run's directory. */
|
|
190
228
|
export declare function getRunDir(jobName: string, runId: string): string;
|
|
191
229
|
/** Discover routine YAML files in a repository's routines/ directory. */
|
package/dist/lib/routines.js
CHANGED
|
@@ -10,8 +10,8 @@ import * as fs from 'fs';
|
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import * as yaml from 'yaml';
|
|
12
12
|
import { Cron } from 'croner';
|
|
13
|
-
import { getRoutinesDir, getRunsDir, ensureAgentsDir, getProjectRoutinesDir } from './state.js';
|
|
14
|
-
import { safeJoin } from './paths.js';
|
|
13
|
+
import { getRoutinesDir, getSystemRoutinesDir, getRunsDir, ensureAgentsDir, getProjectRoutinesDir } from './state.js';
|
|
14
|
+
import { safeJoin, isSafeSegmentName } from './paths.js';
|
|
15
15
|
import { atomicWriteFileSync } from './fs-atomic.js';
|
|
16
16
|
import { ALL_AGENT_IDS } from './agents.js';
|
|
17
17
|
import { machineId, normalizeHost } from './machine-id.js';
|
|
@@ -22,6 +22,14 @@ export const GITHUB_TRIGGER_EVENTS = [
|
|
|
22
22
|
'issue_comment',
|
|
23
23
|
'workflow_run',
|
|
24
24
|
];
|
|
25
|
+
/** Canonical set of accepted Linear trigger events — single source for validation. */
|
|
26
|
+
export const LINEAR_TRIGGER_EVENTS = [
|
|
27
|
+
'Issue',
|
|
28
|
+
'IssueLabel',
|
|
29
|
+
'Comment',
|
|
30
|
+
'Project',
|
|
31
|
+
'Cycle',
|
|
32
|
+
];
|
|
25
33
|
/**
|
|
26
34
|
* Map a user-facing `--on` alias to a canonical GitHub trigger event.
|
|
27
35
|
* Accepts the canonical names plus friendly shortcuts (e.g. `pr`, `pr_opened`
|
|
@@ -79,10 +87,13 @@ const JOB_DEFAULTS = {
|
|
|
79
87
|
enabled: true,
|
|
80
88
|
};
|
|
81
89
|
/**
|
|
82
|
-
* List all job configs, scanning project > user routine dirs.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
90
|
+
* List all job configs, scanning project > user > system routine dirs.
|
|
91
|
+
* Higher layers shadow lower ones of the same name (first-seen wins): a project
|
|
92
|
+
* routine shadows a user routine, and a user routine shadows a built-in system
|
|
93
|
+
* routine (`~/.agents/.system/routines/`, shipped via gh:phnx-labs/.agents-system).
|
|
94
|
+
* Project discovery is opt-in via `cwd`; the daemon (which calls `listJobs()`
|
|
95
|
+
* with no argument) sees user + system routines, so a built-in routine fires for
|
|
96
|
+
* every install unless the user overrides or disables it by name.
|
|
86
97
|
*/
|
|
87
98
|
export function listJobs(cwd) {
|
|
88
99
|
ensureAgentsDir();
|
|
@@ -95,6 +106,7 @@ export function listJobs(cwd) {
|
|
|
95
106
|
dirs.push(projectDir);
|
|
96
107
|
}
|
|
97
108
|
dirs.push(getRoutinesDir());
|
|
109
|
+
dirs.push(getSystemRoutinesDir());
|
|
98
110
|
for (const dir of dirs) {
|
|
99
111
|
if (!fs.existsSync(dir))
|
|
100
112
|
continue;
|
|
@@ -112,9 +124,10 @@ export function listJobs(cwd) {
|
|
|
112
124
|
return jobs;
|
|
113
125
|
}
|
|
114
126
|
/**
|
|
115
|
-
* Read a single job config by name, checking project > user.
|
|
127
|
+
* Read a single job config by name, checking project > user > system.
|
|
116
128
|
* Project discovery is opt-in via `cwd`; daemon callers pass no argument and
|
|
117
|
-
*
|
|
129
|
+
* resolve user + system routines (a user routine of the same name shadows a
|
|
130
|
+
* built-in system routine).
|
|
118
131
|
*/
|
|
119
132
|
export function readJob(name, cwd) {
|
|
120
133
|
ensureAgentsDir();
|
|
@@ -125,6 +138,7 @@ export function readJob(name, cwd) {
|
|
|
125
138
|
dirs.push(projectDir);
|
|
126
139
|
}
|
|
127
140
|
dirs.push(getRoutinesDir());
|
|
141
|
+
dirs.push(getSystemRoutinesDir());
|
|
128
142
|
for (const dir of dirs) {
|
|
129
143
|
for (const ext of ['.yml', '.yaml']) {
|
|
130
144
|
const filePath = safeJoin(dir, name + ext);
|
|
@@ -260,6 +274,13 @@ export function validateJob(config) {
|
|
|
260
274
|
if (!config.name || typeof config.name !== 'string') {
|
|
261
275
|
errors.push('name is required');
|
|
262
276
|
}
|
|
277
|
+
else if (!isSafeSegmentName(config.name)) {
|
|
278
|
+
// The name becomes a filesystem path segment (overlay HOME under the
|
|
279
|
+
// routines dir). Reject separators, '.'/'..', and null bytes so a synced
|
|
280
|
+
// config can't traverse out of ~/.agents/routines.
|
|
281
|
+
errors.push(`invalid name ${JSON.stringify(config.name)}: must be a single path segment ` +
|
|
282
|
+
`(no '/', '\\\\', or null bytes, and not '.' or '..')`);
|
|
283
|
+
}
|
|
263
284
|
const hasSchedule = Boolean(config.schedule && typeof config.schedule === 'string');
|
|
264
285
|
const hasTrigger = config.trigger !== undefined;
|
|
265
286
|
if (!hasSchedule && !hasTrigger) {
|
|
@@ -284,11 +305,16 @@ export function validateJob(config) {
|
|
|
284
305
|
}
|
|
285
306
|
const hasAgent = Boolean(config.agent && typeof config.agent === 'string');
|
|
286
307
|
const hasWorkflow = Boolean(config.workflow && typeof config.workflow === 'string');
|
|
287
|
-
|
|
288
|
-
|
|
308
|
+
const hasCommand = Boolean(config.command && typeof config.command === 'string');
|
|
309
|
+
const set = [hasAgent, hasWorkflow, hasCommand].filter(Boolean).length;
|
|
310
|
+
if (set === 0) {
|
|
311
|
+
errors.push('exactly one of agent, workflow, or command is required');
|
|
312
|
+
}
|
|
313
|
+
else if (set > 1) {
|
|
314
|
+
errors.push('exactly one of agent, workflow, or command may be set (not more)');
|
|
289
315
|
}
|
|
290
|
-
|
|
291
|
-
errors.push('
|
|
316
|
+
if (config.command !== undefined && (typeof config.command !== 'string' || config.command.trim() === '')) {
|
|
317
|
+
errors.push('command must be a non-empty shell command string');
|
|
292
318
|
}
|
|
293
319
|
if (hasAgent && config.agent && !ALL_AGENT_IDS.includes(config.agent)) {
|
|
294
320
|
errors.push(`agent must be one of: ${ALL_AGENT_IDS.join(', ')}`);
|
|
@@ -322,7 +348,8 @@ export function validateJob(config) {
|
|
|
322
348
|
if (config.effort && !['low', 'medium', 'high', 'xhigh', 'max', 'auto'].includes(config.effort)) {
|
|
323
349
|
errors.push('effort must be low, medium, high, xhigh, max, or auto');
|
|
324
350
|
}
|
|
325
|
-
|
|
351
|
+
// command routines run a plain shell and never build a prompt; agent/workflow routines require one.
|
|
352
|
+
if (!hasCommand && (!config.prompt || typeof config.prompt !== 'string')) {
|
|
326
353
|
errors.push('prompt is required');
|
|
327
354
|
}
|
|
328
355
|
if (config.timeout && !parseTimeout(config.timeout)) {
|
|
@@ -358,17 +385,35 @@ export function validateTrigger(trigger) {
|
|
|
358
385
|
return ['trigger must be an object'];
|
|
359
386
|
}
|
|
360
387
|
const t = trigger;
|
|
361
|
-
if (t.type !== 'github_event') {
|
|
362
|
-
errors.push("trigger.type must be 'github_event'");
|
|
388
|
+
if (t.type !== 'github_event' && t.type !== 'linear_event') {
|
|
389
|
+
errors.push("trigger.type must be 'github_event' or 'linear_event'");
|
|
390
|
+
return errors;
|
|
391
|
+
}
|
|
392
|
+
if (t.type === 'github_event') {
|
|
393
|
+
const github = t;
|
|
394
|
+
if (!github.event || !GITHUB_TRIGGER_EVENTS.includes(github.event)) {
|
|
395
|
+
errors.push(`trigger.event must be one of: ${GITHUB_TRIGGER_EVENTS.join(', ')}`);
|
|
396
|
+
}
|
|
397
|
+
if (github.repo !== undefined && (typeof github.repo !== 'string' || !/^[^/\s]+\/[^/\s]+$/.test(github.repo))) {
|
|
398
|
+
errors.push('trigger.repo must be in owner/name form');
|
|
399
|
+
}
|
|
400
|
+
if (github.branch !== undefined && typeof github.branch !== 'string') {
|
|
401
|
+
errors.push('trigger.branch must be a string');
|
|
402
|
+
}
|
|
403
|
+
return errors;
|
|
363
404
|
}
|
|
364
|
-
|
|
365
|
-
|
|
405
|
+
const linear = t;
|
|
406
|
+
if (!linear.event || !LINEAR_TRIGGER_EVENTS.includes(linear.event)) {
|
|
407
|
+
errors.push(`trigger.event must be one of: ${LINEAR_TRIGGER_EVENTS.join(', ')}`);
|
|
366
408
|
}
|
|
367
|
-
if (
|
|
368
|
-
errors.push('trigger.
|
|
409
|
+
if (linear.action !== undefined && typeof linear.action !== 'string') {
|
|
410
|
+
errors.push('trigger.action must be a string');
|
|
369
411
|
}
|
|
370
|
-
if (
|
|
371
|
-
errors.push('trigger.
|
|
412
|
+
if (linear.teamKey !== undefined && (typeof linear.teamKey !== 'string' || !/^[A-Z][A-Z0-9]*$/.test(linear.teamKey))) {
|
|
413
|
+
errors.push('trigger.teamKey must be an uppercase Linear team key');
|
|
414
|
+
}
|
|
415
|
+
if (linear.label !== undefined && typeof linear.label !== 'string') {
|
|
416
|
+
errors.push('trigger.label must be a string');
|
|
372
417
|
}
|
|
373
418
|
return errors;
|
|
374
419
|
}
|
|
@@ -413,7 +458,7 @@ export function resolveJobPrompt(config) {
|
|
|
413
458
|
// Last report (special handling)
|
|
414
459
|
const latestRun = getLatestRun(config.name);
|
|
415
460
|
if (latestRun) {
|
|
416
|
-
const reportPath = path.join(
|
|
461
|
+
const reportPath = path.join(getJobRunsDir(config.name), latestRun.runId, 'report.md');
|
|
417
462
|
if (fs.existsSync(reportPath)) {
|
|
418
463
|
const report = fs.readFileSync(reportPath, 'utf-8');
|
|
419
464
|
prompt = prompt.replace(/\{last_report\}/g, report);
|
|
@@ -449,8 +494,7 @@ export function parseTimeout(timeout) {
|
|
|
449
494
|
}
|
|
450
495
|
/** List all run metadata entries for a job, sorted chronologically. */
|
|
451
496
|
export function listRuns(jobName) {
|
|
452
|
-
const
|
|
453
|
-
const jobRunsDir = path.join(runsDir, jobName);
|
|
497
|
+
const jobRunsDir = getJobRunsDir(jobName);
|
|
454
498
|
if (!fs.existsSync(jobRunsDir))
|
|
455
499
|
return [];
|
|
456
500
|
const entries = fs.readdirSync(jobRunsDir, { withFileTypes: true })
|
|
@@ -473,13 +517,13 @@ export function getLatestRun(jobName) {
|
|
|
473
517
|
/** Persist run metadata to its run directory as meta.json. */
|
|
474
518
|
export function writeRunMeta(meta) {
|
|
475
519
|
ensureAgentsDir();
|
|
476
|
-
const runDir = path.join(
|
|
520
|
+
const runDir = path.join(getJobRunsDir(meta.jobName), meta.runId);
|
|
477
521
|
fs.mkdirSync(runDir, { recursive: true });
|
|
478
522
|
fs.writeFileSync(path.join(runDir, 'meta.json'), JSON.stringify(meta, null, 2), 'utf-8');
|
|
479
523
|
}
|
|
480
524
|
/** Read run metadata from disk. Returns null if missing or corrupt. */
|
|
481
525
|
export function readRunMeta(jobName, runId) {
|
|
482
|
-
const metaPath = path.join(
|
|
526
|
+
const metaPath = path.join(getJobRunsDir(jobName), runId, 'meta.json');
|
|
483
527
|
if (!fs.existsSync(metaPath))
|
|
484
528
|
return null;
|
|
485
529
|
try {
|
|
@@ -489,9 +533,20 @@ export function readRunMeta(jobName, runId) {
|
|
|
489
533
|
return null;
|
|
490
534
|
}
|
|
491
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* Runs directory for a single job, with the (untrusted) job name contained to a
|
|
538
|
+
* single segment beneath the runs dir — same guard as `getJobHomePath`. The name
|
|
539
|
+
* comes from routine YAML and can arrive via a synced config repo; every runs-dir
|
|
540
|
+
* sink (run dir, meta read/write, last-report read) routes through here so a
|
|
541
|
+
* crafted `name` like `../../../../tmp/x` can't `mkdirSync`/write `stdout.log`,
|
|
542
|
+
* `meta.json`, or `report.md` outside `~/.agents/.history/runs`.
|
|
543
|
+
*/
|
|
544
|
+
export function getJobRunsDir(jobName) {
|
|
545
|
+
return safeJoin(getRunsDir(), jobName);
|
|
546
|
+
}
|
|
492
547
|
/** Get the filesystem path for a specific run's directory. */
|
|
493
548
|
export function getRunDir(jobName, runId) {
|
|
494
|
-
return path.join(
|
|
549
|
+
return path.join(getJobRunsDir(jobName), runId);
|
|
495
550
|
}
|
|
496
551
|
/** Discover routine YAML files in a repository's routines/ directory. */
|
|
497
552
|
export function discoverJobsFromRepo(repoPath) {
|