@usecontextlayer/ctxe 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +143 -15
- package/dist/cli.mjs.map +1 -1
- package/package.json +3 -3
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]="
|
|
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){}}();
|
|
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.
|
|
62
|
+
var version$1 = "0.4.3";
|
|
63
63
|
|
|
64
64
|
//#endregion
|
|
65
65
|
//#region sentry.ts
|
|
@@ -29490,7 +29490,7 @@ function validate(raw, sourcePath) {
|
|
|
29490
29490
|
}
|
|
29491
29491
|
function applyDefaults(input) {
|
|
29492
29492
|
return {
|
|
29493
|
-
synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.
|
|
29493
|
+
synthesizerImage: input.synthesizerImage ?? "ghcr.io/usecontextlayer/ctx-sandbox:0.4.3",
|
|
29494
29494
|
...input.microsandbox !== void 0 ? { microsandbox: input.microsandbox } : {},
|
|
29495
29495
|
...input.oldestConsideredPoint !== void 0 ? { oldestConsideredPoint: input.oldestConsideredPoint } : {},
|
|
29496
29496
|
maxSliceSize: input.maxSliceSize ?? 864e5,
|
|
@@ -29663,6 +29663,9 @@ async function commitSynthesizerTransaction(repo, input) {
|
|
|
29663
29663
|
async function commitRootRepair(repo, input) {
|
|
29664
29664
|
return await createCommit(repo, buildRootRepairCommitMessage(input));
|
|
29665
29665
|
}
|
|
29666
|
+
async function commitRootReset(repo, input) {
|
|
29667
|
+
return await createCommit(repo, buildRootResetCommitMessage(input));
|
|
29668
|
+
}
|
|
29666
29669
|
async function pushToOrigin(repo) {
|
|
29667
29670
|
if (repo.mode !== "remote") return;
|
|
29668
29671
|
await runGit(repo, [
|
|
@@ -29792,6 +29795,14 @@ function buildRootRepairCommitMessage(input) {
|
|
|
29792
29795
|
""
|
|
29793
29796
|
].join("\n");
|
|
29794
29797
|
}
|
|
29798
|
+
function buildRootResetCommitMessage(input) {
|
|
29799
|
+
return [
|
|
29800
|
+
`ctxe: root reset @ ${input.resetId}`,
|
|
29801
|
+
"",
|
|
29802
|
+
"reset: wipe synthesized content + cursors",
|
|
29803
|
+
""
|
|
29804
|
+
].join("\n");
|
|
29805
|
+
}
|
|
29795
29806
|
function parsePorcelainV1Z(stdout) {
|
|
29796
29807
|
const entries = stdout.split("\0").filter((entry) => entry.length > 0);
|
|
29797
29808
|
const paths = [];
|
|
@@ -30160,7 +30171,7 @@ engine has prepared three files in your working directory:
|
|
|
30160
30171
|
- \`./output/\` — the only path that persists back to the host. Anything written
|
|
30161
30172
|
elsewhere is lost when the sandbox exits.
|
|
30162
30173
|
|
|
30163
|
-
A \`contextengine\` skill is installed. Read and follow it — it defines how to
|
|
30174
|
+
A \`contextengine-synthesizer\` skill is installed. Read and follow it — it defines how to
|
|
30164
30175
|
synthesize well. Then do the work in \`./synthesizer.md\` and exit.
|
|
30165
30176
|
|
|
30166
30177
|
A clean exit is success; the engine records the run from its own metadata. Do
|
|
@@ -31141,6 +31152,44 @@ function latestRunByName(entries) {
|
|
|
31141
31152
|
for (const { record } of entries) if (!latest.has(record.synthesizer)) latest.set(record.synthesizer, record);
|
|
31142
31153
|
return latest;
|
|
31143
31154
|
}
|
|
31155
|
+
const GUEST_MEMORY_MIB = 2048;
|
|
31156
|
+
const EXEC_INACTIVITY_TIMEOUT_MS = 600 * 1e3;
|
|
31157
|
+
const EXEC_MAX_DURATION_MS = 7200 * 1e3;
|
|
31158
|
+
const DRAIN_TIMED_OUT = Symbol("drain-timed-out");
|
|
31159
|
+
async function drainExecStream(handle, options) {
|
|
31160
|
+
const startedAt = Date.now();
|
|
31161
|
+
let lastEventAt = startedAt;
|
|
31162
|
+
for (;;) {
|
|
31163
|
+
const ceilingDeadline = startedAt + options.maxDurationMs;
|
|
31164
|
+
const inactivityDeadline = lastEventAt + options.inactivityMs;
|
|
31165
|
+
const deadline = Math.min(ceilingDeadline, inactivityDeadline);
|
|
31166
|
+
let timer;
|
|
31167
|
+
const timedOut = new Promise((resolve) => {
|
|
31168
|
+
timer = setTimeout(() => resolve(DRAIN_TIMED_OUT), Math.max(0, deadline - Date.now()));
|
|
31169
|
+
});
|
|
31170
|
+
const pending = handle.recv();
|
|
31171
|
+
let settled;
|
|
31172
|
+
try {
|
|
31173
|
+
settled = await Promise.race([pending, timedOut]);
|
|
31174
|
+
} finally {
|
|
31175
|
+
clearTimeout(timer);
|
|
31176
|
+
}
|
|
31177
|
+
if (settled === DRAIN_TIMED_OUT) {
|
|
31178
|
+
pending.catch(() => {});
|
|
31179
|
+
return {
|
|
31180
|
+
kind: "timed_out",
|
|
31181
|
+
reason: ceilingDeadline <= inactivityDeadline ? "max_duration" : "inactivity"
|
|
31182
|
+
};
|
|
31183
|
+
}
|
|
31184
|
+
if (settled === null) return { kind: "ended" };
|
|
31185
|
+
lastEventAt = Date.now();
|
|
31186
|
+
if (settled.kind === "stderr") options.onStderr(settled.data);
|
|
31187
|
+
else if (settled.kind === "exited") return {
|
|
31188
|
+
exitCode: settled.code,
|
|
31189
|
+
kind: "exited"
|
|
31190
|
+
};
|
|
31191
|
+
}
|
|
31192
|
+
}
|
|
31144
31193
|
var MicrosandboxClaudeExecutor = class {
|
|
31145
31194
|
async run(input) {
|
|
31146
31195
|
const microsandbox = await import("microsandbox");
|
|
@@ -31163,25 +31212,27 @@ var MicrosandboxClaudeExecutor = class {
|
|
|
31163
31212
|
synthesizerBody: input.synthesizerBody
|
|
31164
31213
|
});
|
|
31165
31214
|
await ensureHostFilesystemReady(fsPlan);
|
|
31166
|
-
let builder = microsandbox.Sandbox.builder(runPlan.sandboxName).image(runPlan.image).replace();
|
|
31215
|
+
let builder = microsandbox.Sandbox.builder(runPlan.sandboxName).image(runPlan.image).memory(GUEST_MEMORY_MIB).replace();
|
|
31167
31216
|
builder = applyUserMicrosandboxConfig(builder, runPlan.microsandboxConfig, stderrParts);
|
|
31168
31217
|
builder = applySandboxFilesystemPlan(builder, fsPlan);
|
|
31169
31218
|
sandbox = await builder.create();
|
|
31170
31219
|
const [cmd, ...args] = runPlan.command;
|
|
31171
31220
|
const stderrDecoder = new TextDecoder("utf-8", { fatal: false });
|
|
31172
31221
|
try {
|
|
31173
|
-
const
|
|
31174
|
-
|
|
31175
|
-
|
|
31222
|
+
const drained = await drainExecStream(await sandbox.execStreamWith(cmd, (e) => e.args(args)), {
|
|
31223
|
+
inactivityMs: EXEC_INACTIVITY_TIMEOUT_MS,
|
|
31224
|
+
maxDurationMs: EXEC_MAX_DURATION_MS,
|
|
31225
|
+
onStderr: (data) => stderrParts.push(stderrDecoder.decode(data, { stream: true }))
|
|
31226
|
+
});
|
|
31176
31227
|
stderrParts.push(stderrDecoder.decode());
|
|
31177
|
-
|
|
31178
|
-
if (
|
|
31228
|
+
if (drained.kind === "exited") exitCode = drained.exitCode;
|
|
31229
|
+
else if (drained.kind === "timed_out") {
|
|
31179
31230
|
exitCode = 124;
|
|
31180
|
-
stderrParts.push(`exec timed out: ${
|
|
31181
|
-
} else {
|
|
31182
|
-
exitCode = 1;
|
|
31183
|
-
stderrParts.push(`exec failed: ${formatError(error)}`);
|
|
31231
|
+
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`);
|
|
31184
31232
|
}
|
|
31233
|
+
} catch (error) {
|
|
31234
|
+
exitCode = 1;
|
|
31235
|
+
stderrParts.push(`exec failed: ${formatError(error)}`);
|
|
31185
31236
|
}
|
|
31186
31237
|
} catch (error) {
|
|
31187
31238
|
exitCode = 1;
|
|
@@ -31335,6 +31386,53 @@ function formatCombinedRepairError(repo, branch, tree) {
|
|
|
31335
31386
|
"Operator must inspect and choose the order manually."
|
|
31336
31387
|
].join("\n");
|
|
31337
31388
|
}
|
|
31389
|
+
const RESERVED_OUTPUT_SUBDIRS = new Set(["types", "views"]);
|
|
31390
|
+
const OUTPUT_DIR_RELATIVE = "output";
|
|
31391
|
+
const CURSORS_DIR_RELATIVE = ".engine/cursors";
|
|
31392
|
+
async function planRootReset(rootDir) {
|
|
31393
|
+
const cursorFiles = (await readDirNames(cursorsDirPath(rootDir))).filter((name) => name.endsWith(".json"));
|
|
31394
|
+
return {
|
|
31395
|
+
contentPaths: (await readDirNames(path.join(rootDir, OUTPUT_DIR_RELATIVE))).filter((name) => !RESERVED_OUTPUT_SUBDIRS.has(name)).map((name) => path.join(OUTPUT_DIR_RELATIVE, name)).sort(),
|
|
31396
|
+
cursorPaths: cursorFiles.map((name) => path.join(CURSORS_DIR_RELATIVE, name)).sort()
|
|
31397
|
+
};
|
|
31398
|
+
}
|
|
31399
|
+
async function runRootReset(repo, options = {}) {
|
|
31400
|
+
const preflight = await runRootPreflight(repo);
|
|
31401
|
+
if (!preflight.ready && !options.force) throw new Error(formatNotReadyError(repo, preflight.issues));
|
|
31402
|
+
const plan = await planRootReset(repo.rootDir);
|
|
31403
|
+
for (const relativePath of [...plan.cursorPaths, ...plan.contentPaths]) await rm(path.join(repo.rootDir, relativePath), {
|
|
31404
|
+
force: true,
|
|
31405
|
+
recursive: true
|
|
31406
|
+
});
|
|
31407
|
+
if ((await inspectWorkingTree(repo)).clean) return { kind: "no_op" };
|
|
31408
|
+
const { sha } = await commitRootReset(repo, { resetId: v7() });
|
|
31409
|
+
await pushToOrigin(repo);
|
|
31410
|
+
return {
|
|
31411
|
+
commit: {
|
|
31412
|
+
pushed: repo.mode === "remote",
|
|
31413
|
+
sha
|
|
31414
|
+
},
|
|
31415
|
+
kind: "reset",
|
|
31416
|
+
removedContent: plan.contentPaths,
|
|
31417
|
+
removedCursors: plan.cursorPaths
|
|
31418
|
+
};
|
|
31419
|
+
}
|
|
31420
|
+
async function readDirNames(dir) {
|
|
31421
|
+
try {
|
|
31422
|
+
return await readdir$1(dir);
|
|
31423
|
+
} catch (error) {
|
|
31424
|
+
if (isNodeError(error) && error.code === "ENOENT") return [];
|
|
31425
|
+
throw error;
|
|
31426
|
+
}
|
|
31427
|
+
}
|
|
31428
|
+
function formatNotReadyError(repo, issues) {
|
|
31429
|
+
const list = issues.map((issue) => ` - ${issue.message}`).join("\n");
|
|
31430
|
+
return [
|
|
31431
|
+
`RootReset refuses to act: engine root '${repo.rootDir}' is not ready.`,
|
|
31432
|
+
list,
|
|
31433
|
+
"Commit your authored config (or run 'ctxe repair'), or pass --force to reset anyway."
|
|
31434
|
+
].join("\n");
|
|
31435
|
+
}
|
|
31338
31436
|
const SOURCE_UPDATED_AT_COLUMN = "_ctx_source_updated_at";
|
|
31339
31437
|
async function captureWatermark(input) {
|
|
31340
31438
|
const sql = src_default(input.databaseUrl);
|
|
@@ -31510,6 +31608,29 @@ async function runRepairCommand(repo) {
|
|
|
31510
31608
|
process.stdout.write(`${formatJsonOutput(result)}\n`);
|
|
31511
31609
|
}
|
|
31512
31610
|
|
|
31611
|
+
//#endregion
|
|
31612
|
+
//#region commands/reset.ts
|
|
31613
|
+
async function runResetCommand(input) {
|
|
31614
|
+
const { repo, yes, force } = input;
|
|
31615
|
+
if (!yes) {
|
|
31616
|
+
const preflight = await runRootPreflight(repo);
|
|
31617
|
+
const plan = await planRootReset(repo.rootDir);
|
|
31618
|
+
process.stdout.write(`${formatJsonOutput({
|
|
31619
|
+
dryRun: true,
|
|
31620
|
+
hint: "pass --yes to execute (commits the wipe), then 'ctxe backfill <name>' to rebuild",
|
|
31621
|
+
issues: preflight.issues,
|
|
31622
|
+
ready: preflight.ready,
|
|
31623
|
+
wouldRemove: {
|
|
31624
|
+
content: plan.contentPaths,
|
|
31625
|
+
cursors: plan.cursorPaths
|
|
31626
|
+
}
|
|
31627
|
+
})}\n`);
|
|
31628
|
+
return;
|
|
31629
|
+
}
|
|
31630
|
+
const result = await runRootReset(repo, { force });
|
|
31631
|
+
process.stdout.write(`${formatJsonOutput(result)}\n`);
|
|
31632
|
+
}
|
|
31633
|
+
|
|
31513
31634
|
//#endregion
|
|
31514
31635
|
//#region commands/run.ts
|
|
31515
31636
|
async function runRunCommand(input) {
|
|
@@ -31612,6 +31733,13 @@ function createProgram() {
|
|
|
31612
31733
|
program.command("repair").description("Explicit out-of-band root repair: safe fast-forward when clean and stale, or dirty-root rollback that preserves .engine/runs/**. Refuses local-ahead state, divergence, and the combined stale-plus-dirty case.").action(async function() {
|
|
31613
31734
|
await runRepairCommand(resolveRepoContext(this));
|
|
31614
31735
|
});
|
|
31736
|
+
program.command("reset").option("--yes", "Execute the reset (delete cursors + wipe synthesized content, then commit). Without it, prints the plan and exits without mutating.").option("--force", "Reset even when the root is not preflight-ready (e.g. a dirty working tree). Use with care; normally you commit authored config or run 'ctxe repair' first.").description("Reset the whole workspace to a clean pre-synthesis state so it can be rebuilt from scratch: delete every synthesizer cursor (the next run bootstraps from engine.json's oldestConsideredPoint) and wipe all synthesized content under output/ except the authored types/ and views/, in one commit. Whole-workspace by design — output/ is not partitioned by synthesizer. Preserves .engine/runs/**. Default is a dry run; pass --yes to execute, then rebuild with 'ctxe backfill <synthesizer>' or 'ctxe daemon start'.").action(async function(options) {
|
|
31737
|
+
await runResetCommand({
|
|
31738
|
+
force: options.force ?? false,
|
|
31739
|
+
repo: resolveRepoContext(this),
|
|
31740
|
+
yes: options.yes ?? false
|
|
31741
|
+
});
|
|
31742
|
+
});
|
|
31615
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) {
|
|
31616
31744
|
await runRunCommand({
|
|
31617
31745
|
databaseUrl: options.databaseUrl,
|
|
@@ -31655,4 +31783,4 @@ runCli().catch((error) => {
|
|
|
31655
31783
|
//#endregion
|
|
31656
31784
|
export { createProgram, resolveRepoContext, resolveRuntimePaths, runCli };
|
|
31657
31785
|
//# sourceMappingURL=cli.mjs.map
|
|
31658
|
-
//# debugId=
|
|
31786
|
+
//# debugId=59a42f57-145c-5f77-afb2-f1f76467783d
|