@agentstrack/collector 0.2.0 → 0.3.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 +177 -1
- package/README.md +78 -28
- package/dist/adapters/claude.d.ts +18 -0
- package/dist/adapters/claude.js +153 -45
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.d.ts +15 -1
- package/dist/adapters/codex.js +81 -33
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/opencode.d.ts +19 -7
- package/dist/adapters/opencode.js +88 -29
- package/dist/adapters/opencode.js.map +1 -1
- package/dist/adapters/types.d.ts +16 -0
- package/dist/adapters/types.js +61 -0
- package/dist/adapters/types.js.map +1 -1
- package/dist/cli.js +165 -34
- package/dist/cli.js.map +1 -1
- package/dist/commands/service.js +40 -10
- package/dist/commands/service.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.js +31 -5
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +73 -21
- package/dist/daemon.js +358 -121
- package/dist/daemon.js.map +1 -1
- package/dist/git/commits.d.ts +7 -1
- package/dist/git/commits.js +36 -17
- package/dist/git/commits.js.map +1 -1
- package/dist/git/repo.d.ts +13 -4
- package/dist/git/repo.js +34 -20
- package/dist/git/repo.js.map +1 -1
- package/dist/machine.d.ts +27 -0
- package/dist/machine.js +46 -0
- package/dist/machine.js.map +1 -0
- package/dist/privacy/pipeline.d.ts +3 -0
- package/dist/privacy/pipeline.js +4 -2
- package/dist/privacy/pipeline.js.map +1 -1
- package/dist/privacy/redact.d.ts +10 -1
- package/dist/privacy/redact.js +29 -3
- package/dist/privacy/redact.js.map +1 -1
- package/dist/queue/event-id.d.ts +9 -0
- package/dist/queue/event-id.js +15 -0
- package/dist/queue/event-id.js.map +1 -0
- package/dist/queue/spool.d.ts +24 -5
- package/dist/queue/spool.js +89 -33
- package/dist/queue/spool.js.map +1 -1
- package/dist/queue/tailer.d.ts +27 -4
- package/dist/queue/tailer.js +89 -28
- package/dist/queue/tailer.js.map +1 -1
- package/dist/transport/client.d.ts +37 -13
- package/dist/transport/client.js +50 -3
- package/dist/transport/client.js.map +1 -1
- package/package.json +2 -2
package/dist/queue/spool.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { EventEnvelope } from '../schema.js';
|
|
2
|
+
export interface Checkpoint {
|
|
3
|
+
inode: string;
|
|
4
|
+
offset: number;
|
|
5
|
+
size: number;
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
8
|
* Durable local spool.
|
|
4
9
|
*
|
|
@@ -12,8 +17,25 @@ import type { EventEnvelope } from '../schema.js';
|
|
|
12
17
|
*/
|
|
13
18
|
export declare class Spool {
|
|
14
19
|
private readonly db;
|
|
20
|
+
private readonly stmt;
|
|
21
|
+
/**
|
|
22
|
+
* Every checkpoint, in memory. A steady-state scan touches every tracked
|
|
23
|
+
* file every 5s and almost none of them changed; answering that from a Map
|
|
24
|
+
* costs nothing, and only a change is written through.
|
|
25
|
+
*/
|
|
26
|
+
private readonly checkpoints;
|
|
15
27
|
constructor(path: string);
|
|
28
|
+
private loadCheckpoints;
|
|
16
29
|
private migrate;
|
|
30
|
+
/**
|
|
31
|
+
* A drained backfill leaves the file at its high-water mark, nearly all of
|
|
32
|
+
* it free pages (a 206MB spool holding 78 events was observed). VACUUM once
|
|
33
|
+
* at open when the freelist is both large and the majority of the file —
|
|
34
|
+
* never on a small db, where it is pure cost.
|
|
35
|
+
*/
|
|
36
|
+
private vacuumIfBloated;
|
|
37
|
+
/** Runs `fn` atomically. A throw rolls back everything written inside it. */
|
|
38
|
+
transaction<T>(fn: () => T): T;
|
|
17
39
|
enqueue(events: EventEnvelope[]): number;
|
|
18
40
|
/** Oldest-first so a backlog drains in the order it happened. */
|
|
19
41
|
peek(limit: number): {
|
|
@@ -33,14 +55,11 @@ export declare class Spool {
|
|
|
33
55
|
*/
|
|
34
56
|
fail(eventIds: string[], maxAttempts: number): number;
|
|
35
57
|
depth(): number;
|
|
36
|
-
getCheckpoint(path: string):
|
|
37
|
-
inode: string;
|
|
38
|
-
offset: number;
|
|
39
|
-
size: number;
|
|
40
|
-
} | null;
|
|
58
|
+
getCheckpoint(path: string): Checkpoint | null;
|
|
41
59
|
setCheckpoint(path: string, inode: string, offset: number, size: number): void;
|
|
42
60
|
getMeta(key: string): string | null;
|
|
43
61
|
setMeta(key: string, value: string): void;
|
|
62
|
+
deleteMeta(key: string): void;
|
|
44
63
|
/** Stable per-install id, generated once. */
|
|
45
64
|
installId(): string;
|
|
46
65
|
close(): void;
|
package/dist/queue/spool.js
CHANGED
|
@@ -15,22 +15,54 @@ import { randomUUID } from 'node:crypto';
|
|
|
15
15
|
*/
|
|
16
16
|
export class Spool {
|
|
17
17
|
db;
|
|
18
|
+
stmt;
|
|
19
|
+
/**
|
|
20
|
+
* Every checkpoint, in memory. A steady-state scan touches every tracked
|
|
21
|
+
* file every 5s and almost none of them changed; answering that from a Map
|
|
22
|
+
* costs nothing, and only a change is written through.
|
|
23
|
+
*/
|
|
24
|
+
checkpoints = new Map();
|
|
18
25
|
constructor(path) {
|
|
19
26
|
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
20
27
|
this.db = new Database(path);
|
|
21
|
-
//
|
|
28
|
+
// WAL so a crash mid-write cannot corrupt the queue.
|
|
29
|
+
this.db.pragma('journal_mode = WAL');
|
|
30
|
+
this.db.pragma('synchronous = NORMAL');
|
|
31
|
+
// The WAL is otherwise only trimmed on close; a backfill leaves it at its
|
|
32
|
+
// high-water mark forever.
|
|
33
|
+
this.db.pragma('journal_size_limit = 8388608');
|
|
34
|
+
// The spool holds un-uploaded telemetry; default umask makes it
|
|
35
|
+
// world-readable. WAL and SHM exist only after the pragma above.
|
|
22
36
|
for (const suffix of ['', '-wal', '-shm']) {
|
|
23
37
|
try {
|
|
24
38
|
chmodSync(`${path}${suffix}`, 0o600);
|
|
25
39
|
}
|
|
26
40
|
catch {
|
|
27
|
-
//
|
|
41
|
+
// The main db is the one that matters.
|
|
28
42
|
}
|
|
29
43
|
}
|
|
30
|
-
// WAL so a crash mid-write cannot corrupt the queue.
|
|
31
|
-
this.db.pragma('journal_mode = WAL');
|
|
32
|
-
this.db.pragma('synchronous = NORMAL');
|
|
33
44
|
this.migrate();
|
|
45
|
+
this.vacuumIfBloated();
|
|
46
|
+
this.stmt = {
|
|
47
|
+
insert: this.db.prepare('INSERT OR IGNORE INTO events (event_id, body, created_at) VALUES (?, ?, ?)'),
|
|
48
|
+
peek: this.db.prepare('SELECT event_id, body FROM events ORDER BY created_at ASC, rowid ASC LIMIT ?'),
|
|
49
|
+
del: this.db.prepare('DELETE FROM events WHERE event_id = ?'),
|
|
50
|
+
bump: this.db.prepare('UPDATE events SET attempts = attempts + 1 WHERE event_id = ?'),
|
|
51
|
+
drop: this.db.prepare('DELETE FROM events WHERE event_id = ? AND attempts >= ?'),
|
|
52
|
+
count: this.db.prepare('SELECT COUNT(*) AS n FROM events'),
|
|
53
|
+
checkpoint: this.db.prepare(`INSERT INTO checkpoints (path, inode, offset, size) VALUES (?, ?, ?, ?)
|
|
54
|
+
ON CONFLICT(path) DO UPDATE SET inode = excluded.inode, offset = excluded.offset, size = excluded.size`),
|
|
55
|
+
getMeta: this.db.prepare('SELECT value FROM meta WHERE key = ?'),
|
|
56
|
+
setMeta: this.db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value'),
|
|
57
|
+
delMeta: this.db.prepare('DELETE FROM meta WHERE key = ?'),
|
|
58
|
+
};
|
|
59
|
+
this.loadCheckpoints();
|
|
60
|
+
}
|
|
61
|
+
loadCheckpoints() {
|
|
62
|
+
const rows = this.db.prepare('SELECT path, inode, offset, size FROM checkpoints').all();
|
|
63
|
+
this.checkpoints.clear();
|
|
64
|
+
for (const { path: p, ...cp } of rows)
|
|
65
|
+
this.checkpoints.set(p, cp);
|
|
34
66
|
}
|
|
35
67
|
migrate() {
|
|
36
68
|
this.db.exec(`
|
|
@@ -55,24 +87,50 @@ export class Spool {
|
|
|
55
87
|
);
|
|
56
88
|
`);
|
|
57
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* A drained backfill leaves the file at its high-water mark, nearly all of
|
|
92
|
+
* it free pages (a 206MB spool holding 78 events was observed). VACUUM once
|
|
93
|
+
* at open when the freelist is both large and the majority of the file —
|
|
94
|
+
* never on a small db, where it is pure cost.
|
|
95
|
+
*/
|
|
96
|
+
vacuumIfBloated() {
|
|
97
|
+
const free = Number(this.db.pragma('freelist_count', { simple: true }));
|
|
98
|
+
const pages = Number(this.db.pragma('page_count', { simple: true }));
|
|
99
|
+
if (free > 2048 && free > pages / 2) {
|
|
100
|
+
try {
|
|
101
|
+
this.db.exec('VACUUM');
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Out of disk or a concurrent reader: the queue still works, just fat.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Runs `fn` atomically. A throw rolls back everything written inside it. */
|
|
109
|
+
transaction(fn) {
|
|
110
|
+
try {
|
|
111
|
+
return this.db.transaction(fn)();
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
// setCheckpoint() updated the cache before the rollback (or a failed
|
|
115
|
+
// COMMIT — SQLITE_FULL) undid the row; bring it back in line with disk.
|
|
116
|
+
this.loadCheckpoints();
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
58
120
|
enqueue(events) {
|
|
59
121
|
if (events.length === 0)
|
|
60
122
|
return 0;
|
|
61
|
-
const stmt = this.db.prepare('INSERT OR IGNORE INTO events (event_id, body, created_at) VALUES (?, ?, ?)');
|
|
62
123
|
const now = Date.now();
|
|
63
|
-
|
|
124
|
+
return this.transaction(() => {
|
|
64
125
|
let written = 0;
|
|
65
|
-
for (const event of
|
|
66
|
-
written += stmt.run(event.event_id, JSON.stringify(event), now).changes;
|
|
126
|
+
for (const event of events)
|
|
127
|
+
written += this.stmt.insert.run(event.event_id, JSON.stringify(event), now).changes;
|
|
67
128
|
return written;
|
|
68
129
|
});
|
|
69
|
-
return insertAll(events);
|
|
70
130
|
}
|
|
71
131
|
/** Oldest-first so a backlog drains in the order it happened. */
|
|
72
132
|
peek(limit) {
|
|
73
|
-
const rows = this.
|
|
74
|
-
.prepare('SELECT event_id, body FROM events ORDER BY created_at ASC, rowid ASC LIMIT ?')
|
|
75
|
-
.all(limit);
|
|
133
|
+
const rows = this.stmt.peek.all(limit);
|
|
76
134
|
return rows.flatMap((row) => {
|
|
77
135
|
try {
|
|
78
136
|
return [{ eventId: row.event_id, event: JSON.parse(row.body) }];
|
|
@@ -87,8 +145,7 @@ export class Spool {
|
|
|
87
145
|
ack(eventIds) {
|
|
88
146
|
if (eventIds.length === 0)
|
|
89
147
|
return;
|
|
90
|
-
|
|
91
|
-
this.db.transaction((ids) => ids.forEach((id) => stmt.run(id)))(eventIds);
|
|
148
|
+
this.transaction(() => eventIds.forEach((id) => this.stmt.del.run(id)));
|
|
92
149
|
}
|
|
93
150
|
/**
|
|
94
151
|
* Records a failed attempt so poison events can be dropped eventually.
|
|
@@ -104,37 +161,36 @@ export class Spool {
|
|
|
104
161
|
if (eventIds.length === 0)
|
|
105
162
|
return 0;
|
|
106
163
|
const limit = Math.max(1, maxAttempts);
|
|
107
|
-
|
|
108
|
-
const drop = this.db.prepare('DELETE FROM events WHERE event_id = ? AND attempts >= ?');
|
|
109
|
-
return this.db.transaction((ids) => {
|
|
164
|
+
return this.transaction(() => {
|
|
110
165
|
let dropped = 0;
|
|
111
|
-
for (const id of
|
|
112
|
-
bump.run(id);
|
|
113
|
-
dropped += drop.run(id, limit).changes;
|
|
166
|
+
for (const id of eventIds) {
|
|
167
|
+
this.stmt.bump.run(id);
|
|
168
|
+
dropped += this.stmt.drop.run(id, limit).changes;
|
|
114
169
|
}
|
|
115
170
|
return dropped;
|
|
116
|
-
})
|
|
171
|
+
});
|
|
117
172
|
}
|
|
118
173
|
depth() {
|
|
119
|
-
return this.
|
|
174
|
+
return this.stmt.count.get().n;
|
|
120
175
|
}
|
|
121
176
|
getCheckpoint(path) {
|
|
122
|
-
return
|
|
177
|
+
return this.checkpoints.get(path) ?? null;
|
|
123
178
|
}
|
|
124
179
|
setCheckpoint(path, inode, offset, size) {
|
|
125
|
-
this.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
180
|
+
const current = this.checkpoints.get(path);
|
|
181
|
+
if (current && current.inode === inode && current.offset === offset && current.size === size)
|
|
182
|
+
return;
|
|
183
|
+
this.stmt.checkpoint.run(path, inode, offset, size);
|
|
184
|
+
this.checkpoints.set(path, { inode, offset, size });
|
|
129
185
|
}
|
|
130
186
|
getMeta(key) {
|
|
131
|
-
return
|
|
132
|
-
null);
|
|
187
|
+
return this.stmt.getMeta.get(key)?.value ?? null;
|
|
133
188
|
}
|
|
134
189
|
setMeta(key, value) {
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
190
|
+
this.stmt.setMeta.run(key, value);
|
|
191
|
+
}
|
|
192
|
+
deleteMeta(key) {
|
|
193
|
+
this.stmt.delMeta.run(key);
|
|
138
194
|
}
|
|
139
195
|
/** Stable per-install id, generated once. */
|
|
140
196
|
installId() {
|
package/dist/queue/spool.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spool.js","sourceRoot":"","sources":["../../src/queue/spool.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"spool.js","sourceRoot":"","sources":["../../src/queue/spool.ts"],"names":[],"mappings":"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"}
|
package/dist/queue/tailer.d.ts
CHANGED
|
@@ -2,8 +2,11 @@ import type { Spool } from './spool.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Resumable line tailer.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Reads in fixed-size chunks and hands every complete line to `onLines`; the
|
|
6
|
+
* checkpoint (path, inode, offset) for a chunk is written in the SAME
|
|
7
|
+
* transaction as whatever `onLines` spooled, so a throw or a crash between
|
|
8
|
+
* "read" and "queued" re-reads those lines instead of losing them. Failure
|
|
9
|
+
* modes it must survive:
|
|
7
10
|
*
|
|
8
11
|
* - rotation/replacement: the inode changes, so the old offset is meaningless
|
|
9
12
|
* and we start from zero.
|
|
@@ -14,12 +17,32 @@ import type { Spool } from './spool.js';
|
|
|
14
17
|
* remainder arrives on the next pass as an orphaned fragment and both
|
|
15
18
|
* halves fail to parse. So the checkpoint stops at the last complete
|
|
16
19
|
* newline and the partial line is re-read whole next time.
|
|
20
|
+
* - a pathological line: anything over `maxLineBytes` is skipped to the next
|
|
21
|
+
* newline and counted, never buffered — one 512MiB line used to throw and
|
|
22
|
+
* abort every later file on every scan.
|
|
23
|
+
* - a huge backlog: at most `maxBytesPerScan` per file per call, so a first
|
|
24
|
+
* import yields to the upload loop instead of reading 27MB files back to
|
|
25
|
+
* back.
|
|
17
26
|
*
|
|
18
27
|
* Offsets are byte offsets, computed from the buffer rather than from decoded
|
|
19
28
|
* strings — a multi-byte character would otherwise desynchronise the position.
|
|
20
29
|
*/
|
|
30
|
+
export interface TailedLine {
|
|
31
|
+
text: string;
|
|
32
|
+
/** Byte offset of the line's first byte. Stable for an append-only file. */
|
|
33
|
+
offset: number;
|
|
34
|
+
}
|
|
21
35
|
export interface TailResult {
|
|
22
|
-
lines:
|
|
36
|
+
lines: number;
|
|
23
37
|
bytesRead: number;
|
|
38
|
+
/** Lines over `maxLineBytes`, discarded. */
|
|
39
|
+
skipped: number;
|
|
40
|
+
/** True when `maxBytesPerScan` stopped the read before EOF. */
|
|
41
|
+
more: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface TailOptions {
|
|
44
|
+
chunkBytes?: number;
|
|
45
|
+
maxLineBytes?: number;
|
|
46
|
+
maxBytesPerScan?: number;
|
|
24
47
|
}
|
|
25
|
-
export declare function tailFile(path: string, spool: Spool): Promise<TailResult>;
|
|
48
|
+
export declare function tailFile(path: string, spool: Spool, onLines: (lines: TailedLine[]) => void, options?: TailOptions): Promise<TailResult>;
|
package/dist/queue/tailer.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
2
|
import { open } from 'node:fs/promises';
|
|
3
3
|
const NEWLINE = 0x0a;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
4
|
+
const CR = 0x0d;
|
|
5
|
+
const EMPTY = Buffer.alloc(0);
|
|
6
|
+
export async function tailFile(path, spool, onLines, options = {}) {
|
|
7
|
+
const chunkBytes = options.chunkBytes ?? 4 * 1024 * 1024;
|
|
8
|
+
const maxLineBytes = options.maxLineBytes ?? 8 * 1024 * 1024;
|
|
9
|
+
const maxBytesPerScan = options.maxBytesPerScan ?? 64 * 1024 * 1024;
|
|
10
|
+
const result = { lines: 0, bytesRead: 0, skipped: 0, more: false };
|
|
11
|
+
const stats = statSync(path, { throwIfNoEntry: false });
|
|
12
|
+
if (!stats)
|
|
13
|
+
return result;
|
|
8
14
|
const inode = String(stats.ino);
|
|
9
15
|
const checkpoint = spool.getCheckpoint(path);
|
|
10
16
|
let start = 0;
|
|
@@ -14,35 +20,90 @@ export async function tailFile(path, spool) {
|
|
|
14
20
|
// else: rotated (inode differs) or truncated (offset > size) — reread from 0.
|
|
15
21
|
if (start >= stats.size) {
|
|
16
22
|
spool.setCheckpoint(path, inode, stats.size, stats.size);
|
|
17
|
-
return
|
|
23
|
+
return result;
|
|
18
24
|
}
|
|
19
|
-
const length = stats.size - start;
|
|
20
|
-
const buffer = Buffer.allocUnsafe(length);
|
|
21
25
|
const handle = await open(path, 'r');
|
|
22
26
|
try {
|
|
23
|
-
|
|
27
|
+
let position = start;
|
|
28
|
+
let carry = EMPTY;
|
|
29
|
+
// Inside a line that already exceeded maxLineBytes: drop bytes up to and
|
|
30
|
+
// including the next newline.
|
|
31
|
+
let skipping = false;
|
|
32
|
+
while (position < stats.size && result.bytesRead < maxBytesPerScan) {
|
|
33
|
+
const want = Math.min(chunkBytes, stats.size - position);
|
|
34
|
+
const chunk = Buffer.allocUnsafe(want);
|
|
35
|
+
let got = 0;
|
|
36
|
+
// read() may return short; loop until the chunk is full or the file
|
|
37
|
+
// turned out shorter than stat said (truncated under us).
|
|
38
|
+
while (got < want) {
|
|
39
|
+
const { bytesRead } = await handle.read(chunk, got, want - got, position + got);
|
|
40
|
+
if (bytesRead === 0)
|
|
41
|
+
break;
|
|
42
|
+
got += bytesRead;
|
|
43
|
+
}
|
|
44
|
+
if (got === 0)
|
|
45
|
+
break;
|
|
46
|
+
result.bytesRead += got;
|
|
47
|
+
const data = carry.length ? Buffer.concat([carry, chunk.subarray(0, got)]) : chunk.subarray(0, got);
|
|
48
|
+
const dataStart = position - carry.length;
|
|
49
|
+
position += got;
|
|
50
|
+
const lastNewline = data.lastIndexOf(NEWLINE);
|
|
51
|
+
if (lastNewline === -1) {
|
|
52
|
+
// No complete line in what we have. Either keep waiting for the
|
|
53
|
+
// newline or, past the cap, give up on this line entirely.
|
|
54
|
+
if (skipping || data.length > maxLineBytes) {
|
|
55
|
+
if (!skipping)
|
|
56
|
+
result.skipped += 1;
|
|
57
|
+
skipping = true;
|
|
58
|
+
carry = EMPTY;
|
|
59
|
+
spool.setCheckpoint(path, inode, position, stats.size);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
carry = Buffer.from(data);
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const lines = [];
|
|
67
|
+
let lineStart = 0;
|
|
68
|
+
while (lineStart <= lastNewline) {
|
|
69
|
+
const nl = data.indexOf(NEWLINE, lineStart);
|
|
70
|
+
const end = nl > lineStart && data[nl - 1] === CR ? nl - 1 : nl;
|
|
71
|
+
if (skipping) {
|
|
72
|
+
skipping = false; // this newline terminates the oversized line
|
|
73
|
+
}
|
|
74
|
+
else if (nl - lineStart > maxLineBytes) {
|
|
75
|
+
result.skipped += 1;
|
|
76
|
+
}
|
|
77
|
+
else if (end > lineStart) {
|
|
78
|
+
lines.push({ text: data.toString('utf8', lineStart, end), offset: dataStart + lineStart });
|
|
79
|
+
}
|
|
80
|
+
lineStart = nl + 1;
|
|
81
|
+
}
|
|
82
|
+
const consumed = dataStart + lastNewline + 1;
|
|
83
|
+
// Lines and their checkpoint commit together, or not at all.
|
|
84
|
+
spool.transaction(() => {
|
|
85
|
+
if (lines.length > 0)
|
|
86
|
+
onLines(lines);
|
|
87
|
+
spool.setCheckpoint(path, inode, consumed, stats.size);
|
|
88
|
+
});
|
|
89
|
+
result.lines += lines.length;
|
|
90
|
+
const rest = data.subarray(lastNewline + 1);
|
|
91
|
+
if (rest.length > maxLineBytes) {
|
|
92
|
+
result.skipped += 1;
|
|
93
|
+
skipping = true;
|
|
94
|
+
carry = EMPTY;
|
|
95
|
+
spool.setCheckpoint(path, inode, position, stats.size);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Copy, so the 4MB chunk behind the slice can be collected.
|
|
99
|
+
carry = rest.length ? Buffer.from(rest) : EMPTY;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
result.more = position < stats.size;
|
|
24
103
|
}
|
|
25
104
|
finally {
|
|
26
105
|
await handle.close();
|
|
27
106
|
}
|
|
28
|
-
|
|
29
|
-
// agent has not finished writing.
|
|
30
|
-
const lastNewline = buffer.lastIndexOf(NEWLINE);
|
|
31
|
-
if (lastNewline === -1) {
|
|
32
|
-
// No complete line yet — leave the checkpoint untouched so the whole
|
|
33
|
-
// partial line is re-read once it is terminated.
|
|
34
|
-
return { lines: [], bytesRead: 0 };
|
|
35
|
-
}
|
|
36
|
-
const consumable = buffer.subarray(0, lastNewline + 1);
|
|
37
|
-
const lines = consumable
|
|
38
|
-
.toString('utf8')
|
|
39
|
-
.split('\n')
|
|
40
|
-
// The trailing element after the final \n is always '' — drop it, and drop
|
|
41
|
-
// any blank lines rather than handing '' to a parser.
|
|
42
|
-
.filter((line) => line.length > 0)
|
|
43
|
-
.map((line) => (line.endsWith('\r') ? line.slice(0, -1) : line));
|
|
44
|
-
const consumed = start + consumable.length;
|
|
45
|
-
spool.setCheckpoint(path, inode, consumed, stats.size);
|
|
46
|
-
return { lines, bytesRead: consumable.length };
|
|
107
|
+
return result;
|
|
47
108
|
}
|
|
48
109
|
//# sourceMappingURL=tailer.js.map
|
package/dist/queue/tailer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tailer.js","sourceRoot":"","sources":["../../src/queue/tailer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"tailer.js","sourceRoot":"","sources":["../../src/queue/tailer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAoDxC,MAAM,OAAO,GAAG,IAAI,CAAC;AACrB,MAAM,EAAE,GAAG,IAAI,CAAC;AAChB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAE9B,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,KAAY,EACZ,OAAsC,EACtC,UAAuB,EAAE;IAEzB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IACzD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IAC7D,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;IACpE,MAAM,MAAM,GAAe,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAE/E,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAE7C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,UAAU,IAAI,UAAU,CAAC,KAAK,KAAK,KAAK,IAAI,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAChF,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC;IAC5B,CAAC;IACD,8EAA8E;IAE9E,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACzD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,yEAAyE;QACzE,8BAA8B;QAC9B,IAAI,QAAQ,GAAG,KAAK,CAAC;QAErB,OAAO,QAAQ,GAAG,KAAK,CAAC,IAAI,IAAI,MAAM,CAAC,SAAS,GAAG,eAAe,EAAE,CAAC;YACnE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;YACzD,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,GAAG,GAAG,CAAC,CAAC;YACZ,oEAAoE;YACpE,0DAA0D;YAC1D,OAAO,GAAG,GAAG,IAAI,EAAE,CAAC;gBAClB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,GAAG,EAAE,QAAQ,GAAG,GAAG,CAAC,CAAC;gBAChF,IAAI,SAAS,KAAK,CAAC;oBAAE,MAAM;gBAC3B,GAAG,IAAI,SAAS,CAAC;YACnB,CAAC;YACD,IAAI,GAAG,KAAK,CAAC;gBAAE,MAAM;YACrB,MAAM,CAAC,SAAS,IAAI,GAAG,CAAC;YAExB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACpG,MAAM,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;YAC1C,QAAQ,IAAI,GAAG,CAAC;YAEhB,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,WAAW,KAAK,CAAC,CAAC,EAAE,CAAC;gBACvB,gEAAgE;gBAChE,2DAA2D;gBAC3D,IAAI,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;oBAC3C,IAAI,CAAC,QAAQ;wBAAE,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;oBACnC,QAAQ,GAAG,IAAI,CAAC;oBAChB,KAAK,GAAG,KAAK,CAAC;oBACd,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBACzD,CAAC;qBAAM,CAAC;oBACN,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5B,CAAC;gBACD,SAAS;YACX,CAAC;YAED,MAAM,KAAK,GAAiB,EAAE,CAAC;YAC/B,IAAI,SAAS,GAAG,CAAC,CAAC;YAClB,OAAO,SAAS,IAAI,WAAW,EAAE,CAAC;gBAChC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;gBAC5C,MAAM,GAAG,GAAG,EAAE,GAAG,SAAS,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChE,IAAI,QAAQ,EAAE,CAAC;oBACb,QAAQ,GAAG,KAAK,CAAC,CAAC,6CAA6C;gBACjE,CAAC;qBAAM,IAAI,EAAE,GAAG,SAAS,GAAG,YAAY,EAAE,CAAC;oBACzC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;gBACtB,CAAC;qBAAM,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;oBAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC,CAAC;gBAC7F,CAAC;gBACD,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;YACrB,CAAC;YAED,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,CAAC,CAAC;YAC7C,6DAA6D;YAC7D,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;gBACrB,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO,CAAC,KAAK,CAAC,CAAC;gBACrC,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACzD,CAAC,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;YAE7B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;YAC5C,IAAI,IAAI,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC;gBAC/B,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;gBACpB,QAAQ,GAAG,IAAI,CAAC;gBAChB,KAAK,GAAG,KAAK,CAAC;gBACd,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,4DAA4D;gBAC5D,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAClD,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI,GAAG,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC;IACtC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import type { EventEnvelope } from '../schema.js';
|
|
3
|
+
import type { MachineInfo } from '../machine.js';
|
|
4
|
+
/** Read from package.json so the CLI, register and health can never drift from what is published. */
|
|
5
|
+
export declare const VERSION: string;
|
|
2
6
|
/**
|
|
3
7
|
* HTTP client for the AgentsTrack API.
|
|
4
8
|
*
|
|
@@ -10,14 +14,25 @@ export interface ClientOptions {
|
|
|
10
14
|
apiKey: string;
|
|
11
15
|
timeoutMs?: number;
|
|
12
16
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The batch response is a trust boundary: a proxy error page or a newer
|
|
19
|
+
* server shape must fail loudly here, not surface as `undefined.length` deep
|
|
20
|
+
* in the upload policy. `quota` arrives only from servers that meter events.
|
|
21
|
+
*/
|
|
22
|
+
export declare const BatchResult: z.ZodObject<{
|
|
23
|
+
accepted: z.ZodNumber;
|
|
24
|
+
duplicates: z.ZodNumber;
|
|
25
|
+
rejected: z.ZodArray<z.ZodObject<{
|
|
26
|
+
index: z.ZodNumber;
|
|
27
|
+
reason: z.ZodString;
|
|
28
|
+
}, z.core.$strip>>;
|
|
29
|
+
quota: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
limit: z.ZodNullable<z.ZodNumber>;
|
|
31
|
+
used: z.ZodNullable<z.ZodNumber>;
|
|
32
|
+
exceeded: z.ZodBoolean;
|
|
33
|
+
}, z.core.$strip>>;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
export type BatchResult = z.infer<typeof BatchResult>;
|
|
21
36
|
export interface ServerConfig {
|
|
22
37
|
privacy_mode: 'metadata' | 'analytics' | 'full';
|
|
23
38
|
idle_timeout_seconds: number;
|
|
@@ -33,18 +48,25 @@ export declare class ApiError extends Error {
|
|
|
33
48
|
readonly status: number;
|
|
34
49
|
/** Whether trying again later could succeed. */
|
|
35
50
|
readonly retryable: boolean;
|
|
51
|
+
/** Server-requested minimum wait before the next attempt, from Retry-After. */
|
|
52
|
+
readonly retryAfterMs?: number | undefined;
|
|
36
53
|
constructor(message: string, status: number,
|
|
37
54
|
/** Whether trying again later could succeed. */
|
|
38
|
-
retryable: boolean
|
|
55
|
+
retryable: boolean,
|
|
56
|
+
/** Server-requested minimum wait before the next attempt, from Retry-After. */
|
|
57
|
+
retryAfterMs?: number | undefined);
|
|
39
58
|
}
|
|
40
59
|
export declare class ApiClient {
|
|
41
60
|
private readonly options;
|
|
42
61
|
constructor(options: ClientOptions);
|
|
43
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Register and health both carry `MachineInfo`. The server keeps hostname,
|
|
64
|
+
* os, release, arch, machine kind and the request IP for operations and
|
|
65
|
+
* abuse prevention only; none of it is shown in the product.
|
|
66
|
+
*/
|
|
67
|
+
registerCollector(input: Partial<MachineInfo> & {
|
|
44
68
|
hostname: string;
|
|
45
69
|
label?: string;
|
|
46
|
-
os?: string;
|
|
47
|
-
arch?: string;
|
|
48
70
|
version?: string;
|
|
49
71
|
/** The mode this device will actually enforce, which may be stricter than the org's. */
|
|
50
72
|
privacy_mode?: string;
|
|
@@ -58,7 +80,7 @@ export declare class ApiClient {
|
|
|
58
80
|
}>;
|
|
59
81
|
getConfig(): Promise<ServerConfig>;
|
|
60
82
|
sendBatch(events: EventEnvelope[]): Promise<BatchResult>;
|
|
61
|
-
health(input: {
|
|
83
|
+
health(input: Partial<MachineInfo> & {
|
|
62
84
|
collector_id: string;
|
|
63
85
|
queue_depth: number;
|
|
64
86
|
version?: string;
|
|
@@ -72,5 +94,7 @@ export declare class ApiClient {
|
|
|
72
94
|
}>;
|
|
73
95
|
private request;
|
|
74
96
|
}
|
|
97
|
+
/** Retry-After is either delta-seconds or an HTTP date. Unparseable means no request. */
|
|
98
|
+
export declare function retryAfterMs(header: string | null): number | undefined;
|
|
75
99
|
/** Exponential backoff with jitter, capped. */
|
|
76
100
|
export declare function backoffMs(attempt: number, baseMs?: number, capMs?: number): number;
|