@automatalabs/workflows 0.54.1 → 0.56.0
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/README.md +24 -12
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-server.js +1184 -230
- package/dist/validate.d.ts +7 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +10 -3
- package/package.json +4 -4
package/dist/mcp-server.js
CHANGED
|
@@ -14,7 +14,7 @@ import { realpathSync as realpathSync4 } from "node:fs";
|
|
|
14
14
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
15
15
|
|
|
16
16
|
// ../mcp-server/src/daemon/commands.ts
|
|
17
|
-
import { readFileSync as
|
|
17
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
18
18
|
|
|
19
19
|
// ../../node_modules/.pnpm/@modelcontextprotocol+server@2.0.0/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
20
20
|
var __create = Object.create;
|
|
@@ -1419,8 +1419,8 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
|
|
|
1419
1419
|
let i = 0;
|
|
1420
1420
|
while (i < fullpath.length) {
|
|
1421
1421
|
const el = fullpath[i];
|
|
1422
|
-
const
|
|
1423
|
-
if (!
|
|
1422
|
+
const terminal2 = i === fullpath.length - 1;
|
|
1423
|
+
if (!terminal2) {
|
|
1424
1424
|
curr[el] = curr[el] || { _errors: [] };
|
|
1425
1425
|
} else {
|
|
1426
1426
|
curr[el] = curr[el] || { _errors: [] };
|
|
@@ -1457,7 +1457,7 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
1457
1457
|
let i = 0;
|
|
1458
1458
|
while (i < fullpath.length) {
|
|
1459
1459
|
const el = fullpath[i];
|
|
1460
|
-
const
|
|
1460
|
+
const terminal2 = i === fullpath.length - 1;
|
|
1461
1461
|
if (typeof el === "string") {
|
|
1462
1462
|
curr.properties ?? (curr.properties = {});
|
|
1463
1463
|
(_a3 = curr.properties)[el] ?? (_a3[el] = { errors: [] });
|
|
@@ -1467,7 +1467,7 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
|
|
|
1467
1467
|
(_b = curr.items)[el] ?? (_b[el] = { errors: [] });
|
|
1468
1468
|
curr = curr.items[el];
|
|
1469
1469
|
}
|
|
1470
|
-
if (
|
|
1470
|
+
if (terminal2) {
|
|
1471
1471
|
curr.errors.push(mapper(issue2));
|
|
1472
1472
|
}
|
|
1473
1473
|
i++;
|
|
@@ -11559,8 +11559,8 @@ function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
|
|
|
11559
11559
|
return _result.schema;
|
|
11560
11560
|
}
|
|
11561
11561
|
function extractDefs(ctx, schema) {
|
|
11562
|
-
const
|
|
11563
|
-
if (!
|
|
11562
|
+
const root2 = ctx.seen.get(schema);
|
|
11563
|
+
if (!root2)
|
|
11564
11564
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
11565
11565
|
const idToSchema = /* @__PURE__ */ new Map();
|
|
11566
11566
|
for (const entry of ctx.seen.entries()) {
|
|
@@ -11585,7 +11585,7 @@ function extractDefs(ctx, schema) {
|
|
|
11585
11585
|
entry[1].defId = id;
|
|
11586
11586
|
return { defId: id, ref: `${uriGenerator("__shared")}#/${defsSegment}/${id}` };
|
|
11587
11587
|
}
|
|
11588
|
-
if (entry[1] ===
|
|
11588
|
+
if (entry[1] === root2) {
|
|
11589
11589
|
return { ref: "#" };
|
|
11590
11590
|
}
|
|
11591
11591
|
const uriPrefix = `#`;
|
|
@@ -11649,8 +11649,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11649
11649
|
}
|
|
11650
11650
|
}
|
|
11651
11651
|
function finalize(ctx, schema) {
|
|
11652
|
-
const
|
|
11653
|
-
if (!
|
|
11652
|
+
const root2 = ctx.seen.get(schema);
|
|
11653
|
+
if (!root2)
|
|
11654
11654
|
throw new Error("Unprocessed schema. This is a bug in Zod.");
|
|
11655
11655
|
const flattenRef = (zodSchema) => {
|
|
11656
11656
|
const seen = ctx.seen.get(zodSchema);
|
|
@@ -11733,7 +11733,7 @@ function finalize(ctx, schema) {
|
|
|
11733
11733
|
throw new Error("Schema is missing an `id` property");
|
|
11734
11734
|
result.$id = ctx.external.uri(id);
|
|
11735
11735
|
}
|
|
11736
|
-
Object.assign(result,
|
|
11736
|
+
Object.assign(result, root2.def ?? root2.schema);
|
|
11737
11737
|
const rootMetaId = ctx.metadataRegistry.get(schema)?.id;
|
|
11738
11738
|
if (rootMetaId !== void 0 && result.id === rootMetaId)
|
|
11739
11739
|
delete result.id;
|
|
@@ -18579,8 +18579,8 @@ function decodeMcpParamValue(value) {
|
|
|
18579
18579
|
return;
|
|
18580
18580
|
}
|
|
18581
18581
|
}
|
|
18582
|
-
function valueAtPath(
|
|
18583
|
-
let node =
|
|
18582
|
+
function valueAtPath(root2, path) {
|
|
18583
|
+
let node = root2;
|
|
18584
18584
|
for (const key of path) {
|
|
18585
18585
|
if (node === null || typeof node !== "object") return void 0;
|
|
18586
18586
|
node = node[key];
|
|
@@ -23206,24 +23206,24 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
23206
23206
|
}
|
|
23207
23207
|
}
|
|
23208
23208
|
exports.compileSchema = compileSchema;
|
|
23209
|
-
function resolveRef2(
|
|
23209
|
+
function resolveRef2(root2, baseId, ref) {
|
|
23210
23210
|
var _a3;
|
|
23211
23211
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
23212
|
-
const schOrFunc =
|
|
23212
|
+
const schOrFunc = root2.refs[ref];
|
|
23213
23213
|
if (schOrFunc) return schOrFunc;
|
|
23214
|
-
let _sch = resolve.call(this,
|
|
23214
|
+
let _sch = resolve.call(this, root2, ref);
|
|
23215
23215
|
if (_sch === void 0) {
|
|
23216
|
-
const schema = (_a3 =
|
|
23216
|
+
const schema = (_a3 = root2.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
23217
23217
|
const { schemaId } = this.opts;
|
|
23218
23218
|
if (schema) _sch = new SchemaEnv({
|
|
23219
23219
|
schema,
|
|
23220
23220
|
schemaId,
|
|
23221
|
-
root,
|
|
23221
|
+
root: root2,
|
|
23222
23222
|
baseId
|
|
23223
23223
|
});
|
|
23224
23224
|
}
|
|
23225
23225
|
if (_sch === void 0) return;
|
|
23226
|
-
return
|
|
23226
|
+
return root2.refs[ref] = inlineOrCompile.call(this, _sch);
|
|
23227
23227
|
}
|
|
23228
23228
|
exports.resolveRef = resolveRef2;
|
|
23229
23229
|
function inlineOrCompile(sch) {
|
|
@@ -23237,20 +23237,20 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
23237
23237
|
function sameSchemaEnv(s1, s2) {
|
|
23238
23238
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
23239
23239
|
}
|
|
23240
|
-
function resolve(
|
|
23240
|
+
function resolve(root2, ref) {
|
|
23241
23241
|
let sch;
|
|
23242
23242
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
23243
|
-
return sch || this.schemas[ref] || resolveSchema.call(this,
|
|
23243
|
+
return sch || this.schemas[ref] || resolveSchema.call(this, root2, ref);
|
|
23244
23244
|
}
|
|
23245
|
-
function resolveSchema(
|
|
23245
|
+
function resolveSchema(root2, ref) {
|
|
23246
23246
|
const p = this.opts.uriResolver.parse(ref);
|
|
23247
23247
|
const refPath = (0, resolve_1._getFullPath)(this.opts.uriResolver, p);
|
|
23248
|
-
let baseId = (0, resolve_1.getFullPath)(this.opts.uriResolver,
|
|
23249
|
-
if (Object.keys(
|
|
23248
|
+
let baseId = (0, resolve_1.getFullPath)(this.opts.uriResolver, root2.baseId, void 0);
|
|
23249
|
+
if (Object.keys(root2.schema).length > 0 && refPath === baseId) return getJsonPointer.call(this, p, root2);
|
|
23250
23250
|
const id = (0, resolve_1.normalizeId)(refPath);
|
|
23251
23251
|
const schOrRef = this.refs[id] || this.schemas[id];
|
|
23252
23252
|
if (typeof schOrRef == "string") {
|
|
23253
|
-
const sch = resolveSchema.call(this,
|
|
23253
|
+
const sch = resolveSchema.call(this, root2, schOrRef);
|
|
23254
23254
|
if (typeof (sch === null || sch === void 0 ? void 0 : sch.schema) !== "object") return;
|
|
23255
23255
|
return getJsonPointer.call(this, p, sch);
|
|
23256
23256
|
}
|
|
@@ -23264,7 +23264,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
23264
23264
|
return new SchemaEnv({
|
|
23265
23265
|
schema,
|
|
23266
23266
|
schemaId,
|
|
23267
|
-
root,
|
|
23267
|
+
root: root2,
|
|
23268
23268
|
baseId
|
|
23269
23269
|
});
|
|
23270
23270
|
}
|
|
@@ -23278,7 +23278,7 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
23278
23278
|
"dependencies",
|
|
23279
23279
|
"definitions"
|
|
23280
23280
|
]);
|
|
23281
|
-
function getJsonPointer(parsedRef, { baseId, schema, root }) {
|
|
23281
|
+
function getJsonPointer(parsedRef, { baseId, schema, root: root2 }) {
|
|
23282
23282
|
var _a3;
|
|
23283
23283
|
if (((_a3 = parsedRef.fragment) === null || _a3 === void 0 ? void 0 : _a3[0]) !== "/") return;
|
|
23284
23284
|
for (const part of parsedRef.fragment.slice(1).split("/")) {
|
|
@@ -23292,13 +23292,13 @@ var require_compile = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
23292
23292
|
let env;
|
|
23293
23293
|
if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) {
|
|
23294
23294
|
const $ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schema.$ref);
|
|
23295
|
-
env = resolveSchema.call(this,
|
|
23295
|
+
env = resolveSchema.call(this, root2, $ref);
|
|
23296
23296
|
}
|
|
23297
23297
|
const { schemaId } = this.opts;
|
|
23298
23298
|
env = env || new SchemaEnv({
|
|
23299
23299
|
schema,
|
|
23300
23300
|
schemaId,
|
|
23301
|
-
root,
|
|
23301
|
+
root: root2,
|
|
23302
23302
|
baseId
|
|
23303
23303
|
});
|
|
23304
23304
|
if (env.schema !== env.root.schema) return env;
|
|
@@ -24163,11 +24163,11 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
24163
24163
|
while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch;
|
|
24164
24164
|
if (sch === void 0) {
|
|
24165
24165
|
const { schemaId } = this.opts;
|
|
24166
|
-
const
|
|
24166
|
+
const root2 = new compile_1.SchemaEnv({
|
|
24167
24167
|
schema: {},
|
|
24168
24168
|
schemaId
|
|
24169
24169
|
});
|
|
24170
|
-
sch = compile_1.resolveSchema.call(this,
|
|
24170
|
+
sch = compile_1.resolveSchema.call(this, root2, keyRef);
|
|
24171
24171
|
if (!sch) return;
|
|
24172
24172
|
this.refs[keyRef] = sch;
|
|
24173
24173
|
}
|
|
@@ -24472,16 +24472,16 @@ var require_ref = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
24472
24472
|
code(cxt) {
|
|
24473
24473
|
const { gen, schema: $ref, it } = cxt;
|
|
24474
24474
|
const { baseId, schemaEnv: env, validateName, opts, self } = it;
|
|
24475
|
-
const { root } = env;
|
|
24476
|
-
if (($ref === "#" || $ref === "#/") && baseId ===
|
|
24477
|
-
const schOrEnv = compile_1.resolveRef.call(self,
|
|
24475
|
+
const { root: root2 } = env;
|
|
24476
|
+
if (($ref === "#" || $ref === "#/") && baseId === root2.baseId) return callRootRef();
|
|
24477
|
+
const schOrEnv = compile_1.resolveRef.call(self, root2, baseId, $ref);
|
|
24478
24478
|
if (schOrEnv === void 0) throw new ref_error_1.default(it.opts.uriResolver, baseId, $ref);
|
|
24479
24479
|
if (schOrEnv instanceof compile_1.SchemaEnv) return callValidate(schOrEnv);
|
|
24480
24480
|
return inlineRefSchema(schOrEnv);
|
|
24481
24481
|
function callRootRef() {
|
|
24482
|
-
if (env ===
|
|
24483
|
-
const rootName = gen.scopeValue("root", { ref:
|
|
24484
|
-
return callRef(cxt, (0, codegen_1._)`${rootName}.validate`,
|
|
24482
|
+
if (env === root2) return callRef(cxt, validateName, env, env.$async);
|
|
24483
|
+
const rootName = gen.scopeValue("root", { ref: root2 });
|
|
24484
|
+
return callRef(cxt, (0, codegen_1._)`${rootName}.validate`, root2, root2.$async);
|
|
24485
24485
|
}
|
|
24486
24486
|
function callValidate(sch) {
|
|
24487
24487
|
callRef(cxt, getValidate(cxt, sch), sch, sch.$async);
|
|
@@ -26196,12 +26196,12 @@ var require_dynamicAnchor = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
26196
26196
|
exports.dynamicAnchor = dynamicAnchor;
|
|
26197
26197
|
function _getValidate(cxt) {
|
|
26198
26198
|
const { schemaEnv, schema, self } = cxt.it;
|
|
26199
|
-
const { root, baseId, localRefs, meta: meta3 } = schemaEnv.root;
|
|
26199
|
+
const { root: root2, baseId, localRefs, meta: meta3 } = schemaEnv.root;
|
|
26200
26200
|
const { schemaId } = self.opts;
|
|
26201
26201
|
const sch = new compile_1.SchemaEnv({
|
|
26202
26202
|
schema,
|
|
26203
26203
|
schemaId,
|
|
26204
|
-
root,
|
|
26204
|
+
root: root2,
|
|
26205
26205
|
baseId,
|
|
26206
26206
|
localRefs,
|
|
26207
26207
|
meta: meta3
|
|
@@ -30640,15 +30640,16 @@ function createRequestStateCodec(options) {
|
|
|
30640
30640
|
|
|
30641
30641
|
// ../mcp-server/src/server.ts
|
|
30642
30642
|
import { createHash, randomBytes } from "node:crypto";
|
|
30643
|
-
import { readFileSync as
|
|
30643
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
30644
30644
|
import { createRequire } from "node:module";
|
|
30645
30645
|
import {
|
|
30646
30646
|
buildModelFilter,
|
|
30647
30647
|
parseWorkflowScript,
|
|
30648
|
-
probeHarnessConfig,
|
|
30648
|
+
probeHarnessConfig as probeHarnessConfig2,
|
|
30649
30649
|
redactText as redactText2,
|
|
30650
30650
|
validateWorkflowScript,
|
|
30651
30651
|
truncateUtf8 as truncateUtf83,
|
|
30652
|
+
workflowMayUseDefaultModel,
|
|
30652
30653
|
WorkflowError,
|
|
30653
30654
|
WorkflowErrorCode,
|
|
30654
30655
|
WorkflowManager as WorkflowManager3
|
|
@@ -30706,6 +30707,9 @@ var workflowToolInputShape = {
|
|
|
30706
30707
|
callIndex: external_exports.number().int().nonnegative().safe().optional().describe(
|
|
30707
30708
|
"With action=stop, cancel exactly this in-flight agent call without aborting the run. Forbidden for every other action."
|
|
30708
30709
|
),
|
|
30710
|
+
forceOwner: external_exports.boolean().optional().describe(
|
|
30711
|
+
"With whole-run action=stop, explicitly authorize terminating a superseded owner daemon when graceful cross-generation control cannot settle. Forbidden with callIndex and every other action."
|
|
30712
|
+
),
|
|
30709
30713
|
lastN: external_exports.number().int().min(1).max(50).optional().describe("Latest matching calls. Default 20; range 1..50."),
|
|
30710
30714
|
labelGlob: external_exports.string().refine((value) => [...value].length >= 1 && [...value].length <= 128, {
|
|
30711
30715
|
message: "labelGlob must contain from 1 through 128 Unicode code points"
|
|
@@ -30724,7 +30728,7 @@ function invalid(message) {
|
|
|
30724
30728
|
}
|
|
30725
30729
|
function parseWorkflowToolInput(raw, options = {}) {
|
|
30726
30730
|
if (raw.action === "config") {
|
|
30727
|
-
if (raw.script !== void 0 || raw.scriptPath !== void 0 || raw.args !== void 0 || raw.maxAgents !== void 0 || raw.concurrency !== void 0 || raw.agentRetries !== void 0 || raw.agentTimeoutMs !== void 0 || raw.resumeFromRunId !== void 0 || raw.resumePolicy !== void 0 || raw.checkpointReplies !== void 0 || raw.background !== void 0 || raw.runId !== void 0 || raw.callIndex !== void 0 || raw.waitMs !== void 0 || raw.lastN !== void 0 || raw.labelGlob !== void 0 || raw.logLines !== void 0) {
|
|
30731
|
+
if (raw.script !== void 0 || raw.scriptPath !== void 0 || raw.args !== void 0 || raw.maxAgents !== void 0 || raw.concurrency !== void 0 || raw.agentRetries !== void 0 || raw.agentTimeoutMs !== void 0 || raw.resumeFromRunId !== void 0 || raw.resumePolicy !== void 0 || raw.checkpointReplies !== void 0 || raw.background !== void 0 || raw.runId !== void 0 || raw.callIndex !== void 0 || raw.forceOwner !== void 0 || raw.waitMs !== void 0 || raw.lastN !== void 0 || raw.labelGlob !== void 0 || raw.logLines !== void 0) {
|
|
30728
30732
|
invalid('action="config" accepts only projectDir, harnesses, modelSpecs, modelFilter, and probeTimeoutMs');
|
|
30729
30733
|
}
|
|
30730
30734
|
if (options.requireProjectDir === true && raw.projectDir === void 0) {
|
|
@@ -30743,7 +30747,7 @@ function parseWorkflowToolInput(raw, options = {}) {
|
|
|
30743
30747
|
}
|
|
30744
30748
|
if (raw.action === "inspect") {
|
|
30745
30749
|
if (!raw.runId) invalid('action="inspect" requires runId');
|
|
30746
|
-
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.waitMs !== void 0 || raw.callIndex !== void 0) {
|
|
30750
|
+
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.waitMs !== void 0 || raw.callIndex !== void 0 || raw.forceOwner !== void 0) {
|
|
30747
30751
|
invalid('action="inspect" cannot include execution fields');
|
|
30748
30752
|
}
|
|
30749
30753
|
return {
|
|
@@ -30756,7 +30760,7 @@ function parseWorkflowToolInput(raw, options = {}) {
|
|
|
30756
30760
|
}
|
|
30757
30761
|
if (raw.action === "await") {
|
|
30758
30762
|
if (!raw.runId) invalid('action="await" requires runId');
|
|
30759
|
-
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.callIndex !== void 0) {
|
|
30763
|
+
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.callIndex !== void 0 || raw.forceOwner !== void 0) {
|
|
30760
30764
|
invalid('action="await" cannot include execution fields');
|
|
30761
30765
|
}
|
|
30762
30766
|
return {
|
|
@@ -30773,16 +30777,20 @@ function parseWorkflowToolInput(raw, options = {}) {
|
|
|
30773
30777
|
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.waitMs !== void 0) {
|
|
30774
30778
|
invalid('action="stop" cannot include execution fields or waitMs');
|
|
30775
30779
|
}
|
|
30780
|
+
if (raw.callIndex !== void 0 && raw.forceOwner !== void 0) {
|
|
30781
|
+
invalid('action="stop" forceOwner is forbidden with callIndex');
|
|
30782
|
+
}
|
|
30776
30783
|
return {
|
|
30777
30784
|
action: "stop",
|
|
30778
30785
|
runId: raw.runId,
|
|
30779
30786
|
callIndex: raw.callIndex,
|
|
30787
|
+
...raw.forceOwner === void 0 ? {} : { forceOwner: raw.forceOwner },
|
|
30780
30788
|
lastN: raw.lastN,
|
|
30781
30789
|
labelGlob: raw.labelGlob,
|
|
30782
30790
|
logLines: raw.logLines
|
|
30783
30791
|
};
|
|
30784
30792
|
}
|
|
30785
|
-
if (raw.runId !== void 0 || raw.callIndex !== void 0 || raw.waitMs !== void 0 || raw.lastN !== void 0 || raw.labelGlob !== void 0 || raw.logLines !== void 0 || hasConfigFields(raw)) {
|
|
30793
|
+
if (raw.runId !== void 0 || raw.callIndex !== void 0 || raw.forceOwner !== void 0 || raw.waitMs !== void 0 || raw.lastN !== void 0 || raw.labelGlob !== void 0 || raw.logLines !== void 0 || hasConfigFields(raw)) {
|
|
30786
30794
|
invalid("run inputs cannot include inspection fields");
|
|
30787
30795
|
}
|
|
30788
30796
|
const hasScript = raw.script !== void 0;
|
|
@@ -30826,9 +30834,131 @@ function clampWorkflowInput(input) {
|
|
|
30826
30834
|
};
|
|
30827
30835
|
}
|
|
30828
30836
|
|
|
30837
|
+
// ../mcp-server/src/default-backend.ts
|
|
30838
|
+
import {
|
|
30839
|
+
probeHarnessConfig
|
|
30840
|
+
} from "@automatalabs/workflows";
|
|
30841
|
+
var DEFAULT_BACKEND_ENV = "AGENTPRISM_DEFAULT_BACKEND";
|
|
30842
|
+
var BUILTIN_BACKENDS = /* @__PURE__ */ new Set(["claude", "codex", "opencode", "pi"]);
|
|
30843
|
+
var NoAutoDefaultBackendError = class extends Error {
|
|
30844
|
+
constructor(candidates) {
|
|
30845
|
+
super(
|
|
30846
|
+
[
|
|
30847
|
+
"No usable default ACP backend was found for workflow agent() calls that omit model/tier.",
|
|
30848
|
+
...candidates.map((candidate) => `- ${candidate.backendId}: ${candidate.reason}`),
|
|
30849
|
+
`Authenticate or install a backend, set ${DEFAULT_BACKEND_ENV} explicitly, or pin model on each agent() call.`
|
|
30850
|
+
].join("\n")
|
|
30851
|
+
);
|
|
30852
|
+
this.candidates = candidates;
|
|
30853
|
+
this.name = "NoAutoDefaultBackendError";
|
|
30854
|
+
}
|
|
30855
|
+
candidates;
|
|
30856
|
+
};
|
|
30857
|
+
function workflowNeedsPinnedDefault(report) {
|
|
30858
|
+
return report.dryRun?.agentCalls.some((call) => call.model === void 0 && call.tier === void 0) === true;
|
|
30859
|
+
}
|
|
30860
|
+
function recordedDefaultModel(source) {
|
|
30861
|
+
if (!source) return void 0;
|
|
30862
|
+
if (typeof source.defaultModel === "string" && source.defaultModel.trim() !== "") return source.defaultModel;
|
|
30863
|
+
const backends = new Set(
|
|
30864
|
+
(source.calls ?? []).filter((call) => call.kind === "agent" && call.modelRequested === void 0 && call.backendId).map((call) => call.backendId)
|
|
30865
|
+
);
|
|
30866
|
+
return backends.size === 1 ? [...backends][0] : void 0;
|
|
30867
|
+
}
|
|
30868
|
+
function modelCatalogState(harness) {
|
|
30869
|
+
const option = (harness.options ?? []).find(
|
|
30870
|
+
(candidate) => candidate.id === "model" && candidate.type === "select"
|
|
30871
|
+
);
|
|
30872
|
+
if (!option || option.type !== "select") return "absent";
|
|
30873
|
+
const current = typeof option.currentValue === "string" ? option.currentValue.trim() : "";
|
|
30874
|
+
return current !== "" || option.options.length > 0 ? "usable" : "empty";
|
|
30875
|
+
}
|
|
30876
|
+
function classifyAutoDefaultCandidates(report, customBackendIds = []) {
|
|
30877
|
+
const customs = new Set(customBackendIds.map((id) => id.toLowerCase()));
|
|
30878
|
+
return report.harnessOptions.map((harness) => {
|
|
30879
|
+
const backendId = harness.backendId.toLowerCase();
|
|
30880
|
+
if (!harness.probed) {
|
|
30881
|
+
return {
|
|
30882
|
+
backendId: harness.backendId,
|
|
30883
|
+
status: "unavailable",
|
|
30884
|
+
reason: `probe failed${harness.error ? ` \u2014 ${harness.error}` : ""}`
|
|
30885
|
+
};
|
|
30886
|
+
}
|
|
30887
|
+
const builtIn = BUILTIN_BACKENDS.has(backendId) && !customs.has(backendId);
|
|
30888
|
+
const catalog = modelCatalogState(harness);
|
|
30889
|
+
if (builtIn && catalog === "empty") {
|
|
30890
|
+
return {
|
|
30891
|
+
backendId: harness.backendId,
|
|
30892
|
+
status: "unavailable",
|
|
30893
|
+
reason: "session opened but the built-in advertised no usable default or selectable model"
|
|
30894
|
+
};
|
|
30895
|
+
}
|
|
30896
|
+
if (builtIn && backendId === "codex") {
|
|
30897
|
+
return {
|
|
30898
|
+
backendId: harness.backendId,
|
|
30899
|
+
status: "ready",
|
|
30900
|
+
readiness: "ready",
|
|
30901
|
+
reason: "session/config probe succeeded and Codex checks authorization during session creation"
|
|
30902
|
+
};
|
|
30903
|
+
}
|
|
30904
|
+
if (builtIn && backendId === "pi" && catalog === "usable") {
|
|
30905
|
+
return {
|
|
30906
|
+
backendId: harness.backendId,
|
|
30907
|
+
status: "ready",
|
|
30908
|
+
readiness: "ready",
|
|
30909
|
+
reason: "session/config probe succeeded with Pi's credential-filtered model catalog"
|
|
30910
|
+
};
|
|
30911
|
+
}
|
|
30912
|
+
return {
|
|
30913
|
+
backendId: harness.backendId,
|
|
30914
|
+
status: "unknown",
|
|
30915
|
+
readiness: "unknown",
|
|
30916
|
+
reason: "session/config probe succeeded; zero-token authentication readiness is not universally observable"
|
|
30917
|
+
};
|
|
30918
|
+
});
|
|
30919
|
+
}
|
|
30920
|
+
function selectAutoDefaultBackend(report, customBackendIds = []) {
|
|
30921
|
+
const candidates = classifyAutoDefaultCandidates(report, customBackendIds);
|
|
30922
|
+
const selected = candidates.find((candidate) => candidate.status === "ready") ?? candidates.find((candidate) => candidate.status === "unknown");
|
|
30923
|
+
if (!selected || selected.status === "unavailable") throw new NoAutoDefaultBackendError(candidates);
|
|
30924
|
+
return { backendId: selected.backendId, readiness: selected.readiness, reason: selected.reason };
|
|
30925
|
+
}
|
|
30926
|
+
async function discoverProjectDefaultBackend(context, probeRunner, timeoutMs = 6e4) {
|
|
30927
|
+
if (context.autoDefaultBackend) return context.autoDefaultBackend;
|
|
30928
|
+
if (context.autoDefaultBackendPending) return context.autoDefaultBackendPending;
|
|
30929
|
+
const backendIds = [...new Set(probeRunner.listBackends?.() ?? [])];
|
|
30930
|
+
if (backendIds.length === 0) throw new NoAutoDefaultBackendError([]);
|
|
30931
|
+
const pending = Promise.all(
|
|
30932
|
+
backendIds.map(
|
|
30933
|
+
(backendId) => probeHarnessConfig({
|
|
30934
|
+
harnesses: [backendId],
|
|
30935
|
+
cwd: context.projectDir,
|
|
30936
|
+
timeoutMs,
|
|
30937
|
+
probeRunner
|
|
30938
|
+
})
|
|
30939
|
+
)
|
|
30940
|
+
).then((reports) => {
|
|
30941
|
+
const harnessOptions = reports.flatMap((report2) => report2.harnessOptions);
|
|
30942
|
+
const report = {
|
|
30943
|
+
ok: harnessOptions.every((harness) => harness.probed),
|
|
30944
|
+
exitCode: harnessOptions.every((harness) => harness.probed) ? 0 : 1,
|
|
30945
|
+
harnessOptions
|
|
30946
|
+
};
|
|
30947
|
+
const selected = selectAutoDefaultBackend(report, probeRunner.listCustomBackends?.() ?? []);
|
|
30948
|
+
context.autoDefaultBackend = selected;
|
|
30949
|
+
return selected;
|
|
30950
|
+
});
|
|
30951
|
+
context.autoDefaultBackendPending = pending;
|
|
30952
|
+
try {
|
|
30953
|
+
return await pending;
|
|
30954
|
+
} finally {
|
|
30955
|
+
context.autoDefaultBackendPending = void 0;
|
|
30956
|
+
}
|
|
30957
|
+
}
|
|
30958
|
+
|
|
30829
30959
|
// ../mcp-server/src/project-registry.ts
|
|
30830
|
-
import { existsSync as existsSync2, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
30831
|
-
import { isAbsolute as isAbsolute2, join } from "node:path";
|
|
30960
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, realpathSync, statSync } from "node:fs";
|
|
30961
|
+
import { isAbsolute as isAbsolute2, join as join2 } from "node:path";
|
|
30832
30962
|
import {
|
|
30833
30963
|
WORKFLOW_PROJECTS_SUBDIR,
|
|
30834
30964
|
WorkflowManager,
|
|
@@ -31117,6 +31247,152 @@ async function resetReplProjectState(state, boundMs = SHUTDOWN_DEADLINE_MS) {
|
|
|
31117
31247
|
state.drainError = null;
|
|
31118
31248
|
}
|
|
31119
31249
|
|
|
31250
|
+
// ../mcp-server/src/daemon/run-control-store.ts
|
|
31251
|
+
import { randomUUID } from "node:crypto";
|
|
31252
|
+
import {
|
|
31253
|
+
chmodSync,
|
|
31254
|
+
linkSync,
|
|
31255
|
+
mkdirSync,
|
|
31256
|
+
readFileSync,
|
|
31257
|
+
readdirSync,
|
|
31258
|
+
rmSync,
|
|
31259
|
+
writeFileSync
|
|
31260
|
+
} from "node:fs";
|
|
31261
|
+
import { join } from "node:path";
|
|
31262
|
+
var CONTROL_DIR = ".control";
|
|
31263
|
+
var RUN_ID = /^[a-z0-9]+-[a-z0-9]+$/;
|
|
31264
|
+
var OPERATION_ID = /^[0-9a-f-]{36}$/i;
|
|
31265
|
+
function root(manager) {
|
|
31266
|
+
return join(manager.getPersistence().getRunsDir(), CONTROL_DIR);
|
|
31267
|
+
}
|
|
31268
|
+
function runDir(manager, runId) {
|
|
31269
|
+
if (!RUN_ID.test(runId)) throw new Error(`Invalid run-control runId: ${runId}`);
|
|
31270
|
+
return join(root(manager), runId);
|
|
31271
|
+
}
|
|
31272
|
+
function requestPath(manager, runId, operationId) {
|
|
31273
|
+
if (!OPERATION_ID.test(operationId)) throw new Error(`Invalid run-control operationId: ${operationId}`);
|
|
31274
|
+
return join(runDir(manager, runId), `${operationId}.request.json`);
|
|
31275
|
+
}
|
|
31276
|
+
function acknowledgementPath(manager, runId, operationId) {
|
|
31277
|
+
if (!OPERATION_ID.test(operationId)) throw new Error(`Invalid run-control operationId: ${operationId}`);
|
|
31278
|
+
return join(runDir(manager, runId), `${operationId}.ack.json`);
|
|
31279
|
+
}
|
|
31280
|
+
function ensureRunDir(manager, runId) {
|
|
31281
|
+
const dir = runDir(manager, runId);
|
|
31282
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
31283
|
+
chmodSync(root(manager), 448);
|
|
31284
|
+
chmodSync(dir, 448);
|
|
31285
|
+
}
|
|
31286
|
+
function writeImmutableJson(path, value) {
|
|
31287
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
31288
|
+
try {
|
|
31289
|
+
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
|
|
31290
|
+
`, { flag: "wx", mode: 384 });
|
|
31291
|
+
linkSync(tmp, path);
|
|
31292
|
+
} finally {
|
|
31293
|
+
rmSync(tmp, { force: true });
|
|
31294
|
+
}
|
|
31295
|
+
}
|
|
31296
|
+
function readJson(path) {
|
|
31297
|
+
try {
|
|
31298
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
31299
|
+
} catch {
|
|
31300
|
+
return void 0;
|
|
31301
|
+
}
|
|
31302
|
+
}
|
|
31303
|
+
function validIntent(value, runId) {
|
|
31304
|
+
return value?.version === 1 && value.action === "stop" && OPERATION_ID.test(value.operationId) && RUN_ID.test(value.runId) && (runId === void 0 || value.runId === runId) && typeof value.requestedAt === "string" && typeof value.requestedByInstanceId === "string";
|
|
31305
|
+
}
|
|
31306
|
+
function validAcknowledgement(value, runId, operationId) {
|
|
31307
|
+
return value?.version === 1 && value.runId === runId && value.operationId === operationId && typeof value.acknowledgedAt === "string" && typeof value.acknowledgedByInstanceId === "string" && (value.outcome === "stopped" || value.outcome === "already-terminal");
|
|
31308
|
+
}
|
|
31309
|
+
function findPendingWholeStopIntent(manager, runId) {
|
|
31310
|
+
let files;
|
|
31311
|
+
try {
|
|
31312
|
+
files = readdirSync(runDir(manager, runId)).filter((file2) => file2.endsWith(".request.json")).sort();
|
|
31313
|
+
} catch {
|
|
31314
|
+
return void 0;
|
|
31315
|
+
}
|
|
31316
|
+
for (const file2 of files) {
|
|
31317
|
+
const operationId = file2.slice(0, -".request.json".length);
|
|
31318
|
+
if (!OPERATION_ID.test(operationId)) continue;
|
|
31319
|
+
const intent = readJson(requestPath(manager, runId, operationId));
|
|
31320
|
+
if (!validIntent(intent, runId)) continue;
|
|
31321
|
+
const acknowledgement = readWholeStopAcknowledgement(manager, runId, operationId);
|
|
31322
|
+
if (acknowledgement === void 0) return intent;
|
|
31323
|
+
}
|
|
31324
|
+
return void 0;
|
|
31325
|
+
}
|
|
31326
|
+
function createOrReuseWholeStopIntent(manager, runId, requesterInstanceId) {
|
|
31327
|
+
const pending = findPendingWholeStopIntent(manager, runId);
|
|
31328
|
+
if (pending) return pending;
|
|
31329
|
+
ensureRunDir(manager, runId);
|
|
31330
|
+
for (; ; ) {
|
|
31331
|
+
const intent = {
|
|
31332
|
+
version: 1,
|
|
31333
|
+
operationId: randomUUID(),
|
|
31334
|
+
runId,
|
|
31335
|
+
action: "stop",
|
|
31336
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31337
|
+
requestedByInstanceId: requesterInstanceId
|
|
31338
|
+
};
|
|
31339
|
+
try {
|
|
31340
|
+
writeImmutableJson(requestPath(manager, runId, intent.operationId), intent);
|
|
31341
|
+
return intent;
|
|
31342
|
+
} catch (error51) {
|
|
31343
|
+
if (error51.code !== "EEXIST") throw error51;
|
|
31344
|
+
}
|
|
31345
|
+
}
|
|
31346
|
+
}
|
|
31347
|
+
function readWholeStopIntent(manager, runId, operationId) {
|
|
31348
|
+
const value = readJson(requestPath(manager, runId, operationId));
|
|
31349
|
+
return validIntent(value, runId) && value.operationId === operationId ? value : void 0;
|
|
31350
|
+
}
|
|
31351
|
+
function readWholeStopAcknowledgement(manager, runId, operationId) {
|
|
31352
|
+
const value = readJson(acknowledgementPath(manager, runId, operationId));
|
|
31353
|
+
return validAcknowledgement(value, runId, operationId) ? value : void 0;
|
|
31354
|
+
}
|
|
31355
|
+
function acknowledgeWholeStopIntent(manager, intent, ownerInstanceId, outcome) {
|
|
31356
|
+
ensureRunDir(manager, intent.runId);
|
|
31357
|
+
const acknowledgement = {
|
|
31358
|
+
version: 1,
|
|
31359
|
+
operationId: intent.operationId,
|
|
31360
|
+
runId: intent.runId,
|
|
31361
|
+
acknowledgedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
31362
|
+
acknowledgedByInstanceId: ownerInstanceId,
|
|
31363
|
+
outcome
|
|
31364
|
+
};
|
|
31365
|
+
const path = acknowledgementPath(manager, intent.runId, intent.operationId);
|
|
31366
|
+
try {
|
|
31367
|
+
writeImmutableJson(path, acknowledgement);
|
|
31368
|
+
return acknowledgement;
|
|
31369
|
+
} catch (error51) {
|
|
31370
|
+
const existing = readWholeStopAcknowledgement(manager, intent.runId, intent.operationId);
|
|
31371
|
+
if (existing) return existing;
|
|
31372
|
+
throw error51;
|
|
31373
|
+
}
|
|
31374
|
+
}
|
|
31375
|
+
function listPendingWholeStopIntents(manager) {
|
|
31376
|
+
let runIds;
|
|
31377
|
+
try {
|
|
31378
|
+
runIds = readdirSync(root(manager)).filter((entry) => RUN_ID.test(entry));
|
|
31379
|
+
} catch {
|
|
31380
|
+
return [];
|
|
31381
|
+
}
|
|
31382
|
+
const intents = [];
|
|
31383
|
+
for (const runId of runIds) {
|
|
31384
|
+
const pending = findPendingWholeStopIntent(manager, runId);
|
|
31385
|
+
if (pending) intents.push(pending);
|
|
31386
|
+
}
|
|
31387
|
+
return intents;
|
|
31388
|
+
}
|
|
31389
|
+
function deleteRunControlSidecars(manager, runId) {
|
|
31390
|
+
try {
|
|
31391
|
+
rmSync(runDir(manager, runId), { recursive: true, force: true });
|
|
31392
|
+
} catch {
|
|
31393
|
+
}
|
|
31394
|
+
}
|
|
31395
|
+
|
|
31120
31396
|
// ../mcp-server/src/project-registry.ts
|
|
31121
31397
|
var MAX_BACKGROUND_RUNS = 4;
|
|
31122
31398
|
var BackgroundRunRegistry = class {
|
|
@@ -31166,13 +31442,16 @@ function resolveProjectDir(raw) {
|
|
|
31166
31442
|
}
|
|
31167
31443
|
}
|
|
31168
31444
|
var WorkflowProjectRegistry = class {
|
|
31169
|
-
constructor(runner) {
|
|
31445
|
+
constructor(runner, options = {}) {
|
|
31170
31446
|
this.runner = runner;
|
|
31447
|
+
this.options = options;
|
|
31171
31448
|
}
|
|
31172
31449
|
runner;
|
|
31450
|
+
options;
|
|
31173
31451
|
contexts = /* @__PURE__ */ new Map();
|
|
31174
31452
|
deletionListeners = /* @__PURE__ */ new Set();
|
|
31175
31453
|
persistedEventListeners = /* @__PURE__ */ new Set();
|
|
31454
|
+
stoppedListeners = /* @__PURE__ */ new Set();
|
|
31176
31455
|
/** Adopt an externally built manager as its project's context (composition back-compat). */
|
|
31177
31456
|
adopt(manager, backgroundRuns) {
|
|
31178
31457
|
const existing = this.contexts.get(manager.cwd);
|
|
@@ -31189,18 +31468,26 @@ var WorkflowProjectRegistry = class {
|
|
|
31189
31468
|
if (existing !== void 0) return existing;
|
|
31190
31469
|
return this.register({
|
|
31191
31470
|
projectDir,
|
|
31192
|
-
manager: new WorkflowManager({
|
|
31471
|
+
manager: new WorkflowManager({
|
|
31472
|
+
agent: this.runner,
|
|
31473
|
+
cwd: projectDir,
|
|
31474
|
+
leaseOwnerId: this.options.leaseOwnerId
|
|
31475
|
+
}),
|
|
31193
31476
|
backgroundRuns: new BackgroundRunRegistry()
|
|
31194
31477
|
});
|
|
31195
31478
|
}
|
|
31196
31479
|
register(context) {
|
|
31197
31480
|
this.contexts.set(context.projectDir, context);
|
|
31198
31481
|
context.manager.on("runDeleted", (event) => {
|
|
31482
|
+
deleteRunControlSidecars(context.manager, event.runId);
|
|
31199
31483
|
for (const listener of this.deletionListeners) listener(event);
|
|
31200
31484
|
});
|
|
31201
31485
|
context.manager.on("runEventPersisted", (record2) => {
|
|
31202
31486
|
for (const listener of this.persistedEventListeners) listener(record2);
|
|
31203
31487
|
});
|
|
31488
|
+
context.manager.on("stopped", (event) => {
|
|
31489
|
+
for (const listener of this.stoppedListeners) listener(event);
|
|
31490
|
+
});
|
|
31204
31491
|
return context;
|
|
31205
31492
|
}
|
|
31206
31493
|
storeFor(runId) {
|
|
@@ -31216,18 +31503,18 @@ var WorkflowProjectRegistry = class {
|
|
|
31216
31503
|
* project's next engine construction).
|
|
31217
31504
|
*/
|
|
31218
31505
|
locateOnDisk(runId) {
|
|
31219
|
-
const projectsDir =
|
|
31506
|
+
const projectsDir = join2(workflowHomeDir(), WORKFLOW_PROJECTS_SUBDIR);
|
|
31220
31507
|
let keys;
|
|
31221
31508
|
try {
|
|
31222
|
-
keys =
|
|
31509
|
+
keys = readdirSync2(projectsDir);
|
|
31223
31510
|
} catch {
|
|
31224
31511
|
return void 0;
|
|
31225
31512
|
}
|
|
31226
31513
|
for (const key of keys) {
|
|
31227
|
-
const rootDir =
|
|
31514
|
+
const rootDir = join2(projectsDir, key);
|
|
31228
31515
|
try {
|
|
31229
|
-
if (!existsSync2(
|
|
31230
|
-
const manifest = JSON.parse(
|
|
31516
|
+
if (!existsSync2(join2(rootDir, "runs", `${runId}.json`))) continue;
|
|
31517
|
+
const manifest = JSON.parse(readFileSync2(join2(rootDir, "project.json"), "utf-8"));
|
|
31231
31518
|
if (typeof manifest.projectDir !== "string" || !isAbsolute2(manifest.projectDir)) continue;
|
|
31232
31519
|
return this.getOrCreate(manifest.projectDir);
|
|
31233
31520
|
} catch {
|
|
@@ -31247,9 +31534,13 @@ var WorkflowProjectRegistry = class {
|
|
|
31247
31534
|
this.persistedEventListeners.add(listener);
|
|
31248
31535
|
return () => this.persistedEventListeners.delete(listener);
|
|
31249
31536
|
}
|
|
31537
|
+
onRunStopped(listener) {
|
|
31538
|
+
this.stoppedListeners.add(listener);
|
|
31539
|
+
return () => this.stoppedListeners.delete(listener);
|
|
31540
|
+
}
|
|
31250
31541
|
activeRunCount() {
|
|
31251
31542
|
let total = 0;
|
|
31252
|
-
for (const context of this.contexts.values()) total += context.
|
|
31543
|
+
for (const context of this.contexts.values()) total += context.manager.activeExecutionCount();
|
|
31253
31544
|
return total;
|
|
31254
31545
|
}
|
|
31255
31546
|
/** Dispose every context's REPL workspace: each one DRAINS with the
|
|
@@ -31284,7 +31575,7 @@ var WorkflowProjectRegistry = class {
|
|
|
31284
31575
|
snapshot() {
|
|
31285
31576
|
return [...this.contexts.values()].map((context) => ({
|
|
31286
31577
|
projectDir: context.projectDir,
|
|
31287
|
-
activeRuns: context.
|
|
31578
|
+
activeRuns: context.manager.activeExecutionCount()
|
|
31288
31579
|
}));
|
|
31289
31580
|
}
|
|
31290
31581
|
};
|
|
@@ -31301,6 +31592,10 @@ function singleStoreRouter(manager) {
|
|
|
31301
31592
|
onRunEventPersisted: (listener) => {
|
|
31302
31593
|
manager.on("runEventPersisted", listener);
|
|
31303
31594
|
return () => manager.off("runEventPersisted", listener);
|
|
31595
|
+
},
|
|
31596
|
+
onRunStopped: (listener) => {
|
|
31597
|
+
manager.on("stopped", listener);
|
|
31598
|
+
return () => manager.off("stopped", listener);
|
|
31304
31599
|
}
|
|
31305
31600
|
};
|
|
31306
31601
|
}
|
|
@@ -31728,6 +32023,19 @@ var discoveryOutputFields = [
|
|
|
31728
32023
|
"omittedHarnesses",
|
|
31729
32024
|
"models"
|
|
31730
32025
|
];
|
|
32026
|
+
var stopControlSchema = external_exports.object({
|
|
32027
|
+
state: external_exports.literal("pending"),
|
|
32028
|
+
operationId: external_exports.string(),
|
|
32029
|
+
requestedAt: external_exports.string(),
|
|
32030
|
+
owner: external_exports.object({
|
|
32031
|
+
pid: external_exports.number().int().positive(),
|
|
32032
|
+
instanceId: external_exports.string().optional(),
|
|
32033
|
+
version: external_exports.string().optional(),
|
|
32034
|
+
lameDuck: external_exports.boolean().optional(),
|
|
32035
|
+
activeRuns: external_exports.number().int().nonnegative().optional(),
|
|
32036
|
+
controlProtocol: external_exports.literal(1).optional()
|
|
32037
|
+
}).optional()
|
|
32038
|
+
});
|
|
31731
32039
|
var variantOutputFields = [
|
|
31732
32040
|
...executionDetailFields,
|
|
31733
32041
|
"scriptSource",
|
|
@@ -31736,6 +32044,7 @@ var variantOutputFields = [
|
|
|
31736
32044
|
"outcome",
|
|
31737
32045
|
"stopped",
|
|
31738
32046
|
"alreadyTerminal",
|
|
32047
|
+
"control",
|
|
31739
32048
|
...discoveryOutputFields
|
|
31740
32049
|
];
|
|
31741
32050
|
var forbidsRequired = (...fields) => ({
|
|
@@ -31777,23 +32086,26 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
31777
32086
|
wait: waitSchema.optional(),
|
|
31778
32087
|
outcome: executionResultSchema.optional(),
|
|
31779
32088
|
stopped: external_exports.boolean().optional(),
|
|
31780
|
-
alreadyTerminal: external_exports.boolean().optional()
|
|
32089
|
+
alreadyTerminal: external_exports.boolean().optional(),
|
|
32090
|
+
control: stopControlSchema.optional()
|
|
31781
32091
|
}).superRefine((value, context) => {
|
|
31782
32092
|
const has = (field) => value[field] !== void 0;
|
|
31783
32093
|
const inspectionComplete = inspectionRequired.every((field) => has(field));
|
|
31784
32094
|
const runCommonComplete = has("runId") && has("status") && has("scriptUri");
|
|
31785
|
-
const
|
|
32095
|
+
const terminal2 = terminalStatuses.includes(value.status);
|
|
31786
32096
|
let valid;
|
|
31787
32097
|
if (value.action === "config") {
|
|
31788
32098
|
valid = has("ok") && has("harnessOptions") && has("omittedHarnesses") && has("models") && hasOnlyExactFields(value, ["action", "ok", "harnessOptions", "omittedHarnesses", "models"]);
|
|
31789
32099
|
} else if (value.action === "run") {
|
|
31790
32100
|
valid = value.status === "rejected" && has("validation") && hasOnlyExactFields(value, ["action", "status", "validation"]);
|
|
31791
32101
|
} else if (has("scriptSource")) {
|
|
31792
|
-
valid = runCommonComplete && has("limits") && (value.status === "running" ? hasOnlyFields(value, ["scriptSource"]) :
|
|
32102
|
+
valid = runCommonComplete && has("limits") && (value.status === "running" ? hasOnlyFields(value, ["scriptSource"]) : terminal2 && hasOnlyFields(value, ["scriptSource", ...executionDetailFields]));
|
|
32103
|
+
} else if (has("control")) {
|
|
32104
|
+
valid = runCommonComplete && inspectionComplete && value.stopped === false && value.alreadyTerminal === false && (value.status === "pending" || value.status === "running") && hasOnlyFields(value, [...inspectionFields, "stopped", "alreadyTerminal", "control"]);
|
|
31793
32105
|
} else if (has("stopped") || has("alreadyTerminal")) {
|
|
31794
32106
|
valid = runCommonComplete && inspectionComplete && has("stopped") && has("alreadyTerminal") && (value.status === "completed" || value.status === "failed" || value.status === "aborted") && hasOnlyFields(value, [...inspectionFields, "stopped", "alreadyTerminal"]);
|
|
31795
32107
|
} else if (has("wait")) {
|
|
31796
|
-
valid = runCommonComplete && inspectionComplete && hasOnlyFields(value, [...inspectionFields, "wait", "tokenUsage", "outcome"]) && (
|
|
32108
|
+
valid = runCommonComplete && inspectionComplete && hasOnlyFields(value, [...inspectionFields, "wait", "tokenUsage", "outcome"]) && (terminal2 ? has("outcome") : !has("outcome"));
|
|
31797
32109
|
} else {
|
|
31798
32110
|
valid = runCommonComplete && inspectionComplete && hasOnlyFields(value, inspectionFields);
|
|
31799
32111
|
}
|
|
@@ -31817,7 +32129,8 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
31817
32129
|
"wait",
|
|
31818
32130
|
"outcome",
|
|
31819
32131
|
"stopped",
|
|
31820
|
-
"alreadyTerminal"
|
|
32132
|
+
"alreadyTerminal",
|
|
32133
|
+
"control"
|
|
31821
32134
|
)
|
|
31822
32135
|
},
|
|
31823
32136
|
{
|
|
@@ -31837,7 +32150,8 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
31837
32150
|
"wait",
|
|
31838
32151
|
"outcome",
|
|
31839
32152
|
"stopped",
|
|
31840
|
-
"alreadyTerminal"
|
|
32153
|
+
"alreadyTerminal",
|
|
32154
|
+
"control"
|
|
31841
32155
|
)
|
|
31842
32156
|
},
|
|
31843
32157
|
{
|
|
@@ -31877,6 +32191,16 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
31877
32191
|
required: [...runOutputRequired, ...inspectionRequired, "stopped", "alreadyTerminal"],
|
|
31878
32192
|
properties: { status: { enum: ["completed", "failed", "aborted"] } },
|
|
31879
32193
|
...forbidsOutside([...inspectionFields, "stopped", "alreadyTerminal"])
|
|
32194
|
+
},
|
|
32195
|
+
{
|
|
32196
|
+
title: "Workflow stop pending",
|
|
32197
|
+
required: [...runOutputRequired, ...inspectionRequired, "stopped", "alreadyTerminal", "control"],
|
|
32198
|
+
properties: {
|
|
32199
|
+
status: { enum: nonterminalStatuses },
|
|
32200
|
+
stopped: { const: false },
|
|
32201
|
+
alreadyTerminal: { const: false }
|
|
32202
|
+
},
|
|
32203
|
+
...forbidsOutside([...inspectionFields, "stopped", "alreadyTerminal", "control"])
|
|
31880
32204
|
}
|
|
31881
32205
|
]
|
|
31882
32206
|
});
|
|
@@ -32142,9 +32466,9 @@ var AUTHORING_DOC_TOPICS = [
|
|
|
32142
32466
|
"workflow/run-lifecycle",
|
|
32143
32467
|
"workflow/examples"
|
|
32144
32468
|
],
|
|
32145
|
-
"bytes":
|
|
32146
|
-
"sha256": "
|
|
32147
|
-
"text": '# Workflow scripts: quickstart\n\n**Context:** JavaScript passed to the MCP `workflow` tool. This is not REPL code: workflow scripts use `agent(prompt, options?)`, allow top-level `return`, and start from a required metadata export.\n\nA workflow script is a deterministic orchestrator. Script code owns loops, fan-out, conditionals, aggregation, and checkpoints; `agent()` workers perform repository or research tasks. Workers start fresh sessions and do not share memory, so interpolate every prior result a later worker needs into its prompt.\n\n## Minimal valid script\n\n```js\nexport const meta = {\n name: "review-target",\n description: "Review a target and return concrete findings",\n phases: [{ title: "Review" }],\n};\n\nphase("Review");\nconst report = await agent(\n `Review ${args.target}. Read the relevant files and report concrete findings.`,\n { label: "review" },\n);\nreturn { report };\n```\n\nThe metadata export must be the first statement and a pure object literal. `name` and `description` are required non-empty strings. `phases`, when present, is an array of objects shaped `{ title: string, detail?: string, model?: string }`, never strings.\n\nSubmit the source without Markdown fences using the `workflow` tool\'s run form, with an absolute `projectDir` on the shared daemon. `args` is the JSON value supplied by the tool call. Some hosts may carry caller data as a JSON string, so harden scripts that accept external input:\n\n```js\nconst raw = typeof args === "string" ? (() => {\n try { return JSON.parse(args); } catch { return {}; }\n})() : args;\nconst input = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};\n```\n\n## Core rules\n\n- The DSL primitives are injected globals; do not import them.\n- Top-level `await` and top-level `return` are supported.\n- Scripts are JavaScript, not TypeScript.\n- No `require`, imports, filesystem API, network API, timers, `Date.now()`, `Math.random()`, or no-argument `Date` construction. Pass nondeterministic values through `args`.\n- Every `agent()` call should have a stable descriptive `label`.\n- A recoverable worker failure resolves to `null` after retries. Null-check load-bearing results.\n- `parallel()` takes thunks, not already-started promises:\n\n```js\nconst results = (await parallel([\n () => agent("Review correctness", { label: "review:correctness" }),\n () => agent("Review test coverage", { label: "review:coverage" }),\n])).filter(Boolean);\n```\n\n- Use a plain JSON Schema object in `schema` when script control flow depends on a worker result.\n- Return a compact JSON-serializable result; do not return a transcript.\n\n## Model selection\n\nOmit `model` for the server default, or use a backend-only value such as `"codex"` to retain that backend\'s configured default model. Before pinning a model id, `mode`, or `configOptions`, call `workflow` with `action:"config"`. After choosing a model, use `modelSpecs` to read that exact model\'s option domain. Set `mode` only when that selected harness entry\'s `modes.availableModes` explicitly lists the exact id; `modes:null` means the backend/model supports no modes, so omit `mode`. Never infer a generic `"default"` and never guess model or option ids.\n\n## Validation and execution\n\nEvery run is statically parsed, mock-executed, and checked against no-prompt backend configuration before admission. A rejection creates no run ID, reserves no background slot, and spends no tokens. Read the diagnostic, correct the script, and submit it again.\n\nUse foreground execution for short work. Use `background:true` for work that may outlive one tool request; retain the returned `runId`, then use bounded `await`, `inspect`, or `stop` calls.\n\n## What to read next\n\n- `workflow/composition-and-failure` \u2014 metadata, fan-out, phases, and null semantics.\n- `workflow/api-agents` \u2014 every `agent()` option and structured output.\n- `workflow/run-lifecycle` \u2014 config, run, await, inspect, stop, and resume.\n- `workflow/examples` \u2014 complete composition patterns.\n'
|
|
32469
|
+
"bytes": 4233,
|
|
32470
|
+
"sha256": "42ce5da7c5f57960b4cee8e0901c5ae6542a77a17dc5c0e4db8b97cf72544130",
|
|
32471
|
+
"text": '# Workflow scripts: quickstart\n\n**Context:** JavaScript passed to the MCP `workflow` tool. This is not REPL code: workflow scripts use `agent(prompt, options?)`, allow top-level `return`, and start from a required metadata export.\n\nA workflow script is a deterministic orchestrator. Script code owns loops, fan-out, conditionals, aggregation, and checkpoints; `agent()` workers perform repository or research tasks. Workers start fresh sessions and do not share memory, so interpolate every prior result a later worker needs into its prompt.\n\n## Minimal valid script\n\n```js\nexport const meta = {\n name: "review-target",\n description: "Review a target and return concrete findings",\n phases: [{ title: "Review" }],\n};\n\nphase("Review");\nconst report = await agent(\n `Review ${args.target}. Read the relevant files and report concrete findings.`,\n { label: "review" },\n);\nreturn { report };\n```\n\nThe metadata export must be the first statement and a pure object literal. `name` and `description` are required non-empty strings. `phases`, when present, is an array of objects shaped `{ title: string, detail?: string, model?: string }`, never strings.\n\nSubmit the source without Markdown fences using the `workflow` tool\'s run form, with an absolute `projectDir` on the shared daemon. `args` is the JSON value supplied by the tool call. Some hosts may carry caller data as a JSON string, so harden scripts that accept external input:\n\n```js\nconst raw = typeof args === "string" ? (() => {\n try { return JSON.parse(args); } catch { return {}; }\n})() : args;\nconst input = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};\n```\n\n## Core rules\n\n- The DSL primitives are injected globals; do not import them.\n- Top-level `await` and top-level `return` are supported.\n- Scripts are JavaScript, not TypeScript.\n- No `require`, imports, filesystem API, network API, timers, `Date.now()`, `Math.random()`, or no-argument `Date` construction. Pass nondeterministic values through `args`.\n- Every `agent()` call should have a stable descriptive `label`.\n- A recoverable worker failure resolves to `null` after retries. Null-check load-bearing results.\n- `parallel()` takes thunks, not already-started promises:\n\n```js\nconst results = (await parallel([\n () => agent("Review correctness", { label: "review:correctness" }),\n () => agent("Review test coverage", { label: "review:coverage" }),\n])).filter(Boolean);\n```\n\n- Use a plain JSON Schema object in `schema` when script control flow depends on a worker result.\n- Return a compact JSON-serializable result; do not return a transcript.\n\n## Model selection\n\nOmit `model` for the server default, or use a backend-only value such as `"codex"` to retain that backend\'s configured default model. When `AGENTPRISM_DEFAULT_BACKEND` is truly unset, the MCP server probes backend readiness without prompting, pins one project default before validation/execution, and keeps that backend for the run and resume; an explicit environment default always wins. Before pinning a model id, `mode`, or `configOptions`, call `workflow` with `action:"config"`. After choosing a model, use `modelSpecs` to read that exact model\'s option domain. Set `mode` only when that selected harness entry\'s `modes.availableModes` explicitly lists the exact id; `modes:null` means the backend/model supports no modes, so omit `mode`. Never infer a generic `"default"` and never guess model or option ids.\n\n## Validation and execution\n\nEvery run is statically parsed, mock-executed, and checked against no-prompt backend configuration before admission. A rejection creates no run ID, reserves no background slot, and spends no tokens. Read the diagnostic, correct the script, and submit it again.\n\nUse foreground execution for short work. Use `background:true` for work that may outlive one tool request; retain the returned `runId`, then use bounded `await`, `inspect`, or `stop` calls.\n\n## What to read next\n\n- `workflow/composition-and-failure` \u2014 metadata, fan-out, phases, and null semantics.\n- `workflow/api-agents` \u2014 every `agent()` option and structured output.\n- `workflow/run-lifecycle` \u2014 config, run, await, inspect, stop, and resume.\n- `workflow/examples` \u2014 complete composition patterns.\n'
|
|
32148
32472
|
},
|
|
32149
32473
|
{
|
|
32150
32474
|
"id": "workflow/run-lifecycle",
|
|
@@ -32157,9 +32481,9 @@ var AUTHORING_DOC_TOPICS = [
|
|
|
32157
32481
|
"workflow/determinism-and-resume",
|
|
32158
32482
|
"workflow/models-and-config"
|
|
32159
32483
|
],
|
|
32160
|
-
"bytes":
|
|
32161
|
-
"sha256": "
|
|
32162
|
-
"text": '## Running workflows \u2014 the MCP `workflow` tool\n\n**Context:** JavaScript passed to the MCP `workflow` tool. Workflow scripts use `agent(prompt, options?)`; REPL evals use a different API.\n\nUse the connected `workflow` tool for deterministic batch orchestration. The shared server daemon owns execution, so admitted runs survive MCP client session churn and tool-request timeouts;
|
|
32484
|
+
"bytes": 7889,
|
|
32485
|
+
"sha256": "3ad743e112881c25d4ac12e5f2795ec7e810c571a1141b492228a33ab4cc5276",
|
|
32486
|
+
"text": '## Running workflows \u2014 the MCP `workflow` tool\n\n**Context:** JavaScript passed to the MCP `workflow` tool. Workflow scripts use `agent(prompt, options?)`; REPL evals use a different API.\n\nUse the connected `workflow` tool for deterministic batch orchestration. The shared server daemon owns execution, so admitted runs survive MCP client session churn and tool-request timeouts. During a version upgrade, the successor becomes the front door while a predecessor may remain the execution owner; signed run-control forwarding keeps later-session stop/cancel operations location-independent. Owner-process exit can still interrupt in-flight work. Any later session can await, inspect, or stop a run. Runs, journals, logs, and outstanding whole-stop intents persist per project namespace.\n\nEvery `config` and `run` call on the shared daemon names its project with the required `projectDir` argument \u2014 an absolute path, normally the workspace root. `inspect`/`await`/`stop` take only a `runId`; the run ID locates its project store automatically. In a single-project server, `projectDir` defaults to that server\'s project.\n\n### The `workflow` tool, by action\n\n- **Config** (`{ action: "config", projectDir, harnesses?, modelSpecs?, modelFilter? }`): discover live model, mode, effort, and `configOptions` values from no-prompt backend sessions. Use `harnesses` plus `modelFilter` to find ids, then `modelSpecs` to select exact models and read their model-specific option domains. Each successful entry reports `modes` explicitly: use only exact ids in `modes.availableModes`; `modes:null` means omit `mode`, never guess a default. It starts no workflow and spends zero tokens. Use it only when pinning those values; an omitted model or backend-only model uses configured defaults without discovery.\n- **Run** (default, no `action`): supply exactly one of `script` (the raw source string, no Markdown fences) or `scriptPath` (an absolute path on the server\'s filesystem), plus `projectDir`. The tool automatically performs static validation, a mocked dry run, and routed config checks before admission. Invalid scripts return bounded `status:"rejected"` diagnostics with no run ID, background slot, or token spend. A path is read once at admission and its content snapshotted; later edits affect only a new run. `args` arrives in the script as the `args` global; the run\'s base directory is the `cwd` global. Some hosts hand `args` through as a JSON **string** \u2014 tolerate both shapes (`typeof args === "string" ? JSON.parse(args) : args`). Foreground streams progress but is bound to the request and its timeout. Pass `background: true` for anything that may outlive one request; it acknowledges after durable admission with a `runId`.\n- **Await** (`{ action: "await", runId, waitMs }`): bounded collection for background runs. A timeout is progress, not failure \u2014 call again (`waitMs: 20000` is typical). At terminal status the response adds `outcome`: the authored result or pause context, plus `replayEligibility`, `resumeReport`, `fallbacks`, and `checkpointsTaken`.\n- **Inspect** (`{ action: "inspect", runId, lastN, labelGlob, logLines }`): a bounded snapshot \u2014 the latest matching calls with compact result previews plus the newest log lines. Use a narrow `labelGlob` to diagnose before deciding whether to resume, edit, or stop. Inspection never executes or resumes a script.\n- **Stop**: `{ action: "stop", runId }` durably aborts the whole run and normally returns its final snapshot; stopping a terminal run is a successful no-op. Across a daemon upgrade, the successor persists an idempotent stop intent and forwards to the predecessor that owns execution. If that owner does not settle within the bounded control wait, the successful response remains nonterminal with `control:{ state:"pending", operationId, requestedAt, owner? }`; retry stop, inspect, or await to observe settlement. `{ action: "stop", runId, callIndex }` synchronously routes to the live owner and cancels exactly that in-flight agent: its slot settles to `null` with `AGENT_CANCELLED` and the run stays live; call cancellation is never reconstructed after owner loss. Whole-run `{ action:"stop", runId, forceOwner:true }` explicitly authorizes terminating a superseded owner daemon when graceful control cannot settle and may interrupt sibling runs in that process; it is forbidden with `callIndex`. `labelGlob` only filters the returned snapshot; it never selects what to cancel.\n- **Resume**: a NEW run with `resumeFromRunId` plus the script content re-sent (the same `script` or `scriptPath`) and the desired `args` (+ `checkpointReplies` when answering a durable checkpoint). Read the returned `replayEligibility` for the predicted and observed replay prefix; never assume a prefix hit. Full semantics: **Determinism and resume**.\n\n### Operating rules\n\n- **Always retain the returned `runId`.** A paused, failed, or aborted response carries a redacted final-20 `logTail`. Read it before you change anything. Every admitted script is also an immutable resource at `workflow://runs/{runId}/script`, so a later session can recover a lost inline script.\n- **Two fingerprints control replay.** The identity hash covers the prompt, the resolved model, `mode` when set, non-empty sorted `configOptions`, `tier`, `phase`, `agentType`, the resolved agent definition, and the schema. The input fingerprint covers the resolved label, per-call `cwd` and isolation, `keepSession`, images, MCP servers, session/prompt metadata, and the approved script-backend digest.\n- **Operational bounds are not replay inputs.** Host `concurrency`, `agentRetries`, and `agentTimeoutMs`, plus per-call `timeoutMs` and `retries`, enter neither fingerprint. A resume does not inherit them from its source run; pass the values you want on every run. `agentTimeoutMs` caps the wall-clock time of each attempt; it is not an idle timer. A per-call `timeoutMs` can tighten that ceiling but cannot escape it. Each retry gets a fresh clock, so the envelope is `(resolved retries + 1) \xD7 resolved timeout`, with retries clamped to 3.\n- **Old journals stay usable.** Input formats below 2 replay positionally with `fallbackReason: "inputs-format-legacy"`. A current-format crash snapshot uses identity matching even without terminal-environment capture. Ancestor-scoped rows carried from \u22640.23 resume chains replay only while that ancestor run is still persisted. Journals resume across filesystem, environment, engine, Node, and V8 changes; `replayEligibility` reports those differences as diagnostics, never as gates.\n- **A background start returns immediately.** It sends no progress after it returns; collect progress with later bounded awaits. Background runs have no live checkpoint channel, so authored `headless` checkpoint modes apply. When a run\'s owner process dies, a pending whole-stop intent is applied under the reclaimed lease; otherwise cold preflights reconcile stale `pending`/`running` state to `paused` with `pauseReason:"interrupted"`. A live owner lease is never stolen because of a timeout.\n- A run paused with `reason: "auth_required"` resumes as a new run after that backend\'s credentials are configured.\n\n### Execution logs \u2014 the events resource\n\nEvery journaling run publishes an MCP resource at `workflow://runs/{runId}/events`. Subscribe to the canonical URI for advisory `resources/updated` hints, then read and paginate with `after`, `limit`, and `streamId`. Progress is coarse and redacted: `agentTranscript` rows are assistant/tool upserts partitioned by `(scope, callIndex, executionStartSeq)` and reduced by greatest revision per entry index. The durable cursor is authoritative when hints coalesce or a subscriber falls behind.\n\nEmbedding hosts can drive the same contract with `runDynamicWorkflow` / `WorkflowManager` from `@automatalabs/workflows`; the script contract is identical either way.\n'
|
|
32163
32487
|
},
|
|
32164
32488
|
{
|
|
32165
32489
|
"id": "workflow/models-and-config",
|
|
@@ -32172,9 +32496,9 @@ var AUTHORING_DOC_TOPICS = [
|
|
|
32172
32496
|
"workflow/environment-and-tools",
|
|
32173
32497
|
"workflow/run-lifecycle"
|
|
32174
32498
|
],
|
|
32175
|
-
"bytes":
|
|
32176
|
-
"sha256": "
|
|
32177
|
-
"text": '## Choosing the agent for each call\n\n**Context:** JavaScript passed to the MCP `workflow` tool. Workflow scripts use `agent(prompt, options?)`; REPL evals use a different API.\n\nThe backend is selected **per `agent()` call** from its effective `model` string. One script can plan on one vendor\'s agent, implement on another\'s, and review on a third\'s, handing structured results between them.\n\nThe built-in names (`claude`, `codex`, `opencode`, `pi`) come from the runtime backend registry. Registered custom names extend that set.\n\n- **Omit `model` entirely** for maximum portability
|
|
32499
|
+
"bytes": 9604,
|
|
32500
|
+
"sha256": "21ea6e619a1e0220c60cba74f8323fb5ae00db29b06fe2858d27e750d80c5795",
|
|
32501
|
+
"text": '## Choosing the agent for each call\n\n**Context:** JavaScript passed to the MCP `workflow` tool. Workflow scripts use `agent(prompt, options?)`; REPL evals use a different API.\n\nThe backend is selected **per `agent()` call** from its effective `model` string. One script can plan on one vendor\'s agent, implement on another\'s, and review on a third\'s, handing structured results between them.\n\nThe built-in names (`claude`, `codex`, `opencode`, `pi`) come from the runtime backend registry. Registered custom names extend that set.\n\n- **Omit `model` entirely** for maximum portability. In the MCP server, an explicitly present `AGENTPRISM_DEFAULT_BACKEND` wins; when it is truly unset, the first model-less run for a project performs zero-token backend readiness probes, pins one effective backend before validation/execution, and reuses that pin for the run and resume. The SDK runner itself retains its configured default (`AGENTPRISM_DEFAULT_BACKEND`, historical fallback Claude). A script with no model specs remains backend-portable.\n- **Route by one registered first segment.** Split on the first `/`; ASCII-case-insensitive `claude`, `codex`, `opencode`, `pi`, or a registered custom backend name selects that harness and is stripped exactly once. A custom registration wins on a built-in-name collision.\n- **Use a backend name alone** (`claude`, `codex`, `opencode`, `pi`, or a custom name) to preserve the harness\'s configured default model. No model config call is made.\n- **Everything else goes intact to the default backend.** `anthropic/\u2026`, `openai/\u2026`, bare `opus`, and bare `gpt-\u2026` are not routing aliases. When an id remains after routing, it is sent byte-for-byte: no catalog matching, case folding, bracket parsing, effort/Fast option driving, retry, or fallback. Harness rejection is an agent error.\n- **`tier`** (`"small" | "medium" | "big"`) is a coarse alternative resolved from the host\'s tier config \u2014 use it for "a cheap model" without naming a vendor.\n\nThe published examples use ids verified against live harness catalogs: `claude/opus[1m]`, `codex/gpt-5.6-sol`, and `opencode/zai/glm-5.2`. For Pi, `pi/openrouter/vendor/model-id` strips only `pi/`; Pi then splits provider `openrouter` from model id `vendor/model-id`. Prefer backend-only forms when the desired model is configured inside the harness.\n\nNever guess model ids, mode ids, effort values, or option names from memory. With MCP, call the `workflow` tool using `action:"config"` and optional `harnesses` / `modelFilter`; it returns the live catalog without starting a workflow.\n\nOne no-prompt session per harness, zero tokens: each successful harness entry contains `modes` plus its config-option catalog. A non-null `modes` object carries `currentModeId` and `availableModes`; only those exact advertised ids are valid. `modes:null` explicitly means that backend/model supports no ACP session modes, so omit `mode`\u2014absence never licenses an invented generic `"default"`. Config options list model ids (including bracket variants like `opus[1m]`), effort levels, and every other negotiable option exactly as the installed harness advertises them. `probed:true` means session/config discovery succeeded, **not** that every backend has proven it can authenticate a first prompt: ACP has no universal zero-token auth-status method, and some agents defer that check. Automatic MCP default selection treats failed probes and explicitly empty built-in model catalogs as unavailable, prefers stronger session-open evidence (Codex authorization; Pi\'s credential-filtered catalog), then falls back to the first session-ready backend whose prompt readiness is unknown. One additional caveat: the bare `config` probe reads each harness with its **default model** selected, and option domains are **model-specific**. An option can appear only after a particular model is selected. Ceilings differ per model. Provider-served variants of the same model can advertise different domains. The authoritative per-model probe is the validator run on your real script: it selects each authored model spec first and echoes that pair\'s advertised modes and options. Confirm every pinned value against its own echoed entry; do not read package internals to discover options.\n\n```js\nconst plan = await agent(PLAN_PROMPT, { label: "plan", model: "opencode/zai/glm-5.2", schema: PLAN });\nconst impl = await agent(implPrompt(plan), { label: "implement", model: "codex/gpt-5.6-sol" });\nconst review = await agent(reviewPrompt(impl), { label: "review", model: "claude/opus[1m]", schema: REVIEW });\n```\n\nUse `configOptions` only for exact ACP session options advertised by that routed harness. With MCP, read the selected harness\'s `action:"config"` result before choosing ids or select values; catalogs vary by harness version, login, and machine.\n\n```js\nconst impl = await agent(implPrompt(plan), {\n label: "implement",\n model: "codex",\n configOptions: { "fast-mode": true, reasoning_effort: "high" },\n});\n```\n\nIds and string/boolean values pass through verbatim in ascending id order, after model selection and before the prompt. There are no aliases, coercion, client-side vocabulary, defaults, or cached catalogs. Copy option ids character-for-character from the catalog, punctuation included \u2014 `"fast-mode"`, not `fast_mode` \u2014 and quote ids that are not valid identifiers. Never put `"model"` in `configOptions`; use the dedicated `model` field. A harness rejection follows the ordinary agent-error path.\n\nPi\'s thought-level option is named `thinkingLevel`, and its choices depend on the exact model in the same call:\n\n```js\nconst review = await agent(REVIEW_PROMPT, {\n label: "pi-review",\n model: "pi/openrouter/vendor/model-id",\n configOptions: { thinkingLevel: "high" },\n});\n```\n\nValidation selects `openrouter/vendor/model-id` before reading Pi\'s choices. A listed value passes unchanged. A recognized value above an ordered model\'s ceiling, or in a model-specific gap, passes with a warning that names the effective clamp target. Pi advertises its SDK-derived domain directly. Claude and Codex are also ordered: when their options omit domain metadata, validation enumerates the advertised models and merges their per-model effort orders. A Claude model without an `effort` option does not support effort, and `default` never becomes a ceiling target. OpenCode and custom backends have no declared value order, so validation is exact-set. An unrecognized or unadvertised value fails with exit code `2`. Enumeration stops at 32 advertised models; a larger or inconsistently ordered catalog warns and falls back to exact advertised-value validation.\n\n**The harness is authoritative.** The client never substitutes a nearby model or silently falls back. A rejected id follows the existing agent-error path; a harness that accepts or ignores it determines the outcome. The public `fallbacks`/`onModelFallback` fields remain for compatibility but model resolution does not emit them.\n\n## Structured output\n\nPass `schema` \u2014 a **plain JSON Schema object literal** (no schema builders exist inside the realm) \u2014 and the call resolves to a **validated object** instead of text:\n\n```js\nconst FINDINGS = {\n type: "object",\n additionalProperties: false,\n required: ["findings"],\n properties: {\n findings: {\n type: "array",\n items: {\n type: "object",\n additionalProperties: false,\n required: ["file", "line", "summary"],\n properties: {\n file: { type: "string", description: "Repo-relative path \u2014 copy it exactly, never invent one" },\n line: { type: "number", description: "1-indexed line the finding anchors to" },\n summary: { type: "string", description: "One sentence stating the defect, grounded in code you actually read" },\n },\n },\n },\n },\n};\n\nconst report = await agent("Review the diff on this branch for correctness bugs.", {\n label: "review", schema: FINDINGS,\n});\nreport.findings.forEach((f) => log(`${f.file}:${f.line} ${f.summary}`));\n```\n\nThe same schema works on **every** backend; only the fulfillment channel differs, and the runner picks it for you: Claude uses its `outputFormat`, Codex its strict `outputSchema`, while Pi, OpenCode, and eligible custom ACP agents receive a client-hosted `StructuredOutput` MCP tool when they advertise HTTP MCP support. Pi accepts stdio, Streamable HTTP, and SSE MCP servers. If no valid tool capture exists, Pi retains the runner\'s common prompt-embedded schema and validated final-text JSON fallback. In every channel the runner validates the value client-side (with type coercion) and re-prompts a bounded number of times before failing the call with non-recoverable `SCHEMA_NONCOMPLIANCE`.\n\nSchema authoring rules that keep all channels healthy:\n\n- Root must be an object; set `additionalProperties: false` and list every property in `required`.\n- Put a `description` on every field \u2014 descriptions are the per-field prompt.\n- Keep schemas structurally simple. Exotic keywords (`oneOf`, `patternProperties`, unusual `format`s, backreference regexes) are normalized or stripped on the wire for some backends \u2014 validation still enforces them client-side, which shows up as re-prompt churn. Prefer `anyOf`, `enum`, and plain types.\n- Keep free-text fields small (tens of lines). An oversized structured output can exhaust schema repair and fail the call.\n- Validation checks structure, not truth. Check load-bearing values in script code (for example, reject findings whose `file` is not in a known file list) before spending more agents on them.\n'
|
|
32178
32502
|
},
|
|
32179
32503
|
{
|
|
32180
32504
|
"id": "workflow/composition-and-failure",
|
|
@@ -32247,9 +32571,9 @@ var AUTHORING_DOC_TOPICS = [
|
|
|
32247
32571
|
"workflow/environment-and-tools",
|
|
32248
32572
|
"workflow/api-control-flow"
|
|
32249
32573
|
],
|
|
32250
|
-
"bytes":
|
|
32251
|
-
"sha256": "
|
|
32252
|
-
"text": '# Workflow agent API reference\n\n**Context:** JavaScript passed to the MCP `workflow` tool. Workflow scripts use `agent(prompt, options?)`; REPL evals use a different API.\n\n## `agent(prompt, options?)` \u2014 full option table\n\nReturns the agent\'s final assistant text, or the schema-validated object when `schema` is set. Resolves to `null` when a *recoverable* failure survives all retries.\n\n| option | type | meaning |\n|---|---|---|\n| `label` | `string` | Display/telemetry name; also stamped on every live ACP event for this call. Always set it. Not part of the resume hash. |\n| `phase` | `string` | Assign this call to a phase explicitly (needed inside concurrent stages where the global `phase()` state would race). |\n| `schema` | JSON Schema object | Structured output. Plain object literal only \u2014 no schema builders exist in the realm. Part of the resume hash. |\n| `model` | `string` | Model spec: optional registered harness prefix plus a verbatim id, or a backend-only name. See [Model specs & routing](#model-specs--routing). Part of the resume hash. |\n| `tier` | `"small" \\| "medium" \\| "big"` | Coarse tier resolved from host config; beats phase/meta model, loses to explicit `model`. Part of the resume hash. |\n| `mode` | `string` | ACP session mode id advertised by the selected backend/model. **Strict**: unsupported/unadvertised ids fail before prompting (and automatic workflow preflight rejects them before admission). Read the selected `action:"config"` entry\'s `modes.availableModes` and copy only an exact id; `modes:null` means omit this field. Never infer a generic `"default"`. Part of the resume hash when set. |\n| `configOptions` | `Record<string, string \\| boolean>` | Exact ACP session option ids and authored values. Applied in ascending id order after model and before the prompt, with no aliases or coercion. `"model"` is reserved for the dedicated `model` field. Part of the resume hash only when non-empty, with sorted keys. With MCP, read the advertised-options table from `workflow` action `config` before choosing values. |\n| `agentType` | `string` | Bind a named subagent definition (tools allow/deny, model, isolation, role prompt). See [agentType definitions](#agenttype-definitions). Part of the resume hash. |\n| `isolation` | `"worktree"` | Run in a throwaway git worktree branched from the run cwd. **Always removed (worktree + branch) when the call ends** \u2014 edits are discarded; return work as data. Degrades to the shared tree outside a git repo (logged). |\n| `resume` | `{ filesystem: "read-only" }` | Deprecated compatibility annotation. It is recorded as legacy diagnostic provenance, is not sent to the runner or hashed, and has no effect on replay. New scripts should omit it. |\n| `cwd` | `string` | Per-session working directory; relative resolves against the run\'s base cwd. Overridden by worktree isolation. Not hashed. |\n| `timeoutMs` | `number \\| null` | Total wall-clock cap for each attempt. A finite value may tighten a finite host `agentTimeoutMs` ceiling but cannot raise or disable it. With no host ceiling, a finite value applies and `null`/omitted is uncapped. |\n| `retries` | `number` | Retries after *recoverable* failures (default 0, host-overridable). Exhausted retries \u21D2 the call resolves `null`. |\n| `mcpServers` | `McpServerConfig[]` | MCP servers attached to this session. Stdio shape: `{ name, command, args: [], env: [{ name, value }] }` (`args`/`env` required, `env` is name/value pairs, not a map); `{ type: "http" \\| "sse", name, url, headers: [] }` also accepted. Not hashed. |\n| `images` | `PromptImage[]` | Base64 image blocks appended to the prompt; backends without image support get a bracketed text note. Not hashed. |\n| `meta` | `object` | ACP `_meta` merged into `session/new` \u2014 session-scoped extension passthrough (pairs with custom backends). Not hashed. |\n| `promptMeta` | `object` | ACP `_meta` merged into `session/prompt` \u2014 turn-scoped passthrough. Backend-computed keys win on conflict. Not hashed. |\n| `keepSession` | `boolean` | Skip release-time best-effort `session/close`; the non-secret re-attach record lands in `WorkflowRunResult.agentSessions` for host-side `loadSession()` / `resumeSession()`. Usage/auth pause failures are kept open automatically for managed continuation. Not identity-hashed; included in the input fingerprint. |\n\nThe timeout clock measures the whole attempt, including backend startup, model/config setup, tool\nwork, and streamed output; it is not an idle timer. Each retry starts a fresh clock, so the maximum\ntimeout envelope is `(retries + 1) \xD7 resolved timeoutMs` (retries are clamped to 3). An exhausted\ntimeout is recoverable `AGENT_TIMEOUT`: the call resolves to `null`, releases its concurrency slot,\nand asks the ACP session to cancel. A session that keeps running after the cancellation grace is\nclosed where supported and its pooled child is recycled.\n\nEvery new run, including one admitted with `resumeFromRunId`, resolves host limits from that run\'s\nrequest. It does not inherit `agentTimeoutMs`, retries, concurrency, or agent-count values from\nits source, so pass every operational bound the resumed execution should use.\n\n## Model specs & routing\n\nA `model` string is resolved solely from its first segment, then delegated to the harness:\n\n| spec shape | routes to | notes |\n|---|---|---|\n| *(omitted)* | host
|
|
32574
|
+
"bytes": 8551,
|
|
32575
|
+
"sha256": "84d2cb5639a385074b815fa8e13214138d1d59fa3f7fd34f206e4f9ce19d37d0",
|
|
32576
|
+
"text": '# Workflow agent API reference\n\n**Context:** JavaScript passed to the MCP `workflow` tool. Workflow scripts use `agent(prompt, options?)`; REPL evals use a different API.\n\n## `agent(prompt, options?)` \u2014 full option table\n\nReturns the agent\'s final assistant text, or the schema-validated object when `schema` is set. Resolves to `null` when a *recoverable* failure survives all retries.\n\n| option | type | meaning |\n|---|---|---|\n| `label` | `string` | Display/telemetry name; also stamped on every live ACP event for this call. Always set it. Not part of the resume hash. |\n| `phase` | `string` | Assign this call to a phase explicitly (needed inside concurrent stages where the global `phase()` state would race). |\n| `schema` | JSON Schema object | Structured output. Plain object literal only \u2014 no schema builders exist in the realm. Part of the resume hash. |\n| `model` | `string` | Model spec: optional registered harness prefix plus a verbatim id, or a backend-only name. See [Model specs & routing](#model-specs--routing). Part of the resume hash. |\n| `tier` | `"small" \\| "medium" \\| "big"` | Coarse tier resolved from host config; beats phase/meta model, loses to explicit `model`. Part of the resume hash. |\n| `mode` | `string` | ACP session mode id advertised by the selected backend/model. **Strict**: unsupported/unadvertised ids fail before prompting (and automatic workflow preflight rejects them before admission). Read the selected `action:"config"` entry\'s `modes.availableModes` and copy only an exact id; `modes:null` means omit this field. Never infer a generic `"default"`. Part of the resume hash when set. |\n| `configOptions` | `Record<string, string \\| boolean>` | Exact ACP session option ids and authored values. Applied in ascending id order after model and before the prompt, with no aliases or coercion. `"model"` is reserved for the dedicated `model` field. Part of the resume hash only when non-empty, with sorted keys. With MCP, read the advertised-options table from `workflow` action `config` before choosing values. |\n| `agentType` | `string` | Bind a named subagent definition (tools allow/deny, model, isolation, role prompt). See [agentType definitions](#agenttype-definitions). Part of the resume hash. |\n| `isolation` | `"worktree"` | Run in a throwaway git worktree branched from the run cwd. **Always removed (worktree + branch) when the call ends** \u2014 edits are discarded; return work as data. Degrades to the shared tree outside a git repo (logged). |\n| `resume` | `{ filesystem: "read-only" }` | Deprecated compatibility annotation. It is recorded as legacy diagnostic provenance, is not sent to the runner or hashed, and has no effect on replay. New scripts should omit it. |\n| `cwd` | `string` | Per-session working directory; relative resolves against the run\'s base cwd. Overridden by worktree isolation. Not hashed. |\n| `timeoutMs` | `number \\| null` | Total wall-clock cap for each attempt. A finite value may tighten a finite host `agentTimeoutMs` ceiling but cannot raise or disable it. With no host ceiling, a finite value applies and `null`/omitted is uncapped. |\n| `retries` | `number` | Retries after *recoverable* failures (default 0, host-overridable). Exhausted retries \u21D2 the call resolves `null`. |\n| `mcpServers` | `McpServerConfig[]` | MCP servers attached to this session. Stdio shape: `{ name, command, args: [], env: [{ name, value }] }` (`args`/`env` required, `env` is name/value pairs, not a map); `{ type: "http" \\| "sse", name, url, headers: [] }` also accepted. Not hashed. |\n| `images` | `PromptImage[]` | Base64 image blocks appended to the prompt; backends without image support get a bracketed text note. Not hashed. |\n| `meta` | `object` | ACP `_meta` merged into `session/new` \u2014 session-scoped extension passthrough (pairs with custom backends). Not hashed. |\n| `promptMeta` | `object` | ACP `_meta` merged into `session/prompt` \u2014 turn-scoped passthrough. Backend-computed keys win on conflict. Not hashed. |\n| `keepSession` | `boolean` | Skip release-time best-effort `session/close`; the non-secret re-attach record lands in `WorkflowRunResult.agentSessions` for host-side `loadSession()` / `resumeSession()`. Usage/auth pause failures are kept open automatically for managed continuation. Not identity-hashed; included in the input fingerprint. |\n\nThe timeout clock measures the whole attempt, including backend startup, model/config setup, tool\nwork, and streamed output; it is not an idle timer. Each retry starts a fresh clock, so the maximum\ntimeout envelope is `(retries + 1) \xD7 resolved timeoutMs` (retries are clamped to 3). An exhausted\ntimeout is recoverable `AGENT_TIMEOUT`: the call resolves to `null`, releases its concurrency slot,\nand asks the ACP session to cancel. A session that keeps running after the cancellation grace is\nclosed where supported and its pooled child is recycled.\n\nEvery new run, including one admitted with `resumeFromRunId`, resolves host limits from that run\'s\nrequest. It does not inherit `agentTimeoutMs`, retries, concurrency, or agent-count values from\nits source, so pass every operational bound the resumed execution should use.\n\n## Model specs & routing\n\nA `model` string is resolved solely from its first segment, then delegated to the harness:\n\n| spec shape | routes to | notes |\n|---|---|---|\n| *(omitted)* | host-pinned/default backend | MCP: explicit `AGENTPRISM_DEFAULT_BACKEND` wins; when truly unset, zero-token readiness discovery pins one project default before validation/execution and preserves it across resume. SDK runner: configured default, historical fallback `claude`. The selected harness keeps its session default model. Most portable. |\n| `claude`, `codex`, `opencode`, `pi`, or `<custom-name>` | that registered harness | Backend-only: no model config call; the harness default remains active. |\n| `claude/<id>`, `codex/<id>`, `opencode/<id>`, `pi/<id>`, or `<custom-name>/<id>` | that registered harness | Match the first segment ASCII-case-insensitively and strip exactly one segment. Custom names take priority on collision. The remaining `<id>` is sent verbatim, including further `/` characters. For Pi, that remainder is its `<provider>/<model-id>` and Pi preserves any further slashes in the model id. |\n| any other string, including `anthropic/\u2026`, `openai/\u2026`, bare `opus`, or bare `gpt-\u2026` | host default backend | The **entire** authored string is sent verbatim; these are not routing aliases. |\n\nSelection is a single `session/set_config_option` with `configId: "model"` and the exact remaining string. There is no catalog matching, case folding, normalization, bracket parsing, nearest-neighbor selection, sibling effort/Fast option driving, retry, or echo verification. Brackets, dots, and provider-style prefixes are ordinary model-id characters.\n\nWhatever the harness returns is the outcome. A rejection follows the existing agent-error path with no resolution-specific code or model fallback event. `onModelFallback` and `WorkflowRunResult.fallbacks` remain public compatibility surfaces; model resolution does not emit entries, while pause recovery emits `kind: "continuation"` reattach/skip notices.\n\n## Structured output channels\n\nOne author API (`schema`), four fulfillment paths \u2014 chosen automatically per backend:\n\n| backend | channel |\n|---|---|\n| Claude | native `outputFormat`, schema normalized to Anthropic\'s structured-outputs subset (e.g. `oneOf` \u2192 `anyOf`; unsupported keywords/formats stripped on the wire) |\n| Codex | native strict `outputSchema` (OpenAI strict subset normalization) |\n| Pi | a client-hosted `StructuredOutput` MCP tool injected when the agent advertises HTTP MCP support; common prompt-embedded schema and validated final-text JSON fallback |\n| OpenCode / custom ACP | a client-hosted **`StructuredOutput` MCP tool** injected into the session when the agent advertises HTTP MCP support (an agent may show it as `structured_output_StructuredOutput`); otherwise prompt-embedded schema + JSON parse of the final message. Custom backends can opt out of tool injection with `structuredOutputTool: false`. |\n\nPi accepts stdio, Streamable HTTP, and SSE MCP servers; ACP-transport MCP hosting remains client-side.\n\nIn every channel the runner coerces + validates client-side and re-prompts a bounded number of times; the final miss fails the call with non-recoverable `SCHEMA_NONCOMPLIANCE`. Constraints stripped from the wire are still enforced client-side \u2014 an exotic schema keyword shows up as re-prompt churn, so keep schemas simple.\n'
|
|
32253
32577
|
},
|
|
32254
32578
|
{
|
|
32255
32579
|
"id": "workflow/api-control-flow",
|
|
@@ -33430,11 +33754,13 @@ var WorkflowScriptResources = class {
|
|
|
33430
33754
|
this.modernNotifier = modernNotifier;
|
|
33431
33755
|
this.router = source instanceof WorkflowManager2 ? singleStoreRouter(source) : source.router;
|
|
33432
33756
|
this.detachRunDeleted = this.router.onRunDeleted(this.onRunDeleted);
|
|
33757
|
+
this.detachRunStopped = this.router.onRunStopped(({ runId }) => this.cancelPendingElicitation(runId));
|
|
33433
33758
|
const previousOnClose = this.mcp.server.onclose;
|
|
33434
33759
|
this.mcp.server.onclose = () => {
|
|
33435
33760
|
for (const controller of this.elicitationControllers.values()) controller.abort();
|
|
33436
33761
|
this.elicitationControllers.clear();
|
|
33437
33762
|
for (const uri of [...this.eventSubscriptions.keys()]) this.closeEventSubscription(uri);
|
|
33763
|
+
this.detachRunStopped();
|
|
33438
33764
|
this.detachRunDeleted();
|
|
33439
33765
|
previousOnClose?.();
|
|
33440
33766
|
};
|
|
@@ -33444,6 +33770,7 @@ var WorkflowScriptResources = class {
|
|
|
33444
33770
|
modernNotifier;
|
|
33445
33771
|
router;
|
|
33446
33772
|
detachRunDeleted;
|
|
33773
|
+
detachRunStopped;
|
|
33447
33774
|
subscriptions = /* @__PURE__ */ new Set();
|
|
33448
33775
|
externalReaders = /* @__PURE__ */ new Map();
|
|
33449
33776
|
eventSubscriptions = /* @__PURE__ */ new Map();
|
|
@@ -33832,6 +34159,43 @@ var WorkflowScriptResources = class {
|
|
|
33832
34159
|
}
|
|
33833
34160
|
};
|
|
33834
34161
|
|
|
34162
|
+
// ../mcp-server/src/workflow-stop.ts
|
|
34163
|
+
function requireDurableStoppedRun(manager, runId) {
|
|
34164
|
+
const persistence = manager.getPersistence();
|
|
34165
|
+
const persisted = persistence.load(runId);
|
|
34166
|
+
if (persisted?.status !== "aborted") {
|
|
34167
|
+
throw new ProtocolError(
|
|
34168
|
+
ProtocolErrorCode.InternalError,
|
|
34169
|
+
`Workflow stop for runId "${runId}" could not be durably acknowledged: the persisted status is ${persisted?.status ?? "missing"}, not aborted.`
|
|
34170
|
+
);
|
|
34171
|
+
}
|
|
34172
|
+
if (persisted.eventLogIncomplete || persisted.eventStreamId === void 0 || persisted.eventSeq === void 0 || persisted.eventSeq < 1) {
|
|
34173
|
+
throw new ProtocolError(
|
|
34174
|
+
ProtocolErrorCode.InternalError,
|
|
34175
|
+
`Workflow stop for runId "${runId}" could not be durably acknowledged: its stopped event is not durably readable.`
|
|
34176
|
+
);
|
|
34177
|
+
}
|
|
34178
|
+
let stoppedEventIsDurable = false;
|
|
34179
|
+
try {
|
|
34180
|
+
const events = persistence.readEvents(runId, {
|
|
34181
|
+
after: persisted.eventSeq - 1,
|
|
34182
|
+
streamId: persisted.eventStreamId,
|
|
34183
|
+
limit: 1
|
|
34184
|
+
});
|
|
34185
|
+
stoppedEventIsDurable = events.events.some(
|
|
34186
|
+
(record2) => record2.seq === persisted.eventSeq && record2.event.type === "stopped"
|
|
34187
|
+
);
|
|
34188
|
+
} catch {
|
|
34189
|
+
stoppedEventIsDurable = false;
|
|
34190
|
+
}
|
|
34191
|
+
if (!stoppedEventIsDurable) {
|
|
34192
|
+
throw new ProtocolError(
|
|
34193
|
+
ProtocolErrorCode.InternalError,
|
|
34194
|
+
`Workflow stop for runId "${runId}" could not be durably acknowledged: its terminal stopped event is missing.`
|
|
34195
|
+
);
|
|
34196
|
+
}
|
|
34197
|
+
}
|
|
34198
|
+
|
|
33835
34199
|
// ../mcp-server/src/server.ts
|
|
33836
34200
|
var SERVER_NAME = "agentprism-workflow";
|
|
33837
34201
|
var DEFAULT_REQUEST_STATE_CODEC = createRequestStateCodec({
|
|
@@ -33839,7 +34203,7 @@ var DEFAULT_REQUEST_STATE_CODEC = createRequestStateCodec({
|
|
|
33839
34203
|
bind: (ctx) => ctx.mcpReq.method
|
|
33840
34204
|
});
|
|
33841
34205
|
var require2 = createRequire(import.meta.url);
|
|
33842
|
-
var SERVER_VERSION = true ? "0.
|
|
34206
|
+
var SERVER_VERSION = true ? "0.36.0" : require2("../package.json").version;
|
|
33843
34207
|
var SERVER_INSTRUCTIONS = [
|
|
33844
34208
|
"This server exposes three model-facing tools for authoring and orchestrating multi-agent work. workflow and repl spawn subagents over the same ACP backends \u2014 the registry built-ins Claude, Codex, OpenCode, and pi, plus any registered custom agents \u2014 and key their durable state by an absolute projectDir (required on the shared daemon; defaults to the server's own project in single-project mode). Backend credentials come from each agent's own login (claude, codex, opencode, pi), so there is nothing auth-shaped to configure here.",
|
|
33845
34209
|
'\u2022 docs \u2014 SELECTIVE VERSION-MATCHED REFERENCE. Omit topic or use topic:"index" for the bounded catalog, then read exactly one workflow/* or repl/* topic. It embeds the selected text/markdown resource, runs no code, opens no backend, and needs no projectDir. Use it when the compact tool descriptions do not contain enough syntax or lifecycle detail.',
|
|
@@ -34426,6 +34790,17 @@ function formatStopSummary(result) {
|
|
|
34426
34790
|
}
|
|
34427
34791
|
return truncateUtf83(lines.join("\n"), 8192, "\u2026[text truncated]");
|
|
34428
34792
|
}
|
|
34793
|
+
function formatPendingStopSummary(result) {
|
|
34794
|
+
const lines = inspectionSummaryLines(result);
|
|
34795
|
+
const owner = result.control.owner;
|
|
34796
|
+
lines.splice(
|
|
34797
|
+
2,
|
|
34798
|
+
0,
|
|
34799
|
+
`Stop request ${result.control.operationId} is durably pending; retry stop, inspect, or await to observe settlement.`,
|
|
34800
|
+
owner === void 0 ? "No live execution owner is currently discoverable; a later lease holder will apply the intent." : `Execution owner: daemon pid ${owner.pid}${owner.version ? ` v${owner.version}` : ""}${owner.lameDuck ? " (draining)" : ""}.`
|
|
34801
|
+
);
|
|
34802
|
+
return truncateUtf83(lines.join("\n"), 8192, "\u2026[text truncated]");
|
|
34803
|
+
}
|
|
34429
34804
|
function formatAgentCancellationSummary(status, cancellation) {
|
|
34430
34805
|
const lines = inspectionSummaryLines(status);
|
|
34431
34806
|
lines.splice(
|
|
@@ -34437,7 +34812,7 @@ function formatAgentCancellationSummary(status, cancellation) {
|
|
|
34437
34812
|
}
|
|
34438
34813
|
function readScriptAtAdmission(scriptPath) {
|
|
34439
34814
|
try {
|
|
34440
|
-
return
|
|
34815
|
+
return readFileSync3(scriptPath, "utf8");
|
|
34441
34816
|
} catch (error51) {
|
|
34442
34817
|
const cause = error51 instanceof Error ? error51.message : String(error51);
|
|
34443
34818
|
throw new ProtocolError(
|
|
@@ -34482,41 +34857,6 @@ async function settleForegroundRun(manager, started) {
|
|
|
34482
34857
|
throw error51;
|
|
34483
34858
|
}
|
|
34484
34859
|
}
|
|
34485
|
-
function requireDurableStoppedRun(manager, runId) {
|
|
34486
|
-
const persistence = manager.getPersistence();
|
|
34487
|
-
const persisted = persistence.load(runId);
|
|
34488
|
-
if (persisted?.status !== "aborted") {
|
|
34489
|
-
throw new ProtocolError(
|
|
34490
|
-
ProtocolErrorCode.InternalError,
|
|
34491
|
-
`Workflow stop for runId "${runId}" could not be durably acknowledged: the persisted status is ${persisted?.status ?? "missing"}, not aborted.`
|
|
34492
|
-
);
|
|
34493
|
-
}
|
|
34494
|
-
if (persisted.eventLogIncomplete || persisted.eventStreamId === void 0 || persisted.eventSeq === void 0 || persisted.eventSeq < 1) {
|
|
34495
|
-
throw new ProtocolError(
|
|
34496
|
-
ProtocolErrorCode.InternalError,
|
|
34497
|
-
`Workflow stop for runId "${runId}" could not be durably acknowledged: its stopped event is not durably readable.`
|
|
34498
|
-
);
|
|
34499
|
-
}
|
|
34500
|
-
let stoppedEventIsDurable = false;
|
|
34501
|
-
try {
|
|
34502
|
-
const events = persistence.readEvents(runId, {
|
|
34503
|
-
after: persisted.eventSeq - 1,
|
|
34504
|
-
streamId: persisted.eventStreamId,
|
|
34505
|
-
limit: 1
|
|
34506
|
-
});
|
|
34507
|
-
stoppedEventIsDurable = events.events.some(
|
|
34508
|
-
(record2) => record2.seq === persisted.eventSeq && record2.event.type === "stopped"
|
|
34509
|
-
);
|
|
34510
|
-
} catch {
|
|
34511
|
-
stoppedEventIsDurable = false;
|
|
34512
|
-
}
|
|
34513
|
-
if (!stoppedEventIsDurable) {
|
|
34514
|
-
throw new ProtocolError(
|
|
34515
|
-
ProtocolErrorCode.InternalError,
|
|
34516
|
-
`Workflow stop for runId "${runId}" could not be durably acknowledged: its terminal stopped event is missing.`
|
|
34517
|
-
);
|
|
34518
|
-
}
|
|
34519
|
-
}
|
|
34520
34860
|
function normalizeTokenUsage(usage) {
|
|
34521
34861
|
if (!usage) return void 0;
|
|
34522
34862
|
return {
|
|
@@ -34797,7 +35137,7 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
34797
35137
|
const workflowToolOutputSchema = workflowToolOutputShape;
|
|
34798
35138
|
const workflowToolConfig = {
|
|
34799
35139
|
title: "Discover, validate, run, inspect, await, stop, or narrow-cancel an agent workflow",
|
|
34800
|
-
description: 'Author and operate JavaScript agent workflows through one project-scoped tool. A script\'s first statement must be `export const meta = { name, description, phases? }`. When present, phases must be an array of objects shaped `{ title: string, detail?: string, model?: string }`, never an array of strings. Inside the deterministic script realm use agent(prompt, options?) for one subagent; parallel([thunks]) for a barrier; pipeline(items, ...stages) for streaming stages; checkpoint(prompt, options?) for a human gate; phase(title) and log(message) for progress; and return the final JSON-serializable value. Top-level await is supported. Imports, require, network APIs, Date.now(), and Math.random() are unavailable. Always label agent calls; schema is a plain JSON Schema object for structured results. The only agent option keys are label, phase, model, tier, mode, configOptions, schema, cwd, timeoutMs, retries, isolation:"worktree", resume, agentType, mcpServers, images, meta, promptMeta, and keepSession; unknown keys reject before admission. Every parallel entry must be a thunk: parallel([() => agent(...), () => agent(...)]). For deeper syntax, read docs topic workflow/quickstart and then one related workflow/* topic. Minimal script: `export const meta = { name: "review", description: "Review a target", phases: [{ title: "Review" }] }; phase("Review"); const report = await agent("Review " + args.target, { label: "review" }); return { report };`. Omit model for the server default, or use a backend name alone to preserve that backend\'s configured default. Before choosing a pinned model, mode, or configOptions, call action:"config" with projectDir and optional harnesses/modelFilter; after choosing a model, pass modelSpecs to read its model-specific options. Set mode only when that selected harness entry\'s modes.availableModes explicitly lists the exact id; modes:null means unsupported, so omit mode\u2014never infer a default from an absent value. Config opens no-prompt sessions, spends zero tokens, and starts no workflow. action:"run" automatically performs static validation, a mocked dry run, and routed config checks before admission. Invalid scripts return bounded diagnostics with status:"rejected" and create no run ID, reserve no background slot, and spend no tokens. Run, resume, inspect, await, or stop an admitted workflow through the same tool. The script orchestrates agent() subagents (and optional checkpoint() gates) over registry built-ins\u2014currently Claude, Codex, OpenCode, and pi\u2014ACP backends, plus registered custom agents. Supply exactly one of inline script or absolute scriptPath; path content is read once and snapshotted at admission. ' + (requireProjectDir ? "config and run REQUIRE projectDir (absolute): it is the discovery cwd and selects the project-scoped run store/default execution cwd. " : "run optionally takes projectDir (absolute) to select the project-scoped run store; default is this server's own project. ") +
|
|
35140
|
+
description: 'Author and operate JavaScript agent workflows through one project-scoped tool. A script\'s first statement must be `export const meta = { name, description, phases? }`. When present, phases must be an array of objects shaped `{ title: string, detail?: string, model?: string }`, never an array of strings. Inside the deterministic script realm use agent(prompt, options?) for one subagent; parallel([thunks]) for a barrier; pipeline(items, ...stages) for streaming stages; checkpoint(prompt, options?) for a human gate; phase(title) and log(message) for progress; and return the final JSON-serializable value. Top-level await is supported. Imports, require, network APIs, Date.now(), and Math.random() are unavailable. Always label agent calls; schema is a plain JSON Schema object for structured results. The only agent option keys are label, phase, model, tier, mode, configOptions, schema, cwd, timeoutMs, retries, isolation:"worktree", resume, agentType, mcpServers, images, meta, promptMeta, and keepSession; unknown keys reject before admission. Every parallel entry must be a thunk: parallel([() => agent(...), () => agent(...)]). For deeper syntax, read docs topic workflow/quickstart and then one related workflow/* topic. Minimal script: `export const meta = { name: "review", description: "Review a target", phases: [{ title: "Review" }] }; phase("Review"); const report = await agent("Review " + args.target, { label: "review" }); return { report };`. Omit model for the server default (explicit AGENTPRISM_DEFAULT_BACKEND, else a zero-token auto-selected project pin), or use a backend name alone to preserve that backend\'s configured default. Before choosing a pinned model, mode, or configOptions, call action:"config" with projectDir and optional harnesses/modelFilter; after choosing a model, pass modelSpecs to read its model-specific options. Set mode only when that selected harness entry\'s modes.availableModes explicitly lists the exact id; modes:null means unsupported, so omit mode\u2014never infer a default from an absent value. Config opens no-prompt sessions, spends zero tokens, and starts no workflow. action:"run" automatically performs static validation, a mocked dry run, and routed config checks before admission. Invalid scripts return bounded diagnostics with status:"rejected" and create no run ID, reserve no background slot, and spend no tokens. Run, resume, inspect, await, or stop an admitted workflow through the same tool. The script orchestrates agent() subagents (and optional checkpoint() gates) over registry built-ins\u2014currently Claude, Codex, OpenCode, and pi\u2014ACP backends, plus registered custom agents. Supply exactly one of inline script or absolute scriptPath; path content is read once and snapshotted at admission. ' + (requireProjectDir ? "config and run REQUIRE projectDir (absolute): it is the discovery cwd and selects the project-scoped run store/default execution cwd. " : "run optionally takes projectDir (absolute) to select the project-scoped run store; default is this server's own project. ") + `inspect/await/stop locate the project store from runId and never accept projectDir. Foreground is the default and streams progress; background:true returns a durable runId for bounded action:"await" calls. run and await honor _meta.progressToken with notifications/progress while they block. Pass resumeFromRunId to execute a new run from a prior journal prefix. In hosts that render MCP Apps, every call of this tool shows a live self-updating run-monitor panel and the panel reports phase starts, pauses, and terminal outcomes on its own \u2014 do NOT poll action:"inspect" to check on a run there; prefer a single bounded action:"await". Use action:"inspect" with a runId when you need machine-readable status data: a safe bounded status, log tail, and attributed call previews. Use action:"stop" to durably abort through the run's execution owner; cross-generation control may return a durable pending operationId before final settlement. Add callIndex to cancel only that live agent and keep the run live. forceOwner explicitly authorizes terminating a superseded owner and is forbidden with callIndex. labelGlob remains an output filter. A final whole-run stop makes resume safe immediately; pending control must be retried or observed with inspect/await. Every admitted script is readable at workflow://runs/{runId}/script and results include resource links. Background runs are tracked per project, capped at four active/starting runs, and use headless checkpoint semantics; checkpointReplies continue a checkpoint pause in a new run.`,
|
|
34801
35141
|
inputSchema: workflowToolInputSchema,
|
|
34802
35142
|
outputSchema: workflowToolOutputSchema,
|
|
34803
35143
|
annotations: void 0
|
|
@@ -34849,7 +35189,7 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
34849
35189
|
);
|
|
34850
35190
|
}
|
|
34851
35191
|
}
|
|
34852
|
-
const report = await
|
|
35192
|
+
const report = await probeHarnessConfig2({
|
|
34853
35193
|
harnesses: parsedInput.harnesses,
|
|
34854
35194
|
modelSpecs: parsedInput.modelSpecs,
|
|
34855
35195
|
cwd,
|
|
@@ -35021,20 +35361,34 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
35021
35361
|
`Workflow run "${parsedInput.runId}" is already terminal (${persisted.status}); no agent call is in flight to cancel. Whole-run stop without callIndex is a successful no-op for terminal runs.`
|
|
35022
35362
|
);
|
|
35023
35363
|
}
|
|
35024
|
-
if (!manager.getRun(parsedInput.runId)) {
|
|
35025
|
-
throw new ProtocolError(
|
|
35026
|
-
ProtocolErrorCode.InvalidParams,
|
|
35027
|
-
`Workflow run "${parsedInput.runId}" is persisted as ${persisted.status}, but there is nothing live to cancel in this server process.`
|
|
35028
|
-
);
|
|
35029
|
-
}
|
|
35030
35364
|
let cancellation;
|
|
35031
|
-
|
|
35032
|
-
|
|
35033
|
-
|
|
35034
|
-
|
|
35035
|
-
|
|
35036
|
-
|
|
35037
|
-
|
|
35365
|
+
if (!manager.getRun(parsedInput.runId)) {
|
|
35366
|
+
if (!options.runControl) {
|
|
35367
|
+
throw new ProtocolError(
|
|
35368
|
+
ProtocolErrorCode.InvalidParams,
|
|
35369
|
+
`Workflow run "${parsedInput.runId}" is persisted as ${persisted.status}, but there is nothing live to cancel in this server process.`
|
|
35370
|
+
);
|
|
35371
|
+
}
|
|
35372
|
+
const routed = await options.runControl.control(manager, {
|
|
35373
|
+
runId: parsedInput.runId,
|
|
35374
|
+
callIndex: parsedInput.callIndex
|
|
35375
|
+
});
|
|
35376
|
+
if (routed.kind !== "agent") {
|
|
35377
|
+
throw new ProtocolError(
|
|
35378
|
+
ProtocolErrorCode.InternalError,
|
|
35379
|
+
`Workflow agent cancellation returned an invalid routed outcome for runId "${parsedInput.runId}".`
|
|
35380
|
+
);
|
|
35381
|
+
}
|
|
35382
|
+
cancellation = routed.cancellation;
|
|
35383
|
+
} else {
|
|
35384
|
+
try {
|
|
35385
|
+
cancellation = await manager.cancelAgentCall(parsedInput.runId, parsedInput.callIndex);
|
|
35386
|
+
} catch (error51) {
|
|
35387
|
+
throw new ProtocolError(
|
|
35388
|
+
error51 instanceof WorkflowError && error51.code === WorkflowErrorCode.PERSISTENCE_ERROR ? ProtocolErrorCode.InternalError : ProtocolErrorCode.InvalidParams,
|
|
35389
|
+
error51 instanceof Error ? error51.message : String(error51)
|
|
35390
|
+
);
|
|
35391
|
+
}
|
|
35038
35392
|
}
|
|
35039
35393
|
const status2 = manager.inspectRun(parsedInput.runId, inspectionOptions);
|
|
35040
35394
|
if (!status2) {
|
|
@@ -35066,22 +35420,97 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
35066
35420
|
if (!alreadyTerminal) {
|
|
35067
35421
|
const live = manager.getRun(parsedInput.runId);
|
|
35068
35422
|
if (!live) {
|
|
35069
|
-
|
|
35070
|
-
|
|
35071
|
-
|
|
35072
|
-
|
|
35073
|
-
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35082
|
-
|
|
35423
|
+
if (options.runControl) {
|
|
35424
|
+
const routed = await options.runControl.control(manager, {
|
|
35425
|
+
runId: parsedInput.runId,
|
|
35426
|
+
forceOwner: parsedInput.forceOwner
|
|
35427
|
+
});
|
|
35428
|
+
if (routed.kind !== "whole") {
|
|
35429
|
+
throw new ProtocolError(
|
|
35430
|
+
ProtocolErrorCode.InternalError,
|
|
35431
|
+
`Workflow stop returned an invalid routed outcome for runId "${parsedInput.runId}".`
|
|
35432
|
+
);
|
|
35433
|
+
}
|
|
35434
|
+
if (routed.state === "pending") {
|
|
35435
|
+
const pendingStatus = manager.inspectRun(parsedInput.runId, inspectionOptions);
|
|
35436
|
+
if (!pendingStatus) {
|
|
35437
|
+
throw new ProtocolError(
|
|
35438
|
+
ProtocolErrorCode.InvalidParams,
|
|
35439
|
+
`No workflow run found for runId "${parsedInput.runId}" in this server's project-scoped run store.`
|
|
35440
|
+
);
|
|
35441
|
+
}
|
|
35442
|
+
if (pendingStatus.status !== "pending" && pendingStatus.status !== "running") {
|
|
35443
|
+
throw new ProtocolError(
|
|
35444
|
+
ProtocolErrorCode.InternalError,
|
|
35445
|
+
`Workflow stop intent ${routed.operationId} remained pending but runId "${parsedInput.runId}" is ${pendingStatus.status}.`
|
|
35446
|
+
);
|
|
35447
|
+
}
|
|
35448
|
+
const lineage2 = scriptResources.lineage(parsedInput.runId);
|
|
35449
|
+
const projected2 = addInspectionResourceFields(
|
|
35450
|
+
pendingStatus,
|
|
35451
|
+
{
|
|
35452
|
+
scriptUri: workflowScriptUri(parsedInput.runId),
|
|
35453
|
+
lineage: lineage2,
|
|
35454
|
+
stopped: false,
|
|
35455
|
+
alreadyTerminal: false,
|
|
35456
|
+
control: {
|
|
35457
|
+
state: "pending",
|
|
35458
|
+
operationId: routed.operationId,
|
|
35459
|
+
requestedAt: routed.requestedAt,
|
|
35460
|
+
...routed.owner === void 0 ? {} : { owner: routed.owner }
|
|
35461
|
+
}
|
|
35462
|
+
},
|
|
35463
|
+
inspectionRetentionMetadata(manager, parsedInput.runId, pendingStatus)
|
|
35464
|
+
);
|
|
35465
|
+
const result2 = {
|
|
35466
|
+
...projected2,
|
|
35467
|
+
status: pendingStatus.status
|
|
35468
|
+
};
|
|
35469
|
+
const currentLink2 = scriptResources.links(lineage2).filter((link) => link.uri === workflowScriptUri(parsedInput.runId));
|
|
35470
|
+
return {
|
|
35471
|
+
structuredContent: { ...result2 },
|
|
35472
|
+
content: [{ type: "text", text: formatPendingStopSummary(result2) }, ...currentLink2],
|
|
35473
|
+
isError: false
|
|
35474
|
+
};
|
|
35475
|
+
}
|
|
35476
|
+
stopped = routed.stopped;
|
|
35477
|
+
alreadyTerminal = routed.alreadyTerminal;
|
|
35478
|
+
} else {
|
|
35479
|
+
const cold = manager.stopPersistedRun(parsedInput.runId);
|
|
35480
|
+
stopped = cold.outcome === "stopped";
|
|
35481
|
+
alreadyTerminal = cold.outcome === "already-terminal";
|
|
35482
|
+
if (cold.outcome === "owned-elsewhere") {
|
|
35483
|
+
throw new ProtocolError(
|
|
35484
|
+
ProtocolErrorCode.InvalidParams,
|
|
35485
|
+
`Workflow run "${parsedInput.runId}" is persisted as ${persisted.status} and is owned by another live process; this server has no daemon run-control router.`
|
|
35486
|
+
);
|
|
35487
|
+
}
|
|
35488
|
+
if (cold.outcome === "missing") {
|
|
35489
|
+
throw new ProtocolError(
|
|
35490
|
+
ProtocolErrorCode.InvalidParams,
|
|
35491
|
+
`No workflow run found for runId "${parsedInput.runId}" in this server's project-scoped run store.`
|
|
35492
|
+
);
|
|
35493
|
+
}
|
|
35083
35494
|
}
|
|
35084
35495
|
} else {
|
|
35496
|
+
stopped = manager.stop(parsedInput.runId);
|
|
35497
|
+
if (!stopped) {
|
|
35498
|
+
const current = manager.getPersistence().load(parsedInput.runId);
|
|
35499
|
+
alreadyTerminal = current !== null && isAlreadyTerminalForStop(current.status);
|
|
35500
|
+
if (!alreadyTerminal) {
|
|
35501
|
+
const cold = manager.stopPersistedRun(parsedInput.runId);
|
|
35502
|
+
stopped = cold.outcome === "stopped";
|
|
35503
|
+
alreadyTerminal = cold.outcome === "already-terminal";
|
|
35504
|
+
}
|
|
35505
|
+
if (!stopped && !alreadyTerminal) {
|
|
35506
|
+
throw new ProtocolError(
|
|
35507
|
+
ProtocolErrorCode.InvalidParams,
|
|
35508
|
+
`Workflow run "${parsedInput.runId}" could not be stopped; its persisted status is ${current?.status ?? persisted.status}.`
|
|
35509
|
+
);
|
|
35510
|
+
}
|
|
35511
|
+
}
|
|
35512
|
+
}
|
|
35513
|
+
if (stopped) {
|
|
35085
35514
|
scriptResources.cancelPendingElicitation(parsedInput.runId);
|
|
35086
35515
|
requireDurableStoppedRun(manager, parsedInput.runId);
|
|
35087
35516
|
}
|
|
@@ -35306,12 +35735,49 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
35306
35735
|
isError: true
|
|
35307
35736
|
};
|
|
35308
35737
|
}
|
|
35738
|
+
const routingDiscovery = await validateWorkflowScript(admittedScript, {
|
|
35739
|
+
args: input.args,
|
|
35740
|
+
cwd: context.projectDir,
|
|
35741
|
+
maxAgents: input.maxAgents,
|
|
35742
|
+
timeoutMs: 3e4,
|
|
35743
|
+
probeConfig: false,
|
|
35744
|
+
loadSavedWorkflow: (name) => context.manager.resolveSavedWorkflow(name)
|
|
35745
|
+
});
|
|
35746
|
+
let defaultModel;
|
|
35747
|
+
let defaultBackendWarning;
|
|
35748
|
+
if (workflowNeedsPinnedDefault(routingDiscovery) || workflowMayUseDefaultModel(admittedScript)) {
|
|
35749
|
+
const explicitDefault = process.env[DEFAULT_BACKEND_ENV] !== void 0;
|
|
35750
|
+
if (explicitDefault) {
|
|
35751
|
+
defaultModel = probeRunner.defaultBackendId?.();
|
|
35752
|
+
} else {
|
|
35753
|
+
const source = input.resumeFromRunId ? context.manager.getPersistence().load(input.resumeFromRunId) : null;
|
|
35754
|
+
defaultModel = recordedDefaultModel(source);
|
|
35755
|
+
if (defaultModel) {
|
|
35756
|
+
defaultBackendWarning = `Model-less agent calls inherit pinned backend ${JSON.stringify(defaultModel)} from the resume source; the run will not switch providers automatically.`;
|
|
35757
|
+
} else if (probeRunner.defaultBackendId && probeRunner.listBackends) {
|
|
35758
|
+
try {
|
|
35759
|
+
const selected = await discoverProjectDefaultBackend(context, probeRunner);
|
|
35760
|
+
defaultModel = selected.backendId;
|
|
35761
|
+
defaultBackendWarning = `Model-less agent calls use auto-selected backend ${JSON.stringify(selected.backendId)} for this run (${selected.reason}); the run will not switch providers automatically.`;
|
|
35762
|
+
} catch (error51) {
|
|
35763
|
+
if (error51 instanceof NoAutoDefaultBackendError) {
|
|
35764
|
+
return {
|
|
35765
|
+
content: [{ type: "text", text: truncateUtf83(error51.message, 8192, "\u2026[backend diagnostics truncated]") }],
|
|
35766
|
+
isError: true
|
|
35767
|
+
};
|
|
35768
|
+
}
|
|
35769
|
+
throw error51;
|
|
35770
|
+
}
|
|
35771
|
+
}
|
|
35772
|
+
}
|
|
35773
|
+
}
|
|
35309
35774
|
const preflight = await validateWorkflowScript(admittedScript, {
|
|
35310
35775
|
args: input.args,
|
|
35311
35776
|
cwd: context.projectDir,
|
|
35312
35777
|
maxAgents: input.maxAgents,
|
|
35313
35778
|
timeoutMs: 3e4,
|
|
35314
35779
|
probeTimeoutMs: 6e4,
|
|
35780
|
+
defaultModel,
|
|
35315
35781
|
probeRunner,
|
|
35316
35782
|
loadSavedWorkflow: (name) => context.manager.resolveSavedWorkflow(name)
|
|
35317
35783
|
});
|
|
@@ -35323,10 +35789,14 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
35323
35789
|
isError: true
|
|
35324
35790
|
};
|
|
35325
35791
|
}
|
|
35326
|
-
|
|
35327
|
-
|
|
35328
|
-
|
|
35329
|
-
|
|
35792
|
+
const admissionWarnings = [
|
|
35793
|
+
...defaultBackendWarning ? [defaultBackendWarning] : [],
|
|
35794
|
+
...preflight.warnings
|
|
35795
|
+
];
|
|
35796
|
+
if (admissionWarnings.length > 0) {
|
|
35797
|
+
const lines = admissionWarnings.slice(0, 20).map((warning) => `- ${warning}`);
|
|
35798
|
+
if (admissionWarnings.length > lines.length) {
|
|
35799
|
+
lines.push(`- \u2026 ${admissionWarnings.length - lines.length} more warning(s) omitted`);
|
|
35330
35800
|
}
|
|
35331
35801
|
preflightWarningText = truncateUtf83(
|
|
35332
35802
|
`
|
|
@@ -35353,6 +35823,7 @@ ${lines.join("\n")}`,
|
|
|
35353
35823
|
const exec = {
|
|
35354
35824
|
agent: runner,
|
|
35355
35825
|
executionAdmission: executionLatch.decision,
|
|
35826
|
+
defaultModel,
|
|
35356
35827
|
scriptBackends: backendsGate.backends,
|
|
35357
35828
|
maxAgents: input.maxAgents,
|
|
35358
35829
|
concurrency: input.concurrency,
|
|
@@ -35528,24 +35999,24 @@ runId: ${started2.runId}
|
|
|
35528
35999
|
}
|
|
35529
36000
|
|
|
35530
36001
|
// ../mcp-server/src/shim/ensure-daemon.ts
|
|
35531
|
-
import { closeSync, mkdirSync as
|
|
36002
|
+
import { closeSync, mkdirSync as mkdirSync3, openSync } from "node:fs";
|
|
35532
36003
|
import { homedir } from "node:os";
|
|
35533
36004
|
import { dirname as dirname2 } from "node:path";
|
|
35534
36005
|
import { spawn } from "node:child_process";
|
|
35535
36006
|
|
|
35536
36007
|
// ../mcp-server/src/daemon/daemon-info.ts
|
|
35537
|
-
import { randomUUID } from "node:crypto";
|
|
36008
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
35538
36009
|
import { createHash as createHash2 } from "node:crypto";
|
|
35539
36010
|
import {
|
|
35540
|
-
chmodSync,
|
|
35541
|
-
mkdirSync,
|
|
35542
|
-
readdirSync as
|
|
35543
|
-
readFileSync as
|
|
36011
|
+
chmodSync as chmodSync2,
|
|
36012
|
+
mkdirSync as mkdirSync2,
|
|
36013
|
+
readdirSync as readdirSync3,
|
|
36014
|
+
readFileSync as readFileSync4,
|
|
35544
36015
|
renameSync as renameSync2,
|
|
35545
|
-
rmSync,
|
|
35546
|
-
writeFileSync
|
|
36016
|
+
rmSync as rmSync2,
|
|
36017
|
+
writeFileSync as writeFileSync2
|
|
35547
36018
|
} from "node:fs";
|
|
35548
|
-
import { dirname, join as
|
|
36019
|
+
import { dirname, join as join3 } from "node:path";
|
|
35549
36020
|
import { workflowHomeDir as workflowHomeDir2 } from "@automatalabs/workflows";
|
|
35550
36021
|
async function probeHealthz(port, timeoutMs = 2e3) {
|
|
35551
36022
|
try {
|
|
@@ -35561,22 +36032,22 @@ async function probeHealthz(port, timeoutMs = 2e3) {
|
|
|
35561
36032
|
}
|
|
35562
36033
|
}
|
|
35563
36034
|
function daemonsDir() {
|
|
35564
|
-
return
|
|
36035
|
+
return join3(workflowHomeDir2(), "daemons");
|
|
35565
36036
|
}
|
|
35566
36037
|
function daemonInfoPath(fingerprint = envFingerprint()) {
|
|
35567
|
-
return
|
|
36038
|
+
return join3(daemonsDir(), `${fingerprint}.json`);
|
|
35568
36039
|
}
|
|
35569
36040
|
function daemonLockPath(fingerprint = envFingerprint()) {
|
|
35570
|
-
return
|
|
36041
|
+
return join3(daemonsDir(), `${fingerprint}.lock`);
|
|
35571
36042
|
}
|
|
35572
36043
|
function daemonInstancePath(pid) {
|
|
35573
|
-
return
|
|
36044
|
+
return join3(daemonsDir(), "instances", `${pid}.json`);
|
|
35574
36045
|
}
|
|
35575
36046
|
function legacyDaemonInfoPath() {
|
|
35576
|
-
return
|
|
36047
|
+
return join3(workflowHomeDir2(), "daemon.json");
|
|
35577
36048
|
}
|
|
35578
36049
|
function daemonLogPath() {
|
|
35579
|
-
return
|
|
36050
|
+
return join3(workflowHomeDir2(), "logs", "daemon.log");
|
|
35580
36051
|
}
|
|
35581
36052
|
function pidIsAlive(pid) {
|
|
35582
36053
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -35590,7 +36061,7 @@ function pidIsAlive(pid) {
|
|
|
35590
36061
|
}
|
|
35591
36062
|
function readInfoFile(path) {
|
|
35592
36063
|
try {
|
|
35593
|
-
const parsed = JSON.parse(
|
|
36064
|
+
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
35594
36065
|
if (parsed.name !== DAEMON_NAME || typeof parsed.pid !== "number" || typeof parsed.port !== "number" || typeof parsed.url !== "string" || typeof parsed.version !== "string") {
|
|
35595
36066
|
return void 0;
|
|
35596
36067
|
}
|
|
@@ -35602,12 +36073,15 @@ function readInfoFile(path) {
|
|
|
35602
36073
|
function readDaemonInfo(fingerprint = envFingerprint()) {
|
|
35603
36074
|
return readInfoFile(daemonInfoPath(fingerprint));
|
|
35604
36075
|
}
|
|
36076
|
+
function readDaemonInstance(pid) {
|
|
36077
|
+
return readInfoFile(daemonInstancePath(pid));
|
|
36078
|
+
}
|
|
35605
36079
|
function writeInfoFile(path, info) {
|
|
35606
|
-
|
|
35607
|
-
const tmp = `${path}.${info.pid}.${
|
|
35608
|
-
|
|
36080
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
36081
|
+
const tmp = `${path}.${info.pid}.${randomUUID2().slice(0, 8)}.tmp`;
|
|
36082
|
+
writeFileSync2(tmp, `${JSON.stringify(info, null, 2)}
|
|
35609
36083
|
`, { mode: 384 });
|
|
35610
|
-
|
|
36084
|
+
chmodSync2(tmp, 384);
|
|
35611
36085
|
renameSync2(tmp, path);
|
|
35612
36086
|
}
|
|
35613
36087
|
function writeDaemonInfo(info) {
|
|
@@ -35619,11 +36093,11 @@ function clearDaemonInfo(pid) {
|
|
|
35619
36093
|
const fingerprint = instance?.envFingerprint ?? envFingerprint();
|
|
35620
36094
|
const current = readDaemonInfo(fingerprint);
|
|
35621
36095
|
try {
|
|
35622
|
-
if (current !== void 0 && current.pid === pid)
|
|
36096
|
+
if (current !== void 0 && current.pid === pid) rmSync2(daemonInfoPath(fingerprint), { force: true });
|
|
35623
36097
|
} catch {
|
|
35624
36098
|
}
|
|
35625
36099
|
try {
|
|
35626
|
-
|
|
36100
|
+
rmSync2(daemonInstancePath(pid), { force: true });
|
|
35627
36101
|
} catch {
|
|
35628
36102
|
}
|
|
35629
36103
|
}
|
|
@@ -35634,20 +36108,20 @@ function isSupersededBy(ownPid, fingerprint = envFingerprint()) {
|
|
|
35634
36108
|
function listDaemonInstances() {
|
|
35635
36109
|
const instances = [];
|
|
35636
36110
|
const seen = /* @__PURE__ */ new Set();
|
|
35637
|
-
const dir =
|
|
36111
|
+
const dir = join3(daemonsDir(), "instances");
|
|
35638
36112
|
let files = [];
|
|
35639
36113
|
try {
|
|
35640
|
-
files =
|
|
36114
|
+
files = readdirSync3(dir);
|
|
35641
36115
|
} catch {
|
|
35642
36116
|
files = [];
|
|
35643
36117
|
}
|
|
35644
36118
|
for (const file2 of files) {
|
|
35645
36119
|
if (!file2.endsWith(".json")) continue;
|
|
35646
|
-
const path =
|
|
36120
|
+
const path = join3(dir, file2);
|
|
35647
36121
|
const info = readInfoFile(path);
|
|
35648
36122
|
if (info === void 0 || !pidIsAlive(info.pid)) {
|
|
35649
36123
|
try {
|
|
35650
|
-
|
|
36124
|
+
rmSync2(path, { force: true });
|
|
35651
36125
|
} catch {
|
|
35652
36126
|
}
|
|
35653
36127
|
continue;
|
|
@@ -35690,23 +36164,23 @@ function compareVersions(a, b) {
|
|
|
35690
36164
|
}
|
|
35691
36165
|
function claimSpawnLock(fingerprint = envFingerprint()) {
|
|
35692
36166
|
const path = daemonLockPath(fingerprint);
|
|
35693
|
-
|
|
35694
|
-
const lock = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), token:
|
|
36167
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
36168
|
+
const lock = { pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), token: randomUUID2() };
|
|
35695
36169
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
35696
36170
|
try {
|
|
35697
|
-
|
|
36171
|
+
writeFileSync2(path, JSON.stringify(lock), { flag: "wx", mode: 384 });
|
|
35698
36172
|
return lock;
|
|
35699
36173
|
} catch (err) {
|
|
35700
36174
|
if (err.code !== "EEXIST") return null;
|
|
35701
36175
|
let holder = null;
|
|
35702
36176
|
try {
|
|
35703
|
-
holder = JSON.parse(
|
|
36177
|
+
holder = JSON.parse(readFileSync4(path, "utf-8"));
|
|
35704
36178
|
} catch {
|
|
35705
36179
|
holder = null;
|
|
35706
36180
|
}
|
|
35707
36181
|
if (holder !== null && pidIsAlive(holder.pid)) return null;
|
|
35708
36182
|
try {
|
|
35709
|
-
|
|
36183
|
+
rmSync2(path, { force: true });
|
|
35710
36184
|
} catch {
|
|
35711
36185
|
return null;
|
|
35712
36186
|
}
|
|
@@ -35717,9 +36191,9 @@ function claimSpawnLock(fingerprint = envFingerprint()) {
|
|
|
35717
36191
|
function releaseSpawnLock(lock, fingerprint = envFingerprint()) {
|
|
35718
36192
|
const path = daemonLockPath(fingerprint);
|
|
35719
36193
|
try {
|
|
35720
|
-
const holder = JSON.parse(
|
|
36194
|
+
const holder = JSON.parse(readFileSync4(path, "utf-8"));
|
|
35721
36195
|
if (holder.token !== lock.token) return;
|
|
35722
|
-
|
|
36196
|
+
rmSync2(path, { force: true });
|
|
35723
36197
|
} catch {
|
|
35724
36198
|
}
|
|
35725
36199
|
}
|
|
@@ -35733,7 +36207,14 @@ async function probeLiveDaemon(fingerprint) {
|
|
|
35733
36207
|
if (info === void 0 || !pidIsAlive(info.pid)) return void 0;
|
|
35734
36208
|
const health = await probeHealthz(info.port);
|
|
35735
36209
|
if (health === void 0 || health.pid !== info.pid) return void 0;
|
|
35736
|
-
return {
|
|
36210
|
+
return {
|
|
36211
|
+
info,
|
|
36212
|
+
sessions: health.sessions,
|
|
36213
|
+
activeRuns: health.activeRuns,
|
|
36214
|
+
inflightRequests: health.inflightRequests ?? 0,
|
|
36215
|
+
controlProtocol: health.controlProtocol === 1 ? 1 : void 0,
|
|
36216
|
+
version: health.version
|
|
36217
|
+
};
|
|
35737
36218
|
}
|
|
35738
36219
|
async function waitForCurrentDaemon(fingerprint, timeoutMs) {
|
|
35739
36220
|
const start = Date.now();
|
|
@@ -35759,7 +36240,7 @@ async function stopDaemon(pid, timeoutMs = 5e3) {
|
|
|
35759
36240
|
}
|
|
35760
36241
|
function spawnDetachedDaemon(args) {
|
|
35761
36242
|
const logPath = daemonLogPath();
|
|
35762
|
-
|
|
36243
|
+
mkdirSync3(dirname2(logPath), { recursive: true });
|
|
35763
36244
|
const logFd = openSync(logPath, "a");
|
|
35764
36245
|
const argv = [args.bundlePath, "--daemon-run"];
|
|
35765
36246
|
if (args.port !== void 0) argv.push("--port", String(args.port));
|
|
@@ -35786,6 +36267,12 @@ async function ensureDaemonRunning(options) {
|
|
|
35786
36267
|
};
|
|
35787
36268
|
const live = await probeLiveDaemon(fingerprint);
|
|
35788
36269
|
if (live !== void 0 && !isStale(live)) return adopt(live);
|
|
36270
|
+
if (live !== void 0 && live.controlProtocol !== 1 && (live.activeRuns > 0 || live.inflightRequests > 0)) {
|
|
36271
|
+
options.log(
|
|
36272
|
+
`[${DAEMON_NAME}] compatibility drain: temporarily adopting stale daemon pid ${live.info.pid} (v${live.version}, ${live.activeRuns} run(s), ${live.inflightRequests} request(s) in flight) because it predates run-control v1`
|
|
36273
|
+
);
|
|
36274
|
+
return { ...live.info, compatibilityDrain: true };
|
|
36275
|
+
}
|
|
35789
36276
|
if (live !== void 0) {
|
|
35790
36277
|
options.log(
|
|
35791
36278
|
`[${DAEMON_NAME}] superseding stale daemon (pid ${live.info.pid}, ${live.sessions} session(s), ${live.activeRuns} run(s)): version v${live.version} \u2192 v${SERVER_VERSION} \u2014 spawning a successor and repointing discovery`
|
|
@@ -35802,6 +36289,12 @@ async function ensureDaemonRunning(options) {
|
|
|
35802
36289
|
try {
|
|
35803
36290
|
const raced = await probeLiveDaemon(fingerprint);
|
|
35804
36291
|
if (raced !== void 0 && !isStale(raced)) return adopt(raced);
|
|
36292
|
+
if (raced !== void 0 && raced.controlProtocol !== 1 && (raced.activeRuns > 0 || raced.inflightRequests > 0)) {
|
|
36293
|
+
options.log(
|
|
36294
|
+
`[${DAEMON_NAME}] compatibility drain retained under the spawn lock for stale daemon pid ${raced.info.pid}`
|
|
36295
|
+
);
|
|
36296
|
+
return { ...raced.info, compatibilityDrain: true };
|
|
36297
|
+
}
|
|
35805
36298
|
const superseding = raced !== void 0;
|
|
35806
36299
|
spawnDaemon({ bundlePath: options.bundlePath, port: options.port, supersede: superseding });
|
|
35807
36300
|
const info = await waitForCurrentDaemon(fingerprint, SPAWN_HEALTH_TIMEOUT_MS);
|
|
@@ -35822,6 +36315,7 @@ async function ensureDaemonRunning(options) {
|
|
|
35822
36315
|
}
|
|
35823
36316
|
|
|
35824
36317
|
// ../mcp-server/src/daemon/run-daemon.ts
|
|
36318
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
35825
36319
|
import { createAcpRunner } from "@automatalabs/workflows";
|
|
35826
36320
|
|
|
35827
36321
|
// ../mcp-server/src/daemon/daemon-lifecycle.ts
|
|
@@ -35841,6 +36335,7 @@ function installDaemonLifecycle(options) {
|
|
|
35841
36335
|
const reaper = setInterval(() => {
|
|
35842
36336
|
const daemon = options.daemon;
|
|
35843
36337
|
const superseded = daemon.isSuperseded();
|
|
36338
|
+
void daemon.processPendingControlIntents?.();
|
|
35844
36339
|
if (superseded) {
|
|
35845
36340
|
if (!supersessionAnnounced) {
|
|
35846
36341
|
supersessionAnnounced = true;
|
|
@@ -35848,11 +36343,9 @@ function installDaemonLifecycle(options) {
|
|
|
35848
36343
|
`[agentprism-daemon] superseded by a newer daemon; draining \u2014 ${daemon.sessions.size} session(s), ${daemon.activeRunCount()} run(s), ${daemon.inflightRequestCount()} request(s) in flight`
|
|
35849
36344
|
);
|
|
35850
36345
|
}
|
|
35851
|
-
|
|
35852
|
-
|
|
35853
|
-
|
|
35854
|
-
log(`[agentprism-daemon] migrated ${migrated.length} idle session(s) to the successor: ${migrated.join(", ")}`);
|
|
35855
|
-
}
|
|
36346
|
+
const migrated = daemon.evictDrainableSessions();
|
|
36347
|
+
if (migrated.length > 0) {
|
|
36348
|
+
log(`[agentprism-daemon] migrated ${migrated.length} idle session(s) to the successor: ${migrated.join(", ")}`);
|
|
35856
36349
|
}
|
|
35857
36350
|
} else {
|
|
35858
36351
|
supersessionAnnounced = false;
|
|
@@ -35861,7 +36354,7 @@ function installDaemonLifecycle(options) {
|
|
|
35861
36354
|
log(`[agentprism-daemon] evicted ${evicted.length} idle session(s): ${evicted.join(", ")}`);
|
|
35862
36355
|
}
|
|
35863
36356
|
}
|
|
35864
|
-
const busy = daemon.sessions.size > 0 || daemon.activeRunCount() > 0 || daemon.activeReplDrainCount() > 0;
|
|
36357
|
+
const busy = daemon.sessions.size > 0 || daemon.activeRunCount() > 0 || daemon.inflightRequestCount() > 0 || daemon.activeReplDrainCount() > 0;
|
|
35865
36358
|
if (busy) {
|
|
35866
36359
|
idleSince = void 0;
|
|
35867
36360
|
return;
|
|
@@ -35904,7 +36397,7 @@ function installDaemonLifecycle(options) {
|
|
|
35904
36397
|
|
|
35905
36398
|
// ../mcp-server/src/daemon/http-daemon.ts
|
|
35906
36399
|
import http from "node:http";
|
|
35907
|
-
import { randomUUID as
|
|
36400
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
35908
36401
|
|
|
35909
36402
|
// ../../node_modules/.pnpm/@hono+node-server@1.19.14_hono@4.12.27/node_modules/@hono/node-server/dist/index.mjs
|
|
35910
36403
|
import { Http2ServerRequest as Http2ServerRequest2, constants as h2constants } from "http2";
|
|
@@ -36867,16 +37360,16 @@ function validateRequest(headers, boundPort, env = process.env) {
|
|
|
36867
37360
|
|
|
36868
37361
|
// ../mcp-server/src/daemon/request-state.ts
|
|
36869
37362
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
36870
|
-
import { chmodSync as
|
|
36871
|
-
import { dirname as dirname3, join as
|
|
37363
|
+
import { chmodSync as chmodSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
37364
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
36872
37365
|
import { workflowHomeDir as workflowHomeDir3 } from "@automatalabs/workflows";
|
|
36873
37366
|
function requestStateKeyPath(fingerprint) {
|
|
36874
|
-
return
|
|
37367
|
+
return join4(workflowHomeDir3(), "daemons", `${fingerprint}.request-state-key.json`);
|
|
36875
37368
|
}
|
|
36876
37369
|
function decodeStoredKey(path) {
|
|
36877
37370
|
let parsed;
|
|
36878
37371
|
try {
|
|
36879
|
-
parsed = JSON.parse(
|
|
37372
|
+
parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
36880
37373
|
} catch (error51) {
|
|
36881
37374
|
throw new Error(`Unable to read the daemon requestState key at ${path}: ${String(error51)}`);
|
|
36882
37375
|
}
|
|
@@ -36885,18 +37378,18 @@ function decodeStoredKey(path) {
|
|
|
36885
37378
|
}
|
|
36886
37379
|
const key = Buffer.from(parsed.key, "base64");
|
|
36887
37380
|
if (key.byteLength !== 32) throw new Error(`Invalid daemon requestState key length at ${path}`);
|
|
36888
|
-
|
|
37381
|
+
chmodSync3(path, 384);
|
|
36889
37382
|
return key;
|
|
36890
37383
|
}
|
|
36891
37384
|
function loadOrCreateRequestStateKey(fingerprint) {
|
|
36892
37385
|
const path = requestStateKeyPath(fingerprint);
|
|
36893
|
-
|
|
37386
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
36894
37387
|
const key = randomBytes2(32);
|
|
36895
37388
|
const stored = { version: 1, key: key.toString("base64") };
|
|
36896
37389
|
try {
|
|
36897
|
-
|
|
37390
|
+
writeFileSync3(path, `${JSON.stringify(stored)}
|
|
36898
37391
|
`, { flag: "wx", mode: 384 });
|
|
36899
|
-
|
|
37392
|
+
chmodSync3(path, 384);
|
|
36900
37393
|
return key;
|
|
36901
37394
|
} catch (error51) {
|
|
36902
37395
|
if (error51.code !== "EEXIST") throw error51;
|
|
@@ -36904,6 +37397,337 @@ function loadOrCreateRequestStateKey(fingerprint) {
|
|
|
36904
37397
|
}
|
|
36905
37398
|
}
|
|
36906
37399
|
|
|
37400
|
+
// ../mcp-server/src/daemon/run-control-auth.ts
|
|
37401
|
+
import { createHash as createHash3, createHmac, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
37402
|
+
import { chmodSync as chmodSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
37403
|
+
import { dirname as dirname4, join as join5 } from "node:path";
|
|
37404
|
+
import { workflowHomeDir as workflowHomeDir4 } from "@automatalabs/workflows";
|
|
37405
|
+
var RUN_CONTROL_PATH = "/_agentprism/control/v1/run";
|
|
37406
|
+
var RUN_CONTROL_PROTOCOL = 1;
|
|
37407
|
+
var RUN_CONTROL_MAX_CLOCK_SKEW_MS = 3e4;
|
|
37408
|
+
function runControlKeyPath() {
|
|
37409
|
+
return join5(workflowHomeDir4(), "daemons", "run-control-key.json");
|
|
37410
|
+
}
|
|
37411
|
+
function decodeStoredKey2(path) {
|
|
37412
|
+
let parsed;
|
|
37413
|
+
try {
|
|
37414
|
+
parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
37415
|
+
} catch (error51) {
|
|
37416
|
+
throw new Error(`Unable to read the daemon run-control key at ${path}: ${String(error51)}`);
|
|
37417
|
+
}
|
|
37418
|
+
if (parsed.version !== 1 || typeof parsed.key !== "string") {
|
|
37419
|
+
throw new Error(`Invalid daemon run-control key file at ${path}`);
|
|
37420
|
+
}
|
|
37421
|
+
const key = Buffer.from(parsed.key, "base64");
|
|
37422
|
+
if (key.byteLength !== 32) throw new Error(`Invalid daemon run-control key length at ${path}`);
|
|
37423
|
+
chmodSync4(path, 384);
|
|
37424
|
+
return key;
|
|
37425
|
+
}
|
|
37426
|
+
function loadOrCreateRunControlKey() {
|
|
37427
|
+
const path = runControlKeyPath();
|
|
37428
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
37429
|
+
const key = randomBytes3(32);
|
|
37430
|
+
const stored = { version: 1, key: key.toString("base64") };
|
|
37431
|
+
try {
|
|
37432
|
+
writeFileSync4(path, `${JSON.stringify(stored)}
|
|
37433
|
+
`, { flag: "wx", mode: 384 });
|
|
37434
|
+
chmodSync4(path, 384);
|
|
37435
|
+
return key;
|
|
37436
|
+
} catch (error51) {
|
|
37437
|
+
if (error51.code !== "EEXIST") throw error51;
|
|
37438
|
+
return decodeStoredKey2(path);
|
|
37439
|
+
}
|
|
37440
|
+
}
|
|
37441
|
+
function signaturePayload(method, path, timestamp, operationId, body) {
|
|
37442
|
+
const bodyHash = createHash3("sha256").update(body).digest("hex");
|
|
37443
|
+
return `${method.toUpperCase()}
|
|
37444
|
+
${path}
|
|
37445
|
+
${timestamp}
|
|
37446
|
+
${operationId}
|
|
37447
|
+
${bodyHash}`;
|
|
37448
|
+
}
|
|
37449
|
+
function signRunControlRequest(key, method, path, operationId, body, now = Date.now()) {
|
|
37450
|
+
const timestamp = String(now);
|
|
37451
|
+
const signature = createHmac("sha256", key).update(signaturePayload(method, path, timestamp, operationId, body)).digest("hex");
|
|
37452
|
+
return {
|
|
37453
|
+
"x-agentprism-control-timestamp": timestamp,
|
|
37454
|
+
"x-agentprism-control-operation": operationId,
|
|
37455
|
+
"x-agentprism-control-signature": signature
|
|
37456
|
+
};
|
|
37457
|
+
}
|
|
37458
|
+
function verifyRunControlRequest(key, input) {
|
|
37459
|
+
if (!input.timestamp || !input.operationId || !input.signature) return false;
|
|
37460
|
+
const timestamp = Number(input.timestamp);
|
|
37461
|
+
const now = input.now ?? Date.now();
|
|
37462
|
+
if (!Number.isSafeInteger(timestamp) || Math.abs(now - timestamp) > RUN_CONTROL_MAX_CLOCK_SKEW_MS) return false;
|
|
37463
|
+
if (!/^[0-9a-f]{64}$/i.test(input.signature)) return false;
|
|
37464
|
+
const expected = createHmac("sha256", key).update(signaturePayload(input.method, input.path, input.timestamp, input.operationId, input.body)).digest();
|
|
37465
|
+
const actual = Buffer.from(input.signature, "hex");
|
|
37466
|
+
return actual.byteLength === expected.byteLength && timingSafeEqual(actual, expected);
|
|
37467
|
+
}
|
|
37468
|
+
|
|
37469
|
+
// ../mcp-server/src/daemon/run-control.ts
|
|
37470
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
37471
|
+
var FORWARD_TIMEOUT_MS = 5e3;
|
|
37472
|
+
var FORCE_TERM_WAIT_MS = 5e3;
|
|
37473
|
+
var FORCE_KILL_WAIT_MS = 2e3;
|
|
37474
|
+
function terminal(status) {
|
|
37475
|
+
return status === "completed" || status === "failed" || status === "aborted";
|
|
37476
|
+
}
|
|
37477
|
+
function ownerView(owner) {
|
|
37478
|
+
if (!owner) return void 0;
|
|
37479
|
+
return {
|
|
37480
|
+
pid: owner.pid,
|
|
37481
|
+
...owner.daemon?.instanceId === void 0 ? {} : { instanceId: owner.daemon.instanceId },
|
|
37482
|
+
...owner.daemon?.version === void 0 ? {} : { version: owner.daemon.version },
|
|
37483
|
+
...owner.health?.lameDuck === void 0 ? {} : { lameDuck: owner.health.lameDuck },
|
|
37484
|
+
...owner.health?.activeRuns === void 0 ? {} : { activeRuns: owner.health.activeRuns },
|
|
37485
|
+
...owner.daemon?.controlProtocol === 1 ? { controlProtocol: 1 } : {}
|
|
37486
|
+
};
|
|
37487
|
+
}
|
|
37488
|
+
function actionableOwnerMessage(runId, owner, action) {
|
|
37489
|
+
if (!owner) return `Workflow run "${runId}" has no discoverable live execution owner for ${action}.`;
|
|
37490
|
+
const version2 = owner.daemon?.version ? ` v${owner.daemon.version}` : "";
|
|
37491
|
+
const draining = owner.health?.lameDuck ? ", draining" : "";
|
|
37492
|
+
const control = owner.daemon?.controlProtocol === 1 ? "control v1" : "no compatible run-control endpoint";
|
|
37493
|
+
return `Workflow run "${runId}" is executing in daemon pid ${owner.pid}${version2} (${control}${draining}); ${action} could not be delivered.`;
|
|
37494
|
+
}
|
|
37495
|
+
async function waitForPidExit(pid, timeoutMs, alive) {
|
|
37496
|
+
const deadline = Date.now() + timeoutMs;
|
|
37497
|
+
while (Date.now() < deadline) {
|
|
37498
|
+
if (!alive(pid)) return true;
|
|
37499
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
37500
|
+
}
|
|
37501
|
+
return !alive(pid);
|
|
37502
|
+
}
|
|
37503
|
+
var DaemonRunControl = class {
|
|
37504
|
+
constructor(options) {
|
|
37505
|
+
this.options = options;
|
|
37506
|
+
this.log = options.log ?? (() => void 0);
|
|
37507
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
37508
|
+
this.killProcess = options.kill ?? ((pid, signal) => process.kill(pid, signal));
|
|
37509
|
+
this.isPidAlive = options.isPidAlive ?? pidIsAlive;
|
|
37510
|
+
}
|
|
37511
|
+
options;
|
|
37512
|
+
log;
|
|
37513
|
+
fetchImpl;
|
|
37514
|
+
killProcess;
|
|
37515
|
+
isPidAlive;
|
|
37516
|
+
processingPending;
|
|
37517
|
+
async resolveOwner(manager, runId) {
|
|
37518
|
+
const lease = manager.getPersistence().inspectRunLease?.(runId);
|
|
37519
|
+
if (!lease || !this.isPidAlive(lease.pid)) return void 0;
|
|
37520
|
+
const daemon = readDaemonInstance(lease.pid);
|
|
37521
|
+
if (daemon?.instanceId !== void 0 && lease.ownerId !== void 0 && daemon.instanceId !== lease.ownerId) {
|
|
37522
|
+
return { pid: lease.pid, ownerId: lease.ownerId };
|
|
37523
|
+
}
|
|
37524
|
+
const health = daemon === void 0 ? void 0 : await probeHealthz(daemon.port, 1e3);
|
|
37525
|
+
const verifiedHealth = health !== void 0 && health.pid === lease.pid && (lease.ownerId === void 0 || health.instanceId === void 0 || health.instanceId === lease.ownerId) ? health : void 0;
|
|
37526
|
+
return { pid: lease.pid, ownerId: lease.ownerId, ...daemon ? { daemon } : {}, ...verifiedHealth ? { health: verifiedHealth } : {} };
|
|
37527
|
+
}
|
|
37528
|
+
controlCapable(owner) {
|
|
37529
|
+
return owner.daemon?.controlProtocol === 1 && typeof owner.daemon.controlUrl === "string" && (owner.ownerId === void 0 || owner.daemon.instanceId === owner.ownerId);
|
|
37530
|
+
}
|
|
37531
|
+
async post(owner, request) {
|
|
37532
|
+
const body = JSON.stringify(request);
|
|
37533
|
+
const headers = signRunControlRequest(
|
|
37534
|
+
this.options.key,
|
|
37535
|
+
"POST",
|
|
37536
|
+
RUN_CONTROL_PATH,
|
|
37537
|
+
request.operationId,
|
|
37538
|
+
body
|
|
37539
|
+
);
|
|
37540
|
+
const response = await this.fetchImpl(owner.daemon.controlUrl, {
|
|
37541
|
+
method: "POST",
|
|
37542
|
+
headers: { "content-type": "application/json", ...headers },
|
|
37543
|
+
body,
|
|
37544
|
+
signal: AbortSignal.timeout(FORWARD_TIMEOUT_MS)
|
|
37545
|
+
});
|
|
37546
|
+
const value = await response.json();
|
|
37547
|
+
if (!response.ok && value.ok !== false) throw new Error(`run-control owner returned HTTP ${response.status}`);
|
|
37548
|
+
return value;
|
|
37549
|
+
}
|
|
37550
|
+
settledWhole(manager, runId) {
|
|
37551
|
+
const current = manager.getPersistence().load(runId);
|
|
37552
|
+
if (!current || !terminal(current.status)) return void 0;
|
|
37553
|
+
if (current.status === "aborted") {
|
|
37554
|
+
try {
|
|
37555
|
+
requireDurableStoppedRun(manager, runId);
|
|
37556
|
+
return { stopped: true, alreadyTerminal: false };
|
|
37557
|
+
} catch {
|
|
37558
|
+
return { stopped: false, alreadyTerminal: true };
|
|
37559
|
+
}
|
|
37560
|
+
}
|
|
37561
|
+
return { stopped: false, alreadyTerminal: true };
|
|
37562
|
+
}
|
|
37563
|
+
applyWholeIntent(manager, intent) {
|
|
37564
|
+
const before = manager.getPersistence().load(intent.runId);
|
|
37565
|
+
if (!before) return { ok: false, code: "UNKNOWN_RUN", message: `No workflow run found for ${intent.runId}` };
|
|
37566
|
+
if (terminal(before.status)) {
|
|
37567
|
+
acknowledgeWholeStopIntent(manager, intent, this.options.ownInstanceId, "already-terminal");
|
|
37568
|
+
return { ok: true, outcome: "already-terminal" };
|
|
37569
|
+
}
|
|
37570
|
+
const stopped = manager.getRun(intent.runId) ? manager.stop(intent.runId) ? { outcome: "stopped" } : { outcome: "owned-elsewhere" } : manager.stopPersistedRun(intent.runId);
|
|
37571
|
+
if (stopped.outcome === "owned-elsewhere") {
|
|
37572
|
+
return { ok: false, code: "NOT_OWNER", message: `Daemon no longer owns run ${intent.runId}` };
|
|
37573
|
+
}
|
|
37574
|
+
if (stopped.outcome === "missing") {
|
|
37575
|
+
return { ok: false, code: "UNKNOWN_RUN", message: `No workflow run found for ${intent.runId}` };
|
|
37576
|
+
}
|
|
37577
|
+
if (stopped.outcome === "already-terminal") {
|
|
37578
|
+
acknowledgeWholeStopIntent(manager, intent, this.options.ownInstanceId, "already-terminal");
|
|
37579
|
+
return { ok: true, outcome: "already-terminal" };
|
|
37580
|
+
}
|
|
37581
|
+
requireDurableStoppedRun(manager, intent.runId);
|
|
37582
|
+
acknowledgeWholeStopIntent(manager, intent, this.options.ownInstanceId, "stopped");
|
|
37583
|
+
return { ok: true, outcome: "stopped" };
|
|
37584
|
+
}
|
|
37585
|
+
async applyLocal(request) {
|
|
37586
|
+
const context = this.options.projects.storeFor(request.runId);
|
|
37587
|
+
if (!context) return { ok: false, code: "UNKNOWN_RUN", message: `No workflow run found for ${request.runId}` };
|
|
37588
|
+
const manager = context.manager;
|
|
37589
|
+
if (request.action === "stop") {
|
|
37590
|
+
const intent = readWholeStopIntent(manager, request.runId, request.operationId);
|
|
37591
|
+
if (!intent) return { ok: false, code: "INVALID_OPERATION", message: "Durable stop intent is missing" };
|
|
37592
|
+
return this.applyWholeIntent(manager, intent);
|
|
37593
|
+
}
|
|
37594
|
+
if (!manager.getRun(request.runId)) {
|
|
37595
|
+
return { ok: false, code: "NOT_OWNER", message: `Daemon has no live run ${request.runId}` };
|
|
37596
|
+
}
|
|
37597
|
+
try {
|
|
37598
|
+
const cancellation = await manager.cancelAgentCall(request.runId, request.callIndex);
|
|
37599
|
+
return { ok: true, outcome: "agent-cancelled", cancellation };
|
|
37600
|
+
} catch (error51) {
|
|
37601
|
+
return {
|
|
37602
|
+
ok: false,
|
|
37603
|
+
code: "INVALID_OPERATION",
|
|
37604
|
+
message: error51 instanceof Error ? error51.message : String(error51)
|
|
37605
|
+
};
|
|
37606
|
+
}
|
|
37607
|
+
}
|
|
37608
|
+
processPendingIntents() {
|
|
37609
|
+
if (this.processingPending) return this.processingPending;
|
|
37610
|
+
this.processingPending = (async () => {
|
|
37611
|
+
for (const context of this.options.projects.stores()) {
|
|
37612
|
+
for (const intent of listPendingWholeStopIntents(context.manager)) {
|
|
37613
|
+
try {
|
|
37614
|
+
const response = this.applyWholeIntent(context.manager, intent);
|
|
37615
|
+
if (response.ok) {
|
|
37616
|
+
this.log(`[agentprism-daemon] run-control ${intent.operationId} for ${intent.runId}: ${response.outcome}`);
|
|
37617
|
+
}
|
|
37618
|
+
} catch (error51) {
|
|
37619
|
+
this.log(`[agentprism-daemon] run-control ${intent.operationId} for ${intent.runId} failed: ${String(error51)}`);
|
|
37620
|
+
}
|
|
37621
|
+
}
|
|
37622
|
+
}
|
|
37623
|
+
})().finally(() => {
|
|
37624
|
+
this.processingPending = void 0;
|
|
37625
|
+
});
|
|
37626
|
+
return this.processingPending;
|
|
37627
|
+
}
|
|
37628
|
+
async forceOwner(manager, runId, owner) {
|
|
37629
|
+
const lease = manager.getPersistence().inspectRunLease?.(runId);
|
|
37630
|
+
const instance = readDaemonInstance(owner.pid);
|
|
37631
|
+
if (!lease || lease.pid !== owner.pid || !instance || instance.pid !== owner.pid) {
|
|
37632
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Refusing force stop for run "${runId}": owner identity changed.`);
|
|
37633
|
+
}
|
|
37634
|
+
if (owner.pid === this.options.ownPid) {
|
|
37635
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Refusing force stop for run "${runId}": owner is this daemon.`);
|
|
37636
|
+
}
|
|
37637
|
+
if (lease.ownerId !== void 0 && instance.instanceId !== lease.ownerId) {
|
|
37638
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Refusing force stop for run "${runId}": owner instance identity does not match the lease.`);
|
|
37639
|
+
}
|
|
37640
|
+
const current = readDaemonInfo(instance.envFingerprint);
|
|
37641
|
+
if (!current || current.pid === owner.pid || !this.isPidAlive(current.pid)) {
|
|
37642
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Refusing force stop for run "${runId}": owner is not a superseded daemon with a live successor.`);
|
|
37643
|
+
}
|
|
37644
|
+
const health = await probeHealthz(instance.port, 1e3);
|
|
37645
|
+
if (lease.ownerId === void 0 && (health?.pid !== owner.pid || health.startedAt !== instance.startedAt)) {
|
|
37646
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Refusing force stop for run "${runId}": legacy owner identity could not be revalidated.`);
|
|
37647
|
+
}
|
|
37648
|
+
this.log(
|
|
37649
|
+
`[agentprism-daemon] force-stopping superseded owner pid ${owner.pid} for run ${runId}; ${health?.activeRuns ?? "unknown"} owned run(s) may be interrupted`
|
|
37650
|
+
);
|
|
37651
|
+
try {
|
|
37652
|
+
this.killProcess(owner.pid, "SIGTERM");
|
|
37653
|
+
} catch {
|
|
37654
|
+
}
|
|
37655
|
+
if (!await waitForPidExit(owner.pid, FORCE_TERM_WAIT_MS, this.isPidAlive)) {
|
|
37656
|
+
this.killProcess(owner.pid, "SIGKILL");
|
|
37657
|
+
if (!await waitForPidExit(owner.pid, FORCE_KILL_WAIT_MS, this.isPidAlive)) {
|
|
37658
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, `Forced owner pid ${owner.pid} did not exit.`);
|
|
37659
|
+
}
|
|
37660
|
+
}
|
|
37661
|
+
}
|
|
37662
|
+
async control(manager, input) {
|
|
37663
|
+
let owner = await this.resolveOwner(manager, input.runId);
|
|
37664
|
+
if (input.callIndex !== void 0) {
|
|
37665
|
+
if (!owner || !this.controlCapable(owner)) {
|
|
37666
|
+
throw new ProtocolError(
|
|
37667
|
+
ProtocolErrorCode.InvalidParams,
|
|
37668
|
+
`${actionableOwnerMessage(input.runId, owner, "agent cancellation")} Per-agent cancellation requires the live control-capable owner.`
|
|
37669
|
+
);
|
|
37670
|
+
}
|
|
37671
|
+
let response;
|
|
37672
|
+
try {
|
|
37673
|
+
response = await this.post(owner, {
|
|
37674
|
+
operationId: randomUUID3(),
|
|
37675
|
+
runId: input.runId,
|
|
37676
|
+
action: "cancel-agent",
|
|
37677
|
+
callIndex: input.callIndex
|
|
37678
|
+
});
|
|
37679
|
+
} catch (error51) {
|
|
37680
|
+
throw new ProtocolError(
|
|
37681
|
+
ProtocolErrorCode.InternalError,
|
|
37682
|
+
`${actionableOwnerMessage(input.runId, owner, "agent cancellation")} ${String(error51)}`
|
|
37683
|
+
);
|
|
37684
|
+
}
|
|
37685
|
+
if (!response.ok || response.outcome !== "agent-cancelled") {
|
|
37686
|
+
throw new ProtocolError(
|
|
37687
|
+
response.ok || response.code === "INTERNAL_ERROR" ? ProtocolErrorCode.InternalError : ProtocolErrorCode.InvalidParams,
|
|
37688
|
+
response.ok ? "Owner returned an invalid agent-cancellation response." : response.message
|
|
37689
|
+
);
|
|
37690
|
+
}
|
|
37691
|
+
return { kind: "agent", cancellation: response.cancellation };
|
|
37692
|
+
}
|
|
37693
|
+
const intent = createOrReuseWholeStopIntent(manager, input.runId, this.options.ownInstanceId);
|
|
37694
|
+
if (owner && this.controlCapable(owner)) {
|
|
37695
|
+
try {
|
|
37696
|
+
await this.post(owner, { operationId: intent.operationId, runId: input.runId, action: "stop" });
|
|
37697
|
+
} catch (error51) {
|
|
37698
|
+
this.log(`[agentprism-daemon] run-control ${intent.operationId} forwarding to pid ${owner.pid} failed: ${String(error51)}`);
|
|
37699
|
+
}
|
|
37700
|
+
const settled = this.settledWhole(manager, input.runId);
|
|
37701
|
+
if (settled) return { kind: "whole", state: "settled", ...settled };
|
|
37702
|
+
owner = await this.resolveOwner(manager, input.runId);
|
|
37703
|
+
}
|
|
37704
|
+
if (input.forceOwner && owner) {
|
|
37705
|
+
await this.forceOwner(manager, input.runId, owner);
|
|
37706
|
+
owner = await this.resolveOwner(manager, input.runId);
|
|
37707
|
+
}
|
|
37708
|
+
if (!owner) {
|
|
37709
|
+
const result = manager.stopPersistedRun(input.runId);
|
|
37710
|
+
if (result.outcome === "stopped") {
|
|
37711
|
+
requireDurableStoppedRun(manager, input.runId);
|
|
37712
|
+
acknowledgeWholeStopIntent(manager, intent, this.options.ownInstanceId, "stopped");
|
|
37713
|
+
} else if (result.outcome === "already-terminal") {
|
|
37714
|
+
acknowledgeWholeStopIntent(manager, intent, this.options.ownInstanceId, "already-terminal");
|
|
37715
|
+
} else if (result.outcome === "owned-elsewhere") {
|
|
37716
|
+
owner = await this.resolveOwner(manager, input.runId);
|
|
37717
|
+
}
|
|
37718
|
+
const settled = this.settledWhole(manager, input.runId);
|
|
37719
|
+
if (settled) return { kind: "whole", state: "settled", ...settled };
|
|
37720
|
+
}
|
|
37721
|
+
return {
|
|
37722
|
+
kind: "whole",
|
|
37723
|
+
state: "pending",
|
|
37724
|
+
operationId: intent.operationId,
|
|
37725
|
+
requestedAt: intent.requestedAt,
|
|
37726
|
+
owner: ownerView(owner)
|
|
37727
|
+
};
|
|
37728
|
+
}
|
|
37729
|
+
};
|
|
37730
|
+
|
|
36907
37731
|
// ../mcp-server/src/daemon/session-registry.ts
|
|
36908
37732
|
var SessionRegistry = class {
|
|
36909
37733
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -37041,6 +37865,7 @@ function writeJsonRpcError(res, status, message, code = -32e3) {
|
|
|
37041
37865
|
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id: null }));
|
|
37042
37866
|
}
|
|
37043
37867
|
var MAX_MCP_BODY_BYTES = 4 * 1024 * 1024;
|
|
37868
|
+
var MAX_CONTROL_BODY_BYTES = 64 * 1024;
|
|
37044
37869
|
var BODY_REJECTED = /* @__PURE__ */ Symbol("body-rejected");
|
|
37045
37870
|
async function readMcpJsonBody(req, res) {
|
|
37046
37871
|
if (req.method !== "POST") return void 0;
|
|
@@ -37068,17 +37893,103 @@ async function readMcpJsonBody(req, res) {
|
|
|
37068
37893
|
return BODY_REJECTED;
|
|
37069
37894
|
}
|
|
37070
37895
|
}
|
|
37896
|
+
function controlHeader(req, name) {
|
|
37897
|
+
const value = req.headers[name];
|
|
37898
|
+
return Array.isArray(value) ? value[0] : value;
|
|
37899
|
+
}
|
|
37900
|
+
function writeControlResponse(res, status, body) {
|
|
37901
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
37902
|
+
res.end(JSON.stringify(body));
|
|
37903
|
+
}
|
|
37904
|
+
function isInternalRunControlRequest(value) {
|
|
37905
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
37906
|
+
const row = value;
|
|
37907
|
+
const keys = Object.keys(row).sort();
|
|
37908
|
+
if (typeof row.operationId !== "string" || !/^[0-9a-f-]{36}$/i.test(row.operationId) || typeof row.runId !== "string" || !/^[a-z0-9]+-[a-z0-9]+$/.test(row.runId)) return false;
|
|
37909
|
+
if (row.action === "stop") {
|
|
37910
|
+
return keys.join(",") === "action,operationId,runId";
|
|
37911
|
+
}
|
|
37912
|
+
return row.action === "cancel-agent" && Number.isSafeInteger(row.callIndex) && row.callIndex >= 0 && keys.join(",") === "action,callIndex,operationId,runId";
|
|
37913
|
+
}
|
|
37914
|
+
async function handleRunControlRequest(req, res, key, runControl) {
|
|
37915
|
+
if (req.method !== "POST") {
|
|
37916
|
+
writeControlResponse(res, 405, { ok: false, code: "INVALID_REQUEST", message: "Method Not Allowed" });
|
|
37917
|
+
return;
|
|
37918
|
+
}
|
|
37919
|
+
const contentType = controlHeader(req, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
37920
|
+
if (contentType !== "application/json") {
|
|
37921
|
+
writeControlResponse(res, 415, { ok: false, code: "INVALID_REQUEST", message: "Content-Type must be application/json" });
|
|
37922
|
+
return;
|
|
37923
|
+
}
|
|
37924
|
+
const chunks = [];
|
|
37925
|
+
let bytes = 0;
|
|
37926
|
+
for await (const chunk of req) {
|
|
37927
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
37928
|
+
bytes += buffer.byteLength;
|
|
37929
|
+
if (bytes > MAX_CONTROL_BODY_BYTES) {
|
|
37930
|
+
writeControlResponse(res, 413, { ok: false, code: "INVALID_REQUEST", message: "Run-control body exceeds 64 KiB" });
|
|
37931
|
+
return;
|
|
37932
|
+
}
|
|
37933
|
+
chunks.push(buffer);
|
|
37934
|
+
}
|
|
37935
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
37936
|
+
const operationId = controlHeader(req, "x-agentprism-control-operation");
|
|
37937
|
+
if (!verifyRunControlRequest(key, {
|
|
37938
|
+
method: req.method,
|
|
37939
|
+
path: RUN_CONTROL_PATH,
|
|
37940
|
+
body,
|
|
37941
|
+
timestamp: controlHeader(req, "x-agentprism-control-timestamp"),
|
|
37942
|
+
operationId,
|
|
37943
|
+
signature: controlHeader(req, "x-agentprism-control-signature")
|
|
37944
|
+
})) {
|
|
37945
|
+
writeControlResponse(res, 401, { ok: false, code: "UNAUTHORIZED", message: "Invalid run-control signature" });
|
|
37946
|
+
return;
|
|
37947
|
+
}
|
|
37948
|
+
let parsed;
|
|
37949
|
+
try {
|
|
37950
|
+
parsed = JSON.parse(body);
|
|
37951
|
+
} catch {
|
|
37952
|
+
writeControlResponse(res, 400, { ok: false, code: "INVALID_REQUEST", message: "Invalid JSON" });
|
|
37953
|
+
return;
|
|
37954
|
+
}
|
|
37955
|
+
if (!isInternalRunControlRequest(parsed) || parsed.operationId !== operationId) {
|
|
37956
|
+
writeControlResponse(res, 400, { ok: false, code: "INVALID_REQUEST", message: "Invalid run-control request" });
|
|
37957
|
+
return;
|
|
37958
|
+
}
|
|
37959
|
+
let response;
|
|
37960
|
+
try {
|
|
37961
|
+
response = await runControl.applyLocal(parsed);
|
|
37962
|
+
} catch (error51) {
|
|
37963
|
+
writeControlResponse(res, 500, {
|
|
37964
|
+
ok: false,
|
|
37965
|
+
code: "INTERNAL_ERROR",
|
|
37966
|
+
message: error51 instanceof Error ? error51.message : String(error51)
|
|
37967
|
+
});
|
|
37968
|
+
return;
|
|
37969
|
+
}
|
|
37970
|
+
const status = response.ok ? 200 : response.code === "UNKNOWN_RUN" ? 404 : response.code === "NOT_OWNER" ? 409 : response.code === "INTERNAL_ERROR" ? 500 : 400;
|
|
37971
|
+
writeControlResponse(res, status, response);
|
|
37972
|
+
}
|
|
37071
37973
|
async function createDaemon(options) {
|
|
37072
37974
|
const host = options.host ?? "127.0.0.1";
|
|
37073
37975
|
const env = options.env ?? process.env;
|
|
37074
37976
|
const log = options.log ?? ((line) => console.error(line));
|
|
37075
37977
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
37076
37978
|
const ownPid = options.ownPid ?? process.pid;
|
|
37979
|
+
const ownInstanceId = options.ownInstanceId ?? randomUUID4();
|
|
37077
37980
|
const version2 = options.version ?? SERVER_VERSION;
|
|
37078
37981
|
const isSuperseded = options.isSuperseded ?? (() => isSupersededBy(ownPid));
|
|
37079
37982
|
const familyFingerprint = envFingerprint(env);
|
|
37080
37983
|
const sessions = new SessionRegistry();
|
|
37081
|
-
const projects = new WorkflowProjectRegistry(options.runner);
|
|
37984
|
+
const projects = new WorkflowProjectRegistry(options.runner, { leaseOwnerId: ownInstanceId });
|
|
37985
|
+
const runControlKey = loadOrCreateRunControlKey();
|
|
37986
|
+
const runControl = new DaemonRunControl({
|
|
37987
|
+
projects,
|
|
37988
|
+
ownPid,
|
|
37989
|
+
ownInstanceId,
|
|
37990
|
+
key: runControlKey,
|
|
37991
|
+
log
|
|
37992
|
+
});
|
|
37082
37993
|
const replDrainBoundMs = options.replDrainBoundMs ?? options.sessionTtlMs ?? REPL_DRAIN_BOUND_MS;
|
|
37083
37994
|
const replPresence = new ReplPresenceLedger(replDrainBoundMs);
|
|
37084
37995
|
sessions.onConnectionOpened = (sessionId) => replPresence.reconnect(sessionId);
|
|
@@ -37099,7 +38010,7 @@ async function createDaemon(options) {
|
|
|
37099
38010
|
};
|
|
37100
38011
|
modernHandler = createMcpHandler(
|
|
37101
38012
|
() => {
|
|
37102
|
-
const clientId = `modern:${
|
|
38013
|
+
const clientId = `modern:${randomUUID4()}`;
|
|
37103
38014
|
return createWorkflowServer(options.runner, {
|
|
37104
38015
|
projects,
|
|
37105
38016
|
requireProjectDir: true,
|
|
@@ -37111,7 +38022,8 @@ async function createDaemon(options) {
|
|
|
37111
38022
|
protocolEra: "modern",
|
|
37112
38023
|
requestStateCodec,
|
|
37113
38024
|
disconnectReplClientOnClose: true,
|
|
37114
|
-
modernNotifier
|
|
38025
|
+
modernNotifier,
|
|
38026
|
+
runControl
|
|
37115
38027
|
});
|
|
37116
38028
|
},
|
|
37117
38029
|
{
|
|
@@ -37167,7 +38079,7 @@ async function createDaemon(options) {
|
|
|
37167
38079
|
return;
|
|
37168
38080
|
}
|
|
37169
38081
|
const transport = new NodeStreamableHTTPServerTransport({
|
|
37170
|
-
sessionIdGenerator: () =>
|
|
38082
|
+
sessionIdGenerator: () => randomUUID4(),
|
|
37171
38083
|
eventStore: new BoundedEventStore(),
|
|
37172
38084
|
onsessioninitialized: (sid) => {
|
|
37173
38085
|
sessions.add({
|
|
@@ -37189,7 +38101,8 @@ async function createDaemon(options) {
|
|
|
37189
38101
|
replPresence,
|
|
37190
38102
|
replClientId: () => transport.sessionId,
|
|
37191
38103
|
replDrainBoundMs,
|
|
37192
|
-
replEvalBreakChannel: options.evalBreakChannel
|
|
38104
|
+
replEvalBreakChannel: options.evalBreakChannel,
|
|
38105
|
+
runControl
|
|
37193
38106
|
});
|
|
37194
38107
|
await server.connect(transport);
|
|
37195
38108
|
const protocolOnClose = transport.onclose;
|
|
@@ -37216,6 +38129,10 @@ async function createDaemon(options) {
|
|
|
37216
38129
|
return;
|
|
37217
38130
|
}
|
|
37218
38131
|
const url2 = new URL(req.url ?? "/", `http://${host}:${boundPort}`);
|
|
38132
|
+
if (url2.pathname === RUN_CONTROL_PATH) {
|
|
38133
|
+
await handleRunControlRequest(req, res, runControlKey, runControl);
|
|
38134
|
+
return;
|
|
38135
|
+
}
|
|
37219
38136
|
if (url2.pathname === HEALTHZ_PATH && req.method === "GET") {
|
|
37220
38137
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
37221
38138
|
res.end(
|
|
@@ -37229,6 +38146,8 @@ async function createDaemon(options) {
|
|
|
37229
38146
|
activeRuns: projects.activeRunCount(),
|
|
37230
38147
|
envFingerprint: familyFingerprint,
|
|
37231
38148
|
projects: projects.snapshot(),
|
|
38149
|
+
instanceId: ownInstanceId,
|
|
38150
|
+
controlProtocol: RUN_CONTROL_PROTOCOL,
|
|
37232
38151
|
lameDuck: isSuperseded(),
|
|
37233
38152
|
inflightRequests: sessions.inflightCount() + modernInflight
|
|
37234
38153
|
})
|
|
@@ -37261,6 +38180,8 @@ async function createDaemon(options) {
|
|
|
37261
38180
|
port: boundPort,
|
|
37262
38181
|
url: `http://${host}:${boundPort}${MCP_ENDPOINT_PATH}`,
|
|
37263
38182
|
startedAt,
|
|
38183
|
+
instanceId: ownInstanceId,
|
|
38184
|
+
controlUrl: `http://${host}:${boundPort}${RUN_CONTROL_PATH}`,
|
|
37264
38185
|
sessions,
|
|
37265
38186
|
projects,
|
|
37266
38187
|
activeRunCount: () => projects.activeRunCount(),
|
|
@@ -37268,6 +38189,7 @@ async function createDaemon(options) {
|
|
|
37268
38189
|
inflightRequestCount: () => sessions.inflightCount() + modernInflight,
|
|
37269
38190
|
isSuperseded,
|
|
37270
38191
|
evictDrainableSessions: () => sessions.evictDrainable((sessionId) => replPresence.sessionHasBusyWorkspace(sessionId)),
|
|
38192
|
+
processPendingControlIntents: () => runControl.processPendingIntents(),
|
|
37271
38193
|
async close() {
|
|
37272
38194
|
const closed = new Promise((resolvePromise) => {
|
|
37273
38195
|
httpServer.close(() => resolvePromise());
|
|
@@ -37305,6 +38227,7 @@ async function runDaemon(options = {}) {
|
|
|
37305
38227
|
const log = (line) => console.error(line);
|
|
37306
38228
|
const runner = createAcpRunner();
|
|
37307
38229
|
const supersede = options.supersede ?? false;
|
|
38230
|
+
const instanceId = randomUUID5();
|
|
37308
38231
|
let daemon;
|
|
37309
38232
|
const sessionTtlMs = envInt(SESSION_IDLE_TTL_ENV, SESSION_IDLE_TTL_MS);
|
|
37310
38233
|
const replDrainBoundMs = envInt(REPL_DRAIN_BOUND_ENV, REPL_DRAIN_BOUND_MS);
|
|
@@ -37314,7 +38237,7 @@ async function runDaemon(options = {}) {
|
|
|
37314
38237
|
return `port ${port} is still held by ${holder.legacy ? "a legacy " : ""}daemon pid ${holder.info.pid} (v${holder.info.version}, started ${holder.info.startedAt})`;
|
|
37315
38238
|
};
|
|
37316
38239
|
const evalBreakChannel = createEvalBreakChannel2();
|
|
37317
|
-
const daemonOptions = { runner, log, replDrainBoundMs, evalBreakChannel };
|
|
38240
|
+
const daemonOptions = { runner, log, replDrainBoundMs, evalBreakChannel, ownInstanceId: instanceId };
|
|
37318
38241
|
if (supersede) {
|
|
37319
38242
|
let port = options.port ?? 0;
|
|
37320
38243
|
try {
|
|
@@ -37348,6 +38271,9 @@ async function runDaemon(options = {}) {
|
|
|
37348
38271
|
url: daemon.url,
|
|
37349
38272
|
startedAt: daemon.startedAt,
|
|
37350
38273
|
envFingerprint: envFingerprint(),
|
|
38274
|
+
instanceId: daemon.instanceId,
|
|
38275
|
+
controlUrl: daemon.controlUrl,
|
|
38276
|
+
controlProtocol: 1,
|
|
37351
38277
|
...await evalBreakChannel.breakUrl().then((url2) => ({ replBreakUrl: url2 })).catch(() => ({}))
|
|
37352
38278
|
});
|
|
37353
38279
|
installDaemonLifecycle({
|
|
@@ -37410,7 +38336,9 @@ function formatInstance(instance, currentPid) {
|
|
|
37410
38336
|
`${DAEMON_NAME} v${health?.version ?? info.version}`,
|
|
37411
38337
|
` pid: ${info.pid}`,
|
|
37412
38338
|
` url: ${info.url}`,
|
|
37413
|
-
` role: ${role}
|
|
38339
|
+
` role: ${role}`,
|
|
38340
|
+
` instance: ${health?.instanceId ?? info.instanceId ?? "legacy/unknown"}`,
|
|
38341
|
+
` run control: ${health?.controlProtocol === 1 || info.controlProtocol === 1 ? "v1" : "unavailable"}`
|
|
37414
38342
|
];
|
|
37415
38343
|
if (health === void 0) {
|
|
37416
38344
|
lines.push(" health: not responding (pid alive, /healthz unreachable)");
|
|
@@ -37508,7 +38436,7 @@ async function runDaemonCommand(args, options) {
|
|
|
37508
38436
|
const count = Number.isFinite(lines) && lines > 0 ? lines : 100;
|
|
37509
38437
|
let content;
|
|
37510
38438
|
try {
|
|
37511
|
-
content =
|
|
38439
|
+
content = readFileSync7(daemonLogPath(), "utf-8");
|
|
37512
38440
|
} catch {
|
|
37513
38441
|
console.log(`no daemon log at ${daemonLogPath()}`);
|
|
37514
38442
|
return 0;
|
|
@@ -44475,24 +45403,24 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44475
45403
|
}
|
|
44476
45404
|
}
|
|
44477
45405
|
exports.compileSchema = compileSchema;
|
|
44478
|
-
function resolveRef2(
|
|
45406
|
+
function resolveRef2(root2, baseId, ref) {
|
|
44479
45407
|
var _a3;
|
|
44480
45408
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
44481
|
-
const schOrFunc =
|
|
45409
|
+
const schOrFunc = root2.refs[ref];
|
|
44482
45410
|
if (schOrFunc) return schOrFunc;
|
|
44483
|
-
let _sch = resolve.call(this,
|
|
45411
|
+
let _sch = resolve.call(this, root2, ref);
|
|
44484
45412
|
if (_sch === void 0) {
|
|
44485
|
-
const schema = (_a3 =
|
|
45413
|
+
const schema = (_a3 = root2.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
44486
45414
|
const { schemaId } = this.opts;
|
|
44487
45415
|
if (schema) _sch = new SchemaEnv({
|
|
44488
45416
|
schema,
|
|
44489
45417
|
schemaId,
|
|
44490
|
-
root,
|
|
45418
|
+
root: root2,
|
|
44491
45419
|
baseId
|
|
44492
45420
|
});
|
|
44493
45421
|
}
|
|
44494
45422
|
if (_sch === void 0) return;
|
|
44495
|
-
return
|
|
45423
|
+
return root2.refs[ref] = inlineOrCompile.call(this, _sch);
|
|
44496
45424
|
}
|
|
44497
45425
|
exports.resolveRef = resolveRef2;
|
|
44498
45426
|
function inlineOrCompile(sch) {
|
|
@@ -44506,20 +45434,20 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44506
45434
|
function sameSchemaEnv(s1, s2) {
|
|
44507
45435
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
44508
45436
|
}
|
|
44509
|
-
function resolve(
|
|
45437
|
+
function resolve(root2, ref) {
|
|
44510
45438
|
let sch;
|
|
44511
45439
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
44512
|
-
return sch || this.schemas[ref] || resolveSchema.call(this,
|
|
45440
|
+
return sch || this.schemas[ref] || resolveSchema.call(this, root2, ref);
|
|
44513
45441
|
}
|
|
44514
|
-
function resolveSchema(
|
|
45442
|
+
function resolveSchema(root2, ref) {
|
|
44515
45443
|
const p = this.opts.uriResolver.parse(ref);
|
|
44516
45444
|
const refPath = (0, resolve_1._getFullPath)(this.opts.uriResolver, p);
|
|
44517
|
-
let baseId = (0, resolve_1.getFullPath)(this.opts.uriResolver,
|
|
44518
|
-
if (Object.keys(
|
|
45445
|
+
let baseId = (0, resolve_1.getFullPath)(this.opts.uriResolver, root2.baseId, void 0);
|
|
45446
|
+
if (Object.keys(root2.schema).length > 0 && refPath === baseId) return getJsonPointer.call(this, p, root2);
|
|
44519
45447
|
const id = (0, resolve_1.normalizeId)(refPath);
|
|
44520
45448
|
const schOrRef = this.refs[id] || this.schemas[id];
|
|
44521
45449
|
if (typeof schOrRef == "string") {
|
|
44522
|
-
const sch = resolveSchema.call(this,
|
|
45450
|
+
const sch = resolveSchema.call(this, root2, schOrRef);
|
|
44523
45451
|
if (typeof (sch === null || sch === void 0 ? void 0 : sch.schema) !== "object") return;
|
|
44524
45452
|
return getJsonPointer.call(this, p, sch);
|
|
44525
45453
|
}
|
|
@@ -44533,7 +45461,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44533
45461
|
return new SchemaEnv({
|
|
44534
45462
|
schema,
|
|
44535
45463
|
schemaId,
|
|
44536
|
-
root,
|
|
45464
|
+
root: root2,
|
|
44537
45465
|
baseId
|
|
44538
45466
|
});
|
|
44539
45467
|
}
|
|
@@ -44547,7 +45475,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44547
45475
|
"dependencies",
|
|
44548
45476
|
"definitions"
|
|
44549
45477
|
]);
|
|
44550
|
-
function getJsonPointer(parsedRef, { baseId, schema, root }) {
|
|
45478
|
+
function getJsonPointer(parsedRef, { baseId, schema, root: root2 }) {
|
|
44551
45479
|
var _a3;
|
|
44552
45480
|
if (((_a3 = parsedRef.fragment) === null || _a3 === void 0 ? void 0 : _a3[0]) !== "/") return;
|
|
44553
45481
|
for (const part of parsedRef.fragment.slice(1).split("/")) {
|
|
@@ -44561,13 +45489,13 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44561
45489
|
let env;
|
|
44562
45490
|
if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) {
|
|
44563
45491
|
const $ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schema.$ref);
|
|
44564
|
-
env = resolveSchema.call(this,
|
|
45492
|
+
env = resolveSchema.call(this, root2, $ref);
|
|
44565
45493
|
}
|
|
44566
45494
|
const { schemaId } = this.opts;
|
|
44567
45495
|
env = env || new SchemaEnv({
|
|
44568
45496
|
schema,
|
|
44569
45497
|
schemaId,
|
|
44570
|
-
root,
|
|
45498
|
+
root: root2,
|
|
44571
45499
|
baseId
|
|
44572
45500
|
});
|
|
44573
45501
|
if (env.schema !== env.root.schema) return env;
|
|
@@ -45432,11 +46360,11 @@ var require_core$32 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
45432
46360
|
while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch;
|
|
45433
46361
|
if (sch === void 0) {
|
|
45434
46362
|
const { schemaId } = this.opts;
|
|
45435
|
-
const
|
|
46363
|
+
const root2 = new compile_1.SchemaEnv({
|
|
45436
46364
|
schema: {},
|
|
45437
46365
|
schemaId
|
|
45438
46366
|
});
|
|
45439
|
-
sch = compile_1.resolveSchema.call(this,
|
|
46367
|
+
sch = compile_1.resolveSchema.call(this, root2, keyRef);
|
|
45440
46368
|
if (!sch) return;
|
|
45441
46369
|
this.refs[keyRef] = sch;
|
|
45442
46370
|
}
|
|
@@ -45741,16 +46669,16 @@ var require_ref2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
45741
46669
|
code(cxt) {
|
|
45742
46670
|
const { gen, schema: $ref, it } = cxt;
|
|
45743
46671
|
const { baseId, schemaEnv: env, validateName, opts, self } = it;
|
|
45744
|
-
const { root } = env;
|
|
45745
|
-
if (($ref === "#" || $ref === "#/") && baseId ===
|
|
45746
|
-
const schOrEnv = compile_1.resolveRef.call(self,
|
|
46672
|
+
const { root: root2 } = env;
|
|
46673
|
+
if (($ref === "#" || $ref === "#/") && baseId === root2.baseId) return callRootRef();
|
|
46674
|
+
const schOrEnv = compile_1.resolveRef.call(self, root2, baseId, $ref);
|
|
45747
46675
|
if (schOrEnv === void 0) throw new ref_error_1.default(it.opts.uriResolver, baseId, $ref);
|
|
45748
46676
|
if (schOrEnv instanceof compile_1.SchemaEnv) return callValidate(schOrEnv);
|
|
45749
46677
|
return inlineRefSchema(schOrEnv);
|
|
45750
46678
|
function callRootRef() {
|
|
45751
|
-
if (env ===
|
|
45752
|
-
const rootName = gen.scopeValue("root", { ref:
|
|
45753
|
-
return callRef(cxt, (0, codegen_1._)`${rootName}.validate`,
|
|
46679
|
+
if (env === root2) return callRef(cxt, validateName, env, env.$async);
|
|
46680
|
+
const rootName = gen.scopeValue("root", { ref: root2 });
|
|
46681
|
+
return callRef(cxt, (0, codegen_1._)`${rootName}.validate`, root2, root2.$async);
|
|
45754
46682
|
}
|
|
45755
46683
|
function callValidate(sch) {
|
|
45756
46684
|
callRef(cxt, getValidate(cxt, sch), sch, sch.$async);
|
|
@@ -47465,12 +48393,12 @@ var require_dynamicAnchor2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
47465
48393
|
exports.dynamicAnchor = dynamicAnchor;
|
|
47466
48394
|
function _getValidate(cxt) {
|
|
47467
48395
|
const { schemaEnv, schema, self } = cxt.it;
|
|
47468
|
-
const { root, baseId, localRefs, meta: meta3 } = schemaEnv.root;
|
|
48396
|
+
const { root: root2, baseId, localRefs, meta: meta3 } = schemaEnv.root;
|
|
47469
48397
|
const { schemaId } = self.opts;
|
|
47470
48398
|
const sch = new compile_1.SchemaEnv({
|
|
47471
48399
|
schema,
|
|
47472
48400
|
schemaId,
|
|
47473
|
-
root,
|
|
48401
|
+
root: root2,
|
|
47474
48402
|
baseId,
|
|
47475
48403
|
localRefs,
|
|
47476
48404
|
meta: meta3
|
|
@@ -48867,8 +49795,8 @@ async function random(size) {
|
|
|
48867
49795
|
const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
|
|
48868
49796
|
let result = "";
|
|
48869
49797
|
while (result.length < size) {
|
|
48870
|
-
const
|
|
48871
|
-
for (const randomByte of
|
|
49798
|
+
const randomBytes4 = await getRandomValues(size - result.length);
|
|
49799
|
+
for (const randomByte of randomBytes4) {
|
|
48872
49800
|
if (randomByte < evenDistCutoff) {
|
|
48873
49801
|
result += mask[randomByte % mask.length];
|
|
48874
49802
|
}
|
|
@@ -50992,6 +51920,7 @@ async function runShim(options) {
|
|
|
50992
51920
|
const info = await ensureDaemonRunning({ bundlePath: options.bundlePath, port: options.port, log });
|
|
50993
51921
|
let replBreakUrl = info.replBreakUrl;
|
|
50994
51922
|
let exiting = false;
|
|
51923
|
+
let compatibilityDrainTimer;
|
|
50995
51924
|
function fireOutOfBandBreak(projectDir) {
|
|
50996
51925
|
if (typeof projectDir !== "string" || replBreakUrl === void 0) return;
|
|
50997
51926
|
let key;
|
|
@@ -51112,6 +52041,27 @@ async function runShim(options) {
|
|
|
51112
52041
|
log(`[${DAEMON_NAME} shim] fatal: ${reason}`);
|
|
51113
52042
|
void shutdown(1);
|
|
51114
52043
|
}
|
|
52044
|
+
function armCompatibilityDrain(candidate) {
|
|
52045
|
+
if (compatibilityDrainTimer !== void 0) {
|
|
52046
|
+
clearInterval(compatibilityDrainTimer);
|
|
52047
|
+
compatibilityDrainTimer = void 0;
|
|
52048
|
+
}
|
|
52049
|
+
if (candidate.compatibilityDrain !== true) return;
|
|
52050
|
+
compatibilityDrainTimer = setInterval(() => {
|
|
52051
|
+
void (async () => {
|
|
52052
|
+
if (exiting || reinitializing) return;
|
|
52053
|
+
const health = await probeHealthz(candidate.port, 1e3);
|
|
52054
|
+
if (health === void 0 || health.pid !== candidate.pid || health.activeRuns === 0 && (health.inflightRequests ?? 0) === 0) {
|
|
52055
|
+
if (compatibilityDrainTimer !== void 0) clearInterval(compatibilityDrainTimer);
|
|
52056
|
+
compatibilityDrainTimer = void 0;
|
|
52057
|
+
await startReinitialize(
|
|
52058
|
+
health === void 0 ? "compatibility-drain predecessor became unavailable" : "compatibility-drain predecessor became idle"
|
|
52059
|
+
);
|
|
52060
|
+
}
|
|
52061
|
+
})().catch((error51) => log(`[${DAEMON_NAME} shim] compatibility-drain monitor failed: ${String(error51)}`));
|
|
52062
|
+
}, 1e3);
|
|
52063
|
+
compatibilityDrainTimer.unref();
|
|
52064
|
+
}
|
|
51115
52065
|
async function startReinitialize(reason) {
|
|
51116
52066
|
if (reinitializing || exiting) return;
|
|
51117
52067
|
if (!recoveryAllowed()) {
|
|
@@ -51127,6 +52077,7 @@ async function runShim(options) {
|
|
|
51127
52077
|
retireTransport(http2, reason);
|
|
51128
52078
|
const fresh = await ensureDaemonRunning({ bundlePath: options.bundlePath, port: options.port, log });
|
|
51129
52079
|
replBreakUrl = fresh.replBreakUrl;
|
|
52080
|
+
armCompatibilityDrain(fresh);
|
|
51130
52081
|
http2 = makeHttpTransport(fresh.url);
|
|
51131
52082
|
await http2.start();
|
|
51132
52083
|
if (cachedInitialize === void 0) {
|
|
@@ -51218,6 +52169,8 @@ async function runShim(options) {
|
|
|
51218
52169
|
async function shutdown(code) {
|
|
51219
52170
|
if (exiting) return;
|
|
51220
52171
|
exiting = true;
|
|
52172
|
+
if (compatibilityDrainTimer !== void 0) clearInterval(compatibilityDrainTimer);
|
|
52173
|
+
compatibilityDrainTimer = void 0;
|
|
51221
52174
|
await http2.terminateSession().catch(() => void 0);
|
|
51222
52175
|
await http2.close().catch(() => void 0);
|
|
51223
52176
|
await stdio.close().catch(() => void 0);
|
|
@@ -51226,6 +52179,7 @@ async function runShim(options) {
|
|
|
51226
52179
|
stdio.onclose = () => void shutdown(0);
|
|
51227
52180
|
process.once("SIGINT", () => void shutdown(0));
|
|
51228
52181
|
process.once("SIGTERM", () => void shutdown(0));
|
|
52182
|
+
armCompatibilityDrain(info);
|
|
51229
52183
|
await http2.start();
|
|
51230
52184
|
await stdio.start();
|
|
51231
52185
|
}
|