@krapht/ork 1.0.0-alpha.2 → 1.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{__build_info__-fy6L1Mrd.mjs → __build_info__-1i1IwLg7.mjs} +2 -2
- package/dist/bin.mjs +72 -32
- package/dist/{clean-DaHjs__2.mjs → clean-B1zPxq6A.mjs} +1 -1
- package/dist/{completion-CSU5vkEt.mjs → completion-BY-frez8.mjs} +5 -5
- package/dist/{context-DT8MfEIR.mjs → context-CD5TCD1D.mjs} +3 -3
- package/dist/{decide-CGyx9rv7.mjs → decide-CBhYcjwI.mjs} +1 -1
- package/dist/{find-D6Gj1sxg.mjs → find-BgZjVKhv.mjs} +1 -1
- package/dist/{format-Dnfv8zMT.mjs → format-Creyq4U3.mjs} +2 -2
- package/dist/{in-a-run-pLAZeNys.mjs → in-a-run-CsSBQQ2q.mjs} +1 -1
- package/dist/{limits-B1-DYc3d.mjs → limits-D_HxH0CK.mjs} +1 -1
- package/dist/{list-BrH8KRSJ.mjs → list-CzzpVEpM.mjs} +1 -1
- package/dist/{mcp-DE_EmyVm.mjs → mcp-DYmQt372.mjs} +6 -6
- package/dist/{output-BLQWD6MJ.mjs → output-CuivUcDa.mjs} +1 -1
- package/dist/{pause-CL6N_Xrj.mjs → pause-W6MzQqg8.mjs} +4 -4
- package/dist/{relay-0SD0erwf.mjs → relay-CsdoZUXJ.mjs} +2 -2
- package/dist/{remove-BeGdqroy.mjs → remove-DLfDjBdF.mjs} +3 -3
- package/dist/{send-C8tunWhv.mjs → send-Bw2FI_Sv.mjs} +3 -3
- package/dist/{src-Dujmvv4s.mjs → src-BRB5EQyC.mjs} +235 -32
- package/dist/{src-DOcK8Jfp.mjs → src-Bc9yaAiZ.mjs} +159 -161
- package/dist/{src-CCUKRX51.mjs → src-CrYCkU9J.mjs} +16 -8
- package/dist/{src-BbyEoMn4.mjs → src-CsjumY1t.mjs} +7 -7
- package/dist/ui/assets/{app-T7wVAhgx.js → app-m9Gfrg_S.js} +1 -1
- package/dist/ui/assets/{index-9dFha9Qn.js → index-UEC5ZLYw.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CTmO2Xzu.mjs → ui-wX050aH1.mjs} +4 -4
- package/dist/{usage-DYUmP0i4.mjs → usage-C96RDH-G.mjs} +5 -5
- package/dist/{watch-CtbgDAcz.mjs → watch-vdRobJil.mjs} +6 -6
- package/dist/{workflows-D1_WVukk.mjs → workflows-B28q8zMY.mjs} +3 -3
- package/dist/{worktrees-DdMYoNOC.mjs → worktrees-CKDRkl6b.mjs} +5 -5
- package/package.json +1 -1
|
@@ -8,6 +8,65 @@ import { createConnection, createServer } from "node:net";
|
|
|
8
8
|
import { createInterface } from "node:readline";
|
|
9
9
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
10
10
|
import { setImmediate, setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
11
|
+
// ../core/src/graph/diagnostics.ts
|
|
12
|
+
const error = (code, message, path) => ({
|
|
13
|
+
severity: "error",
|
|
14
|
+
code,
|
|
15
|
+
message,
|
|
16
|
+
path
|
|
17
|
+
});
|
|
18
|
+
const warning = (code, message, path) => ({
|
|
19
|
+
severity: "warning",
|
|
20
|
+
code,
|
|
21
|
+
message,
|
|
22
|
+
path
|
|
23
|
+
});
|
|
24
|
+
// ../core/src/experiments.ts
|
|
25
|
+
/** The only list: an entry is added by the change that adds its experiment. */
|
|
26
|
+
const EXPERIMENTS = [];
|
|
27
|
+
const EXPERIMENTAL_ENV = "ORK_EXPERIMENTAL";
|
|
28
|
+
/** The registry's names, for a message. */
|
|
29
|
+
const knownExperiments = (registry = EXPERIMENTS) => registry.map(({ name }) => name).join(", ") || "none";
|
|
30
|
+
const envNames = (env) => [...new Set((env[EXPERIMENTAL_ENV] ?? "").split(",").map((name) => name.trim()).filter((name) => name !== ""))];
|
|
31
|
+
/**
|
|
32
|
+
* What is on, from the project file and the environment together. Any name the
|
|
33
|
+
* registry doesn't have makes it an error instead: a typo must not look like a
|
|
34
|
+
* running experiment.
|
|
35
|
+
*/
|
|
36
|
+
const experimentsOn = ({ env = process.env, project, where, registry = EXPERIMENTS } = {}) => {
|
|
37
|
+
const inProject = project?.experimental ?? [];
|
|
38
|
+
const listed = [...[...new Set(inProject)].map((name) => ({
|
|
39
|
+
name,
|
|
40
|
+
source: "project",
|
|
41
|
+
at: where?.(["experimental", inProject.indexOf(name)]) ?? "project.yaml"
|
|
42
|
+
})), ...envNames(env).map((name) => ({
|
|
43
|
+
name,
|
|
44
|
+
source: "environment",
|
|
45
|
+
at: EXPERIMENTAL_ENV
|
|
46
|
+
}))];
|
|
47
|
+
const unknown = listed.filter(({ name }) => !registry.some((entry) => entry.name === name)).map(({ name, source, at }) => ({
|
|
48
|
+
name,
|
|
49
|
+
source,
|
|
50
|
+
where: at,
|
|
51
|
+
message: `${at}${source === "project" ? ":" : ""} names an unknown experiment \`${name}\`; known: ${knownExperiments(registry)}`
|
|
52
|
+
}));
|
|
53
|
+
if (unknown.length > 0) return {
|
|
54
|
+
ok: false,
|
|
55
|
+
unknown
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
on: [...new Set(listed.map(({ name }) => name))].map((name) => ({
|
|
60
|
+
name,
|
|
61
|
+
sources: listed.filter((l) => l.name === name).map((l) => l.source)
|
|
62
|
+
}))
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
/** Unknown names as errors, for the checks every workflow goes through. */
|
|
66
|
+
const experimentDiagnostics = (options) => {
|
|
67
|
+
const state = experimentsOn(options);
|
|
68
|
+
return state.ok ? [] : state.unknown.map(({ message }) => error("experiment.unknown", message, []));
|
|
69
|
+
};
|
|
11
70
|
// ../integrations/src/integration.ts
|
|
12
71
|
/** Declares an integration with its option types checked. */
|
|
13
72
|
const defineIntegration = (integration) => integration;
|
|
@@ -400,6 +459,7 @@ const PracticesSchema = z.strictObject({
|
|
|
400
459
|
])]).optional().describe("Smallest complete change. `true` is `full`. Default: `full`.")
|
|
401
460
|
});
|
|
402
461
|
const TurnsSchema = z.number().int().positive();
|
|
462
|
+
const McpNamesSchema = z.array(z.string().min(1)).optional().describe("MCP servers from the project's `.mcp.json` this node's sessions load, beside Ork's own. Default: none.");
|
|
403
463
|
const ToolRulesSchema = z.strictObject({
|
|
404
464
|
allow: z.array(z.string()).optional(),
|
|
405
465
|
deny: z.array(z.string()).optional(),
|
|
@@ -553,6 +613,7 @@ const claudeFields = {
|
|
|
553
613
|
const ClaudeLeaderNodeSchema = z.strictObject({
|
|
554
614
|
type: z.literal("claude.leader"),
|
|
555
615
|
...claudeFields,
|
|
616
|
+
mcp: McpNamesSchema,
|
|
556
617
|
...notifyFields,
|
|
557
618
|
...stepFields,
|
|
558
619
|
maxWorkers: z.number().int().nonnegative().optional().describe("Cap on dynamically spawned workers."),
|
|
@@ -561,6 +622,7 @@ const ClaudeLeaderNodeSchema = z.strictObject({
|
|
|
561
622
|
const ClaudeWorkerNodeSchema = z.strictObject({
|
|
562
623
|
type: z.literal("claude.worker"),
|
|
563
624
|
...claudeFields,
|
|
625
|
+
mcp: McpNamesSchema,
|
|
564
626
|
...notifyFields,
|
|
565
627
|
...stepFields
|
|
566
628
|
});
|
|
@@ -863,6 +925,7 @@ const DefaultsSchema = z.strictObject({
|
|
|
863
925
|
maxTurns: TurnsSchema.optional(),
|
|
864
926
|
timeout: TimeoutSchema.optional().describe("For tool and Claude nodes that set none; a gate's own timeout is separate."),
|
|
865
927
|
practices: PracticesSchema.optional(),
|
|
928
|
+
mcp: McpNamesSchema,
|
|
866
929
|
before: stepFields.before
|
|
867
930
|
});
|
|
868
931
|
/** What the placeholders in a git template may name. */
|
|
@@ -894,7 +957,8 @@ const ProjectSchema = z.strictObject({
|
|
|
894
957
|
defaults: DefaultsSchema.optional().describe("Defaults for every workflow in the project; a workflow's own win."),
|
|
895
958
|
quotas: QuotasSchema.optional().describe("Most this project may spend on this machine. The user's own caps apply too."),
|
|
896
959
|
calls: z.strictObject({ accept: z.array(z.string()).describe("The registered projects whose workflows may call this one's.") }).optional().describe("Default: any registered project may call this one's workflows."),
|
|
897
|
-
context: z.array(z.string()).optional().describe("Context files every node reads, relative to the project root; they come before the workflow's.")
|
|
960
|
+
context: z.array(z.string()).optional().describe("Context files every node reads, relative to the project root; they come before the workflow's."),
|
|
961
|
+
experimental: z.array(z.string()).optional().describe("Experimental features this project turns on.")
|
|
898
962
|
});
|
|
899
963
|
/** `~/.ork/settings.yaml`: the user's own settings, and never a credential. */
|
|
900
964
|
const UserSettingsSchema = z.strictObject({
|
|
@@ -1223,7 +1287,7 @@ const renderJsonSchema = () => {
|
|
|
1223
1287
|
* model the serializer follows, so that a document edit puts a new key where
|
|
1224
1288
|
* the serializer would and a canonical file stays canonical (RFC-008).
|
|
1225
1289
|
*/
|
|
1226
|
-
const isRecord$
|
|
1290
|
+
const isRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1227
1291
|
const unwrapped = (schema) => {
|
|
1228
1292
|
let current = schema;
|
|
1229
1293
|
while (current instanceof z.ZodOptional || current instanceof z.ZodNullable || current instanceof z.ZodDefault) current = current.unwrap();
|
|
@@ -1244,8 +1308,8 @@ const declaresType = (object, type) => {
|
|
|
1244
1308
|
/** The object `value` is, among those `schema` allows. */
|
|
1245
1309
|
const objectFor = (schema, value) => {
|
|
1246
1310
|
const objects = objectsIn(schema);
|
|
1247
|
-
const keys = isRecord$
|
|
1248
|
-
const type = isRecord$
|
|
1311
|
+
const keys = isRecord$3(value) ? Object.keys(value) : [];
|
|
1312
|
+
const type = isRecord$3(value) ? value["type"] : void 0;
|
|
1249
1313
|
return objects.find((object) => declaresType(object, type)) ?? objects.find((object) => keys.every((key) => key in object.shape)) ?? objects[0];
|
|
1250
1314
|
};
|
|
1251
1315
|
/** The keys `schema` declares for `value`, in canonical order. */
|
|
@@ -1261,14 +1325,14 @@ const childOf = (schema, key, value) => {
|
|
|
1261
1325
|
/** `value` with every object's keys in canonical order and no undefined. */
|
|
1262
1326
|
const inCanonicalOrder = (value, schema) => {
|
|
1263
1327
|
if (Array.isArray(value)) return value.map((item, index) => inCanonicalOrder(item, childOf(schema, index, value)));
|
|
1264
|
-
if (!isRecord$
|
|
1328
|
+
if (!isRecord$3(value)) return value;
|
|
1265
1329
|
const order = keysOf(schema, value);
|
|
1266
1330
|
return Object.fromEntries([...order.filter((known) => known in value), ...Object.keys(value).filter((other) => !order.includes(other))].filter((key) => value[key] !== void 0).map((key) => [key, inCanonicalOrder(value[key], childOf(schema, key, value))]));
|
|
1267
1331
|
};
|
|
1268
1332
|
/** Two plain values alike, whatever the order of their keys. */
|
|
1269
1333
|
const sameValue$1 = (a, b) => {
|
|
1270
1334
|
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => sameValue$1(x, b[i]));
|
|
1271
|
-
if (isRecord$
|
|
1335
|
+
if (isRecord$3(a) && isRecord$3(b)) {
|
|
1272
1336
|
const left = Object.entries(a);
|
|
1273
1337
|
return left.length === Object.keys(b).length && left.every(([key, value]) => sameValue$1(value, b[key]));
|
|
1274
1338
|
}
|
|
@@ -1435,7 +1499,7 @@ const entryOf = (doc, raw) => {
|
|
|
1435
1499
|
}
|
|
1436
1500
|
if (!isMap(item)) return void 0;
|
|
1437
1501
|
const value = item.toJS(doc);
|
|
1438
|
-
if (!isRecord$
|
|
1502
|
+
if (!isRecord$3(value)) return void 0;
|
|
1439
1503
|
const { from, to, when, maxRounds, ...rest } = value;
|
|
1440
1504
|
const whenIsText = typeof when === "string";
|
|
1441
1505
|
const roundsIsNumber = typeof maxRounds === "number";
|
|
@@ -1653,7 +1717,7 @@ const withOptions = (doc, item, next) => {
|
|
|
1653
1717
|
const custom = Object.keys(next.extra).length > 0;
|
|
1654
1718
|
if (!isMap(item) || !hasOptions && !custom && !hasComment(item)) return authored(next);
|
|
1655
1719
|
const value = item.toJS(doc);
|
|
1656
|
-
const { when: _when, maxRounds: _rounds, ...rest } = isRecord$
|
|
1720
|
+
const { when: _when, maxRounds: _rounds, ...rest } = isRecord$3(value) ? value : {};
|
|
1657
1721
|
return {
|
|
1658
1722
|
...rest,
|
|
1659
1723
|
...next.when === void 0 ? {} : { when: next.when },
|
|
@@ -2394,19 +2458,6 @@ const parseSkillRef = (ref) => {
|
|
|
2394
2458
|
message: `\`${ref}\` is neither a built-in (ork:<name>) nor a path to a skill folder (./…); project skills load by themselves`
|
|
2395
2459
|
};
|
|
2396
2460
|
};
|
|
2397
|
-
// ../core/src/graph/diagnostics.ts
|
|
2398
|
-
const error = (code, message, path) => ({
|
|
2399
|
-
severity: "error",
|
|
2400
|
-
code,
|
|
2401
|
-
message,
|
|
2402
|
-
path
|
|
2403
|
-
});
|
|
2404
|
-
const warning = (code, message, path) => ({
|
|
2405
|
-
severity: "warning",
|
|
2406
|
-
code,
|
|
2407
|
-
message,
|
|
2408
|
-
path
|
|
2409
|
-
});
|
|
2410
2461
|
// ../core/src/practices/caveman.ts
|
|
2411
2462
|
const CAVEMAN = `## Practice: caveman (terse voice)
|
|
2412
2463
|
|
|
@@ -4516,6 +4567,119 @@ const FEEDBACK_SEVERITIES = [
|
|
|
4516
4567
|
"medium",
|
|
4517
4568
|
"high"
|
|
4518
4569
|
];
|
|
4570
|
+
// ../core/src/project/mcp-file.ts
|
|
4571
|
+
/**
|
|
4572
|
+
* @file The project's `.mcp.json`, the file Claude Code reads too, so a server
|
|
4573
|
+
* is defined in one place: reading its servers, and the compiler's check of
|
|
4574
|
+
* the servers a Claude node names (`mcp:`, ADR-067). A session's servers are
|
|
4575
|
+
* read with the file's text left as written, so no value leaves the file.
|
|
4576
|
+
*/
|
|
4577
|
+
const MCP_FILE = ".mcp.json";
|
|
4578
|
+
/** Ork's own MCP server: a project can't redefine it. */
|
|
4579
|
+
const ORK_MCP_NAME = "ork";
|
|
4580
|
+
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4581
|
+
/** The file's `mcpServers`; `{}` when there is no file. */
|
|
4582
|
+
const serversIn = (root) => {
|
|
4583
|
+
const file = path.join(root, MCP_FILE);
|
|
4584
|
+
if (!existsSync(file)) return {
|
|
4585
|
+
ok: true,
|
|
4586
|
+
value: {}
|
|
4587
|
+
};
|
|
4588
|
+
try {
|
|
4589
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
4590
|
+
const servers = isRecord$2(parsed) ? parsed["mcpServers"] : void 0;
|
|
4591
|
+
return {
|
|
4592
|
+
ok: true,
|
|
4593
|
+
value: isRecord$2(servers) ? servers : {}
|
|
4594
|
+
};
|
|
4595
|
+
} catch (error) {
|
|
4596
|
+
const reason = error instanceof SyntaxError ? "it is not valid JSON" : error instanceof Error ? error.message : String(error);
|
|
4597
|
+
return {
|
|
4598
|
+
ok: false,
|
|
4599
|
+
error: `${MCP_FILE} can't be read: ${reason}`
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4602
|
+
};
|
|
4603
|
+
/** A server's field that holds something other than text: it would be dropped. */
|
|
4604
|
+
const mcpNotText = (server) => [
|
|
4605
|
+
"args",
|
|
4606
|
+
"env",
|
|
4607
|
+
"headers"
|
|
4608
|
+
].find((field) => {
|
|
4609
|
+
const value = server[field];
|
|
4610
|
+
return (Array.isArray(value) ? value : isRecord$2(value) ? Object.values(value) : []).some((item) => typeof item !== "string");
|
|
4611
|
+
});
|
|
4612
|
+
/** Why an entry can't be loaded, whatever its name; its values aren't quoted. */
|
|
4613
|
+
const entryProblem = (server) => {
|
|
4614
|
+
if (!isRecord$2(server)) return "it is not an object";
|
|
4615
|
+
if (server["args"] !== void 0 && !Array.isArray(server["args"])) return "its args is not a list";
|
|
4616
|
+
const notRecord = ["env", "headers"].find((field) => server[field] !== void 0 && !isRecord$2(server[field]));
|
|
4617
|
+
if (notRecord !== void 0) return `its ${notRecord} is not an object`;
|
|
4618
|
+
const field = mcpNotText(server);
|
|
4619
|
+
if (field !== void 0) return `its ${field} holds a value that isn't text`;
|
|
4620
|
+
return typeof server["command"] === "string" || typeof server["url"] === "string" ? void 0 : "it has neither a command nor a url";
|
|
4621
|
+
};
|
|
4622
|
+
/** What the compiler checks a workflow's `mcp:` names against. */
|
|
4623
|
+
const mcpFileOptions = (root) => {
|
|
4624
|
+
const read = serversIn(root);
|
|
4625
|
+
if (!read.ok) return {
|
|
4626
|
+
mcpServers: [],
|
|
4627
|
+
mcpFileProblem: read.error
|
|
4628
|
+
};
|
|
4629
|
+
if (!existsSync(path.join(root, ".mcp.json"))) return {
|
|
4630
|
+
mcpServers: [],
|
|
4631
|
+
mcpFileProblem: `there is no ${MCP_FILE}`
|
|
4632
|
+
};
|
|
4633
|
+
return {
|
|
4634
|
+
mcpServers: Object.keys(read.value),
|
|
4635
|
+
mcpMalformed: Object.fromEntries(Object.entries(read.value).flatMap(([name, server]) => {
|
|
4636
|
+
const problem = entryProblem(server);
|
|
4637
|
+
return problem === void 0 ? [] : [[name, problem]];
|
|
4638
|
+
}))
|
|
4639
|
+
};
|
|
4640
|
+
};
|
|
4641
|
+
/** The error for one name a node gave, or nothing when it can load. */
|
|
4642
|
+
const nameProblem = (id, name, options, known) => {
|
|
4643
|
+
if (name === ORK_MCP_NAME) return {
|
|
4644
|
+
code: "mcp.reserved",
|
|
4645
|
+
message: `\`${id}\`: \`${ORK_MCP_NAME}\` is Ork's own server, name another`
|
|
4646
|
+
};
|
|
4647
|
+
if (!known.includes(name)) return {
|
|
4648
|
+
code: "mcp.unknown",
|
|
4649
|
+
message: `\`${id}\`: no MCP server \`${name}\`${options.mcpFileProblem === void 0 ? ` in ${MCP_FILE}, which has ${known.length === 0 ? "none" : known.join(", ")}` : `: ${options.mcpFileProblem}`}`
|
|
4650
|
+
};
|
|
4651
|
+
const problem = Object.hasOwn(options.mcpMalformed ?? {}, name) ? options.mcpMalformed?.[name] : void 0;
|
|
4652
|
+
return problem === void 0 ? void 0 : {
|
|
4653
|
+
code: "mcp.malformed",
|
|
4654
|
+
message: `\`${id}\`: MCP server \`${name}\` in ${MCP_FILE} is malformed: ${problem}`
|
|
4655
|
+
};
|
|
4656
|
+
};
|
|
4657
|
+
/** Every server a Claude node's sessions would load exists and can load. */
|
|
4658
|
+
const checkMcpServers = (workflow, options) => {
|
|
4659
|
+
const known = options.mcpServers;
|
|
4660
|
+
if (known === void 0) return [];
|
|
4661
|
+
return Object.entries(workflow.nodes).flatMap(([id, node]) => {
|
|
4662
|
+
if (node.type !== "claude.leader" && node.type !== "claude.worker") return [];
|
|
4663
|
+
const own = node.mcp !== void 0;
|
|
4664
|
+
return (node.mcp ?? workflow.defaults?.mcp ?? []).flatMap((name, index) => {
|
|
4665
|
+
const problem = nameProblem(id, name, options, known);
|
|
4666
|
+
return problem === void 0 ? [] : [{
|
|
4667
|
+
severity: "error",
|
|
4668
|
+
...problem,
|
|
4669
|
+
path: own ? [
|
|
4670
|
+
"nodes",
|
|
4671
|
+
id,
|
|
4672
|
+
"mcp",
|
|
4673
|
+
index
|
|
4674
|
+
] : [
|
|
4675
|
+
"defaults",
|
|
4676
|
+
"mcp",
|
|
4677
|
+
index
|
|
4678
|
+
]
|
|
4679
|
+
}];
|
|
4680
|
+
});
|
|
4681
|
+
});
|
|
4682
|
+
};
|
|
4519
4683
|
// ../core/src/project/templates.ts
|
|
4520
4684
|
/**
|
|
4521
4685
|
* @file Git name templates (`ork/{run}/{node}`, `{inputs.type}/{inputs.slug}`):
|
|
@@ -4561,11 +4725,15 @@ const PROJECT_FILE = ".ork/project.yaml";
|
|
|
4561
4725
|
const loadProject = (projectRoot, file = path.join(projectRoot, PROJECT_FILE)) => {
|
|
4562
4726
|
if (!existsSync(file)) return { diagnostics: [] };
|
|
4563
4727
|
const parsed = parseProject(readFileSync(file, "utf8"));
|
|
4728
|
+
const label = shownPath(file, projectRoot);
|
|
4564
4729
|
if (parsed.ok) return {
|
|
4565
4730
|
project: parsed.project,
|
|
4566
|
-
diagnostics: []
|
|
4731
|
+
diagnostics: [],
|
|
4732
|
+
where: (at) => {
|
|
4733
|
+
const position = parsed.locate(at);
|
|
4734
|
+
return position === null ? label : `${label}:${position.line}:${position.column}`;
|
|
4735
|
+
}
|
|
4567
4736
|
};
|
|
4568
|
-
const label = shownPath(file, projectRoot);
|
|
4569
4737
|
return { diagnostics: parsed.diagnostics.map((d) => {
|
|
4570
4738
|
const at = d.line === void 0 ? "" : `:${d.line}:${d.column ?? 1}`;
|
|
4571
4739
|
return error(d.code, `${label}${at}: ${d.message}`, []);
|
|
@@ -5891,6 +6059,7 @@ const compileWorkflow = (authored, options = {}) => {
|
|
|
5891
6059
|
...rules.diagnostics,
|
|
5892
6060
|
...channels.diagnostics,
|
|
5893
6061
|
...checkRules(workflow),
|
|
6062
|
+
...checkMcpServers(workflow, options),
|
|
5894
6063
|
...checkReach(workflow, edges, entries)
|
|
5895
6064
|
];
|
|
5896
6065
|
diagnostics.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);
|
|
@@ -9022,12 +9191,17 @@ const checkWorkflowText = (text, { place, workflowDir, registered, home }) => {
|
|
|
9022
9191
|
catalog,
|
|
9023
9192
|
workflowDir,
|
|
9024
9193
|
projectRoot,
|
|
9025
|
-
...loaded.project === void 0 ? {} : { project: loaded.project }
|
|
9194
|
+
...loaded.project === void 0 ? {} : { project: loaded.project },
|
|
9195
|
+
...mcpFileOptions(projectRoot)
|
|
9026
9196
|
});
|
|
9027
9197
|
const { graph } = compiled;
|
|
9028
9198
|
const diagnostics = [];
|
|
9029
9199
|
for (const diagnostic of [
|
|
9030
9200
|
...loaded.diagnostics,
|
|
9201
|
+
...experimentDiagnostics({
|
|
9202
|
+
project: loaded.project,
|
|
9203
|
+
where: loaded.where
|
|
9204
|
+
}),
|
|
9031
9205
|
...compiled.diagnostics,
|
|
9032
9206
|
...checkCallTargets(parsed.workflow, place, registered),
|
|
9033
9207
|
...graph === null ? [] : contextDiagnostics(graph.workflow, {
|
|
@@ -9612,10 +9786,11 @@ const roleSection = (workflow, node) => {
|
|
|
9612
9786
|
return role === void 0 || role === "" ? [] : [`# Your role: ${node.agent}\n\n${role}`];
|
|
9613
9787
|
};
|
|
9614
9788
|
/** The node's own tool rules, the project's and workflow's, and isolation's. */
|
|
9615
|
-
const toolRules = (node, readonly, rules) => ({
|
|
9789
|
+
const toolRules = (node, readonly, rules, mcp) => ({
|
|
9616
9790
|
allow: [.../* @__PURE__ */ new Set([
|
|
9617
9791
|
...rules?.permissions?.allow ?? [],
|
|
9618
9792
|
...node.tools?.allow ?? [],
|
|
9793
|
+
...mcp.map((server) => `mcp__${server.replaceAll(/[^\w-]/gu, "_")}`),
|
|
9619
9794
|
...readonly ? READ_ONLY_COMMANDS : []
|
|
9620
9795
|
])],
|
|
9621
9796
|
deny: [.../* @__PURE__ */ new Set([
|
|
@@ -9645,6 +9820,7 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
9645
9820
|
error: `\`${id}\` is a sub-agent; it runs inside \`${node.parent}\`'s session`
|
|
9646
9821
|
};
|
|
9647
9822
|
const defaults = workflow.defaults ?? {};
|
|
9823
|
+
const mcp = node.mcp ?? defaults.mcp ?? [];
|
|
9648
9824
|
const isolation = node.isolation ?? defaults.isolation ?? "worktree";
|
|
9649
9825
|
const ownWorktree = options.checkout?.own === true;
|
|
9650
9826
|
if ((isolation === "worktree" && !ownWorktree || isolation === "continue" && options.checkout === void 0) && options.inPlace !== true) return {
|
|
@@ -9688,7 +9864,7 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
9688
9864
|
...contextSections(context.files, "node")
|
|
9689
9865
|
].join("\n\n"),
|
|
9690
9866
|
settingSources: ["project", "local"],
|
|
9691
|
-
tools: toolRules(node, isolation === "readonly", workflow.rules),
|
|
9867
|
+
tools: toolRules(node, isolation === "readonly", workflow.rules, mcp),
|
|
9692
9868
|
permissionMode: "default",
|
|
9693
9869
|
reportSchema: reportSchemaFor(node)
|
|
9694
9870
|
};
|
|
@@ -9714,6 +9890,7 @@ const planNodeSession = (workflow, id, options) => {
|
|
|
9714
9890
|
ok: true,
|
|
9715
9891
|
spec,
|
|
9716
9892
|
driver: namedDriver(node, workflow),
|
|
9893
|
+
mcp,
|
|
9717
9894
|
warnings,
|
|
9718
9895
|
context
|
|
9719
9896
|
};
|
|
@@ -11383,7 +11560,7 @@ const runGit = (cwd, args) => new Promise((resolve) => {
|
|
|
11383
11560
|
resolve({
|
|
11384
11561
|
code: error === null ? 0 : typeof error.code === "number" ? error.code : -1,
|
|
11385
11562
|
stdout,
|
|
11386
|
-
stderr: stderr || (error?.message ?? "")
|
|
11563
|
+
stderr: error !== null && !existsSync(cwd) ? `the folder ${cwd} doesn't exist` : stderr || (error?.message ?? "")
|
|
11387
11564
|
});
|
|
11388
11565
|
});
|
|
11389
11566
|
});
|
|
@@ -12560,6 +12737,14 @@ const upstreamPlace = (graph, id, placeOf, root) => {
|
|
|
12560
12737
|
return other === void 0 ? only : void 0;
|
|
12561
12738
|
};
|
|
12562
12739
|
// ../core/src/worktree/handoff.ts
|
|
12740
|
+
/**
|
|
12741
|
+
* @file A node's worktree under the project's git rules (ADR-032): opening it
|
|
12742
|
+
* (named by the rules, on a branch or detached, taking its owner's work),
|
|
12743
|
+
* bringing it up to its owner's work on a later run, and handing a passing
|
|
12744
|
+
* child's work to its owner: a `--no-ff` merge, or a patch applied
|
|
12745
|
+
* uncommitted. Also what a session is told about its checkout, and the
|
|
12746
|
+
* artifacts carried into a new worktree.
|
|
12747
|
+
*/
|
|
12563
12748
|
/** Where a child's work goes: its owner's branch, or its folder. */
|
|
12564
12749
|
const nameOf = (worktree) => worktree.branch ?? worktree.path;
|
|
12565
12750
|
const headTree = async (worktree) => (await gitOk(worktree, ["rev-parse", "HEAD^{tree}"])).trim();
|
|
@@ -12611,6 +12796,16 @@ const openWorktree = async (options) => {
|
|
|
12611
12796
|
};
|
|
12612
12797
|
};
|
|
12613
12798
|
/**
|
|
12799
|
+
* Whether a node's worktree is still on disk. One removed outside Ork (an agent
|
|
12800
|
+
* ran `git worktree remove`) is forgotten by git too, so it can be opened again
|
|
12801
|
+
* from its owner's work rather than failing the node's next run.
|
|
12802
|
+
*/
|
|
12803
|
+
const stillThere = async (root, worktree) => {
|
|
12804
|
+
if (existsSync(worktree.path)) return true;
|
|
12805
|
+
await gitOk(root, ["worktree", "prune"]);
|
|
12806
|
+
return false;
|
|
12807
|
+
};
|
|
12808
|
+
/**
|
|
12614
12809
|
* A later run (a loop, another delegation) starts from the owner's work. With
|
|
12615
12810
|
* commits, the branch fast-forwards when it has nothing of its own; under
|
|
12616
12811
|
* `commit: never`, a child whose work reached its owner takes the owner's
|
|
@@ -12625,6 +12820,15 @@ const refreshWorktree = async (rules, worktree, host, handedOver) => {
|
|
|
12625
12820
|
worktree.snapshot = await syncFrom(worktree.path, host.path);
|
|
12626
12821
|
return true;
|
|
12627
12822
|
};
|
|
12823
|
+
/**
|
|
12824
|
+
* A node's worktree when it runs again: `gone` when its folder was removed
|
|
12825
|
+
* outside Ork (it is opened anew), `synced` when it took its owner's work, or
|
|
12826
|
+
* `same`.
|
|
12827
|
+
*/
|
|
12828
|
+
const reenter = async (root, rules, worktree, host, handedOver) => {
|
|
12829
|
+
if (!await stillThere(root, worktree)) return "gone";
|
|
12830
|
+
return host !== void 0 && await refreshWorktree(rules, worktree, host, handedOver) ? "synced" : "same";
|
|
12831
|
+
};
|
|
12628
12832
|
/** Hands a passing child's work to its owner's worktree: merged or patched. */
|
|
12629
12833
|
const handOver = async (rules, child, owner, message) => {
|
|
12630
12834
|
if (rules.merge === "patch" || child.branch === void 0) {
|
|
@@ -12789,10 +12993,9 @@ const createWorkspace = (options) => {
|
|
|
12789
12993
|
const host = hostOf(id);
|
|
12790
12994
|
const hosted = host === void 0 ? void 0 : worktrees.get(host);
|
|
12791
12995
|
const existing = worktrees.get(id);
|
|
12792
|
-
|
|
12793
|
-
|
|
12794
|
-
|
|
12795
|
-
}
|
|
12996
|
+
const again = existing === void 0 ? "new" : await reenter(root, rules, existing, hosted, merged.has(id));
|
|
12997
|
+
if (again === "synced" && existing !== void 0 && hosted !== void 0) event(id, "synced", existing, `from ${nameOf(hosted)}`);
|
|
12998
|
+
if (existing !== void 0 && again !== "gone") return existing;
|
|
12796
12999
|
const { worktree, copied } = await openWorktree({
|
|
12797
13000
|
rules,
|
|
12798
13001
|
root,
|
|
@@ -12937,4 +13140,4 @@ const createWorkspace = (options) => {
|
|
|
12937
13140
|
close
|
|
12938
13141
|
};
|
|
12939
13142
|
};
|
|
12940
|
-
export { text as $,
|
|
13143
|
+
export { text as $, isToolNode as $n, maskReplacer as $t, gitOk as A, projectRootOf as An, runScope as At, linkNodes as B, readDocument as Bn, serversIn as Bt, workflowTextHash as C, placeFiles as Cn, walkChecks as Ct, inspectProject as D, shownPath as Dn, userMessageText as Dt, BRANCH_PREFIX as E, runDirOf as En, MESSAGE_LIMIT as Et, declaredProblem as F, writePlainFile as Fn, PROJECT_FILE as Ft, requestContinue as G, EdgeInputSchema as Gn, planPost as Gt, tellNode as H, parseWorkflow as Hn, FEEDBACK_SEVERITIES as Ht, createCalls as I, skillsIn as In, gitRulesOf as It, tellLiveRun as J, expandEdge as Jn, publishPost as Jt, requestPause as K, WorkflowSchema as Kn, runGh as Kt, addressOf as L, WorkflowEditSchema as Ln, loadProject as Lt, clearGitLocalEnv as M, estimateTokens as Mn, envReadsOf as Mt, parseInputPairs as N, guardedBase as Nn, nodeSays as Nt, removeWorktree as O, workflowDirOf as On, heldTimeout as Ot, resolveInputs as P, plainPathInside as Pn, declaredOutputOf as Pt, inTransaction as Q, isClaudeNode as Qn, targetOf as Qt, endLink as R, applyEdits as Rn, MCP_FILE as Rt, readProjectTriggers as S, newProjectId as Sn, checkWorkflowText as St, watchingPid as T, projectsHome as Tn, startRun as Tt, unlinkNodes as U, serializeWorkflow as Un, FEEDBACK_TARGETS as Ut, peerOf as V, renderJsonSchema as Vn, FEEDBACK_KINDS as Vt, flattenTree as W, markValue as Wn, DISCUSSIONS as Wt, usageQueries as X, NodeSchema as Xn, fingerprint as Xt, backfillUsage as Y, settingsOf as Yn, createFeedback as Yt, eventOf as Z, beforeOf as Zn, normalizeTitle as Zt, lateTriggers as _, catalogAgent as _n, runStatus as _t, limitsNow as a, serveRun as an, integrationFor as ar, DETACHED_ENV as at, triggerStanding as b, hasInRepoFiles as bn, processAlive as bt, usd as c, wakeRun as cn, CLAUDE_DRIVERS as cr, explainWorkflowText as ct, runningRunIds as d, planMoveInto as dn, experimentsOn as dr, namingNoDriver as dt, maskSecrets as en, programsRunBy as er, adoptedCalls as et, runningScript as f, linkTools as fn, knownExperiments as fr, sessionDriver as ft, parseVersion as g, deleteAgent as gn, pausedOf as gt, isNewer as h, createAgent as hn, liveStatus as ht, NOT_STARTED as i, readCondition as in, TOOL_PREFIX as ir, replayLoopAnswers as it, lines as j, LESSON_CAP_TOKENS as jn, calledWorkflows as jt, git as k, inside as kn, scriptContactsOf as kt, planUpgrade as l, planAgentMigration as ln, firstLine as lr, explainSession as lt, compareVersions as m, toolsInUse as mn, reportSchemaFor as mt, resolveBase as n, parseTemplate as nn, timeoutMs as nr, resumable as nt, limitsReport as o, socketPath as on, AGENT_TOOL_LAYOUTS as or, detachRun as ot, detectInstaller as p, toolLinks as pn, parseReport as pt, requestStop as q, CheckReportSchema as qn, sendPost as qt, capsHere as r, renderTemplate as rn, INTEGRATIONS as rr, createReplay as rt, planReason as s, socketRoom as sn, ORK_LAYOUT as sr, followEvents as st, createWorkspace as t, evaluate as tn, NodeIdSchema as tr, detachResume as tt, releaseNotes as u, applySteps as un, EXPERIMENTS as ur, planNodeSession as ut, triggersReport as v, loadAgents as vn, runTree as vt, takeWatchLock as w, placeFor as wn, walkSummary as wt, declaredTriggers as x, inRepoPlace as xn, stillTheSameProcess as xt, createWatcher as y, PROJECT_MODES as yn, ownProcessStart as yt, endLinksOf as z, newWorkflowText as zn, mcpNotText as zt };
|