@krapht/ork 1.0.0-alpha.18 → 1.0.0-alpha.19

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 (48) hide show
  1. package/dist/{__build_info__-B8TNbFtN.mjs → __build_info__-dPf4cEGL.mjs} +3 -134
  2. package/dist/{attach-CU4Pk42S.mjs → attach-BQl8nQz2.mjs} +5 -5
  3. package/dist/{attach-qvfyaQXC.mjs → attach-DhZQ87Nz.mjs} +49 -224
  4. package/dist/bin.mjs +204 -154
  5. package/dist/{brief-Bh1FnAHv.mjs → brief-BQbeoPMc.mjs} +19 -9
  6. package/dist/{builtin-files-dnTP2GaX.mjs → builtin-files-CWL3JEo-.mjs} +1 -1
  7. package/dist/{command-tree-BkSYWmYJ.mjs → command-tree-B6uvMJq1.mjs} +1 -1
  8. package/dist/{completion-CzXUGSVn.mjs → completion-DKPuD_Rw.mjs} +47 -14
  9. package/dist/{context-b4_gPlv3.mjs → context-Bw6R_EYq.mjs} +3 -3
  10. package/dist/detached-BQlbmqR0.mjs +38 -0
  11. package/dist/{find-P-VV4B47.mjs → find-B30-rd-u.mjs} +1 -1
  12. package/dist/flags-ByYBx-Xb.mjs +170 -0
  13. package/dist/{follow-ups-B_c1DSpA.mjs → follow-ups-Cxca1Jgg.mjs} +24 -18
  14. package/dist/{format-BS1qzpkf.mjs → format-C-nEZv-P.mjs} +27 -11
  15. package/dist/{help-groups-hB6kY34d.mjs → help-groups-BpRgBSTh.mjs} +13 -7
  16. package/dist/{in-a-run-DKoMGTNE.mjs → in-a-run-CZFzoT3R.mjs} +2 -2
  17. package/dist/{install-B743Bwzz.mjs → install-CsqK1avz.mjs} +2 -183
  18. package/dist/{limits-DrsKCqaN.mjs → limits-CXh052II.mjs} +1 -1
  19. package/dist/{mcp-BMwekw38.mjs → mcp-B4wME9C4.mjs} +6 -6
  20. package/dist/{once-6L8gciJV.mjs → once-BKn3pOtg.mjs} +3 -3
  21. package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
  22. package/dist/{pause-8gnVgICR.mjs → pause-B1grR1cl.mjs} +3 -3
  23. package/dist/{print-DLe4MHn-.mjs → print-Bd8MWVJk.mjs} +1 -1
  24. package/dist/{project-records-CsILxwfI.mjs → project-records-BOEVs69i.mjs} +1 -1
  25. package/dist/{parse-BzWpMN-a.mjs → refusals-DQQX3AIb.mjs} +10 -1
  26. package/dist/{relay-Bvm9JEoN.mjs → relay-CeNiq3GT.mjs} +1 -1
  27. package/dist/{run-D4yuTmmJ.mjs → run-C0C54UiU.mjs} +116 -137
  28. package/dist/{run-y8jI7bAI.mjs → run-D6yU2rLf.mjs} +1 -1
  29. package/dist/{send-BV9eIxQ7.mjs → send-DqgF58hb.mjs} +2 -2
  30. package/dist/{src-Frwpx3WF.mjs → src-Ciy7i3se.mjs} +843 -66
  31. package/dist/{src-ygFMh2eP.mjs → src-D2qpd-G7.mjs} +4 -4
  32. package/dist/{src-CVeA4tAw.mjs → src-DP02RP9O.mjs} +227 -180
  33. package/dist/{src-DWAcd3Sy.mjs → src-n-u4TGev.mjs} +186 -43
  34. package/dist/{store-B0nGqnPq.mjs → store-2KXzMCEN.mjs} +1 -1
  35. package/dist/ui/assets/app-C5vO1M-l.css +1 -0
  36. package/dist/ui/assets/{app-C4yxmYQR.js → app-ICEKpy_C.js} +21 -20
  37. package/dist/ui/assets/{index-CcbJWo5C.js → index-BGaix5wh.js} +2 -2
  38. package/dist/ui/index.html +1 -1
  39. package/dist/{ui-BZ9BhFw8.mjs → ui-CAvygxTb.mjs} +53 -24
  40. package/dist/{usage-BIs4j5Rw.mjs → usage-BFBco5OS.mjs} +7 -7
  41. package/dist/{validate-Xn2ZbJQC.mjs → validate-UrmC5AG1.mjs} +5 -5
  42. package/dist/{watch-9VM3DFTp.mjs → watch-DTxkgB8R.mjs} +5 -5
  43. package/dist/{workflows-CBYtycIy.mjs → workflows-8GV-SeRm.mjs} +28 -4
  44. package/dist/{worktrees-C7vfh7y5.mjs → worktrees-CUdUvZlO.mjs} +9 -6
  45. package/package.json +1 -1
  46. package/dist/flags-CqjJ44uW.mjs +0 -125
  47. package/dist/post-y1qL1Fnj.mjs +0 -46
  48. package/dist/ui/assets/app-9Q-62Ywz.css +0 -1
@@ -1,11 +1,11 @@
1
- import { An as liveStatus, Ar as loadAgents, Br as inside, Bt as decisionListTable, D as triggersReport, Dn as requestPause, En as requestContinue, F as activeTree, Fr as placeFor, Gt as worktreeListTable, Hr as LESSON_CAP_TOKENS, Ht as followUpListTable, I as isNodeEvent, In as processAlive, Jr as WorkflowEditSchema, Jt as keyOf, Kn as endOf, Kt as applyClean, L as openStore, Lr as runDirOf, Mn as pausedOf, Nn as runStatus, On as requestStop, Pr as placeFiles, R as storePath, S as detectInstaller, St as walkSummary, Tn as unlinkNodes, Un as loadProject, Ut as runListTable, Vt as feedbackListTable, Wt as usageListTable, Xr as newWorkflowText, Xt as stepActs, Yn as END_KINDS, Yr as applyEdits, Yt as planClean, Zr as readDocument, Zt as stepPrunes, _ as capsHere, a as planRemove, ai as EdgeInputSchema, b as runningRunIds, bn as liveSessionOf, bt as checkWorkflowText, ci as expandEdge, d as planLine, er as DISCUSSIONS, f as removeRefusal, g as usageReport, gn as startFollowUpChecks, ht as explainWorkflowText, i as lostWork, kn as tellLiveRun, l as labelOf, lt as detachRun, ni as parseWorkflow, nr as runGh, nt as answerDecision, o as projectHead, on as nodeSays, ot as detachResume, p as stateParts, pn as checkFollowUpsDue, pr as readCondition, pt as limitsReport, qr as skillsIn, qt as cleanStep, r as applyRemove, rr as sendPost, rt as pendingDecisions, s as readWorktrees, st as resumable, ti as renderJsonSchema, tr as planPost, u as mergeStateOf, ui as NodeSchema, ut as followEvents, v as planUpgrade, wn as linkNodes, x as runningScript, xn as DEFAULT_FOLLOW_UPS_RULE, xt as walkChecks, y as releaseNotes, zn as nestItems, zr as workflowDirOf, zt as LIST_TABLES } from "./src-Frwpx3WF.mjs";
2
- import { a as acceptItem, c as feedbackActions, i as recordNote, l as openFixRun, n as previewPost, o as lessonPlaceOf, s as placeOf$1, t as runInstall } from "./install-B743Bwzz.mjs";
3
- import { _ as dueCause, a as registryShown, c as fetchLatest, d as readUpdateCache, f as refreshUpdateCache, g as changeFollowUp, h as nodeSession, i as registryOf, l as newerVersion, m as writeUpdateCache, o as updateSwitchOff, p as updateCachePath, s as checkDue, v as findFollowUp, y as followUpActions } from "./__build_info__-B8TNbFtN.mjs";
4
- import { t as postFollowUpMessage } from "./post-y1qL1Fnj.mjs";
1
+ import { $t as stepActs, An as requestPause, C as detectInstaller, Cn as liveSessionOf, Ct as checkWorkflowText, Dn as linkNodes, Fn as pausedOf, Gt as followUpListTable, Hn as nestItems, Ht as LIST_TABLES, I as activeTree, In as runStatus, Jn as changeFollowUp, Jr as workflowDirOf, Jt as worktreeListTable, Kn as loadProject, Kr as runDirOf, Kt as runListTable, L as isNodeEvent, Mn as tellLiveRun, Nn as liveStatus, O as triggersReport, On as unlinkNodes, Qt as planClean, R as openStore, S as runningScript, Sr as readCondition, Tt as walkSummary, Ur as placeFiles, Ut as decisionListTable, Wr as placeFor, Wt as feedbackListTable, Xn as findFollowUp, Xt as cleanStep, Yn as dueCause, Yr as inside, Yt as applyClean, Zn as followUpActions, Zr as LESSON_CAP_TOKENS, Zt as keyOf, _ as usageReport, _i as expandEdge, _n as checkFollowUpsDue, _r as feedbackActions, a as planRemove, ai as newWorkflowText, ar as previewPost, at as answerDecision, b as releaseNotes, bn as startFollowUpChecks, d as mergeStateOf, di as parseWorkflow, dr as DISCUSSIONS, en as stepPrunes, er as endOf, f as planLine, fr as planPost, ft as detachRun, gn as postFollowUpMessage, gt as limitsReport, i as lostWork, ii as applyEdits, jn as requestStop, kn as requestContinue, ln as nodeSays, lt as detachResume, m as stateParts, mi as EdgeInputSchema, mr as sendPost, ni as skillsIn, o as projectHead, oi as readDocument, ot as pendingDecisions, p as removeRefusal, pr as runGh, pt as followEvents, qt as usageListTable, r as applyRemove, ri as WorkflowEditSchema, rr as END_KINDS, s as readWorktrees, sr as recordNote, u as labelOf, ui as renderJsonSchema, ut as resumable, v as capsHere, vr as openFixRun, vt as explainWorkflowText, wn as DEFAULT_FOLLOW_UPS_RULE, wt as walkChecks, x as runningRunIds, y as planUpgrade, yi as NodeSchema, z as storePath, zn as processAlive, zr as loadAgents } from "./src-Ciy7i3se.mjs";
2
+ import { i as placeOf$1, n as acceptItem, r as lessonPlaceOf, t as runInstall } from "./install-CsqK1avz.mjs";
5
3
  import { t as applyFilters } from "./apply-Mb0fNpCX.mjs";
6
- import { t as parseFilters } from "./parse-BzWpMN-a.mjs";
7
- import { t as builtinProblem } from "./builtin-files-dnTP2GaX.mjs";
8
- import { n as newRunId, t as findRun } from "./find-P-VV4B47.mjs";
4
+ import { i as parseLimit, n as unknownProjectRefusal, r as parseFilters, t as limitRefusal } from "./refusals-DQQX3AIb.mjs";
5
+ import { t as builtinProblem } from "./builtin-files-CWL3JEo-.mjs";
6
+ import { n as newRunId, t as findRun } from "./find-B30-rd-u.mjs";
7
+ import { a as registryShown, c as fetchLatest, d as readUpdateCache, f as refreshUpdateCache, h as nodeSession, i as registryOf, l as newerVersion, m as writeUpdateCache, o as updateSwitchOff, p as updateCachePath, s as checkDue } from "./__build_info__-dPf4cEGL.mjs";
8
+ import { t as outputsText } from "./outputs-text-CNTJ7Qq_.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";
@@ -436,6 +436,11 @@ const API = {
436
436
  method: "GET",
437
437
  path: "/api/health"
438
438
  },
439
+ /** Bearer token only (RFC-019): mints a single-use code for `/?code=`. */
440
+ signIn: {
441
+ method: "POST",
442
+ path: "/api/sign-in"
443
+ },
439
444
  ...BUILDER_API,
440
445
  ...CONTEXT_API,
441
446
  ...AFTER_API,
@@ -542,6 +547,64 @@ const STREAMS = {
542
547
  overview: "/api/stream",
543
548
  upgrade: "/api/upgrade/stream"
544
549
  };
550
+ /** A route's path with its `:params` filled in, each encoded. */
551
+ const fillPath = (path, params) => path.replaceAll(/:(\w+)/gu, (_, name) => encodeURIComponent(params[name] ?? ""));
552
+ // ../server/src/client.ts
553
+ /**
554
+ * @file The typed client of the API (ADR-039), built from the contract: a call
555
+ * names a route and passes its params, query and body, and gets the route's
556
+ * response type back, or the server's error. The UI uses it in the browser
557
+ * (the session cookie goes with each request on its own); scripts pass the
558
+ * token. It uses only `fetch`, so it runs in a browser and in Node.
559
+ */
560
+ /** An object's string and number fields, as text. */
561
+ const entries = (value) => typeof value === "object" && value !== null ? Object.entries(value).flatMap(([key, field]) => typeof field === "string" || typeof field === "number" ? [[key, String(field)]] : []) : [];
562
+ const queryString = (query) => {
563
+ const search = new URLSearchParams();
564
+ for (const [key, value] of entries(query)) search.set(key, value);
565
+ const text = search.toString();
566
+ return text === "" ? "" : `?${text}`;
567
+ };
568
+ const errorOf = (body, status) => typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : `The server answered ${String(status)}.`;
569
+ const createClient = (options = {}) => {
570
+ const send = (url, init) => (options.fetch ?? globalThis.fetch)(url, init);
571
+ return { call: async (name, ...[input]) => {
572
+ const route = API[name];
573
+ const parts = input ?? {};
574
+ const params = Object.fromEntries(entries(parts.params));
575
+ const url = `${options.base ?? ""}${fillPath(route.path, params)}${queryString(parts.query)}`;
576
+ const headers = { accept: "application/json" };
577
+ if (options.token !== void 0) headers["authorization"] = `Bearer ${options.token}`;
578
+ const init = {
579
+ method: route.method,
580
+ headers
581
+ };
582
+ if (route.method === "POST") {
583
+ headers["content-type"] = "application/json";
584
+ init.body = JSON.stringify(parts.body ?? {});
585
+ }
586
+ let response;
587
+ try {
588
+ response = await send(url, init);
589
+ } catch (error) {
590
+ return {
591
+ ok: false,
592
+ status: 0,
593
+ error: `No answer: ${error instanceof Error ? error.message : String(error)}`
594
+ };
595
+ }
596
+ const body = await response.json().catch(() => null);
597
+ return response.ok ? {
598
+ ok: true,
599
+ status: response.status,
600
+ data: body
601
+ } : {
602
+ ok: false,
603
+ status: response.status,
604
+ error: errorOf(body, response.status)
605
+ };
606
+ } };
607
+ };
545
608
  // ../server/src/graph.ts
546
609
  /**
547
610
  * @file A workflow or a run as the graph the UI draws: its nodes, its edges
@@ -597,13 +660,16 @@ const runGraph = (events, file) => {
597
660
  };
598
661
  // ../server/src/guards.ts
599
662
  /**
600
- * @file What the server lets in (ADR-039,
663
+ * @file What the server lets in (ADR-039, RFC-019,
601
664
  * specs/server/streams-contract-and-security.md#security): only its own
602
- * loopback `Host` (against DNS rebinding); only with its token, from the
603
- * session cookie or an `Authorization: Bearer` header; and, for a write sent
604
- * with the cookie, only from its own `Origin` (against CSRF). The token in
605
- * `?token=` is swapped once for the cookie, with a redirect that takes it out
606
- * of the address bar. Pure: it reads the request's parts and decides.
665
+ * loopback `Host` (against DNS rebinding); only with the token in an
666
+ * `Authorization: Bearer` header (scripts, `ork mcp`) or the session secret
667
+ * in the cookie (the browser); and, for a write sent with the cookie, only
668
+ * from its own `Origin` (against CSRF). A single-use code in `?code=` is
669
+ * swapped once for the cookie, with a redirect that takes it out of the
670
+ * address bar; the token itself never reaches a browser. It reads the
671
+ * request's parts and decides; its only state is the code store it spends
672
+ * from.
607
673
  */
608
674
  const WRITES = /* @__PURE__ */ new Set([
609
675
  "POST",
@@ -611,11 +677,13 @@ const WRITES = /* @__PURE__ */ new Set([
611
677
  "PATCH",
612
678
  "DELETE"
613
679
  ]);
680
+ /** What a used, expired or wrong code all get, so none tells them apart. */
681
+ const BAD_CODE = "That sign-in link is not valid any more; open it again with `ork ui`.";
614
682
  /** A new token: 32 random bytes. */
615
683
  const newToken = () => randomBytes(32).toString("base64url");
616
684
  /** The session cookie's name; one per port, as cookies ignore ports. */
617
685
  const cookieName = (port) => `ork_ui_${String(port)}`;
618
- /** Whether `given` is the token, compared in constant time. */
686
+ /** Whether `given` is the token (or session secret), compared in constant time. */
619
687
  const sameToken = (given, token) => {
620
688
  const a = Buffer.from(given);
621
689
  const b = Buffer.from(token);
@@ -631,22 +699,22 @@ const cookieValue = (request, name) => (header(request, "cookie") ?? "").split("
631
699
  const bearer = (request) => {
632
700
  return /^Bearer\s+(\S+)$/iu.exec(header(request, "authorization") ?? "")?.[1];
633
701
  };
634
- /** The session cookie that holds `token`. */
635
- const sessionCookie = (port, token) => `${cookieName(port)}=${token}; HttpOnly; SameSite=Strict; Path=/`;
702
+ /** The session cookie that holds the session secret. */
703
+ const sessionCookie = (port, session) => `${cookieName(port)}=${session}; HttpOnly; SameSite=Strict; Path=/`;
636
704
  const exchange = (request, context) => {
637
- const url = new URL(request.url, "http://127.0.0.1");
638
- const given = url.searchParams.get("token");
705
+ const url = new URL(request.url.replace(/^[/\\]+/u, "/"), "http://127.0.0.1");
706
+ const given = url.searchParams.get("code");
639
707
  if (given === null || request.method !== "GET") return null;
640
- if (!sameToken(given, context.token)) return {
708
+ if (!context.codes.redeem(given)) return {
641
709
  kind: "refuse",
642
710
  status: 401,
643
- error: "That link's token is not this server's; open it again with `ork ui`."
711
+ error: BAD_CODE
644
712
  };
645
- url.searchParams.delete("token");
713
+ url.searchParams.delete("code");
646
714
  return {
647
715
  kind: "exchange",
648
- cookie: sessionCookie(context.port, context.token),
649
- location: `${url.pathname}${url.search}`
716
+ cookie: sessionCookie(context.port, context.session),
717
+ location: `${url.pathname.replace(/^\/+/u, "/")}${url.search}`
650
718
  };
651
719
  };
652
720
  /** Decides whether a request may go on. */
@@ -661,15 +729,20 @@ const guard = (request, context) => {
661
729
  if (exchanged !== null) return exchanged;
662
730
  const fromBearer = bearer(request);
663
731
  const fromCookie = cookieValue(request, cookieName(context.port));
732
+ const via = fromBearer === void 0 ? "cookie" : "bearer";
664
733
  const given = fromBearer ?? fromCookie;
665
- if (given === void 0 || !sameToken(given, context.token)) return {
734
+ const secret = via === "bearer" ? context.token : context.session;
735
+ if (given === void 0 || !sameToken(given, secret)) return {
666
736
  kind: "refuse",
667
737
  status: 401,
668
738
  error: "Not signed in; open the UI with `ork ui`."
669
739
  };
670
- if (!WRITES.has(request.method)) return { kind: "pass" };
740
+ if (!WRITES.has(request.method)) return {
741
+ kind: "pass",
742
+ via
743
+ };
671
744
  const origin = header(request, "origin");
672
- if ((fromBearer === void 0 || origin !== void 0) && (origin === void 0 || !ownOrigins(context.port).includes(origin))) return {
745
+ if ((via === "cookie" || origin !== void 0) && (origin === void 0 || !ownOrigins(context.port).includes(origin))) return {
673
746
  kind: "refuse",
674
747
  status: 403,
675
748
  error: `Origin ${origin ?? "(none)"} may not write here.`
@@ -680,7 +753,10 @@ const guard = (request, context) => {
680
753
  status: 415,
681
754
  error: "Send JSON (Content-Type: application/json)."
682
755
  };
683
- return { kind: "pass" };
756
+ return {
757
+ kind: "pass",
758
+ via
759
+ };
684
760
  };
685
761
  /** How often an open event stream gets a keep-alive comment. */
686
762
  const KEEP_ALIVE_MS = 15e3;
@@ -904,6 +980,15 @@ const viewOf$2 = (context, run) => {
904
980
  paused: pausedOf(events)
905
981
  };
906
982
  };
983
+ /** The run's view plus its last finish's declared outputs, when it has any. */
984
+ const viewWithOutputs = (context, run) => {
985
+ const finished = context.store.controlEvents(run.id).map((stored) => stored.event).findLast((event) => event.type === "run.finished");
986
+ const outputs = outputsText(finished?.type === "run.finished" ? finished.result.outputs : void 0);
987
+ return {
988
+ ...viewOf$2(context, run),
989
+ ...outputs.length === 0 ? {} : { outputs }
990
+ };
991
+ };
907
992
  // ../server/src/mcp-answers.ts
908
993
  /**
909
994
  * @file What `ork mcp`'s tools answer with (RFC-012): a route's reply as text,
@@ -1701,6 +1786,23 @@ const projectRootOf = (context, filter) => {
1701
1786
  const key = filter["project"];
1702
1787
  return key === void 0 || key === "all" ? void 0 : projectOf(context, key)?.root ?? key;
1703
1788
  };
1789
+ /** `now` is injectable so a test can move the clock. */
1790
+ const createSignInCodes = (now = Date.now) => {
1791
+ const expiries = /* @__PURE__ */ new Map();
1792
+ return {
1793
+ mint: () => {
1794
+ for (const [code, expiry] of expiries) if (expiry <= now()) expiries.delete(code);
1795
+ const code = randomBytes(32).toString("base64url");
1796
+ expiries.set(code, now() + 6e4);
1797
+ return code;
1798
+ },
1799
+ redeem: (code) => {
1800
+ const expiry = expiries.get(code);
1801
+ expiries.delete(code);
1802
+ return expiry !== void 0 && expiry > now();
1803
+ }
1804
+ };
1805
+ };
1704
1806
  // ../server/src/workflows.ts
1705
1807
  /**
1706
1808
  * @file A project's workflows, for the UI's Run workflow dialog: each
@@ -1776,6 +1878,10 @@ const readRoutes = (context) => ({
1776
1878
  pid: context.pid,
1777
1879
  startedAt: context.startedAt
1778
1880
  }),
1881
+ signIn: () => ok({
1882
+ code: context.codes.mint(),
1883
+ expiresIn: 60
1884
+ }),
1779
1885
  projects: () => ok(context.store.projects()),
1780
1886
  triggers: () => ok(triggersReport(context.store, context.store.projects(), context.home)),
1781
1887
  links: ({ query }) => ok(context.store.links(query.run === void 0 ? {} : { run: query.run })),
@@ -1813,7 +1919,7 @@ const readRoutes = (context) => ({
1813
1919
  },
1814
1920
  run: ({ params }) => {
1815
1921
  const found = runOf(context, params.id);
1816
- return found.ok ? ok(viewOf$2(context, found.run)) : found.refusal;
1922
+ return found.ok ? ok(viewWithOutputs(context, found.run)) : found.refusal;
1817
1923
  },
1818
1924
  tree: ({ params }) => {
1819
1925
  const found = runOf(context, params.id);
@@ -1876,6 +1982,8 @@ const RECENT_EVENTS = 30;
1876
1982
  const ANSWERED_BY_A_PERSON = "Only a person answers these: in `ork ui`, or with `ork decide` in their own terminal. Tell them what waits, and where.";
1877
1983
  const project = z.string().min(1).describe("A registered project's id or root");
1878
1984
  const run = z.string().min(1).describe("A run's id, or a unique start of it");
1985
+ /** An error answer, in the CLI's words, when `project` names none registered. */
1986
+ const unknownProject = (context, key) => key === void 0 || key === "all" || projectOf(context, key) !== void 0 ? void 0 : refused$1(unknownProjectRefusal(key, context.store.projects().map((p) => p.id)));
1879
1987
  const mcpTools = (context) => {
1880
1988
  const read = readRoutes(context);
1881
1989
  const { builderCatalog } = builderRoutes(context);
@@ -1890,13 +1998,22 @@ const mcpTools = (context) => {
1890
1998
  ...NO_QUERY,
1891
1999
  params: { id: args.project }
1892
2000
  }))),
1893
- tool("runs", "Runs, newest first, with their live status and cost. 10 unless a limit is given. Narrow them with `status` (running, paused, crashed, succeeded, failed, … or all), `workflow`, `project` (or all), `since` (24h, 30d, 2026-11-01, all) and `until` (a day).", RunsQuerySchema.shape, (args) => fromReply(read.runs({
1894
- ...NO_QUERY,
1895
- query: {
1896
- limit: 10,
1897
- ...args
1898
- }
1899
- }))),
2001
+ tool("runs", "Runs, newest first, with their live status and cost. 10 unless a limit is given. Narrow them with `status` (running, paused, crashed, succeeded, failed, … or all), `workflow`, `project` (or all), `since` (24h, 30d, 2026-11-01, all) and `until` (a day).", {
2002
+ ...RunsQuerySchema.shape,
2003
+ limit: z.union([z.number(), z.string()]).optional()
2004
+ }, ({ limit, ...args }) => {
2005
+ const bad = unknownProject(context, args.project);
2006
+ if (bad !== void 0) return bad;
2007
+ const count = limit === void 0 ? 10 : parseLimit(String(limit));
2008
+ if (count === null) return refused$1(limitRefusal(String(limit)));
2009
+ return fromReply(read.runs({
2010
+ ...NO_QUERY,
2011
+ query: {
2012
+ ...args,
2013
+ limit: count
2014
+ }
2015
+ }));
2016
+ }),
1900
2017
  tool("run", "One run: its status, what it waits on, its cost, and each node's state.", { run }, async (args) => {
1901
2018
  const view = await read.run({
1902
2019
  ...NO_QUERY,
@@ -1926,11 +2043,11 @@ const mcpTools = (context) => {
1926
2043
  }
1927
2044
  }));
1928
2045
  }),
1929
- tool("usage", "What runs spent, by workflow and model, and how close the limits are. Narrow it with `workflow`, `project` (or all), `since` and `until`: an age (24h, 30d), a day (2026-11-01) or an ISO time; `since` also takes all.", UsageQuerySchema.shape, (args) => fromReply(read.usage({
2046
+ tool("usage", "What runs spent, by workflow and model, and how close the limits are. Narrow it with `workflow`, `project` (or all), `since` and `until`: an age (24h, 30d), a day (2026-11-01) or an ISO time; `since` also takes all.", UsageQuerySchema.shape, (args) => unknownProject(context, args.project) ?? fromReply(read.usage({
1930
2047
  ...NO_QUERY,
1931
2048
  query: args
1932
2049
  }))),
1933
- tool("decisions", "The decisions open in live runs, waiting on a person. This server can't answer them. Narrow them with `workflow`, `run` (an id or a start of one) and `project` (or all).", DecisionsQuerySchema.shape, (args) => fromReply(read.decisions({
2050
+ tool("decisions", "The decisions open in live runs, waiting on a person. This server can't answer them. Narrow them with `workflow`, `run` (an id or a start of one) and `project` (or all).", DecisionsQuerySchema.shape, (args) => unknownProject(context, args.project) ?? fromReply(read.decisions({
1934
2051
  ...NO_QUERY,
1935
2052
  query: args
1936
2053
  }), (open) => ({
@@ -2042,7 +2159,8 @@ const openMcp = (options) => {
2042
2159
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2043
2160
  ...options.home === void 0 ? {} : { home: options.home },
2044
2161
  ...options.script === void 0 ? {} : { script: options.script },
2045
- updates: createUpdates({ version: options.version })
2162
+ updates: createUpdates({ version: options.version }),
2163
+ codes: createSignInCodes()
2046
2164
  };
2047
2165
  return {
2048
2166
  tools: [...mcpTools(context), ...actingTools(context, { start: options.start })],
@@ -2060,6 +2178,17 @@ const openMcp = (options) => {
2060
2178
  }
2061
2179
  };
2062
2180
  };
2181
+ // ../server/src/access.ts
2182
+ /**
2183
+ * @file The secrets a server starts with (RFC-019): the API token (bearer), the
2184
+ * session secret (the browser's cookie) and the sign-in code store. Each is
2185
+ * the caller's or a new one.
2186
+ */
2187
+ const createAccess = (given) => ({
2188
+ token: given.token ?? newToken(),
2189
+ session: given.session ?? newToken(),
2190
+ codes: given.codes ?? createSignInCodes()
2191
+ });
2063
2192
  // ../server/src/assets.ts
2064
2193
  /**
2065
2194
  * @file The built UI's files (`packages/ui/dist`, shipped as the CLI's
@@ -2328,7 +2457,8 @@ const feedbackRoutes = (context) => {
2328
2457
  note: ({ params, body }) => {
2329
2458
  const found = runOf(context, params.id);
2330
2459
  if (!found.ok) return found.refusal;
2331
- const noted = recordNote(store, found.run, body);
2460
+ const scrub = context.noteScrub?.(found.run, store.projects());
2461
+ const noted = recordNote(store, found.run, body, "", scrub);
2332
2462
  return noted.ok ? ok({ item: noted.item }) : fail(400, noted.error);
2333
2463
  },
2334
2464
  feedbackItems: ({ query }) => {
@@ -3368,7 +3498,9 @@ const createDispatch = (context, access) => {
3368
3498
  headers: req.headers
3369
3499
  }, {
3370
3500
  port: access.port(),
3371
- token: access.token
3501
+ token: access.token,
3502
+ session: access.session,
3503
+ codes: context.codes
3372
3504
  });
3373
3505
  if (verdict.kind === "refuse") {
3374
3506
  if (api) sendError(res, verdict.status, verdict.error);
@@ -3383,6 +3515,10 @@ const createDispatch = (context, access) => {
3383
3515
  }).end();
3384
3516
  return;
3385
3517
  }
3518
+ if (url.pathname === API.signIn.path && verdict.via !== "bearer") {
3519
+ sendError(res, 403, "Sign-in codes are minted with the API token only.");
3520
+ return;
3521
+ }
3386
3522
  const streamed = RUN_STREAM.pattern.exec(url.pathname);
3387
3523
  if (method === "GET" && streamed !== null) {
3388
3524
  const id = decodeURIComponent(streamed[1] ?? "");
@@ -3471,7 +3607,13 @@ const startServer = async (options) => {
3471
3607
  ...options.home === void 0 ? {} : { home: options.home },
3472
3608
  ...options.update === void 0 ? {} : { options: options.update }
3473
3609
  });
3610
+ const { token, session, codes } = createAccess({
3611
+ ...options.token === void 0 ? {} : { token: options.token },
3612
+ ...options.sessionSecret === void 0 ? {} : { session: options.sessionSecret },
3613
+ ...options.codes === void 0 ? {} : { codes: options.codes }
3614
+ });
3474
3615
  const context = {
3616
+ codes,
3475
3617
  store,
3476
3618
  version: options.version,
3477
3619
  pid: process.pid,
@@ -3481,9 +3623,9 @@ const startServer = async (options) => {
3481
3623
  ...uiDir === void 0 ? {} : { uiDir },
3482
3624
  ...options.gh === void 0 ? {} : { gh: options.gh },
3483
3625
  updates,
3484
- ...options.restart === void 0 ? {} : { restart: options.restart }
3626
+ ...options.restart === void 0 ? {} : { restart: options.restart },
3627
+ ...options.noteScrub === void 0 ? {} : { noteScrub: options.noteScrub }
3485
3628
  };
3486
- const token = options.token ?? newToken();
3487
3629
  const shutdown = new AbortController();
3488
3630
  const log = options.log ?? ((line) => {
3489
3631
  process.stderr.write(`${line}\n`);
@@ -3492,6 +3634,7 @@ const startServer = async (options) => {
3492
3634
  const handle = createDispatch(context, {
3493
3635
  port: () => port,
3494
3636
  token,
3637
+ session,
3495
3638
  shutdown: shutdown.signal
3496
3639
  });
3497
3640
  const server = createServer((req, res) => {
@@ -3549,4 +3692,4 @@ const startServer = async (options) => {
3549
3692
  }
3550
3693
  };
3551
3694
  };
3552
- export { openMcp as a, refused$1 as c, uiFilePath as i, newToken as l, startServer as n, tool as o, runningServer as r, answer as s, DEFAULT_PORT as t };
3695
+ export { openMcp as a, refused$1 as c, uiFilePath as i, newToken as l, startServer as n, tool as o, runningServer as r, answer as s, DEFAULT_PORT as t, createClient as u };
@@ -1,4 +1,4 @@
1
- import { L as openStore } from "./src-Frwpx3WF.mjs";
1
+ import { R as openStore } from "./src-Ciy7i3se.mjs";
2
2
  // src/store.ts
3
3
  /**
4
4
  * @file Opening the user's store (`~/.ork/state.db`) for the length of one