iteration-cli 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +75 -58
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -936,7 +936,7 @@ import { spawnSync } from "node:child_process";
936
936
  import os9 from "node:os";
937
937
  import path24 from "node:path";
938
938
  import { promises as fs20 } from "node:fs";
939
- import { createInterface as createInterface3 } from "node:readline/promises";
939
+ import { createInterface as createInterface2 } from "node:readline/promises";
940
940
  import { parseArgs } from "node:util";
941
941
 
942
942
  // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/util.js
@@ -10790,11 +10790,10 @@ async function cursorHookFiles(dataDirectory2) {
10790
10790
  }
10791
10791
 
10792
10792
  // src/storage.ts
10793
- import { once } from "node:events";
10794
10793
  import { createReadStream, createWriteStream as createWriteStream2, promises as fs13 } from "node:fs";
10795
10794
  import os6 from "node:os";
10796
10795
  import path13 from "node:path";
10797
- import { createInterface as createInterface2 } from "node:readline";
10796
+ import { pipeline as pipeline2 } from "node:stream/promises";
10798
10797
 
10799
10798
  // ../../node_modules/.pnpm/@secretlint+core@13.0.6/node_modules/@secretlint/core/module/SecretLintSourceCodeImpl.js
10800
10799
  var import_structured_source = __toESM(require_structured_source(), 1);
@@ -15711,6 +15710,32 @@ var healthSchema = object({
15711
15710
  }).strict().nullable()
15712
15711
  }).strict();
15713
15712
  var redactionMarkerSchema = object({ version: literal(1) }).strict();
15713
+ async function* outboxLines(filename) {
15714
+ const input = createReadStream(filename);
15715
+ let parts = [];
15716
+ try {
15717
+ for await (const value of input) {
15718
+ const chunk = value;
15719
+ let start2 = 0;
15720
+ let newline = chunk.indexOf(10);
15721
+ while (newline !== -1) {
15722
+ const end = chunk.subarray(start2, newline);
15723
+ if (parts.length === 0) yield end.toString("utf8");
15724
+ else {
15725
+ parts.push(end);
15726
+ yield Buffer.concat(parts).toString("utf8");
15727
+ parts = [];
15728
+ }
15729
+ start2 = newline + 1;
15730
+ newline = chunk.indexOf(10, start2);
15731
+ }
15732
+ if (start2 < chunk.length) parts.push(chunk.subarray(start2));
15733
+ }
15734
+ if (parts.length > 0) yield Buffer.concat(parts).toString("utf8");
15735
+ } finally {
15736
+ input.destroy();
15737
+ }
15738
+ }
15714
15739
  function parseOutboxEvent(line) {
15715
15740
  let decoded;
15716
15741
  try {
@@ -15925,13 +15950,7 @@ async function pendingEventIds(paths) {
15925
15950
  if (size > MAX_OUTBOX_BYTES)
15926
15951
  throw new Error("Trace outbox exceeds its configured capacity.");
15927
15952
  try {
15928
- const input = await fs13.open(paths.outbox, "r");
15929
- const stream = input.createReadStream({
15930
- autoClose: true,
15931
- encoding: "utf8"
15932
- });
15933
- const lines = createInterface2({ input: stream, crlfDelay: Infinity });
15934
- for await (const line of lines) {
15953
+ for await (const line of outboxLines(paths.outbox)) {
15935
15954
  ids.add(parseOutboxEvent(line).eventId);
15936
15955
  }
15937
15956
  } catch (error2) {
@@ -15988,11 +16007,7 @@ async function redactPendingOutbox(paths) {
15988
16007
  const temporary = `${paths.outbox}.${process.pid}.redacting`;
15989
16008
  const output = await fs13.open(temporary, "w", 384);
15990
16009
  try {
15991
- const lines = createInterface2({
15992
- input: createReadStream(paths.outbox, { encoding: "utf8" }),
15993
- crlfDelay: Infinity
15994
- });
15995
- for await (const line of lines) {
16010
+ for await (const line of outboxLines(paths.outbox)) {
15996
16011
  const event3 = await redactTraceEvent(parseOutboxEvent(line));
15997
16012
  await output.writeFile(`${JSON.stringify(event3)}
15998
16013
  `, "utf8");
@@ -16034,21 +16049,14 @@ async function nextBatch(paths, maxBytes) {
16034
16049
  if (size === 0) return [];
16035
16050
  if (size > MAX_OUTBOX_BYTES)
16036
16051
  throw new Error("Trace outbox exceeds its configured capacity.");
16037
- const input = await fs13.open(paths.outbox, "r");
16038
- const stream = input.createReadStream({ autoClose: true, encoding: "utf8" });
16039
- const lines = createInterface2({ input: stream, crlfDelay: Infinity });
16040
16052
  const events = [];
16041
16053
  let bytes = 0;
16042
- for await (const line of lines) {
16054
+ for await (const line of outboxLines(paths.outbox)) {
16043
16055
  const lineBytes = Buffer.byteLength(line, "utf8") + 1;
16044
16056
  if (events.length >= MAX_TRACE_EVENTS_PER_BATCH || events.length > 0 && bytes + lineBytes > maxBytes) {
16045
- lines.close();
16046
- stream.destroy();
16047
16057
  break;
16048
16058
  }
16049
16059
  if (lineBytes > maxBytes) {
16050
- lines.close();
16051
- stream.destroy();
16052
16060
  throw new Error("A queued event exceeds the maximum upload batch size.");
16053
16061
  }
16054
16062
  events.push(parseOutboxEvent(line));
@@ -16057,28 +16065,20 @@ async function nextBatch(paths, maxBytes) {
16057
16065
  return events;
16058
16066
  }
16059
16067
  async function acknowledgeEvents(acknowledgedIds, paths) {
16068
+ if (await outboxSize(paths) === 0) return;
16060
16069
  const temporary = `${paths.outbox}.${process.pid}.tmp`;
16061
- const input = await fs13.open(paths.outbox, "r").catch((error2) => {
16062
- if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT")
16063
- return null;
16064
- throw error2;
16065
- });
16066
- if (input === null) return;
16067
- const output = createWriteStream2(temporary, { flags: "w", mode: 384 });
16068
16070
  try {
16069
- const lines = createInterface2({
16070
- input: input.createReadStream({ autoClose: true, encoding: "utf8" }),
16071
- crlfDelay: Infinity
16072
- });
16073
- for await (const line of lines) {
16074
- const event3 = parseOutboxEvent(line);
16075
- if (!acknowledgedIds.has(event3.eventId)) {
16076
- if (!output.write(`${line}
16077
- `)) await once(output, "drain");
16078
- }
16079
- }
16080
- output.end();
16081
- await once(output, "close");
16071
+ await pipeline2(
16072
+ outboxLines(paths.outbox),
16073
+ async function* (lines) {
16074
+ for await (const line of lines) {
16075
+ const event3 = parseOutboxEvent(line);
16076
+ if (!acknowledgedIds.has(event3.eventId)) yield `${line}
16077
+ `;
16078
+ }
16079
+ },
16080
+ createWriteStream2(temporary, { flags: "w", mode: 384 })
16081
+ );
16082
16082
  const durable = await fs13.open(temporary, "r+");
16083
16083
  try {
16084
16084
  await durable.sync();
@@ -16087,7 +16087,6 @@ async function acknowledgeEvents(acknowledgedIds, paths) {
16087
16087
  }
16088
16088
  await fs13.rename(temporary, paths.outbox);
16089
16089
  } catch (error2) {
16090
- output.destroy();
16091
16090
  await fs13.rm(temporary, { force: true });
16092
16091
  throw error2;
16093
16092
  }
@@ -17846,12 +17845,18 @@ async function scanSourceOnce(config2, paths, signal) {
17846
17845
  }
17847
17846
  };
17848
17847
  }
17848
+ function retryAfterMs(response) {
17849
+ const header = response.headers.get("retry-after");
17850
+ if (header === null) return null;
17851
+ const milliseconds = /^\d+$/.test(header) ? Number(header) * 1e3 : Date.parse(header) - Date.now();
17852
+ return Number.isFinite(milliseconds) ? Math.max(1e3, Math.min(milliseconds, 3e5)) : 5e3;
17853
+ }
17849
17854
  async function uploadPendingOnce(config2, paths, fetcher = collectorFetch, signal, onAcknowledged) {
17850
17855
  let uploaded = 0;
17851
17856
  for (let attempt = 0; attempt < MAX_UPLOADS_PER_TICK; attempt += 1) {
17852
17857
  signal?.throwIfAborted();
17853
17858
  const batch = await nextBatch(paths, MAX_UPLOAD_BYTES);
17854
- if (batch.length === 0) return uploaded;
17859
+ if (batch.length === 0) return { uploaded, retryAfterMs: null };
17855
17860
  const body = JSON.stringify({ events: batch });
17856
17861
  if (Buffer.byteLength(body, "utf8") > MAX_REQUEST_BYTES) {
17857
17862
  throw new Error("Queued trace batch exceeds the upload request limit.");
@@ -17876,6 +17881,10 @@ async function uploadPendingOnce(config2, paths, fetcher = collectorFetch, signa
17876
17881
  { cause: error2 }
17877
17882
  );
17878
17883
  }
17884
+ if (response.status === 429) {
17885
+ await response.body?.cancel();
17886
+ return { uploaded, retryAfterMs: retryAfterMs(response) ?? 5e3 };
17887
+ }
17879
17888
  if (response.status !== 202) {
17880
17889
  throw new TraceUploadError(
17881
17890
  "http",
@@ -17902,12 +17911,14 @@ async function uploadPendingOnce(config2, paths, fetcher = collectorFetch, signa
17902
17911
  await acknowledgeEvents(new Set(parsed.value.acceptedEventIds), paths);
17903
17912
  onAcknowledged?.();
17904
17913
  uploaded += batch.length;
17914
+ const pause = retryAfterMs(response);
17915
+ if (pause !== null) return { uploaded, retryAfterMs: pause };
17905
17916
  }
17906
- return uploaded;
17917
+ return { uploaded, retryAfterMs: null };
17907
17918
  }
17908
17919
  async function collectorTick(config2, paths, fetcher = collectorFetch, signal, onAcknowledged) {
17909
17920
  await redactPendingOutbox(paths);
17910
- let uploaded = await uploadPendingOnce(
17921
+ let upload = await uploadPendingOnce(
17911
17922
  config2,
17912
17923
  paths,
17913
17924
  fetcher,
@@ -17915,19 +17926,23 @@ async function collectorTick(config2, paths, fetcher = collectorFetch, signal, o
17915
17926
  onAcknowledged
17916
17927
  );
17917
17928
  const scan = await outboxSize(paths) >= SCAN_PAUSE_OUTBOX_BYTES ? { discovered: 0, caughtUp: false, history: null } : await scanSourceOnce(config2, paths, signal);
17918
- uploaded += await uploadPendingOnce(
17919
- config2,
17920
- paths,
17921
- fetcher,
17922
- signal,
17923
- onAcknowledged
17924
- );
17929
+ if (upload.retryAfterMs === null) {
17930
+ const next = await uploadPendingOnce(
17931
+ config2,
17932
+ paths,
17933
+ fetcher,
17934
+ signal,
17935
+ onAcknowledged
17936
+ );
17937
+ upload = { ...next, uploaded: upload.uploaded + next.uploaded };
17938
+ }
17925
17939
  return {
17926
17940
  discovered: scan.discovered,
17927
- uploaded,
17941
+ uploaded: upload.uploaded,
17928
17942
  outboxBytes: await outboxSize(paths),
17929
17943
  historyCaughtUp: scan.caughtUp,
17930
- history: scan.history
17944
+ history: scan.history,
17945
+ retryAfterMs: upload.retryAfterMs ?? 0
17931
17946
  };
17932
17947
  }
17933
17948
 
@@ -19035,7 +19050,9 @@ async function runCollector(directory, expectedSource) {
19035
19050
  })
19036
19051
  );
19037
19052
  }
19038
- retryDelayMs = 5e3;
19053
+ retryDelayMs = Math.max(5e3, result.retryAfterMs);
19054
+ if (result.retryAfterMs > 0)
19055
+ retryDelayMs += Math.floor(Math.random() * 1e3);
19039
19056
  } catch (error2) {
19040
19057
  if (controller.signal.aborted) break;
19041
19058
  if (!trialSettled && runtimeVersion !== void 0) {
@@ -19865,7 +19882,7 @@ Connect supported agents${safeName ? ` to ${safeName}` : " to the workspace you
19865
19882
  "Run iteration connect interactively or pass --yes to approve uploads."
19866
19883
  );
19867
19884
  }
19868
- const prompt = createInterface3({
19885
+ const prompt = createInterface2({
19869
19886
  input: process.stdin,
19870
19887
  output: process.stdout
19871
19888
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iteration-cli",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",