@automatalabs/workflows 0.55.0 → 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 +3 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mcp-server.js +1004 -215
- 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,7 +30640,7 @@ 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,
|
|
@@ -30707,6 +30707,9 @@ var workflowToolInputShape = {
|
|
|
30707
30707
|
callIndex: external_exports.number().int().nonnegative().safe().optional().describe(
|
|
30708
30708
|
"With action=stop, cancel exactly this in-flight agent call without aborting the run. Forbidden for every other action."
|
|
30709
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
|
+
),
|
|
30710
30713
|
lastN: external_exports.number().int().min(1).max(50).optional().describe("Latest matching calls. Default 20; range 1..50."),
|
|
30711
30714
|
labelGlob: external_exports.string().refine((value) => [...value].length >= 1 && [...value].length <= 128, {
|
|
30712
30715
|
message: "labelGlob must contain from 1 through 128 Unicode code points"
|
|
@@ -30725,7 +30728,7 @@ function invalid(message) {
|
|
|
30725
30728
|
}
|
|
30726
30729
|
function parseWorkflowToolInput(raw, options = {}) {
|
|
30727
30730
|
if (raw.action === "config") {
|
|
30728
|
-
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) {
|
|
30729
30732
|
invalid('action="config" accepts only projectDir, harnesses, modelSpecs, modelFilter, and probeTimeoutMs');
|
|
30730
30733
|
}
|
|
30731
30734
|
if (options.requireProjectDir === true && raw.projectDir === void 0) {
|
|
@@ -30744,7 +30747,7 @@ function parseWorkflowToolInput(raw, options = {}) {
|
|
|
30744
30747
|
}
|
|
30745
30748
|
if (raw.action === "inspect") {
|
|
30746
30749
|
if (!raw.runId) invalid('action="inspect" requires runId');
|
|
30747
|
-
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) {
|
|
30748
30751
|
invalid('action="inspect" cannot include execution fields');
|
|
30749
30752
|
}
|
|
30750
30753
|
return {
|
|
@@ -30757,7 +30760,7 @@ function parseWorkflowToolInput(raw, options = {}) {
|
|
|
30757
30760
|
}
|
|
30758
30761
|
if (raw.action === "await") {
|
|
30759
30762
|
if (!raw.runId) invalid('action="await" requires runId');
|
|
30760
|
-
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.callIndex !== void 0) {
|
|
30763
|
+
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.callIndex !== void 0 || raw.forceOwner !== void 0) {
|
|
30761
30764
|
invalid('action="await" cannot include execution fields');
|
|
30762
30765
|
}
|
|
30763
30766
|
return {
|
|
@@ -30774,16 +30777,20 @@ function parseWorkflowToolInput(raw, options = {}) {
|
|
|
30774
30777
|
if (hasExecutionFields(raw) || hasConfigFields(raw) || raw.waitMs !== void 0) {
|
|
30775
30778
|
invalid('action="stop" cannot include execution fields or waitMs');
|
|
30776
30779
|
}
|
|
30780
|
+
if (raw.callIndex !== void 0 && raw.forceOwner !== void 0) {
|
|
30781
|
+
invalid('action="stop" forceOwner is forbidden with callIndex');
|
|
30782
|
+
}
|
|
30777
30783
|
return {
|
|
30778
30784
|
action: "stop",
|
|
30779
30785
|
runId: raw.runId,
|
|
30780
30786
|
callIndex: raw.callIndex,
|
|
30787
|
+
...raw.forceOwner === void 0 ? {} : { forceOwner: raw.forceOwner },
|
|
30781
30788
|
lastN: raw.lastN,
|
|
30782
30789
|
labelGlob: raw.labelGlob,
|
|
30783
30790
|
logLines: raw.logLines
|
|
30784
30791
|
};
|
|
30785
30792
|
}
|
|
30786
|
-
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)) {
|
|
30787
30794
|
invalid("run inputs cannot include inspection fields");
|
|
30788
30795
|
}
|
|
30789
30796
|
const hasScript = raw.script !== void 0;
|
|
@@ -30950,8 +30957,8 @@ async function discoverProjectDefaultBackend(context, probeRunner, timeoutMs = 6
|
|
|
30950
30957
|
}
|
|
30951
30958
|
|
|
30952
30959
|
// ../mcp-server/src/project-registry.ts
|
|
30953
|
-
import { existsSync as existsSync2, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
30954
|
-
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";
|
|
30955
30962
|
import {
|
|
30956
30963
|
WORKFLOW_PROJECTS_SUBDIR,
|
|
30957
30964
|
WorkflowManager,
|
|
@@ -31240,6 +31247,152 @@ async function resetReplProjectState(state, boundMs = SHUTDOWN_DEADLINE_MS) {
|
|
|
31240
31247
|
state.drainError = null;
|
|
31241
31248
|
}
|
|
31242
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
|
+
|
|
31243
31396
|
// ../mcp-server/src/project-registry.ts
|
|
31244
31397
|
var MAX_BACKGROUND_RUNS = 4;
|
|
31245
31398
|
var BackgroundRunRegistry = class {
|
|
@@ -31289,13 +31442,16 @@ function resolveProjectDir(raw) {
|
|
|
31289
31442
|
}
|
|
31290
31443
|
}
|
|
31291
31444
|
var WorkflowProjectRegistry = class {
|
|
31292
|
-
constructor(runner) {
|
|
31445
|
+
constructor(runner, options = {}) {
|
|
31293
31446
|
this.runner = runner;
|
|
31447
|
+
this.options = options;
|
|
31294
31448
|
}
|
|
31295
31449
|
runner;
|
|
31450
|
+
options;
|
|
31296
31451
|
contexts = /* @__PURE__ */ new Map();
|
|
31297
31452
|
deletionListeners = /* @__PURE__ */ new Set();
|
|
31298
31453
|
persistedEventListeners = /* @__PURE__ */ new Set();
|
|
31454
|
+
stoppedListeners = /* @__PURE__ */ new Set();
|
|
31299
31455
|
/** Adopt an externally built manager as its project's context (composition back-compat). */
|
|
31300
31456
|
adopt(manager, backgroundRuns) {
|
|
31301
31457
|
const existing = this.contexts.get(manager.cwd);
|
|
@@ -31312,18 +31468,26 @@ var WorkflowProjectRegistry = class {
|
|
|
31312
31468
|
if (existing !== void 0) return existing;
|
|
31313
31469
|
return this.register({
|
|
31314
31470
|
projectDir,
|
|
31315
|
-
manager: new WorkflowManager({
|
|
31471
|
+
manager: new WorkflowManager({
|
|
31472
|
+
agent: this.runner,
|
|
31473
|
+
cwd: projectDir,
|
|
31474
|
+
leaseOwnerId: this.options.leaseOwnerId
|
|
31475
|
+
}),
|
|
31316
31476
|
backgroundRuns: new BackgroundRunRegistry()
|
|
31317
31477
|
});
|
|
31318
31478
|
}
|
|
31319
31479
|
register(context) {
|
|
31320
31480
|
this.contexts.set(context.projectDir, context);
|
|
31321
31481
|
context.manager.on("runDeleted", (event) => {
|
|
31482
|
+
deleteRunControlSidecars(context.manager, event.runId);
|
|
31322
31483
|
for (const listener of this.deletionListeners) listener(event);
|
|
31323
31484
|
});
|
|
31324
31485
|
context.manager.on("runEventPersisted", (record2) => {
|
|
31325
31486
|
for (const listener of this.persistedEventListeners) listener(record2);
|
|
31326
31487
|
});
|
|
31488
|
+
context.manager.on("stopped", (event) => {
|
|
31489
|
+
for (const listener of this.stoppedListeners) listener(event);
|
|
31490
|
+
});
|
|
31327
31491
|
return context;
|
|
31328
31492
|
}
|
|
31329
31493
|
storeFor(runId) {
|
|
@@ -31339,18 +31503,18 @@ var WorkflowProjectRegistry = class {
|
|
|
31339
31503
|
* project's next engine construction).
|
|
31340
31504
|
*/
|
|
31341
31505
|
locateOnDisk(runId) {
|
|
31342
|
-
const projectsDir =
|
|
31506
|
+
const projectsDir = join2(workflowHomeDir(), WORKFLOW_PROJECTS_SUBDIR);
|
|
31343
31507
|
let keys;
|
|
31344
31508
|
try {
|
|
31345
|
-
keys =
|
|
31509
|
+
keys = readdirSync2(projectsDir);
|
|
31346
31510
|
} catch {
|
|
31347
31511
|
return void 0;
|
|
31348
31512
|
}
|
|
31349
31513
|
for (const key of keys) {
|
|
31350
|
-
const rootDir =
|
|
31514
|
+
const rootDir = join2(projectsDir, key);
|
|
31351
31515
|
try {
|
|
31352
|
-
if (!existsSync2(
|
|
31353
|
-
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"));
|
|
31354
31518
|
if (typeof manifest.projectDir !== "string" || !isAbsolute2(manifest.projectDir)) continue;
|
|
31355
31519
|
return this.getOrCreate(manifest.projectDir);
|
|
31356
31520
|
} catch {
|
|
@@ -31370,9 +31534,13 @@ var WorkflowProjectRegistry = class {
|
|
|
31370
31534
|
this.persistedEventListeners.add(listener);
|
|
31371
31535
|
return () => this.persistedEventListeners.delete(listener);
|
|
31372
31536
|
}
|
|
31537
|
+
onRunStopped(listener) {
|
|
31538
|
+
this.stoppedListeners.add(listener);
|
|
31539
|
+
return () => this.stoppedListeners.delete(listener);
|
|
31540
|
+
}
|
|
31373
31541
|
activeRunCount() {
|
|
31374
31542
|
let total = 0;
|
|
31375
|
-
for (const context of this.contexts.values()) total += context.
|
|
31543
|
+
for (const context of this.contexts.values()) total += context.manager.activeExecutionCount();
|
|
31376
31544
|
return total;
|
|
31377
31545
|
}
|
|
31378
31546
|
/** Dispose every context's REPL workspace: each one DRAINS with the
|
|
@@ -31407,7 +31575,7 @@ var WorkflowProjectRegistry = class {
|
|
|
31407
31575
|
snapshot() {
|
|
31408
31576
|
return [...this.contexts.values()].map((context) => ({
|
|
31409
31577
|
projectDir: context.projectDir,
|
|
31410
|
-
activeRuns: context.
|
|
31578
|
+
activeRuns: context.manager.activeExecutionCount()
|
|
31411
31579
|
}));
|
|
31412
31580
|
}
|
|
31413
31581
|
};
|
|
@@ -31424,6 +31592,10 @@ function singleStoreRouter(manager) {
|
|
|
31424
31592
|
onRunEventPersisted: (listener) => {
|
|
31425
31593
|
manager.on("runEventPersisted", listener);
|
|
31426
31594
|
return () => manager.off("runEventPersisted", listener);
|
|
31595
|
+
},
|
|
31596
|
+
onRunStopped: (listener) => {
|
|
31597
|
+
manager.on("stopped", listener);
|
|
31598
|
+
return () => manager.off("stopped", listener);
|
|
31427
31599
|
}
|
|
31428
31600
|
};
|
|
31429
31601
|
}
|
|
@@ -31851,6 +32023,19 @@ var discoveryOutputFields = [
|
|
|
31851
32023
|
"omittedHarnesses",
|
|
31852
32024
|
"models"
|
|
31853
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
|
+
});
|
|
31854
32039
|
var variantOutputFields = [
|
|
31855
32040
|
...executionDetailFields,
|
|
31856
32041
|
"scriptSource",
|
|
@@ -31859,6 +32044,7 @@ var variantOutputFields = [
|
|
|
31859
32044
|
"outcome",
|
|
31860
32045
|
"stopped",
|
|
31861
32046
|
"alreadyTerminal",
|
|
32047
|
+
"control",
|
|
31862
32048
|
...discoveryOutputFields
|
|
31863
32049
|
];
|
|
31864
32050
|
var forbidsRequired = (...fields) => ({
|
|
@@ -31900,23 +32086,26 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
31900
32086
|
wait: waitSchema.optional(),
|
|
31901
32087
|
outcome: executionResultSchema.optional(),
|
|
31902
32088
|
stopped: external_exports.boolean().optional(),
|
|
31903
|
-
alreadyTerminal: external_exports.boolean().optional()
|
|
32089
|
+
alreadyTerminal: external_exports.boolean().optional(),
|
|
32090
|
+
control: stopControlSchema.optional()
|
|
31904
32091
|
}).superRefine((value, context) => {
|
|
31905
32092
|
const has = (field) => value[field] !== void 0;
|
|
31906
32093
|
const inspectionComplete = inspectionRequired.every((field) => has(field));
|
|
31907
32094
|
const runCommonComplete = has("runId") && has("status") && has("scriptUri");
|
|
31908
|
-
const
|
|
32095
|
+
const terminal2 = terminalStatuses.includes(value.status);
|
|
31909
32096
|
let valid;
|
|
31910
32097
|
if (value.action === "config") {
|
|
31911
32098
|
valid = has("ok") && has("harnessOptions") && has("omittedHarnesses") && has("models") && hasOnlyExactFields(value, ["action", "ok", "harnessOptions", "omittedHarnesses", "models"]);
|
|
31912
32099
|
} else if (value.action === "run") {
|
|
31913
32100
|
valid = value.status === "rejected" && has("validation") && hasOnlyExactFields(value, ["action", "status", "validation"]);
|
|
31914
32101
|
} else if (has("scriptSource")) {
|
|
31915
|
-
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"]);
|
|
31916
32105
|
} else if (has("stopped") || has("alreadyTerminal")) {
|
|
31917
32106
|
valid = runCommonComplete && inspectionComplete && has("stopped") && has("alreadyTerminal") && (value.status === "completed" || value.status === "failed" || value.status === "aborted") && hasOnlyFields(value, [...inspectionFields, "stopped", "alreadyTerminal"]);
|
|
31918
32107
|
} else if (has("wait")) {
|
|
31919
|
-
valid = runCommonComplete && inspectionComplete && hasOnlyFields(value, [...inspectionFields, "wait", "tokenUsage", "outcome"]) && (
|
|
32108
|
+
valid = runCommonComplete && inspectionComplete && hasOnlyFields(value, [...inspectionFields, "wait", "tokenUsage", "outcome"]) && (terminal2 ? has("outcome") : !has("outcome"));
|
|
31920
32109
|
} else {
|
|
31921
32110
|
valid = runCommonComplete && inspectionComplete && hasOnlyFields(value, inspectionFields);
|
|
31922
32111
|
}
|
|
@@ -31940,7 +32129,8 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
31940
32129
|
"wait",
|
|
31941
32130
|
"outcome",
|
|
31942
32131
|
"stopped",
|
|
31943
|
-
"alreadyTerminal"
|
|
32132
|
+
"alreadyTerminal",
|
|
32133
|
+
"control"
|
|
31944
32134
|
)
|
|
31945
32135
|
},
|
|
31946
32136
|
{
|
|
@@ -31960,7 +32150,8 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
31960
32150
|
"wait",
|
|
31961
32151
|
"outcome",
|
|
31962
32152
|
"stopped",
|
|
31963
|
-
"alreadyTerminal"
|
|
32153
|
+
"alreadyTerminal",
|
|
32154
|
+
"control"
|
|
31964
32155
|
)
|
|
31965
32156
|
},
|
|
31966
32157
|
{
|
|
@@ -32000,6 +32191,16 @@ var workflowToolOutputShape = external_exports.object({
|
|
|
32000
32191
|
required: [...runOutputRequired, ...inspectionRequired, "stopped", "alreadyTerminal"],
|
|
32001
32192
|
properties: { status: { enum: ["completed", "failed", "aborted"] } },
|
|
32002
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"])
|
|
32003
32204
|
}
|
|
32004
32205
|
]
|
|
32005
32206
|
});
|
|
@@ -32280,9 +32481,9 @@ var AUTHORING_DOC_TOPICS = [
|
|
|
32280
32481
|
"workflow/determinism-and-resume",
|
|
32281
32482
|
"workflow/models-and-config"
|
|
32282
32483
|
],
|
|
32283
|
-
"bytes":
|
|
32284
|
-
"sha256": "
|
|
32285
|
-
"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'
|
|
32286
32487
|
},
|
|
32287
32488
|
{
|
|
32288
32489
|
"id": "workflow/models-and-config",
|
|
@@ -33553,11 +33754,13 @@ var WorkflowScriptResources = class {
|
|
|
33553
33754
|
this.modernNotifier = modernNotifier;
|
|
33554
33755
|
this.router = source instanceof WorkflowManager2 ? singleStoreRouter(source) : source.router;
|
|
33555
33756
|
this.detachRunDeleted = this.router.onRunDeleted(this.onRunDeleted);
|
|
33757
|
+
this.detachRunStopped = this.router.onRunStopped(({ runId }) => this.cancelPendingElicitation(runId));
|
|
33556
33758
|
const previousOnClose = this.mcp.server.onclose;
|
|
33557
33759
|
this.mcp.server.onclose = () => {
|
|
33558
33760
|
for (const controller of this.elicitationControllers.values()) controller.abort();
|
|
33559
33761
|
this.elicitationControllers.clear();
|
|
33560
33762
|
for (const uri of [...this.eventSubscriptions.keys()]) this.closeEventSubscription(uri);
|
|
33763
|
+
this.detachRunStopped();
|
|
33561
33764
|
this.detachRunDeleted();
|
|
33562
33765
|
previousOnClose?.();
|
|
33563
33766
|
};
|
|
@@ -33567,6 +33770,7 @@ var WorkflowScriptResources = class {
|
|
|
33567
33770
|
modernNotifier;
|
|
33568
33771
|
router;
|
|
33569
33772
|
detachRunDeleted;
|
|
33773
|
+
detachRunStopped;
|
|
33570
33774
|
subscriptions = /* @__PURE__ */ new Set();
|
|
33571
33775
|
externalReaders = /* @__PURE__ */ new Map();
|
|
33572
33776
|
eventSubscriptions = /* @__PURE__ */ new Map();
|
|
@@ -33955,6 +34159,43 @@ var WorkflowScriptResources = class {
|
|
|
33955
34159
|
}
|
|
33956
34160
|
};
|
|
33957
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
|
+
|
|
33958
34199
|
// ../mcp-server/src/server.ts
|
|
33959
34200
|
var SERVER_NAME = "agentprism-workflow";
|
|
33960
34201
|
var DEFAULT_REQUEST_STATE_CODEC = createRequestStateCodec({
|
|
@@ -33962,7 +34203,7 @@ var DEFAULT_REQUEST_STATE_CODEC = createRequestStateCodec({
|
|
|
33962
34203
|
bind: (ctx) => ctx.mcpReq.method
|
|
33963
34204
|
});
|
|
33964
34205
|
var require2 = createRequire(import.meta.url);
|
|
33965
|
-
var SERVER_VERSION = true ? "0.
|
|
34206
|
+
var SERVER_VERSION = true ? "0.36.0" : require2("../package.json").version;
|
|
33966
34207
|
var SERVER_INSTRUCTIONS = [
|
|
33967
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.",
|
|
33968
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.',
|
|
@@ -34549,6 +34790,17 @@ function formatStopSummary(result) {
|
|
|
34549
34790
|
}
|
|
34550
34791
|
return truncateUtf83(lines.join("\n"), 8192, "\u2026[text truncated]");
|
|
34551
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
|
+
}
|
|
34552
34804
|
function formatAgentCancellationSummary(status, cancellation) {
|
|
34553
34805
|
const lines = inspectionSummaryLines(status);
|
|
34554
34806
|
lines.splice(
|
|
@@ -34560,7 +34812,7 @@ function formatAgentCancellationSummary(status, cancellation) {
|
|
|
34560
34812
|
}
|
|
34561
34813
|
function readScriptAtAdmission(scriptPath) {
|
|
34562
34814
|
try {
|
|
34563
|
-
return
|
|
34815
|
+
return readFileSync3(scriptPath, "utf8");
|
|
34564
34816
|
} catch (error51) {
|
|
34565
34817
|
const cause = error51 instanceof Error ? error51.message : String(error51);
|
|
34566
34818
|
throw new ProtocolError(
|
|
@@ -34605,41 +34857,6 @@ async function settleForegroundRun(manager, started) {
|
|
|
34605
34857
|
throw error51;
|
|
34606
34858
|
}
|
|
34607
34859
|
}
|
|
34608
|
-
function requireDurableStoppedRun(manager, runId) {
|
|
34609
|
-
const persistence = manager.getPersistence();
|
|
34610
|
-
const persisted = persistence.load(runId);
|
|
34611
|
-
if (persisted?.status !== "aborted") {
|
|
34612
|
-
throw new ProtocolError(
|
|
34613
|
-
ProtocolErrorCode.InternalError,
|
|
34614
|
-
`Workflow stop for runId "${runId}" could not be durably acknowledged: the persisted status is ${persisted?.status ?? "missing"}, not aborted.`
|
|
34615
|
-
);
|
|
34616
|
-
}
|
|
34617
|
-
if (persisted.eventLogIncomplete || persisted.eventStreamId === void 0 || persisted.eventSeq === void 0 || persisted.eventSeq < 1) {
|
|
34618
|
-
throw new ProtocolError(
|
|
34619
|
-
ProtocolErrorCode.InternalError,
|
|
34620
|
-
`Workflow stop for runId "${runId}" could not be durably acknowledged: its stopped event is not durably readable.`
|
|
34621
|
-
);
|
|
34622
|
-
}
|
|
34623
|
-
let stoppedEventIsDurable = false;
|
|
34624
|
-
try {
|
|
34625
|
-
const events = persistence.readEvents(runId, {
|
|
34626
|
-
after: persisted.eventSeq - 1,
|
|
34627
|
-
streamId: persisted.eventStreamId,
|
|
34628
|
-
limit: 1
|
|
34629
|
-
});
|
|
34630
|
-
stoppedEventIsDurable = events.events.some(
|
|
34631
|
-
(record2) => record2.seq === persisted.eventSeq && record2.event.type === "stopped"
|
|
34632
|
-
);
|
|
34633
|
-
} catch {
|
|
34634
|
-
stoppedEventIsDurable = false;
|
|
34635
|
-
}
|
|
34636
|
-
if (!stoppedEventIsDurable) {
|
|
34637
|
-
throw new ProtocolError(
|
|
34638
|
-
ProtocolErrorCode.InternalError,
|
|
34639
|
-
`Workflow stop for runId "${runId}" could not be durably acknowledged: its terminal stopped event is missing.`
|
|
34640
|
-
);
|
|
34641
|
-
}
|
|
34642
|
-
}
|
|
34643
34860
|
function normalizeTokenUsage(usage) {
|
|
34644
34861
|
if (!usage) return void 0;
|
|
34645
34862
|
return {
|
|
@@ -34920,7 +35137,7 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
34920
35137
|
const workflowToolOutputSchema = workflowToolOutputShape;
|
|
34921
35138
|
const workflowToolConfig = {
|
|
34922
35139
|
title: "Discover, validate, run, inspect, await, stop, or narrow-cancel an agent workflow",
|
|
34923
|
-
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. ") +
|
|
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.`,
|
|
34924
35141
|
inputSchema: workflowToolInputSchema,
|
|
34925
35142
|
outputSchema: workflowToolOutputSchema,
|
|
34926
35143
|
annotations: void 0
|
|
@@ -35144,20 +35361,34 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
35144
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.`
|
|
35145
35362
|
);
|
|
35146
35363
|
}
|
|
35147
|
-
if (!manager.getRun(parsedInput.runId)) {
|
|
35148
|
-
throw new ProtocolError(
|
|
35149
|
-
ProtocolErrorCode.InvalidParams,
|
|
35150
|
-
`Workflow run "${parsedInput.runId}" is persisted as ${persisted.status}, but there is nothing live to cancel in this server process.`
|
|
35151
|
-
);
|
|
35152
|
-
}
|
|
35153
35364
|
let cancellation;
|
|
35154
|
-
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
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
|
+
}
|
|
35161
35392
|
}
|
|
35162
35393
|
const status2 = manager.inspectRun(parsedInput.runId, inspectionOptions);
|
|
35163
35394
|
if (!status2) {
|
|
@@ -35189,22 +35420,97 @@ function createWorkflowServer(runner, options = {}) {
|
|
|
35189
35420
|
if (!alreadyTerminal) {
|
|
35190
35421
|
const live = manager.getRun(parsedInput.runId);
|
|
35191
35422
|
if (!live) {
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
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
|
+
}
|
|
35206
35494
|
}
|
|
35207
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) {
|
|
35208
35514
|
scriptResources.cancelPendingElicitation(parsedInput.runId);
|
|
35209
35515
|
requireDurableStoppedRun(manager, parsedInput.runId);
|
|
35210
35516
|
}
|
|
@@ -35693,24 +35999,24 @@ runId: ${started2.runId}
|
|
|
35693
35999
|
}
|
|
35694
36000
|
|
|
35695
36001
|
// ../mcp-server/src/shim/ensure-daemon.ts
|
|
35696
|
-
import { closeSync, mkdirSync as
|
|
36002
|
+
import { closeSync, mkdirSync as mkdirSync3, openSync } from "node:fs";
|
|
35697
36003
|
import { homedir } from "node:os";
|
|
35698
36004
|
import { dirname as dirname2 } from "node:path";
|
|
35699
36005
|
import { spawn } from "node:child_process";
|
|
35700
36006
|
|
|
35701
36007
|
// ../mcp-server/src/daemon/daemon-info.ts
|
|
35702
|
-
import { randomUUID } from "node:crypto";
|
|
36008
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
35703
36009
|
import { createHash as createHash2 } from "node:crypto";
|
|
35704
36010
|
import {
|
|
35705
|
-
chmodSync,
|
|
35706
|
-
mkdirSync,
|
|
35707
|
-
readdirSync as
|
|
35708
|
-
readFileSync as
|
|
36011
|
+
chmodSync as chmodSync2,
|
|
36012
|
+
mkdirSync as mkdirSync2,
|
|
36013
|
+
readdirSync as readdirSync3,
|
|
36014
|
+
readFileSync as readFileSync4,
|
|
35709
36015
|
renameSync as renameSync2,
|
|
35710
|
-
rmSync,
|
|
35711
|
-
writeFileSync
|
|
36016
|
+
rmSync as rmSync2,
|
|
36017
|
+
writeFileSync as writeFileSync2
|
|
35712
36018
|
} from "node:fs";
|
|
35713
|
-
import { dirname, join as
|
|
36019
|
+
import { dirname, join as join3 } from "node:path";
|
|
35714
36020
|
import { workflowHomeDir as workflowHomeDir2 } from "@automatalabs/workflows";
|
|
35715
36021
|
async function probeHealthz(port, timeoutMs = 2e3) {
|
|
35716
36022
|
try {
|
|
@@ -35726,22 +36032,22 @@ async function probeHealthz(port, timeoutMs = 2e3) {
|
|
|
35726
36032
|
}
|
|
35727
36033
|
}
|
|
35728
36034
|
function daemonsDir() {
|
|
35729
|
-
return
|
|
36035
|
+
return join3(workflowHomeDir2(), "daemons");
|
|
35730
36036
|
}
|
|
35731
36037
|
function daemonInfoPath(fingerprint = envFingerprint()) {
|
|
35732
|
-
return
|
|
36038
|
+
return join3(daemonsDir(), `${fingerprint}.json`);
|
|
35733
36039
|
}
|
|
35734
36040
|
function daemonLockPath(fingerprint = envFingerprint()) {
|
|
35735
|
-
return
|
|
36041
|
+
return join3(daemonsDir(), `${fingerprint}.lock`);
|
|
35736
36042
|
}
|
|
35737
36043
|
function daemonInstancePath(pid) {
|
|
35738
|
-
return
|
|
36044
|
+
return join3(daemonsDir(), "instances", `${pid}.json`);
|
|
35739
36045
|
}
|
|
35740
36046
|
function legacyDaemonInfoPath() {
|
|
35741
|
-
return
|
|
36047
|
+
return join3(workflowHomeDir2(), "daemon.json");
|
|
35742
36048
|
}
|
|
35743
36049
|
function daemonLogPath() {
|
|
35744
|
-
return
|
|
36050
|
+
return join3(workflowHomeDir2(), "logs", "daemon.log");
|
|
35745
36051
|
}
|
|
35746
36052
|
function pidIsAlive(pid) {
|
|
35747
36053
|
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
@@ -35755,7 +36061,7 @@ function pidIsAlive(pid) {
|
|
|
35755
36061
|
}
|
|
35756
36062
|
function readInfoFile(path) {
|
|
35757
36063
|
try {
|
|
35758
|
-
const parsed = JSON.parse(
|
|
36064
|
+
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
35759
36065
|
if (parsed.name !== DAEMON_NAME || typeof parsed.pid !== "number" || typeof parsed.port !== "number" || typeof parsed.url !== "string" || typeof parsed.version !== "string") {
|
|
35760
36066
|
return void 0;
|
|
35761
36067
|
}
|
|
@@ -35767,12 +36073,15 @@ function readInfoFile(path) {
|
|
|
35767
36073
|
function readDaemonInfo(fingerprint = envFingerprint()) {
|
|
35768
36074
|
return readInfoFile(daemonInfoPath(fingerprint));
|
|
35769
36075
|
}
|
|
36076
|
+
function readDaemonInstance(pid) {
|
|
36077
|
+
return readInfoFile(daemonInstancePath(pid));
|
|
36078
|
+
}
|
|
35770
36079
|
function writeInfoFile(path, info) {
|
|
35771
|
-
|
|
35772
|
-
const tmp = `${path}.${info.pid}.${
|
|
35773
|
-
|
|
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)}
|
|
35774
36083
|
`, { mode: 384 });
|
|
35775
|
-
|
|
36084
|
+
chmodSync2(tmp, 384);
|
|
35776
36085
|
renameSync2(tmp, path);
|
|
35777
36086
|
}
|
|
35778
36087
|
function writeDaemonInfo(info) {
|
|
@@ -35784,11 +36093,11 @@ function clearDaemonInfo(pid) {
|
|
|
35784
36093
|
const fingerprint = instance?.envFingerprint ?? envFingerprint();
|
|
35785
36094
|
const current = readDaemonInfo(fingerprint);
|
|
35786
36095
|
try {
|
|
35787
|
-
if (current !== void 0 && current.pid === pid)
|
|
36096
|
+
if (current !== void 0 && current.pid === pid) rmSync2(daemonInfoPath(fingerprint), { force: true });
|
|
35788
36097
|
} catch {
|
|
35789
36098
|
}
|
|
35790
36099
|
try {
|
|
35791
|
-
|
|
36100
|
+
rmSync2(daemonInstancePath(pid), { force: true });
|
|
35792
36101
|
} catch {
|
|
35793
36102
|
}
|
|
35794
36103
|
}
|
|
@@ -35799,20 +36108,20 @@ function isSupersededBy(ownPid, fingerprint = envFingerprint()) {
|
|
|
35799
36108
|
function listDaemonInstances() {
|
|
35800
36109
|
const instances = [];
|
|
35801
36110
|
const seen = /* @__PURE__ */ new Set();
|
|
35802
|
-
const dir =
|
|
36111
|
+
const dir = join3(daemonsDir(), "instances");
|
|
35803
36112
|
let files = [];
|
|
35804
36113
|
try {
|
|
35805
|
-
files =
|
|
36114
|
+
files = readdirSync3(dir);
|
|
35806
36115
|
} catch {
|
|
35807
36116
|
files = [];
|
|
35808
36117
|
}
|
|
35809
36118
|
for (const file2 of files) {
|
|
35810
36119
|
if (!file2.endsWith(".json")) continue;
|
|
35811
|
-
const path =
|
|
36120
|
+
const path = join3(dir, file2);
|
|
35812
36121
|
const info = readInfoFile(path);
|
|
35813
36122
|
if (info === void 0 || !pidIsAlive(info.pid)) {
|
|
35814
36123
|
try {
|
|
35815
|
-
|
|
36124
|
+
rmSync2(path, { force: true });
|
|
35816
36125
|
} catch {
|
|
35817
36126
|
}
|
|
35818
36127
|
continue;
|
|
@@ -35855,23 +36164,23 @@ function compareVersions(a, b) {
|
|
|
35855
36164
|
}
|
|
35856
36165
|
function claimSpawnLock(fingerprint = envFingerprint()) {
|
|
35857
36166
|
const path = daemonLockPath(fingerprint);
|
|
35858
|
-
|
|
35859
|
-
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() };
|
|
35860
36169
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
35861
36170
|
try {
|
|
35862
|
-
|
|
36171
|
+
writeFileSync2(path, JSON.stringify(lock), { flag: "wx", mode: 384 });
|
|
35863
36172
|
return lock;
|
|
35864
36173
|
} catch (err) {
|
|
35865
36174
|
if (err.code !== "EEXIST") return null;
|
|
35866
36175
|
let holder = null;
|
|
35867
36176
|
try {
|
|
35868
|
-
holder = JSON.parse(
|
|
36177
|
+
holder = JSON.parse(readFileSync4(path, "utf-8"));
|
|
35869
36178
|
} catch {
|
|
35870
36179
|
holder = null;
|
|
35871
36180
|
}
|
|
35872
36181
|
if (holder !== null && pidIsAlive(holder.pid)) return null;
|
|
35873
36182
|
try {
|
|
35874
|
-
|
|
36183
|
+
rmSync2(path, { force: true });
|
|
35875
36184
|
} catch {
|
|
35876
36185
|
return null;
|
|
35877
36186
|
}
|
|
@@ -35882,9 +36191,9 @@ function claimSpawnLock(fingerprint = envFingerprint()) {
|
|
|
35882
36191
|
function releaseSpawnLock(lock, fingerprint = envFingerprint()) {
|
|
35883
36192
|
const path = daemonLockPath(fingerprint);
|
|
35884
36193
|
try {
|
|
35885
|
-
const holder = JSON.parse(
|
|
36194
|
+
const holder = JSON.parse(readFileSync4(path, "utf-8"));
|
|
35886
36195
|
if (holder.token !== lock.token) return;
|
|
35887
|
-
|
|
36196
|
+
rmSync2(path, { force: true });
|
|
35888
36197
|
} catch {
|
|
35889
36198
|
}
|
|
35890
36199
|
}
|
|
@@ -35898,7 +36207,14 @@ async function probeLiveDaemon(fingerprint) {
|
|
|
35898
36207
|
if (info === void 0 || !pidIsAlive(info.pid)) return void 0;
|
|
35899
36208
|
const health = await probeHealthz(info.port);
|
|
35900
36209
|
if (health === void 0 || health.pid !== info.pid) return void 0;
|
|
35901
|
-
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
|
+
};
|
|
35902
36218
|
}
|
|
35903
36219
|
async function waitForCurrentDaemon(fingerprint, timeoutMs) {
|
|
35904
36220
|
const start = Date.now();
|
|
@@ -35924,7 +36240,7 @@ async function stopDaemon(pid, timeoutMs = 5e3) {
|
|
|
35924
36240
|
}
|
|
35925
36241
|
function spawnDetachedDaemon(args) {
|
|
35926
36242
|
const logPath = daemonLogPath();
|
|
35927
|
-
|
|
36243
|
+
mkdirSync3(dirname2(logPath), { recursive: true });
|
|
35928
36244
|
const logFd = openSync(logPath, "a");
|
|
35929
36245
|
const argv = [args.bundlePath, "--daemon-run"];
|
|
35930
36246
|
if (args.port !== void 0) argv.push("--port", String(args.port));
|
|
@@ -35951,6 +36267,12 @@ async function ensureDaemonRunning(options) {
|
|
|
35951
36267
|
};
|
|
35952
36268
|
const live = await probeLiveDaemon(fingerprint);
|
|
35953
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
|
+
}
|
|
35954
36276
|
if (live !== void 0) {
|
|
35955
36277
|
options.log(
|
|
35956
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`
|
|
@@ -35967,6 +36289,12 @@ async function ensureDaemonRunning(options) {
|
|
|
35967
36289
|
try {
|
|
35968
36290
|
const raced = await probeLiveDaemon(fingerprint);
|
|
35969
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
|
+
}
|
|
35970
36298
|
const superseding = raced !== void 0;
|
|
35971
36299
|
spawnDaemon({ bundlePath: options.bundlePath, port: options.port, supersede: superseding });
|
|
35972
36300
|
const info = await waitForCurrentDaemon(fingerprint, SPAWN_HEALTH_TIMEOUT_MS);
|
|
@@ -35987,6 +36315,7 @@ async function ensureDaemonRunning(options) {
|
|
|
35987
36315
|
}
|
|
35988
36316
|
|
|
35989
36317
|
// ../mcp-server/src/daemon/run-daemon.ts
|
|
36318
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
35990
36319
|
import { createAcpRunner } from "@automatalabs/workflows";
|
|
35991
36320
|
|
|
35992
36321
|
// ../mcp-server/src/daemon/daemon-lifecycle.ts
|
|
@@ -36006,6 +36335,7 @@ function installDaemonLifecycle(options) {
|
|
|
36006
36335
|
const reaper = setInterval(() => {
|
|
36007
36336
|
const daemon = options.daemon;
|
|
36008
36337
|
const superseded = daemon.isSuperseded();
|
|
36338
|
+
void daemon.processPendingControlIntents?.();
|
|
36009
36339
|
if (superseded) {
|
|
36010
36340
|
if (!supersessionAnnounced) {
|
|
36011
36341
|
supersessionAnnounced = true;
|
|
@@ -36013,11 +36343,9 @@ function installDaemonLifecycle(options) {
|
|
|
36013
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`
|
|
36014
36344
|
);
|
|
36015
36345
|
}
|
|
36016
|
-
|
|
36017
|
-
|
|
36018
|
-
|
|
36019
|
-
log(`[agentprism-daemon] migrated ${migrated.length} idle session(s) to the successor: ${migrated.join(", ")}`);
|
|
36020
|
-
}
|
|
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(", ")}`);
|
|
36021
36349
|
}
|
|
36022
36350
|
} else {
|
|
36023
36351
|
supersessionAnnounced = false;
|
|
@@ -36026,7 +36354,7 @@ function installDaemonLifecycle(options) {
|
|
|
36026
36354
|
log(`[agentprism-daemon] evicted ${evicted.length} idle session(s): ${evicted.join(", ")}`);
|
|
36027
36355
|
}
|
|
36028
36356
|
}
|
|
36029
|
-
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;
|
|
36030
36358
|
if (busy) {
|
|
36031
36359
|
idleSince = void 0;
|
|
36032
36360
|
return;
|
|
@@ -36069,7 +36397,7 @@ function installDaemonLifecycle(options) {
|
|
|
36069
36397
|
|
|
36070
36398
|
// ../mcp-server/src/daemon/http-daemon.ts
|
|
36071
36399
|
import http from "node:http";
|
|
36072
|
-
import { randomUUID as
|
|
36400
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
36073
36401
|
|
|
36074
36402
|
// ../../node_modules/.pnpm/@hono+node-server@1.19.14_hono@4.12.27/node_modules/@hono/node-server/dist/index.mjs
|
|
36075
36403
|
import { Http2ServerRequest as Http2ServerRequest2, constants as h2constants } from "http2";
|
|
@@ -37032,16 +37360,16 @@ function validateRequest(headers, boundPort, env = process.env) {
|
|
|
37032
37360
|
|
|
37033
37361
|
// ../mcp-server/src/daemon/request-state.ts
|
|
37034
37362
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
37035
|
-
import { chmodSync as
|
|
37036
|
-
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";
|
|
37037
37365
|
import { workflowHomeDir as workflowHomeDir3 } from "@automatalabs/workflows";
|
|
37038
37366
|
function requestStateKeyPath(fingerprint) {
|
|
37039
|
-
return
|
|
37367
|
+
return join4(workflowHomeDir3(), "daemons", `${fingerprint}.request-state-key.json`);
|
|
37040
37368
|
}
|
|
37041
37369
|
function decodeStoredKey(path) {
|
|
37042
37370
|
let parsed;
|
|
37043
37371
|
try {
|
|
37044
|
-
parsed = JSON.parse(
|
|
37372
|
+
parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
37045
37373
|
} catch (error51) {
|
|
37046
37374
|
throw new Error(`Unable to read the daemon requestState key at ${path}: ${String(error51)}`);
|
|
37047
37375
|
}
|
|
@@ -37050,18 +37378,18 @@ function decodeStoredKey(path) {
|
|
|
37050
37378
|
}
|
|
37051
37379
|
const key = Buffer.from(parsed.key, "base64");
|
|
37052
37380
|
if (key.byteLength !== 32) throw new Error(`Invalid daemon requestState key length at ${path}`);
|
|
37053
|
-
|
|
37381
|
+
chmodSync3(path, 384);
|
|
37054
37382
|
return key;
|
|
37055
37383
|
}
|
|
37056
37384
|
function loadOrCreateRequestStateKey(fingerprint) {
|
|
37057
37385
|
const path = requestStateKeyPath(fingerprint);
|
|
37058
|
-
|
|
37386
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
37059
37387
|
const key = randomBytes2(32);
|
|
37060
37388
|
const stored = { version: 1, key: key.toString("base64") };
|
|
37061
37389
|
try {
|
|
37062
|
-
|
|
37390
|
+
writeFileSync3(path, `${JSON.stringify(stored)}
|
|
37063
37391
|
`, { flag: "wx", mode: 384 });
|
|
37064
|
-
|
|
37392
|
+
chmodSync3(path, 384);
|
|
37065
37393
|
return key;
|
|
37066
37394
|
} catch (error51) {
|
|
37067
37395
|
if (error51.code !== "EEXIST") throw error51;
|
|
@@ -37069,6 +37397,337 @@ function loadOrCreateRequestStateKey(fingerprint) {
|
|
|
37069
37397
|
}
|
|
37070
37398
|
}
|
|
37071
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
|
+
|
|
37072
37731
|
// ../mcp-server/src/daemon/session-registry.ts
|
|
37073
37732
|
var SessionRegistry = class {
|
|
37074
37733
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -37206,6 +37865,7 @@ function writeJsonRpcError(res, status, message, code = -32e3) {
|
|
|
37206
37865
|
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id: null }));
|
|
37207
37866
|
}
|
|
37208
37867
|
var MAX_MCP_BODY_BYTES = 4 * 1024 * 1024;
|
|
37868
|
+
var MAX_CONTROL_BODY_BYTES = 64 * 1024;
|
|
37209
37869
|
var BODY_REJECTED = /* @__PURE__ */ Symbol("body-rejected");
|
|
37210
37870
|
async function readMcpJsonBody(req, res) {
|
|
37211
37871
|
if (req.method !== "POST") return void 0;
|
|
@@ -37233,17 +37893,103 @@ async function readMcpJsonBody(req, res) {
|
|
|
37233
37893
|
return BODY_REJECTED;
|
|
37234
37894
|
}
|
|
37235
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
|
+
}
|
|
37236
37973
|
async function createDaemon(options) {
|
|
37237
37974
|
const host = options.host ?? "127.0.0.1";
|
|
37238
37975
|
const env = options.env ?? process.env;
|
|
37239
37976
|
const log = options.log ?? ((line) => console.error(line));
|
|
37240
37977
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
37241
37978
|
const ownPid = options.ownPid ?? process.pid;
|
|
37979
|
+
const ownInstanceId = options.ownInstanceId ?? randomUUID4();
|
|
37242
37980
|
const version2 = options.version ?? SERVER_VERSION;
|
|
37243
37981
|
const isSuperseded = options.isSuperseded ?? (() => isSupersededBy(ownPid));
|
|
37244
37982
|
const familyFingerprint = envFingerprint(env);
|
|
37245
37983
|
const sessions = new SessionRegistry();
|
|
37246
|
-
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
|
+
});
|
|
37247
37993
|
const replDrainBoundMs = options.replDrainBoundMs ?? options.sessionTtlMs ?? REPL_DRAIN_BOUND_MS;
|
|
37248
37994
|
const replPresence = new ReplPresenceLedger(replDrainBoundMs);
|
|
37249
37995
|
sessions.onConnectionOpened = (sessionId) => replPresence.reconnect(sessionId);
|
|
@@ -37264,7 +38010,7 @@ async function createDaemon(options) {
|
|
|
37264
38010
|
};
|
|
37265
38011
|
modernHandler = createMcpHandler(
|
|
37266
38012
|
() => {
|
|
37267
|
-
const clientId = `modern:${
|
|
38013
|
+
const clientId = `modern:${randomUUID4()}`;
|
|
37268
38014
|
return createWorkflowServer(options.runner, {
|
|
37269
38015
|
projects,
|
|
37270
38016
|
requireProjectDir: true,
|
|
@@ -37276,7 +38022,8 @@ async function createDaemon(options) {
|
|
|
37276
38022
|
protocolEra: "modern",
|
|
37277
38023
|
requestStateCodec,
|
|
37278
38024
|
disconnectReplClientOnClose: true,
|
|
37279
|
-
modernNotifier
|
|
38025
|
+
modernNotifier,
|
|
38026
|
+
runControl
|
|
37280
38027
|
});
|
|
37281
38028
|
},
|
|
37282
38029
|
{
|
|
@@ -37332,7 +38079,7 @@ async function createDaemon(options) {
|
|
|
37332
38079
|
return;
|
|
37333
38080
|
}
|
|
37334
38081
|
const transport = new NodeStreamableHTTPServerTransport({
|
|
37335
|
-
sessionIdGenerator: () =>
|
|
38082
|
+
sessionIdGenerator: () => randomUUID4(),
|
|
37336
38083
|
eventStore: new BoundedEventStore(),
|
|
37337
38084
|
onsessioninitialized: (sid) => {
|
|
37338
38085
|
sessions.add({
|
|
@@ -37354,7 +38101,8 @@ async function createDaemon(options) {
|
|
|
37354
38101
|
replPresence,
|
|
37355
38102
|
replClientId: () => transport.sessionId,
|
|
37356
38103
|
replDrainBoundMs,
|
|
37357
|
-
replEvalBreakChannel: options.evalBreakChannel
|
|
38104
|
+
replEvalBreakChannel: options.evalBreakChannel,
|
|
38105
|
+
runControl
|
|
37358
38106
|
});
|
|
37359
38107
|
await server.connect(transport);
|
|
37360
38108
|
const protocolOnClose = transport.onclose;
|
|
@@ -37381,6 +38129,10 @@ async function createDaemon(options) {
|
|
|
37381
38129
|
return;
|
|
37382
38130
|
}
|
|
37383
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
|
+
}
|
|
37384
38136
|
if (url2.pathname === HEALTHZ_PATH && req.method === "GET") {
|
|
37385
38137
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
37386
38138
|
res.end(
|
|
@@ -37394,6 +38146,8 @@ async function createDaemon(options) {
|
|
|
37394
38146
|
activeRuns: projects.activeRunCount(),
|
|
37395
38147
|
envFingerprint: familyFingerprint,
|
|
37396
38148
|
projects: projects.snapshot(),
|
|
38149
|
+
instanceId: ownInstanceId,
|
|
38150
|
+
controlProtocol: RUN_CONTROL_PROTOCOL,
|
|
37397
38151
|
lameDuck: isSuperseded(),
|
|
37398
38152
|
inflightRequests: sessions.inflightCount() + modernInflight
|
|
37399
38153
|
})
|
|
@@ -37426,6 +38180,8 @@ async function createDaemon(options) {
|
|
|
37426
38180
|
port: boundPort,
|
|
37427
38181
|
url: `http://${host}:${boundPort}${MCP_ENDPOINT_PATH}`,
|
|
37428
38182
|
startedAt,
|
|
38183
|
+
instanceId: ownInstanceId,
|
|
38184
|
+
controlUrl: `http://${host}:${boundPort}${RUN_CONTROL_PATH}`,
|
|
37429
38185
|
sessions,
|
|
37430
38186
|
projects,
|
|
37431
38187
|
activeRunCount: () => projects.activeRunCount(),
|
|
@@ -37433,6 +38189,7 @@ async function createDaemon(options) {
|
|
|
37433
38189
|
inflightRequestCount: () => sessions.inflightCount() + modernInflight,
|
|
37434
38190
|
isSuperseded,
|
|
37435
38191
|
evictDrainableSessions: () => sessions.evictDrainable((sessionId) => replPresence.sessionHasBusyWorkspace(sessionId)),
|
|
38192
|
+
processPendingControlIntents: () => runControl.processPendingIntents(),
|
|
37436
38193
|
async close() {
|
|
37437
38194
|
const closed = new Promise((resolvePromise) => {
|
|
37438
38195
|
httpServer.close(() => resolvePromise());
|
|
@@ -37470,6 +38227,7 @@ async function runDaemon(options = {}) {
|
|
|
37470
38227
|
const log = (line) => console.error(line);
|
|
37471
38228
|
const runner = createAcpRunner();
|
|
37472
38229
|
const supersede = options.supersede ?? false;
|
|
38230
|
+
const instanceId = randomUUID5();
|
|
37473
38231
|
let daemon;
|
|
37474
38232
|
const sessionTtlMs = envInt(SESSION_IDLE_TTL_ENV, SESSION_IDLE_TTL_MS);
|
|
37475
38233
|
const replDrainBoundMs = envInt(REPL_DRAIN_BOUND_ENV, REPL_DRAIN_BOUND_MS);
|
|
@@ -37479,7 +38237,7 @@ async function runDaemon(options = {}) {
|
|
|
37479
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})`;
|
|
37480
38238
|
};
|
|
37481
38239
|
const evalBreakChannel = createEvalBreakChannel2();
|
|
37482
|
-
const daemonOptions = { runner, log, replDrainBoundMs, evalBreakChannel };
|
|
38240
|
+
const daemonOptions = { runner, log, replDrainBoundMs, evalBreakChannel, ownInstanceId: instanceId };
|
|
37483
38241
|
if (supersede) {
|
|
37484
38242
|
let port = options.port ?? 0;
|
|
37485
38243
|
try {
|
|
@@ -37513,6 +38271,9 @@ async function runDaemon(options = {}) {
|
|
|
37513
38271
|
url: daemon.url,
|
|
37514
38272
|
startedAt: daemon.startedAt,
|
|
37515
38273
|
envFingerprint: envFingerprint(),
|
|
38274
|
+
instanceId: daemon.instanceId,
|
|
38275
|
+
controlUrl: daemon.controlUrl,
|
|
38276
|
+
controlProtocol: 1,
|
|
37516
38277
|
...await evalBreakChannel.breakUrl().then((url2) => ({ replBreakUrl: url2 })).catch(() => ({}))
|
|
37517
38278
|
});
|
|
37518
38279
|
installDaemonLifecycle({
|
|
@@ -37575,7 +38336,9 @@ function formatInstance(instance, currentPid) {
|
|
|
37575
38336
|
`${DAEMON_NAME} v${health?.version ?? info.version}`,
|
|
37576
38337
|
` pid: ${info.pid}`,
|
|
37577
38338
|
` url: ${info.url}`,
|
|
37578
|
-
` role: ${role}
|
|
38339
|
+
` role: ${role}`,
|
|
38340
|
+
` instance: ${health?.instanceId ?? info.instanceId ?? "legacy/unknown"}`,
|
|
38341
|
+
` run control: ${health?.controlProtocol === 1 || info.controlProtocol === 1 ? "v1" : "unavailable"}`
|
|
37579
38342
|
];
|
|
37580
38343
|
if (health === void 0) {
|
|
37581
38344
|
lines.push(" health: not responding (pid alive, /healthz unreachable)");
|
|
@@ -37673,7 +38436,7 @@ async function runDaemonCommand(args, options) {
|
|
|
37673
38436
|
const count = Number.isFinite(lines) && lines > 0 ? lines : 100;
|
|
37674
38437
|
let content;
|
|
37675
38438
|
try {
|
|
37676
|
-
content =
|
|
38439
|
+
content = readFileSync7(daemonLogPath(), "utf-8");
|
|
37677
38440
|
} catch {
|
|
37678
38441
|
console.log(`no daemon log at ${daemonLogPath()}`);
|
|
37679
38442
|
return 0;
|
|
@@ -44640,24 +45403,24 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44640
45403
|
}
|
|
44641
45404
|
}
|
|
44642
45405
|
exports.compileSchema = compileSchema;
|
|
44643
|
-
function resolveRef2(
|
|
45406
|
+
function resolveRef2(root2, baseId, ref) {
|
|
44644
45407
|
var _a3;
|
|
44645
45408
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
44646
|
-
const schOrFunc =
|
|
45409
|
+
const schOrFunc = root2.refs[ref];
|
|
44647
45410
|
if (schOrFunc) return schOrFunc;
|
|
44648
|
-
let _sch = resolve.call(this,
|
|
45411
|
+
let _sch = resolve.call(this, root2, ref);
|
|
44649
45412
|
if (_sch === void 0) {
|
|
44650
|
-
const schema = (_a3 =
|
|
45413
|
+
const schema = (_a3 = root2.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
44651
45414
|
const { schemaId } = this.opts;
|
|
44652
45415
|
if (schema) _sch = new SchemaEnv({
|
|
44653
45416
|
schema,
|
|
44654
45417
|
schemaId,
|
|
44655
|
-
root,
|
|
45418
|
+
root: root2,
|
|
44656
45419
|
baseId
|
|
44657
45420
|
});
|
|
44658
45421
|
}
|
|
44659
45422
|
if (_sch === void 0) return;
|
|
44660
|
-
return
|
|
45423
|
+
return root2.refs[ref] = inlineOrCompile.call(this, _sch);
|
|
44661
45424
|
}
|
|
44662
45425
|
exports.resolveRef = resolveRef2;
|
|
44663
45426
|
function inlineOrCompile(sch) {
|
|
@@ -44671,20 +45434,20 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44671
45434
|
function sameSchemaEnv(s1, s2) {
|
|
44672
45435
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
44673
45436
|
}
|
|
44674
|
-
function resolve(
|
|
45437
|
+
function resolve(root2, ref) {
|
|
44675
45438
|
let sch;
|
|
44676
45439
|
while (typeof (sch = this.refs[ref]) == "string") ref = sch;
|
|
44677
|
-
return sch || this.schemas[ref] || resolveSchema.call(this,
|
|
45440
|
+
return sch || this.schemas[ref] || resolveSchema.call(this, root2, ref);
|
|
44678
45441
|
}
|
|
44679
|
-
function resolveSchema(
|
|
45442
|
+
function resolveSchema(root2, ref) {
|
|
44680
45443
|
const p = this.opts.uriResolver.parse(ref);
|
|
44681
45444
|
const refPath = (0, resolve_1._getFullPath)(this.opts.uriResolver, p);
|
|
44682
|
-
let baseId = (0, resolve_1.getFullPath)(this.opts.uriResolver,
|
|
44683
|
-
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);
|
|
44684
45447
|
const id = (0, resolve_1.normalizeId)(refPath);
|
|
44685
45448
|
const schOrRef = this.refs[id] || this.schemas[id];
|
|
44686
45449
|
if (typeof schOrRef == "string") {
|
|
44687
|
-
const sch = resolveSchema.call(this,
|
|
45450
|
+
const sch = resolveSchema.call(this, root2, schOrRef);
|
|
44688
45451
|
if (typeof (sch === null || sch === void 0 ? void 0 : sch.schema) !== "object") return;
|
|
44689
45452
|
return getJsonPointer.call(this, p, sch);
|
|
44690
45453
|
}
|
|
@@ -44698,7 +45461,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44698
45461
|
return new SchemaEnv({
|
|
44699
45462
|
schema,
|
|
44700
45463
|
schemaId,
|
|
44701
|
-
root,
|
|
45464
|
+
root: root2,
|
|
44702
45465
|
baseId
|
|
44703
45466
|
});
|
|
44704
45467
|
}
|
|
@@ -44712,7 +45475,7 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44712
45475
|
"dependencies",
|
|
44713
45476
|
"definitions"
|
|
44714
45477
|
]);
|
|
44715
|
-
function getJsonPointer(parsedRef, { baseId, schema, root }) {
|
|
45478
|
+
function getJsonPointer(parsedRef, { baseId, schema, root: root2 }) {
|
|
44716
45479
|
var _a3;
|
|
44717
45480
|
if (((_a3 = parsedRef.fragment) === null || _a3 === void 0 ? void 0 : _a3[0]) !== "/") return;
|
|
44718
45481
|
for (const part of parsedRef.fragment.slice(1).split("/")) {
|
|
@@ -44726,13 +45489,13 @@ var require_compile2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
44726
45489
|
let env;
|
|
44727
45490
|
if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) {
|
|
44728
45491
|
const $ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schema.$ref);
|
|
44729
|
-
env = resolveSchema.call(this,
|
|
45492
|
+
env = resolveSchema.call(this, root2, $ref);
|
|
44730
45493
|
}
|
|
44731
45494
|
const { schemaId } = this.opts;
|
|
44732
45495
|
env = env || new SchemaEnv({
|
|
44733
45496
|
schema,
|
|
44734
45497
|
schemaId,
|
|
44735
|
-
root,
|
|
45498
|
+
root: root2,
|
|
44736
45499
|
baseId
|
|
44737
45500
|
});
|
|
44738
45501
|
if (env.schema !== env.root.schema) return env;
|
|
@@ -45597,11 +46360,11 @@ var require_core$32 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
45597
46360
|
while (typeof (sch = getSchEnv.call(this, keyRef)) == "string") keyRef = sch;
|
|
45598
46361
|
if (sch === void 0) {
|
|
45599
46362
|
const { schemaId } = this.opts;
|
|
45600
|
-
const
|
|
46363
|
+
const root2 = new compile_1.SchemaEnv({
|
|
45601
46364
|
schema: {},
|
|
45602
46365
|
schemaId
|
|
45603
46366
|
});
|
|
45604
|
-
sch = compile_1.resolveSchema.call(this,
|
|
46367
|
+
sch = compile_1.resolveSchema.call(this, root2, keyRef);
|
|
45605
46368
|
if (!sch) return;
|
|
45606
46369
|
this.refs[keyRef] = sch;
|
|
45607
46370
|
}
|
|
@@ -45906,16 +46669,16 @@ var require_ref2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
45906
46669
|
code(cxt) {
|
|
45907
46670
|
const { gen, schema: $ref, it } = cxt;
|
|
45908
46671
|
const { baseId, schemaEnv: env, validateName, opts, self } = it;
|
|
45909
|
-
const { root } = env;
|
|
45910
|
-
if (($ref === "#" || $ref === "#/") && baseId ===
|
|
45911
|
-
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);
|
|
45912
46675
|
if (schOrEnv === void 0) throw new ref_error_1.default(it.opts.uriResolver, baseId, $ref);
|
|
45913
46676
|
if (schOrEnv instanceof compile_1.SchemaEnv) return callValidate(schOrEnv);
|
|
45914
46677
|
return inlineRefSchema(schOrEnv);
|
|
45915
46678
|
function callRootRef() {
|
|
45916
|
-
if (env ===
|
|
45917
|
-
const rootName = gen.scopeValue("root", { ref:
|
|
45918
|
-
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);
|
|
45919
46682
|
}
|
|
45920
46683
|
function callValidate(sch) {
|
|
45921
46684
|
callRef(cxt, getValidate(cxt, sch), sch, sch.$async);
|
|
@@ -47630,12 +48393,12 @@ var require_dynamicAnchor2 = /* @__PURE__ */ __commonJSMin2(((exports) => {
|
|
|
47630
48393
|
exports.dynamicAnchor = dynamicAnchor;
|
|
47631
48394
|
function _getValidate(cxt) {
|
|
47632
48395
|
const { schemaEnv, schema, self } = cxt.it;
|
|
47633
|
-
const { root, baseId, localRefs, meta: meta3 } = schemaEnv.root;
|
|
48396
|
+
const { root: root2, baseId, localRefs, meta: meta3 } = schemaEnv.root;
|
|
47634
48397
|
const { schemaId } = self.opts;
|
|
47635
48398
|
const sch = new compile_1.SchemaEnv({
|
|
47636
48399
|
schema,
|
|
47637
48400
|
schemaId,
|
|
47638
|
-
root,
|
|
48401
|
+
root: root2,
|
|
47639
48402
|
baseId,
|
|
47640
48403
|
localRefs,
|
|
47641
48404
|
meta: meta3
|
|
@@ -49032,8 +49795,8 @@ async function random(size) {
|
|
|
49032
49795
|
const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
|
|
49033
49796
|
let result = "";
|
|
49034
49797
|
while (result.length < size) {
|
|
49035
|
-
const
|
|
49036
|
-
for (const randomByte of
|
|
49798
|
+
const randomBytes4 = await getRandomValues(size - result.length);
|
|
49799
|
+
for (const randomByte of randomBytes4) {
|
|
49037
49800
|
if (randomByte < evenDistCutoff) {
|
|
49038
49801
|
result += mask[randomByte % mask.length];
|
|
49039
49802
|
}
|
|
@@ -51157,6 +51920,7 @@ async function runShim(options) {
|
|
|
51157
51920
|
const info = await ensureDaemonRunning({ bundlePath: options.bundlePath, port: options.port, log });
|
|
51158
51921
|
let replBreakUrl = info.replBreakUrl;
|
|
51159
51922
|
let exiting = false;
|
|
51923
|
+
let compatibilityDrainTimer;
|
|
51160
51924
|
function fireOutOfBandBreak(projectDir) {
|
|
51161
51925
|
if (typeof projectDir !== "string" || replBreakUrl === void 0) return;
|
|
51162
51926
|
let key;
|
|
@@ -51277,6 +52041,27 @@ async function runShim(options) {
|
|
|
51277
52041
|
log(`[${DAEMON_NAME} shim] fatal: ${reason}`);
|
|
51278
52042
|
void shutdown(1);
|
|
51279
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
|
+
}
|
|
51280
52065
|
async function startReinitialize(reason) {
|
|
51281
52066
|
if (reinitializing || exiting) return;
|
|
51282
52067
|
if (!recoveryAllowed()) {
|
|
@@ -51292,6 +52077,7 @@ async function runShim(options) {
|
|
|
51292
52077
|
retireTransport(http2, reason);
|
|
51293
52078
|
const fresh = await ensureDaemonRunning({ bundlePath: options.bundlePath, port: options.port, log });
|
|
51294
52079
|
replBreakUrl = fresh.replBreakUrl;
|
|
52080
|
+
armCompatibilityDrain(fresh);
|
|
51295
52081
|
http2 = makeHttpTransport(fresh.url);
|
|
51296
52082
|
await http2.start();
|
|
51297
52083
|
if (cachedInitialize === void 0) {
|
|
@@ -51383,6 +52169,8 @@ async function runShim(options) {
|
|
|
51383
52169
|
async function shutdown(code) {
|
|
51384
52170
|
if (exiting) return;
|
|
51385
52171
|
exiting = true;
|
|
52172
|
+
if (compatibilityDrainTimer !== void 0) clearInterval(compatibilityDrainTimer);
|
|
52173
|
+
compatibilityDrainTimer = void 0;
|
|
51386
52174
|
await http2.terminateSession().catch(() => void 0);
|
|
51387
52175
|
await http2.close().catch(() => void 0);
|
|
51388
52176
|
await stdio.close().catch(() => void 0);
|
|
@@ -51391,6 +52179,7 @@ async function runShim(options) {
|
|
|
51391
52179
|
stdio.onclose = () => void shutdown(0);
|
|
51392
52180
|
process.once("SIGINT", () => void shutdown(0));
|
|
51393
52181
|
process.once("SIGTERM", () => void shutdown(0));
|
|
52182
|
+
armCompatibilityDrain(info);
|
|
51394
52183
|
await http2.start();
|
|
51395
52184
|
await stdio.start();
|
|
51396
52185
|
}
|