@agentstrack/collector 0.2.1 → 0.4.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +209 -1
  2. package/README.md +114 -38
  3. package/dist/adapters/claude.d.ts +18 -0
  4. package/dist/adapters/claude.js +153 -45
  5. package/dist/adapters/claude.js.map +1 -1
  6. package/dist/adapters/codex.d.ts +15 -1
  7. package/dist/adapters/codex.js +87 -34
  8. package/dist/adapters/codex.js.map +1 -1
  9. package/dist/adapters/opencode.d.ts +17 -6
  10. package/dist/adapters/opencode.js +72 -26
  11. package/dist/adapters/opencode.js.map +1 -1
  12. package/dist/adapters/types.d.ts +16 -0
  13. package/dist/adapters/types.js +61 -0
  14. package/dist/adapters/types.js.map +1 -1
  15. package/dist/cli.js +164 -33
  16. package/dist/cli.js.map +1 -1
  17. package/dist/commands/service.js +40 -10
  18. package/dist/commands/service.js.map +1 -1
  19. package/dist/config.d.ts +1 -1
  20. package/dist/config.js +18 -5
  21. package/dist/config.js.map +1 -1
  22. package/dist/daemon.d.ts +73 -21
  23. package/dist/daemon.js +350 -119
  24. package/dist/daemon.js.map +1 -1
  25. package/dist/git/commits.d.ts +7 -1
  26. package/dist/git/commits.js +36 -17
  27. package/dist/git/commits.js.map +1 -1
  28. package/dist/git/repo.d.ts +13 -4
  29. package/dist/git/repo.js +34 -20
  30. package/dist/git/repo.js.map +1 -1
  31. package/dist/machine.d.ts +27 -0
  32. package/dist/machine.js +46 -0
  33. package/dist/machine.js.map +1 -0
  34. package/dist/privacy/pipeline.d.ts +6 -0
  35. package/dist/privacy/pipeline.js +41 -7
  36. package/dist/privacy/pipeline.js.map +1 -1
  37. package/dist/privacy/redact.d.ts +15 -2
  38. package/dist/privacy/redact.js +45 -6
  39. package/dist/privacy/redact.js.map +1 -1
  40. package/dist/queue/event-id.d.ts +9 -0
  41. package/dist/queue/event-id.js +15 -0
  42. package/dist/queue/event-id.js.map +1 -0
  43. package/dist/queue/spool.d.ts +24 -5
  44. package/dist/queue/spool.js +89 -33
  45. package/dist/queue/spool.js.map +1 -1
  46. package/dist/queue/tailer.d.ts +27 -4
  47. package/dist/queue/tailer.js +89 -28
  48. package/dist/queue/tailer.js.map +1 -1
  49. package/dist/sessions/title.d.ts +14 -2
  50. package/dist/sessions/title.js +18 -6
  51. package/dist/sessions/title.js.map +1 -1
  52. package/dist/transport/client.d.ts +37 -13
  53. package/dist/transport/client.js +50 -3
  54. package/dist/transport/client.js.map +1 -1
  55. package/package.json +2 -2
package/dist/daemon.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { loadConfig, type Config } from './config.js';
2
+ import { Spool } from './queue/spool.js';
3
+ import { deterministicEventId } from './queue/event-id.js';
4
+ export { deterministicEventId };
5
+ import { ApiClient, VERSION } from './transport/client.js';
2
6
  import type { AgentAdapter } from './adapters/types.js';
7
+ export { VERSION };
3
8
  export declare function log(message: string): void;
4
9
  export declare function buildAdapters(config: Config): AgentAdapter[];
5
10
  /**
@@ -14,6 +19,13 @@ export declare function buildAdapters(config: Config): AgentAdapter[];
14
19
  export declare function attributable(eventType: string, occurredAt: string, liveSinceMs: number): boolean;
15
20
  /** Recursively lists .jsonl files under a directory, newest first. */
16
21
  export declare function listTranscripts(dir: string, maxAgeDays?: number): string[];
22
+ /** Splits one peeked wave into `batchSize` slices, every event in exactly one. */
23
+ export declare function chunkWave<T>(wave: T[], batchSize: number): T[][];
24
+ type PauseReason = 'auth' | 'quota' | 'schema';
25
+ /** Spool meta key naming the current upload pause (`<reason>: <detail>`); `status` prints it. */
26
+ export declare const UPLOAD_PAUSE_META = "upload_paused_reason";
27
+ /** The subset of ApiClient the daemon uses — a test hands in a fake. */
28
+ type Transport = Pick<ApiClient, 'registerCollector' | 'getConfig' | 'sendBatch' | 'health'>;
17
29
  export declare class Collector {
18
30
  private config;
19
31
  private readonly spool;
@@ -22,6 +34,7 @@ export declare class Collector {
22
34
  /** Null when git metadata is switched off — then we never shell out to git. */
23
35
  private readonly commitWatcher;
24
36
  private serverConfig;
37
+ private orgRules;
25
38
  /**
26
39
  * When this collector started, and therefore the earliest event it can
27
40
  * honestly attribute to an account.
@@ -35,10 +48,22 @@ export declare class Collector {
35
48
  */
36
49
  private readonly liveSinceMs;
37
50
  private uploadFailures;
38
- /** Shrinks on 413, recovers on success. Never below 1. */
51
+ /** Shrinks on 413, recovers on success. Never below 1, never above maxBatchSize. */
39
52
  private batchSize;
53
+ /** Local batch_size clamped to the server's max_batch_events. */
54
+ private maxBatchSize;
55
+ /** Uploads are gated on this instead of sleeping, so scanning never stops. */
56
+ private nextUploadAt;
57
+ private pausedReason;
58
+ /** cwd -> repo, valid for one scan pass. */
59
+ private readonly repoCache;
60
+ /** agent::session_id -> last activity, for idle session.ended. */
61
+ private readonly openSessions;
40
62
  private running;
41
- constructor(config: Config);
63
+ constructor(config: Config, deps?: {
64
+ spool?: Spool;
65
+ client?: Transport;
66
+ });
42
67
  start(): Promise<void>;
43
68
  stop(): void;
44
69
  /** Registers this device once and remembers the id. */
@@ -46,34 +71,61 @@ export declare class Collector {
46
71
  private refreshServerConfig;
47
72
  /** One pass over every tracked transcript file. */
48
73
  private scan;
74
+ /**
75
+ * Polls a database-backed adapter. Its cursors and "already started" markers
76
+ * are buffered and written in the same transaction as the events — an
77
+ * enqueue that fails (SQLITE_FULL) must not leave a cursor pointing past
78
+ * rows that were never spooled, or a session marked started that never was.
79
+ */
80
+ private pollAdapter;
81
+ /**
82
+ * Emits session.ended for every tracked session quiet for longer than
83
+ * `idleMs`. Claude Code and Codex never write an end marker, so without this
84
+ * their sessions stay in_progress on the server forever. `occurred_at` is
85
+ * when the timeout elapsed, not now: a backfilled session ended back then.
86
+ */
87
+ private endIdleSessions;
88
+ private repoFor;
49
89
  private enqueue;
50
- /** Drains the spool, oldest first, until it is empty or the server pushes back. */
90
+ private trackSession;
51
91
  /**
52
- * Drains the spool.
92
+ * Drains the spool, one wave of `upload.concurrency` batches at a time.
53
93
  *
54
- * Batches go out `upload.concurrency` at a time. Uploading is round-trip
55
- * bound, not bandwidth bound a first import moved ~330 events/s
56
- * sequentially, which is one 100-event batch per ~300ms of mostly waiting
57
- * so sending several at once divides the wall clock of a backfill by roughly
58
- * that number.
94
+ * Uploading is round-trip bound, not bandwidth bound — a first import moved
95
+ * ~330 events/s sequentially, which is one 100-event batch per ~300ms of
96
+ * mostly waiting so sending several at once divides the wall clock of a
97
+ * backfill by roughly that number. Order is deliberately NOT preserved
98
+ * across in-flight batches: the server derives a session's start from
99
+ * min(recorded start, earliest stored event), so a later batch landing first
100
+ * is corrected once the rest arrive. Within a batch the spool still yields
101
+ * oldest-first.
59
102
  *
60
- * Order is deliberately NOT preserved across in-flight batches, and does not
61
- * need to be: the server derives a session's start from
62
- * min(recorded start, earliest stored event) and re-runs reconstruction after
63
- * every batch, so a later batch arriving first is corrected once the rest
64
- * land. Within a single batch the spool still yields oldest-first.
65
- */
66
- flush(): Promise<void>;
67
- /**
68
- * Sends one batch. Returns false when the wave should stop.
103
+ * The failure policy runs ONCE per wave, on the collected outcomes:
104
+ * - any 413 -> batch size halves once
105
+ * - any retryable -> one backoff step; uploads are gated on nextUploadAt,
106
+ * never slept on, so tailing continues meanwhile
107
+ * - any poison -> strikes for those batches only
108
+ * - 401/403, quota, whole-batch schema rejection -> paused, nothing dropped
109
+ * - every batch ok -> counter reset, batch size creeps back up
69
110
  *
70
- * Every failure branch is the same policy this had when batches went out one
71
- * at a time; only the `return` became `return false`.
111
+ * Returns true when it stopped only because `maxWaves` ran out, i.e. there
112
+ * is more to send right now.
72
113
  */
114
+ flush(maxWaves?: number): Promise<boolean>;
115
+ /** Sends one batch and reports what happened. Touches no shared state. */
73
116
  private sendBatch;
117
+ /** Backs off and records why, once per reason, where `agentstrack status` can read it. */
118
+ private pause;
119
+ private resume;
74
120
  private reportHealth;
75
121
  queueDepth(): number;
122
+ /** Upload policy state, for tests and diagnostics. */
123
+ uploadState(): {
124
+ failures: number;
125
+ batchSize: number;
126
+ nextUploadAt: number;
127
+ paused: PauseReason | null;
128
+ };
76
129
  }
77
- export declare const VERSION = "0.1.0";
78
130
  export declare function errorMessage(error: unknown): string;
79
131
  export { loadConfig };