@phnx-labs/agents-cli 1.14.7 → 1.16.0
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 +78 -39
- package/README.md +74 -7
- package/dist/commands/alias.js +2 -2
- package/dist/commands/beta.js +6 -1
- package/dist/commands/browser-picker.d.ts +21 -0
- package/dist/commands/browser-picker.js +114 -0
- package/dist/commands/browser.js +546 -75
- package/dist/commands/commands.js +72 -22
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/exec.js +9 -2
- package/dist/commands/fork.js +2 -2
- package/dist/commands/hooks.js +71 -26
- package/dist/commands/mcp.js +85 -43
- package/dist/commands/plugins.js +48 -15
- package/dist/commands/prune.d.ts +0 -20
- package/dist/commands/prune.js +291 -16
- package/dist/commands/pull.js +3 -3
- package/dist/commands/repo.js +1 -1
- package/dist/commands/routines.js +2 -2
- package/dist/commands/secrets.js +37 -1
- package/dist/commands/sessions.js +62 -19
- package/dist/commands/{init.d.ts → setup.d.ts} +7 -6
- package/dist/commands/{init.js → setup.js} +32 -21
- package/dist/commands/skills.js +60 -19
- package/dist/commands/subagents.js +41 -13
- package/dist/commands/teams.js +2 -3
- package/dist/commands/usage.js +6 -0
- package/dist/commands/utils.d.ts +16 -0
- package/dist/commands/utils.js +32 -0
- package/dist/commands/versions.js +8 -6
- package/dist/commands/view.js +61 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -20
- package/dist/lib/agents.js +2 -2
- package/dist/lib/auto-pull-worker.js +2 -3
- package/dist/lib/auto-pull.js +2 -2
- package/dist/lib/browser/cdp.d.ts +7 -1
- package/dist/lib/browser/cdp.js +29 -1
- package/dist/lib/browser/chrome.js +6 -3
- package/dist/lib/browser/devices.d.ts +4 -0
- package/dist/lib/browser/devices.js +27 -0
- package/dist/lib/browser/drivers/local.js +9 -4
- package/dist/lib/browser/drivers/ssh.d.ts +1 -0
- package/dist/lib/browser/drivers/ssh.js +32 -4
- package/dist/lib/browser/ipc.js +145 -23
- package/dist/lib/browser/profiles.d.ts +5 -2
- package/dist/lib/browser/profiles.js +77 -37
- package/dist/lib/browser/service.d.ts +84 -13
- package/dist/lib/browser/service.js +806 -122
- package/dist/lib/browser/types.d.ts +81 -3
- package/dist/lib/browser/types.js +16 -0
- package/dist/lib/cloud/rush.js +2 -2
- package/dist/lib/cloud/store.js +2 -2
- package/dist/lib/commands.d.ts +1 -0
- package/dist/lib/commands.js +6 -2
- package/dist/lib/daemon.js +6 -7
- package/dist/lib/doctor-diff.js +4 -4
- package/dist/lib/events.d.ts +94 -1
- package/dist/lib/events.js +264 -6
- package/dist/lib/exec.js +16 -10
- package/dist/lib/hooks.d.ts +11 -7
- package/dist/lib/hooks.js +125 -49
- package/dist/lib/migrate.d.ts +1 -1
- package/dist/lib/migrate.js +1178 -21
- package/dist/lib/models.js +2 -2
- package/dist/lib/permissions.d.ts +14 -11
- package/dist/lib/permissions.js +46 -42
- package/dist/lib/plugins.d.ts +30 -1
- package/dist/lib/plugins.js +75 -3
- package/dist/lib/pty-server.js +9 -10
- package/dist/lib/resources/hooks.d.ts +5 -1
- package/dist/lib/resources/hooks.js +21 -4
- package/dist/lib/rotate.js +3 -4
- package/dist/lib/routines.d.ts +15 -0
- package/dist/lib/routines.js +68 -0
- package/dist/lib/runner.js +9 -5
- package/dist/lib/secrets/index.d.ts +14 -11
- package/dist/lib/secrets/index.js +49 -21
- package/dist/lib/secrets/linux.d.ts +27 -0
- package/dist/lib/secrets/linux.js +161 -0
- package/dist/lib/session/active.d.ts +3 -0
- package/dist/lib/session/active.js +92 -6
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +34 -3
- package/dist/lib/session/discover.js +30 -15
- package/dist/lib/session/team-filter.js +2 -2
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +6 -6
- package/dist/lib/skills.js +6 -2
- package/dist/lib/state.d.ts +86 -14
- package/dist/lib/state.js +150 -23
- package/dist/lib/subagents.d.ts +28 -0
- package/dist/lib/subagents.js +98 -1
- package/dist/lib/sync-manifest.d.ts +1 -1
- package/dist/lib/sync-manifest.js +3 -3
- package/dist/lib/teams/persistence.js +15 -5
- package/dist/lib/teams/registry.js +2 -2
- package/dist/lib/types.d.ts +32 -3
- package/dist/lib/types.js +3 -3
- package/dist/lib/usage.d.ts +1 -1
- package/dist/lib/usage.js +15 -48
- package/dist/lib/versions.js +31 -21
- package/package.json +1 -1
- package/scripts/postinstall.js +37 -9
package/dist/lib/events.js
CHANGED
|
@@ -3,15 +3,35 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Structured JSONL logs at ~/.agents/logs/events-YYYY-MM-DD.jsonl
|
|
5
5
|
* with automatic daily rotation and rich metadata for debugging/auditing.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Rich metadata: hostname, platform, arch, pid, timezone
|
|
9
|
+
* - Timing helpers: measure operation duration automatically
|
|
10
|
+
* - Truncation: long inputs/outputs are trimmed with ellipsis
|
|
11
|
+
* - Permissions: logs dir is 0700, files are 0600 (owner-only)
|
|
12
|
+
* - Performance tracking: withTiming() wrapper for any async function
|
|
6
13
|
*/
|
|
7
14
|
import * as fs from 'fs';
|
|
8
15
|
import * as path from 'path';
|
|
9
16
|
import * as os from 'os';
|
|
10
17
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
const LOGS_DIR = path.join(
|
|
18
|
+
// Logs live under the cache bucket — they're regenerable telemetry.
|
|
19
|
+
const LOGS_DIR = path.join(os.homedir(), '.agents', '.cache', 'logs');
|
|
13
20
|
/** Default retention period in days. */
|
|
14
21
|
const DEFAULT_RETENTION_DAYS = 30;
|
|
22
|
+
/** Default max length for truncated strings. */
|
|
23
|
+
const DEFAULT_TRUNCATE_LENGTH = 500;
|
|
24
|
+
/** Environment variable to disable event logging. */
|
|
25
|
+
const DISABLE_ENV_VAR = 'AGENTS_DISABLE_EVENT_LOG';
|
|
26
|
+
/** Check if audit logging is disabled via environment variable. */
|
|
27
|
+
function isDisabled() {
|
|
28
|
+
const val = process.env[DISABLE_ENV_VAR];
|
|
29
|
+
return val === '1' || val === 'true';
|
|
30
|
+
}
|
|
31
|
+
/** Directory permissions (owner read/write/execute only). */
|
|
32
|
+
const DIR_MODE = 0o700;
|
|
33
|
+
/** File permissions (owner read/write only). */
|
|
34
|
+
const FILE_MODE = 0o600;
|
|
15
35
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
16
36
|
function getTimezoneOffset() {
|
|
17
37
|
const offset = new Date().getTimezoneOffset();
|
|
@@ -36,8 +56,48 @@ function getLogFilePath(date = new Date()) {
|
|
|
36
56
|
}
|
|
37
57
|
function ensureLogsDir() {
|
|
38
58
|
if (!fs.existsSync(LOGS_DIR)) {
|
|
39
|
-
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
59
|
+
fs.mkdirSync(LOGS_DIR, { recursive: true, mode: DIR_MODE });
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Ensure permissions are correct on existing dir
|
|
63
|
+
try {
|
|
64
|
+
fs.chmodSync(LOGS_DIR, DIR_MODE);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// May fail if not owner
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// ─── Truncation ───────────────────────────────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Truncate a string to maxLength, adding ellipsis if truncated.
|
|
74
|
+
* Returns undefined for null/undefined input.
|
|
75
|
+
*/
|
|
76
|
+
export function truncate(str, maxLength = DEFAULT_TRUNCATE_LENGTH) {
|
|
77
|
+
if (str == null)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (str.length <= maxLength)
|
|
80
|
+
return str;
|
|
81
|
+
return str.slice(0, maxLength - 3) + '...';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Truncate all string values in a payload object.
|
|
85
|
+
*/
|
|
86
|
+
function truncatePayload(payload, maxLength = DEFAULT_TRUNCATE_LENGTH) {
|
|
87
|
+
const result = {};
|
|
88
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
89
|
+
if (typeof value === 'string') {
|
|
90
|
+
result[key] = truncate(value, maxLength);
|
|
91
|
+
}
|
|
92
|
+
else if (Array.isArray(value)) {
|
|
93
|
+
// Truncate array to first 10 items, truncate each string item
|
|
94
|
+
result[key] = value.slice(0, 10).map(v => typeof v === 'string' ? truncate(v, maxLength) : v);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
result[key] = value;
|
|
98
|
+
}
|
|
40
99
|
}
|
|
100
|
+
return result;
|
|
41
101
|
}
|
|
42
102
|
// ─── Core API ─────────────────────────────────────────────────────────────────
|
|
43
103
|
/**
|
|
@@ -47,6 +107,8 @@ function ensureLogsDir() {
|
|
|
47
107
|
* @param payload - Event-specific data (agent, version, cwd, etc.)
|
|
48
108
|
*/
|
|
49
109
|
export function emit(event, payload = {}) {
|
|
110
|
+
if (isDisabled())
|
|
111
|
+
return;
|
|
50
112
|
try {
|
|
51
113
|
ensureLogsDir();
|
|
52
114
|
const record = {
|
|
@@ -57,11 +119,20 @@ export function emit(event, payload = {}) {
|
|
|
57
119
|
platform: os.platform(),
|
|
58
120
|
arch: os.arch(),
|
|
59
121
|
pid: process.pid,
|
|
122
|
+
ppid: process.ppid,
|
|
60
123
|
event,
|
|
61
|
-
...payload,
|
|
124
|
+
...truncatePayload(payload),
|
|
62
125
|
};
|
|
63
126
|
const line = JSON.stringify(record) + '\n';
|
|
64
|
-
|
|
127
|
+
const logPath = getLogFilePath();
|
|
128
|
+
fs.appendFileSync(logPath, line, { mode: FILE_MODE });
|
|
129
|
+
// Ensure file permissions (appendFileSync doesn't respect mode on existing files)
|
|
130
|
+
try {
|
|
131
|
+
fs.chmodSync(logPath, FILE_MODE);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// May fail if not owner
|
|
135
|
+
}
|
|
65
136
|
}
|
|
66
137
|
catch {
|
|
67
138
|
// Silent failure - logging should never break the CLI
|
|
@@ -88,6 +159,166 @@ export function emitStart(startEvent, payload = {}) {
|
|
|
88
159
|
});
|
|
89
160
|
};
|
|
90
161
|
}
|
|
162
|
+
// ─── Timing Utilities ─────────────────────────────────────────────────────────
|
|
163
|
+
/**
|
|
164
|
+
* Measure execution time of a synchronous function.
|
|
165
|
+
* Emits a perf.timing event with the duration.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* const result = time('parse-config', () => parseConfig(path));
|
|
169
|
+
*/
|
|
170
|
+
export function time(label, fn, payload = {}) {
|
|
171
|
+
const start = Date.now();
|
|
172
|
+
try {
|
|
173
|
+
const result = fn();
|
|
174
|
+
emit('perf.timing', {
|
|
175
|
+
...payload,
|
|
176
|
+
label,
|
|
177
|
+
durationMs: Date.now() - start,
|
|
178
|
+
status: 'success',
|
|
179
|
+
});
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
emit('perf.timing', {
|
|
184
|
+
...payload,
|
|
185
|
+
label,
|
|
186
|
+
durationMs: Date.now() - start,
|
|
187
|
+
status: 'error',
|
|
188
|
+
error: err instanceof Error ? err.message : String(err),
|
|
189
|
+
});
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Measure execution time of an async function.
|
|
195
|
+
* Emits a perf.timing event with the duration.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* const result = await timeAsync('fetch-data', () => fetchData(url));
|
|
199
|
+
*/
|
|
200
|
+
export async function timeAsync(label, fn, payload = {}) {
|
|
201
|
+
const start = Date.now();
|
|
202
|
+
try {
|
|
203
|
+
const result = await fn();
|
|
204
|
+
emit('perf.timing', {
|
|
205
|
+
...payload,
|
|
206
|
+
label,
|
|
207
|
+
durationMs: Date.now() - start,
|
|
208
|
+
status: 'success',
|
|
209
|
+
});
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
emit('perf.timing', {
|
|
214
|
+
...payload,
|
|
215
|
+
label,
|
|
216
|
+
durationMs: Date.now() - start,
|
|
217
|
+
status: 'error',
|
|
218
|
+
error: err instanceof Error ? err.message : String(err),
|
|
219
|
+
});
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Create a timing context for measuring multiple phases of an operation.
|
|
225
|
+
* Useful for tracking startup time vs execution time.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* const timer = createTimer('agent.run', { agent: 'claude' });
|
|
229
|
+
* // ... setup work ...
|
|
230
|
+
* timer.mark('startup'); // records startup time
|
|
231
|
+
* // ... main work ...
|
|
232
|
+
* timer.end({ exitCode: 0 }); // records total time and emits event
|
|
233
|
+
*/
|
|
234
|
+
export function createTimer(label, payload = {}) {
|
|
235
|
+
const start = Date.now();
|
|
236
|
+
const marks = {};
|
|
237
|
+
return {
|
|
238
|
+
mark(phase) {
|
|
239
|
+
const elapsed = Date.now() - start;
|
|
240
|
+
marks[phase] = elapsed;
|
|
241
|
+
return elapsed;
|
|
242
|
+
},
|
|
243
|
+
elapsed() {
|
|
244
|
+
return Date.now() - start;
|
|
245
|
+
},
|
|
246
|
+
end(endPayload = {}) {
|
|
247
|
+
const durationMs = Date.now() - start;
|
|
248
|
+
emit('perf.timing', {
|
|
249
|
+
...payload,
|
|
250
|
+
...endPayload,
|
|
251
|
+
label,
|
|
252
|
+
durationMs,
|
|
253
|
+
phases: marks,
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Higher-order function that wraps an async function with timing.
|
|
260
|
+
* The wrapper emits start/end events automatically.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* const timedFetch = withTiming('fetch', fetchData, { service: 'api' });
|
|
264
|
+
* const result = await timedFetch(url);
|
|
265
|
+
*/
|
|
266
|
+
export function withTiming(label, fn, basePayload = {}) {
|
|
267
|
+
return async (...args) => {
|
|
268
|
+
const start = Date.now();
|
|
269
|
+
try {
|
|
270
|
+
const result = await fn(...args);
|
|
271
|
+
emit('perf.timing', {
|
|
272
|
+
...basePayload,
|
|
273
|
+
label,
|
|
274
|
+
durationMs: Date.now() - start,
|
|
275
|
+
status: 'success',
|
|
276
|
+
});
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
emit('perf.timing', {
|
|
281
|
+
...basePayload,
|
|
282
|
+
label,
|
|
283
|
+
durationMs: Date.now() - start,
|
|
284
|
+
status: 'error',
|
|
285
|
+
error: err instanceof Error ? err.message : String(err),
|
|
286
|
+
});
|
|
287
|
+
throw err;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
// ─── Command Tracking ─────────────────────────────────────────────────────────
|
|
292
|
+
/**
|
|
293
|
+
* Emit a command.start event with CLI args.
|
|
294
|
+
* Returns a done() function to emit command.end with duration.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* // At CLI entry point:
|
|
298
|
+
* const done = emitCommand('run', process.argv.slice(2));
|
|
299
|
+
* // ... execute command ...
|
|
300
|
+
* done({ exitCode: 0 });
|
|
301
|
+
*/
|
|
302
|
+
export function emitCommand(command, args = [], payload = {}) {
|
|
303
|
+
return emitStart('command.start', {
|
|
304
|
+
...payload,
|
|
305
|
+
command,
|
|
306
|
+
args: args.slice(0, 20), // Limit args to first 20
|
|
307
|
+
cwd: process.cwd(),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
// ─── Error Tracking ───────────────────────────────────────────────────────────
|
|
311
|
+
/**
|
|
312
|
+
* Emit an error event with full details.
|
|
313
|
+
*/
|
|
314
|
+
export function emitError(err, payload = {}) {
|
|
315
|
+
const error = err instanceof Error ? err : new Error(err);
|
|
316
|
+
emit('error', {
|
|
317
|
+
...payload,
|
|
318
|
+
error: error.message,
|
|
319
|
+
errorStack: truncate(error.stack, 1000),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
91
322
|
// ─── Rotation ─────────────────────────────────────────────────────────────────
|
|
92
323
|
/**
|
|
93
324
|
* Remove log files older than the retention period.
|
|
@@ -140,7 +371,7 @@ export function maybeRotate() {
|
|
|
140
371
|
* @returns Array of event records
|
|
141
372
|
*/
|
|
142
373
|
export function query(options) {
|
|
143
|
-
const { startDate, endDate = new Date(), eventTypes, agent, limit } = options;
|
|
374
|
+
const { startDate, endDate = new Date(), eventTypes, agent, command, limit } = options;
|
|
144
375
|
const results = [];
|
|
145
376
|
if (!fs.existsSync(LOGS_DIR))
|
|
146
377
|
return results;
|
|
@@ -167,6 +398,8 @@ export function query(options) {
|
|
|
167
398
|
continue;
|
|
168
399
|
if (agent && record.agent !== agent)
|
|
169
400
|
continue;
|
|
401
|
+
if (command && record.command !== command)
|
|
402
|
+
continue;
|
|
170
403
|
results.push(record);
|
|
171
404
|
if (limit && results.length >= limit) {
|
|
172
405
|
return results;
|
|
@@ -179,5 +412,30 @@ export function query(options) {
|
|
|
179
412
|
}
|
|
180
413
|
return results;
|
|
181
414
|
}
|
|
415
|
+
// ─── Stats ────────────────────────────────────────────────────────────────────
|
|
416
|
+
/**
|
|
417
|
+
* Get performance stats for a specific label.
|
|
418
|
+
*/
|
|
419
|
+
export function getTimingStats(label, options = {}) {
|
|
420
|
+
const days = options.days ?? 7;
|
|
421
|
+
const startDate = new Date();
|
|
422
|
+
startDate.setDate(startDate.getDate() - days);
|
|
423
|
+
const events = query({
|
|
424
|
+
startDate,
|
|
425
|
+
eventTypes: ['perf.timing'],
|
|
426
|
+
}).filter(e => e.label === label && typeof e.durationMs === 'number');
|
|
427
|
+
if (events.length === 0)
|
|
428
|
+
return null;
|
|
429
|
+
const durations = events.map(e => e.durationMs).sort((a, b) => a - b);
|
|
430
|
+
const sum = durations.reduce((a, b) => a + b, 0);
|
|
431
|
+
return {
|
|
432
|
+
count: durations.length,
|
|
433
|
+
avgMs: Math.round(sum / durations.length),
|
|
434
|
+
minMs: durations[0],
|
|
435
|
+
maxMs: durations[durations.length - 1],
|
|
436
|
+
p50Ms: durations[Math.floor(durations.length * 0.5)],
|
|
437
|
+
p95Ms: durations[Math.floor(durations.length * 0.95)],
|
|
438
|
+
};
|
|
439
|
+
}
|
|
182
440
|
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
183
441
|
export const LOGS_PATH = LOGS_DIR;
|
package/dist/lib/exec.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as path from 'path';
|
|
|
10
10
|
import { parseTimeout } from './routines.js';
|
|
11
11
|
import { getVersionHomePath, isVersionInstalled, resolveVersion } from './versions.js';
|
|
12
12
|
import { resolveModel, buildReasoningFlags } from './models.js';
|
|
13
|
-
import {
|
|
13
|
+
import { maybeRotate, createTimer, truncate } from './events.js';
|
|
14
14
|
/** Pattern for valid environment variable names (C identifier rules). */
|
|
15
15
|
const EXEC_ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16
16
|
/** Parse a single KEY=VALUE string into a tuple, validating the key name. */
|
|
@@ -305,13 +305,17 @@ async function spawnAgent(options) {
|
|
|
305
305
|
const piped = !process.stdout.isTTY;
|
|
306
306
|
const interactive = options.interactive === true || options.prompt === undefined;
|
|
307
307
|
maybeRotate();
|
|
308
|
-
const
|
|
308
|
+
const timer = createTimer('agent.run', {
|
|
309
309
|
agent: options.agent,
|
|
310
310
|
version: options.version,
|
|
311
311
|
cwd: options.cwd || process.cwd(),
|
|
312
312
|
mode: options.mode,
|
|
313
313
|
model: options.model,
|
|
314
314
|
interactive,
|
|
315
|
+
sessionId: options.sessionId,
|
|
316
|
+
prompt: truncate(options.prompt, 200),
|
|
317
|
+
command: executable,
|
|
318
|
+
args: args.slice(0, 10),
|
|
315
319
|
});
|
|
316
320
|
return new Promise((resolve, reject) => {
|
|
317
321
|
// Interactive mode inherits all stdio so the CLI owns the TTY (TUI
|
|
@@ -327,6 +331,8 @@ async function spawnAgent(options) {
|
|
|
327
331
|
env: buildExecEnv(options),
|
|
328
332
|
shell: false,
|
|
329
333
|
});
|
|
334
|
+
// Mark startup time (time from function call to process spawn)
|
|
335
|
+
timer.mark('startup');
|
|
330
336
|
if (!interactive && piped && child.stdout) {
|
|
331
337
|
child.stdout.pipe(process.stdout);
|
|
332
338
|
}
|
|
@@ -343,23 +349,23 @@ async function spawnAgent(options) {
|
|
|
343
349
|
}
|
|
344
350
|
});
|
|
345
351
|
}
|
|
346
|
-
let
|
|
352
|
+
let timeoutTimer;
|
|
347
353
|
if (timeoutMs) {
|
|
348
|
-
|
|
354
|
+
timeoutTimer = setTimeout(() => {
|
|
349
355
|
child.kill('SIGTERM');
|
|
350
356
|
setTimeout(() => child.kill('SIGKILL'), 5000);
|
|
351
357
|
}, timeoutMs);
|
|
352
358
|
}
|
|
353
359
|
child.on('error', (err) => {
|
|
354
|
-
if (
|
|
355
|
-
clearTimeout(
|
|
356
|
-
|
|
360
|
+
if (timeoutTimer)
|
|
361
|
+
clearTimeout(timeoutTimer);
|
|
362
|
+
timer.end({ error: err.message, exitCode: -1, status: 'error' });
|
|
357
363
|
reject(err);
|
|
358
364
|
});
|
|
359
365
|
child.on('close', (code) => {
|
|
360
|
-
if (
|
|
361
|
-
clearTimeout(
|
|
362
|
-
|
|
366
|
+
if (timeoutTimer)
|
|
367
|
+
clearTimeout(timeoutTimer);
|
|
368
|
+
timer.end({ exitCode: code ?? 0, status: code === 0 ? 'success' : 'failed' });
|
|
363
369
|
resolve({ exitCode: code ?? 0, stderr: stderrBuffer });
|
|
364
370
|
});
|
|
365
371
|
});
|
package/dist/lib/hooks.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare function installHookToVersion(agent: AgentId, version: string, ho
|
|
|
66
66
|
};
|
|
67
67
|
/**
|
|
68
68
|
* Remove a single hook (script + data file) from a specific version home.
|
|
69
|
+
* Soft-deletes to ~/.agents/.trash/hooks/.
|
|
69
70
|
*/
|
|
70
71
|
export declare function removeHookFromVersion(agent: AgentId, version: string, hookName: string): {
|
|
71
72
|
success: boolean;
|
|
@@ -113,10 +114,11 @@ export declare function installHooksCentrally(source: string): Promise<{
|
|
|
113
114
|
*/
|
|
114
115
|
export declare function listCentralHooks(): HookEntry[];
|
|
115
116
|
/**
|
|
116
|
-
* Parse
|
|
117
|
-
* and user
|
|
118
|
-
*
|
|
119
|
-
*
|
|
117
|
+
* Parse hook manifests. Reads system hooks from ~/.agents-system/hooks.yaml
|
|
118
|
+
* (npm-shipped defaults) and user hooks from the `hooks:` section of
|
|
119
|
+
* ~/.agents/agents.yaml. Merges with user-wins-on-key-collision precedence.
|
|
120
|
+
* A user entry with `enabled: false` disables the system-shipped hook of
|
|
121
|
+
* the same name without forking the system file.
|
|
120
122
|
*
|
|
121
123
|
* Hooks marked `enabled: false` are dropped from the returned map.
|
|
122
124
|
*/
|
|
@@ -124,10 +126,12 @@ export declare function parseHookManifest(): Record<string, ManifestHook>;
|
|
|
124
126
|
/**
|
|
125
127
|
* Register hooks as lifecycle events in an agent's config.
|
|
126
128
|
* Reads hooks.yaml manifest, merges into the agent's config file(s).
|
|
127
|
-
* Only manages hooks whose command paths are under ~/.agents/hooks
|
|
128
|
-
* Does not remove user-added hooks.
|
|
129
|
+
* Only manages hooks whose command paths are under ~/.agents/hooks/ or
|
|
130
|
+
* ~/.agents-system/hooks/. Does not remove user-added hooks.
|
|
129
131
|
*
|
|
130
|
-
* @param agentsDirOverride -
|
|
132
|
+
* @param agentsDirOverride - When provided, treats this single dir as the
|
|
133
|
+
* only managed hook root. Used by tests to inject a temp path. In normal
|
|
134
|
+
* operation, both user and system roots are consulted with user precedence.
|
|
131
135
|
*/
|
|
132
136
|
export declare function registerHooksToSettings(agentId: AgentId, versionHome: string, hookManifest?: Record<string, ManifestHook>, agentsDirOverride?: string): {
|
|
133
137
|
registered: string[];
|