@krapht/ork 1.0.0-alpha.3 → 1.0.0-alpha.5

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.
Files changed (36) hide show
  1. package/dist/{__build_info__-BwrKe6yz.mjs → __build_info__-Dl8yTEu-.mjs} +2 -2
  2. package/dist/bin.mjs +63 -225
  3. package/dist/{clean-hXSaN8BT.mjs → clean-DYVmLkd4.mjs} +1 -1
  4. package/dist/{completion-Cga01yQP.mjs → completion-BVx917Yr.mjs} +5 -5
  5. package/dist/{context-mCAwzFFM.mjs → context-CepHRoj9.mjs} +3 -3
  6. package/dist/{decide-BvZnKErW.mjs → decide-CK_-lI5V.mjs} +3 -3
  7. package/dist/{find-CbetS8I2.mjs → find-DWiDJ5Lc.mjs} +1 -1
  8. package/dist/{format-D87bY6Ud.mjs → format-yBEAXZhk.mjs} +223 -17
  9. package/dist/{in-a-run-Bs4Wv-Cz.mjs → in-a-run-CKFIeMpU.mjs} +1 -1
  10. package/dist/{limits-RzKQ_zPk.mjs → limits-CwzPe9wZ.mjs} +1 -1
  11. package/dist/{list-DX65Sbu7.mjs → list-BE02pCZ9.mjs} +1 -1
  12. package/dist/{mcp-DKrrxowo.mjs → mcp-DMuBbUHU.mjs} +6 -6
  13. package/dist/{output-Q3TZ-Ave.mjs → output-BhYkshYq.mjs} +9 -6
  14. package/dist/{pause-B5Z41lR3.mjs → pause-C_Q0W6iX.mjs} +4 -4
  15. package/dist/{relay-BLnjMdjT.mjs → relay-COCvG-0I.mjs} +2 -2
  16. package/dist/{remove-IbcZOemm.mjs → remove-RVcX1KqP.mjs} +3 -3
  17. package/dist/{send-CJKEhBL1.mjs → send-OVpRei_5.mjs} +3 -3
  18. package/dist/{src-LSxjnWDH.mjs → src-CxZr4Bp2.mjs} +16 -8
  19. package/dist/{src-DnETlJ1r.mjs → src-Do_MhwiV.mjs} +186 -30
  20. package/dist/{src-XDSRnAZV.mjs → src-HxtJOMRr.mjs} +172 -167
  21. package/dist/{src-BTDzibW3.mjs → src-QEycKJWk.mjs} +26 -12
  22. package/dist/ui/THIRD_PARTY_LICENSES.txt +594 -0
  23. package/dist/ui/assets/app-DzI77WV9.js +75 -0
  24. package/dist/ui/assets/app-pTYGrAbI.css +1 -0
  25. package/dist/ui/assets/{index-BkI68sr5.js → index-2G_6XIIv.js} +3 -3
  26. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  27. package/dist/ui/index.html +2 -2
  28. package/dist/{ui-fvrO7Gxd.mjs → ui-Dz_qfUOz.mjs} +4 -4
  29. package/dist/{usage-bNdcBcj9.mjs → usage-BcLUZ1xw.mjs} +5 -5
  30. package/dist/{watch-EhlO304A.mjs → watch-Buyn0Mhz.mjs} +6 -6
  31. package/dist/{workflows-C5hYAVcE.mjs → workflows-1sntXqSd.mjs} +3 -3
  32. package/dist/{worktrees-BQFcpYz1.mjs → worktrees-B_Go_A9w.mjs} +4 -4
  33. package/package.json +1 -1
  34. package/dist/ui/assets/app-BNElhp7Z.js +0 -67
  35. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  36. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
@@ -1,6 +1,6 @@
1
- import { $n as timeoutMs, $t as parseTemplate, At as runScope, F as declaredProblem, Jn as beforeOf, Mt as envReadsOf, Ot as heldTimeout, Pt as declaredOutputOf, Qt as evaluate, Vn as markValue, Xn as isToolNode, Zn as programsRunBy, en as renderTemplate, er as INTEGRATIONS, ft as sessionDriver, jt as calledWorkflows, mt as reportSchemaFor, nr as integrationFor, s as planReason, tr as TOOL_PREFIX, ut as planNodeSession } from "./src-DnETlJ1r.mjs";
1
+ import { $n as isToolNode, $t as evaluate, At as runScope, Bt as serversIn, F as declaredProblem, Mt as envReadsOf, Ot as heldTimeout, Pt as declaredOutputOf, Rt as MCP_FILE, Wn as markValue, Zn as beforeOf, ar as integrationFor, en as parseTemplate, er as programsRunBy, ft as sessionDriver, ir as TOOL_PREFIX, jt as calledWorkflows, mt as reportSchemaFor, nr as timeoutMs, rr as INTEGRATIONS, s as planReason, tn as renderTemplate, ut as planNodeSession, zt as mcpNotText } from "./src-Do_MhwiV.mjs";
2
2
  import path from "node:path";
3
- import { accessSync, constants, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { accessSync, constants, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { spawn } from "node:child_process";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -123,6 +123,156 @@ const resultOf = (outcome, declared) => {
123
123
  if (!result.ok) result.error = report.summary;
124
124
  return result;
125
125
  };
126
+ // ../nodes/src/environ.ts
127
+ /** A variable of the user's. */
128
+ const readEnv = (environ, name) => Object.hasOwn(environ, name) ? environ[name] : void 0;
129
+ // ../nodes/src/mcp-servers.ts
130
+ /**
131
+ * @file Reading an MCP server's definition from the project's `.mcp.json`, the
132
+ * file Claude Code reads too, so a server is defined in one place (RFC-006).
133
+ * `${VAR}` and `${VAR:-default}` read the user's environment, which is where
134
+ * secrets stay: Ork stores none, and a run masks each value read this way. A
135
+ * server that needs an OAuth sign-in holds Claude Code's tokens, not Ork's,
136
+ * so it is refused.
137
+ */
138
+ const VARIABLE = /\$\{([A-Za-z_]\w*)(?::-([^}]*))?\}/gu;
139
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
140
+ const stringsOf = (value) => isRecord(value) ? Object.fromEntries(Object.entries(value).flatMap(([key, item]) => typeof item === "string" ? [[key, item]] : [])) : {};
141
+ const listOf = (value) => Array.isArray(value) ? value.flatMap((item) => typeof item === "string" ? [item] : []) : [];
142
+ /** The server as written, or why Ork can't call it. */
143
+ const readMcpServer = (root, name) => {
144
+ const servers = serversIn(root);
145
+ if (!servers.ok) return servers;
146
+ const known = Object.keys(servers.value);
147
+ const server = Object.hasOwn(servers.value, name) ? servers.value[name] : void 0;
148
+ if (!isRecord(server)) return {
149
+ ok: false,
150
+ error: `no MCP server \`${name}\` in ${MCP_FILE}${known.length === 0 ? "" : `, which has ${known.join(", ")}`}`
151
+ };
152
+ if ("oauth" in server) return {
153
+ ok: false,
154
+ error: `MCP server \`${name}\` signs in with OAuth, whose tokens are Claude Code's: give it a token through headers and \`env:\`, or call it from a session`
155
+ };
156
+ const notText = mcpNotText(server);
157
+ if (notText !== void 0) return {
158
+ ok: false,
159
+ error: `MCP server \`${name}\` has a value in ${notText} that isn't a string: write it in quotes`
160
+ };
161
+ const { type, url, command } = server;
162
+ if (type === "http" && typeof url === "string") return {
163
+ ok: true,
164
+ value: {
165
+ kind: "http",
166
+ url,
167
+ headers: stringsOf(server["headers"])
168
+ }
169
+ };
170
+ if ((type === void 0 || type === "stdio") && typeof command === "string") return {
171
+ ok: true,
172
+ value: {
173
+ kind: "stdio",
174
+ command,
175
+ args: listOf(server["args"]),
176
+ env: stringsOf(server["env"])
177
+ }
178
+ };
179
+ return {
180
+ ok: false,
181
+ error: `MCP server \`${name}\` is not one Ork can call: it takes stdio and http servers`
182
+ };
183
+ };
184
+ /**
185
+ * The named servers' entries as written, for a session: `${VAR}` stays text for
186
+ * the `claude` that loads them to expand. The compiler has checked the names.
187
+ */
188
+ const namedMcpEntries = (root, names) => {
189
+ if (names.length === 0) return {
190
+ ok: true,
191
+ value: {}
192
+ };
193
+ const servers = serversIn(root);
194
+ if (!servers.ok) return servers;
195
+ const mcpServers = {};
196
+ for (const name of names) {
197
+ const server = Object.hasOwn(servers.value, name) ? servers.value[name] : void 0;
198
+ if (!isRecord(server)) return {
199
+ ok: false,
200
+ error: `no MCP server \`${name}\` in ${MCP_FILE}`
201
+ };
202
+ mcpServers[name] = server;
203
+ }
204
+ return {
205
+ ok: true,
206
+ value: { mcpServers }
207
+ };
208
+ };
209
+ /** Each variable a server reads, and whether it has a default. */
210
+ const readsOf = (server) => {
211
+ return (server.kind === "stdio" ? [
212
+ server.command,
213
+ ...server.args,
214
+ ...Object.values(server.env)
215
+ ] : [server.url, ...Object.values(server.headers)]).flatMap((text) => [...text.matchAll(VARIABLE)].map((match) => ({
216
+ name: String(match[1]),
217
+ fallback: match[2] !== void 0
218
+ })));
219
+ };
220
+ /**
221
+ * The server with its variables filled in, and the values to mask: all it read
222
+ * but those `plain` names, which aren't secret.
223
+ */
224
+ const expandServer = (server, environ, plain = []) => {
225
+ const unset = readsOf(server).find(({ name, fallback }) => !fallback && readEnv(environ, name) === void 0);
226
+ if (unset !== void 0) return {
227
+ ok: false,
228
+ error: `${MCP_FILE} reads ${unset.name}, which is not set`
229
+ };
230
+ const values = [];
231
+ const fill = (text) => text.replaceAll(VARIABLE, (_all, name, fallback) => {
232
+ const value = readEnv(environ, name);
233
+ if (value !== void 0 && !plain.includes(name)) values.push(value);
234
+ return value ?? fallback ?? "";
235
+ });
236
+ const fillAll = (record) => Object.fromEntries(Object.entries(record).map(([key, text]) => [key, fill(text)]));
237
+ return {
238
+ ok: true,
239
+ value: {
240
+ server: server.kind === "stdio" ? {
241
+ kind: "stdio",
242
+ command: fill(server.command),
243
+ args: server.args.map((arg) => fill(arg)),
244
+ env: fillAll(server.env)
245
+ } : {
246
+ kind: "http",
247
+ url: fill(server.url),
248
+ headers: fillAll(server.headers)
249
+ },
250
+ values
251
+ }
252
+ };
253
+ };
254
+ /** What a workflow's MCP servers read of the user's, unless called not secret. */
255
+ const mcpVariablesOf = (workflow, root) => Object.values(workflow.nodes).flatMap((node) => {
256
+ if (node.type !== "mcp.call") return [];
257
+ const read = readMcpServer(root, node.server);
258
+ return read.ok ? readsOf(read.value).map(({ name }) => name).filter((name) => node.plainEnv?.includes(name) !== true) : [];
259
+ });
260
+ /** A stdio server's program, when its name can be known before it is filled in. */
261
+ const programOf = (server) => server.kind === "stdio" && !server.command.includes("$") ? server.command : void 0;
262
+ /**
263
+ * What stops a workflow's `mcp.call` nodes, found before a run spends anything:
264
+ * a server that isn't there or can't be called, a program that isn't installed,
265
+ * or a variable that is not set.
266
+ */
267
+ const mcpProblems = (workflow, root, environ = process.env, finished = /* @__PURE__ */ new Set(), installed = () => true) => Object.entries(workflow.nodes).flatMap(([id, node]) => {
268
+ if (node.type !== "mcp.call" || finished.has(id)) return [];
269
+ const read = readMcpServer(root, node.server);
270
+ if (!read.ok) return [`${id}: ${read.error}`];
271
+ const given = new Set(Object.keys(node.env ?? {}));
272
+ const unset = readsOf(read.value).flatMap(({ name, fallback }) => fallback || given.has(name) || readEnv(environ, name) !== void 0 ? [] : [`${id}: ${MCP_FILE} reads ${name}, which is not set`]);
273
+ const program = programOf(read.value);
274
+ return program === void 0 || installed(program) ? unset : [`\`${id}\`: MCP server \`${node.server}\` runs \`${program}\`, which is not installed`].concat(unset);
275
+ });
126
276
  /** The only built-in tools a fork has: it reads, and reports. */
127
277
  const FORK_TOOLS = [
128
278
  "Read",
@@ -660,163 +810,6 @@ const tokenize = (command) => {
660
810
  args
661
811
  };
662
812
  };
663
- // ../nodes/src/environ.ts
664
- /** A variable of the user's. */
665
- const readEnv = (environ, name) => Object.hasOwn(environ, name) ? environ[name] : void 0;
666
- // ../nodes/src/mcp-servers.ts
667
- /**
668
- * @file Reading an MCP server's definition from the project's `.mcp.json`, the
669
- * file Claude Code reads too, so a server is defined in one place (RFC-006).
670
- * `${VAR}` and `${VAR:-default}` read the user's environment, which is where
671
- * secrets stay: Ork stores none, and a run masks each value read this way. A
672
- * server that needs an OAuth sign-in holds Claude Code's tokens, not Ork's,
673
- * so it is refused.
674
- */
675
- const MCP_FILE = ".mcp.json";
676
- const VARIABLE = /\$\{([A-Za-z_]\w*)(?::-([^}]*))?\}/gu;
677
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
678
- /** The file's `mcpServers`; `{}` when there is no file. */
679
- const serversIn = (root) => {
680
- const file = path.join(root, MCP_FILE);
681
- if (!existsSync(file)) return {
682
- ok: true,
683
- value: {}
684
- };
685
- try {
686
- const parsed = JSON.parse(readFileSync(file, "utf8"));
687
- const servers = isRecord(parsed) ? parsed["mcpServers"] : void 0;
688
- return {
689
- ok: true,
690
- value: isRecord(servers) ? servers : {}
691
- };
692
- } catch (error) {
693
- const reason = error instanceof SyntaxError ? "it is not valid JSON" : error instanceof Error ? error.message : String(error);
694
- return {
695
- ok: false,
696
- error: `${MCP_FILE} can't be read: ${reason}`
697
- };
698
- }
699
- };
700
- const stringsOf = (value) => isRecord(value) ? Object.fromEntries(Object.entries(value).flatMap(([key, item]) => typeof item === "string" ? [[key, item]] : [])) : {};
701
- const listOf = (value) => Array.isArray(value) ? value.flatMap((item) => typeof item === "string" ? [item] : []) : [];
702
- /** A server's field that holds something other than text: it would be dropped. */
703
- const notTextIn = (server) => [
704
- "args",
705
- "env",
706
- "headers"
707
- ].find((field) => {
708
- const value = server[field];
709
- return (Array.isArray(value) ? value : isRecord(value) ? Object.values(value) : []).some((item) => typeof item !== "string");
710
- });
711
- /** The server as written, or why Ork can't call it. */
712
- const readMcpServer = (root, name) => {
713
- const servers = serversIn(root);
714
- if (!servers.ok) return servers;
715
- const known = Object.keys(servers.value);
716
- const server = Object.hasOwn(servers.value, name) ? servers.value[name] : void 0;
717
- if (!isRecord(server)) return {
718
- ok: false,
719
- error: `no MCP server \`${name}\` in ${MCP_FILE}${known.length === 0 ? "" : `, which has ${known.join(", ")}`}`
720
- };
721
- if ("oauth" in server) return {
722
- ok: false,
723
- error: `MCP server \`${name}\` signs in with OAuth, whose tokens are Claude Code's: give it a token through headers and \`env:\`, or call it from a session`
724
- };
725
- const notText = notTextIn(server);
726
- if (notText !== void 0) return {
727
- ok: false,
728
- error: `MCP server \`${name}\` has a value in ${notText} that isn't a string: write it in quotes`
729
- };
730
- const { type, url, command } = server;
731
- if (type === "http" && typeof url === "string") return {
732
- ok: true,
733
- value: {
734
- kind: "http",
735
- url,
736
- headers: stringsOf(server["headers"])
737
- }
738
- };
739
- if ((type === void 0 || type === "stdio") && typeof command === "string") return {
740
- ok: true,
741
- value: {
742
- kind: "stdio",
743
- command,
744
- args: listOf(server["args"]),
745
- env: stringsOf(server["env"])
746
- }
747
- };
748
- return {
749
- ok: false,
750
- error: `MCP server \`${name}\` is not one Ork can call: it takes stdio and http servers`
751
- };
752
- };
753
- /** Each variable a server reads, and whether it has a default. */
754
- const readsOf = (server) => {
755
- return (server.kind === "stdio" ? [
756
- server.command,
757
- ...server.args,
758
- ...Object.values(server.env)
759
- ] : [server.url, ...Object.values(server.headers)]).flatMap((text) => [...text.matchAll(VARIABLE)].map((match) => ({
760
- name: String(match[1]),
761
- fallback: match[2] !== void 0
762
- })));
763
- };
764
- /**
765
- * The server with its variables filled in, and the values to mask: all it read
766
- * but those `plain` names, which aren't secret.
767
- */
768
- const expandServer = (server, environ, plain = []) => {
769
- const unset = readsOf(server).find(({ name, fallback }) => !fallback && readEnv(environ, name) === void 0);
770
- if (unset !== void 0) return {
771
- ok: false,
772
- error: `${MCP_FILE} reads ${unset.name}, which is not set`
773
- };
774
- const values = [];
775
- const fill = (text) => text.replaceAll(VARIABLE, (_all, name, fallback) => {
776
- const value = readEnv(environ, name);
777
- if (value !== void 0 && !plain.includes(name)) values.push(value);
778
- return value ?? fallback ?? "";
779
- });
780
- const fillAll = (record) => Object.fromEntries(Object.entries(record).map(([key, text]) => [key, fill(text)]));
781
- return {
782
- ok: true,
783
- value: {
784
- server: server.kind === "stdio" ? {
785
- kind: "stdio",
786
- command: fill(server.command),
787
- args: server.args.map((arg) => fill(arg)),
788
- env: fillAll(server.env)
789
- } : {
790
- kind: "http",
791
- url: fill(server.url),
792
- headers: fillAll(server.headers)
793
- },
794
- values
795
- }
796
- };
797
- };
798
- /** What a workflow's MCP servers read of the user's, unless called not secret. */
799
- const mcpVariablesOf = (workflow, root) => Object.values(workflow.nodes).flatMap((node) => {
800
- if (node.type !== "mcp.call") return [];
801
- const read = readMcpServer(root, node.server);
802
- return read.ok ? readsOf(read.value).map(({ name }) => name).filter((name) => node.plainEnv?.includes(name) !== true) : [];
803
- });
804
- /** A stdio server's program, when its name can be known before it is filled in. */
805
- const programOf = (server) => server.kind === "stdio" && !server.command.includes("$") ? server.command : void 0;
806
- /**
807
- * What stops a workflow's `mcp.call` nodes, found before a run spends anything:
808
- * a server that isn't there or can't be called, a program that isn't installed,
809
- * or a variable that is not set.
810
- */
811
- const mcpProblems = (workflow, root, environ = process.env, finished = /* @__PURE__ */ new Set(), installed = () => true) => Object.entries(workflow.nodes).flatMap(([id, node]) => {
812
- if (node.type !== "mcp.call" || finished.has(id)) return [];
813
- const read = readMcpServer(root, node.server);
814
- if (!read.ok) return [`${id}: ${read.error}`];
815
- const given = new Set(Object.keys(node.env ?? {}));
816
- const unset = readsOf(read.value).flatMap(({ name, fallback }) => fallback || given.has(name) || readEnv(environ, name) !== void 0 ? [] : [`${id}: ${MCP_FILE} reads ${name}, which is not set`]);
817
- const program = programOf(read.value);
818
- return program === void 0 || installed(program) ? unset : [`\`${id}\`: MCP server \`${node.server}\` runs \`${program}\`, which is not installed`].concat(unset);
819
- });
820
813
  // ../nodes/src/tool-scrub.ts
821
814
  /**
822
815
  * @file Masking a secret in what a program prints. A plain search, so a value
@@ -1589,7 +1582,12 @@ const claudeExecutor = (options) => async (context) => {
1589
1582
  },
1590
1583
  error: plan.error
1591
1584
  };
1592
- const spec = specFor(plan.spec, context);
1585
+ const servers = namedMcpEntries(context.projectRoot, plan.mcp);
1586
+ if (!servers.ok) return notStarted(servers.error);
1587
+ const spec = {
1588
+ ...specFor(plan.spec, context),
1589
+ ...servers.value
1590
+ };
1593
1591
  if (options.machineClaude === true) spec.machineClaude = true;
1594
1592
  const driver = sessionDriver({
1595
1593
  override: options.driver,
@@ -1691,10 +1689,11 @@ const claudeExecutor = (options) => async (context) => {
1691
1689
  };
1692
1690
  // ../nodes/src/gate.ts
1693
1691
  /**
1694
- * @file The `human.gate` node type: asks the human the rendered question,
1695
- * outputs their choice with the note they gave, and answers with the
1696
- * `default` when the `timeout` passes. The timeout counts only while the run
1697
- * and the gate are not paused (ADR-041).
1692
+ * @file The `human.gate` node type: asks the human the rendered question, with
1693
+ * its document if it has one, both in readable text (no raw JSON), outputs
1694
+ * their choice with the note they gave, and answers with the `default` when
1695
+ * the `timeout` passes. The timeout counts only while the run and the gate
1696
+ * are not paused (ADR-041).
1698
1697
  */
1699
1698
  /** A promise that never settles: the losing side of a race. */
1700
1699
  const NEVER = new Promise(() => {});
@@ -1716,8 +1715,13 @@ const gateExecutor = async (context) => {
1716
1715
  output: {},
1717
1716
  error: `${context.id} is not a gate`
1718
1717
  };
1719
- const template = parseTemplate(node.question);
1720
- const question = template.ok ? renderTemplate(template.parts, runScope(context.inputs, context.outputs, context.runId)) : node.question;
1718
+ const scope = runScope(context.inputs, context.outputs, context.runId);
1719
+ const render = (text) => {
1720
+ const template = parseTemplate(text);
1721
+ return template.ok ? renderTemplate(template.parts, scope, { readable: true }) : text;
1722
+ };
1723
+ const question = render(node.question);
1724
+ const document = render(node.document ?? "");
1721
1725
  const options = node.options.map((o) => choiceOf(o));
1722
1726
  const withdraw = new AbortController();
1723
1727
  context.signal.addEventListener("abort", () => {
@@ -1734,6 +1738,7 @@ const gateExecutor = async (context) => {
1734
1738
  }
1735
1739
  };
1736
1740
  if (node.recommended !== void 0) asked.recommended = node.recommended;
1741
+ if (document.trim() !== "") asked.document = document;
1737
1742
  const answer = context.human.ask(asked).then((choice) => ({
1738
1743
  choice,
1739
1744
  answeredBy: "user",
@@ -2130,4 +2135,4 @@ const missingPrograms = (workflow, installed = (program) => onPath(program)) =>
2130
2135
  }
2131
2136
  return [...missing.values()];
2132
2137
  };
2133
- export { resultOf as _, builtinExecutors as a, toolExecutor as c, maskedEnv as d, maskedNamesOfRun as f, clearOutputFiles as g, sessionEnv as h, onPath as i, clearStateFiles as l, runScrubber as m, missingProgramMessage as n, spawnTool as o, runMarkerIn as p, missingPrograms as r, mcpCallExecutor as s, envProblems as t, inheritedScrubber as u };
2138
+ export { namedMcpEntries as _, builtinExecutors as a, toolExecutor as c, maskedEnv as d, maskedNamesOfRun as f, clearOutputFiles as g, sessionEnv as h, onPath as i, clearStateFiles as l, runScrubber as m, missingProgramMessage as n, spawnTool as o, runMarkerIn as p, missingPrograms as r, mcpCallExecutor as s, envProblems as t, inheritedScrubber as u, resultOf as v };
@@ -1,11 +1,11 @@
1
- import { B as linkNodes, Cn as runDirOf, Ct as walkChecks, En as inside, Fn as applyEdits, G as requestContinue, Gn as expandEdge, Hn as EdgeInputSchema, Ht as planPost, In as newWorkflowText, J as tellLiveRun, K as requestPause, Ln as readDocument, Nn as skillsIn, Nt as nodeSays, On as LESSON_CAP_TOKENS, Pn as WorkflowEditSchema, Rn as renderJsonSchema, St as checkWorkflowText, Tn as workflowDirOf, U as unlinkNodes, Ut as runGh, Vt as DISCUSSIONS, Wt as sendPost, _t as runStatus, bn as placeFiles, bt as processAlive, ct as explainWorkflowText, d as runningRunIds, f as runningScript, gt as pausedOf, hn as loadAgents, ht as liveStatus, l as planUpgrade, nt as resumable, o as limitsReport, ot as detachRun, p as detectInstaller, q as requestStop, qn as NodeSchema, r as capsHere, st as followEvents, tn as readCondition, tt as detachResume, u as releaseNotes, v as triggersReport, vt as runTree, wt as walkSummary, xn as placeFor, zn as parseWorkflow } from "./src-DnETlJ1r.mjs";
2
- import { C as feedbackActions, S as placeOf$1, _ as previewPost, a as registryShown, b as acceptItem, c as checkDue, f as readUpdateCache, g as nodeSession, h as writeUpdateCache, i as registryOf, l as fetchLatest, m as updateCachePath, o as updateSwitchOff, p as refreshUpdateCache, s as runInstall, u as newerVersion, w as openFixRun, x as lessonPlaceOf, y as recordNote } from "./__build_info__-BwrKe6yz.mjs";
3
- import { n as pendingDecisions, r as builtinProblem, t as answerDecision } from "./decide-BvZnKErW.mjs";
4
- import { n as newRunId, t as findRun } from "./find-CbetS8I2.mjs";
5
- import { c as openStore, l as storePath, s as nodeOf } from "./output-Q3TZ-Ave.mjs";
1
+ import { B as linkNodes, Bn as readDocument, Cn as placeFiles, Ct as walkChecks, En as runDirOf, G as requestContinue, Gn as EdgeInputSchema, Gt as planPost, Hn as parseWorkflow, In as skillsIn, J as tellLiveRun, Jn as expandEdge, K as requestPause, Kt as runGh, Ln as WorkflowEditSchema, Nt as nodeSays, On as workflowDirOf, Rn as applyEdits, St as checkWorkflowText, U as unlinkNodes, Vn as renderJsonSchema, Wt as DISCUSSIONS, Xn as NodeSchema, _t as runStatus, bt as processAlive, ct as explainWorkflowText, d as runningRunIds, f as runningScript, gt as pausedOf, ht as liveStatus, jn as LESSON_CAP_TOKENS, kn as inside, l as planUpgrade, nn as readCondition, nt as resumable, o as limitsReport, ot as detachRun, p as detectInstaller, q as requestStop, qt as sendPost, r as capsHere, st as followEvents, tt as detachResume, u as releaseNotes, v as triggersReport, vn as loadAgents, vt as runTree, wn as placeFor, wt as walkSummary, zn as newWorkflowText } from "./src-Do_MhwiV.mjs";
2
+ import { C as feedbackActions, S as placeOf$1, _ as previewPost, a as registryShown, b as acceptItem, c as checkDue, f as readUpdateCache, g as nodeSession, h as writeUpdateCache, i as registryOf, l as fetchLatest, m as updateCachePath, o as updateSwitchOff, p as refreshUpdateCache, s as runInstall, u as newerVersion, w as openFixRun, x as lessonPlaceOf, y as recordNote } from "./__build_info__-Dl8yTEu-.mjs";
3
+ import { n as pendingDecisions, r as builtinProblem, t as answerDecision } from "./decide-CK_-lI5V.mjs";
4
+ import { n as newRunId, t as findRun } from "./find-DWiDJ5Lc.mjs";
5
+ import { c as openStore, l as storePath, s as nodeOf } from "./output-BhYkshYq.mjs";
6
6
  import { n as usageReport } from "./report-DK4LGqwF.mjs";
7
- import { a as planClean, i as keyOf, o as stepActs, r as cleanStep, s as stepPrunes, t as applyClean } from "./clean-hXSaN8BT.mjs";
8
- import { a as readWorktrees, c as planLine, i as projectHead, l as removeRefusal, n as lostWork, o as labelOf, r as planRemove, s as mergeStateOf, t as applyRemove, u as stateParts } from "./remove-IbcZOemm.mjs";
7
+ import { a as planClean, i as keyOf, o as stepActs, r as cleanStep, s as stepPrunes, t as applyClean } from "./clean-DYVmLkd4.mjs";
8
+ import { a as readWorktrees, c as planLine, i as projectHead, l as removeRefusal, n as lostWork, o as labelOf, r as planRemove, s as mergeStateOf, t as applyRemove, u as stateParts } from "./remove-RVcX1KqP.mjs";
9
9
  import path from "node:path";
10
10
  import { z } from "zod";
11
11
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -349,7 +349,9 @@ const EventsQuerySchema = z.strictObject({
349
349
  });
350
350
  const DecisionsQuerySchema = z.strictObject({
351
351
  run: z.string().min(1).optional(),
352
- project: z.string().min(1).optional()
352
+ project: z.string().min(1).optional(),
353
+ /** Only runs of this workflow, by name; ANDed with `run` or `project`. */
354
+ workflow: z.string().min(1).optional()
353
355
  });
354
356
  /** An answer: the option, and what the person said with it, if anything. */
355
357
  const AnswerSchema = z.strictObject({
@@ -483,7 +485,8 @@ const STREAMS = {
483
485
  run: "/api/runs/:id/stream",
484
486
  /**
485
487
  * `runs`, `decisions`, `version`, each when it changes; `?project=` keeps
486
- * one. `upgrade`: the upgrade's output, then `end` (contract-update.ts).
488
+ * one, `?workflow=` narrows `decisions` to that workflow's. `upgrade`: the
489
+ * upgrade's output, then `end` (contract-update.ts).
487
490
  */
488
491
  overview: "/api/stream",
489
492
  upgrade: "/api/upgrade/stream"
@@ -1625,10 +1628,17 @@ const readRoutes = (context) => ({
1625
1628
  return ok(query.limit === void 0 ? newest : newest.slice(0, query.limit));
1626
1629
  },
1627
1630
  decisions: ({ query }) => {
1628
- if (query.run === void 0) return ok(pendingDecisions(context.store, query.project === void 0 ? {} : { project: projectRoot(context, query.project) }));
1631
+ const workflow = query.workflow === void 0 ? {} : { workflow: query.workflow };
1632
+ if (query.run === void 0) return ok(pendingDecisions(context.store, {
1633
+ ...workflow,
1634
+ ...query.project === void 0 ? {} : { project: projectRoot(context, query.project) }
1635
+ }));
1629
1636
  const found = runOf(context, query.run);
1630
1637
  if (!found.ok) return found.refusal;
1631
- return ok(pendingDecisions(context.store, { runId: found.run.id }));
1638
+ return ok(pendingDecisions(context.store, {
1639
+ ...workflow,
1640
+ runId: found.run.id
1641
+ }));
1632
1642
  },
1633
1643
  decision: ({ params }) => {
1634
1644
  const found = context.store.decision(params.id);
@@ -2869,6 +2879,7 @@ const runStream = async (context, req, res, url, id, shutdown) => {
2869
2879
  const overviewStream = (context, res, url, shutdown) => {
2870
2880
  const key = url.searchParams.get("project");
2871
2881
  const root = key === null ? void 0 : projectOf(context, key)?.root ?? key;
2882
+ const workflow = url.searchParams.get("workflow");
2872
2883
  const stream = openEventStream(res);
2873
2884
  const last = {
2874
2885
  runs: "",
@@ -2881,7 +2892,10 @@ const overviewStream = (context, res, url, shutdown) => {
2881
2892
  ...filter,
2882
2893
  limit: OVERVIEW_RUNS
2883
2894
  }).map((run) => viewOf$2(context, run));
2884
- const decisions = pendingDecisions(context.store, filter);
2895
+ const decisions = pendingDecisions(context.store, {
2896
+ ...filter,
2897
+ ...workflow === null ? {} : { workflow }
2898
+ });
2885
2899
  for (const [name, value] of [
2886
2900
  ["runs", runs],
2887
2901
  ["decisions", decisions],