@usecontextlayer/ctxe 0.4.3 → 0.4.6

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]="ee0e5987-2fbc-50ed-9038-a0c4541b7548")}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.6";
63
63
 
64
64
  //#endregion
65
65
  //#region sentry.ts
@@ -7894,7 +7894,8 @@ const string$1 = (params) => {
7894
7894
  return new RegExp(`^${regex}$`);
7895
7895
  };
7896
7896
  const integer = /^-?\d+$/;
7897
- const number$1 = /^-?\d+(?:\.\d+)?$/;
7897
+ const number$2 = /^-?\d+(?:\.\d+)?$/;
7898
+ const boolean$1 = /^(?:true|false)$/i;
7898
7899
  const lowercase = /^[^A-Z]*$/;
7899
7900
  const uppercase = /^[^a-z]*$/;
7900
7901
 
@@ -8671,7 +8672,7 @@ const $ZodJWT = /*@__PURE__*/ $constructor("$ZodJWT", (inst, def) => {
8671
8672
  });
8672
8673
  const $ZodNumber = /*@__PURE__*/ $constructor("$ZodNumber", (inst, def) => {
8673
8674
  $ZodType.init(inst, def);
8674
- inst._zod.pattern = inst._zod.bag.pattern ?? number$1;
8675
+ inst._zod.pattern = inst._zod.bag.pattern ?? number$2;
8675
8676
  inst._zod.parse = (payload, _ctx) => {
8676
8677
  if (def.coerce) try {
8677
8678
  payload.value = Number(payload.value);
@@ -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;
@@ -9038,6 +9057,62 @@ const $ZodUnion = /*@__PURE__*/ $constructor("$ZodUnion", (inst, def) => {
9038
9057
  });
9039
9058
  };
9040
9059
  });
9060
+ const $ZodDiscriminatedUnion = /*@__PURE__*/ $constructor("$ZodDiscriminatedUnion", (inst, def) => {
9061
+ def.inclusive = false;
9062
+ $ZodUnion.init(inst, def);
9063
+ const _super = inst._zod.parse;
9064
+ defineLazy(inst._zod, "propValues", () => {
9065
+ const propValues = {};
9066
+ for (const option of def.options) {
9067
+ const pv = option._zod.propValues;
9068
+ if (!pv || Object.keys(pv).length === 0) throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
9069
+ for (const [k, v] of Object.entries(pv)) {
9070
+ if (!propValues[k]) propValues[k] = /* @__PURE__ */ new Set();
9071
+ for (const val of v) propValues[k].add(val);
9072
+ }
9073
+ }
9074
+ return propValues;
9075
+ });
9076
+ const disc = cached(() => {
9077
+ const opts = def.options;
9078
+ const map = /* @__PURE__ */ new Map();
9079
+ for (const o of opts) {
9080
+ const values = o._zod.propValues?.[def.discriminator];
9081
+ if (!values || values.size === 0) throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o)}"`);
9082
+ for (const v of values) {
9083
+ if (map.has(v)) throw new Error(`Duplicate discriminator value "${String(v)}"`);
9084
+ map.set(v, o);
9085
+ }
9086
+ }
9087
+ return map;
9088
+ });
9089
+ inst._zod.parse = (payload, ctx) => {
9090
+ const input = payload.value;
9091
+ if (!isObject$1(input)) {
9092
+ payload.issues.push({
9093
+ code: "invalid_type",
9094
+ expected: "object",
9095
+ input,
9096
+ inst
9097
+ });
9098
+ return payload;
9099
+ }
9100
+ const opt = disc.value.get(input?.[def.discriminator]);
9101
+ if (opt) return opt._zod.run(payload, ctx);
9102
+ if (def.unionFallback || ctx.direction === "backward") return _super(payload, ctx);
9103
+ payload.issues.push({
9104
+ code: "invalid_union",
9105
+ errors: [],
9106
+ note: "No matching discriminator",
9107
+ discriminator: def.discriminator,
9108
+ options: Array.from(disc.value.keys()),
9109
+ input,
9110
+ path: [def.discriminator],
9111
+ inst
9112
+ });
9113
+ return payload;
9114
+ };
9115
+ });
9041
9116
  const $ZodIntersection = /*@__PURE__*/ $constructor("$ZodIntersection", (inst, def) => {
9042
9117
  $ZodType.init(inst, def);
9043
9118
  inst._zod.parse = (payload, ctx) => {
@@ -9208,7 +9283,7 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
9208
9283
  issues: []
9209
9284
  }, ctx);
9210
9285
  if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
9211
- if (typeof key === "string" && number$1.test(key) && keyResult.issues.length) {
9286
+ if (typeof key === "string" && number$2.test(key) && keyResult.issues.length) {
9212
9287
  const retryResult = def.keyType._zod.run({
9213
9288
  value: Number(key),
9214
9289
  issues: []
@@ -9264,6 +9339,24 @@ const $ZodEnum = /*@__PURE__*/ $constructor("$ZodEnum", (inst, def) => {
9264
9339
  return payload;
9265
9340
  };
9266
9341
  });
9342
+ const $ZodLiteral = /*@__PURE__*/ $constructor("$ZodLiteral", (inst, def) => {
9343
+ $ZodType.init(inst, def);
9344
+ if (def.values.length === 0) throw new Error("Cannot create literal schema with no valid values");
9345
+ const values = new Set(def.values);
9346
+ inst._zod.values = values;
9347
+ inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$`);
9348
+ inst._zod.parse = (payload, _ctx) => {
9349
+ const input = payload.value;
9350
+ if (values.has(input)) return payload;
9351
+ payload.issues.push({
9352
+ code: "invalid_value",
9353
+ values: def.values,
9354
+ input,
9355
+ inst
9356
+ });
9357
+ return payload;
9358
+ };
9359
+ });
9267
9360
  const $ZodTransform = /*@__PURE__*/ $constructor("$ZodTransform", (inst, def) => {
9268
9361
  $ZodType.init(inst, def);
9269
9362
  inst._zod.optin = "optional";
@@ -9827,6 +9920,15 @@ function _number(Class, params) {
9827
9920
  });
9828
9921
  }
9829
9922
  // @__NO_SIDE_EFFECTS__
9923
+ function _coercedNumber(Class, params) {
9924
+ return new Class({
9925
+ type: "number",
9926
+ coerce: true,
9927
+ checks: [],
9928
+ ...normalizeParams(params)
9929
+ });
9930
+ }
9931
+ // @__NO_SIDE_EFFECTS__
9830
9932
  function _int(Class, params) {
9831
9933
  return new Class({
9832
9934
  type: "number",
@@ -9837,6 +9939,13 @@ function _int(Class, params) {
9837
9939
  });
9838
9940
  }
9839
9941
  // @__NO_SIDE_EFFECTS__
9942
+ function _boolean(Class, params) {
9943
+ return new Class({
9944
+ type: "boolean",
9945
+ ...normalizeParams(params)
9946
+ });
9947
+ }
9948
+ // @__NO_SIDE_EFFECTS__
9840
9949
  function _unknown(Class) {
9841
9950
  return new Class({ type: "unknown" });
9842
9951
  }
@@ -10384,6 +10493,9 @@ const numberProcessor = (schema, ctx, _json, _params) => {
10384
10493
  else if (typeof maximum === "number") json.maximum = maximum;
10385
10494
  if (typeof multipleOf === "number") json.multipleOf = multipleOf;
10386
10495
  };
10496
+ const booleanProcessor = (_schema, _ctx, json, _params) => {
10497
+ json.type = "boolean";
10498
+ };
10387
10499
  const neverProcessor = (_schema, _ctx, json, _params) => {
10388
10500
  json.not = {};
10389
10501
  };
@@ -10395,6 +10507,27 @@ const enumProcessor = (schema, _ctx, json, _params) => {
10395
10507
  if (values.every((v) => typeof v === "string")) json.type = "string";
10396
10508
  json.enum = values;
10397
10509
  };
10510
+ const literalProcessor = (schema, ctx, json, _params) => {
10511
+ const def = schema._zod.def;
10512
+ const vals = [];
10513
+ for (const val of def.values) if (val === void 0) {
10514
+ if (ctx.unrepresentable === "throw") throw new Error("Literal `undefined` cannot be represented in JSON Schema");
10515
+ } else if (typeof val === "bigint") if (ctx.unrepresentable === "throw") throw new Error("BigInt literals cannot be represented in JSON Schema");
10516
+ else vals.push(Number(val));
10517
+ else vals.push(val);
10518
+ if (vals.length === 0) {} else if (vals.length === 1) {
10519
+ const val = vals[0];
10520
+ json.type = val === null ? "null" : typeof val;
10521
+ if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") json.enum = [val];
10522
+ else json.const = val;
10523
+ } else {
10524
+ if (vals.every((v) => typeof v === "number")) json.type = "number";
10525
+ if (vals.every((v) => typeof v === "string")) json.type = "string";
10526
+ if (vals.every((v) => typeof v === "boolean")) json.type = "boolean";
10527
+ if (vals.every((v) => v === null)) json.type = "null";
10528
+ json.enum = vals;
10529
+ }
10530
+ };
10398
10531
  const customProcessor = (_schema, ctx, _json, _params) => {
10399
10532
  if (ctx.unrepresentable === "throw") throw new Error("Custom types cannot be represented in JSON Schema");
10400
10533
  };
@@ -11040,7 +11173,7 @@ const ZodNumber = /*@__PURE__*/ $constructor("ZodNumber", (inst, def) => {
11040
11173
  inst.isFinite = true;
11041
11174
  inst.format = bag.format ?? null;
11042
11175
  });
11043
- function number(params) {
11176
+ function number$1(params) {
11044
11177
  return _number(ZodNumber, params);
11045
11178
  }
11046
11179
  const ZodNumberFormat = /*@__PURE__*/ $constructor("ZodNumberFormat", (inst, def) => {
@@ -11050,6 +11183,14 @@ const ZodNumberFormat = /*@__PURE__*/ $constructor("ZodNumberFormat", (inst, def
11050
11183
  function int(params) {
11051
11184
  return _int(ZodNumberFormat, params);
11052
11185
  }
11186
+ const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => {
11187
+ $ZodBoolean.init(inst, def);
11188
+ ZodType.init(inst, def);
11189
+ inst._zod.processJSONSchema = (ctx, json, params) => booleanProcessor(inst, ctx, json, params);
11190
+ });
11191
+ function boolean(params) {
11192
+ return _boolean(ZodBoolean, params);
11193
+ }
11053
11194
  const ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => {
11054
11195
  $ZodUnknown.init(inst, def);
11055
11196
  ZodType.init(inst, def);
@@ -11171,6 +11312,14 @@ function strictObject(shape, params) {
11171
11312
  ...normalizeParams(params)
11172
11313
  });
11173
11314
  }
11315
+ function looseObject(shape, params) {
11316
+ return new ZodObject({
11317
+ type: "object",
11318
+ shape,
11319
+ catchall: unknown(),
11320
+ ...normalizeParams(params)
11321
+ });
11322
+ }
11174
11323
  const ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
11175
11324
  $ZodUnion.init(inst, def);
11176
11325
  ZodType.init(inst, def);
@@ -11184,6 +11333,18 @@ function union(options, params) {
11184
11333
  ...normalizeParams(params)
11185
11334
  });
11186
11335
  }
11336
+ const ZodDiscriminatedUnion = /*@__PURE__*/ $constructor("ZodDiscriminatedUnion", (inst, def) => {
11337
+ ZodUnion.init(inst, def);
11338
+ $ZodDiscriminatedUnion.init(inst, def);
11339
+ });
11340
+ function discriminatedUnion(discriminator, options, params) {
11341
+ return new ZodDiscriminatedUnion({
11342
+ type: "union",
11343
+ options,
11344
+ discriminator,
11345
+ ...normalizeParams(params)
11346
+ });
11347
+ }
11187
11348
  const ZodIntersection = /*@__PURE__*/ $constructor("ZodIntersection", (inst, def) => {
11188
11349
  $ZodIntersection.init(inst, def);
11189
11350
  ZodType.init(inst, def);
@@ -11254,6 +11415,23 @@ function _enum(values, params) {
11254
11415
  ...normalizeParams(params)
11255
11416
  });
11256
11417
  }
11418
+ const ZodLiteral = /*@__PURE__*/ $constructor("ZodLiteral", (inst, def) => {
11419
+ $ZodLiteral.init(inst, def);
11420
+ ZodType.init(inst, def);
11421
+ inst._zod.processJSONSchema = (ctx, json, params) => literalProcessor(inst, ctx, json, params);
11422
+ inst.values = new Set(def.values);
11423
+ Object.defineProperty(inst, "value", { get() {
11424
+ if (def.values.length > 1) throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
11425
+ return def.values[0];
11426
+ } });
11427
+ });
11428
+ function literal(value, params) {
11429
+ return new ZodLiteral({
11430
+ type: "literal",
11431
+ values: Array.isArray(value) ? value : [value],
11432
+ ...normalizeParams(params)
11433
+ });
11434
+ }
11257
11435
  const ZodTransform = /*@__PURE__*/ $constructor("ZodTransform", (inst, def) => {
11258
11436
  $ZodTransform.init(inst, def);
11259
11437
  ZodType.init(inst, def);
@@ -11420,6 +11598,12 @@ function superRefine(fn, params) {
11420
11598
  return _superRefine(fn, params);
11421
11599
  }
11422
11600
 
11601
+ //#endregion
11602
+ //#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/coerce.js
11603
+ function number(params) {
11604
+ return _coercedNumber(ZodNumber, params);
11605
+ }
11606
+
11423
11607
  //#endregion
11424
11608
  //#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
11425
11609
  function isPlainObject(value) {
@@ -17742,6 +17926,74 @@ const execaNode = createExeca(mapNode);
17742
17926
  const $ = createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
17743
17927
  const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
17744
17928
 
17929
+ //#endregion
17930
+ //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/rng.js
17931
+ const rnds8 = new Uint8Array(16);
17932
+ function rng() {
17933
+ return crypto.getRandomValues(rnds8);
17934
+ }
17935
+
17936
+ //#endregion
17937
+ //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/stringify.js
17938
+ const byteToHex = [];
17939
+ for (let i = 0; i < 256; ++i) byteToHex.push((i + 256).toString(16).slice(1));
17940
+ function unsafeStringify(arr, offset = 0) {
17941
+ return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
17942
+ }
17943
+
17944
+ //#endregion
17945
+ //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/v7.js
17946
+ const _state = {};
17947
+ function v7(options, buf, offset) {
17948
+ let bytes;
17949
+ if (options) bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
17950
+ else {
17951
+ const now = Date.now();
17952
+ const rnds = rng();
17953
+ updateV7State(_state, now, rnds);
17954
+ bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
17955
+ }
17956
+ return buf ?? unsafeStringify(bytes);
17957
+ }
17958
+ function updateV7State(state, now, rnds) {
17959
+ state.msecs ??= -Infinity;
17960
+ state.seq ??= 0;
17961
+ if (now > state.msecs) {
17962
+ state.seq = rnds[6] << 23 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
17963
+ state.msecs = now;
17964
+ } else {
17965
+ state.seq = state.seq + 1 | 0;
17966
+ if (state.seq === 0) state.msecs++;
17967
+ }
17968
+ return state;
17969
+ }
17970
+ function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
17971
+ if (rnds.length < 16) throw new Error("Random bytes length must be >= 16");
17972
+ if (!buf) {
17973
+ buf = new Uint8Array(16);
17974
+ offset = 0;
17975
+ } else if (offset < 0 || offset + 16 > buf.length) throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
17976
+ msecs ??= Date.now();
17977
+ seq ??= rnds[6] * 127 << 24 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
17978
+ buf[offset++] = msecs / 1099511627776 & 255;
17979
+ buf[offset++] = msecs / 4294967296 & 255;
17980
+ buf[offset++] = msecs / 16777216 & 255;
17981
+ buf[offset++] = msecs / 65536 & 255;
17982
+ buf[offset++] = msecs / 256 & 255;
17983
+ buf[offset++] = msecs & 255;
17984
+ buf[offset++] = 112 | seq >>> 28 & 15;
17985
+ buf[offset++] = seq >>> 20 & 255;
17986
+ buf[offset++] = 128 | seq >>> 14 & 63;
17987
+ buf[offset++] = seq >>> 6 & 255;
17988
+ buf[offset++] = seq << 2 & 255 | rnds[10] & 3;
17989
+ buf[offset++] = rnds[11];
17990
+ buf[offset++] = rnds[12];
17991
+ buf[offset++] = rnds[13];
17992
+ buf[offset++] = rnds[14];
17993
+ buf[offset++] = rnds[15];
17994
+ return buf;
17995
+ }
17996
+
17745
17997
  //#endregion
17746
17998
  //#region ../../node_modules/.pnpm/kind-of@6.0.3/node_modules/kind-of/index.js
17747
17999
  var require_kind_of = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -25968,74 +26220,6 @@ var require_gray_matter = /* @__PURE__ */ __commonJSMin(((exports, module) => {
25968
26220
  module.exports = matter;
25969
26221
  }));
25970
26222
 
25971
- //#endregion
25972
- //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/rng.js
25973
- const rnds8 = new Uint8Array(16);
25974
- function rng() {
25975
- return crypto.getRandomValues(rnds8);
25976
- }
25977
-
25978
- //#endregion
25979
- //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/stringify.js
25980
- const byteToHex = [];
25981
- for (let i = 0; i < 256; ++i) byteToHex.push((i + 256).toString(16).slice(1));
25982
- function unsafeStringify(arr, offset = 0) {
25983
- return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
25984
- }
25985
-
25986
- //#endregion
25987
- //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/v7.js
25988
- const _state = {};
25989
- function v7(options, buf, offset) {
25990
- let bytes;
25991
- if (options) bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
25992
- else {
25993
- const now = Date.now();
25994
- const rnds = rng();
25995
- updateV7State(_state, now, rnds);
25996
- bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
25997
- }
25998
- return buf ?? unsafeStringify(bytes);
25999
- }
26000
- function updateV7State(state, now, rnds) {
26001
- state.msecs ??= -Infinity;
26002
- state.seq ??= 0;
26003
- if (now > state.msecs) {
26004
- state.seq = rnds[6] << 23 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
26005
- state.msecs = now;
26006
- } else {
26007
- state.seq = state.seq + 1 | 0;
26008
- if (state.seq === 0) state.msecs++;
26009
- }
26010
- return state;
26011
- }
26012
- function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
26013
- if (rnds.length < 16) throw new Error("Random bytes length must be >= 16");
26014
- if (!buf) {
26015
- buf = new Uint8Array(16);
26016
- offset = 0;
26017
- } else if (offset < 0 || offset + 16 > buf.length) throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
26018
- msecs ??= Date.now();
26019
- seq ??= rnds[6] * 127 << 24 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
26020
- buf[offset++] = msecs / 1099511627776 & 255;
26021
- buf[offset++] = msecs / 4294967296 & 255;
26022
- buf[offset++] = msecs / 16777216 & 255;
26023
- buf[offset++] = msecs / 65536 & 255;
26024
- buf[offset++] = msecs / 256 & 255;
26025
- buf[offset++] = msecs & 255;
26026
- buf[offset++] = 112 | seq >>> 28 & 15;
26027
- buf[offset++] = seq >>> 20 & 255;
26028
- buf[offset++] = 128 | seq >>> 14 & 63;
26029
- buf[offset++] = seq >>> 6 & 255;
26030
- buf[offset++] = seq << 2 & 255 | rnds[10] & 3;
26031
- buf[offset++] = rnds[11];
26032
- buf[offset++] = rnds[12];
26033
- buf[offset++] = rnds[13];
26034
- buf[offset++] = rnds[14];
26035
- buf[offset++] = rnds[15];
26036
- return buf;
26037
- }
26038
-
26039
26223
  //#endregion
26040
26224
  //#region ../../node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/polyfills.js
26041
26225
  var require_polyfills = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -27397,9 +27581,113 @@ var require_proper_lockfile = /* @__PURE__ */ __commonJSMin(((exports, module) =
27397
27581
  }));
27398
27582
 
27399
27583
  //#endregion
27400
- //#region ../../node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/query.js
27584
+ //#region ../base-schemas/dist/index.mjs
27401
27585
  var import_gray_matter = /* @__PURE__ */ __toESM(require_gray_matter(), 1);
27402
27586
  var import_proper_lockfile = /* @__PURE__ */ __toESM(require_proper_lockfile(), 1);
27587
+ const apiKeyAuthSchema = strictObject({
27588
+ api_key: string().trim().min(1),
27589
+ type: literal("api_key")
27590
+ }).meta({ title: "ApiKeyAuth" });
27591
+ const authenticatedAccountRefSchema = strictObject({
27592
+ account_id: string().trim().min(1),
27593
+ provider_id: string().trim().min(1),
27594
+ type: literal("authenticated_account")
27595
+ }).meta({ title: "AuthenticatedAccountRef" });
27596
+ const authenticatedAccountIdentitySchema = authenticatedAccountRefSchema.omit({ type: true }).meta({ title: "AuthenticatedAccountIdentity" });
27597
+ const bindingAuthNoneSchema = strictObject({ type: literal("none") }).meta({ title: "BindingAuthNone" });
27598
+ const clientCredentialsAuthSchema = strictObject({
27599
+ client_id: string().trim().min(1),
27600
+ client_secret: string().trim().min(1),
27601
+ type: literal("client_credentials")
27602
+ }).meta({ title: "ClientCredentialsAuth" });
27603
+ const bindingAuthSchema = discriminatedUnion("type", [
27604
+ bindingAuthNoneSchema,
27605
+ authenticatedAccountRefSchema,
27606
+ apiKeyAuthSchema,
27607
+ clientCredentialsAuthSchema
27608
+ ]).meta({ title: "BindingAuth" });
27609
+ const MODES = ["dagster"];
27610
+ const modeSchema = _enum(MODES);
27611
+ const generatedAtSchema = string().datetime({ offset: true });
27612
+ const bindingModelNameSchema = string().trim().min(1).meta({ title: "BindingModelName" });
27613
+ const bindingModelsSchema = strictObject({
27614
+ active: array(bindingModelNameSchema).optional(),
27615
+ filter: string().trim().min(1).optional()
27616
+ }).meta({ title: "BindingModels" });
27617
+ const dagsterAllPlanBindingSchema = strictObject({
27618
+ auth: bindingAuthSchema,
27619
+ binding_id: string().uuid(),
27620
+ config: record(string(), unknown()),
27621
+ mode: modeSchema,
27622
+ models: bindingModelsSchema.optional(),
27623
+ plugin_id: string().trim().min(1)
27624
+ }).meta({ title: "PlanBinding" }).extend({ mode: literal("dagster") }).meta({ title: "DagsterAllPlanBinding" });
27625
+ const dagsterBindingPlanAllSchema = strictObject({
27626
+ bindings: array(dagsterAllPlanBindingSchema),
27627
+ generated_at: generatedAtSchema,
27628
+ version: literal(1)
27629
+ }).meta({ title: "DagsterBindingPlanAll" });
27630
+ const dagsterPluginPlanBindingSchema = dagsterAllPlanBindingSchema.omit({
27631
+ mode: true,
27632
+ plugin_id: true
27633
+ }).meta({ title: "DagsterPluginPlanBinding" });
27634
+ const dagsterBindingPlanPluginSchema = strictObject({
27635
+ bindings: array(dagsterPluginPlanBindingSchema),
27636
+ generated_at: generatedAtSchema,
27637
+ mode: literal("dagster"),
27638
+ plugin_id: string().trim().min(1),
27639
+ version: literal(1)
27640
+ }).meta({ title: "DagsterBindingPlanPlugin" });
27641
+ const RUN_STATUSES = [
27642
+ "QUEUED",
27643
+ "NOT_STARTED",
27644
+ "MANAGED",
27645
+ "STARTING",
27646
+ "STARTED",
27647
+ "SUCCESS",
27648
+ "FAILURE",
27649
+ "CANCELING",
27650
+ "CANCELED"
27651
+ ];
27652
+ const runStatusSchema$1 = _enum(RUN_STATUSES).meta({ title: "RunStatus" });
27653
+ const stateTimestampSchema = string().datetime({ offset: true });
27654
+ const bindingStateSchema = strictObject({
27655
+ frontier: stateTimestampSchema.nullable(),
27656
+ last_run: stateTimestampSchema.nullable(),
27657
+ last_run_status: runStatusSchema$1.nullable(),
27658
+ plugin_id: string().trim().min(1)
27659
+ }).meta({ title: "BindingState" });
27660
+ const bindingStateResponseSchema = strictObject({
27661
+ bindings: record(string().uuid(), bindingStateSchema),
27662
+ version: literal(1)
27663
+ }).meta({ title: "BindingStateResponse" });
27664
+ const oauthAccessTokenSuccessSchema = looseObject({
27665
+ accessToken: string().trim().min(1),
27666
+ accessTokenExpiresAt: string().datetime({ offset: true })
27667
+ }).meta({ title: "OAuthAccessTokenSuccess" });
27668
+ const pluginAuthNoneSchema = strictObject({ type: literal("none") }).meta({ title: "PluginAuthNone" });
27669
+ const pluginAuthApiKeySchema = strictObject({ type: literal("api_key") }).meta({ title: "PluginAuthApiKey" });
27670
+ const pluginAuthClientCredentialsSchema = strictObject({ type: literal("client_credentials") }).meta({ title: "PluginAuthClientCredentials" });
27671
+ const pluginOAuthSchema = strictObject({
27672
+ provider_id: string().trim().min(1),
27673
+ scopes: array(string().trim().min(1)).min(1),
27674
+ type: literal("oauth")
27675
+ }).meta({ title: "PluginOAuth" });
27676
+ const pluginAuthSchema = discriminatedUnion("type", [
27677
+ pluginAuthNoneSchema,
27678
+ pluginAuthApiKeySchema,
27679
+ pluginAuthClientCredentialsSchema,
27680
+ pluginOAuthSchema
27681
+ ]).meta({ title: "PluginAuth" });
27682
+ const pluginManifestSchema = strictObject({
27683
+ auth: pluginAuthSchema,
27684
+ mode: modeSchema,
27685
+ plugin_id: string().trim().min(1),
27686
+ skill: string().optional()
27687
+ }).meta({ title: "PluginManifest" });
27688
+
27689
+ //#endregion
27690
+ //#region ../../node_modules/.pnpm/postgres@3.4.9/node_modules/postgres/src/query.js
27403
27691
  const originCache = /* @__PURE__ */ new Map(), originStackCache = /* @__PURE__ */ new Map(), originError = Symbol("OriginError");
27404
27692
  const CLOSE = {};
27405
27693
  var Query = class extends Promise {
@@ -29490,7 +29778,7 @@ function validate(raw, sourcePath) {
29490
29778
  }
29491
29779
  function applyDefaults(input) {
29492
29780
  return {
29493
- synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.3",
29781
+ synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.6",
29494
29782
  ...input.microsandbox !== void 0 ? { microsandbox: input.microsandbox } : {},
29495
29783
  ...input.oldestConsideredPoint !== void 0 ? { oldestConsideredPoint: input.oldestConsideredPoint } : {},
29496
29784
  maxSliceSize: input.maxSliceSize ?? 864e5,
@@ -29506,38 +29794,65 @@ function formatZodError$1(error) {
29506
29794
  return `${issue.path.length > 0 ? issue.path.join(".") : "root"}: ${issue.message}`;
29507
29795
  }).join("; ");
29508
29796
  }
29797
+ function gatePasses(input) {
29798
+ if (input.syncHorizon === null) return false;
29799
+ if (input.mode === "manual") return input.frontier < input.syncHorizon;
29800
+ return input.syncHorizon - input.frontier >= input.tick;
29801
+ }
29509
29802
  function planLoopPass(input) {
29510
- const due = input.synthesizers.filter((synthesizer) => input.now - synthesizer.frontier >= input.tick);
29511
- if (due.length > 0) return {
29803
+ const horizon = input.syncHorizon;
29804
+ const horizonDue = horizon === null ? [] : input.synthesizers.filter((synthesizer) => gatePasses({
29805
+ frontier: synthesizer.frontier,
29806
+ mode: "loop",
29807
+ syncHorizon: horizon,
29808
+ tick: input.tick
29809
+ }));
29810
+ const due = horizonDue.filter((synthesizer) => (synthesizer.notBefore ?? 0) <= input.now);
29811
+ if (horizon !== null && due.length > 0) return {
29512
29812
  kind: "run",
29513
- order: due.map((synthesizer) => synthesizer.name).sort()
29813
+ order: due.map((synthesizer) => synthesizer.name).sort(),
29814
+ syncHorizon: horizon
29514
29815
  };
29816
+ const held = new Set(horizonDue);
29515
29817
  return {
29516
29818
  kind: "idle",
29517
- wakeAt: input.synthesizers.length === 0 ? input.now + input.tick : Math.min(...input.synthesizers.map((synthesizer) => synthesizer.frontier + input.tick))
29819
+ reason: held.size > 0 ? "rate_limited" : "caught_up",
29820
+ wakeAt: input.synthesizers.length === 0 ? input.now + input.tick : Math.min(...input.synthesizers.map((synthesizer) => {
29821
+ if (held.has(synthesizer)) return synthesizer.notBefore ?? 0;
29822
+ return Math.max(synthesizer.notBefore ?? 0, input.now + input.tick);
29823
+ }))
29824
+ };
29825
+ }
29826
+ function concludeLoopPass(results) {
29827
+ if (results.includes("advanced")) return { kind: "progressed" };
29828
+ return {
29829
+ kind: "blocked",
29830
+ reason: results.includes("rate_limited") ? "rate_limited" : "stalled"
29518
29831
  };
29519
29832
  }
29520
29833
  async function runEngineLoop(deps, options) {
29521
29834
  for (;;) {
29522
29835
  if (options.signal?.aborted) return { stopReason: "aborted" };
29523
- const synthesizers = await deps.listSynthesizers();
29836
+ const [syncHorizon, synthesizers] = await Promise.all([deps.captureSyncHorizon(), deps.listSynthesizers()]);
29524
29837
  const pass = planLoopPass({
29525
29838
  now: deps.now(),
29839
+ syncHorizon,
29526
29840
  synthesizers,
29527
29841
  tick: options.tick
29528
29842
  });
29529
29843
  if (pass.kind === "idle") {
29530
- if (options.mode === "backfill") return { stopReason: "caught_up" };
29844
+ if (options.mode === "backfill") return { stopReason: pass.reason };
29531
29845
  await deps.sleep(Math.max(0, pass.wakeAt - deps.now()));
29532
29846
  continue;
29533
29847
  }
29534
- let advancedAny = false;
29848
+ const results = [];
29535
29849
  for (const name of pass.order) {
29536
29850
  if (options.signal?.aborted) return { stopReason: "aborted" };
29537
- if ((await deps.runSlice(name)).advanced) advancedAny = true;
29851
+ results.push(await deps.runSlice(name, pass.syncHorizon));
29538
29852
  }
29539
- if (!advancedAny) {
29540
- if (options.mode === "backfill") return { stopReason: "stalled" };
29853
+ const conclusion = concludeLoopPass(results);
29854
+ if (conclusion.kind === "blocked") {
29855
+ if (options.mode === "backfill") return { stopReason: conclusion.reason };
29541
29856
  await deps.sleep(options.tick);
29542
29857
  }
29543
29858
  }
@@ -29861,6 +30176,18 @@ function formatRelativeAge(ms) {
29861
30176
  }
29862
30177
  return rtf.format(-Math.round(value), "year");
29863
30178
  }
30179
+ function formatDuration(ms) {
30180
+ let value = Math.trunc(ms) / 1e3;
30181
+ if (value < 1) return "under a second";
30182
+ for (const [unit, perNext] of DIVISIONS) {
30183
+ if (value < perNext) return pluralizeUnit(Math.round(value), unit);
30184
+ value /= perNext;
30185
+ }
30186
+ return pluralizeUnit(Math.round(value), "year");
30187
+ }
30188
+ function pluralizeUnit(value, unit) {
30189
+ return `${value} ${unit}${value === 1 ? "" : "s"}`;
30190
+ }
29864
30191
  async function runRootPreflight(repo) {
29865
30192
  await assertGitCheckout(repo);
29866
30193
  const branch = await inspectBranchReadiness(repo);
@@ -29904,13 +30231,266 @@ function dirtyWorkingTreeIssue(paths) {
29904
30231
  message: `Working tree is dirty:\n${formatWorkingTreePaths(paths)}\nRun 'ctxe repair' to roll back to HEAD (preserves .engine/runs/**).`
29905
30232
  };
29906
30233
  }
29907
- const synthesizerSpecFrontmatterSchema = strictObject({
29908
- description: string().optional(),
29909
- name: string().trim().min(1)
29910
- });
29911
- function parseSynthesizerSpecMarkdown(content, sourcePath) {
29912
- if (!hasYamlFrontmatter(content)) return {
29913
- kind: "not_synthesizer",
30234
+ const runTriggerSchema = _enum([
30235
+ "backfill",
30236
+ "daemon",
30237
+ "manual"
30238
+ ]);
30239
+ _enum([
30240
+ "success",
30241
+ "failed",
30242
+ "timed_out",
30243
+ "rate_limited"
30244
+ ]);
30245
+ const runStatusSchema = _enum([
30246
+ "starting",
30247
+ "running",
30248
+ "success",
30249
+ "failed",
30250
+ "timed_out",
30251
+ "rate_limited",
30252
+ "interrupted"
30253
+ ]);
30254
+ const claudeResultSchema = strictObject({
30255
+ api_error_status: number$1().int().optional(),
30256
+ is_error: boolean(),
30257
+ num_turns: number$1().int(),
30258
+ subtype: string().min(1),
30259
+ terminal_reason: string().min(1).optional(),
30260
+ total_cost_usd: number$1()
30261
+ });
30262
+ const runRecordSchema = strictObject({
30263
+ claude_result: claudeResultSchema.optional(),
30264
+ completed_at: string().min(1).optional(),
30265
+ duration_ms: number$1().int().nonnegative().optional(),
30266
+ exit_code: number$1().int().optional(),
30267
+ rate_limit_resets_at: string().min(1).optional(),
30268
+ run_id: string().min(1),
30269
+ started_at: string().min(1),
30270
+ status: runStatusSchema,
30271
+ synthesizer: string().min(1),
30272
+ triggered_by: runTriggerSchema
30273
+ });
30274
+ const RECORD_FILE_NAME = "record.json";
30275
+ const STDERR_FILE_NAME = "stderr";
30276
+ function buildStartingRunRecord(input) {
30277
+ return {
30278
+ run_id: input.runId,
30279
+ started_at: input.startedAt,
30280
+ status: "starting",
30281
+ synthesizer: input.synthesizerName,
30282
+ triggered_by: input.trigger
30283
+ };
30284
+ }
30285
+ function buildRunningRunRecord(record) {
30286
+ return {
30287
+ ...record,
30288
+ status: "running"
30289
+ };
30290
+ }
30291
+ function buildTerminalRunRecord(input) {
30292
+ return {
30293
+ ...input.claudeResult !== void 0 && { claude_result: input.claudeResult },
30294
+ completed_at: input.completedAt,
30295
+ duration_ms: input.durationMs,
30296
+ exit_code: input.exitCode,
30297
+ ...input.rateLimitResetsAt !== void 0 && { rate_limit_resets_at: input.rateLimitResetsAt },
30298
+ run_id: input.runId,
30299
+ started_at: input.startedAt,
30300
+ status: input.status,
30301
+ synthesizer: input.synthesizerName,
30302
+ triggered_by: input.trigger
30303
+ };
30304
+ }
30305
+ function buildInterruptedRunRecord(record, completedAt) {
30306
+ return {
30307
+ ...record,
30308
+ completed_at: completedAt,
30309
+ status: "interrupted"
30310
+ };
30311
+ }
30312
+ function getRunDir(input) {
30313
+ return runDirPath(input.rootDir, input.synthesizerName, input.runId);
30314
+ }
30315
+ async function ensureRunDir(input) {
30316
+ const runDir = getRunDir(input);
30317
+ await mkdir(runDir, { recursive: true });
30318
+ return runDir;
30319
+ }
30320
+ async function writeRunRecord(runDir, record) {
30321
+ await mkdir(runDir, { recursive: true });
30322
+ await writeJsonFileAtomic(path.join(runDir, RECORD_FILE_NAME), runRecordSchema.parse(record));
30323
+ }
30324
+ async function readRunRecord(runDir) {
30325
+ const raw = await readFile(path.join(runDir, RECORD_FILE_NAME), "utf8");
30326
+ return runRecordSchema.parse(JSON.parse(raw));
30327
+ }
30328
+ async function readLatestRunRecord(input) {
30329
+ const synthesizerDir = synthesizerRunsDirPath(input.rootDir, input.synthesizerName);
30330
+ let runIds;
30331
+ try {
30332
+ runIds = await listDirectoryNames(synthesizerDir);
30333
+ } catch (error) {
30334
+ if (isNodeError(error) && error.code === "ENOENT") return null;
30335
+ throw error;
30336
+ }
30337
+ const latest = runIds.reduce((max, runId) => max === null || runId > max ? runId : max, null);
30338
+ if (latest === null) return null;
30339
+ try {
30340
+ return await readRunRecord(path.join(synthesizerDir, latest));
30341
+ } catch (error) {
30342
+ if (isNodeError(error) && error.code === "ENOENT") return null;
30343
+ throw error;
30344
+ }
30345
+ }
30346
+ async function listRunRecords(input) {
30347
+ const synthesizerNames = input.synthesizerName !== void 0 ? [input.synthesizerName] : await listSynthesizerRunDirNames(input.rootDir);
30348
+ const entries = [];
30349
+ for (const synthesizerName of synthesizerNames) {
30350
+ const synthesizerDir = synthesizerRunsDirPath(input.rootDir, synthesizerName);
30351
+ let runIds;
30352
+ try {
30353
+ runIds = await listDirectoryNames(synthesizerDir);
30354
+ } catch (error) {
30355
+ if (isNodeError(error) && error.code === "ENOENT") continue;
30356
+ throw error;
30357
+ }
30358
+ for (const runId of runIds) {
30359
+ const runDir = path.join(synthesizerDir, runId);
30360
+ try {
30361
+ entries.push({
30362
+ record: await readRunRecord(runDir),
30363
+ runDir,
30364
+ runId
30365
+ });
30366
+ } catch (error) {
30367
+ if (isNodeError(error) && error.code === "ENOENT") continue;
30368
+ throw error;
30369
+ }
30370
+ }
30371
+ }
30372
+ return entries.sort(compareRunRecordEntries);
30373
+ }
30374
+ async function readRunStderr(runDir) {
30375
+ return await readOptionalTextFile(path.join(runDir, STDERR_FILE_NAME));
30376
+ }
30377
+ async function writeRunStderr(input) {
30378
+ await mkdir(input.runDir, { recursive: true });
30379
+ await writeFileAtomic(path.join(input.runDir, STDERR_FILE_NAME), input.stderr);
30380
+ }
30381
+ async function markInterruptedRuns(rootDir) {
30382
+ const interrupted = [];
30383
+ const engineRunsDir = runsDirPath(rootDir);
30384
+ let synthesizerDirs;
30385
+ try {
30386
+ synthesizerDirs = await listDirectoryNames(engineRunsDir);
30387
+ } catch (error) {
30388
+ if (isNodeError(error) && error.code === "ENOENT") return [];
30389
+ throw error;
30390
+ }
30391
+ for (const synthesizerName of synthesizerDirs) {
30392
+ const synthesizerDir = path.join(engineRunsDir, synthesizerName);
30393
+ for (const runId of await listDirectoryNames(synthesizerDir)) {
30394
+ const runDir = path.join(synthesizerDir, runId);
30395
+ let record;
30396
+ try {
30397
+ record = await readRunRecord(runDir);
30398
+ } catch (error) {
30399
+ if (isNodeError(error) && error.code === "ENOENT") continue;
30400
+ throw error;
30401
+ }
30402
+ if (record.status !== "starting" && record.status !== "running") continue;
30403
+ const nextRecord = buildInterruptedRunRecord(record, (/* @__PURE__ */ new Date()).toISOString());
30404
+ await writeRunRecord(runDir, nextRecord);
30405
+ interrupted.push(nextRecord);
30406
+ }
30407
+ }
30408
+ return interrupted;
30409
+ }
30410
+ async function listDirectoryNames(dirPath) {
30411
+ return (await readdir$1(dirPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
30412
+ }
30413
+ async function listSynthesizerRunDirNames(rootDir) {
30414
+ const engineRunsDir = runsDirPath(rootDir);
30415
+ try {
30416
+ return await listDirectoryNames(engineRunsDir);
30417
+ } catch (error) {
30418
+ if (isNodeError(error) && error.code === "ENOENT") return [];
30419
+ throw error;
30420
+ }
30421
+ }
30422
+ async function readOptionalTextFile(filePath) {
30423
+ try {
30424
+ return await readFile(filePath, "utf8");
30425
+ } catch (error) {
30426
+ if (isNodeError(error) && error.code === "ENOENT") return null;
30427
+ throw error;
30428
+ }
30429
+ }
30430
+ function compareRunRecordEntries(left, right) {
30431
+ const timeCompare = Date.parse(right.record.started_at) - Date.parse(left.record.started_at);
30432
+ if (timeCompare !== 0) return timeCompare;
30433
+ return right.runId.localeCompare(left.runId);
30434
+ }
30435
+ async function runRootFastForward(repo) {
30436
+ await assertGitCheckout(repo);
30437
+ const branch = await inspectBranchReadiness(repo);
30438
+ const tree = await inspectWorkingTree(repo);
30439
+ if (branch.kind === "no_origin") throw new Error("RootRepair requires 'origin' to be configured in remote mode. Provisioning must add origin before repair.");
30440
+ if (branch.kind === "local_head_without_remote_main") throw new Error(`RootRepair refuses to act: local HEAD is at ${branch.localSha} but origin/${repo.branch} does not exist. Operator must reconcile manually.`);
30441
+ const needsFastForward = branch.kind === "local_unborn_remote_has_main" || branch.kind === "remote_mismatch";
30442
+ if (needsFastForward && !tree.clean) throw new Error(formatCombinedRepairError(repo, branch, tree));
30443
+ if (needsFastForward) return {
30444
+ kind: "fast_forward",
30445
+ ...await fastForwardRoot(repo)
30446
+ };
30447
+ return { kind: "no_op" };
30448
+ }
30449
+ async function runRootRepair(repo) {
30450
+ const fastForward = await runRootFastForward(repo);
30451
+ if (fastForward.kind === "fast_forward") return fastForward;
30452
+ if (!(await inspectWorkingTree(repo)).clean) return await runDirtyRootRepair(repo);
30453
+ return { kind: "no_op" };
30454
+ }
30455
+ async function runDirtyRootRepair(repo) {
30456
+ await repairDirtyRoot(repo);
30457
+ const interruptedRunIds = (await markInterruptedRuns(repo.rootDir)).map((record) => record.run_id);
30458
+ if ((await inspectWorkingTree(repo)).clean) return {
30459
+ commit: null,
30460
+ interruptedRunIds,
30461
+ kind: "dirty_root_repair"
30462
+ };
30463
+ const { sha } = await commitRootRepair(repo, {
30464
+ interruptedRunIds,
30465
+ repairId: v7()
30466
+ });
30467
+ await pushToOrigin(repo);
30468
+ return {
30469
+ commit: {
30470
+ pushed: repo.mode === "remote",
30471
+ sha
30472
+ },
30473
+ interruptedRunIds,
30474
+ kind: "dirty_root_repair"
30475
+ };
30476
+ }
30477
+ function formatCombinedRepairError(repo, branch, tree) {
30478
+ const dirtyPaths = tree.clean ? "(clean)" : formatWorkingTreePaths(tree.paths);
30479
+ return [
30480
+ "RootRepair refuses to combine branch fast-forward with dirty-root repair in v1.",
30481
+ `Branch: ${branch.kind === "local_unborn_remote_has_main" ? `local checkout is unborn but origin/${repo.branch} exists` : branch.kind === "remote_mismatch" ? `local HEAD does not match origin/${repo.branch}` : `branch state is '${branch.kind}'`}.`,
30482
+ "Dirty paths:",
30483
+ dirtyPaths,
30484
+ "Operator must inspect and choose the order manually."
30485
+ ].join("\n");
30486
+ }
30487
+ const synthesizerSpecFrontmatterSchema = strictObject({
30488
+ description: string().optional(),
30489
+ name: string().trim().min(1)
30490
+ });
30491
+ function parseSynthesizerSpecMarkdown(content, sourcePath) {
30492
+ if (!hasYamlFrontmatter(content)) return {
30493
+ kind: "not_synthesizer",
29914
30494
  reason: "missing_frontmatter",
29915
30495
  sourcePath
29916
30496
  };
@@ -30461,12 +31041,15 @@ async function executeSynthesizerRun(input) {
30461
31041
  const result = await input.executor.run(executorInput);
30462
31042
  const completedAtMs = now().getTime();
30463
31043
  const durationMs = Math.max(0, completedAtMs - startedAtMs);
30464
- const status = exitCodeToStatus(result.exitCode);
31044
+ const status = classifyOutcome(result.exitCode, result.observation);
31045
+ const rateLimitResetsAt = observedRateLimitResetsAt(result.observation);
30465
31046
  return {
30466
31047
  outcome: {
30467
31048
  durationMs,
30468
31049
  exitCode: result.exitCode,
30469
- status
31050
+ status,
31051
+ ...result.observation.result !== void 0 && { claudeResult: result.observation.result },
31052
+ ...rateLimitResetsAt !== void 0 && { rateLimitResetsAt }
30470
31053
  },
30471
31054
  stderr: result.stderr
30472
31055
  };
@@ -30482,11 +31065,19 @@ async function executeSynthesizerRun(input) {
30482
31065
  };
30483
31066
  }
30484
31067
  }
30485
- function exitCodeToStatus(exitCode) {
31068
+ function classifyOutcome(exitCode, observation) {
30486
31069
  if (exitCode === 0) return "success";
30487
31070
  if (exitCode === 124) return "timed_out";
31071
+ if (observation.result?.api_error_status === 429) return "rate_limited";
31072
+ if (observation.lastRateLimit?.status === "rejected") return "rate_limited";
30488
31073
  return "failed";
30489
31074
  }
31075
+ function observedRateLimitResetsAt(observation) {
31076
+ const rateLimit = observation.lastRateLimit;
31077
+ if (rateLimit === void 0) return void 0;
31078
+ if (rateLimit.status !== "rejected" || rateLimit.resetsAt === void 0) return;
31079
+ return msToIso(rateLimit.resetsAt * 1e3);
31080
+ }
30490
31081
  function buildExecutorInput(input) {
30491
31082
  return {
30492
31083
  image: input.synthesizerImage,
@@ -30512,7 +31103,7 @@ function buildSandboxName(synthesizerName, runId) {
30512
31103
  return `ctxe-${synthesizerName.slice(0, 24).toLowerCase()}-${runId.slice(-8).toLowerCase()}`;
30513
31104
  }
30514
31105
  function planCeiling(input) {
30515
- return Math.min(input.prevCursor.frontier + input.config.maxSliceSize, input.wallclock);
31106
+ return Math.min(input.prevCursor.frontier + input.config.maxSliceSize, input.syncHorizon);
30516
31107
  }
30517
31108
  function buildSlice(input) {
30518
31109
  const slice = {};
@@ -30541,11 +31132,11 @@ function nextCursor(input) {
30541
31132
  };
30542
31133
  }
30543
31134
  function plan(input) {
30544
- const ceiling = planCeiling({
31135
+ const ceiling = Math.max(planCeiling({
30545
31136
  config: input.config,
30546
31137
  prevCursor: input.prevCursor,
30547
- wallclock: input.wallclock
30548
- });
31138
+ syncHorizon: input.syncHorizon
31139
+ }), input.prevCursor.frontier);
30549
31140
  const slice = buildSlice({
30550
31141
  ceiling,
30551
31142
  floors: input.prevCursor.floors
@@ -30576,175 +31167,6 @@ function reconcileCursor(input) {
30576
31167
  frontier: input.prevCursor.frontier
30577
31168
  };
30578
31169
  }
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
31170
  function renderSliceJson(slice) {
30749
31171
  const windows = {};
30750
31172
  for (const [table, window] of Object.entries(slice)) windows[table] = {
@@ -30754,10 +31176,13 @@ function renderSliceJson(slice) {
30754
31176
  return `${JSON.stringify(windows, null, " ")}\n`;
30755
31177
  }
30756
31178
  const DEFAULT_OLDEST_LOOKBACK_MS = 720 * 60 * 60 * 1e3;
31179
+ function resolveOldest(config, beginMs) {
31180
+ return config.oldestConsideredPoint ?? beginMs - DEFAULT_OLDEST_LOOKBACK_MS;
31181
+ }
30757
31182
  async function openSynthesizerTransaction(input) {
30758
- const now = input.now ?? (() => /* @__PURE__ */ new Date());
30759
31183
  const runId = assertSafeEngineName((input.runIdFactory ?? v7)());
30760
- const startedAt = now().toISOString();
31184
+ const begin = (input.now ?? (() => /* @__PURE__ */ new Date()))();
31185
+ const startedAt = begin.toISOString();
30761
31186
  const runDir = await ensureRunDir({
30762
31187
  rootDir: input.repo.rootDir,
30763
31188
  runId,
@@ -30778,25 +31203,24 @@ async function openSynthesizerTransaction(input) {
30778
31203
  synthesizer: input.synthesizer,
30779
31204
  trigger: input.trigger
30780
31205
  });
30781
- const wallclock = isoToMs(startedAt, "transaction startedAt");
31206
+ const persistedCursor = await readCursor(input.repo.rootDir, input.synthesizer.name);
30782
31207
  const planConfig = {
30783
31208
  maxSliceSize: input.config.maxSliceSize,
30784
- oldestConsideredPoint: input.config.oldestConsideredPoint ?? wallclock - DEFAULT_OLDEST_LOOKBACK_MS
31209
+ oldestConsideredPoint: resolveOldest(input.config, begin.getTime())
30785
31210
  };
30786
31211
  const { next, slice } = plan({
30787
31212
  config: planConfig,
30788
31213
  prevCursor: reconcileCursor({
30789
31214
  config: planConfig,
30790
- prevCursor: await readCursor(input.repo.rootDir, input.synthesizer.name),
31215
+ prevCursor: persistedCursor,
30791
31216
  tables: Object.keys(watermark)
30792
31217
  }),
30793
- wallclock,
31218
+ syncHorizon: input.syncHorizon,
30794
31219
  watermark
30795
31220
  });
30796
31221
  await writeRunRecord(runDir, buildRunningRunRecord(startingRecord));
30797
31222
  return {
30798
31223
  next,
30799
- reachedWallclock: next.frontier === wallclock,
30800
31224
  runDir,
30801
31225
  runId,
30802
31226
  sliceJson: renderSliceJson(slice),
@@ -30859,9 +31283,11 @@ async function finalizeFailedSynthesizerTransaction(input) {
30859
31283
  function buildTerminalRecord(input) {
30860
31284
  const { ctx, runResult, completedAt } = input;
30861
31285
  return buildTerminalRunRecord({
31286
+ ...runResult.outcome.claudeResult !== void 0 && { claudeResult: runResult.outcome.claudeResult },
30862
31287
  completedAt,
30863
31288
  durationMs: runResult.outcome.durationMs,
30864
31289
  exitCode: runResult.outcome.exitCode,
31290
+ ...runResult.outcome.rateLimitResetsAt !== void 0 && { rateLimitResetsAt: runResult.outcome.rateLimitResetsAt },
30865
31291
  runId: ctx.runId,
30866
31292
  startedAt: ctx.startedAt,
30867
31293
  status: runResult.outcome.status,
@@ -30901,6 +31327,7 @@ var Engine = class {
30901
31327
  now;
30902
31328
  runIdFactory;
30903
31329
  repo;
31330
+ syncHorizonCapture;
30904
31331
  watermarkCapture;
30905
31332
  synthesizers = /* @__PURE__ */ new Map();
30906
31333
  config = null;
@@ -30918,13 +31345,19 @@ var Engine = class {
30918
31345
  mode: options.git?.mode ?? DEFAULT_GIT_MODE,
30919
31346
  rootDir: path.resolve(options.rootDir)
30920
31347
  };
31348
+ this.syncHorizonCapture = options.syncHorizonCapture;
30921
31349
  this.watermarkCapture = options.watermarkCapture;
30922
31350
  }
30923
31351
  async runDaemon(options = {}) {
30924
31352
  if (this.daemonRunning) throw new Error("Engine.runDaemon is already running.");
30925
31353
  this.daemonRunning = true;
30926
31354
  try {
30927
- const report = await runRootPreflight(this.repo);
31355
+ let report = await runRootPreflight(this.repo);
31356
+ if (!report.ready) {
31357
+ const repaired = await runRootFastForward(this.repo);
31358
+ if (repaired.kind === "fast_forward") this.logger.info(`[engine] root was behind origin/${this.repo.branch} — fast-forwarded ${repaired.from ?? "(unborn)"} → ${repaired.to}`);
31359
+ report = await runRootPreflight(this.repo);
31360
+ }
30928
31361
  if (!report.ready) throw new Error(formatPreflightFailure(this.repo.rootDir, report.issues));
30929
31362
  await this.ensureLoaded();
30930
31363
  await runEngineLoop(this.buildLoopDeps({
@@ -30941,10 +31374,24 @@ var Engine = class {
30941
31374
  }
30942
31375
  async runOnce(synthesizerName) {
30943
31376
  await this.prepareSynthesizerRun(synthesizerName);
30944
- return (await this.runSynthesizerTransaction({
31377
+ const config = this.requireConfig();
31378
+ const syncHorizon = await this.syncHorizonCapture();
31379
+ const frontier = (await readCursor(this.repo.rootDir, synthesizerName))?.frontier ?? resolveOldest(config, this.nowDate().getTime());
31380
+ if (syncHorizon === null || !gatePasses({
31381
+ frontier,
31382
+ mode: "manual",
31383
+ syncHorizon,
31384
+ tick: config.tick
31385
+ })) return { kind: "caught_up" };
31386
+ const { record } = await this.runSynthesizerTransaction({
31387
+ syncHorizon,
30945
31388
  synthesizerName,
30946
31389
  trigger: "manual"
30947
- })).record;
31390
+ });
31391
+ return {
31392
+ kind: "ran",
31393
+ record
31394
+ };
30948
31395
  }
30949
31396
  async backfill(synthesizerName, options = {}) {
30950
31397
  await this.prepareSynthesizerRun(synthesizerName);
@@ -30978,32 +31425,43 @@ var Engine = class {
30978
31425
  getDiscoveryErrors() {
30979
31426
  return [...this.discoveryErrors];
30980
31427
  }
31428
+ nowDate() {
31429
+ return (this.now ?? (() => /* @__PURE__ */ new Date()))();
31430
+ }
30981
31431
  buildLoopDeps(options) {
30982
31432
  const rootDir = this.repo.rootDir;
30983
- const clock = this.now ?? (() => /* @__PURE__ */ new Date());
30984
31433
  return {
31434
+ captureSyncHorizon: this.syncHorizonCapture,
30985
31435
  listSynthesizers: async () => {
30986
31436
  await this.refreshSynthesizers();
30987
31437
  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
- })));
31438
+ const passNowMs = this.nowDate().getTime();
31439
+ const config = this.requireConfig();
31440
+ return Promise.all(specs.map(async (spec) => {
31441
+ const notBefore = await this.readRateLimitGate(spec.name, passNowMs);
31442
+ return {
31443
+ frontier: (await readCursor(rootDir, spec.name))?.frontier ?? resolveOldest(config, passNowMs),
31444
+ name: spec.name,
31445
+ ...notBefore !== void 0 && { notBefore }
31446
+ };
31447
+ }));
30992
31448
  },
30993
- now: () => clock().getTime(),
30994
- runSlice: async (name) => {
31449
+ now: () => this.nowDate().getTime(),
31450
+ runSlice: async (name, syncHorizon) => {
30995
31451
  try {
30996
31452
  const outcome = await this.runSynthesizerTransaction({
31453
+ syncHorizon,
30997
31454
  synthesizerName: name,
30998
31455
  trigger: options.trigger
30999
31456
  });
31000
31457
  options.onSlice?.(outcome);
31001
31458
  this.logSliceLag(name, outcome);
31002
- return { advanced: outcome.record.status === "success" };
31459
+ const status = outcome.record.status;
31460
+ return status === "success" ? "advanced" : status === "rate_limited" ? "rate_limited" : "failed";
31003
31461
  } catch (error) {
31004
31462
  if (error instanceof TransactionLockHeldError) {
31005
31463
  this.logger.warn(`[engine] skipping '${name}': ${error.message}`);
31006
- return { advanced: false };
31464
+ return "failed";
31007
31465
  }
31008
31466
  throw error;
31009
31467
  }
@@ -31011,13 +31469,24 @@ var Engine = class {
31011
31469
  sleep: (ms) => sleepAbortable(ms, options.signal)
31012
31470
  };
31013
31471
  }
31472
+ async readRateLimitGate(synthesizerName, nowMs) {
31473
+ const latest = await readLatestRunRecord({
31474
+ rootDir: this.repo.rootDir,
31475
+ synthesizerName
31476
+ });
31477
+ if (latest?.status !== "rate_limited") return void 0;
31478
+ if (latest.rate_limit_resets_at === void 0) return void 0;
31479
+ const resetsAt = isoToMs(latest.rate_limit_resets_at, "rate_limit_resets_at");
31480
+ if (resetsAt <= nowMs) return void 0;
31481
+ this.logger.info(`[engine] '${synthesizerName}' rate limited — usage window resets ${latest.rate_limit_resets_at}; holding runs until then`);
31482
+ return resetsAt;
31483
+ }
31014
31484
  logSliceLag(name, outcome) {
31015
31485
  if (outcome.record.status !== "success") {
31016
31486
  this.logger.warn(`[engine] '${name}' slice did not advance (status=${outcome.record.status})`);
31017
31487
  return;
31018
31488
  }
31019
- const clock = this.now ?? (() => /* @__PURE__ */ new Date());
31020
- const lagMs = Math.max(0, clock().getTime() - outcome.frontier);
31489
+ const lagMs = Math.max(0, this.nowDate().getTime() - outcome.frontier);
31021
31490
  const note = `[engine] '${name}' frontier ${formatRelativeAge(lagMs)}`;
31022
31491
  if (lagMs > LAG_BEHIND_TICKS * this.requireConfig().tick) this.logger.warn(`${note} — behind`);
31023
31492
  else this.logger.info(note);
@@ -31042,6 +31511,7 @@ var Engine = class {
31042
31511
  now: this.now,
31043
31512
  repo: this.repo,
31044
31513
  runIdFactory: this.runIdFactory,
31514
+ syncHorizon: request.syncHorizon,
31045
31515
  synthesizer,
31046
31516
  trigger: request.trigger,
31047
31517
  watermarkCapture: this.watermarkCapture
@@ -31068,7 +31538,6 @@ var Engine = class {
31068
31538
  });
31069
31539
  return {
31070
31540
  frontier: ctx.next.frontier,
31071
- reachedWallclock: ctx.reachedWallclock,
31072
31541
  record
31073
31542
  };
31074
31543
  } finally {
@@ -31118,10 +31587,24 @@ function formatPreflightFailure(rootDir, issues) {
31118
31587
  return `Engine root '${rootDir}' is not ready:\n${issues.map((issue) => ` [${issue.code}] ${issue.message}`).join("\n")}`;
31119
31588
  }
31120
31589
  function assembleEngineStatus(input) {
31590
+ const observation = input.syncHorizon;
31591
+ const horizon = observation.kind === "captured" ? observation.horizon : null;
31121
31592
  const synthesizers = input.synthesizers.map((synthesizer) => {
31122
- const lagMs = synthesizer.frontier === null ? null : Math.max(0, input.now - synthesizer.frontier);
31593
+ const frontier = synthesizer.frontier;
31594
+ const lagMs = frontier === null ? null : Math.max(0, input.now - frontier);
31595
+ let horizonGapMs = null;
31596
+ let horizonGapText;
31597
+ if (frontier === null) horizonGapText = "never run";
31598
+ else if (observation.kind === "unavailable") horizonGapText = "control plane unavailable";
31599
+ else if (horizon === null) horizonGapText = "no binding has synced";
31600
+ else {
31601
+ horizonGapMs = Math.max(0, horizon - frontier);
31602
+ horizonGapText = horizonGapMs === 0 ? "caught up to the sync horizon" : `${formatDuration(horizonGapMs)} of synced ground pending`;
31603
+ }
31123
31604
  return {
31124
- frontier: synthesizer.frontier === null ? null : msToIso(synthesizer.frontier),
31605
+ frontier: frontier === null ? null : msToIso(frontier),
31606
+ horizonGapMs,
31607
+ horizonGapText,
31125
31608
  lagMs,
31126
31609
  lagText: lagMs === null ? "never run" : formatRelativeAge(lagMs),
31127
31610
  latestRun: synthesizer.latestRun,
@@ -31130,21 +31613,37 @@ function assembleEngineStatus(input) {
31130
31613
  }).sort((left, right) => left.name.localeCompare(right.name));
31131
31614
  return {
31132
31615
  now: msToIso(input.now),
31616
+ syncHorizon: horizon === null ? null : msToIso(horizon),
31617
+ ...observation.kind === "unavailable" && { syncHorizonError: `control plane unavailable: ${observation.error}` },
31133
31618
  synthesizers
31134
31619
  };
31135
31620
  }
31136
31621
  async function loadEngineStatus(input) {
31137
31622
  const now = input.now ?? Date.now();
31623
+ let syncHorizon;
31624
+ try {
31625
+ syncHorizon = {
31626
+ horizon: await input.syncHorizonCapture(),
31627
+ kind: "captured"
31628
+ };
31629
+ } catch (error) {
31630
+ syncHorizon = {
31631
+ error: error instanceof Error ? error.message : String(error),
31632
+ kind: "unavailable"
31633
+ };
31634
+ }
31138
31635
  const discovery = await discoverSynthesizers(path.resolve(input.rootDir, SYNTHESIZERS_DIR_RELATIVE));
31139
31636
  const latestByName = latestRunByName(await listRunRecords({ rootDir: input.rootDir }));
31140
31637
  const names = discovery.synthesizers.map((synthesizer) => synthesizer.name).filter((name) => input.synthesizerName === void 0 || name === input.synthesizerName);
31638
+ const synthesizers = await Promise.all(names.map(async (name) => ({
31639
+ frontier: (await readCursor(input.rootDir, name))?.frontier ?? null,
31640
+ latestRun: latestByName.get(name) ?? null,
31641
+ name
31642
+ })));
31141
31643
  return assembleEngineStatus({
31142
31644
  now,
31143
- synthesizers: await Promise.all(names.map(async (name) => ({
31144
- frontier: (await readCursor(input.rootDir, name))?.frontier ?? null,
31145
- latestRun: latestByName.get(name) ?? null,
31146
- name
31147
- })))
31645
+ syncHorizon,
31646
+ synthesizers
31148
31647
  });
31149
31648
  }
31150
31649
  function latestRunByName(entries) {
@@ -31152,17 +31651,83 @@ function latestRunByName(entries) {
31152
31651
  for (const { record } of entries) if (!latest.has(record.synthesizer)) latest.set(record.synthesizer, record);
31153
31652
  return latest;
31154
31653
  }
31654
+ const rateLimitEventSchema = object({
31655
+ rate_limit_info: object({
31656
+ resetsAt: number$1().optional(),
31657
+ status: string().min(1)
31658
+ }),
31659
+ type: literal("rate_limit_event")
31660
+ });
31661
+ const resultEventSchema = object({
31662
+ ...claudeResultSchema.shape,
31663
+ type: literal("result")
31664
+ });
31665
+ var ClaudeStreamObserver = class {
31666
+ decoder = new TextDecoder("utf-8", { fatal: false });
31667
+ pendingLine = "";
31668
+ lastRateLimit;
31669
+ result;
31670
+ feed(chunk) {
31671
+ this.pendingLine += this.decoder.decode(chunk, { stream: true });
31672
+ for (;;) {
31673
+ const newlineAt = this.pendingLine.indexOf("\n");
31674
+ if (newlineAt === -1) return;
31675
+ const line = this.pendingLine.slice(0, newlineAt);
31676
+ this.pendingLine = this.pendingLine.slice(newlineAt + 1);
31677
+ this.observeLine(line);
31678
+ }
31679
+ }
31680
+ get resultSeen() {
31681
+ return this.result !== void 0;
31682
+ }
31683
+ observation() {
31684
+ const trailing = this.pendingLine + this.decoder.decode();
31685
+ this.pendingLine = "";
31686
+ if (trailing.length > 0) this.observeLine(trailing);
31687
+ return {
31688
+ ...this.lastRateLimit !== void 0 && { lastRateLimit: this.lastRateLimit },
31689
+ ...this.result !== void 0 && { result: this.result }
31690
+ };
31691
+ }
31692
+ observeLine(line) {
31693
+ if (line.trim().length === 0) return;
31694
+ let event;
31695
+ try {
31696
+ event = JSON.parse(line);
31697
+ } catch {
31698
+ return;
31699
+ }
31700
+ const rateLimit = rateLimitEventSchema.safeParse(event);
31701
+ if (rateLimit.success) {
31702
+ const { status, resetsAt } = rateLimit.data.rate_limit_info;
31703
+ this.lastRateLimit = {
31704
+ status,
31705
+ ...resetsAt !== void 0 && { resetsAt }
31706
+ };
31707
+ return;
31708
+ }
31709
+ const result = resultEventSchema.safeParse(event);
31710
+ if (result.success) {
31711
+ const { type: _type, ...fields } = result.data;
31712
+ this.result = fields;
31713
+ }
31714
+ }
31715
+ };
31155
31716
  const GUEST_MEMORY_MIB = 2048;
31156
31717
  const EXEC_INACTIVITY_TIMEOUT_MS = 600 * 1e3;
31157
31718
  const EXEC_MAX_DURATION_MS = 7200 * 1e3;
31719
+ const EXEC_RESULT_GRACE_MS = 60 * 1e3;
31158
31720
  const DRAIN_TIMED_OUT = Symbol("drain-timed-out");
31159
31721
  async function drainExecStream(handle, options) {
31722
+ const observer = new ClaudeStreamObserver();
31160
31723
  const startedAt = Date.now();
31161
31724
  let lastEventAt = startedAt;
31725
+ let resultSeenAt;
31162
31726
  for (;;) {
31163
31727
  const ceilingDeadline = startedAt + options.maxDurationMs;
31164
31728
  const inactivityDeadline = lastEventAt + options.inactivityMs;
31165
- const deadline = Math.min(ceilingDeadline, inactivityDeadline);
31729
+ const graceDeadline = resultSeenAt === void 0 ? Number.POSITIVE_INFINITY : resultSeenAt + options.resultGraceMs;
31730
+ const deadline = Math.min(ceilingDeadline, inactivityDeadline, graceDeadline);
31166
31731
  let timer;
31167
31732
  const timedOut = new Promise((resolve) => {
31168
31733
  timer = setTimeout(() => resolve(DRAIN_TIMED_OUT), Math.max(0, deadline - Date.now()));
@@ -31176,17 +31741,31 @@ async function drainExecStream(handle, options) {
31176
31741
  }
31177
31742
  if (settled === DRAIN_TIMED_OUT) {
31178
31743
  pending.catch(() => {});
31744
+ const observation = observer.observation();
31745
+ if (observation.result !== void 0) return {
31746
+ kind: "result_no_exit",
31747
+ observation,
31748
+ result: observation.result
31749
+ };
31179
31750
  return {
31180
31751
  kind: "timed_out",
31752
+ observation,
31181
31753
  reason: ceilingDeadline <= inactivityDeadline ? "max_duration" : "inactivity"
31182
31754
  };
31183
31755
  }
31184
- if (settled === null) return { kind: "ended" };
31756
+ if (settled === null) return {
31757
+ kind: "ended",
31758
+ observation: observer.observation()
31759
+ };
31185
31760
  lastEventAt = Date.now();
31186
31761
  if (settled.kind === "stderr") options.onStderr(settled.data);
31187
- else if (settled.kind === "exited") return {
31762
+ else if (settled.kind === "stdout") {
31763
+ observer.feed(settled.data);
31764
+ if (resultSeenAt === void 0 && observer.resultSeen) resultSeenAt = lastEventAt;
31765
+ } else if (settled.kind === "exited") return {
31188
31766
  exitCode: settled.code,
31189
- kind: "exited"
31767
+ kind: "exited",
31768
+ observation: observer.observation()
31190
31769
  };
31191
31770
  }
31192
31771
  }
@@ -31196,6 +31775,7 @@ var MicrosandboxClaudeExecutor = class {
31196
31775
  const runPlan = buildMicrosandboxClaudeRunPlan(input);
31197
31776
  const stderrParts = [];
31198
31777
  let exitCode = 1;
31778
+ let observation = {};
31199
31779
  let sandbox = null;
31200
31780
  let authMaterial = null;
31201
31781
  try {
@@ -31222,11 +31802,16 @@ var MicrosandboxClaudeExecutor = class {
31222
31802
  const drained = await drainExecStream(await sandbox.execStreamWith(cmd, (e) => e.args(args)), {
31223
31803
  inactivityMs: EXEC_INACTIVITY_TIMEOUT_MS,
31224
31804
  maxDurationMs: EXEC_MAX_DURATION_MS,
31225
- onStderr: (data) => stderrParts.push(stderrDecoder.decode(data, { stream: true }))
31805
+ onStderr: (data) => stderrParts.push(stderrDecoder.decode(data, { stream: true })),
31806
+ resultGraceMs: EXEC_RESULT_GRACE_MS
31226
31807
  });
31227
31808
  stderrParts.push(stderrDecoder.decode());
31809
+ observation = drained.observation;
31228
31810
  if (drained.kind === "exited") exitCode = drained.exitCode;
31229
- else if (drained.kind === "timed_out") {
31811
+ else if (drained.kind === "result_no_exit") {
31812
+ exitCode = drained.result.is_error ? 1 : 0;
31813
+ 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`);
31814
+ } else if (drained.kind === "timed_out") {
31230
31815
  exitCode = 124;
31231
31816
  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
31817
  }
@@ -31254,6 +31839,7 @@ var MicrosandboxClaudeExecutor = class {
31254
31839
  }
31255
31840
  return {
31256
31841
  exitCode,
31842
+ observation,
31257
31843
  stderr: stderrParts.join("")
31258
31844
  };
31259
31845
  }
@@ -31339,53 +31925,6 @@ function applyRegistry(builder, value) {
31339
31925
  return r;
31340
31926
  });
31341
31927
  }
31342
- async function runRootRepair(repo) {
31343
- await assertGitCheckout(repo);
31344
- const branch = await inspectBranchReadiness(repo);
31345
- const tree = await inspectWorkingTree(repo);
31346
- if (branch.kind === "no_origin") throw new Error("RootRepair requires 'origin' to be configured in remote mode. Provisioning must add origin before repair.");
31347
- if (branch.kind === "local_head_without_remote_main") throw new Error(`RootRepair refuses to act: local HEAD is at ${branch.localSha} but origin/${repo.branch} does not exist. Operator must reconcile manually.`);
31348
- const needsFastForward = branch.kind === "local_unborn_remote_has_main" || branch.kind === "remote_mismatch";
31349
- if (needsFastForward && !tree.clean) throw new Error(formatCombinedRepairError(repo, branch, tree));
31350
- if (needsFastForward) return {
31351
- kind: "fast_forward",
31352
- ...await fastForwardRoot(repo)
31353
- };
31354
- if (!tree.clean) return await runDirtyRootRepair(repo);
31355
- return { kind: "no_op" };
31356
- }
31357
- async function runDirtyRootRepair(repo) {
31358
- await repairDirtyRoot(repo);
31359
- const interruptedRunIds = (await markInterruptedRuns(repo.rootDir)).map((record) => record.run_id);
31360
- if ((await inspectWorkingTree(repo)).clean) return {
31361
- commit: null,
31362
- interruptedRunIds,
31363
- kind: "dirty_root_repair"
31364
- };
31365
- const { sha } = await commitRootRepair(repo, {
31366
- interruptedRunIds,
31367
- repairId: v7()
31368
- });
31369
- await pushToOrigin(repo);
31370
- return {
31371
- commit: {
31372
- pushed: repo.mode === "remote",
31373
- sha
31374
- },
31375
- interruptedRunIds,
31376
- kind: "dirty_root_repair"
31377
- };
31378
- }
31379
- function formatCombinedRepairError(repo, branch, tree) {
31380
- const dirtyPaths = tree.clean ? "(clean)" : formatWorkingTreePaths(tree.paths);
31381
- return [
31382
- "RootRepair refuses to combine branch fast-forward with dirty-root repair in v1.",
31383
- `Branch: ${branch.kind === "local_unborn_remote_has_main" ? `local checkout is unborn but origin/${repo.branch} exists` : branch.kind === "remote_mismatch" ? `local HEAD does not match origin/${repo.branch}` : `branch state is '${branch.kind}'`}.`,
31384
- "Dirty paths:",
31385
- dirtyPaths,
31386
- "Operator must inspect and choose the order manually."
31387
- ].join("\n");
31388
- }
31389
31928
  const RESERVED_OUTPUT_SUBDIRS = new Set(["types", "views"]);
31390
31929
  const OUTPUT_DIR_RELATIVE = "output";
31391
31930
  const CURSORS_DIR_RELATIVE = ".engine/cursors";
@@ -31433,6 +31972,26 @@ function formatNotReadyError(repo, issues) {
31433
31972
  "Commit your authored config (or run 'ctxe repair'), or pass --force to reset anyway."
31434
31973
  ].join("\n");
31435
31974
  }
31975
+ function foldSyncHorizon(response) {
31976
+ let horizon = null;
31977
+ for (const [bindingId, state] of Object.entries(response.bindings)) {
31978
+ if (state.frontier === null) continue;
31979
+ const ms = isoToMs(state.frontier, `binding ${bindingId} frontier`);
31980
+ if (horizon === null || ms < horizon) horizon = ms;
31981
+ }
31982
+ return horizon;
31983
+ }
31984
+ async function captureSyncHorizon(input) {
31985
+ const fetchImpl = input.fetchImpl ?? globalThis.fetch;
31986
+ const url = new URL("/api/v1/state", input.controlPlaneUrl);
31987
+ url.searchParams.set("root_dir", input.contextBaseRootDir);
31988
+ const response = await fetchImpl(url);
31989
+ if (!response.ok) {
31990
+ const body = await response.text();
31991
+ throw new Error(`Sync horizon request to ${url.toString()} failed: ${response.status} ${response.statusText} — ${body}`);
31992
+ }
31993
+ return foldSyncHorizon(bindingStateResponseSchema.parse(await response.json()));
31994
+ }
31436
31995
  const SOURCE_UPDATED_AT_COLUMN = "_ctx_source_updated_at";
31437
31996
  async function captureWatermark(input) {
31438
31997
  const sql = src_default(input.databaseUrl);
@@ -31497,11 +32056,17 @@ function quoteIdentifier(identifier) {
31497
32056
  //#endregion
31498
32057
  //#region env.ts
31499
32058
  const nonEmptyStringSchema = string().trim().min(1);
32059
+ const LOCAL_CONTROL_PLANE_HOST = "127.0.0.1";
31500
32060
  const envSchema = object({
32061
+ CTXB_CONTROL_PLANE_PORT: number().int().min(1).max(65535).default(3999),
31501
32062
  CTXB_DATABASE_URL: nonEmptyStringSchema.optional(),
32063
+ CTXB_ROOT_DIR: nonEmptyStringSchema.optional(),
31502
32064
  CTXE_GIT_MODE: _enum(["local", "remote"]).default("remote"),
31503
32065
  CTXE_ROOT_DIR: nonEmptyStringSchema.optional()
31504
- });
32066
+ }).transform((input) => ({
32067
+ ...input,
32068
+ CTXB_CONTROL_PLANE_URL: new URL(`http://${LOCAL_CONTROL_PLANE_HOST}:${input.CTXB_CONTROL_PLANE_PORT}`).toString().replace(/\/+$/, "")
32069
+ }));
31505
32070
  function parseEngineCliEnv(rawEnv) {
31506
32071
  return envSchema.parse(rawEnv);
31507
32072
  }
@@ -31509,6 +32074,13 @@ const env = parseEngineCliEnv(process.env);
31509
32074
 
31510
32075
  //#endregion
31511
32076
  //#region commands/build-engine.ts
32077
+ function buildSyncHorizonCapture(paths) {
32078
+ const contextBaseRootDir = env.CTXB_ROOT_DIR ?? paths.rootDir;
32079
+ return async () => captureSyncHorizon({
32080
+ contextBaseRootDir,
32081
+ controlPlaneUrl: env.CTXB_CONTROL_PLANE_URL
32082
+ });
32083
+ }
31512
32084
  function buildEngine(input) {
31513
32085
  const watermarkCapture = async () => captureWatermark({ databaseUrl: input.databaseUrl });
31514
32086
  return new Engine({
@@ -31516,6 +32088,7 @@ function buildEngine(input) {
31516
32088
  executor: new MicrosandboxClaudeExecutor(),
31517
32089
  git: { mode: env.CTXE_GIT_MODE },
31518
32090
  rootDir: input.paths.rootDir,
32091
+ syncHorizonCapture: buildSyncHorizonCapture(input.paths),
31519
32092
  watermarkCapture
31520
32093
  });
31521
32094
  }
@@ -31556,9 +32129,10 @@ async function runEngineListCommand(paths) {
31556
32129
  process.stdout.write(`${formatJsonOutput(output)}\n`);
31557
32130
  if (output.errors.length > 0) process.exitCode = 1;
31558
32131
  }
31559
- async function runEngineStatusCommand(synthesizerName, paths) {
32132
+ async function runEngineStatusCommand(synthesizerName, paths, syncHorizonCapture) {
31560
32133
  const status = await loadEngineStatus({
31561
32134
  rootDir: paths.rootDir,
32135
+ syncHorizonCapture,
31562
32136
  ...synthesizerName ? { synthesizerName } : {}
31563
32137
  });
31564
32138
  process.stdout.write(`${formatJsonOutput(status)}\n`);
@@ -31590,7 +32164,7 @@ async function runBackfillCommand(input) {
31590
32164
  ...summary,
31591
32165
  finalFrontier: summary.finalFrontier === null ? null : msToIso(summary.finalFrontier)
31592
32166
  })}\n`);
31593
- if (summary.stopReason === "stalled") process.exitCode = 1;
32167
+ if (summary.stopReason === "stalled" || summary.stopReason === "rate_limited") process.exitCode = 1;
31594
32168
  }
31595
32169
 
31596
32170
  //#endregion
@@ -31634,12 +32208,16 @@ async function runResetCommand(input) {
31634
32208
  //#endregion
31635
32209
  //#region commands/run.ts
31636
32210
  async function runRunCommand(input) {
31637
- const record = await buildEngine({
32211
+ const outcome = await buildEngine({
31638
32212
  databaseUrl: resolveDatabaseUrl({ cliValue: input.databaseUrl }),
31639
32213
  paths: input.paths
31640
32214
  }).runOnce(input.synthesizerName);
31641
- process.stdout.write(`${formatJsonOutput(record)}\n`);
31642
- if (record.status !== "success") process.exitCode = 1;
32215
+ if (outcome.kind === "caught_up") {
32216
+ process.stdout.write(`${formatJsonOutput({ status: "caught_up" })}\n`);
32217
+ return;
32218
+ }
32219
+ process.stdout.write(`${formatJsonOutput(outcome.record)}\n`);
32220
+ if (outcome.record.status !== "success") process.exitCode = 1;
31643
32221
  }
31644
32222
 
31645
32223
  //#endregion
@@ -31721,8 +32299,9 @@ function createProgram() {
31721
32299
  program.command("list").description("List synthesizers").action(async function() {
31722
32300
  await runEngineListCommand(resolveRuntimePaths(this));
31723
32301
  });
31724
- program.command("status").argument("[synthesizer]", "Only show this synthesizer").description("Print each synthesizer's freshness as JSON: its frontier (the source-time its cursor has reached), how far that lags behind now, and its latest run.").action(async function(synthesizerName) {
31725
- await runEngineStatusCommand(synthesizerName, resolveRuntimePaths(this));
32302
+ program.command("status").argument("[synthesizer]", "Only show this synthesizer").description("Print each synthesizer's freshness as JSON: its frontier (the source-time its cursor has reached), how far that lags behind now (output staleness), how much confirmed-synced ground is still pending below the sync horizon (whether the engine is keeping up — the planner's own measure), and its latest run.").action(async function(synthesizerName) {
32303
+ const paths = resolveRuntimePaths(this);
32304
+ await runEngineStatusCommand(synthesizerName, paths, buildSyncHorizonCapture(paths));
31726
32305
  });
31727
32306
  program.command("logs").argument("<synthesizer>", "Synthesizer name").option("--run-id <id>", "Print stderr for a specific run id").description("Print the engine's per-run stderr for a synthesizer run. The Claude session transcript is a separate file at <rootDir>/.claude/projects/<claude-encoded-rootDir>/<runId>.jsonl, where <claude-encoded-rootDir> is Claude Code's own cwd-derived project key (the guest workdir mirrors the host rootDir, so the key matches what host Claude produces in the same directory). The run id and the Claude --session-id are the same UUIDv7 value.").action(async function(synthesizerName, options) {
31728
32307
  await runEngineLogsCommand(synthesizerName, options, resolveRuntimePaths(this));
@@ -31740,14 +32319,14 @@ function createProgram() {
31740
32319
  yes: options.yes ?? false
31741
32320
  });
31742
32321
  });
31743
- program.command("run").argument("<synthesizer>", "Synthesizer name").option("--database-url <url>", "Postgres URL of the ContextBase database the synthesizer reads from. Falls back to CTXB_DATABASE_URL.").description("Run a single synthesizer once. Exits non-zero on a failed or timed-out run.").action(async function(synthesizerName, options) {
32322
+ program.command("run").argument("<synthesizer>", "Synthesizer name").option("--database-url <url>", "Postgres URL of the ContextBase database the synthesizer reads from. Falls back to CTXB_DATABASE_URL.").description("Run a single synthesizer once. Prints caught_up and exits zero when there is no new synced ground; exits non-zero on any non-success run (failed, timed-out, or rate-limited).").action(async function(synthesizerName, options) {
31744
32323
  await runRunCommand({
31745
32324
  databaseUrl: options.databaseUrl,
31746
32325
  paths: resolveRuntimePaths(this),
31747
32326
  synthesizerName
31748
32327
  });
31749
32328
  });
31750
- program.command("backfill").argument("<synthesizer>", "Synthesizer name").option("--database-url <url>", "Postgres URL of the ContextBase database the synthesizer reads from. Falls back to CTXB_DATABASE_URL.").description("Walk a synthesizer's cursor forward slice-by-slice until it catches up to now. The same loop the daemon runs, without the sleep. Streams each slice's run record; prints a summary; exits non-zero only if a slice fails.").action(async function(synthesizerName, options) {
32329
+ program.command("backfill").argument("<synthesizer>", "Synthesizer name").option("--database-url <url>", "Postgres URL of the ContextBase database the synthesizer reads from. Falls back to CTXB_DATABASE_URL.").description("Walk a synthesizer's cursor forward slice-by-slice until it catches up to the sync horizon. The same loop the daemon runs, without the sleep. Streams each slice's run record; prints a summary; exits non-zero if a slice fails or the usage window is rate-limited.").action(async function(synthesizerName, options) {
31751
32330
  await runBackfillCommand({
31752
32331
  databaseUrl: options.databaseUrl,
31753
32332
  paths: resolveRuntimePaths(this),
@@ -31783,4 +32362,4 @@ runCli().catch((error) => {
31783
32362
  //#endregion
31784
32363
  export { createProgram, resolveRepoContext, resolveRuntimePaths, runCli };
31785
32364
  //# sourceMappingURL=cli.mjs.map
31786
- //# debugId=59a42f57-145c-5f77-afb2-f1f76467783d
32365
+ //# debugId=ee0e5987-2fbc-50ed-9038-a0c4541b7548