@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.
- package/dist/{__build_info__-B8TNbFtN.mjs → __build_info__-dPf4cEGL.mjs} +3 -134
- package/dist/{attach-CU4Pk42S.mjs → attach-BQl8nQz2.mjs} +5 -5
- package/dist/{attach-qvfyaQXC.mjs → attach-DhZQ87Nz.mjs} +49 -224
- package/dist/bin.mjs +204 -154
- package/dist/{brief-Bh1FnAHv.mjs → brief-BQbeoPMc.mjs} +19 -9
- package/dist/{builtin-files-dnTP2GaX.mjs → builtin-files-CWL3JEo-.mjs} +1 -1
- package/dist/{command-tree-BkSYWmYJ.mjs → command-tree-B6uvMJq1.mjs} +1 -1
- package/dist/{completion-CzXUGSVn.mjs → completion-DKPuD_Rw.mjs} +47 -14
- package/dist/{context-b4_gPlv3.mjs → context-Bw6R_EYq.mjs} +3 -3
- package/dist/detached-BQlbmqR0.mjs +38 -0
- package/dist/{find-P-VV4B47.mjs → find-B30-rd-u.mjs} +1 -1
- package/dist/flags-ByYBx-Xb.mjs +170 -0
- package/dist/{follow-ups-B_c1DSpA.mjs → follow-ups-Cxca1Jgg.mjs} +24 -18
- package/dist/{format-BS1qzpkf.mjs → format-C-nEZv-P.mjs} +27 -11
- package/dist/{help-groups-hB6kY34d.mjs → help-groups-BpRgBSTh.mjs} +13 -7
- package/dist/{in-a-run-DKoMGTNE.mjs → in-a-run-CZFzoT3R.mjs} +2 -2
- package/dist/{install-B743Bwzz.mjs → install-CsqK1avz.mjs} +2 -183
- package/dist/{limits-DrsKCqaN.mjs → limits-CXh052II.mjs} +1 -1
- package/dist/{mcp-BMwekw38.mjs → mcp-B4wME9C4.mjs} +6 -6
- package/dist/{once-6L8gciJV.mjs → once-BKn3pOtg.mjs} +3 -3
- package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
- package/dist/{pause-8gnVgICR.mjs → pause-B1grR1cl.mjs} +3 -3
- package/dist/{print-DLe4MHn-.mjs → print-Bd8MWVJk.mjs} +1 -1
- package/dist/{project-records-CsILxwfI.mjs → project-records-BOEVs69i.mjs} +1 -1
- package/dist/{parse-BzWpMN-a.mjs → refusals-DQQX3AIb.mjs} +10 -1
- package/dist/{relay-Bvm9JEoN.mjs → relay-CeNiq3GT.mjs} +1 -1
- package/dist/{run-D4yuTmmJ.mjs → run-C0C54UiU.mjs} +116 -137
- package/dist/{run-y8jI7bAI.mjs → run-D6yU2rLf.mjs} +1 -1
- package/dist/{send-BV9eIxQ7.mjs → send-DqgF58hb.mjs} +2 -2
- package/dist/{src-Frwpx3WF.mjs → src-Ciy7i3se.mjs} +843 -66
- package/dist/{src-ygFMh2eP.mjs → src-D2qpd-G7.mjs} +4 -4
- package/dist/{src-CVeA4tAw.mjs → src-DP02RP9O.mjs} +227 -180
- package/dist/{src-DWAcd3Sy.mjs → src-n-u4TGev.mjs} +186 -43
- package/dist/{store-B0nGqnPq.mjs → store-2KXzMCEN.mjs} +1 -1
- package/dist/ui/assets/app-C5vO1M-l.css +1 -0
- package/dist/ui/assets/{app-C4yxmYQR.js → app-ICEKpy_C.js} +21 -20
- package/dist/ui/assets/{index-CcbJWo5C.js → index-BGaix5wh.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-BZ9BhFw8.mjs → ui-CAvygxTb.mjs} +53 -24
- package/dist/{usage-BIs4j5Rw.mjs → usage-BFBco5OS.mjs} +7 -7
- package/dist/{validate-Xn2ZbJQC.mjs → validate-UrmC5AG1.mjs} +5 -5
- package/dist/{watch-9VM3DFTp.mjs → watch-DTxkgB8R.mjs} +5 -5
- package/dist/{workflows-CBYtycIy.mjs → workflows-8GV-SeRm.mjs} +28 -4
- package/dist/{worktrees-C7vfh7y5.mjs → worktrees-CUdUvZlO.mjs} +9 -6
- package/package.json +1 -1
- package/dist/flags-CqjJ44uW.mjs +0 -125
- package/dist/post-y1qL1Fnj.mjs +0 -46
- package/dist/ui/assets/app-9Q-62Ywz.css +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
7
|
-
import { t as builtinProblem } from "./builtin-files-
|
|
8
|
-
import { n as newRunId, t as findRun } from "./find-
|
|
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
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
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
|
|
635
|
-
const sessionCookie = (port,
|
|
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("
|
|
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 (!
|
|
708
|
+
if (!context.codes.redeem(given)) return {
|
|
641
709
|
kind: "refuse",
|
|
642
710
|
status: 401,
|
|
643
|
-
error:
|
|
711
|
+
error: BAD_CODE
|
|
644
712
|
};
|
|
645
|
-
url.searchParams.delete("
|
|
713
|
+
url.searchParams.delete("code");
|
|
646
714
|
return {
|
|
647
715
|
kind: "exchange",
|
|
648
|
-
cookie: sessionCookie(context.port, context.
|
|
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
|
-
|
|
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 {
|
|
740
|
+
if (!WRITES.has(request.method)) return {
|
|
741
|
+
kind: "pass",
|
|
742
|
+
via
|
|
743
|
+
};
|
|
671
744
|
const origin = header(request, "origin");
|
|
672
|
-
if ((
|
|
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 {
|
|
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(
|
|
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).",
|
|
1894
|
-
...
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
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
|
|
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 };
|