@usecontextlayer/ctxe 0.4.3 → 0.4.5

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.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="59a42f57-145c-5f77-afb2-f1f76467783d")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1a858cd0-cb9a-550b-a3f9-5ca9387cf7df")}catch(e){}}();
4
4
  import { createRequire } from "node:module";
5
5
  import * as Sentry from "@sentry/node";
6
6
  import * as xi from "node:fs";
@@ -59,7 +59,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
59
59
 
60
60
  //#endregion
61
61
  //#region package.json
62
- var version$1 = "0.4.3";
62
+ var version$1 = "0.4.5";
63
63
 
64
64
  //#endregion
65
65
  //#region sentry.ts
@@ -7895,6 +7895,7 @@ const string$1 = (params) => {
7895
7895
  };
7896
7896
  const integer = /^-?\d+$/;
7897
7897
  const number$1 = /^-?\d+(?:\.\d+)?$/;
7898
+ const boolean$1 = /^(?:true|false)$/i;
7898
7899
  const lowercase = /^[^A-Z]*$/;
7899
7900
  const uppercase = /^[^a-z]*$/;
7900
7901
 
@@ -8693,6 +8694,24 @@ const $ZodNumberFormat = /*@__PURE__*/ $constructor("$ZodNumberFormat", (inst, d
8693
8694
  $ZodCheckNumberFormat.init(inst, def);
8694
8695
  $ZodNumber.init(inst, def);
8695
8696
  });
8697
+ const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
8698
+ $ZodType.init(inst, def);
8699
+ inst._zod.pattern = boolean$1;
8700
+ inst._zod.parse = (payload, _ctx) => {
8701
+ if (def.coerce) try {
8702
+ payload.value = Boolean(payload.value);
8703
+ } catch (_) {}
8704
+ const input = payload.value;
8705
+ if (typeof input === "boolean") return payload;
8706
+ payload.issues.push({
8707
+ expected: "boolean",
8708
+ code: "invalid_type",
8709
+ input,
8710
+ inst
8711
+ });
8712
+ return payload;
8713
+ };
8714
+ });
8696
8715
  const $ZodUnknown = /*@__PURE__*/ $constructor("$ZodUnknown", (inst, def) => {
8697
8716
  $ZodType.init(inst, def);
8698
8717
  inst._zod.parse = (payload) => payload;
@@ -9264,6 +9283,24 @@ const $ZodEnum = /*@__PURE__*/ $constructor("$ZodEnum", (inst, def) => {
9264
9283
  return payload;
9265
9284
  };
9266
9285
  });
9286
+ const $ZodLiteral = /*@__PURE__*/ $constructor("$ZodLiteral", (inst, def) => {
9287
+ $ZodType.init(inst, def);
9288
+ if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values");
9289
+ const values = new Set(def.values);
9290
+ inst._zod.values = values;
9291
+ inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
9292
+ inst._zod.parse = (payload, _ctx) => {
9293
+ const input = payload.value;
9294
+ if (values.has(input)) return payload;
9295
+ payload.issues.push({
9296
+ code: "invalid_value",
9297
+ values: def.values,
9298
+ input,
9299
+ inst
9300
+ });
9301
+ return payload;
9302
+ };
9303
+ });
9267
9304
  const $ZodTransform = /*@__PURE__*/ $constructor("$ZodTransform", (inst, def) => {
9268
9305
  $ZodType.init(inst, def);
9269
9306
  inst._zod.optin = "optional";
@@ -9837,6 +9874,13 @@ function _int(Class, params) {
9837
9874
  });
9838
9875
  }
9839
9876
  // @__NO_SIDE_EFFECTS__
9877
+ function _boolean(Class, params) {
9878
+ return new Class({
9879
+ type: "boolean",
9880
+ ...normalizeParams(params)
9881
+ });
9882
+ }
9883
+ // @__NO_SIDE_EFFECTS__
9840
9884
  function _unknown(Class) {
9841
9885
  return new Class({ type: "unknown" });
9842
9886
  }
@@ -10384,6 +10428,9 @@ const numberProcessor = (schema, ctx, _json, _params) => {
10384
10428
  else if (typeof maximum === "number") json.maximum = maximum;
10385
10429
  if (typeof multipleOf === "number") json.multipleOf = multipleOf;
10386
10430
  };
10431
+ const booleanProcessor = (_schema, _ctx, json, _params) => {
10432
+ json.type = "boolean";
10433
+ };
10387
10434
  const neverProcessor = (_schema, _ctx, json, _params) => {
10388
10435
  json.not = {};
10389
10436
  };
@@ -10395,6 +10442,27 @@ const enumProcessor = (schema, _ctx, json, _params) => {
10395
10442
  if (values.every((v) => typeof v === "string")) json.type = "string";
10396
10443
  json.enum = values;
10397
10444
  };
10445
+ const literalProcessor = (schema, ctx, json, _params) => {
10446
+ const def = schema._zod.def;
10447
+ const vals = [];
10448
+ for (const val of def.values) if (val === void 0) {
10449
+ if (ctx.unrepresentable === "throw") throw new Error("Literal `undefined` cannot be represented in JSON Schema");
10450
+ } else if (typeof val === "bigint") if (ctx.unrepresentable === "throw") throw new Error("BigInt literals cannot be represented in JSON Schema");
10451
+ else vals.push(Number(val));
10452
+ else vals.push(val);
10453
+ if (vals.length === 0) {} else if (vals.length === 1) {
10454
+ const val = vals[0];
10455
+ json.type = val === null ? "null" : typeof val;
10456
+ if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") json.enum = [val];
10457
+ else json.const = val;
10458
+ } else {
10459
+ if (vals.every((v) => typeof v === "number")) json.type = "number";
10460
+ if (vals.every((v) => typeof v === "string")) json.type = "string";
10461
+ if (vals.every((v) => typeof v === "boolean")) json.type = "boolean";
10462
+ if (vals.every((v) => v === null)) json.type = "null";
10463
+ json.enum = vals;
10464
+ }
10465
+ };
10398
10466
  const customProcessor = (_schema, ctx, _json, _params) => {
10399
10467
  if (ctx.unrepresentable === "throw") throw new Error("Custom types cannot be represented in JSON Schema");
10400
10468
  };
@@ -11050,6 +11118,14 @@ const ZodNumberFormat = /*@__PURE__*/ $constructor("ZodNumberFormat", (inst, def
11050
11118
  function int(params) {
11051
11119
  return _int(ZodNumberFormat, params);
11052
11120
  }
11121
+ const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => {
11122
+ $ZodBoolean.init(inst, def);
11123
+ ZodType.init(inst, def);
11124
+ inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
11125
+ });
11126
+ function boolean(params) {
11127
+ return _boolean(ZodBoolean, params);
11128
+ }
11053
11129
  const ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => {
11054
11130
  $ZodUnknown.init(inst, def);
11055
11131
  ZodType.init(inst, def);
@@ -11254,6 +11330,23 @@ function _enum(values, params) {
11254
11330
  ...normalizeParams(params)
11255
11331
  });
11256
11332
  }
11333
+ const ZodLiteral = /*@__PURE__*/ $constructor("ZodLiteral", (inst, def) => {
11334
+ $ZodLiteral.init(inst, def);
11335
+ ZodType.init(inst, def);
11336
+ inst._zod.processJSONSchema = (ctx, json, params) => literalProcessor(inst, ctx, json, params);
11337
+ inst.values = new Set(def.values);
11338
+ Object.defineProperty(inst, "value", { get() {
11339
+ if (def.values.length > 1) throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
11340
+ return def.values[0];
11341
+ } });
11342
+ });
11343
+ function literal(value, params) {
11344
+ return new ZodLiteral({
11345
+ type: "literal",
11346
+ values: Array.isArray(value) ? value : [value],
11347
+ ...normalizeParams(params)
11348
+ });
11349
+ }
11257
11350
  const ZodTransform = /*@__PURE__*/ $constructor("ZodTransform", (inst, def) => {
11258
11351
  $ZodTransform.init(inst, def);
11259
11352
  ZodType.init(inst, def);
@@ -29490,7 +29583,7 @@ function validate(raw, sourcePath) {
29490
29583
  }
29491
29584
  function applyDefaults(input) {
29492
29585
  return {
29493
- synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.3",
29586
+ synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.5",
29494
29587
  ...input.microsandbox !== void 0 ? { microsandbox: input.microsandbox } : {},
29495
29588
  ...input.oldestConsideredPoint !== void 0 ? { oldestConsideredPoint: input.oldestConsideredPoint } : {},
29496
29589
  maxSliceSize: input.maxSliceSize ?? 864e5,
@@ -29507,14 +29600,18 @@ function formatZodError$1(error) {
29507
29600
  }).join("; ");
29508
29601
  }
29509
29602
  function planLoopPass(input) {
29510
- const due = input.synthesizers.filter((synthesizer) => input.now - synthesizer.frontier >= input.tick);
29603
+ const frontierDue = input.synthesizers.filter((synthesizer) => input.now - synthesizer.frontier >= input.tick);
29604
+ const due = frontierDue.filter((synthesizer) => synthesizer.notBefore === void 0 || synthesizer.notBefore <= input.now);
29511
29605
  if (due.length > 0) return {
29512
29606
  kind: "run",
29513
29607
  order: due.map((synthesizer) => synthesizer.name).sort()
29514
29608
  };
29609
+ const gates = frontierDue.map((synthesizer) => synthesizer.notBefore).filter((notBefore) => notBefore !== void 0);
29610
+ const gatedUntil = gates.length > 0 ? Math.min(...gates) : void 0;
29515
29611
  return {
29516
29612
  kind: "idle",
29517
- wakeAt: input.synthesizers.length === 0 ? input.now + input.tick : Math.min(...input.synthesizers.map((synthesizer) => synthesizer.frontier + input.tick))
29613
+ wakeAt: input.synthesizers.length === 0 ? input.now + input.tick : Math.min(...input.synthesizers.map((synthesizer) => Math.max(synthesizer.frontier + input.tick, synthesizer.notBefore ?? 0))),
29614
+ ...gatedUntil !== void 0 && { gatedUntil }
29518
29615
  };
29519
29616
  }
29520
29617
  async function runEngineLoop(deps, options) {
@@ -29527,17 +29624,20 @@ async function runEngineLoop(deps, options) {
29527
29624
  tick: options.tick
29528
29625
  });
29529
29626
  if (pass.kind === "idle") {
29530
- if (options.mode === "backfill") return { stopReason: "caught_up" };
29627
+ if (options.mode === "backfill") return { stopReason: pass.gatedUntil !== void 0 ? "rate_limited" : "caught_up" };
29531
29628
  await deps.sleep(Math.max(0, pass.wakeAt - deps.now()));
29532
29629
  continue;
29533
29630
  }
29534
29631
  let advancedAny = false;
29632
+ let rateLimitedAny = false;
29535
29633
  for (const name of pass.order) {
29536
29634
  if (options.signal?.aborted) return { stopReason: "aborted" };
29537
- if ((await deps.runSlice(name)).advanced) advancedAny = true;
29635
+ const outcome = await deps.runSlice(name);
29636
+ if (outcome.advanced) advancedAny = true;
29637
+ if (outcome.rateLimited) rateLimitedAny = true;
29538
29638
  }
29539
29639
  if (!advancedAny) {
29540
- if (options.mode === "backfill") return { stopReason: "stalled" };
29640
+ if (options.mode === "backfill") return { stopReason: rateLimitedAny ? "rate_limited" : "stalled" };
29541
29641
  await deps.sleep(options.tick);
29542
29642
  }
29543
29643
  }
@@ -29904,6 +30004,189 @@ function dirtyWorkingTreeIssue(paths) {
29904
30004
  message: `Working tree is dirty:\n${formatWorkingTreePaths(paths)}\nRun 'ctxe repair' to roll back to HEAD (preserves .engine/runs/**).`
29905
30005
  };
29906
30006
  }
30007
+ const runTriggerSchema = _enum([
30008
+ "backfill",
30009
+ "daemon",
30010
+ "manual"
30011
+ ]);
30012
+ _enum([
30013
+ "success",
30014
+ "failed",
30015
+ "timed_out",
30016
+ "rate_limited"
30017
+ ]);
30018
+ const runStatusSchema = _enum([
30019
+ "starting",
30020
+ "running",
30021
+ "success",
30022
+ "failed",
30023
+ "timed_out",
30024
+ "rate_limited",
30025
+ "interrupted"
30026
+ ]);
30027
+ const claudeResultSchema = strictObject({
30028
+ api_error_status: number().int().optional(),
30029
+ is_error: boolean(),
30030
+ num_turns: number().int(),
30031
+ subtype: string().min(1),
30032
+ terminal_reason: string().min(1).optional(),
30033
+ total_cost_usd: number()
30034
+ });
30035
+ const runRecordSchema = strictObject({
30036
+ claude_result: claudeResultSchema.optional(),
30037
+ completed_at: string().min(1).optional(),
30038
+ duration_ms: number().int().nonnegative().optional(),
30039
+ exit_code: number().int().optional(),
30040
+ rate_limit_resets_at: string().min(1).optional(),
30041
+ run_id: string().min(1),
30042
+ started_at: string().min(1),
30043
+ status: runStatusSchema,
30044
+ synthesizer: string().min(1),
30045
+ triggered_by: runTriggerSchema
30046
+ });
30047
+ const RECORD_FILE_NAME = "record.json";
30048
+ const STDERR_FILE_NAME = "stderr";
30049
+ function buildStartingRunRecord(input) {
30050
+ return {
30051
+ run_id: input.runId,
30052
+ started_at: input.startedAt,
30053
+ status: "starting",
30054
+ synthesizer: input.synthesizerName,
30055
+ triggered_by: input.trigger
30056
+ };
30057
+ }
30058
+ function buildRunningRunRecord(record) {
30059
+ return {
30060
+ ...record,
30061
+ status: "running"
30062
+ };
30063
+ }
30064
+ function buildTerminalRunRecord(input) {
30065
+ return {
30066
+ ...input.claudeResult !== void 0 && { claude_result: input.claudeResult },
30067
+ completed_at: input.completedAt,
30068
+ duration_ms: input.durationMs,
30069
+ exit_code: input.exitCode,
30070
+ ...input.rateLimitResetsAt !== void 0 && { rate_limit_resets_at: input.rateLimitResetsAt },
30071
+ run_id: input.runId,
30072
+ started_at: input.startedAt,
30073
+ status: input.status,
30074
+ synthesizer: input.synthesizerName,
30075
+ triggered_by: input.trigger
30076
+ };
30077
+ }
30078
+ function buildInterruptedRunRecord(record, completedAt) {
30079
+ return {
30080
+ ...record,
30081
+ completed_at: completedAt,
30082
+ status: "interrupted"
30083
+ };
30084
+ }
30085
+ function getRunDir(input) {
30086
+ return runDirPath(input.rootDir, input.synthesizerName, input.runId);
30087
+ }
30088
+ async function ensureRunDir(input) {
30089
+ const runDir = getRunDir(input);
30090
+ await mkdir(runDir, { recursive: true });
30091
+ return runDir;
30092
+ }
30093
+ async function writeRunRecord(runDir, record) {
30094
+ await mkdir(runDir, { recursive: true });
30095
+ await writeJsonFileAtomic(path.join(runDir, RECORD_FILE_NAME), runRecordSchema.parse(record));
30096
+ }
30097
+ async function readRunRecord(runDir) {
30098
+ const raw = await readFile(path.join(runDir, RECORD_FILE_NAME), "utf8");
30099
+ return runRecordSchema.parse(JSON.parse(raw));
30100
+ }
30101
+ async function listRunRecords(input) {
30102
+ const synthesizerNames = input.synthesizerName !== void 0 ? [input.synthesizerName] : await listSynthesizerRunDirNames(input.rootDir);
30103
+ const entries = [];
30104
+ for (const synthesizerName of synthesizerNames) {
30105
+ const synthesizerDir = synthesizerRunsDirPath(input.rootDir, synthesizerName);
30106
+ let runIds;
30107
+ try {
30108
+ runIds = await listDirectoryNames(synthesizerDir);
30109
+ } catch (error) {
30110
+ if (isNodeError(error) && error.code === "ENOENT") continue;
30111
+ throw error;
30112
+ }
30113
+ for (const runId of runIds) {
30114
+ const runDir = path.join(synthesizerDir, runId);
30115
+ try {
30116
+ entries.push({
30117
+ record: await readRunRecord(runDir),
30118
+ runDir,
30119
+ runId
30120
+ });
30121
+ } catch (error) {
30122
+ if (isNodeError(error) && error.code === "ENOENT") continue;
30123
+ throw error;
30124
+ }
30125
+ }
30126
+ }
30127
+ return entries.sort(compareRunRecordEntries);
30128
+ }
30129
+ async function readRunStderr(runDir) {
30130
+ return await readOptionalTextFile(path.join(runDir, STDERR_FILE_NAME));
30131
+ }
30132
+ async function writeRunStderr(input) {
30133
+ await mkdir(input.runDir, { recursive: true });
30134
+ await writeFileAtomic(path.join(input.runDir, STDERR_FILE_NAME), input.stderr);
30135
+ }
30136
+ async function markInterruptedRuns(rootDir) {
30137
+ const interrupted = [];
30138
+ const engineRunsDir = runsDirPath(rootDir);
30139
+ let synthesizerDirs;
30140
+ try {
30141
+ synthesizerDirs = await listDirectoryNames(engineRunsDir);
30142
+ } catch (error) {
30143
+ if (isNodeError(error) && error.code === "ENOENT") return [];
30144
+ throw error;
30145
+ }
30146
+ for (const synthesizerName of synthesizerDirs) {
30147
+ const synthesizerDir = path.join(engineRunsDir, synthesizerName);
30148
+ for (const runId of await listDirectoryNames(synthesizerDir)) {
30149
+ const runDir = path.join(synthesizerDir, runId);
30150
+ let record;
30151
+ try {
30152
+ record = await readRunRecord(runDir);
30153
+ } catch (error) {
30154
+ if (isNodeError(error) && error.code === "ENOENT") continue;
30155
+ throw error;
30156
+ }
30157
+ if (record.status !== "starting" && record.status !== "running") continue;
30158
+ const nextRecord = buildInterruptedRunRecord(record, (/* @__PURE__ */ new Date()).toISOString());
30159
+ await writeRunRecord(runDir, nextRecord);
30160
+ interrupted.push(nextRecord);
30161
+ }
30162
+ }
30163
+ return interrupted;
30164
+ }
30165
+ async function listDirectoryNames(dirPath) {
30166
+ return (await readdir$1(dirPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
30167
+ }
30168
+ async function listSynthesizerRunDirNames(rootDir) {
30169
+ const engineRunsDir = runsDirPath(rootDir);
30170
+ try {
30171
+ return await listDirectoryNames(engineRunsDir);
30172
+ } catch (error) {
30173
+ if (isNodeError(error) && error.code === "ENOENT") return [];
30174
+ throw error;
30175
+ }
30176
+ }
30177
+ async function readOptionalTextFile(filePath) {
30178
+ try {
30179
+ return await readFile(filePath, "utf8");
30180
+ } catch (error) {
30181
+ if (isNodeError(error) && error.code === "ENOENT") return null;
30182
+ throw error;
30183
+ }
30184
+ }
30185
+ function compareRunRecordEntries(left, right) {
30186
+ const timeCompare = Date.parse(right.record.started_at) - Date.parse(left.record.started_at);
30187
+ if (timeCompare !== 0) return timeCompare;
30188
+ return right.runId.localeCompare(left.runId);
30189
+ }
29907
30190
  const synthesizerSpecFrontmatterSchema = strictObject({
29908
30191
  description: string().optional(),
29909
30192
  name: string().trim().min(1)
@@ -30461,12 +30744,15 @@ async function executeSynthesizerRun(input) {
30461
30744
  const result = await input.executor.run(executorInput);
30462
30745
  const completedAtMs = now().getTime();
30463
30746
  const durationMs = Math.max(0, completedAtMs - startedAtMs);
30464
- const status = exitCodeToStatus(result.exitCode);
30747
+ const status = classifyOutcome(result.exitCode, result.observation);
30748
+ const rateLimitResetsAt = observedRateLimitResetsAt(result.observation);
30465
30749
  return {
30466
30750
  outcome: {
30467
30751
  durationMs,
30468
30752
  exitCode: result.exitCode,
30469
- status
30753
+ status,
30754
+ ...result.observation.result !== void 0 && { claudeResult: result.observation.result },
30755
+ ...rateLimitResetsAt !== void 0 && { rateLimitResetsAt }
30470
30756
  },
30471
30757
  stderr: result.stderr
30472
30758
  };
@@ -30482,11 +30768,19 @@ async function executeSynthesizerRun(input) {
30482
30768
  };
30483
30769
  }
30484
30770
  }
30485
- function exitCodeToStatus(exitCode) {
30771
+ function classifyOutcome(exitCode, observation) {
30486
30772
  if (exitCode === 0) return "success";
30487
30773
  if (exitCode === 124) return "timed_out";
30774
+ if (observation.result?.api_error_status === 429) return "rate_limited";
30775
+ if (observation.lastRateLimit?.status === "rejected") return "rate_limited";
30488
30776
  return "failed";
30489
30777
  }
30778
+ function observedRateLimitResetsAt(observation) {
30779
+ const rateLimit = observation.lastRateLimit;
30780
+ if (rateLimit === void 0) return void 0;
30781
+ if (rateLimit.status !== "rejected" || rateLimit.resetsAt === void 0) return;
30782
+ return msToIso(rateLimit.resetsAt * 1e3);
30783
+ }
30490
30784
  function buildExecutorInput(input) {
30491
30785
  return {
30492
30786
  image: input.synthesizerImage,
@@ -30576,175 +30870,6 @@ function reconcileCursor(input) {
30576
30870
  frontier: input.prevCursor.frontier
30577
30871
  };
30578
30872
  }
30579
- const runTriggerSchema = _enum([
30580
- "backfill",
30581
- "daemon",
30582
- "manual"
30583
- ]);
30584
- _enum([
30585
- "success",
30586
- "failed",
30587
- "timed_out"
30588
- ]);
30589
- const runStatusSchema = _enum([
30590
- "starting",
30591
- "running",
30592
- "success",
30593
- "failed",
30594
- "timed_out",
30595
- "interrupted"
30596
- ]);
30597
- const runRecordSchema = strictObject({
30598
- completed_at: string().min(1).optional(),
30599
- duration_ms: number().int().nonnegative().optional(),
30600
- exit_code: number().int().optional(),
30601
- run_id: string().min(1),
30602
- started_at: string().min(1),
30603
- status: runStatusSchema,
30604
- synthesizer: string().min(1),
30605
- triggered_by: runTriggerSchema
30606
- });
30607
- const RECORD_FILE_NAME = "record.json";
30608
- const STDERR_FILE_NAME = "stderr";
30609
- function buildStartingRunRecord(input) {
30610
- return {
30611
- run_id: input.runId,
30612
- started_at: input.startedAt,
30613
- status: "starting",
30614
- synthesizer: input.synthesizerName,
30615
- triggered_by: input.trigger
30616
- };
30617
- }
30618
- function buildRunningRunRecord(record) {
30619
- return {
30620
- ...record,
30621
- status: "running"
30622
- };
30623
- }
30624
- function buildTerminalRunRecord(input) {
30625
- return {
30626
- completed_at: input.completedAt,
30627
- duration_ms: input.durationMs,
30628
- exit_code: input.exitCode,
30629
- run_id: input.runId,
30630
- started_at: input.startedAt,
30631
- status: input.status,
30632
- synthesizer: input.synthesizerName,
30633
- triggered_by: input.trigger
30634
- };
30635
- }
30636
- function buildInterruptedRunRecord(record, completedAt) {
30637
- return {
30638
- ...record,
30639
- completed_at: completedAt,
30640
- status: "interrupted"
30641
- };
30642
- }
30643
- function getRunDir(input) {
30644
- return runDirPath(input.rootDir, input.synthesizerName, input.runId);
30645
- }
30646
- async function ensureRunDir(input) {
30647
- const runDir = getRunDir(input);
30648
- await mkdir(runDir, { recursive: true });
30649
- return runDir;
30650
- }
30651
- async function writeRunRecord(runDir, record) {
30652
- await mkdir(runDir, { recursive: true });
30653
- await writeJsonFileAtomic(path.join(runDir, RECORD_FILE_NAME), runRecordSchema.parse(record));
30654
- }
30655
- async function readRunRecord(runDir) {
30656
- const raw = await readFile(path.join(runDir, RECORD_FILE_NAME), "utf8");
30657
- return runRecordSchema.parse(JSON.parse(raw));
30658
- }
30659
- async function listRunRecords(input) {
30660
- const synthesizerNames = input.synthesizerName !== void 0 ? [input.synthesizerName] : await listSynthesizerRunDirNames(input.rootDir);
30661
- const entries = [];
30662
- for (const synthesizerName of synthesizerNames) {
30663
- const synthesizerDir = synthesizerRunsDirPath(input.rootDir, synthesizerName);
30664
- let runIds;
30665
- try {
30666
- runIds = await listDirectoryNames(synthesizerDir);
30667
- } catch (error) {
30668
- if (isNodeError(error) && error.code === "ENOENT") continue;
30669
- throw error;
30670
- }
30671
- for (const runId of runIds) {
30672
- const runDir = path.join(synthesizerDir, runId);
30673
- try {
30674
- entries.push({
30675
- record: await readRunRecord(runDir),
30676
- runDir,
30677
- runId
30678
- });
30679
- } catch (error) {
30680
- if (isNodeError(error) && error.code === "ENOENT") continue;
30681
- throw error;
30682
- }
30683
- }
30684
- }
30685
- return entries.sort(compareRunRecordEntries);
30686
- }
30687
- async function readRunStderr(runDir) {
30688
- return await readOptionalTextFile(path.join(runDir, STDERR_FILE_NAME));
30689
- }
30690
- async function writeRunStderr(input) {
30691
- await mkdir(input.runDir, { recursive: true });
30692
- await writeFileAtomic(path.join(input.runDir, STDERR_FILE_NAME), input.stderr);
30693
- }
30694
- async function markInterruptedRuns(rootDir) {
30695
- const interrupted = [];
30696
- const engineRunsDir = runsDirPath(rootDir);
30697
- let synthesizerDirs;
30698
- try {
30699
- synthesizerDirs = await listDirectoryNames(engineRunsDir);
30700
- } catch (error) {
30701
- if (isNodeError(error) && error.code === "ENOENT") return [];
30702
- throw error;
30703
- }
30704
- for (const synthesizerName of synthesizerDirs) {
30705
- const synthesizerDir = path.join(engineRunsDir, synthesizerName);
30706
- for (const runId of await listDirectoryNames(synthesizerDir)) {
30707
- const runDir = path.join(synthesizerDir, runId);
30708
- let record;
30709
- try {
30710
- record = await readRunRecord(runDir);
30711
- } catch (error) {
30712
- if (isNodeError(error) && error.code === "ENOENT") continue;
30713
- throw error;
30714
- }
30715
- if (record.status !== "starting" && record.status !== "running") continue;
30716
- const nextRecord = buildInterruptedRunRecord(record, (/* @__PURE__ */ new Date()).toISOString());
30717
- await writeRunRecord(runDir, nextRecord);
30718
- interrupted.push(nextRecord);
30719
- }
30720
- }
30721
- return interrupted;
30722
- }
30723
- async function listDirectoryNames(dirPath) {
30724
- return (await readdir$1(dirPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
30725
- }
30726
- async function listSynthesizerRunDirNames(rootDir) {
30727
- const engineRunsDir = runsDirPath(rootDir);
30728
- try {
30729
- return await listDirectoryNames(engineRunsDir);
30730
- } catch (error) {
30731
- if (isNodeError(error) && error.code === "ENOENT") return [];
30732
- throw error;
30733
- }
30734
- }
30735
- async function readOptionalTextFile(filePath) {
30736
- try {
30737
- return await readFile(filePath, "utf8");
30738
- } catch (error) {
30739
- if (isNodeError(error) && error.code === "ENOENT") return null;
30740
- throw error;
30741
- }
30742
- }
30743
- function compareRunRecordEntries(left, right) {
30744
- const timeCompare = Date.parse(right.record.started_at) - Date.parse(left.record.started_at);
30745
- if (timeCompare !== 0) return timeCompare;
30746
- return right.runId.localeCompare(left.runId);
30747
- }
30748
30873
  function renderSliceJson(slice) {
30749
30874
  const windows = {};
30750
30875
  for (const [table, window] of Object.entries(slice)) windows[table] = {
@@ -30859,9 +30984,11 @@ async function finalizeFailedSynthesizerTransaction(input) {
30859
30984
  function buildTerminalRecord(input) {
30860
30985
  const { ctx, runResult, completedAt } = input;
30861
30986
  return buildTerminalRunRecord({
30987
+ ...runResult.outcome.claudeResult !== void 0 && { claudeResult: runResult.outcome.claudeResult },
30862
30988
  completedAt,
30863
30989
  durationMs: runResult.outcome.durationMs,
30864
30990
  exitCode: runResult.outcome.exitCode,
30991
+ ...runResult.outcome.rateLimitResetsAt !== void 0 && { rateLimitResetsAt: runResult.outcome.rateLimitResetsAt },
30865
30992
  runId: ctx.runId,
30866
30993
  startedAt: ctx.startedAt,
30867
30994
  status: runResult.outcome.status,
@@ -30985,10 +31112,14 @@ var Engine = class {
30985
31112
  listSynthesizers: async () => {
30986
31113
  await this.refreshSynthesizers();
30987
31114
  const specs = this.listSynthesizers().filter((spec) => options.scopeTo === void 0 || spec.name === options.scopeTo);
30988
- return Promise.all(specs.map(async (spec) => ({
30989
- frontier: (await readCursor(rootDir, spec.name))?.frontier ?? 0,
30990
- name: spec.name
30991
- })));
31115
+ return Promise.all(specs.map(async (spec) => {
31116
+ const notBefore = await this.readRateLimitGate(spec.name, clock().getTime());
31117
+ return {
31118
+ frontier: (await readCursor(rootDir, spec.name))?.frontier ?? 0,
31119
+ name: spec.name,
31120
+ ...notBefore !== void 0 && { notBefore }
31121
+ };
31122
+ }));
30992
31123
  },
30993
31124
  now: () => clock().getTime(),
30994
31125
  runSlice: async (name) => {
@@ -30999,11 +31130,17 @@ var Engine = class {
30999
31130
  });
31000
31131
  options.onSlice?.(outcome);
31001
31132
  this.logSliceLag(name, outcome);
31002
- return { advanced: outcome.record.status === "success" };
31133
+ return {
31134
+ advanced: outcome.record.status === "success",
31135
+ rateLimited: outcome.record.status === "rate_limited"
31136
+ };
31003
31137
  } catch (error) {
31004
31138
  if (error instanceof TransactionLockHeldError) {
31005
31139
  this.logger.warn(`[engine] skipping '${name}': ${error.message}`);
31006
- return { advanced: false };
31140
+ return {
31141
+ advanced: false,
31142
+ rateLimited: false
31143
+ };
31007
31144
  }
31008
31145
  throw error;
31009
31146
  }
@@ -31011,6 +31148,18 @@ var Engine = class {
31011
31148
  sleep: (ms) => sleepAbortable(ms, options.signal)
31012
31149
  };
31013
31150
  }
31151
+ async readRateLimitGate(synthesizerName, nowMs) {
31152
+ const latest = (await listRunRecords({
31153
+ rootDir: this.repo.rootDir,
31154
+ synthesizerName
31155
+ }))[0]?.record;
31156
+ if (latest?.status !== "rate_limited") return void 0;
31157
+ if (latest.rate_limit_resets_at === void 0) return void 0;
31158
+ const resetsAt = isoToMs(latest.rate_limit_resets_at, "rate_limit_resets_at");
31159
+ if (resetsAt <= nowMs) return void 0;
31160
+ this.logger.info(`[engine] '${synthesizerName}' rate limited — usage window resets ${latest.rate_limit_resets_at}; holding runs until then`);
31161
+ return resetsAt;
31162
+ }
31014
31163
  logSliceLag(name, outcome) {
31015
31164
  if (outcome.record.status !== "success") {
31016
31165
  this.logger.warn(`[engine] '${name}' slice did not advance (status=${outcome.record.status})`);
@@ -31152,17 +31301,83 @@ function latestRunByName(entries) {
31152
31301
  for (const { record } of entries) if (!latest.has(record.synthesizer)) latest.set(record.synthesizer, record);
31153
31302
  return latest;
31154
31303
  }
31304
+ const rateLimitEventSchema = object({
31305
+ rate_limit_info: object({
31306
+ resetsAt: number().optional(),
31307
+ status: string().min(1)
31308
+ }),
31309
+ type: literal("rate_limit_event")
31310
+ });
31311
+ const resultEventSchema = object({
31312
+ ...claudeResultSchema.shape,
31313
+ type: literal("result")
31314
+ });
31315
+ var ClaudeStreamObserver = class {
31316
+ decoder = new TextDecoder("utf-8", { fatal: false });
31317
+ pendingLine = "";
31318
+ lastRateLimit;
31319
+ result;
31320
+ feed(chunk) {
31321
+ this.pendingLine += this.decoder.decode(chunk, { stream: true });
31322
+ for (;;) {
31323
+ const newlineAt = this.pendingLine.indexOf("\n");
31324
+ if (newlineAt === -1) return;
31325
+ const line = this.pendingLine.slice(0, newlineAt);
31326
+ this.pendingLine = this.pendingLine.slice(newlineAt + 1);
31327
+ this.observeLine(line);
31328
+ }
31329
+ }
31330
+ get resultSeen() {
31331
+ return this.result !== void 0;
31332
+ }
31333
+ observation() {
31334
+ const trailing = this.pendingLine + this.decoder.decode();
31335
+ this.pendingLine = "";
31336
+ if (trailing.length > 0) this.observeLine(trailing);
31337
+ return {
31338
+ ...this.lastRateLimit !== void 0 && { lastRateLimit: this.lastRateLimit },
31339
+ ...this.result !== void 0 && { result: this.result }
31340
+ };
31341
+ }
31342
+ observeLine(line) {
31343
+ if (line.trim().length === 0) return;
31344
+ let event;
31345
+ try {
31346
+ event = JSON.parse(line);
31347
+ } catch {
31348
+ return;
31349
+ }
31350
+ const rateLimit = rateLimitEventSchema.safeParse(event);
31351
+ if (rateLimit.success) {
31352
+ const { status, resetsAt } = rateLimit.data.rate_limit_info;
31353
+ this.lastRateLimit = {
31354
+ status,
31355
+ ...resetsAt !== void 0 && { resetsAt }
31356
+ };
31357
+ return;
31358
+ }
31359
+ const result = resultEventSchema.safeParse(event);
31360
+ if (result.success) {
31361
+ const { type: _type, ...fields } = result.data;
31362
+ this.result = fields;
31363
+ }
31364
+ }
31365
+ };
31155
31366
  const GUEST_MEMORY_MIB = 2048;
31156
31367
  const EXEC_INACTIVITY_TIMEOUT_MS = 600 * 1e3;
31157
31368
  const EXEC_MAX_DURATION_MS = 7200 * 1e3;
31369
+ const EXEC_RESULT_GRACE_MS = 60 * 1e3;
31158
31370
  const DRAIN_TIMED_OUT = Symbol("drain-timed-out");
31159
31371
  async function drainExecStream(handle, options) {
31372
+ const observer = new ClaudeStreamObserver();
31160
31373
  const startedAt = Date.now();
31161
31374
  let lastEventAt = startedAt;
31375
+ let resultSeenAt;
31162
31376
  for (;;) {
31163
31377
  const ceilingDeadline = startedAt + options.maxDurationMs;
31164
31378
  const inactivityDeadline = lastEventAt + options.inactivityMs;
31165
- const deadline = Math.min(ceilingDeadline, inactivityDeadline);
31379
+ const graceDeadline = resultSeenAt === void 0 ? Number.POSITIVE_INFINITY : resultSeenAt + options.resultGraceMs;
31380
+ const deadline = Math.min(ceilingDeadline, inactivityDeadline, graceDeadline);
31166
31381
  let timer;
31167
31382
  const timedOut = new Promise((resolve) => {
31168
31383
  timer = setTimeout(() => resolve(DRAIN_TIMED_OUT), Math.max(0, deadline - Date.now()));
@@ -31176,17 +31391,31 @@ async function drainExecStream(handle, options) {
31176
31391
  }
31177
31392
  if (settled === DRAIN_TIMED_OUT) {
31178
31393
  pending.catch(() => {});
31394
+ const observation = observer.observation();
31395
+ if (observation.result !== void 0) return {
31396
+ kind: "result_no_exit",
31397
+ observation,
31398
+ result: observation.result
31399
+ };
31179
31400
  return {
31180
31401
  kind: "timed_out",
31402
+ observation,
31181
31403
  reason: ceilingDeadline <= inactivityDeadline ? "max_duration" : "inactivity"
31182
31404
  };
31183
31405
  }
31184
- if (settled === null) return { kind: "ended" };
31406
+ if (settled === null) return {
31407
+ kind: "ended",
31408
+ observation: observer.observation()
31409
+ };
31185
31410
  lastEventAt = Date.now();
31186
31411
  if (settled.kind === "stderr") options.onStderr(settled.data);
31187
- else if (settled.kind === "exited") return {
31412
+ else if (settled.kind === "stdout") {
31413
+ observer.feed(settled.data);
31414
+ if (resultSeenAt === void 0 && observer.resultSeen) resultSeenAt = lastEventAt;
31415
+ } else if (settled.kind === "exited") return {
31188
31416
  exitCode: settled.code,
31189
- kind: "exited"
31417
+ kind: "exited",
31418
+ observation: observer.observation()
31190
31419
  };
31191
31420
  }
31192
31421
  }
@@ -31196,6 +31425,7 @@ var MicrosandboxClaudeExecutor = class {
31196
31425
  const runPlan = buildMicrosandboxClaudeRunPlan(input);
31197
31426
  const stderrParts = [];
31198
31427
  let exitCode = 1;
31428
+ let observation = {};
31199
31429
  let sandbox = null;
31200
31430
  let authMaterial = null;
31201
31431
  try {
@@ -31222,11 +31452,16 @@ var MicrosandboxClaudeExecutor = class {
31222
31452
  const drained = await drainExecStream(await sandbox.execStreamWith(cmd, (e) => e.args(args)), {
31223
31453
  inactivityMs: EXEC_INACTIVITY_TIMEOUT_MS,
31224
31454
  maxDurationMs: EXEC_MAX_DURATION_MS,
31225
- onStderr: (data) => stderrParts.push(stderrDecoder.decode(data, { stream: true }))
31455
+ onStderr: (data) => stderrParts.push(stderrDecoder.decode(data, { stream: true })),
31456
+ resultGraceMs: EXEC_RESULT_GRACE_MS
31226
31457
  });
31227
31458
  stderrParts.push(stderrDecoder.decode());
31459
+ observation = drained.observation;
31228
31460
  if (drained.kind === "exited") exitCode = drained.exitCode;
31229
- else if (drained.kind === "timed_out") {
31461
+ else if (drained.kind === "result_no_exit") {
31462
+ exitCode = drained.result.is_error ? 1 : 0;
31463
+ stderrParts.push(`claude emitted its terminal result (is_error: ${drained.result.is_error}) but never exited within ${EXEC_RESULT_GRACE_MS / 1e3}s; concluding the run from the result event and tearing the sandbox down\n`);
31464
+ } else if (drained.kind === "timed_out") {
31230
31465
  exitCode = 124;
31231
31466
  stderrParts.push(drained.reason === "inactivity" ? `exec stream timed out: no events for ${EXEC_INACTIVITY_TIMEOUT_MS / 6e4} minutes; abandoning the run and tearing the sandbox down\n` : `exec stream timed out: run exceeded the ${EXEC_MAX_DURATION_MS / 6e4}-minute ceiling; abandoning the run and tearing the sandbox down\n`);
31232
31467
  }
@@ -31254,6 +31489,7 @@ var MicrosandboxClaudeExecutor = class {
31254
31489
  }
31255
31490
  return {
31256
31491
  exitCode,
31492
+ observation,
31257
31493
  stderr: stderrParts.join("")
31258
31494
  };
31259
31495
  }
@@ -31590,7 +31826,7 @@ async function runBackfillCommand(input) {
31590
31826
  ...summary,
31591
31827
  finalFrontier: summary.finalFrontier === null ? null : msToIso(summary.finalFrontier)
31592
31828
  })}\n`);
31593
- if (summary.stopReason === "stalled") process.exitCode = 1;
31829
+ if (summary.stopReason === "stalled" || summary.stopReason === "rate_limited") process.exitCode = 1;
31594
31830
  }
31595
31831
 
31596
31832
  //#endregion
@@ -31783,4 +32019,4 @@ runCli().catch((error) => {
31783
32019
  //#endregion
31784
32020
  export { createProgram, resolveRepoContext, resolveRuntimePaths, runCli };
31785
32021
  //# sourceMappingURL=cli.mjs.map
31786
- //# debugId=59a42f57-145c-5f77-afb2-f1f76467783d
32022
+ //# debugId=1a858cd0-cb9a-550b-a3f9-5ca9387cf7df