iteration-cli 0.14.2 → 0.14.3

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/dist/cli.js CHANGED
@@ -939,7 +939,7 @@ import { promises as fs20 } from "node:fs";
939
939
  import { createInterface as createInterface2 } from "node:readline/promises";
940
940
  import { parseArgs } from "node:util";
941
941
 
942
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/util.js
942
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/util.js
943
943
  var util_exports = {};
944
944
  __export(util_exports, {
945
945
  BIGINT_FORMAT_RANGES: () => BIGINT_FORMAT_RANGES,
@@ -1010,6 +1010,7 @@ __export(util_exports, {
1010
1010
  required: () => required,
1011
1011
  safeExtend: () => safeExtend,
1012
1012
  shallowClone: () => shallowClone,
1013
+ sizeCaps: () => sizeCaps,
1013
1014
  slugify: () => slugify,
1014
1015
  stringifyPrimitive: () => stringifyPrimitive,
1015
1016
  toZod: () => toZod,
@@ -1529,6 +1530,18 @@ function prefixIssues(path25, issues) {
1529
1530
  function unwrapMessage(message) {
1530
1531
  return typeof message === "string" ? message : message?.message;
1531
1532
  }
1533
+ function sizeCaps(defChecks) {
1534
+ const caps = [];
1535
+ for (const ch of defChecks ?? []) {
1536
+ const d = ch._zod.def;
1537
+ if (!/_(length|size)$|_equals$/.test(d.check))
1538
+ break;
1539
+ const max = d.maximum ?? d.length ?? d.size;
1540
+ if (max >= 0)
1541
+ caps.push([max, ch]);
1542
+ }
1543
+ return caps;
1544
+ }
1532
1545
  function attachSchema(issues, start2, inst) {
1533
1546
  var _a3;
1534
1547
  for (let i = start2; i < issues.length; i++) {
@@ -1782,7 +1795,7 @@ function constantCatch(value) {
1782
1795
  return fn;
1783
1796
  }
1784
1797
 
1785
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/core.js
1798
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/core.js
1786
1799
  var _a;
1787
1800
  var _zodDesc = { value: void 0, enumerable: false };
1788
1801
  var _E = "captureStackTrace" in Error ? Error : null;
@@ -1900,7 +1913,7 @@ function config(newConfig) {
1900
1913
  return globalConfig;
1901
1914
  }
1902
1915
 
1903
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/errors.js
1916
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/errors.js
1904
1917
  function _getMessage() {
1905
1918
  const internals = this._zod;
1906
1919
  internals.message ?? (internals.message = JSON.stringify(internals.def, jsonStringifyReplacer, 2));
@@ -2015,7 +2028,7 @@ function formatError(error2, mapper = (issue2) => issue2.message) {
2015
2028
  return fieldErrors;
2016
2029
  }
2017
2030
 
2018
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/parse.js
2031
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/parse.js
2019
2032
  function finalizeParams(callee, params) {
2020
2033
  return { callee: params?.callee ?? callee, Err: params?.Err };
2021
2034
  }
@@ -2163,7 +2176,7 @@ var _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
2163
2176
  return _safeParseAsync(_Err)(schema, value, _ctx);
2164
2177
  };
2165
2178
 
2166
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/regexes.js
2179
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/regexes.js
2167
2180
  var cuid = /^[cC][0-9a-z]{6,}$/;
2168
2181
  var cuid2 = /^[0-9a-z]+$/;
2169
2182
  var ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;
@@ -2223,7 +2236,7 @@ var _null = /^null$/i;
2223
2236
  var lowercase = /^[^A-Z]*$/;
2224
2237
  var uppercase = /^[^a-z]*$/;
2225
2238
 
2226
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/checks.js
2239
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/checks.js
2227
2240
  var $ZodCheck = /* @__PURE__ */ $constructor("$ZodCheck", (inst, def) => {
2228
2241
  var _a3;
2229
2242
  inst._zod ?? (inst._zod = {});
@@ -2543,7 +2556,7 @@ var $ZodCheckOverwrite = /* @__PURE__ */ $constructor("$ZodCheckOverwrite", (ins
2543
2556
  };
2544
2557
  });
2545
2558
 
2546
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/doc.js
2559
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/doc.js
2547
2560
  var Doc = class {
2548
2561
  constructor(args = [], closed = {}) {
2549
2562
  this.content = [];
@@ -2584,14 +2597,14 @@ ${content.join("\n")}
2584
2597
  }
2585
2598
  };
2586
2599
 
2587
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/versions.js
2600
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/versions.js
2588
2601
  var version = {
2589
2602
  major: 4,
2590
2603
  minor: 6,
2591
- patch: 4
2604
+ patch: 6
2592
2605
  };
2593
2606
 
2594
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/schemas.js
2607
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/schemas.js
2595
2608
  var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
2596
2609
  var _a3;
2597
2610
  inst ?? (inst = {});
@@ -3206,6 +3219,29 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
3206
3219
  return payload;
3207
3220
  };
3208
3221
  });
3222
+ function capSize(inst, size) {
3223
+ const caps = sizeCaps(inst._zod.def.checks);
3224
+ if (!caps.length)
3225
+ return;
3226
+ const cap = Math.min(...caps.map((c) => c[0]));
3227
+ const parse2 = inst._zod.parse;
3228
+ inst._zod.parse = (payload, ctx) => {
3229
+ const n = size(payload.value);
3230
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
3231
+ for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
3232
+ if (n > max && ch._zod.def.when(payload)) {
3233
+ const start2 = payload.issues.length;
3234
+ ch._zod.check(payload);
3235
+ payload.issues[start2].continue = false;
3236
+ attachSchema(payload.issues, start2, inst);
3237
+ return payload;
3238
+ }
3239
+ }
3240
+ }
3241
+ return parse2(payload, ctx);
3242
+ };
3243
+ }
3244
+ var arraySize = (input) => Array.isArray(input) ? input.length : -1;
3209
3245
  function handleArrayResult(result, final, index) {
3210
3246
  if (result.issues.length) {
3211
3247
  final.issues.push(...prefixIssues(index, result.issues));
@@ -3249,6 +3285,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
3249
3285
  }
3250
3286
  return payload;
3251
3287
  };
3288
+ capSize(inst, arraySize);
3252
3289
  });
3253
3290
  function handlePropertyResult(result, final, key, input, optin, optout) {
3254
3291
  const isPresent = key in input;
@@ -4292,7 +4329,7 @@ function handleRefineResult(result, payload, input, inst) {
4292
4329
  }
4293
4330
  }
4294
4331
 
4295
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/memoizer.js
4332
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/memoizer.js
4296
4333
  var $ZodCyclicError = class extends Error {
4297
4334
  constructor() {
4298
4335
  super(`Cannot parse a reference cycle that closes through a transform`);
@@ -4566,7 +4603,7 @@ function isBackEdge(ctx, value) {
4566
4603
  return backEdges !== void 0 && isRef(value) && backEdges.has(value);
4567
4604
  }
4568
4605
 
4569
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/locales/en.js
4606
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/locales/en.js
4570
4607
  var error = () => {
4571
4608
  const Sizable = {
4572
4609
  string: { unit: "characters", verb: "to have" },
@@ -4691,7 +4728,7 @@ function en_default() {
4691
4728
  };
4692
4729
  }
4693
4730
 
4694
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/registries.js
4731
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/registries.js
4695
4732
  var _a2;
4696
4733
  var $ZodRegistry = class {
4697
4734
  constructor() {
@@ -4739,7 +4776,7 @@ function registry() {
4739
4776
  (_a2 = globalThis).__zod_globalRegistry ?? (_a2.__zod_globalRegistry = registry());
4740
4777
  var globalRegistry = globalThis.__zod_globalRegistry;
4741
4778
 
4742
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/api.js
4779
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/api.js
4743
4780
  function snapshotChecks(def) {
4744
4781
  if (def.checks)
4745
4782
  def.checks = [...def.checks];
@@ -5286,7 +5323,7 @@ function _check(fn, params) {
5286
5323
  return ch;
5287
5324
  }
5288
5325
 
5289
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/to-json-schema.js
5326
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/to-json-schema.js
5290
5327
  function assignProps(target, ...sources) {
5291
5328
  for (const source of sources) {
5292
5329
  for (const key of Reflect.ownKeys(source)) {
@@ -5816,7 +5853,7 @@ var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) =
5816
5853
  return finalize(ctx, schema);
5817
5854
  };
5818
5855
 
5819
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/json-schema-processors.js
5856
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/json-schema-processors.js
5820
5857
  var narrowMin = (agg, key, value) => {
5821
5858
  if (agg[key] === void 0 || value > agg[key])
5822
5859
  agg[key] = value;
@@ -6366,7 +6403,7 @@ var lazyProcessor = (schema, ctx, _json, params) => {
6366
6403
  seen.ref = innerType;
6367
6404
  };
6368
6405
 
6369
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/errors.js
6406
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/errors.js
6370
6407
  var _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
6371
6408
  function _lazyMethod(proto, key, make) {
6372
6409
  Object.defineProperty(proto, key, {
@@ -6411,7 +6448,7 @@ var ZodRealError = /* @__PURE__ */ $constructor("ZodError", initializer2, void 0
6411
6448
  Parent: Error
6412
6449
  });
6413
6450
 
6414
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/parse.js
6451
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/parse.js
6415
6452
  var parse = /* @__PURE__ */ _parse(ZodRealError);
6416
6453
  var parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
6417
6454
  var safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
@@ -6425,7 +6462,7 @@ var safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
6425
6462
  var safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
6426
6463
  var safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
6427
6464
 
6428
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/schemas.js
6465
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/schemas.js
6429
6466
  function _ensureDefaultLocale() {
6430
6467
  if (!globalConfig.localeError)
6431
6468
  config(en_default());
@@ -7447,7 +7484,7 @@ function json(params) {
7447
7484
  return jsonSchema;
7448
7485
  }
7449
7486
 
7450
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/iso.js
7487
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/iso.js
7451
7488
  var iso_exports = {};
7452
7489
  __export(iso_exports, {
7453
7490
  ZodISODate: () => ZodISODate,
@@ -7472,7 +7509,7 @@ function duration2(params) {
7472
7509
  return _isoDuration(ZodISODuration, params);
7473
7510
  }
7474
7511
 
7475
- // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/classic/coerce.js
7512
+ // ../../node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/coerce.js
7476
7513
  var coerce_exports = {};
7477
7514
  __export(coerce_exports, {
7478
7515
  bigint: () => bigint2,
@@ -7497,7 +7534,7 @@ function date3(params) {
7497
7534
  return _coercedDate(ZodDate, params);
7498
7535
  }
7499
7536
 
7500
- // ../trace-contracts/dist/index.js
7537
+ // ../trace-contracts/dist/contracts.js
7501
7538
  var TRACE_EVENT_TYPES = [
7502
7539
  "user_message",
7503
7540
  "assistant_message",
@@ -7585,10 +7622,10 @@ var codexBillingRequestSchema = object({
7585
7622
  mode: _enum(["chatgpt", "api_key", "conflicting"])
7586
7623
  }).strict()).max(5e3)
7587
7624
  }).strict();
7588
- var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7625
+ var UUID_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
7589
7626
  var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
7590
7627
  var unpairedSurrogate = /[\uD800-\uDFFF]/u;
7591
- var traceIdentitySchema = string2().min(1).max(200).refine((value) => !value.includes("\0") && !unpairedSurrogate.test(value), "Trace identifiers cannot contain NUL or unpaired Unicode surrogates.");
7628
+ var traceIdentitySchema = string2().min(1).max(200).refine((value) => !value.includes("\0") && !unpairedSurrogate.test(value), "Trace identifiers cannot contain NUL or unpaired Unicode surrogates.").describe("Must not contain NUL or unpaired Unicode surrogates.");
7592
7629
  var jsonValueSchema = lazy(() => union([
7593
7630
  string2(),
7594
7631
  number2().finite(),
@@ -7619,28 +7656,28 @@ var traceEventSchema = object({
7619
7656
  sequence: number2().int().min(0).max(Number.MAX_SAFE_INTEGER),
7620
7657
  callId: string2().max(255).nullable(),
7621
7658
  projectId: string2().regex(UUID_PATTERN).nullable(),
7622
- occurredAt: string2().regex(TIMESTAMP_PATTERN).refine((value) => !Number.isNaN(Date.parse(value))).nullable(),
7659
+ occurredAt: string2().regex(TIMESTAMP_PATTERN).refine((value) => !Number.isNaN(Date.parse(value))).describe("Must also be a timestamp accepted by Date.parse.").nullable(),
7623
7660
  eventType: _enum(TRACE_EVENT_TYPES),
7624
7661
  payload: jsonPayloadSchema,
7625
7662
  run: runMarkerSchema.optional()
7626
7663
  }).strict();
7627
7664
  var traceEventBatchSchema = object({
7628
7665
  events: array(traceEventSchema).min(1).max(MAX_TRACE_EVENTS_PER_BATCH)
7629
- }).strict();
7666
+ }).strict().describe("Event IDs must be unique within the batch.");
7630
7667
  var acknowledgementSchema = object({
7631
7668
  deviceId: string2().regex(UUID_PATTERN),
7632
7669
  acceptedEventIds: array(string2())
7633
- }).strict();
7670
+ }).strict().describe("Accepted event IDs must match the submitted batch exactly, without duplicates.");
7634
7671
  var episodeCountSchema = number2().int().min(0).max(Number.MAX_SAFE_INTEGER);
7635
- var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.");
7672
+ var collectorFrictionSummarySchema = object({ weekEpisodes: episodeCountSchema, dayEpisodes: episodeCountSchema }).strict().refine((summary) => summary.dayEpisodes <= summary.weekEpisodes, "The last day cannot have more friction episodes than the last week.").describe("dayEpisodes must not exceed weekEpisodes.");
7636
7673
  var sessionCountSchema = number2().int().min(0).max(1e6);
7637
7674
  var collectorHistoryProgressSchema = object({
7638
7675
  sessions: sessionCountSchema,
7639
7676
  readSessions: sessionCountSchema,
7640
7677
  complete: boolean2()
7641
- }).strict().refine((progress) => progress.readSessions <= progress.sessions, "Read sessions cannot exceed listed sessions.").refine((progress) => !progress.complete || progress.readSessions === progress.sessions, "A complete import has read every listed session.");
7678
+ }).strict().refine((progress) => progress.readSessions <= progress.sessions, "Read sessions cannot exceed listed sessions.").refine((progress) => !progress.complete || progress.readSessions === progress.sessions, "A complete import has read every listed session.").describe("readSessions must not exceed sessions; complete requires equal counts.");
7642
7679
  var collectorEnrollmentRequestSchema = object({
7643
- deviceName: string2().trim().min(1).max(160),
7680
+ deviceName: string2().trim().min(1).max(160).describe("Trimmed before length validation."),
7644
7681
  organizationId: string2().regex(UUID_PATTERN).optional(),
7645
7682
  source: _enum(COLLECTOR_SOURCES).optional()
7646
7683
  }).strict();
@@ -7771,6 +7808,129 @@ import { execFile as execFile2 } from "node:child_process";
7771
7808
  import { createInterface } from "node:readline/promises";
7772
7809
  import { promisify as promisify2 } from "node:util";
7773
7810
 
7811
+ // src/collector-fetch.ts
7812
+ import { randomUUID } from "node:crypto";
7813
+ import http from "node:http";
7814
+ import https from "node:https";
7815
+
7816
+ // src/logger.ts
7817
+ function writeLog(line, level) {
7818
+ if (level === "info") console.info(line);
7819
+ else console.error(line);
7820
+ }
7821
+ function createLogger(context, write = writeLog) {
7822
+ return (entry) => {
7823
+ write(
7824
+ JSON.stringify({
7825
+ ...entry,
7826
+ ...context,
7827
+ at: (/* @__PURE__ */ new Date()).toISOString(),
7828
+ pid: process.pid,
7829
+ runtimeVersion: process.env.ITERATION_RUNTIME_VERSION ?? null
7830
+ }),
7831
+ entry.level
7832
+ );
7833
+ };
7834
+ }
7835
+ var collectorLogger = createLogger({ component: "collector" });
7836
+
7837
+ // src/collector-fetch.ts
7838
+ var MAX_RESPONSE_BYTES = 1024 * 1024;
7839
+ var NULL_BODY_STATUSES = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
7840
+ var agents = {
7841
+ "http:": new http.Agent({ keepAlive: false }),
7842
+ "https:": new https.Agent({ keepAlive: false })
7843
+ };
7844
+ var sendCollectorRequest = (input, init = {}) => {
7845
+ const url2 = new URL(input);
7846
+ if (url2.protocol !== "https:" && url2.protocol !== "http:") {
7847
+ throw new TypeError(
7848
+ `Unsupported collector request protocol ${url2.protocol}`
7849
+ );
7850
+ }
7851
+ const body = init.body ?? null;
7852
+ const headers = { ...init.headers };
7853
+ if (body !== null) {
7854
+ headers["content-length"] = String(Buffer.byteLength(body, "utf8"));
7855
+ }
7856
+ const transport = url2.protocol === "https:" ? https : http;
7857
+ return new Promise((resolve, reject) => {
7858
+ const request2 = transport.request(
7859
+ url2,
7860
+ {
7861
+ method: init.method ?? "GET",
7862
+ headers,
7863
+ agent: agents[url2.protocol],
7864
+ signal: init.signal
7865
+ },
7866
+ (response) => {
7867
+ const chunks = [];
7868
+ let size = 0;
7869
+ response.on("data", (chunk) => {
7870
+ size += chunk.byteLength;
7871
+ if (size > MAX_RESPONSE_BYTES) {
7872
+ response.destroy(
7873
+ new Error("Iteration returned an oversized response.")
7874
+ );
7875
+ return;
7876
+ }
7877
+ chunks.push(chunk);
7878
+ });
7879
+ response.on("error", reject);
7880
+ response.on("end", () => {
7881
+ const status2 = response.statusCode ?? 0;
7882
+ const responseHeaders = new Headers();
7883
+ for (const [name, value] of Object.entries(response.headers)) {
7884
+ if (value === void 0) continue;
7885
+ for (const item of Array.isArray(value) ? value : [value]) {
7886
+ responseHeaders.append(name, item);
7887
+ }
7888
+ }
7889
+ resolve(
7890
+ new Response(
7891
+ NULL_BODY_STATUSES.has(status2) ? null : Buffer.concat(chunks),
7892
+ { status: status2, headers: responseHeaders }
7893
+ )
7894
+ );
7895
+ });
7896
+ }
7897
+ );
7898
+ request2.on("error", reject);
7899
+ request2.end(body ?? void 0);
7900
+ });
7901
+ };
7902
+ var collectorFetch = async (input, init = {}) => {
7903
+ const requestId = randomUUID();
7904
+ const started = performance.now();
7905
+ const headers = new Headers(init.headers);
7906
+ headers.set("X-Request-ID", requestId);
7907
+ try {
7908
+ const response = await sendCollectorRequest(input, {
7909
+ ...init,
7910
+ headers: Object.fromEntries(headers)
7911
+ });
7912
+ collectorLogger({
7913
+ level: response.ok ? "info" : "warning",
7914
+ operation: "http_request",
7915
+ requestId,
7916
+ method: init.method ?? "GET",
7917
+ status: response.status,
7918
+ durationMs: Math.round(performance.now() - started)
7919
+ });
7920
+ return response;
7921
+ } catch (error2) {
7922
+ collectorLogger({
7923
+ level: "error",
7924
+ operation: "http_request",
7925
+ requestId,
7926
+ method: init.method ?? "GET",
7927
+ errorName: error2 instanceof Error ? error2.name : "UnknownError",
7928
+ durationMs: Math.round(performance.now() - started)
7929
+ });
7930
+ throw error2;
7931
+ }
7932
+ };
7933
+
7774
7934
  // src/windows-powershell.ts
7775
7935
  import { execFile as execFileCallback } from "node:child_process";
7776
7936
  import path from "node:path";
@@ -7878,9 +8038,12 @@ async function responseJson(response) {
7878
8038
  }
7879
8039
  }
7880
8040
  async function loadOAuthConfiguration(endpoint) {
7881
- const response = await fetch(apiRoute(endpoint, "./collector-auth/config"), {
7882
- signal: AbortSignal.timeout(1e4)
7883
- });
8041
+ const response = await collectorFetch(
8042
+ apiRoute(endpoint, "./collector-auth/config"),
8043
+ {
8044
+ signal: AbortSignal.timeout(1e4)
8045
+ }
8046
+ );
7884
8047
  const body = await responseJson(response);
7885
8048
  if (!response.ok) {
7886
8049
  throw new Error(
@@ -8036,15 +8199,18 @@ async function enrollDevice(endpoint, accessToken, deviceName, organizationId, s
8036
8199
  const body = { deviceName };
8037
8200
  if (source !== "codex") body.source = source;
8038
8201
  if (organizationId !== null) body.organizationId = organizationId;
8039
- const response = await fetch(apiRoute(endpoint, "./collector-auth/enroll"), {
8040
- method: "POST",
8041
- headers: {
8042
- Authorization: "Bearer " + accessToken,
8043
- "Content-Type": "application/json"
8044
- },
8045
- body: JSON.stringify(body),
8046
- signal: AbortSignal.timeout(15e3)
8047
- });
8202
+ const response = await collectorFetch(
8203
+ apiRoute(endpoint, "./collector-auth/enroll"),
8204
+ {
8205
+ method: "POST",
8206
+ headers: {
8207
+ Authorization: "Bearer " + accessToken,
8208
+ "Content-Type": "application/json"
8209
+ },
8210
+ body: JSON.stringify(body),
8211
+ signal: AbortSignal.timeout(15e3)
8212
+ }
8213
+ );
8048
8214
  const payload = await responseJson(response);
8049
8215
  const parsed = parseCollectorEnrollmentResponse(payload);
8050
8216
  if (response.ok && parsed.success) return parsed.value;
@@ -8824,8 +8990,8 @@ import { promisify as promisify5 } from "node:util";
8824
8990
  import { execFile as execFileCallback3 } from "node:child_process";
8825
8991
  import { createHash as createHash2 } from "node:crypto";
8826
8992
  import { createWriteStream, promises as fs4 } from "node:fs";
8827
- import http from "node:http";
8828
- import https from "node:https";
8993
+ import http2 from "node:http";
8994
+ import https2 from "node:https";
8829
8995
  import path4 from "node:path";
8830
8996
  import { pipeline } from "node:stream/promises";
8831
8997
  import { Transform } from "node:stream";
@@ -8871,7 +9037,7 @@ function validateRegistryUrl(value) {
8871
9037
  return url2;
8872
9038
  }
8873
9039
  function request(url2, accept, signal) {
8874
- const transport = url2.protocol === "https:" ? https : http;
9040
+ const transport = url2.protocol === "https:" ? https2 : http2;
8875
9041
  return new Promise((resolve, reject) => {
8876
9042
  const outgoing = transport.request(
8877
9043
  url2,
@@ -9859,74 +10025,6 @@ async function backgroundServiceUsesRuntime(directory, source) {
9859
10025
  ) === true;
9860
10026
  }
9861
10027
 
9862
- // src/collector-fetch.ts
9863
- import http2 from "node:http";
9864
- import https2 from "node:https";
9865
- var MAX_RESPONSE_BYTES = 1024 * 1024;
9866
- var NULL_BODY_STATUSES = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
9867
- var agents = {
9868
- "http:": new http2.Agent({ keepAlive: false }),
9869
- "https:": new https2.Agent({ keepAlive: false })
9870
- };
9871
- var collectorFetch = (input, init = {}) => {
9872
- const url2 = new URL(input);
9873
- if (url2.protocol !== "https:" && url2.protocol !== "http:") {
9874
- throw new TypeError(
9875
- `Unsupported collector request protocol ${url2.protocol}`
9876
- );
9877
- }
9878
- const body = init.body ?? null;
9879
- const headers = { ...init.headers };
9880
- if (body !== null) {
9881
- headers["content-length"] = String(Buffer.byteLength(body, "utf8"));
9882
- }
9883
- const transport = url2.protocol === "https:" ? https2 : http2;
9884
- return new Promise((resolve, reject) => {
9885
- const request2 = transport.request(
9886
- url2,
9887
- {
9888
- method: init.method ?? "GET",
9889
- headers,
9890
- agent: agents[url2.protocol],
9891
- signal: init.signal
9892
- },
9893
- (response) => {
9894
- const chunks = [];
9895
- let size = 0;
9896
- response.on("data", (chunk) => {
9897
- size += chunk.byteLength;
9898
- if (size > MAX_RESPONSE_BYTES) {
9899
- response.destroy(
9900
- new Error("Iteration returned an oversized response.")
9901
- );
9902
- return;
9903
- }
9904
- chunks.push(chunk);
9905
- });
9906
- response.on("error", reject);
9907
- response.on("end", () => {
9908
- const status2 = response.statusCode ?? 0;
9909
- const responseHeaders = new Headers();
9910
- for (const [name, value] of Object.entries(response.headers)) {
9911
- if (value === void 0) continue;
9912
- for (const item of Array.isArray(value) ? value : [value]) {
9913
- responseHeaders.append(name, item);
9914
- }
9915
- }
9916
- resolve(
9917
- new Response(
9918
- NULL_BODY_STATUSES.has(status2) ? null : Buffer.concat(chunks),
9919
- { status: status2, headers: responseHeaders }
9920
- )
9921
- );
9922
- });
9923
- }
9924
- );
9925
- request2.on("error", reject);
9926
- request2.end(body ?? void 0);
9927
- });
9928
- };
9929
-
9930
10028
  // src/turn-summary.ts
9931
10029
  import { execFile as execFileCallback7 } from "node:child_process";
9932
10030
  import { createHash as createHash3 } from "node:crypto";
@@ -9936,7 +10034,7 @@ import { promisify as promisify8 } from "node:util";
9936
10034
  import { crc32 } from "node:zlib";
9937
10035
 
9938
10036
  // src/claude-git-hook.ts
9939
- import { randomUUID } from "node:crypto";
10037
+ import { randomUUID as randomUUID2 } from "node:crypto";
9940
10038
  import { promises as fs10 } from "node:fs";
9941
10039
  import path9 from "node:path";
9942
10040
 
@@ -10058,7 +10156,7 @@ async function captureClaudeGit(dataDirectory2) {
10058
10156
  (name2) => name2.endsWith(".jsonl")
10059
10157
  );
10060
10158
  if (files.length >= 1e4) throw new Error("Claude Git hook queue is full.");
10061
- const name = randomUUID();
10159
+ const name = randomUUID2();
10062
10160
  const temporary = path9.join(directory, `${name}.tmp`);
10063
10161
  const filename = path9.join(directory, `${name}.jsonl`);
10064
10162
  try {
@@ -10096,7 +10194,7 @@ async function claudeGitHookFiles(dataDirectory2) {
10096
10194
  async function recordClaudeGitHookFailure(dataDirectory2) {
10097
10195
  const filename = claudeGitHookPaths(dataDirectory2).failure;
10098
10196
  await ensurePrivateDirectory(path9.dirname(filename));
10099
- const temporary = `${filename}.${randomUUID()}.tmp`;
10197
+ const temporary = `${filename}.${randomUUID2()}.tmp`;
10100
10198
  await fs10.writeFile(temporary, `${JSON.stringify({ atMs: Date.now() })}
10101
10199
  `, {
10102
10200
  mode: 384
@@ -16651,14 +16749,12 @@ async function connectNewDetectedSources(anchor2, consent) {
16651
16749
  try {
16652
16750
  if (source !== "cursor") await installTurnSummary(config2);
16653
16751
  } catch (error2) {
16654
- console.error(
16655
- JSON.stringify({
16656
- level: "warning",
16657
- source,
16658
- operation: "install_turn_summary",
16659
- message: error2 instanceof Error ? error2.message : "The turn summary could not be installed."
16660
- })
16661
- );
16752
+ collectorLogger({
16753
+ level: "warning",
16754
+ source,
16755
+ operation: "install_turn_summary",
16756
+ message: error2 instanceof Error ? error2.message : "The turn summary could not be installed."
16757
+ });
16662
16758
  }
16663
16759
  }
16664
16760
  return connected;
@@ -16798,12 +16894,17 @@ function parseClaudeLine(line, filename, byteOffset, repositoryUrl = gitReposito
16798
16894
  const record3 = object2(decoded);
16799
16895
  if (record3 === null) return { events: [], gaps: ["record_is_not_an_object"] };
16800
16896
  const recordType = string4(record3.type);
16801
- const sourceSessionId = string4(record3.sessionId);
16802
- if (identity.agentId !== null && sourceSessionId === null || sourceSessionId !== null && `claude:${sourceSessionId}` !== (identity.parentSessionId ?? identity.sessionId)) {
16803
- return { events: [], gaps: ["session_id_does_not_match_path"] };
16804
- }
16805
- if (identity.agentId !== null && string4(record3.agentId) !== identity.agentId)
16806
- return { events: [], gaps: ["agent_id_does_not_match_path"] };
16897
+ const identityGap = () => {
16898
+ const sourceSessionId = string4(record3.sessionId);
16899
+ if (identity.agentId !== null && sourceSessionId === null || sourceSessionId !== null && `claude:${sourceSessionId}` !== (identity.parentSessionId ?? identity.sessionId)) {
16900
+ return "session_id_does_not_match_path";
16901
+ }
16902
+ if (identity.agentId !== null && string4(record3.agentId) !== identity.agentId)
16903
+ return "agent_id_does_not_match_path";
16904
+ return null;
16905
+ };
16906
+ const gap = identityGap();
16907
+ if (gap !== null) return { events: [], gaps: [gap] };
16807
16908
  const gaps = [];
16808
16909
  if (record3.timestamp !== void 0 && timestamp(record3) === null)
16809
16910
  gaps.push("invalid_source_timestamp");
@@ -16841,7 +16942,7 @@ function parseClaudeLine(line, filename, byteOffset, repositoryUrl = gitReposito
16841
16942
  }
16842
16943
  });
16843
16944
  }
16844
- if (recordType === "user") {
16945
+ const appendUserMessage = () => {
16845
16946
  const message = object2(record3.message);
16846
16947
  const content = message === null ? null : string4(message.content);
16847
16948
  if (message === null) gaps.push("user_without_message");
@@ -16889,7 +16990,8 @@ function parseClaudeLine(line, filename, byteOffset, repositoryUrl = gitReposito
16889
16990
  }
16890
16991
  }
16891
16992
  } else gaps.push("unsupported_user_content");
16892
- } else if (recordType === "assistant") {
16993
+ };
16994
+ const appendAssistantMessage = () => {
16893
16995
  const message = object2(record3.message);
16894
16996
  if (message === null || !Array.isArray(message.content))
16895
16997
  gaps.push("assistant_without_content_blocks");
@@ -16942,7 +17044,10 @@ function parseClaudeLine(line, filename, byteOffset, repositoryUrl = gitReposito
16942
17044
  );
16943
17045
  }
16944
17046
  }
16945
- } else if (recordType !== null && CONTEXT_RECORD_TYPES.has(recordType)) {
17047
+ };
17048
+ if (recordType === "user") appendUserMessage();
17049
+ else if (recordType === "assistant") appendAssistantMessage();
17050
+ else if (recordType !== null && CONTEXT_RECORD_TYPES.has(recordType)) {
16946
17051
  const attachment = object2(record3.attachment);
16947
17052
  events.push(
16948
17053
  event(identity, record3, byteOffset, 1, "source_record", {
@@ -17060,8 +17165,7 @@ function parseCursorTranscriptLine(line, filename, byteOffset) {
17060
17165
  }
17061
17166
  });
17062
17167
  }
17063
- const role = string5(source.role);
17064
- if (role === "user" || role === "assistant") {
17168
+ const appendMessage = (role2) => {
17065
17169
  const message = record2(source.message);
17066
17170
  if (message === null || !Array.isArray(message.content))
17067
17171
  gaps.push("cursor_message_without_content");
@@ -17073,7 +17177,7 @@ function parseCursorTranscriptLine(line, filename, byteOffset) {
17073
17177
  continue;
17074
17178
  }
17075
17179
  const type = string5(block.type);
17076
- const eventType = type === "tool_use" ? "tool_call" : type === "thinking" ? "reasoning" : type === "text" ? role === "user" ? "user_message" : "assistant_message" : "source_record";
17180
+ const eventType = type === "tool_use" ? "tool_call" : type === "thinking" ? "reasoning" : type === "text" ? role2 === "user" ? "user_message" : "assistant_message" : "source_record";
17077
17181
  events.push(
17078
17182
  event2(
17079
17183
  conversationId,
@@ -17095,7 +17199,10 @@ function parseCursorTranscriptLine(line, filename, byteOffset) {
17095
17199
  gaps.push(`unsupported_cursor_content_block:${type ?? "unknown"}`);
17096
17200
  }
17097
17201
  }
17098
- } else if (source.type === "turn_ended") {
17202
+ };
17203
+ const role = string5(source.role);
17204
+ if (role === "user" || role === "assistant") appendMessage(role);
17205
+ else if (source.type === "turn_ended") {
17099
17206
  events.push(
17100
17207
  event2(
17101
17208
  conversationId,
@@ -17130,45 +17237,64 @@ function parseCursorHookLine(line, filename) {
17130
17237
  const hook = wrapper === null ? null : string5(wrapper.hook);
17131
17238
  const capturedAt = wrapper === null ? null : string5(wrapper.capturedAt);
17132
17239
  if (wrapper?.kind === "subagent_transcript_chunk") {
17133
- const subagent = record2(wrapper.subagent);
17134
- const part = nonnegativeInteger(subagent?.part);
17135
- const totalParts = nonnegativeInteger(subagent?.totalParts);
17136
- if (conversationId === null || !UUID.test(conversationId) || capturedAt === null || Number.isNaN(Date.parse(capturedAt)) || subagent === null || part === null || totalParts === null || part < 1 || part > totalParts || string5(subagent.base64) === null)
17137
- return { events: [], gaps: ["invalid_cursor_subagent_transcript_chunk"] };
17138
- const transcriptBytes2 = nonnegativeInteger(wrapper.transcriptBytes) ?? 0;
17139
- const candidate = event2(
17240
+ return parseSubagentChunk(
17241
+ wrapper,
17242
+ input,
17140
17243
  conversationId,
17141
- filename,
17142
- transcriptBytes2,
17143
- part,
17144
- "source_record",
17145
- {
17146
- sourceType: "cursor_subagent_transcript",
17147
- text: string5(subagent.text),
17148
- base64: string5(subagent.base64),
17149
- path: string5(subagent.path),
17150
- part,
17151
- totalParts,
17152
- parent_conversation_id: string5(input?.parent_conversation_id),
17153
- subagent_id: string5(input?.subagent_id)
17154
- },
17155
- null,
17156
- capturedAt
17157
- );
17158
- const hash = candidate.eventId.split(":").at(-1);
17159
- return validated(
17160
- [
17161
- {
17162
- ...candidate,
17163
- sequence: transcriptBytes2 * 1e3 + 500,
17164
- eventId: `${String(Date.parse(capturedAt)).padStart(13, "0")}:${String(2e3 + part).padStart(4, "0")}:${hash}`
17165
- }
17166
- ],
17167
- []
17244
+ capturedAt,
17245
+ filename
17168
17246
  );
17169
17247
  }
17170
17248
  if (input === null || conversationId === null || !UUID.test(conversationId) || hook === null || capturedAt === null || Number.isNaN(Date.parse(capturedAt)))
17171
17249
  return { events: [], gaps: ["invalid_cursor_hook_record"] };
17250
+ return parseHookEvents(
17251
+ wrapper,
17252
+ input,
17253
+ conversationId,
17254
+ hook,
17255
+ capturedAt,
17256
+ filename
17257
+ );
17258
+ }
17259
+ function parseSubagentChunk(wrapper, input, conversationId, capturedAt, filename) {
17260
+ const subagent = record2(wrapper.subagent);
17261
+ const part = nonnegativeInteger(subagent?.part);
17262
+ const totalParts = nonnegativeInteger(subagent?.totalParts);
17263
+ if (conversationId === null || !UUID.test(conversationId) || capturedAt === null || Number.isNaN(Date.parse(capturedAt)) || subagent === null || part === null || totalParts === null || part < 1 || part > totalParts || string5(subagent.base64) === null)
17264
+ return { events: [], gaps: ["invalid_cursor_subagent_transcript_chunk"] };
17265
+ const transcriptBytes = nonnegativeInteger(wrapper.transcriptBytes) ?? 0;
17266
+ const candidate = event2(
17267
+ conversationId,
17268
+ filename,
17269
+ transcriptBytes,
17270
+ part,
17271
+ "source_record",
17272
+ {
17273
+ sourceType: "cursor_subagent_transcript",
17274
+ text: string5(subagent.text),
17275
+ base64: string5(subagent.base64),
17276
+ path: string5(subagent.path),
17277
+ part,
17278
+ totalParts,
17279
+ parent_conversation_id: string5(input?.parent_conversation_id),
17280
+ subagent_id: string5(input?.subagent_id)
17281
+ },
17282
+ null,
17283
+ capturedAt
17284
+ );
17285
+ const hash = candidate.eventId.split(":").at(-1);
17286
+ return validated(
17287
+ [
17288
+ {
17289
+ ...candidate,
17290
+ sequence: transcriptBytes * 1e3 + 500,
17291
+ eventId: `${String(Date.parse(capturedAt)).padStart(13, "0")}:${String(2e3 + part).padStart(4, "0")}:${hash}`
17292
+ }
17293
+ ],
17294
+ []
17295
+ );
17296
+ }
17297
+ function parseHookEvents(wrapper, input, conversationId, hook, capturedAt, filename) {
17172
17298
  const transcriptBytes = wrapper?.transcriptBytes;
17173
17299
  const sequence = nonnegativeInteger(transcriptBytes) ?? 0;
17174
17300
  const events = [];
@@ -17243,82 +17369,85 @@ function parseCursorHookLine(line, filename) {
17243
17369
  )
17244
17370
  );
17245
17371
  }
17246
- const next = events.length + 1;
17247
- if (hook === "preToolUse") {
17248
- events.push(
17249
- event2(
17250
- conversationId,
17251
- filename,
17252
- sequence,
17253
- next,
17254
- "tool_call",
17255
- {
17256
- name: string5(input.tool_name),
17257
- input: input.tool_input ?? null
17258
- },
17259
- string5(input.tool_use_id),
17260
- capturedAt
17261
- )
17262
- );
17263
- }
17264
- if (hook === "postToolUse" || hook === "postToolUseFailure") {
17265
- const output = string5(input.tool_output) ?? string5(input.error_message) ?? string5(input.error) ?? (input.tool_output === void 0 && input.error === void 0 ? null : JSON.stringify(input.tool_output ?? input.error));
17266
- const chunks = output === null ? [null] : splitText(output);
17267
- for (const [index, text] of chunks.entries())
17372
+ const appendHookPayload = () => {
17373
+ const next = events.length + 1;
17374
+ if (hook === "preToolUse") {
17268
17375
  events.push(
17269
17376
  event2(
17270
17377
  conversationId,
17271
17378
  filename,
17272
17379
  sequence,
17273
- next + index,
17274
- "tool_result",
17380
+ next,
17381
+ "tool_call",
17275
17382
  {
17276
- text,
17277
17383
  name: string5(input.tool_name),
17278
- part: index + 1,
17279
- totalParts: chunks.length
17384
+ input: input.tool_input ?? null
17280
17385
  },
17281
17386
  string5(input.tool_use_id),
17282
17387
  capturedAt
17283
17388
  )
17284
17389
  );
17285
- } else if (hook === "afterAgentThought") {
17286
- events.push(
17287
- event2(
17288
- conversationId,
17289
- filename,
17290
- sequence,
17291
- next,
17292
- "reasoning",
17293
- {
17294
- text: string5(input.text) ?? string5(input.thought)
17295
- },
17296
- null,
17297
- capturedAt
17298
- )
17299
- );
17300
- } else if (hook === "afterAgentResponse" && (input.usage !== void 0 || input.input_tokens !== void 0 || input.output_tokens !== void 0)) {
17301
- events.push(
17302
- event2(
17303
- conversationId,
17304
- filename,
17305
- sequence,
17306
- next,
17307
- "token_usage_record",
17308
- {
17309
- model: string5(input.model),
17310
- usage: input.usage ?? {
17311
- input_tokens: input.input_tokens ?? null,
17312
- output_tokens: input.output_tokens ?? null,
17313
- cache_read_tokens: input.cache_read_tokens ?? null,
17314
- cache_write_tokens: input.cache_write_tokens ?? null
17315
- }
17316
- },
17317
- null,
17318
- capturedAt
17319
- )
17320
- );
17321
- }
17390
+ }
17391
+ if (hook === "postToolUse" || hook === "postToolUseFailure") {
17392
+ const output = hookToolOutput(input);
17393
+ const chunks = output === null ? [null] : splitText(output);
17394
+ for (const [index, text] of chunks.entries())
17395
+ events.push(
17396
+ event2(
17397
+ conversationId,
17398
+ filename,
17399
+ sequence,
17400
+ next + index,
17401
+ "tool_result",
17402
+ {
17403
+ text,
17404
+ name: string5(input.tool_name),
17405
+ part: index + 1,
17406
+ totalParts: chunks.length
17407
+ },
17408
+ string5(input.tool_use_id),
17409
+ capturedAt
17410
+ )
17411
+ );
17412
+ } else if (hook === "afterAgentThought") {
17413
+ events.push(
17414
+ event2(
17415
+ conversationId,
17416
+ filename,
17417
+ sequence,
17418
+ next,
17419
+ "reasoning",
17420
+ {
17421
+ text: string5(input.text) ?? string5(input.thought)
17422
+ },
17423
+ null,
17424
+ capturedAt
17425
+ )
17426
+ );
17427
+ } else if (hook === "afterAgentResponse" && (input.usage !== void 0 || input.input_tokens !== void 0 || input.output_tokens !== void 0)) {
17428
+ events.push(
17429
+ event2(
17430
+ conversationId,
17431
+ filename,
17432
+ sequence,
17433
+ next,
17434
+ "token_usage_record",
17435
+ {
17436
+ model: string5(input.model),
17437
+ usage: input.usage ?? {
17438
+ input_tokens: input.input_tokens ?? null,
17439
+ output_tokens: input.output_tokens ?? null,
17440
+ cache_read_tokens: input.cache_read_tokens ?? null,
17441
+ cache_write_tokens: input.cache_write_tokens ?? null
17442
+ }
17443
+ },
17444
+ null,
17445
+ capturedAt
17446
+ )
17447
+ );
17448
+ }
17449
+ };
17450
+ appendHookPayload();
17322
17451
  const capturedAtMs = Date.parse(capturedAt);
17323
17452
  const ordered = events.map((candidate, index) => {
17324
17453
  const digest3 = candidate.eventId.split(":").at(-1);
@@ -17333,6 +17462,9 @@ function parseCursorHookLine(line, filename) {
17333
17462
  });
17334
17463
  return validated(ordered, []);
17335
17464
  }
17465
+ function hookToolOutput(input) {
17466
+ return string5(input.tool_output) ?? string5(input.error_message) ?? string5(input.error) ?? (input.tool_output === void 0 && input.error === void 0 ? null : JSON.stringify(input.tool_output ?? input.error));
17467
+ }
17336
17468
  function splitText(value) {
17337
17469
  if (value.length === 0) return [""];
17338
17470
  const chunks = [];
@@ -17569,83 +17701,86 @@ function parseRolloutLine(line, filename, byteOffset) {
17569
17701
  );
17570
17702
  }
17571
17703
  if (recordType === "event_msg" && payload !== null) {
17572
- const messageType = parseJsonString(payload.type);
17573
- if (messageType === "user_message") {
17574
- return capturedEvent(
17575
- record3,
17576
- filename,
17577
- byteOffset,
17578
- "user_message",
17579
- payload,
17580
- null
17581
- );
17582
- }
17583
- if (messageType === "agent_message") {
17584
- return capturedEvent(
17585
- record3,
17586
- filename,
17587
- byteOffset,
17588
- "assistant_message",
17589
- payload,
17590
- null
17591
- );
17592
- }
17593
- if (oneOf(messageType, [
17594
- "mcp_tool_call_end",
17595
- "web_search_end",
17596
- "image_generation_end",
17597
- "patch_apply_end"
17598
- ])) {
17599
- return capturedEvent(
17600
- record3,
17601
- filename,
17602
- byteOffset,
17603
- "tool_result",
17604
- payload,
17605
- callIdFrom(payload)
17606
- );
17607
- }
17608
- if (oneOf(messageType, IGNORED_EVENT_MESSAGES)) return { kind: "ignored" };
17609
- if (oneOf(messageType, EVENT_CONTEXT_TYPES)) {
17610
- const ordinal = sourceOrdinal(record3.ordinal);
17611
- const run = messageType === "thread_settings_applied" && parseJsonString(payload.thread_id) === sessionFromPath(filename).slice(-36) && ordinal !== null ? { kind: "start", sourceOrdinal: ordinal } : void 0;
17612
- return capturedEvent(
17613
- record3,
17614
- filename,
17615
- byteOffset,
17616
- messageType,
17617
- payload,
17618
- null,
17619
- run
17620
- );
17621
- }
17622
- if (messageType !== "item_completed") {
17623
- return {
17624
- kind: "gap",
17625
- reason: `unsupported_event_message:${messageType ?? "unknown"}`
17626
- };
17627
- }
17628
- const item = parseJsonRecord(payload.item);
17629
- if (item === null)
17630
- return { kind: "gap", reason: "item_completed_without_item" };
17631
- const mapped = mapCompletedItem(item);
17632
- if (mapped === null) {
17633
- const itemType = parseJsonString(item.type) ?? "unknown";
17634
- return { kind: "gap", reason: `unsupported_completed_item:${itemType}` };
17635
- }
17704
+ return parseEventMessage(record3, payload, filename, byteOffset);
17705
+ }
17706
+ return {
17707
+ kind: "gap",
17708
+ reason: `unsupported_rollout_record:${recordType ?? "unknown"}`
17709
+ };
17710
+ }
17711
+ function parseEventMessage(record3, payload, filename, byteOffset) {
17712
+ const messageType = parseJsonString(payload.type);
17713
+ if (messageType === "user_message") {
17636
17714
  return capturedEvent(
17637
17715
  record3,
17638
17716
  filename,
17639
17717
  byteOffset,
17640
- mapped.eventType,
17641
- item,
17642
- mapped.callId
17718
+ "user_message",
17719
+ payload,
17720
+ null
17643
17721
  );
17644
17722
  }
17645
- return {
17646
- kind: "gap",
17647
- reason: `unsupported_rollout_record:${recordType ?? "unknown"}`
17648
- };
17723
+ if (messageType === "agent_message") {
17724
+ return capturedEvent(
17725
+ record3,
17726
+ filename,
17727
+ byteOffset,
17728
+ "assistant_message",
17729
+ payload,
17730
+ null
17731
+ );
17732
+ }
17733
+ if (oneOf(messageType, [
17734
+ "mcp_tool_call_end",
17735
+ "web_search_end",
17736
+ "image_generation_end",
17737
+ "patch_apply_end"
17738
+ ])) {
17739
+ return capturedEvent(
17740
+ record3,
17741
+ filename,
17742
+ byteOffset,
17743
+ "tool_result",
17744
+ payload,
17745
+ callIdFrom(payload)
17746
+ );
17747
+ }
17748
+ if (oneOf(messageType, IGNORED_EVENT_MESSAGES)) return { kind: "ignored" };
17749
+ if (oneOf(messageType, EVENT_CONTEXT_TYPES)) {
17750
+ const ordinal = sourceOrdinal(record3.ordinal);
17751
+ const run = messageType === "thread_settings_applied" && parseJsonString(payload.thread_id) === sessionFromPath(filename).slice(-36) && ordinal !== null ? { kind: "start", sourceOrdinal: ordinal } : void 0;
17752
+ return capturedEvent(
17753
+ record3,
17754
+ filename,
17755
+ byteOffset,
17756
+ messageType,
17757
+ payload,
17758
+ null,
17759
+ run
17760
+ );
17761
+ }
17762
+ if (messageType !== "item_completed") {
17763
+ return {
17764
+ kind: "gap",
17765
+ reason: `unsupported_event_message:${messageType ?? "unknown"}`
17766
+ };
17767
+ }
17768
+ const item = parseJsonRecord(payload.item);
17769
+ if (item === null)
17770
+ return { kind: "gap", reason: "item_completed_without_item" };
17771
+ const mapped = mapCompletedItem(item);
17772
+ if (mapped === null) {
17773
+ const itemType = parseJsonString(item.type) ?? "unknown";
17774
+ return { kind: "gap", reason: `unsupported_completed_item:${itemType}` };
17775
+ }
17776
+ return capturedEvent(
17777
+ record3,
17778
+ filename,
17779
+ byteOffset,
17780
+ mapped.eventType,
17781
+ item,
17782
+ mapped.callId
17783
+ );
17649
17784
  }
17650
17785
 
17651
17786
  // src/collector.ts
@@ -17669,15 +17804,15 @@ var TraceUploadError = class extends Error {
17669
17804
  this.status = status2;
17670
17805
  }
17671
17806
  };
17672
- function logGap(filename, offset, reason) {
17673
- console.error(
17674
- JSON.stringify({
17675
- level: "capture_gap",
17676
- filename,
17677
- offset,
17678
- reason
17679
- })
17680
- );
17807
+ function logGap(source, filename, offset, reason) {
17808
+ collectorLogger({
17809
+ level: "warning",
17810
+ operation: "capture_gap",
17811
+ source,
17812
+ filename,
17813
+ offset,
17814
+ reason
17815
+ });
17681
17816
  }
17682
17817
  function parseSourceRecord(source, line, filename, byteOffset) {
17683
17818
  if (source === "claude")
@@ -17716,11 +17851,37 @@ async function scanSourceOnce(config2, paths, signal) {
17716
17851
  const queuedIds = await pendingEventIds(paths);
17717
17852
  const tickIndexes = /* @__PURE__ */ new Map();
17718
17853
  const events = [];
17854
+ const queueRecordEvents = async (parsedEvents, filename, byteOffset) => {
17855
+ for (const parsedEvent of parsedEvents) {
17856
+ const queuedAt = tickIndexes.get(parsedEvent.eventId);
17857
+ if (queuedAt !== void 0 && parsedEvent.eventType === "token_usage_record") {
17858
+ const event4 = await redactTraceEvent(parsedEvent);
17859
+ if (Buffer.byteLength(JSON.stringify(event4), "utf8") <= MAX_EVENT_BYTES)
17860
+ events[queuedAt] = event4;
17861
+ continue;
17862
+ }
17863
+ if (queuedIds.has(parsedEvent.eventId)) continue;
17864
+ const event3 = await redactTraceEvent(parsedEvent);
17865
+ const eventBytes = Buffer.byteLength(JSON.stringify(event3), "utf8");
17866
+ if (eventBytes > MAX_EVENT_BYTES) {
17867
+ logGap(
17868
+ source,
17869
+ filename,
17870
+ byteOffset,
17871
+ "normalized_event_exceeds_upload_limit"
17872
+ );
17873
+ } else {
17874
+ tickIndexes.set(event3.eventId, events.length);
17875
+ events.push(event3);
17876
+ queuedIds.add(event3.eventId);
17877
+ }
17878
+ }
17879
+ };
17719
17880
  let remainingBudget = MAX_SCAN_BYTES_PER_TICK;
17720
17881
  let caughtUp = true;
17721
17882
  for (const filename of files) {
17722
17883
  signal?.throwIfAborted();
17723
- const recordLimit = source === "cursor" && filename.includes(`${path18.sep}cursor-hooks${path18.sep}`) ? 12 * 1024 * 1024 : MAX_RECORD_BYTES;
17884
+ const recordLimit = isCursorHookFile(source, filename) ? 12 * 1024 * 1024 : MAX_RECORD_BYTES;
17724
17885
  if (remainingBudget === 0) {
17725
17886
  caughtUp = false;
17726
17887
  break;
@@ -17728,7 +17889,7 @@ async function scanSourceOnce(config2, paths, signal) {
17728
17889
  const size = listedSizes.get(filename) ?? (await fs15.stat(filename)).size;
17729
17890
  let offset = cursors[filename] ?? 0;
17730
17891
  if (offset > size) {
17731
- logGap(filename, offset, "rollout_file_shrank_resetting_cursor");
17892
+ logGap(source, filename, offset, "rollout_file_shrank_resetting_cursor");
17732
17893
  offset = 0;
17733
17894
  skipping.delete(filename);
17734
17895
  }
@@ -17764,59 +17925,51 @@ async function scanSourceOnce(config2, paths, signal) {
17764
17925
  buffer = buffer.subarray(newline + 1);
17765
17926
  skipping.delete(filename);
17766
17927
  }
17767
- let lineStart = 0;
17768
- for (let index = 0; index < buffer.length; index += 1) {
17769
- if (buffer[index] !== 10) continue;
17770
- let lineBytes = buffer.subarray(lineStart, index);
17771
- if (lineBytes.at(-1) === 13) lineBytes = lineBytes.subarray(0, -1);
17772
- const byteOffset = offset + lineStart;
17773
- if (lineBytes.length > recordLimit) {
17774
- logGap(filename, byteOffset, "rollout_record_exceeds_scan_limit");
17775
- lineStart = index + 1;
17776
- continue;
17777
- }
17778
- let line;
17779
- try {
17780
- line = UTF8_DECODER.decode(lineBytes);
17781
- } catch {
17782
- logGap(filename, byteOffset, "rollout_record_is_not_utf8");
17783
- lineStart = index + 1;
17784
- continue;
17785
- }
17786
- const parsed = parseSourceRecord(source, line, filename, byteOffset);
17787
- for (const reason of parsed.gaps) logGap(filename, byteOffset, reason);
17788
- for (const parsedEvent of parsed.events) {
17789
- const queuedAt = tickIndexes.get(parsedEvent.eventId);
17790
- if (queuedAt !== void 0 && parsedEvent.eventType === "token_usage_record") {
17791
- const event4 = await redactTraceEvent(parsedEvent);
17792
- if (Buffer.byteLength(JSON.stringify(event4), "utf8") <= MAX_EVENT_BYTES)
17793
- events[queuedAt] = event4;
17928
+ const scanRecords = async () => {
17929
+ let lineStart = 0;
17930
+ for (let index = 0; index < buffer.length; index += 1) {
17931
+ if (buffer[index] !== 10) continue;
17932
+ let lineBytes = buffer.subarray(lineStart, index);
17933
+ if (lineBytes.at(-1) === 13) lineBytes = lineBytes.subarray(0, -1);
17934
+ const byteOffset = offset + lineStart;
17935
+ if (lineBytes.length > recordLimit) {
17936
+ logGap(
17937
+ source,
17938
+ filename,
17939
+ byteOffset,
17940
+ "rollout_record_exceeds_scan_limit"
17941
+ );
17942
+ lineStart = index + 1;
17794
17943
  continue;
17795
17944
  }
17796
- if (queuedIds.has(parsedEvent.eventId)) continue;
17797
- const event3 = await redactTraceEvent(parsedEvent);
17798
- const eventBytes = Buffer.byteLength(JSON.stringify(event3), "utf8");
17799
- if (eventBytes > MAX_EVENT_BYTES) {
17800
- logGap(filename, byteOffset, "normalized_event_exceeds_upload_limit");
17801
- } else {
17802
- tickIndexes.set(event3.eventId, events.length);
17803
- events.push(event3);
17804
- queuedIds.add(event3.eventId);
17945
+ let line;
17946
+ try {
17947
+ line = UTF8_DECODER.decode(lineBytes);
17948
+ } catch {
17949
+ logGap(source, filename, byteOffset, "rollout_record_is_not_utf8");
17950
+ lineStart = index + 1;
17951
+ continue;
17805
17952
  }
17953
+ const parsed = parseSourceRecord(source, line, filename, byteOffset);
17954
+ for (const reason of parsed.gaps)
17955
+ logGap(source, filename, byteOffset, reason);
17956
+ await queueRecordEvents(parsed.events, filename, byteOffset);
17957
+ lineStart = index + 1;
17806
17958
  }
17807
- lineStart = index + 1;
17808
- }
17809
- if (buffer.length - lineStart > recordLimit) {
17810
- logGap(
17811
- filename,
17812
- offset + lineStart,
17813
- "incomplete_rollout_record_exceeds_scan_limit"
17814
- );
17815
- skipping.add(filename);
17816
- lineStart = buffer.length;
17817
- }
17818
- cursors[filename] = offset + lineStart;
17819
- if ((source === "cursor" && filename.includes(`${path18.sep}cursor-hooks${path18.sep}`) || source === "claude" && filename.includes(`${path18.sep}git-queue${path18.sep}`)) && cursors[filename] === size)
17959
+ if (buffer.length - lineStart > recordLimit) {
17960
+ logGap(
17961
+ source,
17962
+ filename,
17963
+ offset + lineStart,
17964
+ "incomplete_rollout_record_exceeds_scan_limit"
17965
+ );
17966
+ skipping.add(filename);
17967
+ lineStart = buffer.length;
17968
+ }
17969
+ cursors[filename] = offset + lineStart;
17970
+ };
17971
+ await scanRecords();
17972
+ if (isHookFile(source, filename) && cursors[filename] === size)
17820
17973
  completedHooks.push(filename);
17821
17974
  }
17822
17975
  await appendEvents(events, paths);
@@ -17845,6 +17998,12 @@ async function scanSourceOnce(config2, paths, signal) {
17845
17998
  }
17846
17999
  };
17847
18000
  }
18001
+ function isHookFile(source, filename) {
18002
+ return isCursorHookFile(source, filename) || source === "claude" && filename.includes(`${path18.sep}git-queue${path18.sep}`);
18003
+ }
18004
+ function isCursorHookFile(source, filename) {
18005
+ return source === "cursor" && filename.includes(`${path18.sep}cursor-hooks${path18.sep}`);
18006
+ }
17848
18007
  function retryAfterMs(response) {
17849
18008
  const header = response.headers.get("retry-after");
17850
18009
  if (header === null) return null;
@@ -18206,57 +18365,64 @@ async function syncCursorBilling(config2, fetcher = collectorFetch, signal) {
18206
18365
  nowMs
18207
18366
  );
18208
18367
  const byConversation = /* @__PURE__ */ new Map();
18209
- for (let page = 1; page <= MAX_PAGES; page += 1) {
18210
- signal?.throwIfAborted();
18211
- const response = await fetch(CURSOR_USAGE_URL, {
18212
- method: "POST",
18213
- headers: {
18214
- Authorization: `Bearer ${auth.token}`,
18215
- "Content-Type": "application/json",
18216
- "Connect-Protocol-Version": "1"
18217
- },
18218
- body: JSON.stringify({
18219
- page,
18220
- pageSize: PAGE_SIZE,
18221
- startDate: String(plan.startDateMs)
18222
- }),
18223
- signal: AbortSignal.any([
18224
- AbortSignal.timeout(2e4),
18225
- ...signal ? [signal] : []
18226
- ])
18227
- });
18228
- if (!response.ok)
18229
- throw new Error(
18230
- `Cursor billing lookup failed with HTTP ${response.status}.`
18231
- );
18232
- const rawPage = await response.json();
18233
- if (emptyUsagePageSchema.safeParse(rawPage).success) {
18234
- if (page !== 1)
18368
+ const readUsagePages = async () => {
18369
+ for (let page = 1; page <= MAX_PAGES; page += 1) {
18370
+ signal?.throwIfAborted();
18371
+ const response = await fetch(CURSOR_USAGE_URL, {
18372
+ method: "POST",
18373
+ headers: {
18374
+ Authorization: `Bearer ${auth.token}`,
18375
+ "Content-Type": "application/json",
18376
+ "Connect-Protocol-Version": "1"
18377
+ },
18378
+ body: JSON.stringify({
18379
+ page,
18380
+ pageSize: PAGE_SIZE,
18381
+ startDate: String(plan.startDateMs)
18382
+ }),
18383
+ signal: AbortSignal.any([
18384
+ AbortSignal.timeout(2e4),
18385
+ ...signal ? [signal] : []
18386
+ ])
18387
+ });
18388
+ if (!response.ok)
18389
+ throw new Error(
18390
+ `Cursor billing lookup failed with HTTP ${response.status}.`
18391
+ );
18392
+ const rawPage = await response.json();
18393
+ if (emptyUsagePageSchema.safeParse(rawPage).success) {
18394
+ if (page !== 1)
18395
+ throw new Error(
18396
+ "Cursor billing history ended before its reported count."
18397
+ );
18398
+ break;
18399
+ }
18400
+ const decoded = usagePageSchema.safeParse(rawPage);
18401
+ if (!decoded.success)
18402
+ throw new Error("Cursor returned an invalid billing page.");
18403
+ for (const value of decoded.data.usageEventsDisplay) {
18404
+ const normalized = normalizeCursorUsageEvent(value, localConversations);
18405
+ if (normalized === null) continue;
18406
+ const events = byConversation.get(normalized.conversationId) ?? [];
18407
+ events.push(normalized.event);
18408
+ if (events.length > 5e3)
18409
+ throw new Error(
18410
+ "Cursor conversation exceeds the billing sync limit."
18411
+ );
18412
+ byConversation.set(normalized.conversationId, events);
18413
+ }
18414
+ if (page * PAGE_SIZE >= decoded.data.totalUsageEventsCount) break;
18415
+ if (page === MAX_PAGES)
18416
+ throw new Error(
18417
+ "Cursor account exceeds the billing history page limit."
18418
+ );
18419
+ if (decoded.data.usageEventsDisplay.length === 0)
18235
18420
  throw new Error(
18236
18421
  "Cursor billing history ended before its reported count."
18237
18422
  );
18238
- break;
18239
18423
  }
18240
- const decoded = usagePageSchema.safeParse(rawPage);
18241
- if (!decoded.success)
18242
- throw new Error("Cursor returned an invalid billing page.");
18243
- for (const value of decoded.data.usageEventsDisplay) {
18244
- const normalized = normalizeCursorUsageEvent(value, localConversations);
18245
- if (normalized === null) continue;
18246
- const events = byConversation.get(normalized.conversationId) ?? [];
18247
- events.push(normalized.event);
18248
- if (events.length > 5e3)
18249
- throw new Error("Cursor conversation exceeds the billing sync limit.");
18250
- byConversation.set(normalized.conversationId, events);
18251
- }
18252
- if (page * PAGE_SIZE >= decoded.data.totalUsageEventsCount) break;
18253
- if (page === MAX_PAGES)
18254
- throw new Error("Cursor account exceeds the billing history page limit.");
18255
- if (decoded.data.usageEventsDisplay.length === 0)
18256
- throw new Error(
18257
- "Cursor billing history ended before its reported count."
18258
- );
18259
- }
18424
+ };
18425
+ await readUsagePages();
18260
18426
  const endpoint = new URL(config2.endpoint);
18261
18427
  endpoint.pathname = "/api/traces/cursor-billing";
18262
18428
  let matched = 0;
@@ -18631,11 +18797,14 @@ async function syncCodexBilling(config2, fetcher = collectorFetch, signal) {
18631
18797
  );
18632
18798
  const filenames = await sourceFiles("codex", config2.codexHome);
18633
18799
  const threads = [];
18634
- for (const filename of filenames) {
18635
- signal?.throwIfAborted();
18636
- const thread = await localThread(filename);
18637
- if (thread !== null) threads.push(thread);
18638
- }
18800
+ const readLocalThreads = async () => {
18801
+ for (const filename of filenames) {
18802
+ signal?.throwIfAborted();
18803
+ const thread = await localThread(filename);
18804
+ if (thread !== null) threads.push(thread);
18805
+ }
18806
+ };
18807
+ await readLocalThreads();
18639
18808
  const allTasks = groupCodexTasks(threads, auth.accountId);
18640
18809
  const retryIds = new Set(audit ? prior?.unavailableThreadIds ?? [] : []);
18641
18810
  const selected = allTasks.filter(
@@ -18673,36 +18842,40 @@ async function syncCodexBilling(config2, fetcher = collectorFetch, signal) {
18673
18842
  let apiTurns = 0;
18674
18843
  const endpoint = new URL(config2.endpoint);
18675
18844
  endpoint.pathname = "/api/traces/codex-billing";
18676
- const batches = [];
18677
- let current = [];
18678
- let currentIds = 0;
18679
- let currentTurns = 0;
18680
- for (const task of selected) {
18681
- const size = 1 + task.descendantThreadIds.length;
18682
- const turns = [task.threadId, ...task.descendantThreadIds].reduce(
18683
- (total, threadId) => total + (byThread.get(threadId)?.length ?? 0),
18684
- 0
18685
- );
18686
- if (size > 1e3)
18687
- throw new Error("Codex task exceeds the billing thread limit.");
18688
- if (current.length >= TASK_BATCH_SIZE || currentIds + size > 1e3 || currentTurns + turns > 5e3) {
18689
- if (current.length > 0) batches.push(current);
18690
- current = [];
18691
- currentIds = 0;
18692
- currentTurns = 0;
18693
- }
18694
- current.push(task);
18695
- currentIds += size;
18696
- currentTurns += turns;
18697
- if (turns > 5e3) {
18698
- batches.push(current);
18699
- current = [];
18700
- currentIds = 0;
18701
- currentTurns = 0;
18702
- }
18703
- }
18704
- if (current.length > 0) batches.push(current);
18705
- for (const batch of batches) {
18845
+ const createBatches = () => {
18846
+ const batches2 = [];
18847
+ let current = [];
18848
+ let currentIds = 0;
18849
+ let currentTurns = 0;
18850
+ for (const task of selected) {
18851
+ const size = 1 + task.descendantThreadIds.length;
18852
+ const turns = [task.threadId, ...task.descendantThreadIds].reduce(
18853
+ (total, threadId) => total + (byThread.get(threadId)?.length ?? 0),
18854
+ 0
18855
+ );
18856
+ if (size > 1e3)
18857
+ throw new Error("Codex task exceeds the billing thread limit.");
18858
+ if (current.length >= TASK_BATCH_SIZE || currentIds + size > 1e3 || currentTurns + turns > 5e3) {
18859
+ if (current.length > 0) batches2.push(current);
18860
+ current = [];
18861
+ currentIds = 0;
18862
+ currentTurns = 0;
18863
+ }
18864
+ current.push(task);
18865
+ currentIds += size;
18866
+ currentTurns += turns;
18867
+ if (turns > 5e3) {
18868
+ batches2.push(current);
18869
+ current = [];
18870
+ currentIds = 0;
18871
+ currentTurns = 0;
18872
+ }
18873
+ }
18874
+ if (current.length > 0) batches2.push(current);
18875
+ return batches2;
18876
+ };
18877
+ const batches = createBatches();
18878
+ const syncBatch = async (batch) => {
18706
18879
  signal?.throwIfAborted();
18707
18880
  const responseTasks = auth.accessToken === null || auth.accountId === null ? /* @__PURE__ */ new Map() : await queryTaskUsage(batch, auth.accessToken, auth.accountId, signal);
18708
18881
  const tasks = batch.map((task) => {
@@ -18750,7 +18923,7 @@ async function syncCodexBilling(config2, fetcher = collectorFetch, signal) {
18750
18923
  throw new Error("Iteration returned an invalid Codex billing result.");
18751
18924
  if (result.data.matched < tasks.length) serverIncomplete = true;
18752
18925
  }
18753
- for (const task of tasks) {
18926
+ const reconcileTask = (task) => {
18754
18927
  if (task.dataStatus === "available" && task.balanceUsageCredits !== null && (task.usageSource === "included_plan" || task.usageSource === "plan_and_credits"))
18755
18928
  unavailable.delete(task.threadId);
18756
18929
  else if (task.dataStatus !== "not_queried")
@@ -18762,9 +18935,11 @@ async function syncCodexBilling(config2, fetcher = collectorFetch, signal) {
18762
18935
  else if (codexCreditSign(task.balanceUsageCredits) > 0)
18763
18936
  creditTasks += 1;
18764
18937
  }
18765
- }
18938
+ };
18939
+ for (const task of tasks) reconcileTask(task);
18766
18940
  apiTurns += batchTurns.filter((turn) => turn.mode === "api_key").length;
18767
- }
18941
+ };
18942
+ for (const batch of batches) await syncBatch(batch);
18768
18943
  await saveState2(stateFile, {
18769
18944
  accountFingerprint: auth.accountFingerprint,
18770
18945
  lastSyncedAtMs: nowMs,
@@ -18811,13 +18986,11 @@ async function persistHealth(health, paths) {
18811
18986
  try {
18812
18987
  await saveHealth(health, paths);
18813
18988
  } catch {
18814
- console.error(
18815
- JSON.stringify({
18816
- level: "warning",
18817
- operation: "save_collector_health",
18818
- message: "Local collector status could not be saved; collection continues."
18819
- })
18820
- );
18989
+ collectorLogger({
18990
+ level: "warning",
18991
+ operation: "save_collector_health",
18992
+ message: "Local collector status could not be saved; collection continues."
18993
+ });
18821
18994
  }
18822
18995
  }
18823
18996
  async function acquireCollectorLock(paths) {
@@ -18889,15 +19062,12 @@ async function runtimeStep(version2, signal) {
18889
19062
  signal
18890
19063
  });
18891
19064
  if (outcome.kind !== "updated") return "continue";
18892
- console.info(
18893
- JSON.stringify({
18894
- level: "info",
18895
- at: (/* @__PURE__ */ new Date()).toISOString(),
18896
- operation: "runtime_update",
18897
- from: outcome.from,
18898
- to: outcome.to
18899
- })
18900
- );
19065
+ collectorLogger({
19066
+ level: "info",
19067
+ operation: "runtime_update",
19068
+ from: outcome.from,
19069
+ to: outcome.to
19070
+ });
18901
19071
  return "restart";
18902
19072
  }
18903
19073
  function versionFailure(error2) {
@@ -18927,13 +19097,11 @@ async function runCollector(directory, expectedSource) {
18927
19097
  const loadedHealth = await healthSnapshot(paths);
18928
19098
  let health = loadedHealth.health;
18929
19099
  if (loadedHealth.kind === "unavailable") {
18930
- console.error(
18931
- JSON.stringify({
18932
- level: "warning",
18933
- operation: "load_collector_health",
18934
- message: "Local collector status could not be read; collection continues."
18935
- })
18936
- );
19100
+ collectorLogger({
19101
+ level: "warning",
19102
+ operation: "load_collector_health",
19103
+ message: "Local collector status could not be read; collection continues."
19104
+ });
18937
19105
  }
18938
19106
  if ("cursorHome" in config2) {
18939
19107
  await ensureCursorHistoryBackfill(config2.cursorHome, paths);
@@ -18949,37 +19117,34 @@ async function runCollector(directory, expectedSource) {
18949
19117
  const runtimeVersion = process.env.ITERATION_RUNTIME_VERSION;
18950
19118
  let lastRuntimeCheckAt = 0;
18951
19119
  let trialSettled = runtimeVersion === void 0;
18952
- while (!controller.signal.aborted) {
19120
+ const checkRuntime = async () => {
18953
19121
  if (runtimeVersion !== void 0 && Date.now() - lastRuntimeCheckAt >= RUNTIME_CHECK_INTERVAL_MS) {
18954
19122
  lastRuntimeCheckAt = Date.now();
18955
19123
  let step = "continue";
18956
19124
  try {
18957
19125
  step = await runtimeStep(runtimeVersion, controller.signal);
18958
19126
  } catch (error2) {
18959
- if (controller.signal.aborted) break;
18960
- console.error(
18961
- JSON.stringify({
18962
- level: "error",
18963
- at: (/* @__PURE__ */ new Date()).toISOString(),
18964
- source,
18965
- operation: "runtime_update",
18966
- causes: errorCauses(error2),
18967
- message: error2 instanceof Error ? error2.message : "The update check failed."
18968
- })
18969
- );
19127
+ if (controller.signal.aborted) return false;
19128
+ collectorLogger({
19129
+ level: "error",
19130
+ source,
19131
+ operation: "runtime_update",
19132
+ causes: errorCauses(error2),
19133
+ message: error2 instanceof Error ? error2.message : "The update check failed."
19134
+ });
18970
19135
  }
18971
19136
  if (step === "restart") {
18972
- console.info(
18973
- JSON.stringify({
18974
- level: "info",
18975
- at: (/* @__PURE__ */ new Date()).toISOString(),
18976
- source,
18977
- operation: "runtime_restart"
18978
- })
18979
- );
18980
- break;
19137
+ collectorLogger({
19138
+ level: "info",
19139
+ source,
19140
+ operation: "runtime_restart"
19141
+ });
19142
+ return false;
18981
19143
  }
18982
19144
  }
19145
+ return true;
19146
+ };
19147
+ const connectDetectedSources = async () => {
18983
19148
  if (Date.now() - lastAutoCheckAt >= 6e4) {
18984
19149
  lastAutoCheckAt = Date.now();
18985
19150
  try {
@@ -18987,25 +19152,50 @@ async function runCollector(directory, expectedSource) {
18987
19152
  if (consent?.anchorSource === source) {
18988
19153
  const connected = await connectNewDetectedSources(config2, consent);
18989
19154
  if (connected.length > 0) {
18990
- console.info(
18991
- JSON.stringify({
18992
- level: "info",
18993
- operation: "auto_connect_sources",
18994
- connected
18995
- })
18996
- );
19155
+ collectorLogger({
19156
+ level: "info",
19157
+ operation: "auto_connect_sources",
19158
+ connected
19159
+ });
18997
19160
  }
18998
19161
  }
18999
19162
  } catch (error2) {
19000
- console.error(
19001
- JSON.stringify({
19002
- level: "error",
19003
- operation: "auto_connect_sources",
19004
- message: error2 instanceof Error ? error2.message : "Automatic source connection failed."
19005
- })
19006
- );
19163
+ collectorLogger({
19164
+ level: "error",
19165
+ operation: "auto_connect_sources",
19166
+ message: error2 instanceof Error ? error2.message : "Automatic source connection failed."
19167
+ });
19007
19168
  }
19008
19169
  }
19170
+ };
19171
+ const recordTickSuccess = async (result, acknowledgedAtMs) => {
19172
+ if (acknowledgedAtMs !== null || health.lastFailure !== null || result.historyCaughtUp && health.historyCaughtUpAtMs === null) {
19173
+ health = {
19174
+ lastUploadAtMs: acknowledgedAtMs ?? health.lastUploadAtMs,
19175
+ historyCaughtUpAtMs: health.historyCaughtUpAtMs ?? (result.historyCaughtUp ? Date.now() : null),
19176
+ lastFailure: null
19177
+ };
19178
+ await persistHealth(health, paths);
19179
+ }
19180
+ if (!trialSettled && runtimeVersion !== void 0) {
19181
+ await confirmRuntimeTrial(runtimeVersion, source);
19182
+ trialSettled = true;
19183
+ }
19184
+ if (result.discovered > 0 || result.uploaded > 0) {
19185
+ collectorLogger({
19186
+ level: "info",
19187
+ source,
19188
+ operation: "collector_tick",
19189
+ discovered: result.discovered,
19190
+ uploaded: result.uploaded,
19191
+ outboxBytes: result.outboxBytes
19192
+ });
19193
+ }
19194
+ retryDelayMs = Math.max(5e3, result.retryAfterMs);
19195
+ if (result.retryAfterMs > 0)
19196
+ retryDelayMs += Math.floor(Math.random() * 1e3);
19197
+ };
19198
+ const collect = async () => {
19009
19199
  let acknowledgedAtMs = null;
19010
19200
  let billingReady = false;
19011
19201
  let history = null;
@@ -19026,35 +19216,9 @@ async function runCollector(directory, expectedSource) {
19026
19216
  complete: billingReady && result.history.readSessions === result.history.sessions
19027
19217
  };
19028
19218
  }
19029
- if (acknowledgedAtMs !== null || health.lastFailure !== null || result.historyCaughtUp && health.historyCaughtUpAtMs === null) {
19030
- health = {
19031
- lastUploadAtMs: acknowledgedAtMs ?? health.lastUploadAtMs,
19032
- historyCaughtUpAtMs: health.historyCaughtUpAtMs ?? (result.historyCaughtUp ? Date.now() : null),
19033
- lastFailure: null
19034
- };
19035
- await persistHealth(health, paths);
19036
- }
19037
- if (!trialSettled && runtimeVersion !== void 0) {
19038
- await confirmRuntimeTrial(runtimeVersion, source);
19039
- trialSettled = true;
19040
- }
19041
- if (result.discovered > 0 || result.uploaded > 0) {
19042
- console.info(
19043
- JSON.stringify({
19044
- level: "info",
19045
- at: (/* @__PURE__ */ new Date()).toISOString(),
19046
- source,
19047
- discovered: result.discovered,
19048
- uploaded: result.uploaded,
19049
- outboxBytes: result.outboxBytes
19050
- })
19051
- );
19052
- }
19053
- retryDelayMs = Math.max(5e3, result.retryAfterMs);
19054
- if (result.retryAfterMs > 0)
19055
- retryDelayMs += Math.floor(Math.random() * 1e3);
19219
+ await recordTickSuccess(result, acknowledgedAtMs);
19056
19220
  } catch (error2) {
19057
- if (controller.signal.aborted) break;
19221
+ if (controller.signal.aborted) return null;
19058
19222
  if (!trialSettled && runtimeVersion !== void 0) {
19059
19223
  if (versionFailure(error2) && await onTrial(runtimeVersion))
19060
19224
  throw error2;
@@ -19066,20 +19230,20 @@ async function runCollector(directory, expectedSource) {
19066
19230
  historyCaughtUpAtMs: health.historyCaughtUpAtMs,
19067
19231
  lastFailure: collectorFailure(error2)
19068
19232
  };
19069
- console.error(
19070
- JSON.stringify({
19071
- level: "error",
19072
- at: (/* @__PURE__ */ new Date()).toISOString(),
19073
- source,
19074
- operation: "collector_tick",
19075
- errorName: error2 instanceof Error ? error2.name : "UnknownError",
19076
- causes: errorCauses(error2),
19077
- message: error2 instanceof Error ? error2.message : "Unknown collector failure."
19078
- })
19079
- );
19233
+ collectorLogger({
19234
+ level: "error",
19235
+ source,
19236
+ operation: "collector_tick",
19237
+ errorName: error2 instanceof Error ? error2.name : "UnknownError",
19238
+ causes: errorCauses(error2),
19239
+ message: error2 instanceof Error ? error2.message : "Unknown collector failure."
19240
+ });
19080
19241
  await persistHealth(health, paths);
19081
19242
  retryDelayMs = Math.min(retryDelayMs * 2, 6e4);
19082
19243
  }
19244
+ return { history, billingReady };
19245
+ };
19246
+ const reportHistory = async (history) => {
19083
19247
  if (history !== null) {
19084
19248
  try {
19085
19249
  await historyProgress.report(
@@ -19090,20 +19254,20 @@ async function runCollector(directory, expectedSource) {
19090
19254
  Date.now()
19091
19255
  );
19092
19256
  } catch (error2) {
19093
- if (controller.signal.aborted) break;
19094
- console.error(
19095
- JSON.stringify({
19096
- level: "warning",
19097
- at: (/* @__PURE__ */ new Date()).toISOString(),
19098
- source,
19099
- operation: "report_history_progress",
19100
- errorName: error2 instanceof Error ? error2.name : "UnknownError",
19101
- causes: errorCauses(error2),
19102
- message: error2 instanceof Error ? error2.message : "Unknown history progress failure."
19103
- })
19104
- );
19257
+ if (controller.signal.aborted) return false;
19258
+ collectorLogger({
19259
+ level: "warning",
19260
+ source,
19261
+ operation: "report_history_progress",
19262
+ errorName: error2 instanceof Error ? error2.name : "UnknownError",
19263
+ causes: errorCauses(error2),
19264
+ message: error2 instanceof Error ? error2.message : "Unknown history progress failure."
19265
+ });
19105
19266
  }
19106
19267
  }
19268
+ return true;
19269
+ };
19270
+ const refreshSummaries = async () => {
19107
19271
  try {
19108
19272
  if (source !== "cursor")
19109
19273
  await refreshTurnSummaries(
@@ -19114,19 +19278,19 @@ async function runCollector(directory, expectedSource) {
19114
19278
  controller.signal
19115
19279
  );
19116
19280
  } catch (error2) {
19117
- if (controller.signal.aborted) break;
19118
- console.error(
19119
- JSON.stringify({
19120
- level: "warning",
19121
- at: (/* @__PURE__ */ new Date()).toISOString(),
19122
- source,
19123
- operation: "refresh_turn_summaries",
19124
- errorName: error2 instanceof Error ? error2.name : "UnknownError",
19125
- causes: errorCauses(error2),
19126
- message: error2 instanceof Error ? error2.message : "Unknown turn summary failure."
19127
- })
19128
- );
19281
+ if (controller.signal.aborted) return false;
19282
+ collectorLogger({
19283
+ level: "warning",
19284
+ source,
19285
+ operation: "refresh_turn_summaries",
19286
+ errorName: error2 instanceof Error ? error2.name : "UnknownError",
19287
+ causes: errorCauses(error2),
19288
+ message: error2 instanceof Error ? error2.message : "Unknown turn summary failure."
19289
+ });
19129
19290
  }
19291
+ return true;
19292
+ };
19293
+ const syncBilling = async (billingReady) => {
19130
19294
  if ("cursorHome" in config2 && billingReady && Date.now() - lastBillingSyncAt >= 6 * 60 * 60 * 1e3) {
19131
19295
  try {
19132
19296
  const billing = await syncCursorBilling(
@@ -19135,23 +19299,19 @@ async function runCollector(directory, expectedSource) {
19135
19299
  controller.signal
19136
19300
  );
19137
19301
  lastBillingSyncAt = billing.skipped ? Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3 : Date.now();
19138
- console.info(
19139
- JSON.stringify({
19140
- level: "info",
19141
- operation: "sync_cursor_billing",
19142
- ...billing
19143
- })
19144
- );
19302
+ collectorLogger({
19303
+ level: "info",
19304
+ operation: "sync_cursor_billing",
19305
+ ...billing
19306
+ });
19145
19307
  } catch (error2) {
19146
- if (controller.signal.aborted) break;
19308
+ if (controller.signal.aborted) return false;
19147
19309
  lastBillingSyncAt = Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3;
19148
- console.error(
19149
- JSON.stringify({
19150
- level: "warning",
19151
- operation: "sync_cursor_billing",
19152
- message: error2 instanceof Error ? error2.message : "Cursor billing sync failed."
19153
- })
19154
- );
19310
+ collectorLogger({
19311
+ level: "warning",
19312
+ operation: "sync_cursor_billing",
19313
+ message: error2 instanceof Error ? error2.message : "Cursor billing sync failed."
19314
+ });
19155
19315
  }
19156
19316
  }
19157
19317
  if ("codexHome" in config2 && billingReady && Date.now() - lastBillingSyncAt >= 6 * 60 * 60 * 1e3) {
@@ -19162,25 +19322,31 @@ async function runCollector(directory, expectedSource) {
19162
19322
  controller.signal
19163
19323
  );
19164
19324
  lastBillingSyncAt = billing.skipped ? Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3 : Date.now();
19165
- console.info(
19166
- JSON.stringify({
19167
- level: "info",
19168
- operation: "sync_codex_billing",
19169
- ...billing
19170
- })
19171
- );
19325
+ collectorLogger({
19326
+ level: "info",
19327
+ operation: "sync_codex_billing",
19328
+ ...billing
19329
+ });
19172
19330
  } catch (error2) {
19173
- if (controller.signal.aborted) break;
19331
+ if (controller.signal.aborted) return false;
19174
19332
  lastBillingSyncAt = Date.now() - 6 * 60 * 60 * 1e3 + 5 * 60 * 1e3;
19175
- console.error(
19176
- JSON.stringify({
19177
- level: "warning",
19178
- operation: "sync_codex_billing",
19179
- message: error2 instanceof Error ? error2.message : "Codex billing sync failed."
19180
- })
19181
- );
19333
+ collectorLogger({
19334
+ level: "warning",
19335
+ operation: "sync_codex_billing",
19336
+ message: error2 instanceof Error ? error2.message : "Codex billing sync failed."
19337
+ });
19182
19338
  }
19183
19339
  }
19340
+ return true;
19341
+ };
19342
+ while (!controller.signal.aborted) {
19343
+ if (!await checkRuntime()) break;
19344
+ await connectDetectedSources();
19345
+ const result = await collect();
19346
+ if (result === null) break;
19347
+ if (!await reportHistory(result.history)) break;
19348
+ if (!await refreshSummaries()) break;
19349
+ if (!await syncBilling(result.billingReady)) break;
19184
19350
  try {
19185
19351
  await delay4(retryDelayMs, void 0, { signal: controller.signal });
19186
19352
  } catch (error2) {
@@ -19390,76 +19556,90 @@ function renderHome(status2, nowMs = Date.now(), source = "codex") {
19390
19556
  workspaceName === null ? "Iteration" : `Iteration \xB7 ${workspaceName}`,
19391
19557
  ""
19392
19558
  ];
19393
- if (!status2.running) {
19394
- lines.push(`${name} is stopped`, "New sessions are not being collected.");
19395
- if (status2.pendingEvents > 0) lines.push(`${pendingDescription(status2)}.`);
19396
- lines.push(`Resume with iteration start ${source}.`);
19397
- } else if (status2.hookFailureAtMs !== null && status2.hookFailureAtMs !== void 0) {
19398
- lines.push(
19399
- `${name} hook capture needs attention`,
19400
- `A ${name} hook failed ${relativeTime(status2.hookFailureAtMs, nowMs)}. Some data may be missing. See iteration status ${source}.`
19401
- );
19402
- } else if (!status2.healthAvailable) {
19403
- lines.push(
19404
- `${name} is running; local status needs attention`,
19405
- "Upload history could not be read. Collection continues in the background."
19406
- );
19407
- if (status2.pendingEvents > 0) lines.push(`${pendingDescription(status2)}.`);
19408
- lines.push(`Details: iteration status ${source}`);
19409
- } else if (status2.health.lastFailure !== null) {
19410
- const failure2 = failureDescription(status2, source);
19411
- lines.push(failure2.heading, failure2.explanation);
19412
- if (status2.pendingEvents > 0) lines.push(`${pendingDescription(status2)}.`);
19413
- if (status2.health.lastUploadAtMs !== null) {
19559
+ const appendConnectionStatus = () => {
19560
+ if (!status2.running) {
19561
+ lines.push(`${name} is stopped`, "New sessions are not being collected.");
19562
+ if (status2.pendingEvents > 0)
19563
+ lines.push(`${pendingDescription(status2)}.`);
19564
+ lines.push(`Resume with iteration start ${source}.`);
19565
+ } else if (status2.hookFailureAtMs !== null && status2.hookFailureAtMs !== void 0) {
19566
+ lines.push(
19567
+ `${name} hook capture needs attention`,
19568
+ `A ${name} hook failed ${relativeTime(status2.hookFailureAtMs, nowMs)}. Some data may be missing. See iteration status ${source}.`
19569
+ );
19570
+ } else if (!status2.healthAvailable) {
19571
+ lines.push(
19572
+ `${name} is running; local status needs attention`,
19573
+ "Upload history could not be read. Collection continues in the background."
19574
+ );
19575
+ if (status2.pendingEvents > 0)
19576
+ lines.push(`${pendingDescription(status2)}.`);
19577
+ lines.push(`Details: iteration status ${source}`);
19578
+ } else if (status2.health.lastFailure !== null) {
19579
+ const failure2 = failureDescription(status2, source);
19580
+ lines.push(failure2.heading, failure2.explanation);
19581
+ if (status2.pendingEvents > 0)
19582
+ lines.push(`${pendingDescription(status2)}.`);
19583
+ if (status2.health.lastUploadAtMs !== null) {
19584
+ lines.push(
19585
+ `Last successful upload ${relativeTime(status2.health.lastUploadAtMs, nowMs)}.`
19586
+ );
19587
+ }
19588
+ lines.push(`Details: iteration status ${source}`);
19589
+ } else return false;
19590
+ return true;
19591
+ };
19592
+ const appendScanStatus = () => {
19593
+ if (status2.scan.kind === "waiting") {
19594
+ lines.push(
19595
+ `${name} is preparing your history`,
19596
+ progressDescription(status2.scan, source)
19597
+ );
19598
+ } else if (status2.scan.kind === "scanning" && status2.scan.totalBytes > 0 && status2.scan.scannedBytes < status2.scan.totalBytes && status2.health.historyCaughtUpAtMs === null) {
19599
+ lines.push(
19600
+ `${name} is importing existing sessions`,
19601
+ historyDescription(status2, source),
19602
+ "New activity is collected automatically."
19603
+ );
19604
+ } else if (status2.scan.kind === "scanning" && status2.scan.totalBytes - status2.scan.scannedBytes >= 1e6) {
19605
+ lines.push(
19606
+ `${name} is catching up with new activity`,
19607
+ historyDescription(status2, source)
19608
+ );
19609
+ if (status2.pendingEvents > 0)
19610
+ lines.push(`${pendingDescription(status2)}.`);
19611
+ } else if (status2.pendingEvents > 0) {
19612
+ lines.push(
19613
+ `${name} is uploading captured sessions`,
19614
+ `${pendingDescription(status2)}.`
19615
+ );
19616
+ } else if (status2.scan.totalBytes === 0) {
19617
+ lines.push(
19618
+ `${name} is connected`,
19619
+ `Waiting for ${name} sessions on this computer.`
19620
+ );
19621
+ } else if (status2.scan.kind === "scanning" && status2.scan.scannedBytes < status2.scan.totalBytes) {
19622
+ lines.push(
19623
+ `${name} is collecting new activity`,
19624
+ "Local history is imported; current activity is being scanned."
19625
+ );
19626
+ } else {
19627
+ lines.push(
19628
+ `${name} is collecting`,
19629
+ "Local scan is caught up and no uploads are waiting.",
19630
+ "New activity is collected automatically."
19631
+ );
19632
+ }
19633
+ };
19634
+ if (!appendConnectionStatus()) appendScanStatus();
19635
+ const appendLastUpload = () => {
19636
+ if (status2.running && status2.health.lastFailure === null && status2.health.lastUploadAtMs !== null) {
19414
19637
  lines.push(
19415
19638
  `Last successful upload ${relativeTime(status2.health.lastUploadAtMs, nowMs)}.`
19416
19639
  );
19417
19640
  }
19418
- lines.push(`Details: iteration status ${source}`);
19419
- } else if (status2.scan.kind === "waiting") {
19420
- lines.push(
19421
- `${name} is preparing your history`,
19422
- progressDescription(status2.scan, source)
19423
- );
19424
- } else if (status2.scan.kind === "scanning" && status2.scan.totalBytes > 0 && status2.scan.scannedBytes < status2.scan.totalBytes && status2.health.historyCaughtUpAtMs === null) {
19425
- lines.push(
19426
- `${name} is importing existing sessions`,
19427
- historyDescription(status2, source),
19428
- "New activity is collected automatically."
19429
- );
19430
- } else if (status2.scan.kind === "scanning" && status2.scan.totalBytes - status2.scan.scannedBytes >= 1e6) {
19431
- lines.push(
19432
- `${name} is catching up with new activity`,
19433
- historyDescription(status2, source)
19434
- );
19435
- if (status2.pendingEvents > 0) lines.push(`${pendingDescription(status2)}.`);
19436
- } else if (status2.pendingEvents > 0) {
19437
- lines.push(
19438
- `${name} is uploading captured sessions`,
19439
- `${pendingDescription(status2)}.`
19440
- );
19441
- } else if (status2.scan.totalBytes === 0) {
19442
- lines.push(
19443
- `${name} is connected`,
19444
- `Waiting for ${name} sessions on this computer.`
19445
- );
19446
- } else if (status2.scan.kind === "scanning" && status2.scan.scannedBytes < status2.scan.totalBytes) {
19447
- lines.push(
19448
- `${name} is collecting new activity`,
19449
- "Local history is imported; current activity is being scanned."
19450
- );
19451
- } else {
19452
- lines.push(
19453
- `${name} is collecting`,
19454
- "Local scan is caught up and no uploads are waiting.",
19455
- "New activity is collected automatically."
19456
- );
19457
- }
19458
- if (status2.running && status2.health.lastFailure === null && status2.health.lastUploadAtMs !== null) {
19459
- lines.push(
19460
- `Last successful upload ${relativeTime(status2.health.lastUploadAtMs, nowMs)}.`
19461
- );
19462
- }
19641
+ };
19642
+ appendLastUpload();
19463
19643
  if (status2.turnSummaryNeedsApproval) lines.push("", CODEX_APPROVAL_STEP);
19464
19644
  lines.push("", `Explore traces ${status2.tracesUrl}`);
19465
19645
  return lines.join("\n");
@@ -19902,7 +20082,7 @@ async function connectAll(values) {
19902
20082
  );
19903
20083
  }
19904
20084
  const configs = await Promise.all(COLLECTOR_SOURCES.map(connectedConfig));
19905
- const sourceHomes = {
20085
+ const resolveSourceHomes = () => ({
19906
20086
  codex: path24.resolve(
19907
20087
  optionalString(values, "codex-home") ?? (configs[0] && "codexHome" in configs[0] ? configs[0].codexHome : sourceHome("codex"))
19908
20088
  ),
@@ -19912,7 +20092,8 @@ async function connectAll(values) {
19912
20092
  cursor: path24.resolve(
19913
20093
  optionalString(values, "cursor-home") ?? (configs[2] && "cursorHome" in configs[2] ? configs[2].cursorHome : sourceHome("cursor"))
19914
20094
  )
19915
- };
20095
+ });
20096
+ const sourceHomes = resolveSourceHomes();
19916
20097
  const detected = await detectedSources(sourceHomes);
19917
20098
  const existing = COLLECTOR_SOURCES.flatMap(
19918
20099
  (source, index) => configs[index] ? [{ source, config: configs[index] }] : []
@@ -19934,63 +20115,76 @@ async function connectAll(values) {
19934
20115
  scope: await readCollectorScope(item.config)
19935
20116
  }))
19936
20117
  );
19937
- for (const item of existingScopes) {
19938
- if (item.config.endpoint !== endpoint) {
19939
- throw new Error(
19940
- "Existing sources use different Iteration servers. Connect one source at a time."
19941
- );
20118
+ const validateWorkspace = () => {
20119
+ for (const item of existingScopes) {
20120
+ if (item.config.endpoint !== endpoint) {
20121
+ throw new Error(
20122
+ "Existing sources use different Iteration servers. Connect one source at a time."
20123
+ );
20124
+ }
20125
+ if (existingScopes[0]?.scope.organizationId !== item.scope.organizationId) {
20126
+ throw new Error(
20127
+ "Existing sources use different workspaces. Connect one source at a time."
20128
+ );
20129
+ }
19942
20130
  }
19943
- if (existingScopes[0]?.scope.organizationId !== item.scope.organizationId) {
20131
+ const requestedOrganizationId2 = optionalString(values, "workspace");
20132
+ if (requestedOrganizationId2 && existingScopes[0] && requestedOrganizationId2 !== existingScopes[0].scope.organizationId) {
19944
20133
  throw new Error(
19945
- "Existing sources use different workspaces. Connect one source at a time."
20134
+ "The existing collector belongs to a different workspace."
19946
20135
  );
19947
20136
  }
19948
- }
20137
+ };
20138
+ validateWorkspace();
19949
20139
  const requestedOrganizationId = optionalString(values, "workspace");
19950
- if (requestedOrganizationId && existingScopes[0] && requestedOrganizationId !== existingScopes[0].scope.organizationId) {
19951
- throw new Error("The existing collector belongs to a different workspace.");
19952
- }
19953
20140
  const previousConsent = await loadMachineConsent();
19954
- if (previousConsent === null || anchorExisting === null || previousConsent.endpoint !== endpoint || previousConsent.anchorSource !== anchorSource || previousConsent.anchorDataDirectory !== anchorExisting.dataDirectory || previousConsent.organizationId !== existingScopes[0]?.scope.organizationId || previousConsent.sourceHomes.codex !== sourceHomes.codex || previousConsent.sourceHomes.claude !== sourceHomes.claude || previousConsent.sourceHomes.cursor !== sourceHomes.cursor) {
19955
- await approveAllSources(
19956
- await Promise.all(
19957
- detected.map(async (source) => ({
19958
- source,
19959
- sessions: await countSavedSessions(source, sourceHomes[source])
19960
- }))
19961
- ),
19962
- existingScopes[0]?.scope.organizationName ?? null,
19963
- values.yes === true
19964
- );
19965
- }
20141
+ const approveConnection = async () => {
20142
+ const needsApproval = () => previousConsent === null || anchorExisting === null || previousConsent.endpoint !== endpoint || previousConsent.anchorSource !== anchorSource || previousConsent.anchorDataDirectory !== anchorExisting.dataDirectory || previousConsent.organizationId !== existingScopes[0]?.scope.organizationId || previousConsent.sourceHomes.codex !== sourceHomes.codex || previousConsent.sourceHomes.claude !== sourceHomes.claude || previousConsent.sourceHomes.cursor !== sourceHomes.cursor;
20143
+ if (needsApproval()) {
20144
+ await approveAllSources(
20145
+ await Promise.all(
20146
+ detected.map(async (source) => ({
20147
+ source,
20148
+ sessions: await countSavedSessions(source, sourceHomes[source])
20149
+ }))
20150
+ ),
20151
+ existingScopes[0]?.scope.organizationName ?? null,
20152
+ values.yes === true
20153
+ );
20154
+ }
20155
+ };
20156
+ await approveConnection();
19966
20157
  await prepareBackgroundPersistence();
19967
20158
  let anchor2 = anchorExisting ?? await initialize(anchorSource, values);
19968
20159
  let organizationId = requestedOrganizationId ?? existingScopes[0]?.scope.organizationId ?? anchor2.organizationId ?? null;
19969
- for (const source of detected) {
19970
- if (source === anchorSource || configs[COLLECTOR_SOURCES.indexOf(source)] !== null)
19971
- continue;
19972
- const paths = collectorPaths(defaultCollectorDirectory(source));
19973
- await assertFreshCollectorDirectory(paths, source);
19974
- const state = await prepareSourceInitialState(
19975
- source,
19976
- sourceHomes[source],
19977
- true
19978
- );
19979
- const enrollment = await expandSource(anchor2, source, organizationId);
19980
- const config2 = await activateSource(
19981
- source,
19982
- sourceHomes[source],
19983
- endpoint,
19984
- enrollment,
19985
- state
19986
- );
19987
- organizationId = enrollment.organizationId;
19988
- const notice = await turnSummaryNotice(config2);
19989
- process.stdout.write(
19990
- `${collectorSourceLabel(source)} is connected to ${enrollment.organizationName}.${notice}
20160
+ const connectDetectedSources = async () => {
20161
+ for (const source of detected) {
20162
+ if (source === anchorSource || configs[COLLECTOR_SOURCES.indexOf(source)] !== null)
20163
+ continue;
20164
+ const paths = collectorPaths(defaultCollectorDirectory(source));
20165
+ await assertFreshCollectorDirectory(paths, source);
20166
+ const state = await prepareSourceInitialState(
20167
+ source,
20168
+ sourceHomes[source],
20169
+ true
20170
+ );
20171
+ const enrollment = await expandSource(anchor2, source, organizationId);
20172
+ const config2 = await activateSource(
20173
+ source,
20174
+ sourceHomes[source],
20175
+ endpoint,
20176
+ enrollment,
20177
+ state
20178
+ );
20179
+ organizationId = enrollment.organizationId;
20180
+ const notice = await turnSummaryNotice(config2);
20181
+ process.stdout.write(
20182
+ `${collectorSourceLabel(source)} is connected to ${enrollment.organizationName}.${notice}
19991
20183
  `
19992
- );
19993
- }
20184
+ );
20185
+ }
20186
+ };
20187
+ await connectDetectedSources();
19994
20188
  if (organizationId === null) {
19995
20189
  organizationId = (await readCollectorScope(anchor2)).organizationId;
19996
20190
  }
@@ -20260,15 +20454,15 @@ async function update(setting, values) {
20260
20454
  process.stdout.write(`${await waitForTrial(outcome.to)}
20261
20455
  `);
20262
20456
  }
20263
- async function main() {
20457
+ async function runInternalCommand() {
20264
20458
  if (process.argv[2] === "supervise") {
20265
- const source2 = process.argv[3];
20459
+ const source = process.argv[3];
20266
20460
  const directory = process.argv[5];
20267
- if (source2 !== "codex" && source2 !== "claude" && source2 !== "cursor" || process.argv[4] !== "--data-directory" || !directory || process.argv.length !== 6) {
20461
+ if (source !== "codex" && source !== "claude" && source !== "cursor" || process.argv[4] !== "--data-directory" || !directory || process.argv.length !== 6) {
20268
20462
  throw new Error("Invalid collector supervisor command.");
20269
20463
  }
20270
- await superviseCollector(source2, directory);
20271
- return;
20464
+ await superviseCollector(source, directory);
20465
+ return true;
20272
20466
  }
20273
20467
  if (process.argv[2] === "claude-git-hook") {
20274
20468
  const directory = process.argv[3];
@@ -20280,21 +20474,31 @@ async function main() {
20280
20474
  await recordClaudeGitHookFailure(directory);
20281
20475
  throw error2;
20282
20476
  }
20283
- return;
20477
+ return true;
20284
20478
  }
20285
20479
  if (process.argv[2] === "turn-summary-hook") {
20286
20480
  const directory = process.argv[3];
20287
20481
  if (!directory || process.argv.length !== 4)
20288
20482
  throw new Error("Invalid turn summary hook command.");
20289
20483
  await captureTurnSummary(directory);
20290
- return;
20484
+ return true;
20291
20485
  }
20486
+ return false;
20487
+ }
20488
+ async function main() {
20489
+ if (await runInternalCommand()) return;
20292
20490
  const delegated = await delegateToInstalledVersion();
20293
20491
  if (delegated !== null) {
20294
20492
  process.exitCode = delegated;
20295
20493
  return;
20296
20494
  }
20297
- const { command, source, values } = argumentsForCli();
20495
+ return dispatchCommand(argumentsForCli());
20496
+ }
20497
+ async function dispatchCommand({
20498
+ command,
20499
+ source,
20500
+ values
20501
+ }) {
20298
20502
  const selectedSource = source === "codex" || source === "claude" || source === "cursor" ? source : null;
20299
20503
  if (values.help === true || command === "help") {
20300
20504
  return usage(command === "help" ? source : command);
@@ -20308,18 +20512,7 @@ async function main() {
20308
20512
  return connectAll(values);
20309
20513
  }
20310
20514
  if (command === "") {
20311
- if (Object.keys(values).length > 0) {
20312
- throw new Error(
20313
- "Options require a source. Run iteration connect --help for details."
20314
- );
20315
- }
20316
- if (isCollectorPlatform()) await adoptRuntime();
20317
- await defaultCommand();
20318
- const summary = await runtimeSummaryText();
20319
- if (summary !== null) process.stdout.write(`
20320
- ${summary}
20321
- `);
20322
- return;
20515
+ return showHome(values);
20323
20516
  }
20324
20517
  if (command === "version") {
20325
20518
  const state = await readRuntimeState(runtimePaths());
@@ -20329,13 +20522,45 @@ ${summary}
20329
20522
  }
20330
20523
  if (command === "self-check") return selfCheck2();
20331
20524
  if (command === "update") return update(source, values);
20332
- if (selectedSource !== null) {
20333
- for (const name of ["codex", "claude", "cursor"])
20334
- if (selectedSource !== name && values[`${name}-home`] !== void 0)
20335
- throw new Error(
20336
- `--${name}-home requires the ${collectorSourceLabel(name)} source.`
20337
- );
20525
+ return dispatchSourceCommand(command, source, values, selectedSource);
20526
+ }
20527
+ async function showHome(values) {
20528
+ if (Object.keys(values).length > 0) {
20529
+ throw new Error(
20530
+ "Options require a source. Run iteration connect --help for details."
20531
+ );
20532
+ }
20533
+ if (isCollectorPlatform()) await adoptRuntime();
20534
+ await defaultCommand();
20535
+ const summary = await runtimeSummaryText();
20536
+ if (summary !== null) process.stdout.write(`
20537
+ ${summary}
20538
+ `);
20539
+ }
20540
+ async function disconnect(selectedSource, values) {
20541
+ await uninstallBackgroundService(selectedSource);
20542
+ const paths = collectorPaths(dataDirectory(selectedSource, values));
20543
+ if (await fileExists(paths.config)) {
20544
+ const config2 = await loadConfig(paths);
20545
+ if ("cursorHome" in config2)
20546
+ await uninstallCursorHooks(config2.cursorHome, config2.dataDirectory);
20547
+ else await uninstallTurnSummary(config2);
20338
20548
  }
20549
+ process.stdout.write(
20550
+ "Collector startup removed. Local pending data was retained.\n"
20551
+ );
20552
+ }
20553
+ async function dispatchSourceCommand(command, source, values, selectedSource) {
20554
+ const validateSourceOptions = () => {
20555
+ if (selectedSource !== null) {
20556
+ for (const name of ["codex", "claude", "cursor"])
20557
+ if (selectedSource !== name && values[`${name}-home`] !== void 0)
20558
+ throw new Error(
20559
+ `--${name}-home requires the ${collectorSourceLabel(name)} source.`
20560
+ );
20561
+ }
20562
+ };
20563
+ validateSourceOptions();
20339
20564
  if (command === "connect" && selectedSource !== null) {
20340
20565
  await initialize(selectedSource, values);
20341
20566
  return;
@@ -20349,39 +20574,30 @@ ${summary}
20349
20574
  );
20350
20575
  }
20351
20576
  if (command === "status" && (source === void 0 || selectedSource !== null)) {
20352
- if (source === void 0 && values["data-directory"] !== void 0)
20353
- throw new Error("--data-directory requires a source with status.");
20354
- requireCollectorPlatform();
20355
- await adoptRuntime();
20356
- await status(selectedSource ?? void 0, values);
20357
- const summary = await runtimeSummaryText();
20358
- if (summary !== null) process.stdout.write(`
20359
- ${summary}
20360
- `);
20361
- return;
20577
+ return showStatus(source, selectedSource, values);
20362
20578
  }
20363
20579
  if (command === "import-history" && selectedSource !== null)
20364
20580
  return importHistory(selectedSource, values);
20365
20581
  if (command === "start" && selectedSource !== null)
20366
20582
  return start(selectedSource, values);
20367
20583
  if (command === "disconnect" && selectedSource !== null) {
20368
- await uninstallBackgroundService(selectedSource);
20369
- const paths = collectorPaths(dataDirectory(selectedSource, values));
20370
- if (await fileExists(paths.config)) {
20371
- const config2 = await loadConfig(paths);
20372
- if ("cursorHome" in config2)
20373
- await uninstallCursorHooks(config2.cursorHome, config2.dataDirectory);
20374
- else await uninstallTurnSummary(config2);
20375
- }
20376
- process.stdout.write(
20377
- "Collector startup removed. Local pending data was retained.\n"
20378
- );
20379
- return;
20584
+ return disconnect(selectedSource, values);
20380
20585
  }
20381
20586
  throw new Error(
20382
20587
  `Unknown Iteration command: ${[command, source].filter(Boolean).join(" ")}. Run iteration help for usage.`
20383
20588
  );
20384
20589
  }
20590
+ async function showStatus(source, selectedSource, values) {
20591
+ if (source === void 0 && values["data-directory"] !== void 0)
20592
+ throw new Error("--data-directory requires a source with status.");
20593
+ requireCollectorPlatform();
20594
+ await adoptRuntime();
20595
+ await status(selectedSource ?? void 0, values);
20596
+ const summary = await runtimeSummaryText();
20597
+ if (summary !== null) process.stdout.write(`
20598
+ ${summary}
20599
+ `);
20600
+ }
20385
20601
  var background = process.argv[2] === "run";
20386
20602
  main().then(
20387
20603
  () => {