@kontextmind/kxm 0.7.23 → 0.7.25
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/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +59 -5
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +507 -27
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +500 -26
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/vnext.ts +65 -3
- package/plugins/kxm/src/cli.ts +1 -1
- package/plugins/kxm/src/database.ts +3 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +31 -0
- package/plugins/kxm/src/vnext-engine-fold.ts +87 -2
- package/plugins/kxm/src/vnext-engine-gate-records.ts +19 -6
- package/plugins/kxm/src/vnext-engine.ts +325 -9
- package/plugins/kxm/src/vnext-runtime-owner.ts +2 -0
- package/plugins/kxm/src/vnext-runtime-store.ts +158 -2
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +24 -3
- package/plugins/kxm/src/vnext-runtime.ts +17 -3
- package/schemas/vnext/common.schema.json +1 -0
- package/schemas/vnext/drive-receipt.schema.json +95 -0
- package/schemas/vnext/run-event.schema.json +39 -0
|
@@ -16261,6 +16261,7 @@ var VnextSchemaRegistry = class {
|
|
|
16261
16261
|
migrationReceiptValidator;
|
|
16262
16262
|
permissionDiffValidator;
|
|
16263
16263
|
runEventValidator;
|
|
16264
|
+
driveReceiptValidator;
|
|
16264
16265
|
constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
|
|
16265
16266
|
this.schemasDir = resolve(schemasDir);
|
|
16266
16267
|
this.ajv = new import__.Ajv2020({ allErrors: true, strict: true, strictRequired: false });
|
|
@@ -16277,6 +16278,7 @@ var VnextSchemaRegistry = class {
|
|
|
16277
16278
|
const migrationReceiptFile = "migration-receipt.schema.json";
|
|
16278
16279
|
const permissionDiffFile = "permission-diff.schema.json";
|
|
16279
16280
|
const runEventFile = "run-event.schema.json";
|
|
16281
|
+
const driveReceiptFile = "drive-receipt.schema.json";
|
|
16280
16282
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, localBindingsFile)));
|
|
16281
16283
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, templateProvenanceFile)));
|
|
16282
16284
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, initOperationFile)));
|
|
@@ -16285,6 +16287,7 @@ var VnextSchemaRegistry = class {
|
|
|
16285
16287
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, migrationReceiptFile)));
|
|
16286
16288
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, permissionDiffFile)));
|
|
16287
16289
|
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, runEventFile)));
|
|
16290
|
+
this.ajv.addSchema(readJsonObject(join2(this.schemasDir, driveReceiptFile)));
|
|
16288
16291
|
for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA)) {
|
|
16289
16292
|
const validator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${definition.file}`);
|
|
16290
16293
|
if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
|
|
@@ -16298,6 +16301,7 @@ var VnextSchemaRegistry = class {
|
|
|
16298
16301
|
const migrationReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${migrationReceiptFile}`);
|
|
16299
16302
|
const permissionDiffValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${permissionDiffFile}`);
|
|
16300
16303
|
const runEventValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${runEventFile}`);
|
|
16304
|
+
const driveReceiptValidator = this.ajv.getSchema(`https://schemas.kxm.dev/vnext/${driveReceiptFile}`);
|
|
16301
16305
|
if (!localBindingsValidator) throw new Error(`schema did not compile: ${localBindingsFile}`);
|
|
16302
16306
|
if (!templateProvenanceValidator) throw new Error(`schema did not compile: ${templateProvenanceFile}`);
|
|
16303
16307
|
if (!initOperationValidator) throw new Error(`schema did not compile: ${initOperationFile}`);
|
|
@@ -16306,6 +16310,7 @@ var VnextSchemaRegistry = class {
|
|
|
16306
16310
|
if (!migrationReceiptValidator) throw new Error(`schema did not compile: ${migrationReceiptFile}`);
|
|
16307
16311
|
if (!permissionDiffValidator) throw new Error(`schema did not compile: ${permissionDiffFile}`);
|
|
16308
16312
|
if (!runEventValidator) throw new Error(`schema did not compile: ${runEventFile}`);
|
|
16313
|
+
if (!driveReceiptValidator) throw new Error(`schema did not compile: ${driveReceiptFile}`);
|
|
16309
16314
|
this.localBindingsValidator = localBindingsValidator;
|
|
16310
16315
|
this.templateProvenanceValidator = templateProvenanceValidator;
|
|
16311
16316
|
this.initOperationValidator = initOperationValidator;
|
|
@@ -16314,6 +16319,7 @@ var VnextSchemaRegistry = class {
|
|
|
16314
16319
|
this.migrationReceiptValidator = migrationReceiptValidator;
|
|
16315
16320
|
this.permissionDiffValidator = permissionDiffValidator;
|
|
16316
16321
|
this.runEventValidator = runEventValidator;
|
|
16322
|
+
this.driveReceiptValidator = driveReceiptValidator;
|
|
16317
16323
|
}
|
|
16318
16324
|
validate(kind, value, file) {
|
|
16319
16325
|
const definition = RESOURCE_SCHEMA[kind];
|
|
@@ -16346,6 +16352,9 @@ var VnextSchemaRegistry = class {
|
|
|
16346
16352
|
validatePermissionDiff(value, file) {
|
|
16347
16353
|
return this.validateAuxiliary(value, file, "kxm.permission-diff.v1", this.permissionDiffValidator);
|
|
16348
16354
|
}
|
|
16355
|
+
validateDriveReceipt(value, file) {
|
|
16356
|
+
return this.validateAuxiliary(value, file, "kxm.drive-receipt.v1", this.driveReceiptValidator);
|
|
16357
|
+
}
|
|
16349
16358
|
validateAuxiliary(value, file, identity, validator) {
|
|
16350
16359
|
if (value.schema !== identity) {
|
|
16351
16360
|
return [issue2("schema", "schema_identity_mismatch", file, `expected ${identity}, received ${String(value.schema)}`)];
|
|
@@ -16366,6 +16375,25 @@ function validateRunEvent(value, file) {
|
|
|
16366
16375
|
)]);
|
|
16367
16376
|
}
|
|
16368
16377
|
}
|
|
16378
|
+
function validateDriveReceipt(value, file) {
|
|
16379
|
+
const registry = cachedRunEventRegistry ??= new VnextSchemaRegistry();
|
|
16380
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.schema !== "kxm.drive-receipt.v1") {
|
|
16381
|
+
throw new VnextConfigError([issue2(
|
|
16382
|
+
"schema",
|
|
16383
|
+
"drive_receipt_invalid",
|
|
16384
|
+
file,
|
|
16385
|
+
"expected kxm.drive-receipt.v1"
|
|
16386
|
+
)]);
|
|
16387
|
+
}
|
|
16388
|
+
if (!registry.driveReceiptValidator(value)) {
|
|
16389
|
+
throw new VnextConfigError([issue2(
|
|
16390
|
+
"schema",
|
|
16391
|
+
"drive_receipt_invalid",
|
|
16392
|
+
file,
|
|
16393
|
+
registry.ajv.errorsText(registry.driveReceiptValidator.errors, { separator: "; " })
|
|
16394
|
+
)]);
|
|
16395
|
+
}
|
|
16396
|
+
}
|
|
16369
16397
|
function schemaIssue(file, error) {
|
|
16370
16398
|
const location = error.instancePath || "/";
|
|
16371
16399
|
const suffix = error.params && "additionalProperty" in error.params ? ` (${String(error.params.additionalProperty)})` : "";
|
|
@@ -17817,7 +17845,36 @@ var VnextRuntimeRegistry = class {
|
|
|
17817
17845
|
}
|
|
17818
17846
|
};
|
|
17819
17847
|
var VNEXT_RUN_EVENT_SCHEMA = "kxm.run-event.v1";
|
|
17820
|
-
|
|
17848
|
+
function hashVnextDriveLog(events) {
|
|
17849
|
+
const body = events.map((event) => `${event.eventId}:${event.sequence}`).join("\n");
|
|
17850
|
+
return `sha256:${createHash3("sha256").update(body, "utf8").digest("hex")}`;
|
|
17851
|
+
}
|
|
17852
|
+
function verifyVnextDriveReceipt(receipt, events, foldedStatus, binding) {
|
|
17853
|
+
const reasons = [];
|
|
17854
|
+
if (receipt.runId !== binding.runId) {
|
|
17855
|
+
reasons.push(`runId ${receipt.runId} != binding ${binding.runId}`);
|
|
17856
|
+
}
|
|
17857
|
+
if (receipt.driveId !== binding.driveId) {
|
|
17858
|
+
reasons.push(`driveId ${receipt.driveId} != binding ${binding.driveId}`);
|
|
17859
|
+
}
|
|
17860
|
+
const last = events[events.length - 1];
|
|
17861
|
+
const currentLast = last?.sequence ?? 0;
|
|
17862
|
+
if (receipt.lastSequence !== currentLast) {
|
|
17863
|
+
reasons.push(`lastSequence ${receipt.lastSequence} != log ${currentLast}`);
|
|
17864
|
+
}
|
|
17865
|
+
const prefix = events.filter((event) => event.sequence >= 1 && event.sequence <= receipt.lastSequence);
|
|
17866
|
+
if (hashVnextDriveLog(prefix) !== receipt.logHash) {
|
|
17867
|
+
reasons.push("logHash mismatch");
|
|
17868
|
+
}
|
|
17869
|
+
if (receipt.settlement.status !== foldedStatus) {
|
|
17870
|
+
reasons.push(`settlement.status ${receipt.settlement.status} != folded ${foldedStatus}`);
|
|
17871
|
+
}
|
|
17872
|
+
if (reasons.length === 0) return { verified: true };
|
|
17873
|
+
return { verified: false, divergence: reasons.join("; ") };
|
|
17874
|
+
}
|
|
17875
|
+
var VNEXT_EVENT_STORE_SCHEMA_VERSION = 4;
|
|
17876
|
+
var VNEXT_DRIVE_RECEIPT_SCHEMA = "kxm.drive-receipt.v1";
|
|
17877
|
+
var DRIVE_RECEIPT_MAX_BYTES = 8 * 1024;
|
|
17821
17878
|
var EVENT_STORE_TABLES = {
|
|
17822
17879
|
runs: ["run_id", "project_id", "home_runtime_id", "workflow_id", "prompt_sha256", "status", "config_revision", "memory_revision", "executor_policy_revision", "tool_policy_revision", "created_at", "updated_at"],
|
|
17823
17880
|
events: ["project_id", "run_id", "sequence", "event_id", "event_type", "command_id", "occurred_at", "recorded_at", "monotonic_ns", "config_revision", "memory_revision", "executor_policy_revision", "tool_policy_revision", "payload", "schema", "home_runtime_id"],
|
|
@@ -17896,7 +17953,8 @@ var EVENT_STORE_TABLES = {
|
|
|
17896
17953
|
"outcome",
|
|
17897
17954
|
"settled_event_id",
|
|
17898
17955
|
"content_hash"
|
|
17899
|
-
]
|
|
17956
|
+
],
|
|
17957
|
+
drive_receipts: ["drive_id", "run_id", "project_id", "opened_sequence", "last_sequence", "closed_at", "schema", "receipt"]
|
|
17900
17958
|
};
|
|
17901
17959
|
var EVENT_STORE_SCHEMA = `
|
|
17902
17960
|
CREATE TABLE runs (
|
|
@@ -18038,7 +18096,40 @@ CREATE TABLE gate_evidence (
|
|
|
18038
18096
|
) STRICT;
|
|
18039
18097
|
CREATE UNIQUE INDEX gate_evidence_key ON gate_evidence(run_id, step_id, step_attempt, evidence_key) WHERE evidence_key IS NOT NULL;
|
|
18040
18098
|
CREATE INDEX gate_evidence_run ON gate_evidence(run_id);
|
|
18099
|
+
CREATE TABLE drive_receipts (
|
|
18100
|
+
drive_id TEXT PRIMARY KEY,
|
|
18101
|
+
run_id TEXT NOT NULL,
|
|
18102
|
+
project_id TEXT NOT NULL,
|
|
18103
|
+
opened_sequence INTEGER NOT NULL,
|
|
18104
|
+
last_sequence INTEGER NOT NULL,
|
|
18105
|
+
closed_at TEXT NOT NULL,
|
|
18106
|
+
schema TEXT NOT NULL,
|
|
18107
|
+
receipt TEXT NOT NULL
|
|
18108
|
+
) STRICT;
|
|
18109
|
+
CREATE INDEX drive_receipts_run ON drive_receipts(run_id);
|
|
18041
18110
|
`;
|
|
18111
|
+
var DRIVE_RECEIPTS_DDL = `
|
|
18112
|
+
CREATE TABLE IF NOT EXISTS drive_receipts (
|
|
18113
|
+
drive_id TEXT PRIMARY KEY,
|
|
18114
|
+
run_id TEXT NOT NULL,
|
|
18115
|
+
project_id TEXT NOT NULL,
|
|
18116
|
+
opened_sequence INTEGER NOT NULL,
|
|
18117
|
+
last_sequence INTEGER NOT NULL,
|
|
18118
|
+
closed_at TEXT NOT NULL,
|
|
18119
|
+
schema TEXT NOT NULL,
|
|
18120
|
+
receipt TEXT NOT NULL
|
|
18121
|
+
) STRICT;
|
|
18122
|
+
CREATE INDEX IF NOT EXISTS drive_receipts_run ON drive_receipts(run_id);
|
|
18123
|
+
`;
|
|
18124
|
+
var EVENT_STORE_MIGRATIONS = [
|
|
18125
|
+
{
|
|
18126
|
+
fromVersion: 3,
|
|
18127
|
+
toVersion: 4,
|
|
18128
|
+
migrate(database) {
|
|
18129
|
+
database.exec(DRIVE_RECEIPTS_DDL);
|
|
18130
|
+
}
|
|
18131
|
+
}
|
|
18132
|
+
];
|
|
18042
18133
|
var VnextRunEventStore = class {
|
|
18043
18134
|
path;
|
|
18044
18135
|
database;
|
|
@@ -18047,7 +18138,8 @@ var VnextRunEventStore = class {
|
|
|
18047
18138
|
this.database = openDatabase(this.path, "run event store", {
|
|
18048
18139
|
schema: EVENT_STORE_SCHEMA,
|
|
18049
18140
|
version: VNEXT_EVENT_STORE_SCHEMA_VERSION,
|
|
18050
|
-
tables: EVENT_STORE_TABLES
|
|
18141
|
+
tables: EVENT_STORE_TABLES,
|
|
18142
|
+
migrations: EVENT_STORE_MIGRATIONS
|
|
18051
18143
|
});
|
|
18052
18144
|
}
|
|
18053
18145
|
close() {
|
|
@@ -18204,6 +18296,38 @@ var VnextRunEventStore = class {
|
|
|
18204
18296
|
ON CONFLICT(run_id) DO UPDATE SET last_sequence = excluded.last_sequence, state = excluded.state
|
|
18205
18297
|
`).run(row.runId, row.lastSequence, row.state);
|
|
18206
18298
|
}
|
|
18299
|
+
insertDriveReceipt(receipt) {
|
|
18300
|
+
validateDriveReceipt(receipt, "drive-receipt");
|
|
18301
|
+
const canonical = vnextCanonicalJson(receipt);
|
|
18302
|
+
if (Buffer.byteLength(canonical, "utf8") > DRIVE_RECEIPT_MAX_BYTES) {
|
|
18303
|
+
throw runtimeError("drive_receipt_invalid", receipt.driveId, `drive receipt exceeds ${DRIVE_RECEIPT_MAX_BYTES} bytes`);
|
|
18304
|
+
}
|
|
18305
|
+
const result = this.database.prepare(`
|
|
18306
|
+
INSERT INTO drive_receipts (drive_id, run_id, project_id, opened_sequence, last_sequence, closed_at, schema, receipt)
|
|
18307
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
18308
|
+
ON CONFLICT(drive_id) DO NOTHING
|
|
18309
|
+
`).run(
|
|
18310
|
+
receipt.driveId,
|
|
18311
|
+
receipt.runId,
|
|
18312
|
+
receipt.projectId,
|
|
18313
|
+
receipt.openedSequence,
|
|
18314
|
+
receipt.lastSequence,
|
|
18315
|
+
receipt.closedAt,
|
|
18316
|
+
receipt.schema,
|
|
18317
|
+
canonical
|
|
18318
|
+
);
|
|
18319
|
+
return { inserted: Number(result.changes) === 1 };
|
|
18320
|
+
}
|
|
18321
|
+
driveReceipt(driveId) {
|
|
18322
|
+
const row = this.database.prepare("SELECT receipt FROM drive_receipts WHERE drive_id = ?").get(driveId);
|
|
18323
|
+
return row ? parseDriveReceipt(row.receipt) : void 0;
|
|
18324
|
+
}
|
|
18325
|
+
driveReceiptsForRun(runId, limit = 20) {
|
|
18326
|
+
const rows = this.database.prepare(`
|
|
18327
|
+
SELECT receipt FROM drive_receipts WHERE run_id = ? ORDER BY closed_at DESC, drive_id DESC LIMIT ?
|
|
18328
|
+
`).all(runId, limit);
|
|
18329
|
+
return rows.map((row) => parseDriveReceipt(row.receipt));
|
|
18330
|
+
}
|
|
18207
18331
|
insertCapability(row) {
|
|
18208
18332
|
this.database.prepare(`
|
|
18209
18333
|
INSERT INTO attempt_capabilities (attempt_id, run_id, assignment_id, step_id, step_attempt, producer_id, capability_hash, state)
|
|
@@ -18438,6 +18562,11 @@ var VnextRunEventStore = class {
|
|
|
18438
18562
|
}
|
|
18439
18563
|
}
|
|
18440
18564
|
};
|
|
18565
|
+
function parseDriveReceipt(raw) {
|
|
18566
|
+
const parsed = JSON.parse(raw);
|
|
18567
|
+
validateDriveReceipt(parsed, "drive-receipt");
|
|
18568
|
+
return parsed;
|
|
18569
|
+
}
|
|
18441
18570
|
function runFromRow(row) {
|
|
18442
18571
|
if (typeof row.memory_revision !== "string" || row.memory_revision.length === 0) {
|
|
18443
18572
|
throw runtimeError("runtime_schema_shape_invalid", row.run_id, "memory_revision is required");
|
|
@@ -18833,6 +18962,16 @@ var ATTEMPT_EDGES = {
|
|
|
18833
18962
|
executing: /* @__PURE__ */ new Set(["settling"]),
|
|
18834
18963
|
settling: /* @__PURE__ */ new Set(["terminal"])
|
|
18835
18964
|
};
|
|
18965
|
+
function effectiveRunDurationBudget(planLimits, projectLimits) {
|
|
18966
|
+
const workflow = planLimits?.maxRunDurationMs;
|
|
18967
|
+
const project = projectLimits?.maxRunDurationMs;
|
|
18968
|
+
if (workflow === void 0 && project === void 0) return void 0;
|
|
18969
|
+
if (workflow !== void 0 && project !== void 0) {
|
|
18970
|
+
return { budgetMs: Math.min(workflow, project), source: "both" };
|
|
18971
|
+
}
|
|
18972
|
+
if (workflow !== void 0) return { budgetMs: workflow, source: "workflow" };
|
|
18973
|
+
return { budgetMs: project, source: "project" };
|
|
18974
|
+
}
|
|
18836
18975
|
function emptyPanel() {
|
|
18837
18976
|
return { order: [], assignments: /* @__PURE__ */ Object.create(null) };
|
|
18838
18977
|
}
|
|
@@ -19064,7 +19203,10 @@ function foldVnextRunState(run, plan, events, options = {}) {
|
|
|
19064
19203
|
foldRunStatus(state, plan, event);
|
|
19065
19204
|
break;
|
|
19066
19205
|
case "run.cancel_requested":
|
|
19067
|
-
foldCancelRequested(state, event);
|
|
19206
|
+
foldCancelRequested(state, event, plan, options);
|
|
19207
|
+
break;
|
|
19208
|
+
case "run.drive_opened":
|
|
19209
|
+
foldDriveOpened(state, event);
|
|
19068
19210
|
break;
|
|
19069
19211
|
case "step.entered":
|
|
19070
19212
|
foldStepEntered(state, plan, event);
|
|
@@ -19190,6 +19332,9 @@ function foldRunStatus(state, plan, event) {
|
|
|
19190
19332
|
if ((status === "running" || status === "cancelling") && !plan) {
|
|
19191
19333
|
throw runtimeError("run_plan_missing", state.runId, `status ${status} requires a pinned run plan`);
|
|
19192
19334
|
}
|
|
19335
|
+
if (status === "running" && state.runningSince === void 0) {
|
|
19336
|
+
state.runningSince = event.occurredAt;
|
|
19337
|
+
}
|
|
19193
19338
|
if (TERMINAL_RUN.has(status)) {
|
|
19194
19339
|
assertTerminalRunStatus(state, plan, status, event);
|
|
19195
19340
|
if (typeof event.payload.reason === "string") state.terminalReason = event.payload.reason;
|
|
@@ -19277,10 +19422,47 @@ function isProvenFailure(state, plan) {
|
|
|
19277
19422
|
const used = state.stepAttempts[stepId] ?? 0;
|
|
19278
19423
|
return Boolean(step && used >= step.maxAttempts);
|
|
19279
19424
|
}
|
|
19280
|
-
function
|
|
19425
|
+
function foldDriveOpened(state, event) {
|
|
19426
|
+
if (state.status !== "running" && state.status !== "blocked_uncertain") {
|
|
19427
|
+
throw runtimeError("run_events_illegal", state.runId, "run.drive_opened is only legal while running or blocked_uncertain");
|
|
19428
|
+
}
|
|
19429
|
+
const driveId = stringPayload(event, "driveId");
|
|
19430
|
+
const mode = stringPayload(event, "mode");
|
|
19431
|
+
if (mode !== "simulated" && mode !== "live") {
|
|
19432
|
+
throw runtimeError("run_events_illegal", state.runId, "run.drive_opened mode must be simulated or live");
|
|
19433
|
+
}
|
|
19434
|
+
state.drive = { driveId, mode, openedSequence: event.sequence };
|
|
19435
|
+
}
|
|
19436
|
+
function foldCancelRequested(state, event, plan, options) {
|
|
19281
19437
|
if (state.cancelRequested || TERMINAL_RUN.has(state.status)) {
|
|
19282
19438
|
throw runtimeError("run_events_illegal", state.runId, "duplicate run.cancel_requested is not legal");
|
|
19283
19439
|
}
|
|
19440
|
+
if (event.payload.reason === "budget_run_duration") {
|
|
19441
|
+
const budget = event.payload.budget;
|
|
19442
|
+
if (!budget || typeof budget !== "object" || Array.isArray(budget)) {
|
|
19443
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration requires a budget payload");
|
|
19444
|
+
}
|
|
19445
|
+
const body = budget;
|
|
19446
|
+
const budgetMs = body.budgetMs;
|
|
19447
|
+
const elapsedMs = body.elapsedMs;
|
|
19448
|
+
const source = body.source;
|
|
19449
|
+
if (typeof budgetMs !== "number" || !Number.isInteger(budgetMs) || budgetMs < 0) {
|
|
19450
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs is invalid");
|
|
19451
|
+
}
|
|
19452
|
+
if (typeof elapsedMs !== "number" || !Number.isInteger(elapsedMs) || elapsedMs < 0) {
|
|
19453
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is invalid");
|
|
19454
|
+
}
|
|
19455
|
+
if (source !== "workflow" && source !== "project" && source !== "both") {
|
|
19456
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration source is invalid");
|
|
19457
|
+
}
|
|
19458
|
+
const expected = effectiveRunDurationBudget(plan?.limits, options.projectLimits);
|
|
19459
|
+
if (!expected || expected.budgetMs !== budgetMs || expected.source !== source) {
|
|
19460
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration budgetMs does not match the plan");
|
|
19461
|
+
}
|
|
19462
|
+
if (elapsedMs < budgetMs) {
|
|
19463
|
+
throw runtimeError("run_events_illegal", state.runId, "budget_run_duration elapsedMs is below budgetMs");
|
|
19464
|
+
}
|
|
19465
|
+
}
|
|
19284
19466
|
state.cancelRequested = true;
|
|
19285
19467
|
if (event.commandId) state.cancelCommandId = event.commandId;
|
|
19286
19468
|
}
|
|
@@ -19925,7 +20107,9 @@ function freezeState(state) {
|
|
|
19925
20107
|
transitionsUsed: state.transitionsUsed,
|
|
19926
20108
|
cancelRequested: state.cancelRequested,
|
|
19927
20109
|
...state.terminalReason !== void 0 ? { terminalReason: state.terminalReason } : {},
|
|
19928
|
-
...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {}
|
|
20110
|
+
...state.terminalReason !== void 0 && state.status === "failed" ? { failureReason: state.terminalReason } : {},
|
|
20111
|
+
...state.drive !== void 0 ? { drive: Object.freeze({ ...state.drive }) } : {},
|
|
20112
|
+
...state.runningSince !== void 0 ? { runningSince: state.runningSince } : {}
|
|
19929
20113
|
};
|
|
19930
20114
|
return Object.freeze(frozen);
|
|
19931
20115
|
}
|
|
@@ -21815,7 +21999,8 @@ function foldStoredVnextRun(context, run) {
|
|
|
21815
21999
|
const step = envelope.plan.steps[stepId];
|
|
21816
22000
|
if (!step || step.kind !== "gate") return void 0;
|
|
21817
22001
|
return envelope.gates.definitions[step.gate]?.kind;
|
|
21818
|
-
}
|
|
22002
|
+
},
|
|
22003
|
+
projectLimits: envelope.projectLimits
|
|
21819
22004
|
} : {});
|
|
21820
22005
|
if (envelope) verifyVnextGateEvidence(context.eventStore, run, envelope, events, state);
|
|
21821
22006
|
assertStoredProjection(context, run);
|
|
@@ -21922,10 +22107,17 @@ function cancelVnextRun(context, runId, options = {}) {
|
|
|
21922
22107
|
};
|
|
21923
22108
|
const cancelReason = options.reason ?? "operator_cancel";
|
|
21924
22109
|
const activeAttempt = Boolean(folded.currentStep?.attemptId) || vnextFoldPanelAttemptIds(folded.currentStep).length > 0;
|
|
21925
|
-
|
|
22110
|
+
const cancelPayload = {
|
|
21926
22111
|
actor: { kind: "runtime", id: context.homeRuntimeId },
|
|
21927
22112
|
reason: cancelReason
|
|
21928
|
-
}
|
|
22113
|
+
};
|
|
22114
|
+
if (cancelReason === "budget_run_duration") {
|
|
22115
|
+
if (!options.budget) {
|
|
22116
|
+
throw runtimeError("run_events_illegal", runId, "budget_run_duration requires a budget payload");
|
|
22117
|
+
}
|
|
22118
|
+
cancelPayload.budget = options.budget;
|
|
22119
|
+
}
|
|
22120
|
+
push("run.cancel_requested", cancelPayload);
|
|
21929
22121
|
let status = "cancelled";
|
|
21930
22122
|
if (folded.status === "running" && activeAttempt) {
|
|
21931
22123
|
status = "cancelling";
|
|
@@ -23262,7 +23454,7 @@ function recordGateNoStartInTransaction(context, run, envelope, stepId, observat
|
|
|
23262
23454
|
const cancelled = observation.stopCause === "cancel";
|
|
23263
23455
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23264
23456
|
const seq = sequencer(context, run, now);
|
|
23265
|
-
|
|
23457
|
+
const cancelReason = cancelled ? ensureOperatorCancel(seq, ids.state, run, context) : void 0;
|
|
23266
23458
|
const observationId = newVnextObservationId();
|
|
23267
23459
|
const observed = seq.push("effect.observed", {
|
|
23268
23460
|
effect: { id: ids.effectId, policy: { class: ids.gateKind === "artifacts-exist" ? "read-only" : "unknown", sharedMutable: false } },
|
|
@@ -23294,7 +23486,7 @@ function recordGateNoStartInTransaction(context, run, envelope, stepId, observat
|
|
|
23294
23486
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
23295
23487
|
seq.push("assignment.terminal", { assignmentId: ids.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
23296
23488
|
seq.push("step.status_changed", { stepId, status: "cancelled", previousStatus: "running" });
|
|
23297
|
-
seq.push("run.status_changed", { status: "cancelled", reason: "operator_cancel", stepId });
|
|
23489
|
+
seq.push("run.status_changed", { status: "cancelled", reason: cancelReason ?? "operator_cancel", stepId });
|
|
23298
23490
|
} else {
|
|
23299
23491
|
seq.push("assignment.result_recorded", { assignmentId: ids.assignmentId, resultClass: "producer_rejected", status: "result_recorded" });
|
|
23300
23492
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
@@ -23348,7 +23540,7 @@ function recordGateCancelObservedInTransaction(context, run, envelope, stepId, o
|
|
|
23348
23540
|
}
|
|
23349
23541
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23350
23542
|
const seq = sequencer(context, run, now);
|
|
23351
|
-
ensureOperatorCancel(seq, ids.state, run);
|
|
23543
|
+
const cancelReason = ensureOperatorCancel(seq, ids.state, run, context);
|
|
23352
23544
|
if (ids.gateKind === "artifacts-exist") {
|
|
23353
23545
|
seq.push("assignment.executing", { assignmentId: ids.assignmentId, stepId, stepAttempt: ids.stepAttempt, attemptId: ids.attemptId, status: "executing" });
|
|
23354
23546
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, assignmentId: ids.assignmentId, stepId, stepAttempt: ids.stepAttempt, status: "executing" });
|
|
@@ -23391,7 +23583,7 @@ function recordGateCancelObservedInTransaction(context, run, envelope, stepId, o
|
|
|
23391
23583
|
seq.push("attempt.status_changed", { attemptId: ids.attemptId, status: "terminal" });
|
|
23392
23584
|
seq.push("assignment.terminal", { assignmentId: ids.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
23393
23585
|
seq.push("step.status_changed", { stepId, status: "cancelled", previousStatus: "running" });
|
|
23394
|
-
seq.push("run.status_changed", { status: "cancelled", reason:
|
|
23586
|
+
seq.push("run.status_changed", { status: "cancelled", reason: cancelReason, stepId });
|
|
23395
23587
|
for (const event of seq.events) context.eventStore.appendEvent(event);
|
|
23396
23588
|
context.eventStore.insertGateObservation(observationRow);
|
|
23397
23589
|
context.eventStore.settleCapability(ids.attemptId, "settled");
|
|
@@ -23476,8 +23668,8 @@ function requireIntent(context, run, envelope, stepId) {
|
|
|
23476
23668
|
state: folded
|
|
23477
23669
|
};
|
|
23478
23670
|
}
|
|
23479
|
-
function ensureOperatorCancel(seq, state, run) {
|
|
23480
|
-
if (state.status === "cancelling") return;
|
|
23671
|
+
function ensureOperatorCancel(seq, state, run, context) {
|
|
23672
|
+
if (state.status === "cancelling") return cancelReasonFromLog(context, run.runId);
|
|
23481
23673
|
if (state.status !== "running") {
|
|
23482
23674
|
throw runtimeError("run_events_illegal", run.runId, "cancelled gate proof requires a running or cancelling run");
|
|
23483
23675
|
}
|
|
@@ -23486,6 +23678,17 @@ function ensureOperatorCancel(seq, state, run) {
|
|
|
23486
23678
|
reason: "operator_cancel"
|
|
23487
23679
|
});
|
|
23488
23680
|
seq.push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
|
|
23681
|
+
return "operator_cancel";
|
|
23682
|
+
}
|
|
23683
|
+
function cancelReasonFromLog(context, runId) {
|
|
23684
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
23685
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
23686
|
+
const event = events[index];
|
|
23687
|
+
if (event.eventType === "run.cancel_requested" && typeof event.payload.reason === "string") {
|
|
23688
|
+
return event.payload.reason;
|
|
23689
|
+
}
|
|
23690
|
+
}
|
|
23691
|
+
return "operator_cancel";
|
|
23489
23692
|
}
|
|
23490
23693
|
function sequencer(context, run, now) {
|
|
23491
23694
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
@@ -23824,6 +24027,8 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
|
|
|
23824
24027
|
}
|
|
23825
24028
|
let latest = { state: foldStoredVnextRun(context, requireRun(context, runId)) };
|
|
23826
24029
|
while (latest.state.status === "running") {
|
|
24030
|
+
const overBudget = cancelRunDurationIfExceeded(context, runId);
|
|
24031
|
+
if (overBudget) return overBudget;
|
|
23827
24032
|
const before = context.eventStore.runState(runId)?.lastSequence;
|
|
23828
24033
|
latest = await stepLocked(context, runId, producer, token);
|
|
23829
24034
|
if (latest.handoff) return latest;
|
|
@@ -23839,6 +24044,111 @@ async function driveAdmitted(context, runId, producer, token, options = {}) {
|
|
|
23839
24044
|
function newDriveId(runId, token, runtimeId, monotonicNs) {
|
|
23840
24045
|
return `drv_${createHash10("sha256").update(`${runId}\0${token}\0${runtimeId}\0${monotonicNs}`, "utf8").digest("hex").slice(0, 24)}`;
|
|
23841
24046
|
}
|
|
24047
|
+
function driveErrorSummary(error) {
|
|
24048
|
+
const raw = error instanceof VnextConfigError ? error.issues[0]?.code : void 0;
|
|
24049
|
+
const className = raw && /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/.test(raw) ? raw : "drive-failure";
|
|
24050
|
+
return { class: className, component: "vnext-engine", retryable: false };
|
|
24051
|
+
}
|
|
24052
|
+
function receiptHandoff(handoff) {
|
|
24053
|
+
return {
|
|
24054
|
+
reason: handoff.reason,
|
|
24055
|
+
detail: handoff.detail,
|
|
24056
|
+
...handoff.field !== void 0 ? { field: handoff.field } : {},
|
|
24057
|
+
...handoff.stepId !== void 0 ? { stepId: handoff.stepId } : {}
|
|
24058
|
+
};
|
|
24059
|
+
}
|
|
24060
|
+
function appendDriveOpened(context, runId, driveId, mode) {
|
|
24061
|
+
return context.eventStore.transaction(() => {
|
|
24062
|
+
const run = requireRun(context, runId);
|
|
24063
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
24064
|
+
const sequence = context.eventStore.nextSequence(runId);
|
|
24065
|
+
const event = {
|
|
24066
|
+
...vnextEventBase(context, run, now, vnextMonotonicNs()),
|
|
24067
|
+
eventId: newVnextEventId(),
|
|
24068
|
+
eventType: "run.drive_opened",
|
|
24069
|
+
sequence,
|
|
24070
|
+
payload: { driveId, mode }
|
|
24071
|
+
};
|
|
24072
|
+
context.eventStore.appendEvent(event);
|
|
24073
|
+
const next = foldStoredVnextRun(context, run);
|
|
24074
|
+
persistVnextRunState(context, runId, next, sequence);
|
|
24075
|
+
return { sequence, occurredAt: now };
|
|
24076
|
+
});
|
|
24077
|
+
}
|
|
24078
|
+
function recordDriveReceipt(context, session, closeInfo) {
|
|
24079
|
+
if (isVnextRuntimeContextClosed(context)) return;
|
|
24080
|
+
try {
|
|
24081
|
+
const run = context.eventStore.run(session.runId);
|
|
24082
|
+
if (!run) return;
|
|
24083
|
+
const state = foldStoredVnextRun(context, run);
|
|
24084
|
+
const events = context.eventStore.events(session.runId, 0, 1e6);
|
|
24085
|
+
if (events.length === 0) return;
|
|
24086
|
+
const opened = state.drive;
|
|
24087
|
+
const openedEvent = opened ? events.find((event) => event.sequence === opened.openedSequence && event.eventType === "run.drive_opened") : events.find((event) => event.eventType === "run.drive_opened" && event.payload.driveId === session.driveId);
|
|
24088
|
+
const last = events[events.length - 1];
|
|
24089
|
+
const openedSequence = opened?.openedSequence ?? openedEvent?.sequence;
|
|
24090
|
+
if (openedSequence === void 0) return;
|
|
24091
|
+
const closedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24092
|
+
const receipt = {
|
|
24093
|
+
schema: VNEXT_DRIVE_RECEIPT_SCHEMA,
|
|
24094
|
+
driveId: session.driveId,
|
|
24095
|
+
runId: session.runId,
|
|
24096
|
+
projectId: run.projectId,
|
|
24097
|
+
homeRuntimeId: session.homeRuntimeId,
|
|
24098
|
+
mode: session.mode,
|
|
24099
|
+
openedAt: openedEvent?.occurredAt ?? session.openedAt,
|
|
24100
|
+
openedSequence,
|
|
24101
|
+
closedAt,
|
|
24102
|
+
lastSequence: last.sequence,
|
|
24103
|
+
logHash: hashVnextDriveLog(events.filter((event) => event.sequence >= 1 && event.sequence <= last.sequence)),
|
|
24104
|
+
settlement: {
|
|
24105
|
+
kind: closeInfo.kind,
|
|
24106
|
+
status: state.status,
|
|
24107
|
+
reason: closeInfo.reason ?? state.terminalReason ?? "",
|
|
24108
|
+
...closeInfo.kind === "handoff" && closeInfo.handoff ? { handoff: receiptHandoff(closeInfo.handoff) } : {},
|
|
24109
|
+
...closeInfo.error !== void 0 ? { error: closeInfo.error } : {}
|
|
24110
|
+
},
|
|
24111
|
+
budget: driveReceiptBudget(context, run.runId, state, events, closedAt),
|
|
24112
|
+
producer: { id: closeInfo.producerId, closed: closeInfo.producerClosed }
|
|
24113
|
+
};
|
|
24114
|
+
context.eventStore.insertDriveReceipt(receipt);
|
|
24115
|
+
} catch {
|
|
24116
|
+
}
|
|
24117
|
+
}
|
|
24118
|
+
function vnextDrivePollProjection(context, runId, state) {
|
|
24119
|
+
if (!state.drive) return void 0;
|
|
24120
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
24121
|
+
const openedEvent = events.find((event) => event.sequence === state.drive.openedSequence);
|
|
24122
|
+
let receipt = null;
|
|
24123
|
+
let unreadable = false;
|
|
24124
|
+
try {
|
|
24125
|
+
receipt = context.eventStore.driveReceipt(state.drive.driveId) ?? null;
|
|
24126
|
+
} catch {
|
|
24127
|
+
unreadable = true;
|
|
24128
|
+
}
|
|
24129
|
+
const projection = {
|
|
24130
|
+
driveId: state.drive.driveId,
|
|
24131
|
+
mode: state.drive.mode,
|
|
24132
|
+
openedAt: openedEvent?.occurredAt ?? "",
|
|
24133
|
+
receipt: unreadable ? null : receipt,
|
|
24134
|
+
verified: false
|
|
24135
|
+
};
|
|
24136
|
+
if (unreadable) {
|
|
24137
|
+
projection.divergence = "receipt unreadable";
|
|
24138
|
+
return projection;
|
|
24139
|
+
}
|
|
24140
|
+
if (!receipt) {
|
|
24141
|
+
projection.divergence = "no receipt";
|
|
24142
|
+
return projection;
|
|
24143
|
+
}
|
|
24144
|
+
const checked = verifyVnextDriveReceipt(receipt, events, state.status, {
|
|
24145
|
+
runId,
|
|
24146
|
+
driveId: state.drive.driveId
|
|
24147
|
+
});
|
|
24148
|
+
projection.verified = checked.verified;
|
|
24149
|
+
if (checked.divergence !== void 0) projection.divergence = checked.divergence;
|
|
24150
|
+
return projection;
|
|
24151
|
+
}
|
|
23842
24152
|
function inflightAttemptId(state) {
|
|
23843
24153
|
const leftover = unreconciledPanelAttemptId(state);
|
|
23844
24154
|
if (leftover) return leftover;
|
|
@@ -23986,25 +24296,50 @@ var VnextRunScheduler = class _VnextRunScheduler {
|
|
|
23986
24296
|
throw error;
|
|
23987
24297
|
}
|
|
23988
24298
|
const driveId = newDriveId(runId, token, this.context.homeRuntimeId, vnextMonotonicNs());
|
|
24299
|
+
const openedMeta = appendDriveOpened(this.context, runId, driveId, options.mode);
|
|
24300
|
+
const startedState = foldStoredVnextRun(this.context, requireRun(this.context, runId));
|
|
24301
|
+
const deadlineAt = driveDeadlineAt(this.context, runId, startedState);
|
|
23989
24302
|
const session = {
|
|
23990
24303
|
driveId,
|
|
23991
24304
|
runId,
|
|
23992
24305
|
token,
|
|
23993
24306
|
homeRuntimeId: this.context.homeRuntimeId,
|
|
23994
24307
|
mode: options.mode,
|
|
23995
|
-
openedAt:
|
|
24308
|
+
openedAt: openedMeta.occurredAt,
|
|
24309
|
+
producerId: producer.id,
|
|
23996
24310
|
controller: new AbortController(),
|
|
23997
|
-
settled
|
|
24311
|
+
settled,
|
|
24312
|
+
...deadlineAt !== void 0 ? { deadlineAt } : {}
|
|
23998
24313
|
};
|
|
23999
24314
|
attachVnextDriveSession(this.context.eventStore.path, runId, token, session);
|
|
24000
24315
|
opened = true;
|
|
24001
24316
|
resolveOpen({ driveId, settled });
|
|
24002
24317
|
try {
|
|
24003
24318
|
const result = await driveAdmitted(this.context, runId, producer, token, driveOptions);
|
|
24319
|
+
const kind = result.handoff ? "handoff" : isTerminalRunStatus(result.state.status) ? "terminal" : "unsettled";
|
|
24320
|
+
recordDriveReceipt(this.context, session, {
|
|
24321
|
+
kind,
|
|
24322
|
+
...result.state.terminalReason !== void 0 ? { reason: result.state.terminalReason } : result.handoff ? { reason: result.handoff.reason } : {},
|
|
24323
|
+
...result.handoff ? { handoff: result.handoff } : {},
|
|
24324
|
+
producerId: producer.id,
|
|
24325
|
+
producerClosed: false
|
|
24326
|
+
});
|
|
24004
24327
|
pending.resolve(result);
|
|
24005
24328
|
return result;
|
|
24006
24329
|
} catch (error) {
|
|
24007
24330
|
recordBareDriveFailure(this.context, runId);
|
|
24331
|
+
let foldedStatus;
|
|
24332
|
+
try {
|
|
24333
|
+
foldedStatus = foldStoredVnextRun(this.context, requireRun(this.context, runId)).status;
|
|
24334
|
+
} catch {
|
|
24335
|
+
foldedStatus = void 0;
|
|
24336
|
+
}
|
|
24337
|
+
recordDriveReceipt(this.context, session, {
|
|
24338
|
+
kind: foldedStatus !== void 0 && isTerminalRunStatus(foldedStatus) ? "terminal" : "unsettled",
|
|
24339
|
+
error: driveErrorSummary(error),
|
|
24340
|
+
producerId: producer.id,
|
|
24341
|
+
producerClosed: false
|
|
24342
|
+
});
|
|
24008
24343
|
pending.reject(error);
|
|
24009
24344
|
throw error;
|
|
24010
24345
|
}
|
|
@@ -24053,11 +24388,13 @@ async function stepLocked(context, runId, producer, token) {
|
|
|
24053
24388
|
throw runtimeError("run_events_illegal", prepared.run.runId, "prepared gate is not artifacts-exist");
|
|
24054
24389
|
}
|
|
24055
24390
|
registerVnextAttemptController(context.eventStore.path, runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
24391
|
+
const clearBudget = armRunDurationBudgetTimer(context, runId);
|
|
24056
24392
|
try {
|
|
24057
24393
|
const observation = evaluateArtifactsGate(context, definition);
|
|
24058
24394
|
vnextGateDispatchSeams.afterEvaluate?.(prepared);
|
|
24059
24395
|
return context.eventStore.transaction(() => settlePreparedGate(context, prepared, observation));
|
|
24060
24396
|
} finally {
|
|
24397
|
+
clearBudget();
|
|
24061
24398
|
unregisterVnextAttemptController(context.eventStore.path, runId, prepared.attemptId);
|
|
24062
24399
|
}
|
|
24063
24400
|
}
|
|
@@ -24088,6 +24425,7 @@ async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
|
24088
24425
|
});
|
|
24089
24426
|
armVnextGateHold(storePath, prepared.run.runId, token, prepared.attemptId, () => observer.requestStop("cancel"));
|
|
24090
24427
|
registerVnextAttemptController(storePath, prepared.run.runId, { attemptId: prepared.attemptId, controller: prepared.controller });
|
|
24428
|
+
const clearBudget = armRunDurationBudgetTimer(context, prepared.run.runId);
|
|
24091
24429
|
const unhookClose = registerVnextRuntimeCloseHook(context, () => {
|
|
24092
24430
|
try {
|
|
24093
24431
|
markVnextGateHoldUnsettled(storePath, prepared.run.runId, token, prepared.attemptId);
|
|
@@ -24128,6 +24466,7 @@ async function runPreparedCommandGate(context, prepared, definition, token) {
|
|
|
24128
24466
|
return commitCommandUncertainty(context, prepared, token, outcome.reason, outcome.observation);
|
|
24129
24467
|
} finally {
|
|
24130
24468
|
unhookClose();
|
|
24469
|
+
clearBudget();
|
|
24131
24470
|
unregisterVnextAttemptController(storePath, prepared.run.runId, prepared.attemptId);
|
|
24132
24471
|
}
|
|
24133
24472
|
}
|
|
@@ -24753,8 +25092,10 @@ async function drivePanel(context, panel, producer) {
|
|
|
24753
25092
|
const pending = /* @__PURE__ */ new Map();
|
|
24754
25093
|
let stopBirths = false;
|
|
24755
25094
|
let settlementFailed = false;
|
|
25095
|
+
const budgetClears = /* @__PURE__ */ new Map();
|
|
24756
25096
|
const register = (member) => {
|
|
24757
25097
|
registerVnextAttemptController(context.eventStore.path, runId, { attemptId: member.attemptId, controller: member.controller });
|
|
25098
|
+
budgetClears.set(member.attemptId, armRunDurationBudgetTimer(context, runId));
|
|
24758
25099
|
owned.set(member.attemptId, member);
|
|
24759
25100
|
};
|
|
24760
25101
|
const abortOwned = () => {
|
|
@@ -24937,6 +25278,7 @@ async function drivePanel(context, panel, producer) {
|
|
|
24937
25278
|
throw error;
|
|
24938
25279
|
} finally {
|
|
24939
25280
|
for (const attemptId of owned.keys()) {
|
|
25281
|
+
budgetClears.get(attemptId)?.();
|
|
24940
25282
|
unregisterVnextAttemptController(context.eventStore.path, runId, attemptId);
|
|
24941
25283
|
}
|
|
24942
25284
|
}
|
|
@@ -25116,7 +25458,7 @@ function joinPanel(context, panel) {
|
|
|
25116
25458
|
};
|
|
25117
25459
|
const cancelRun = () => {
|
|
25118
25460
|
push("step.status_changed", { stepId: panel.stepId, status: "cancelled", previousStatus: "running" });
|
|
25119
|
-
push("run.status_changed", { status: "cancelled", reason:
|
|
25461
|
+
push("run.status_changed", { status: "cancelled", reason: cancelReasonFromLog2(context, run.runId) });
|
|
25120
25462
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
25121
25463
|
const next2 = foldStoredVnextRun(context, run);
|
|
25122
25464
|
persistVnextRunState(context, run.runId, next2, events[events.length - 1].sequence);
|
|
@@ -25223,16 +25565,124 @@ function transitionBudgetFailure(plan, state, fromStepId, outcome) {
|
|
|
25223
25565
|
}
|
|
25224
25566
|
return void 0;
|
|
25225
25567
|
}
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
|
|
25568
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
25569
|
+
function elapsedMsBetween(fromIso, toIso) {
|
|
25570
|
+
const from = Date.parse(fromIso);
|
|
25571
|
+
const to = Date.parse(toIso);
|
|
25572
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) return 0;
|
|
25573
|
+
return Math.max(0, Math.trunc(to - from));
|
|
25574
|
+
}
|
|
25575
|
+
function declaredRunDurationBudget(context, runId) {
|
|
25576
|
+
const run = context.eventStore.run(runId);
|
|
25577
|
+
if (!run) return void 0;
|
|
25578
|
+
try {
|
|
25579
|
+
const envelope = loadVnextRunPlanEnvelope(context.eventStore, run);
|
|
25580
|
+
return effectiveRunDurationBudget(envelope.plan.limits, envelope.projectLimits);
|
|
25581
|
+
} catch {
|
|
25582
|
+
return void 0;
|
|
25229
25583
|
}
|
|
25584
|
+
}
|
|
25585
|
+
function runDurationOverrunPayload(context, runId, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
25586
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25587
|
+
if (!declared) return void 0;
|
|
25588
|
+
const run = context.eventStore.run(runId);
|
|
25589
|
+
if (!run) return void 0;
|
|
25590
|
+
const state = foldStoredVnextRun(context, run);
|
|
25591
|
+
if (!state.runningSince) return void 0;
|
|
25592
|
+
if (isTerminalRunStatus(state.status) || state.status === "cancelling") return void 0;
|
|
25593
|
+
const elapsedMs = elapsedMsBetween(state.runningSince, nowIso);
|
|
25594
|
+
if (elapsedMs < declared.budgetMs) return void 0;
|
|
25595
|
+
return { budgetMs: declared.budgetMs, elapsedMs, source: declared.source };
|
|
25596
|
+
}
|
|
25597
|
+
function cancelRunDurationIfExceeded(context, runId) {
|
|
25598
|
+
if (isVnextRuntimeContextClosed(context)) return void 0;
|
|
25599
|
+
const payload = runDurationOverrunPayload(context, runId);
|
|
25600
|
+
if (!payload) return void 0;
|
|
25601
|
+
cancelVnextRun(context, runId, { reason: "budget_run_duration", budget: payload });
|
|
25602
|
+
return { state: foldStoredVnextRun(context, requireRun(context, runId)) };
|
|
25603
|
+
}
|
|
25604
|
+
function driveDeadlineAt(context, runId, state) {
|
|
25605
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25606
|
+
if (!declared || !state.runningSince) return void 0;
|
|
25607
|
+
const start = Date.parse(state.runningSince);
|
|
25608
|
+
if (!Number.isFinite(start)) return void 0;
|
|
25609
|
+
return new Date(start + declared.budgetMs).toISOString();
|
|
25610
|
+
}
|
|
25611
|
+
function driveReceiptBudget(context, runId, state, events, closedAt) {
|
|
25612
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25613
|
+
if (!declared) return null;
|
|
25614
|
+
const elapsedMs = state.runningSince ? elapsedMsBetween(state.runningSince, closedAt) : 0;
|
|
25615
|
+
const overrun = events.some((event) => event.eventType === "run.cancel_requested" && event.payload.reason === "budget_run_duration");
|
|
25616
|
+
return {
|
|
25617
|
+
budgetMs: declared.budgetMs,
|
|
25618
|
+
source: declared.source,
|
|
25619
|
+
elapsedMs,
|
|
25620
|
+
overrun
|
|
25621
|
+
};
|
|
25622
|
+
}
|
|
25623
|
+
function cancelReasonFromLog2(context, runId) {
|
|
25624
|
+
const events = context.eventStore.events(runId, 0, 1e6);
|
|
25625
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
25626
|
+
const event = events[index];
|
|
25627
|
+
if (event.eventType === "run.cancel_requested" && typeof event.payload.reason === "string") {
|
|
25628
|
+
return event.payload.reason;
|
|
25629
|
+
}
|
|
25630
|
+
}
|
|
25631
|
+
return "operator_cancel";
|
|
25632
|
+
}
|
|
25633
|
+
function armRunDurationBudgetTimer(context, runId) {
|
|
25634
|
+
const declared = declaredRunDurationBudget(context, runId);
|
|
25635
|
+
if (!declared) return () => void 0;
|
|
25636
|
+
const run = context.eventStore.run(runId);
|
|
25637
|
+
if (!run) return () => void 0;
|
|
25638
|
+
const state = foldStoredVnextRun(context, run);
|
|
25639
|
+
if (!state.runningSince) return () => void 0;
|
|
25640
|
+
const startMs = Date.parse(state.runningSince);
|
|
25641
|
+
if (!Number.isFinite(startMs)) return () => void 0;
|
|
25642
|
+
const deadlineMs = startMs + declared.budgetMs;
|
|
25643
|
+
let timer;
|
|
25644
|
+
let cleared = false;
|
|
25645
|
+
const fire = () => {
|
|
25646
|
+
timer = void 0;
|
|
25647
|
+
if (cleared || isVnextRuntimeContextClosed(context)) return;
|
|
25648
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
25649
|
+
const elapsedMs = elapsedMsBetween(state.runningSince, nowIso);
|
|
25650
|
+
if (elapsedMs < declared.budgetMs) {
|
|
25651
|
+
arm(declared.budgetMs - elapsedMs);
|
|
25652
|
+
return;
|
|
25653
|
+
}
|
|
25654
|
+
try {
|
|
25655
|
+
cancelVnextRun(context, runId, {
|
|
25656
|
+
reason: "budget_run_duration",
|
|
25657
|
+
budget: { budgetMs: declared.budgetMs, elapsedMs, source: declared.source }
|
|
25658
|
+
});
|
|
25659
|
+
} catch {
|
|
25660
|
+
}
|
|
25661
|
+
};
|
|
25662
|
+
const arm = (remaining) => {
|
|
25663
|
+
if (cleared) return;
|
|
25664
|
+
const delay = Math.min(Math.max(0, remaining), MAX_TIMER_DELAY_MS);
|
|
25665
|
+
timer = setTimeout(fire, delay);
|
|
25666
|
+
timer.unref();
|
|
25667
|
+
};
|
|
25668
|
+
const unhook = registerVnextRuntimeCloseHook(context, () => {
|
|
25669
|
+
cleared = true;
|
|
25670
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
25671
|
+
timer = void 0;
|
|
25672
|
+
});
|
|
25673
|
+
arm(deadlineMs - Date.now());
|
|
25674
|
+
return () => {
|
|
25675
|
+
if (cleared) return;
|
|
25676
|
+
cleared = true;
|
|
25677
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
25678
|
+
timer = void 0;
|
|
25679
|
+
unhook();
|
|
25680
|
+
};
|
|
25681
|
+
}
|
|
25682
|
+
function unsupportedLimit(envelope) {
|
|
25230
25683
|
if (envelope.plan.limits.maxAgentTimeMs !== void 0) {
|
|
25231
25684
|
return { reason: "limit_unsupported", field: "limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
25232
25685
|
}
|
|
25233
|
-
if (envelope.projectLimits.maxRunDurationMs !== void 0) {
|
|
25234
|
-
return { reason: "limit_unsupported", field: "project.limits.maxRunDurationMs", detail: "duration budget enforcement is not available in this slice" };
|
|
25235
|
-
}
|
|
25236
25686
|
if (envelope.projectLimits.maxAgentTimeMs !== void 0) {
|
|
25237
25687
|
return { reason: "limit_unsupported", field: "project.limits.maxAgentTimeMs", detail: "agent-time budget enforcement is not available in this slice" };
|
|
25238
25688
|
}
|
|
@@ -26248,6 +26698,19 @@ async function waitForDriveSessions(settled, graceMs) {
|
|
|
26248
26698
|
if (timeout !== void 0) clearTimeout(timeout);
|
|
26249
26699
|
}
|
|
26250
26700
|
}
|
|
26701
|
+
async function driveSessionStillPending(settled) {
|
|
26702
|
+
let pending = true;
|
|
26703
|
+
void settled.then(
|
|
26704
|
+
() => {
|
|
26705
|
+
pending = false;
|
|
26706
|
+
},
|
|
26707
|
+
() => {
|
|
26708
|
+
pending = false;
|
|
26709
|
+
}
|
|
26710
|
+
);
|
|
26711
|
+
await Promise.resolve();
|
|
26712
|
+
return pending;
|
|
26713
|
+
}
|
|
26251
26714
|
async function startVnextRuntimeSupervisor(options = {}) {
|
|
26252
26715
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
26253
26716
|
const paths = vnextRuntimePaths(options.stateRoot !== void 0 ? { stateRoot: options.stateRoot } : {});
|
|
@@ -26351,7 +26814,9 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26351
26814
|
const bundle = loadVnextProject(projectRoot, {});
|
|
26352
26815
|
if (request.method === "GET" && !sub) {
|
|
26353
26816
|
const projected = rebuildVnextRunProjection(context, runId);
|
|
26354
|
-
|
|
26817
|
+
const folded = foldStoredVnextRun(context, projected);
|
|
26818
|
+
const drive = vnextDrivePollProjection(context, runId, folded);
|
|
26819
|
+
sendJson(response, 200, { ok: true, run: projected, ...drive !== void 0 ? { drive } : {} });
|
|
26355
26820
|
return;
|
|
26356
26821
|
}
|
|
26357
26822
|
if (request.method === "POST" && sub === "drive") {
|
|
@@ -26394,7 +26859,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26394
26859
|
const scheduler = VnextRunScheduler.for(context, bundle);
|
|
26395
26860
|
const opening = scheduler.openDriveSession(runId, {
|
|
26396
26861
|
mode: body.mode,
|
|
26397
|
-
allowLimits:
|
|
26862
|
+
allowLimits: false,
|
|
26398
26863
|
liveMode: body.mode === "live",
|
|
26399
26864
|
createProducer
|
|
26400
26865
|
});
|
|
@@ -26568,6 +27033,15 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26568
27033
|
}
|
|
26569
27034
|
}
|
|
26570
27035
|
await waitForDriveSessions(openSessions.map(({ session }) => session.settled), runtimeStopGraceMs());
|
|
27036
|
+
for (const { context, session } of openSessions) {
|
|
27037
|
+
if (!await driveSessionStillPending(session.settled)) continue;
|
|
27038
|
+
recordDriveReceipt(context, session, {
|
|
27039
|
+
kind: "unsettled",
|
|
27040
|
+
reason: "runtime_shutdown_grace_expired",
|
|
27041
|
+
producerId: session.producerId,
|
|
27042
|
+
producerClosed: false
|
|
27043
|
+
});
|
|
27044
|
+
}
|
|
26571
27045
|
for (const context of contexts.values()) closeVnextRuntimeContext(context);
|
|
26572
27046
|
contexts.clear();
|
|
26573
27047
|
const closed = new Promise((resolveStop) => server.close(() => resolveStop()));
|