@usecontextlayer/ctxe 0.4.11 → 0.4.13

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,12 +1,12 @@
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]="7487993d-7bca-5e7a-92f4-fc03dc11a776")}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]="ef06741d-8451-516d-a05b-dd7da1f526a8")}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";
7
7
  import { appendFileSync, createReadStream, createWriteStream, existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
8
8
  import { constants, homedir, tmpdir } from "node:os";
9
- import path, { posix, sep, win32 } from "node:path";
9
+ import path, { posix, resolve, sep, win32 } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import fs, { lstatSync, readdir, readdirSync, readlinkSync, realpathSync as realpathSync$1 } from "fs";
12
12
  import { access, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir as readdir$1, readlink, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -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.11";
62
+ var version$1 = "0.4.13";
63
63
 
64
64
  //#endregion
65
65
  //#region sentry.ts
@@ -7283,6 +7283,74 @@ function _getDefaultLogLevel() {
7283
7283
  }
7284
7284
  const consola = createConsola();
7285
7285
 
7286
+ //#endregion
7287
+ //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/rng.js
7288
+ const rnds8 = new Uint8Array(16);
7289
+ function rng() {
7290
+ return crypto.getRandomValues(rnds8);
7291
+ }
7292
+
7293
+ //#endregion
7294
+ //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/stringify.js
7295
+ const byteToHex = [];
7296
+ for (let i = 0; i < 256; ++i) byteToHex.push((i + 256).toString(16).slice(1));
7297
+ function unsafeStringify(arr, offset = 0) {
7298
+ 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();
7299
+ }
7300
+
7301
+ //#endregion
7302
+ //#region ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/v7.js
7303
+ const _state = {};
7304
+ function v7(options, buf, offset) {
7305
+ let bytes;
7306
+ if (options) bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
7307
+ else {
7308
+ const now = Date.now();
7309
+ const rnds = rng();
7310
+ updateV7State(_state, now, rnds);
7311
+ bytes = v7Bytes(rnds, _state.msecs, _state.seq, buf, offset);
7312
+ }
7313
+ return buf ?? unsafeStringify(bytes);
7314
+ }
7315
+ function updateV7State(state, now, rnds) {
7316
+ state.msecs ??= -Infinity;
7317
+ state.seq ??= 0;
7318
+ if (now > state.msecs) {
7319
+ state.seq = rnds[6] << 23 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
7320
+ state.msecs = now;
7321
+ } else {
7322
+ state.seq = state.seq + 1 | 0;
7323
+ if (state.seq === 0) state.msecs++;
7324
+ }
7325
+ return state;
7326
+ }
7327
+ function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
7328
+ if (rnds.length < 16) throw new Error("Random bytes length must be >= 16");
7329
+ if (!buf) {
7330
+ buf = new Uint8Array(16);
7331
+ offset = 0;
7332
+ } else if (offset < 0 || offset + 16 > buf.length) throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
7333
+ msecs ??= Date.now();
7334
+ seq ??= rnds[6] * 127 << 24 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
7335
+ buf[offset++] = msecs / 1099511627776 & 255;
7336
+ buf[offset++] = msecs / 4294967296 & 255;
7337
+ buf[offset++] = msecs / 16777216 & 255;
7338
+ buf[offset++] = msecs / 65536 & 255;
7339
+ buf[offset++] = msecs / 256 & 255;
7340
+ buf[offset++] = msecs & 255;
7341
+ buf[offset++] = 112 | seq >>> 28 & 15;
7342
+ buf[offset++] = seq >>> 20 & 255;
7343
+ buf[offset++] = 128 | seq >>> 14 & 63;
7344
+ buf[offset++] = seq >>> 6 & 255;
7345
+ buf[offset++] = seq << 2 & 255 | rnds[10] & 3;
7346
+ buf[offset++] = rnds[11];
7347
+ buf[offset++] = rnds[12];
7348
+ buf[offset++] = rnds[13];
7349
+ buf[offset++] = rnds[14];
7350
+ buf[offset++] = rnds[15];
7351
+ return buf;
7352
+ }
7353
+
7286
7354
  //#endregion
7287
7355
  //#region ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/core.js
7288
7356
  var _a$1;
@@ -17926,74 +17994,6 @@ const execaNode = createExeca(mapNode);
17926
17994
  const $ = createExeca(mapScriptAsync, {}, deepScriptOptions, setScriptSync);
17927
17995
  const { sendMessage, getOneMessage, getEachMessage, getCancelSignal } = getIpcExport();
17928
17996
 
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
-
17997
17997
  //#endregion
17998
17998
  //#region ../../node_modules/.pnpm/kind-of@6.0.3/node_modules/kind-of/index.js
17999
17999
  var require_kind_of = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -29629,6 +29629,44 @@ function osUsername() {
29629
29629
 
29630
29630
  //#endregion
29631
29631
  //#region ../engine/dist/index.mjs
29632
+ function msToIso(ms) {
29633
+ return new Date(ms).toISOString();
29634
+ }
29635
+ const isoMsSchema = string().transform((value, ctx) => {
29636
+ const ms = Date.parse(value);
29637
+ if (Number.isNaN(ms)) {
29638
+ ctx.addIssue({
29639
+ code: "custom",
29640
+ message: "must be an ISO-8601 timestamp"
29641
+ });
29642
+ return NEVER;
29643
+ }
29644
+ return ms;
29645
+ });
29646
+ function isoToMs(iso, label) {
29647
+ const result = isoMsSchema.safeParse(iso);
29648
+ if (!result.success) throw new Error(`${label} is not a valid ISO-8601 timestamp: ${iso}`);
29649
+ return result.data;
29650
+ }
29651
+ function resolveAdHocWindow(input) {
29652
+ const window = input.lastMs !== void 0 ? {
29653
+ from: input.nowMs - input.lastMs,
29654
+ to: input.nowMs
29655
+ } : {
29656
+ from: input.fromMs ?? input.oldestMs,
29657
+ to: input.toMs ?? input.nowMs
29658
+ };
29659
+ if (window.from >= window.to) throw new Error(`adhoc window is empty: from (${msToIso(window.from)}) must be strictly before to (${msToIso(window.to)}).`);
29660
+ return window;
29661
+ }
29662
+ function buildAdHocSlice(tables, window) {
29663
+ const slice = {};
29664
+ for (const table of tables) slice[table] = {
29665
+ from: window.from,
29666
+ to: window.to
29667
+ };
29668
+ return slice;
29669
+ }
29632
29670
  const SYNTHESIZERS_DIR_RELATIVE = ".agents/synthesizers";
29633
29671
  const SAFE_ENGINE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/u;
29634
29672
  function assertSafeEngineName(name) {
@@ -29674,25 +29712,6 @@ function formatError(error) {
29674
29712
  if (error instanceof Error) return error.message;
29675
29713
  return String(error);
29676
29714
  }
29677
- function msToIso(ms) {
29678
- return new Date(ms).toISOString();
29679
- }
29680
- const isoMsSchema = string().transform((value, ctx) => {
29681
- const ms = Date.parse(value);
29682
- if (Number.isNaN(ms)) {
29683
- ctx.addIssue({
29684
- code: "custom",
29685
- message: "must be an ISO-8601 timestamp"
29686
- });
29687
- return NEVER;
29688
- }
29689
- return ms;
29690
- });
29691
- function isoToMs(iso, label) {
29692
- const result = isoMsSchema.safeParse(iso);
29693
- if (!result.success) throw new Error(`${label} is not a valid ISO-8601 timestamp: ${iso}`);
29694
- return result.data;
29695
- }
29696
29715
  const cursorFileSchema = strictObject({
29697
29716
  floors: record(string(), isoMsSchema),
29698
29717
  frontier: isoMsSchema
@@ -29718,12 +29737,12 @@ async function writeCursor(rootDir, synthesizerName, cursor) {
29718
29737
  }
29719
29738
  const ENGINE_CONFIG_FILE_NAME = "engine.json";
29720
29739
  const nonEmptyStringSchema$1 = string().trim().min(1);
29721
- const DURATION_UNIT_MS = {
29740
+ const DURATION_UNIT_MS$1 = {
29722
29741
  h: 36e5,
29723
29742
  m: 6e4,
29724
29743
  s: 1e3
29725
29744
  };
29726
- const durationMsSchema = string().transform((value, ctx) => {
29745
+ const durationMsSchema$1 = string().transform((value, ctx) => {
29727
29746
  const match = /^(\d+)([hms])$/.exec(value);
29728
29747
  if (match === null) {
29729
29748
  ctx.addIssue({
@@ -29733,7 +29752,7 @@ const durationMsSchema = string().transform((value, ctx) => {
29733
29752
  return NEVER;
29734
29753
  }
29735
29754
  const [, amount, unit] = match;
29736
- return Number(amount) * DURATION_UNIT_MS[unit];
29755
+ return Number(amount) * DURATION_UNIT_MS$1[unit];
29737
29756
  });
29738
29757
  const microsandboxConfigSchema = record(string(), unknown()).superRefine((config, ctx) => {
29739
29758
  if ("bindMounts" in config) ctx.addIssue({
@@ -29743,11 +29762,11 @@ const microsandboxConfigSchema = record(string(), unknown()).superRefine((config
29743
29762
  });
29744
29763
  }).optional();
29745
29764
  const engineConfigSchema = strictObject({
29746
- maxSliceSize: durationMsSchema.optional(),
29765
+ maxSliceSize: durationMsSchema$1.optional(),
29747
29766
  microsandbox: microsandboxConfigSchema,
29748
29767
  oldestConsideredPoint: isoMsSchema.optional(),
29749
29768
  synthesizerImage: nonEmptyStringSchema$1.optional(),
29750
- tick: durationMsSchema.optional()
29769
+ tick: durationMsSchema$1.optional()
29751
29770
  });
29752
29771
  function mergeEngineConfig(...inputs) {
29753
29772
  return applyDefaults(Object.assign({}, ...inputs.map((input) => stripUndefined(input))));
@@ -29778,7 +29797,7 @@ function validate(raw, sourcePath) {
29778
29797
  }
29779
29798
  function applyDefaults(input) {
29780
29799
  return {
29781
- synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.11",
29800
+ synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.13",
29782
29801
  ...input.microsandbox !== void 0 ? { microsandbox: input.microsandbox } : {},
29783
29802
  ...input.oldestConsideredPoint !== void 0 ? { oldestConsideredPoint: input.oldestConsideredPoint } : {},
29784
29803
  maxSliceSize: input.maxSliceSize ?? 864e5,
@@ -29796,14 +29815,12 @@ function formatZodError$1(error) {
29796
29815
  }
29797
29816
  function gatePasses(input) {
29798
29817
  if (input.syncHorizon === null) return false;
29799
- if (input.mode === "manual") return input.frontier < input.syncHorizon;
29800
29818
  return input.syncHorizon - input.frontier >= input.tick;
29801
29819
  }
29802
29820
  function planLoopPass(input) {
29803
29821
  const horizon = input.syncHorizon;
29804
29822
  const horizonDue = horizon === null ? [] : input.synthesizers.filter((synthesizer) => gatePasses({
29805
29823
  frontier: synthesizer.frontier,
29806
- mode: "loop",
29807
29824
  syncHorizon: horizon,
29808
29825
  tick: input.tick
29809
29826
  }));
@@ -30232,9 +30249,9 @@ function dirtyWorkingTreeIssue(paths) {
30232
30249
  };
30233
30250
  }
30234
30251
  const runTriggerSchema = _enum([
30252
+ "adhoc",
30235
30253
  "backfill",
30236
- "daemon",
30237
- "manual"
30254
+ "daemon"
30238
30255
  ]);
30239
30256
  _enum([
30240
30257
  "success",
@@ -30484,6 +30501,14 @@ function formatCombinedRepairError(repo, branch, tree) {
30484
30501
  "Operator must inspect and choose the order manually."
30485
30502
  ].join("\n");
30486
30503
  }
30504
+ function renderSliceJson(slice) {
30505
+ const windows = {};
30506
+ for (const [table, window] of Object.entries(slice)) windows[table] = {
30507
+ from: msToIso(window.from),
30508
+ to: msToIso(window.to)
30509
+ };
30510
+ return `${JSON.stringify(windows, null, " ")}\n`;
30511
+ }
30487
30512
  const synthesizerSpecFrontmatterSchema = strictObject({
30488
30513
  description: string().optional(),
30489
30514
  name: string().trim().min(1)
@@ -30800,15 +30825,16 @@ function buildSandboxFilesystemPlan(input) {
30800
30825
  const guestWorkdir = workspaceGuestPath(rootDir);
30801
30826
  const bindMounts = [{
30802
30827
  guestPath: outputGuestPath(rootDir),
30803
- hostPath: path.join(rootDir, "output"),
30828
+ hostPath: input.outputHostDir ?? path.join(rootDir, "output"),
30804
30829
  hostPathBehavior: "createDir",
30805
30830
  mode: "rw"
30806
- }, {
30831
+ }];
30832
+ if (input.mountTranscript ?? true) bindMounts.push({
30807
30833
  guestPath: CLAUDE_PROJECTS_GUEST_PATH,
30808
30834
  hostPath: claudeProjectsDirPath(rootDir),
30809
30835
  hostPathBehavior: "createDir",
30810
30836
  mode: "rw"
30811
- }];
30837
+ });
30812
30838
  const textPatches = [
30813
30839
  {
30814
30840
  content: input.claudeMaterial.claudeJson,
@@ -31036,9 +31062,11 @@ async function executeSynthesizerRun(input) {
31036
31062
  runId: input.runId,
31037
31063
  sliceJson: input.sliceJson,
31038
31064
  synthesizer: input.synthesizer,
31039
- synthesizerImage: input.synthesizerImage
31065
+ synthesizerImage: input.synthesizerImage,
31066
+ ...input.outputHostDir !== void 0 && { outputHostDir: input.outputHostDir },
31067
+ ...input.mountTranscript !== void 0 && { mountTranscript: input.mountTranscript }
31040
31068
  });
31041
- const result = await input.executor.run(executorInput);
31069
+ const result = await input.executor.run(executorInput, input.hooks);
31042
31070
  const completedAtMs = now().getTime();
31043
31071
  const durationMs = Math.max(0, completedAtMs - startedAtMs);
31044
31072
  const status = classifyOutcome(result.exitCode, result.observation);
@@ -31087,7 +31115,9 @@ function buildExecutorInput(input) {
31087
31115
  sandboxName: buildSandboxName(input.synthesizer.name, input.runId),
31088
31116
  sliceJson: input.sliceJson,
31089
31117
  synthesizerBody: input.synthesizer.body,
31090
- synthesizerName: input.synthesizer.name
31118
+ synthesizerName: input.synthesizer.name,
31119
+ ...input.outputHostDir !== void 0 && { outputHostDir: input.outputHostDir },
31120
+ ...input.mountTranscript !== void 0 && { mountTranscript: input.mountTranscript }
31091
31121
  };
31092
31122
  }
31093
31123
  function buildContextEngineEnv(input) {
@@ -31167,14 +31197,6 @@ function reconcileCursor(input) {
31167
31197
  frontier: input.prevCursor.frontier
31168
31198
  };
31169
31199
  }
31170
- function renderSliceJson(slice) {
31171
- const windows = {};
31172
- for (const [table, window] of Object.entries(slice)) windows[table] = {
31173
- from: msToIso(window.from),
31174
- to: msToIso(window.to)
31175
- };
31176
- return `${JSON.stringify(windows, null, " ")}\n`;
31177
- }
31178
31200
  const DEFAULT_OLDEST_LOOKBACK_MS = 720 * 60 * 60 * 1e3;
31179
31201
  function resolveOldest(config, beginMs) {
31180
31202
  return config.oldestConsideredPoint ?? beginMs - DEFAULT_OLDEST_LOOKBACK_MS;
@@ -31195,14 +31217,7 @@ async function openSynthesizerTransaction(input) {
31195
31217
  trigger: input.trigger
31196
31218
  });
31197
31219
  await writeRunRecord(runDir, startingRecord);
31198
- const watermark = await input.watermarkCapture({
31199
- rootDir: input.repo.rootDir,
31200
- runDir,
31201
- runId,
31202
- startedAt,
31203
- synthesizer: input.synthesizer,
31204
- trigger: input.trigger
31205
- });
31220
+ const watermark = await input.watermarkCapture();
31206
31221
  const persistedCursor = await readCursor(input.repo.rootDir, input.synthesizer.name);
31207
31222
  const planConfig = {
31208
31223
  maxSliceSize: input.config.maxSliceSize,
@@ -31372,26 +31387,38 @@ var Engine = class {
31372
31387
  this.daemonRunning = false;
31373
31388
  }
31374
31389
  }
31375
- async runOnce(synthesizerName) {
31376
- await this.prepareSynthesizerRun(synthesizerName);
31390
+ async runAdHoc(input) {
31391
+ const synthesizer = await this.ensureSynthesizerLoaded(input.synthesizerName);
31377
31392
  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,
31388
- synthesizerName,
31389
- trigger: "manual"
31393
+ const begin = this.nowDate();
31394
+ const nowMs = begin.getTime();
31395
+ const window = resolveAdHocWindow({
31396
+ nowMs,
31397
+ oldestMs: resolveOldest(config, nowMs),
31398
+ ...input.fromMs !== void 0 && { fromMs: input.fromMs },
31399
+ ...input.toMs !== void 0 && { toMs: input.toMs },
31400
+ ...input.lastMs !== void 0 && { lastMs: input.lastMs }
31390
31401
  });
31391
- return {
31392
- kind: "ran",
31393
- record
31394
- };
31402
+ const watermark = await this.watermarkCapture();
31403
+ const sliceJson = renderSliceJson(buildAdHocSlice(Object.keys(watermark), window));
31404
+ const runId = assertSafeEngineName((this.runIdFactory ?? v7)());
31405
+ return (await executeSynthesizerRun({
31406
+ databaseUrl: this.databaseUrl,
31407
+ executor: this.executor,
31408
+ logger: this.logger,
31409
+ mountTranscript: false,
31410
+ outputHostDir: input.outputDir,
31411
+ rootDir: this.repo.rootDir,
31412
+ runId,
31413
+ sliceJson,
31414
+ startedAt: begin.toISOString(),
31415
+ synthesizer,
31416
+ synthesizerImage: config.synthesizerImage,
31417
+ trigger: "adhoc",
31418
+ ...config.microsandbox !== void 0 && { microsandboxConfig: config.microsandbox },
31419
+ ...this.now !== void 0 && { now: this.now },
31420
+ ...input.hooks !== void 0 && { hooks: input.hooks }
31421
+ })).outcome;
31395
31422
  }
31396
31423
  async backfill(synthesizerName, options = {}) {
31397
31424
  await this.prepareSynthesizerRun(synthesizerName);
@@ -31494,9 +31521,14 @@ var Engine = class {
31494
31521
  async prepareSynthesizerRun(synthesizerName) {
31495
31522
  const report = await runRootPreflight(this.repo);
31496
31523
  if (!report.ready) throw new Error(formatPreflightFailure(this.repo.rootDir, report.issues));
31524
+ await this.ensureSynthesizerLoaded(synthesizerName);
31525
+ }
31526
+ async ensureSynthesizerLoaded(synthesizerName) {
31497
31527
  await this.ensureLoaded();
31498
31528
  await this.refreshSynthesizers();
31499
- if (!this.synthesizers.has(synthesizerName)) throw new Error(`Unknown synthesizer '${synthesizerName}'.`);
31529
+ const synthesizer = this.synthesizers.get(synthesizerName);
31530
+ if (!synthesizer) throw new Error(`Unknown synthesizer '${synthesizerName}'.`);
31531
+ return synthesizer;
31500
31532
  }
31501
31533
  async runSynthesizerTransaction(request) {
31502
31534
  const releaseLock = await acquireTransactionLock(this.repo.rootDir);
@@ -31761,6 +31793,7 @@ async function drainExecStream(handle, options) {
31761
31793
  if (settled.kind === "stderr") options.onStderr(settled.data);
31762
31794
  else if (settled.kind === "stdout") {
31763
31795
  observer.feed(settled.data);
31796
+ options.onStdout(settled.data);
31764
31797
  if (resultSeenAt === void 0 && observer.resultSeen) resultSeenAt = lastEventAt;
31765
31798
  } else if (settled.kind === "exited") return {
31766
31799
  exitCode: settled.code,
@@ -31770,7 +31803,7 @@ async function drainExecStream(handle, options) {
31770
31803
  }
31771
31804
  }
31772
31805
  var MicrosandboxClaudeExecutor = class {
31773
- async run(input) {
31806
+ async run(input, hooks) {
31774
31807
  const microsandbox = await import("microsandbox");
31775
31808
  const runPlan = buildMicrosandboxClaudeRunPlan(input);
31776
31809
  const stderrParts = [];
@@ -31789,7 +31822,9 @@ var MicrosandboxClaudeExecutor = class {
31789
31822
  guestEnv: runPlan.microsandboxConfig.env,
31790
31823
  rootDir: input.rootDir,
31791
31824
  sliceJson: input.sliceJson,
31792
- synthesizerBody: input.synthesizerBody
31825
+ synthesizerBody: input.synthesizerBody,
31826
+ ...input.outputHostDir !== void 0 && { outputHostDir: input.outputHostDir },
31827
+ ...input.mountTranscript !== void 0 && { mountTranscript: input.mountTranscript }
31793
31828
  });
31794
31829
  await ensureHostFilesystemReady(fsPlan);
31795
31830
  let builder = microsandbox.Sandbox.builder(runPlan.sandboxName).image(runPlan.image).memory(GUEST_MEMORY_MIB).replace();
@@ -31798,14 +31833,23 @@ var MicrosandboxClaudeExecutor = class {
31798
31833
  sandbox = await builder.create();
31799
31834
  const [cmd, ...args] = runPlan.command;
31800
31835
  const stderrDecoder = new TextDecoder("utf-8", { fatal: false });
31836
+ const stdoutDecoder = new TextDecoder("utf-8", { fatal: false });
31801
31837
  try {
31802
31838
  const drained = await drainExecStream(await sandbox.execStreamWith(cmd, (e) => e.args(args)), {
31803
31839
  inactivityMs: EXEC_INACTIVITY_TIMEOUT_MS,
31804
31840
  maxDurationMs: EXEC_MAX_DURATION_MS,
31805
- onStderr: (data) => stderrParts.push(stderrDecoder.decode(data, { stream: true })),
31841
+ onStderr: (data) => {
31842
+ const text = stderrDecoder.decode(data, { stream: true });
31843
+ stderrParts.push(text);
31844
+ hooks?.onStderr?.(text);
31845
+ },
31846
+ onStdout: (data) => {
31847
+ if (hooks?.onStdout) hooks.onStdout(stdoutDecoder.decode(data, { stream: true }));
31848
+ },
31806
31849
  resultGraceMs: EXEC_RESULT_GRACE_MS
31807
31850
  });
31808
31851
  stderrParts.push(stderrDecoder.decode());
31852
+ hooks?.onStdout?.(stdoutDecoder.decode());
31809
31853
  observation = drained.observation;
31810
31854
  if (drained.kind === "exited") exitCode = drained.exitCode;
31811
31855
  else if (drained.kind === "result_no_exit") {
@@ -32151,6 +32195,62 @@ async function runEngineLogsCommand(synthesizerName, options, paths) {
32151
32195
  }));
32152
32196
  }
32153
32197
 
32198
+ //#endregion
32199
+ //#region commands/adhoc.ts
32200
+ const DURATION_UNIT_MS = {
32201
+ d: 864e5,
32202
+ h: 36e5,
32203
+ m: 6e4,
32204
+ s: 1e3
32205
+ };
32206
+ const durationMsSchema = string().transform((value, ctx) => {
32207
+ const match = /^(\d+)(d|h|m|s)$/.exec(value);
32208
+ if (match === null) {
32209
+ ctx.addIssue({
32210
+ code: "custom",
32211
+ message: "must be a duration like \"7d\", \"12h\", \"30m\", or \"45s\""
32212
+ });
32213
+ return NEVER;
32214
+ }
32215
+ const [, amount, unit] = match;
32216
+ return Number(amount) * DURATION_UNIT_MS[unit];
32217
+ });
32218
+ const adHocWindowSchema = object({
32219
+ from: isoMsSchema.optional(),
32220
+ last: durationMsSchema.optional(),
32221
+ to: isoMsSchema.optional()
32222
+ }).refine((window) => window.last !== void 0 !== (window.from !== void 0 || window.to !== void 0), { message: "provide --last, or at least one of --from/--to (not both)" });
32223
+ async function runAdHocCommand(input) {
32224
+ const window = adHocWindowSchema.parse({
32225
+ ...input.from !== void 0 && { from: input.from },
32226
+ ...input.to !== void 0 && { to: input.to },
32227
+ ...input.last !== void 0 && { last: input.last }
32228
+ });
32229
+ const engine = buildEngine({
32230
+ databaseUrl: resolveDatabaseUrl({ cliValue: input.databaseUrl }),
32231
+ paths: input.paths
32232
+ });
32233
+ const outputDir = resolve(input.out);
32234
+ const outcome = await engine.runAdHoc({
32235
+ hooks: {
32236
+ onStderr: (chunk) => process.stderr.write(chunk),
32237
+ onStdout: (chunk) => process.stdout.write(chunk)
32238
+ },
32239
+ outputDir,
32240
+ synthesizerName: input.synthesizerName,
32241
+ ...window.from !== void 0 && { fromMs: window.from },
32242
+ ...window.to !== void 0 && { toMs: window.to },
32243
+ ...window.last !== void 0 && { lastMs: window.last }
32244
+ });
32245
+ process.stderr.write(`${formatJsonOutput({
32246
+ durationMs: outcome.durationMs,
32247
+ exitCode: outcome.exitCode,
32248
+ out: outputDir,
32249
+ status: outcome.status
32250
+ })}\n`);
32251
+ if (outcome.status !== "success") process.exitCode = 1;
32252
+ }
32253
+
32154
32254
  //#endregion
32155
32255
  //#region commands/backfill.ts
32156
32256
  async function runBackfillCommand(input) {
@@ -32205,21 +32305,6 @@ async function runResetCommand(input) {
32205
32305
  process.stdout.write(`${formatJsonOutput(result)}\n`);
32206
32306
  }
32207
32307
 
32208
- //#endregion
32209
- //#region commands/run.ts
32210
- async function runRunCommand(input) {
32211
- const outcome = await buildEngine({
32212
- databaseUrl: resolveDatabaseUrl({ cliValue: input.databaseUrl }),
32213
- paths: input.paths
32214
- }).runOnce(input.synthesizerName);
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;
32221
- }
32222
-
32223
32308
  //#endregion
32224
32309
  //#region commands/start.ts
32225
32310
  async function runStartCommand(input) {
@@ -32319,11 +32404,15 @@ function createProgram() {
32319
32404
  yes: options.yes ?? false
32320
32405
  });
32321
32406
  });
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) {
32323
- await runRunCommand({
32407
+ program.command("adhoc").argument("<synthesizer>", "Synthesizer name").option("--from <iso>", "Window start (ISO-8601, exclusive). Defaults to the engine's oldestConsideredPoint when omitted.").option("--to <iso>", "Window end (ISO-8601, inclusive). Defaults to now when omitted.").option("--last <duration>", "Relative window ending now, e.g. \"7d\", \"12h\", \"30m\". Mutually exclusive with --from/--to.").requiredOption("--out <dir>", "Directory the synthesized output is written to. Used exactly as-is (created if missing); the synthesizer reads its current contents as prior output.").option("--database-url <url>", "Postgres URL of the ContextBase database the synthesizer reads from. Falls back to CTXB_DATABASE_URL.").description("Ephemeral authoring run: run a synthesizer once over an explicit window into --out, reading your live (uncommitted) synthesizer.md and output/types. Touches nothing durable no preflight, no sync horizon, no cursor, no commit, no push. Streams the run to stdout; saves no transcript. Exits non-zero on a non-success run.").action(async function(synthesizerName, options) {
32408
+ await runAdHocCommand({
32324
32409
  databaseUrl: options.databaseUrl,
32410
+ from: options.from,
32411
+ last: options.last,
32412
+ out: options.out,
32325
32413
  paths: resolveRuntimePaths(this),
32326
- synthesizerName
32414
+ synthesizerName,
32415
+ to: options.to
32327
32416
  });
32328
32417
  });
32329
32418
  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) {
@@ -32362,4 +32451,4 @@ runCli().catch((error) => {
32362
32451
  //#endregion
32363
32452
  export { createProgram, resolveRepoContext, resolveRuntimePaths, runCli };
32364
32453
  //# sourceMappingURL=cli.mjs.map
32365
- //# debugId=7487993d-7bca-5e7a-92f4-fc03dc11a776
32454
+ //# debugId=ef06741d-8451-516d-a05b-dd7da1f526a8