@phnx-labs/agents-cli 1.22.11 → 1.22.12
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 +38 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/cli.js +12 -12
- package/dist/commands/events.d.ts +20 -1
- package/dist/commands/events.js +71 -55
- package/dist/commands/logs.js +14 -138
- package/dist/commands/repo.d.ts +1 -1
- package/dist/commands/repo.js +3 -3
- package/dist/commands/view.d.ts +1 -1
- package/dist/commands/view.js +7 -7
- package/dist/lib/cli-resources.js +2 -2
- package/dist/lib/event-stream.d.ts +1 -1
- package/dist/lib/event-stream.js +1 -1
- package/dist/lib/events.d.ts +21 -12
- package/dist/lib/events.js +312 -65
- package/dist/lib/exec.js +1 -2
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/migrate.d.ts +8 -0
- package/dist/lib/migrate.js +26 -62
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/runner.js +1 -2
- 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/audit.js +1 -1
- package/dist/lib/startup/command-registry.js +1 -1
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +60 -2
- package/dist/lib/watchdog/runner.d.ts +13 -0
- package/dist/lib/watchdog/runner.js +75 -6
- package/package.json +1 -1
package/dist/lib/event-stream.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unified event reader -- one stream over BOTH operational events
|
|
3
|
-
* (`~/.agents/.history/events/
|
|
3
|
+
* (`~/.agents/.history/events/YYYY-MM-DD/` via events.ts: secrets, commands, teams, ...) and
|
|
4
4
|
* agent-semantic events (the per-session activity logs via activity.ts: plans,
|
|
5
5
|
* PRs, worktrees, sub-agents, artifacts). They share one {@link EventType}
|
|
6
6
|
* vocabulary and one {@link EventRecord} shape, so `agents events` and any
|
package/dist/lib/events.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized event logging for agents-cli.
|
|
3
3
|
*
|
|
4
|
-
* Structured JSONL audit
|
|
5
|
-
* gzip rotation at 10
|
|
4
|
+
* Structured JSONL audit logs at ~/.agents/.history/events/YYYY-MM-DD/events.jsonl with
|
|
5
|
+
* lossless numbered gzip rotation at 10 MiB and bounded retention.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Rich metadata: hostname, platform, arch, pid, timezone
|
|
@@ -75,6 +75,17 @@ export interface EventPayload {
|
|
|
75
75
|
[key: string]: unknown;
|
|
76
76
|
}
|
|
77
77
|
export type EventRecord = EventMeta & EventPayload;
|
|
78
|
+
/**
|
|
79
|
+
* Move root-level and interim flat-history event families into dated directories.
|
|
80
|
+
*
|
|
81
|
+
* The common case is a whole-family rename into an empty destination. A
|
|
82
|
+
* Each segment is assigned to the local calendar day of its filesystem mtime;
|
|
83
|
+
* new writes are split by day at source. A partially completed migration keeps
|
|
84
|
+
* the destination active file authoritative and assigns a fresh archive number,
|
|
85
|
+
* so no record is overwritten or silently discarded. The legacy active-file
|
|
86
|
+
* lock serializes this with older installed processes that still append there.
|
|
87
|
+
*/
|
|
88
|
+
export declare function migrateLegacyEventLogs(userDir?: string): number;
|
|
78
89
|
/**
|
|
79
90
|
* Replace a prompt string with length + short SHA so we can correlate runs
|
|
80
91
|
* without persisting the raw text. Returns the fields to spread into a payload.
|
|
@@ -187,15 +198,13 @@ export declare function emitError(err: Error | string, payload?: EventPayload):
|
|
|
187
198
|
* the same failure across sessions (e.g. 'remote-cwd-on-add', 'not-installed').
|
|
188
199
|
*/
|
|
189
200
|
export declare function emitFriction(surface: string, failureId: string, payload?: EventPayload): void;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
export declare function rotate(retentionDays?: number): number;
|
|
198
|
-
export declare function maybeRotate(): void;
|
|
201
|
+
export interface RotationResult {
|
|
202
|
+
removedByAge: number;
|
|
203
|
+
removedBySize: number;
|
|
204
|
+
bytesReclaimed: number;
|
|
205
|
+
}
|
|
206
|
+
/** Apply age retention and the total-size ceiling immediately. */
|
|
207
|
+
export declare function rotate(retentionDays?: number, maxStorageBytes?: number): RotationResult;
|
|
199
208
|
/**
|
|
200
209
|
* Read events from log files within a date range.
|
|
201
210
|
*
|
|
@@ -245,4 +254,4 @@ export declare function stats(options?: {
|
|
|
245
254
|
days?: number;
|
|
246
255
|
}): EventStats;
|
|
247
256
|
export declare function getLogsPath(): string;
|
|
248
|
-
export declare function _resetForTest(overrideEventsPath?: string): void;
|
|
257
|
+
export declare function _resetForTest(overrideEventsPath?: string, overrideUserAgentsDir?: string): void;
|
package/dist/lib/events.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized event logging for agents-cli.
|
|
3
3
|
*
|
|
4
|
-
* Structured JSONL audit
|
|
5
|
-
* gzip rotation at 10
|
|
4
|
+
* Structured JSONL audit logs at ~/.agents/.history/events/YYYY-MM-DD/events.jsonl with
|
|
5
|
+
* lossless numbered gzip rotation at 10 MiB and bounded retention.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Rich metadata: hostname, platform, arch, pid, timezone
|
|
@@ -17,7 +17,7 @@ import * as os from 'os';
|
|
|
17
17
|
import { createHash } from 'node:crypto';
|
|
18
18
|
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
19
19
|
import { ensureLockTarget, withFileLock } from './fs-atomic.js';
|
|
20
|
-
import {
|
|
20
|
+
import { getUserAgentsDir } from './state.js';
|
|
21
21
|
import { stampProvenance, resetEventProvenanceForTest } from './event-provenance.js';
|
|
22
22
|
/** Lazy perf warehouse write — avoids a hard cycle at module load. */
|
|
23
23
|
function recordPerfTiming(payload) {
|
|
@@ -47,14 +47,42 @@ function recordPerfTiming(payload) {
|
|
|
47
47
|
// unlike _resetForTest it survives a bare reset AND propagates to CLI subprocesses
|
|
48
48
|
// a test spawns, so fixture events can never land in the user's real log (#910).
|
|
49
49
|
let _eventsPath;
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
let _eventsPathOverride = false;
|
|
51
|
+
let _legacyMigrationChecked = false;
|
|
52
|
+
let _userAgentsDirOverride;
|
|
53
|
+
function userAgentsDir() {
|
|
54
|
+
return _userAgentsDirOverride ?? getUserAgentsDir();
|
|
52
55
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
56
|
+
function localDateKey(date = new Date()) {
|
|
57
|
+
const year = date.getFullYear();
|
|
58
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
59
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
60
|
+
return `${year}-${month}-${day}`;
|
|
61
|
+
}
|
|
62
|
+
function eventsRoot() {
|
|
63
|
+
if (_eventsPathOverride && _eventsPath)
|
|
64
|
+
return path.dirname(_eventsPath);
|
|
65
|
+
return path.join(userAgentsDir(), '.history', 'events');
|
|
66
|
+
}
|
|
67
|
+
function eventsPath(date = new Date()) {
|
|
68
|
+
if (_eventsPathOverride && _eventsPath)
|
|
69
|
+
return _eventsPath;
|
|
70
|
+
const override = _userAgentsDirOverride ? undefined : process.env.AGENTS_EVENTS_PATH;
|
|
71
|
+
if (override) {
|
|
72
|
+
_eventsPathOverride = true;
|
|
73
|
+
return (_eventsPath = override);
|
|
74
|
+
}
|
|
75
|
+
return path.join(eventsRoot(), localDateKey(date), 'events.jsonl');
|
|
76
|
+
}
|
|
77
|
+
function eventsDir(date = new Date()) {
|
|
78
|
+
return path.dirname(eventsPath(date));
|
|
55
79
|
}
|
|
56
80
|
/** Default retention period in days. */
|
|
57
81
|
const DEFAULT_RETENTION_DAYS = 7;
|
|
82
|
+
/** Default total footprint for the active log plus gzip archives (50 MiB). */
|
|
83
|
+
const DEFAULT_MAX_STORAGE_BYTES = 50 * 1024 * 1024;
|
|
84
|
+
/** Cross-process marker used to avoid a full archive scan on every append. */
|
|
85
|
+
const PRUNE_MARKER = '.last-prune';
|
|
58
86
|
/** Default max length for truncated strings. */
|
|
59
87
|
const DEFAULT_TRUNCATE_LENGTH = 500;
|
|
60
88
|
/** Gzip rotation threshold in bytes (10 MB). */
|
|
@@ -154,6 +182,8 @@ function getTimezoneName() {
|
|
|
154
182
|
}
|
|
155
183
|
}
|
|
156
184
|
function ensureLogsDir() {
|
|
185
|
+
eventsPath(); // Resolve an explicit AGENTS_EVENTS_PATH before migration checks.
|
|
186
|
+
migrateLegacyEventLogs();
|
|
157
187
|
if (!fs.existsSync(eventsDir())) {
|
|
158
188
|
fs.mkdirSync(eventsDir(), { recursive: true, mode: DIR_MODE });
|
|
159
189
|
}
|
|
@@ -167,6 +197,102 @@ function ensureLogsDir() {
|
|
|
167
197
|
}
|
|
168
198
|
}
|
|
169
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Move root-level and interim flat-history event families into dated directories.
|
|
202
|
+
*
|
|
203
|
+
* The common case is a whole-family rename into an empty destination. A
|
|
204
|
+
* Each segment is assigned to the local calendar day of its filesystem mtime;
|
|
205
|
+
* new writes are split by day at source. A partially completed migration keeps
|
|
206
|
+
* the destination active file authoritative and assigns a fresh archive number,
|
|
207
|
+
* so no record is overwritten or silently discarded. The legacy active-file
|
|
208
|
+
* lock serializes this with older installed processes that still append there.
|
|
209
|
+
*/
|
|
210
|
+
export function migrateLegacyEventLogs(userDir = userAgentsDir()) {
|
|
211
|
+
if (_eventsPathOverride || _legacyMigrationChecked)
|
|
212
|
+
return 0;
|
|
213
|
+
_legacyMigrationChecked = true;
|
|
214
|
+
const destinationRoot = path.join(userDir, '.history', 'events');
|
|
215
|
+
const legacyActive = path.join(userDir, 'events.jsonl');
|
|
216
|
+
const interimActive = path.join(destinationRoot, 'events.jsonl');
|
|
217
|
+
const listArchives = (dir) => {
|
|
218
|
+
try {
|
|
219
|
+
return fs.readdirSync(dir)
|
|
220
|
+
.map((file) => ({ file, match: file.match(/^events\.(\d+)\.jsonl\.gz$/) }))
|
|
221
|
+
.filter((entry) => entry.match !== null)
|
|
222
|
+
.map((entry) => ({ file: entry.file, number: Number(entry.match[1]) }))
|
|
223
|
+
.sort((a, b) => a.number - b.number);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const hasSourceFiles = fs.existsSync(legacyActive) || fs.existsSync(interimActive) ||
|
|
230
|
+
listArchives(userDir).length > 0 || listArchives(destinationRoot).length > 0;
|
|
231
|
+
if (!hasSourceFiles)
|
|
232
|
+
return 0;
|
|
233
|
+
try {
|
|
234
|
+
fs.mkdirSync(destinationRoot, { recursive: true, mode: DIR_MODE });
|
|
235
|
+
ensureLockTarget(legacyActive, '', DIR_MODE);
|
|
236
|
+
return withFileLock(legacyActive, () => {
|
|
237
|
+
ensureLockTarget(interimActive, '', DIR_MODE);
|
|
238
|
+
return withFileLock(interimActive, () => {
|
|
239
|
+
const sourceFamilies = [
|
|
240
|
+
{ dir: userDir, active: legacyActive, archives: listArchives(userDir) },
|
|
241
|
+
{ dir: destinationRoot, active: interimActive, archives: listArchives(destinationRoot) },
|
|
242
|
+
];
|
|
243
|
+
let moved = 0;
|
|
244
|
+
const nextArchiveNumber = (dir) => {
|
|
245
|
+
const numbers = fs.readdirSync(dir)
|
|
246
|
+
.map((file) => file.match(/^events\.(\d+)\.jsonl\.gz$/))
|
|
247
|
+
.filter((match) => match !== null)
|
|
248
|
+
.map((match) => Number(match[1]));
|
|
249
|
+
return (numbers.length ? Math.max(...numbers) : 0) + 1;
|
|
250
|
+
};
|
|
251
|
+
const withDestinationLock = (dayDir, fn) => {
|
|
252
|
+
const destinationActive = path.join(dayDir, 'events.jsonl');
|
|
253
|
+
fs.mkdirSync(dayDir, { recursive: true, mode: DIR_MODE });
|
|
254
|
+
ensureLockTarget(destinationActive, '', DIR_MODE);
|
|
255
|
+
return withFileLock(destinationActive, fn);
|
|
256
|
+
};
|
|
257
|
+
for (const family of sourceFamilies) {
|
|
258
|
+
const activeBytes = fs.existsSync(family.active) ? fs.statSync(family.active).size : 0;
|
|
259
|
+
if (activeBytes > 0) {
|
|
260
|
+
const activeStat = fs.statSync(family.active);
|
|
261
|
+
const dayDir = path.join(destinationRoot, localDateKey(activeStat.mtime));
|
|
262
|
+
withDestinationLock(dayDir, () => {
|
|
263
|
+
const archivePath = path.join(dayDir, `events.${nextArchiveNumber(dayDir)}.jsonl.gz`);
|
|
264
|
+
fs.writeFileSync(archivePath, gzipSync(fs.readFileSync(family.active)), { mode: FILE_MODE });
|
|
265
|
+
fs.utimesSync(archivePath, activeStat.atime, activeStat.mtime);
|
|
266
|
+
fs.truncateSync(family.active, 0);
|
|
267
|
+
});
|
|
268
|
+
moved++;
|
|
269
|
+
}
|
|
270
|
+
for (const archive of family.archives) {
|
|
271
|
+
const source = path.join(family.dir, archive.file);
|
|
272
|
+
const dayDir = path.join(destinationRoot, localDateKey(fs.statSync(source).mtime));
|
|
273
|
+
withDestinationLock(dayDir, () => {
|
|
274
|
+
const destination = path.join(dayDir, `events.${nextArchiveNumber(dayDir)}.jsonl.gz`);
|
|
275
|
+
fs.renameSync(source, destination);
|
|
276
|
+
});
|
|
277
|
+
moved++;
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
if (fs.existsSync(family.active) && fs.statSync(family.active).size === 0)
|
|
281
|
+
fs.unlinkSync(family.active);
|
|
282
|
+
}
|
|
283
|
+
catch { /* an older process may have reopened it */ }
|
|
284
|
+
}
|
|
285
|
+
return moved;
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Audit logging must remain fail-soft. Files stay at the legacy path and a
|
|
291
|
+
// later process retries because this guard is process-local.
|
|
292
|
+
_legacyMigrationChecked = false;
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
170
296
|
// ─── Redaction ────────────────────────────────────────────────────────────────
|
|
171
297
|
/**
|
|
172
298
|
* Replace a prompt string with length + short SHA so we can correlate runs
|
|
@@ -380,7 +506,8 @@ export function emit(event, payload = {}, overrides = {}) {
|
|
|
380
506
|
// May fail if not owner
|
|
381
507
|
}
|
|
382
508
|
}
|
|
383
|
-
maybeGzipRotateLocked(logPath);
|
|
509
|
+
const rotated = maybeGzipRotateLocked(logPath);
|
|
510
|
+
maybePruneLocked(rotated);
|
|
384
511
|
});
|
|
385
512
|
}
|
|
386
513
|
catch {
|
|
@@ -657,7 +784,7 @@ export function emitFriction(surface, failureId, payload = {}) {
|
|
|
657
784
|
function maybeGzipRotateLocked(logPath) {
|
|
658
785
|
const stat = fs.statSync(logPath);
|
|
659
786
|
if (stat.size < GZIP_ROTATION_BYTES)
|
|
660
|
-
return;
|
|
787
|
+
return false;
|
|
661
788
|
const raw = fs.readFileSync(logPath);
|
|
662
789
|
const tmpArchive = path.join(eventsDir(), `.events.1.jsonl.gz.${process.pid}.tmp`);
|
|
663
790
|
fs.writeFileSync(tmpArchive, gzipSync(raw), { mode: FILE_MODE });
|
|
@@ -672,6 +799,7 @@ function maybeGzipRotateLocked(logPath) {
|
|
|
672
799
|
}
|
|
673
800
|
fs.renameSync(tmpArchive, path.join(eventsDir(), 'events.1.jsonl.gz'));
|
|
674
801
|
fs.truncateSync(logPath, 0);
|
|
802
|
+
return true;
|
|
675
803
|
}
|
|
676
804
|
catch (err) {
|
|
677
805
|
try {
|
|
@@ -681,45 +809,179 @@ function maybeGzipRotateLocked(logPath) {
|
|
|
681
809
|
throw err;
|
|
682
810
|
}
|
|
683
811
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
812
|
+
function listEventLogFiles() {
|
|
813
|
+
const current = eventsPath();
|
|
814
|
+
const files = [];
|
|
815
|
+
const addFile = (filePath, gzip) => {
|
|
816
|
+
try {
|
|
817
|
+
const stat = fs.statSync(filePath);
|
|
818
|
+
files.push({
|
|
819
|
+
path: filePath,
|
|
820
|
+
gzip,
|
|
821
|
+
currentActive: filePath === current,
|
|
822
|
+
mtimeMs: stat.mtimeMs,
|
|
823
|
+
size: stat.size,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
catch { /* file may rotate while an unlocked reader enumerates */ }
|
|
827
|
+
};
|
|
828
|
+
const addDirectory = (dir) => {
|
|
829
|
+
let names = [];
|
|
830
|
+
try {
|
|
831
|
+
names = fs.readdirSync(dir);
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
for (const name of names) {
|
|
837
|
+
if (name !== 'events.jsonl' && !/^events\.\d+\.jsonl\.gz$/.test(name))
|
|
838
|
+
continue;
|
|
839
|
+
addFile(path.join(dir, name), name.endsWith('.gz'));
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
if (_eventsPathOverride) {
|
|
843
|
+
if (path.basename(current) !== 'events.jsonl') {
|
|
844
|
+
addFile(current, false);
|
|
845
|
+
let names = [];
|
|
846
|
+
try {
|
|
847
|
+
names = fs.readdirSync(eventsDir());
|
|
848
|
+
}
|
|
849
|
+
catch {
|
|
850
|
+
return files;
|
|
851
|
+
}
|
|
852
|
+
for (const name of names) {
|
|
853
|
+
if (/^events\.\d+\.jsonl\.gz$/.test(name))
|
|
854
|
+
addFile(path.join(eventsDir(), name), true);
|
|
704
855
|
}
|
|
856
|
+
return files;
|
|
705
857
|
}
|
|
706
|
-
|
|
858
|
+
addDirectory(eventsDir());
|
|
859
|
+
return files;
|
|
860
|
+
}
|
|
861
|
+
let days = [];
|
|
862
|
+
try {
|
|
863
|
+
days = fs.readdirSync(eventsRoot()).filter((name) => /^\d{4}-\d{2}-\d{2}$/.test(name));
|
|
707
864
|
}
|
|
708
865
|
catch {
|
|
709
|
-
return
|
|
866
|
+
return files;
|
|
710
867
|
}
|
|
868
|
+
for (const day of days)
|
|
869
|
+
addDirectory(path.join(eventsRoot(), day));
|
|
870
|
+
return files;
|
|
711
871
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
let
|
|
716
|
-
|
|
717
|
-
|
|
872
|
+
function removeEmptyDayDirectories() {
|
|
873
|
+
if (_eventsPathOverride)
|
|
874
|
+
return;
|
|
875
|
+
let days = [];
|
|
876
|
+
try {
|
|
877
|
+
days = fs.readdirSync(eventsRoot()).filter((name) => /^\d{4}-\d{2}-\d{2}$/.test(name));
|
|
878
|
+
}
|
|
879
|
+
catch {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
for (const day of days) {
|
|
883
|
+
const dir = path.join(eventsRoot(), day);
|
|
884
|
+
try {
|
|
885
|
+
if (fs.readdirSync(dir).length === 0)
|
|
886
|
+
fs.rmdirSync(dir);
|
|
887
|
+
}
|
|
888
|
+
catch { /* best effort */ }
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function finalizePastDayLogs() {
|
|
892
|
+
if (_eventsPathOverride)
|
|
893
|
+
return;
|
|
894
|
+
const currentDay = localDateKey();
|
|
895
|
+
for (const file of listEventLogFiles()) {
|
|
896
|
+
if (path.basename(file.path) !== 'events.jsonl')
|
|
897
|
+
continue;
|
|
898
|
+
if (path.basename(path.dirname(file.path)) === currentDay)
|
|
899
|
+
continue;
|
|
900
|
+
try {
|
|
901
|
+
withFileLock(file.path, () => {
|
|
902
|
+
const dir = path.dirname(file.path);
|
|
903
|
+
const numbers = fs.readdirSync(dir)
|
|
904
|
+
.map((name) => name.match(/^events\.(\d+)\.jsonl\.gz$/))
|
|
905
|
+
.filter((match) => match !== null)
|
|
906
|
+
.map((match) => Number(match[1]));
|
|
907
|
+
const next = (numbers.length ? Math.max(...numbers) : 0) + 1;
|
|
908
|
+
const target = path.join(dir, `events.${next}.jsonl.gz`);
|
|
909
|
+
const sourceStat = fs.statSync(file.path);
|
|
910
|
+
fs.writeFileSync(target, gzipSync(fs.readFileSync(file.path)), { mode: FILE_MODE });
|
|
911
|
+
fs.utimesSync(target, sourceStat.atime, sourceStat.mtime);
|
|
912
|
+
fs.unlinkSync(file.path);
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
catch { /* retry on the next prune */ }
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
function pruneEventLogsLocked(retentionDays = DEFAULT_RETENTION_DAYS, maxStorageBytes = DEFAULT_MAX_STORAGE_BYTES) {
|
|
919
|
+
finalizePastDayLogs();
|
|
920
|
+
const result = { removedByAge: 0, removedBySize: 0, bytesReclaimed: 0 };
|
|
921
|
+
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
922
|
+
let files = listEventLogFiles();
|
|
923
|
+
for (const file of files) {
|
|
924
|
+
if (file.currentActive || file.mtimeMs >= cutoff)
|
|
925
|
+
continue;
|
|
926
|
+
try {
|
|
927
|
+
fs.unlinkSync(file.path);
|
|
928
|
+
result.removedByAge++;
|
|
929
|
+
result.bytesReclaimed += file.size;
|
|
930
|
+
}
|
|
931
|
+
catch { /* another process may already have removed it */ }
|
|
932
|
+
}
|
|
933
|
+
files = listEventLogFiles();
|
|
934
|
+
let totalBytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
935
|
+
const oldestFirst = files
|
|
936
|
+
.filter((file) => !file.currentActive)
|
|
937
|
+
.sort((a, b) => a.mtimeMs - b.mtimeMs || a.path.localeCompare(b.path));
|
|
938
|
+
for (const file of oldestFirst) {
|
|
939
|
+
if (totalBytes <= maxStorageBytes)
|
|
940
|
+
break;
|
|
941
|
+
try {
|
|
942
|
+
fs.unlinkSync(file.path);
|
|
943
|
+
totalBytes -= file.size;
|
|
944
|
+
result.removedBySize++;
|
|
945
|
+
result.bytesReclaimed += file.size;
|
|
946
|
+
}
|
|
947
|
+
catch { /* another process may already have removed it */ }
|
|
948
|
+
}
|
|
949
|
+
removeEmptyDayDirectories();
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
/** Apply age retention and the total-size ceiling immediately. */
|
|
953
|
+
export function rotate(retentionDays = DEFAULT_RETENTION_DAYS, maxStorageBytes = DEFAULT_MAX_STORAGE_BYTES) {
|
|
954
|
+
try {
|
|
955
|
+
ensureLogsDir();
|
|
956
|
+
const active = eventsPath();
|
|
957
|
+
ensureLockTarget(active, '', DIR_MODE);
|
|
958
|
+
return withFileLock(active, () => pruneEventLogsLocked(retentionDays, maxStorageBytes));
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
return { removedByAge: 0, removedBySize: 0, bytesReclaimed: 0 };
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
/** Prune daily across processes, and immediately after a size rotation. */
|
|
965
|
+
function maybePruneLocked(force) {
|
|
966
|
+
const marker = path.join(eventsRoot(), PRUNE_MARKER);
|
|
718
967
|
const oneDayMs = 24 * 60 * 60 * 1000;
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
968
|
+
let due = force;
|
|
969
|
+
try {
|
|
970
|
+
due ||= Date.now() - fs.statSync(marker).mtimeMs > oneDayMs;
|
|
722
971
|
}
|
|
972
|
+
catch {
|
|
973
|
+
due = true;
|
|
974
|
+
}
|
|
975
|
+
if (!due)
|
|
976
|
+
return;
|
|
977
|
+
const maxBytes = force
|
|
978
|
+
? DEFAULT_MAX_STORAGE_BYTES - GZIP_ROTATION_BYTES
|
|
979
|
+
: DEFAULT_MAX_STORAGE_BYTES;
|
|
980
|
+
pruneEventLogsLocked(DEFAULT_RETENTION_DAYS, maxBytes);
|
|
981
|
+
try {
|
|
982
|
+
fs.writeFileSync(marker, '', { mode: FILE_MODE });
|
|
983
|
+
}
|
|
984
|
+
catch { /* retry next append */ }
|
|
723
985
|
}
|
|
724
986
|
// ─── Query ────────────────────────────────────────────────────────────────────
|
|
725
987
|
/**
|
|
@@ -731,19 +993,9 @@ export function maybeRotate() {
|
|
|
731
993
|
export function query(options) {
|
|
732
994
|
const { startDate, endDate = new Date(), eventTypes, level, agent, sessionId, caller, command, module, bundle, limit } = options;
|
|
733
995
|
const results = [];
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const files =
|
|
737
|
-
if (fs.existsSync(eventsPath()))
|
|
738
|
-
files.push({ path: eventsPath(), gzip: false });
|
|
739
|
-
const archives = fs.readdirSync(eventsDir())
|
|
740
|
-
.map((file) => ({ file, match: file.match(/^events\.(\d+)\.jsonl\.gz$/) }))
|
|
741
|
-
.filter((entry) => entry.match !== null)
|
|
742
|
-
.map((entry) => ({ file: entry.file, number: Number(entry.match[1]) }))
|
|
743
|
-
.sort((a, b) => a.number - b.number);
|
|
744
|
-
for (const archive of archives) {
|
|
745
|
-
files.push({ path: path.join(eventsDir(), archive.file), gzip: true });
|
|
746
|
-
}
|
|
996
|
+
eventsPath(); // Resolve AGENTS_EVENTS_PATH before deciding whether to migrate.
|
|
997
|
+
migrateLegacyEventLogs();
|
|
998
|
+
const files = listEventLogFiles().sort((a, b) => Number(b.currentActive) - Number(a.currentActive) || b.mtimeMs - a.mtimeMs || b.path.localeCompare(a.path));
|
|
747
999
|
const startMs = startDate?.getTime();
|
|
748
1000
|
const endMs = endDate?.getTime();
|
|
749
1001
|
for (const file of files) {
|
|
@@ -849,16 +1101,9 @@ export function stats(options = {}) {
|
|
|
849
1101
|
let fileCount = 0;
|
|
850
1102
|
let totalBytes = 0;
|
|
851
1103
|
try {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
for (const f of files) {
|
|
856
|
-
try {
|
|
857
|
-
totalBytes += fs.statSync(path.join(eventsDir(), f)).size;
|
|
858
|
-
}
|
|
859
|
-
catch { /* skip */ }
|
|
860
|
-
}
|
|
861
|
-
}
|
|
1104
|
+
const files = listEventLogFiles();
|
|
1105
|
+
fileCount = files.length;
|
|
1106
|
+
totalBytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
862
1107
|
}
|
|
863
1108
|
catch { /* skip */ }
|
|
864
1109
|
return {
|
|
@@ -876,9 +1121,11 @@ export function stats(options = {}) {
|
|
|
876
1121
|
export function getLogsPath() {
|
|
877
1122
|
return eventsPath();
|
|
878
1123
|
}
|
|
879
|
-
export function _resetForTest(overrideEventsPath) {
|
|
1124
|
+
export function _resetForTest(overrideEventsPath, overrideUserAgentsDir) {
|
|
880
1125
|
_eventsPath = overrideEventsPath;
|
|
1126
|
+
_eventsPathOverride = Boolean(overrideEventsPath);
|
|
1127
|
+
_userAgentsDirOverride = overrideUserAgentsDir;
|
|
1128
|
+
_legacyMigrationChecked = false;
|
|
881
1129
|
resetEventProvenanceForTest();
|
|
882
1130
|
_chmoddedPath = undefined;
|
|
883
|
-
lastRotationCheck = 0;
|
|
884
1131
|
}
|
package/dist/lib/exec.js
CHANGED
|
@@ -14,7 +14,7 @@ import { parseTimeout } from './routines.js';
|
|
|
14
14
|
import { getBinaryPath, getVersionHomePath, isVersionInstalled, resolveVersion } from './versions.js';
|
|
15
15
|
import { resolveModel, buildReasoningFlags } from './models.js';
|
|
16
16
|
import { isTierToken, resolveTier } from './model-tiers.js';
|
|
17
|
-
import {
|
|
17
|
+
import { createTimer, redactPrompt, redactArgs } from './events.js';
|
|
18
18
|
import { sanitizeProcessEnv } from './secrets/bundles.js';
|
|
19
19
|
import { resolveActor, actorEnv } from './actor.js';
|
|
20
20
|
import { getShimsDir, getHistoryDir, getUserAgentsDir } from './state.js';
|
|
@@ -1399,7 +1399,6 @@ async function spawnAgent(options) {
|
|
|
1399
1399
|
const runId = launchId;
|
|
1400
1400
|
options = { ...options, env: { ...options.env, AGENT_LAUNCH_ID: launchId } };
|
|
1401
1401
|
const watcherState = await setupBudgetWatcher(options, cwd, runId);
|
|
1402
|
-
maybeRotate();
|
|
1403
1402
|
const timer = createTimer('agent.run', {
|
|
1404
1403
|
agent: options.agent,
|
|
1405
1404
|
version: options.version,
|
|
Binary file
|
|
Binary file
|
package/dist/lib/migrate.d.ts
CHANGED
|
@@ -133,5 +133,13 @@ export declare function migrateRoutineDeviceToDevices(routinesDir?: string): voi
|
|
|
133
133
|
* real routines dir (and without racing the routine module's own tests).
|
|
134
134
|
*/
|
|
135
135
|
export declare function migrateWatchdogSentinelToRoutine(sentinelPath?: string, ensure?: (enabled: boolean) => void): void;
|
|
136
|
+
/**
|
|
137
|
+
* Rename cli/ → clis/ in each of the given `.agents/` directories.
|
|
138
|
+
*
|
|
139
|
+
* One-way, idempotent: no-op when src is absent. Throws when both
|
|
140
|
+
* `<dir>/cli` and `<dir>/clis` exist — the user must resolve the conflict
|
|
141
|
+
* manually before proceeding. Exported for unit-testing with temp dirs.
|
|
142
|
+
*/
|
|
143
|
+
export declare function migrateCliDirToClis(agentsDirs: string[]): void;
|
|
136
144
|
/** Run all idempotent migrations. Safe to call multiple times. */
|
|
137
145
|
export declare function runMigration(): Promise<void>;
|