@bud.sh/app 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -31
- package/dist/client/assets/bud/design.js +1 -1
- package/dist/client/assets/canvas-Bih9EdKJ.js +1 -0
- package/dist/client/assets/difference-worker-Bzg-rizy.js +8 -0
- package/dist/client/assets/dist-CdE5wBkg.js +5 -0
- package/dist/client/assets/files-1dLuoM2f.js +1 -0
- package/dist/client/assets/i18n-CkO6VFI0.js +1 -0
- package/dist/client/assets/index-Cn2mxroU.css +2 -0
- package/dist/client/assets/{index-ChANGtl-.js → index-DtISWZp9.js} +2 -2
- package/dist/client/assets/pages-BJ-tNJ4J.js +1 -0
- package/dist/client/assets/{project-branches-DO897zM6.js → project-branches-Ce3rXrkc.js} +1 -1
- package/dist/client/assets/project-comments-CPUONfs8.js +1 -0
- package/dist/client/assets/project-thread-links-C-TGkq59.js +1 -0
- package/dist/client/assets/{project-threads-Lay351p4.js → project-threads-Bv7IDBgi.js} +105 -105
- package/dist/client/assets/project-work-DAE9-olg.js +1 -0
- package/dist/client/assets/{task-design-Avb2RO4w.js → task-design-CwySIAOl.js} +3 -3
- package/dist/client/assets/tasks-KWsCLi_h.js +3 -0
- package/dist/client/assets/wasm-inline-DSUyFn1L.js +1 -0
- package/dist/client/assets/workspace-CVdcfaaB.js +2 -0
- package/dist/client/index.html +3 -3
- package/dist/hosted/assets/{canvas-V36732ER.js → canvas-AII16n9i.js} +97 -6
- package/dist/hosted/assets/{comment-data-CF2ZV_3k.js → comment-data-BXfMElfx.js} +2004 -402
- package/dist/hosted/assets/dist-BCSTeNf4.js +4785 -0
- package/dist/hosted/assets/{files-DjSCjfU7.js → files-D_1yeAVO.js} +10 -5
- package/dist/hosted/assets/{pages-BzjzeuPu.js → pages-CN_SbKWA.js} +3 -3
- package/dist/hosted/assets/{project-branches-BT97J9i7.js → project-branches-Bxp0DCwg.js} +1 -1
- package/dist/hosted/assets/{project-thread-links-BRLgByEe.js → project-thread-links-KqQoKBLE.js} +76 -4
- package/dist/{node-host/assets/screen-CZXWJEds.js → hosted/assets/screen-DpAcDUKR.js} +29 -1
- package/dist/hosted/assets/tasks-D66LlwxM.js +1423 -0
- package/dist/hosted/assets/wasm-inline-Dl9tqabx.js +4 -0
- package/dist/hosted/hosted.js +7885 -6219
- package/dist/node/assets/Stream-CAm1eJ2n.js +16604 -0
- package/dist/{server/assets/bridge-B_Blt55E.js → node/assets/bridge-DQSBYHmw.js} +124 -290
- package/dist/node/assets/config-9_jWwNBh.js +1067 -0
- package/dist/node/assets/load-config.server-CMVtRVmO.js +261 -0
- package/dist/node/assets/module-3GgtT4Xs.js +308 -0
- package/dist/node/assets/server-DTQFuoX2.js +846 -0
- package/dist/node/assets/src-D-kG1GxB.js +2 -0
- package/dist/node/assets/{src-CerrKjID.js → src-Dwe9APUx.js} +93 -3
- package/dist/node/assets/src-DysjfbM8.js +8592 -0
- package/dist/node/bridge.js +1 -845
- package/dist/node/cli.js +206 -0
- package/dist/node/design.js +811 -13
- package/dist/node/index.js +2 -739
- package/dist/node/plugins.js +5711 -0
- package/dist/node/server.js +1 -6746
- package/dist/node/vite.js +121 -0
- package/dist/{server/assets/canvas-Dc1mYCwQ.js → node-host/assets/canvas-BeDoufy1.js} +97 -7
- package/dist/node-host/assets/{comment-data-DyIZNpfG.js → comment-data-DE2T9yTj.js} +1980 -376
- package/dist/node-host/assets/dist-BCSTeNf4.js +4785 -0
- package/dist/node-host/assets/{files-Dh2TiQZq.js → files-Dl-PTS09.js} +11 -6
- package/dist/node-host/assets/{pages-BxpjbYhs.js → pages-DL2sk6_U.js} +3 -3
- package/dist/node-host/assets/{project-branches-B7HUGiVK.js → project-branches-DGp5ELct.js} +1 -1
- package/dist/{server/assets/project-thread-links-CBOpf6zk.js → node-host/assets/project-thread-links-CgBV3Jcm.js} +76 -5
- package/dist/{hosted/assets/screen-CKp2C7kx.js → node-host/assets/screen-B55yyVfb.js} +29 -1
- package/dist/node-host/assets/tasks-DZRNkoxe.js +1425 -0
- package/dist/node-host/assets/wasm-inline-Dl9tqabx.js +4 -0
- package/dist/node-host/node-host.js +6127 -4438
- package/dist/server/assets/{branch-previews-ClkoJ0lM.js → branch-previews-DedrpnVR.js} +1555 -673
- package/dist/{node-host/assets/canvas-TRUfxfL5.js → server/assets/canvas-CJWQ-jjv.js} +98 -6
- package/dist/server/assets/{comment-data-CsjukwAW.js → comment-data-Rg42ToTW.js} +1666 -47
- package/dist/server/assets/dist-BCSTeNf4.js +4785 -0
- package/dist/server/assets/{events-iHpLGr3k.js → events-BH_rAu7O.js} +155 -20
- package/dist/server/assets/{files-E9mSFeoc.js → files-CPJqKeMo.js} +12 -7
- package/dist/server/assets/{listener-DN5TyOjJ.js → listener-D61O6PzF.js} +708 -12
- package/dist/server/assets/{pages-BBBXyJWV.js → pages-KJY_Ba92.js} +4 -4
- package/dist/server/assets/{project-branches-B0YIK6Ur.js → project-branches-qG9ACZvo.js} +2 -2
- package/dist/{node-host/assets/project-thread-links-CauJF0Cz.js → server/assets/project-thread-links-CqK0lqCF.js} +77 -4
- package/dist/{node-host/assets/tasks-request-ZWmbc8fE.js → server/assets/project-work-DoIwxWMm.js} +87 -2
- package/dist/server/assets/run-BJ8r5jSh.js +572 -0
- package/dist/server/assets/{screen-BIQA2mr4.js → screen-CBrHPFug.js} +30 -2
- package/dist/server/assets/{server-DxK-UB_Z.js → server-Dkbp5stG.js} +1521 -793
- package/dist/server/assets/{services-BRrgXYlT.js → services-BTfig8A8.js} +234 -997
- package/dist/server/assets/tasks-Bgu5Lg9Y.js +1427 -0
- package/dist/server/assets/wasm-inline-Dl9tqabx.js +4 -0
- package/dist/server/capture.js +1 -2
- package/dist/server/migration.js +569 -12
- package/dist/server/runtime.js +7 -5
- package/dist/server/server.js +3 -3
- package/dist/types/assets/{config-DIC2cf_S.d.ts → config-CvcYmE2s.d.ts} +188 -50
- package/dist/types/assets/{contract-CAT-XFNq.d.ts → contract-B7yuuqSn.d.ts} +44 -2
- package/dist/types/assets/{items-CaVkByHk.d.ts → items-BQdaVksf.d.ts} +12 -1
- package/dist/types/assets/memory-state-BBW-FAVA.d.ts +236 -0
- package/dist/types/assets/picker-Be5Gx4WR.d.ts +9 -0
- package/dist/types/assets/{task-design-xJOhRNWh.d.ts → task-design-BDHKFHpp.d.ts} +3 -1
- package/dist/types/bridge.d.ts +1 -1
- package/dist/types/companion.d.ts +3 -3
- package/dist/types/design.d.ts +2 -2
- package/dist/types/hosted.d.ts +3 -3
- package/dist/types/index.d.ts +2 -2
- package/dist/types/node-host.d.ts +2 -2
- package/dist/types/plugins.d.ts +445 -0
- package/dist/types/screen.d.ts +2 -2
- package/dist/types/server.d.ts +2 -228
- package/dist/types/vite.d.ts +16 -0
- package/package.json +19 -3
- package/dist/client/assets/canvas-Cu6R6xxT.js +0 -1
- package/dist/client/assets/difference-worker-jfTSn-Tw.js +0 -8
- package/dist/client/assets/files-js6ElfkU.js +0 -1
- package/dist/client/assets/i18n-DxqFr2mi.js +0 -1
- package/dist/client/assets/index-CFFxP0gS.css +0 -2
- package/dist/client/assets/pages-6_lhiQmz.js +0 -1
- package/dist/client/assets/project-comments-DFFKVpia.js +0 -1
- package/dist/client/assets/project-thread-links-NLFWQRD2.js +0 -1
- package/dist/client/assets/project-work-du0Y-L5Y.js +0 -1
- package/dist/client/assets/tasks-CA-E8bbK.js +0 -1
- package/dist/client/assets/tasks-request-C55IeuiP.js +0 -1
- package/dist/client/assets/workspace-Cw5HvX5d.js +0 -2
- package/dist/hosted/assets/tasks-Dj7AyBJB.js +0 -649
- package/dist/hosted/assets/tasks-request-DZob623r.js +0 -113
- package/dist/node-host/assets/tasks-Bs1IOpeU.js +0 -651
- package/dist/server/assets/project-work-qSKFO5yj.js +0 -88
- package/dist/server/assets/tasks-CiXodsLN.js +0 -652
- package/dist/server/assets/tasks-request-DhwCzpRi.js +0 -115
- package/dist/types/assets/picker-D1Bw3ndx.d.ts +0 -14
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as selected$1,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { $t as map, At as andThen, C as themeStyle, Cn as scoped, Cr as TaggedError, E as LOGO_TYPES, En as succeed, Et as acquireRelease, Fn as try_, Ft as callback, Gt as flatMap, Ht as ensuring, Pn as tryPromise, Qt as ignore, Rt as catch_, S as resolveScreenRoutes, T as FACTORY_STATUS_ROLES, Ut as fail$1, Vt as die, Zt as gen, an as promise, en as mapError, in as orElseSucceed, kn as sync, m as Git, qt as forEach, s as Previews, t as BranchPreviews, tn as match, w as FACTORY_STATUS_CATEGORIES, x as defaultScreens, zn as void_ } from "./branch-previews-DedrpnVR.js";
|
|
2
|
+
import { C as inspect, D as selected$1, E as ready, F as AdapterRegistry, H as WorkError, M as Attachments, O as validate, T as readWorkspace, U as ScreenModules, a as FileMutations, b as StoreActivity, c as FileReview, f as Files, g as FactoryRuns, h as ReviewActionError, k as LiveChannel, m as FilesInputError, q as taskDesignModuleId, r as FileSaves, t as FileReviewActions, u as FileSearch, w as read } from "./server-Dkbp5stG.js";
|
|
3
|
+
import { An as fieldByRole, B as parseThreadStatusInput, Dn as parseMessageAuthor, F as parseThreadInput, Gt as parseFileIndexQuery, I as parseThreadLinkInput, L as parseThreadReplyInput, M as parseThreadDeleteInput, N as parseThreadFilter, P as parseThreadId, Pt as parseFileSearchQuery, R as parseThreadSnapshot, Sn as valueByRole, St as parseReviewDiffQuery, Yt as parseFilePath, _ as taskRoutes, _n as parseWorkQuery, bn as parseWorkSyncResult, bt as parseReviewComparison, cn as parseWorkActionInput, ct as parseFileSaveRequest, dn as parseWorkCreate, dt as parseMutationApply, fn as parseWorkDetail, ft as parseMutationIntent, g as pageRoutes, h as fileRoutes, hn as parseWorkMutationResult, i as routes, j as parseThread, kt as parseReviewSourceQuery, mn as parseWorkItemRef, nn as MAX_CANVAS_Y, nt as parseReviewActionApply, rn as parseElementAnchor, rt as parseReviewActionIntent, sn as parseSectionData, ut as maxFileSaveBytes, wt as parseReviewHistoryQuery, xn as parseWorkUpdate, yn as parseWorkSync } from "./events-BH_rAu7O.js";
|
|
4
|
+
import { An as hasMessage, Ct as taskHref, En as AppShell, Fn as Text, Gt as Loader, H as parseTaskDesign, Mn as locale, Nn as t, On as readPreference, Ot as legacyFileHref, Pn as useLocaleSource, Q as FieldCardHeader, X as FieldCardContent, Y as FieldCard, _ as requestComments, _t as legacyConversationHref, at as readTabsCookie, bt as agentViewOpen, ct as tabsCookieName, et as activeTab, it as parseShellTabs, jn as isLocale, kn as writePreference, lt as tabsCookieValue, n as unavailableComments, nt as initialShellTabs, ot as stepShellTab, pt as conversationDestination, qt as Button, r as followThreads, rt as openShellTab, st as tabHref, t as commentsUnavailable, tt as closeShellTab, ut as visitShellTab, v as projectLive, wn as LiveStatus, xn as ShellTabBar, xt as selectedTask, yt as readConversationLocation } from "./comment-data-Rg42ToTW.js";
|
|
5
5
|
import { d as parsePreviewHash, f as parsePreviewId, l as parsePreviewBranch, m as parsePreviewViewport } from "./parsers-CsiSkwTT.js";
|
|
6
|
-
import { t as projectWork } from "./project-work-
|
|
6
|
+
import { t as projectWork } from "./project-work-DoIwxWMm.js";
|
|
7
7
|
import { n as showWorkspaceTab } from "./workspace-tab-DAUjkJzZ.js";
|
|
8
|
+
import { c as parseTaskContext, l as parseTaskContextInput } from "./run-BJ8r5jSh.js";
|
|
8
9
|
import { createHash } from "node:crypto";
|
|
9
10
|
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
10
11
|
import { createController, createRouter } from "remix/router";
|
|
@@ -86,10 +87,10 @@ function ProjectLiveStatus(handle) {
|
|
|
86
87
|
//#endregion
|
|
87
88
|
//#region app/client/screens.ts
|
|
88
89
|
var builtIns = {
|
|
89
|
-
"bud:pages": () => import("./pages-
|
|
90
|
-
"bud:canvas": () => import("./canvas-
|
|
91
|
-
"bud:tasks": () => import("./tasks-
|
|
92
|
-
"bud:files": () => import("./files-
|
|
90
|
+
"bud:pages": () => import("./pages-KJY_Ba92.js"),
|
|
91
|
+
"bud:canvas": () => import("./canvas-CJWQ-jjv.js"),
|
|
92
|
+
"bud:tasks": () => import("./tasks-Bgu5Lg9Y.js"),
|
|
93
|
+
"bud:files": () => import("./files-CPJqKeMo.js")
|
|
93
94
|
};
|
|
94
95
|
/**
|
|
95
96
|
* Imports a screen module: a built-in by its key, any other by the URL the server gave it.
|
|
@@ -365,6 +366,14 @@ function Workspace(handle) {
|
|
|
365
366
|
icon: screen.icon
|
|
366
367
|
};
|
|
367
368
|
}
|
|
369
|
+
if (agentViewOpen(href)) {
|
|
370
|
+
let task = selectedTask(href);
|
|
371
|
+
if (task) return {
|
|
372
|
+
href,
|
|
373
|
+
title: t("app.client.workspace.agentView", { task }),
|
|
374
|
+
icon: "terminal"
|
|
375
|
+
};
|
|
376
|
+
}
|
|
368
377
|
let last = (match && match.root !== "/" ? pathname.slice(match.root.length) : pathname).split("/").filter(Boolean).at(-1);
|
|
369
378
|
return {
|
|
370
379
|
href,
|
|
@@ -730,6 +739,97 @@ function createAppController(template, project, runtime) {
|
|
|
730
739
|
} });
|
|
731
740
|
}
|
|
732
741
|
//#endregion
|
|
742
|
+
//#region app/server/comment-task.ts
|
|
743
|
+
var titleLength = 120;
|
|
744
|
+
/**
|
|
745
|
+
* A task's title and description for a discussion: what it asks, where it was made, what it points
|
|
746
|
+
* at and how to get back to it, in the request's language.
|
|
747
|
+
* @param thread - The discussion, with its messages loaded.
|
|
748
|
+
* @param schema - The task store's fields; title and description go where their roles are.
|
|
749
|
+
* @param routes - The project's routes, for page titles.
|
|
750
|
+
* @param found - The element at the comment's point, when the comment didn't keep one.
|
|
751
|
+
* @returns Field values for a new task.
|
|
752
|
+
*/
|
|
753
|
+
function commentTaskValues(thread, schema, routes, found = null) {
|
|
754
|
+
let first = thread.messages[0];
|
|
755
|
+
let place = commentPlace(thread, routes);
|
|
756
|
+
let line = first?.body.split("\n").find((text) => text.trim())?.trim() ?? "";
|
|
757
|
+
let title = (line.length > titleLength ? `${line.slice(0, 119).trimEnd()}…` : line) || t("app.server.commentTask.title", { place });
|
|
758
|
+
let values = {};
|
|
759
|
+
let titleField = fieldByRole(schema, "title");
|
|
760
|
+
if (titleField) values[titleField.id] = title;
|
|
761
|
+
let descriptionField = fieldByRole(schema, "description");
|
|
762
|
+
if (descriptionField) values[descriptionField.id] = commentTaskDescription(thread, place, found);
|
|
763
|
+
return values;
|
|
764
|
+
}
|
|
765
|
+
function commentPlace(thread, routes) {
|
|
766
|
+
let anchor = thread.anchor;
|
|
767
|
+
if (!anchor) return t("app.server.commentTask.linkedTasks");
|
|
768
|
+
if ("type" in anchor) return `${anchor.path}:${anchor.startLine}–${anchor.endLine}`;
|
|
769
|
+
let route = routes.find((candidate) => candidate.id === anchor.pageId);
|
|
770
|
+
return route && route.title !== anchor.pagePath ? `${route.title} (${anchor.pagePath})` : anchor.pagePath;
|
|
771
|
+
}
|
|
772
|
+
function commentTaskDescription(thread, place, found) {
|
|
773
|
+
let first = thread.messages[0];
|
|
774
|
+
let lines = [];
|
|
775
|
+
if (first) {
|
|
776
|
+
lines.push(...first.body.split("\n").map((text) => `> ${text}`));
|
|
777
|
+
lines.push(`>`, `> — ${first.author.name}, ${first.createdAt.slice(0, 10)}`, "");
|
|
778
|
+
}
|
|
779
|
+
let anchor = thread.anchor;
|
|
780
|
+
let where = [place];
|
|
781
|
+
if (anchor && "type" in anchor) where.push(anchor.commit ? anchor.commit.slice(0, 8) : t("app.server.commentTask.uncommitted"));
|
|
782
|
+
else if (anchor) {
|
|
783
|
+
where.push(`${anchor.viewport}px`);
|
|
784
|
+
where.push(anchor.branch ?? t("app.server.commentTask.workingTree"));
|
|
785
|
+
}
|
|
786
|
+
lines.push(`**${t("app.server.commentTask.where")}:** ${where.join(" · ")}`);
|
|
787
|
+
let element = (anchor && !("type" in anchor) ? anchor.element : null) ?? found;
|
|
788
|
+
if (element) {
|
|
789
|
+
let label = element.label || element.breadcrumb;
|
|
790
|
+
lines.push(`**${t("app.server.commentTask.element")}:** ${label} (\`${element.tagName}\`)`);
|
|
791
|
+
let source = element.source;
|
|
792
|
+
if (source?.file) lines.push(`**${t("app.server.commentTask.source")}:** \`${source.file}${source.line ? `:${source.line}` : ""}\`${source.component ? ` (${source.component})` : ""}`);
|
|
793
|
+
}
|
|
794
|
+
if (anchor && "type" in anchor && anchor.context.selected.length) lines.push("", "```", ...anchor.context.selected, "```");
|
|
795
|
+
if (thread.messageCount > 1) lines.push(`**${t("app.server.commentTask.replies")}:** ${t("app.server.commentTask.replyCount", { count: thread.messageCount - 1 })}`);
|
|
796
|
+
lines.push(`**${t("app.server.commentTask.discussion")}:** [/comments/${thread.id}](/comments/${encodeURIComponent(thread.id)})`);
|
|
797
|
+
return lines.join("\n");
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Where a task made from a discussion came from, kept with the task for its page and its agent.
|
|
801
|
+
* @param thread - The discussion.
|
|
802
|
+
* @param routes - The project's routes, for page titles.
|
|
803
|
+
* @param given - What the canvas found: the element and point, and the screenshot's hash.
|
|
804
|
+
* @returns The task's context.
|
|
805
|
+
*/
|
|
806
|
+
function commentTaskContext(thread, routes, given) {
|
|
807
|
+
let anchor = thread.anchor;
|
|
808
|
+
let code = anchor && "type" in anchor ? anchor : null;
|
|
809
|
+
let canvas = anchor && !("type" in anchor) ? anchor : null;
|
|
810
|
+
let route = canvas ? routes.find((candidate) => candidate.id === canvas.pageId) : void 0;
|
|
811
|
+
return {
|
|
812
|
+
version: 1,
|
|
813
|
+
thread: thread.id,
|
|
814
|
+
page: canvas ? {
|
|
815
|
+
id: canvas.pageId,
|
|
816
|
+
path: canvas.pagePath,
|
|
817
|
+
title: route?.title ?? canvas.pagePath
|
|
818
|
+
} : null,
|
|
819
|
+
viewport: canvas?.viewport ?? null,
|
|
820
|
+
branch: canvas?.branch ?? null,
|
|
821
|
+
point: canvas ? given.point : null,
|
|
822
|
+
element: (canvas?.element ?? null) || given.element,
|
|
823
|
+
image: canvas ? given.image : null,
|
|
824
|
+
code: code ? {
|
|
825
|
+
path: code.path,
|
|
826
|
+
startLine: code.startLine,
|
|
827
|
+
endLine: code.endLine,
|
|
828
|
+
commit: code.commit ?? null
|
|
829
|
+
} : null
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
//#endregion
|
|
733
833
|
//#region app/server/comments.ts
|
|
734
834
|
/** A comment request the server refuses, with the HTTP status to answer it with. */
|
|
735
835
|
var CommentError = class extends TaggedError("CommentError") {
|
|
@@ -914,7 +1014,7 @@ function projectApi(options) {
|
|
|
914
1014
|
if (!runtime) return jsonError("The project services are not running. Restart Bud.", 503);
|
|
915
1015
|
return runtime.runPromise(program(project).pipe(match({
|
|
916
1016
|
onFailure: options.failure,
|
|
917
|
-
onSuccess: (value) => Response.json(value, { headers })
|
|
1017
|
+
onSuccess: (value) => value instanceof Response ? value : Response.json(value, { headers })
|
|
918
1018
|
})), { signal: request.signal });
|
|
919
1019
|
};
|
|
920
1020
|
}
|
|
@@ -949,7 +1049,7 @@ function parseQuery(request, schema, allowed) {
|
|
|
949
1049
|
}
|
|
950
1050
|
//#endregion
|
|
951
1051
|
//#region app/actions/comments/controller.ts
|
|
952
|
-
var statuses$
|
|
1052
|
+
var statuses$6 = {
|
|
953
1053
|
input: 400,
|
|
954
1054
|
unavailable: 503,
|
|
955
1055
|
"not-found": 404,
|
|
@@ -1015,7 +1115,7 @@ function createCommentsController(project, runtime) {
|
|
|
1015
1115
|
project,
|
|
1016
1116
|
runtime,
|
|
1017
1117
|
missing: "Connect a project with the Bud Vite plugin to use comments.",
|
|
1018
|
-
failure: (error) => error._tag === "WorkError" ? jsonError(error.message, statuses$
|
|
1118
|
+
failure: (error) => error._tag === "WorkError" ? jsonError(error.message, statuses$6[error.reason], {
|
|
1019
1119
|
code: error.reason,
|
|
1020
1120
|
...error.currentRevision ? { currentRevision: error.currentRevision } : {}
|
|
1021
1121
|
}) : jsonError(error.message, error.status)
|
|
@@ -1114,6 +1214,39 @@ function createCommentsController(project, runtime) {
|
|
|
1114
1214
|
unlink({ request, params }) {
|
|
1115
1215
|
return respond(request, (project) => written(link(request, project, params.id, true)));
|
|
1116
1216
|
},
|
|
1217
|
+
makeTask({ request, params }) {
|
|
1218
|
+
return respond(request, (project) => written(gen(function* () {
|
|
1219
|
+
let id = yield* input$2(() => parseThreadId(params.id));
|
|
1220
|
+
let body = yield* readProjectJsonBody(request, { maxBytes: 32e5 });
|
|
1221
|
+
let given = parseTaskContextInput(body && typeof body === "object" && "context" in body ? body.context : null);
|
|
1222
|
+
let threads = yield* writable(project.root, "link");
|
|
1223
|
+
let thread = yield* threads.get(project.root, id).pipe(flatMap((value) => result(() => parseThread(value))));
|
|
1224
|
+
let image = given.image ? yield* (yield* Attachments).put(given.image).pipe(orElseSucceed(() => null)) : null;
|
|
1225
|
+
let context = commentTaskContext(thread, project.config.routes, {
|
|
1226
|
+
...given,
|
|
1227
|
+
image
|
|
1228
|
+
});
|
|
1229
|
+
let tasks = yield* selected$1(project.root);
|
|
1230
|
+
let work = yield* ready(tasks);
|
|
1231
|
+
if (!tasks.schema.capabilities.create) return yield* new WorkError({
|
|
1232
|
+
reason: "refused",
|
|
1233
|
+
message: "The task store does not support creating tasks."
|
|
1234
|
+
});
|
|
1235
|
+
let draft = yield* input$2(() => parseWorkCreate({ values: commentTaskValues(thread, tasks.schema, project.config.routes, context.element) }, tasks.schema));
|
|
1236
|
+
let created = yield* work.create(project.root, draft).pipe(flatMap((value) => result(() => parseWorkMutationResult(value, tasks.schema), true)));
|
|
1237
|
+
let task = created.item.id;
|
|
1238
|
+
if (work.writeItemData && created.item.revision) yield* ignore(work.writeItemData(project.root, task, "budContext", context, created.item.revision));
|
|
1239
|
+
let linked = yield* threads.link(project.root, id, [task], thread.revision ?? "").pipe(flatMap((value) => result(() => parseThread(value), true)), mapError((cause) => new WorkError({
|
|
1240
|
+
reason: "uncertain",
|
|
1241
|
+
message: `Task ${task} was created, but linking it to this discussion could not be confirmed. Link it from the task before trying again.`,
|
|
1242
|
+
cause
|
|
1243
|
+
})));
|
|
1244
|
+
return {
|
|
1245
|
+
item: created.item,
|
|
1246
|
+
thread: linked
|
|
1247
|
+
};
|
|
1248
|
+
})));
|
|
1249
|
+
},
|
|
1117
1250
|
deleteMessage({ request, params }) {
|
|
1118
1251
|
return respond(request, (project) => written(gen(function* () {
|
|
1119
1252
|
let id = yield* input$2(() => parseThreadId(params.id));
|
|
@@ -1156,6 +1289,544 @@ function createCommentsController(project, runtime) {
|
|
|
1156
1289
|
} });
|
|
1157
1290
|
}
|
|
1158
1291
|
//#endregion
|
|
1292
|
+
//#region app/server/agent-tools.ts
|
|
1293
|
+
/** The task database's tools, as MCP lists them; all of them only read. */
|
|
1294
|
+
var AGENT_TOOLS = [
|
|
1295
|
+
{
|
|
1296
|
+
name: "search_tasks",
|
|
1297
|
+
description: "Searches the project's tasks by words in their title or description, and by status. Use it to find related tasks, duplicates and earlier decisions.",
|
|
1298
|
+
inputSchema: {
|
|
1299
|
+
type: "object",
|
|
1300
|
+
properties: {
|
|
1301
|
+
query: {
|
|
1302
|
+
type: "string",
|
|
1303
|
+
description: "Words to look for."
|
|
1304
|
+
},
|
|
1305
|
+
status: {
|
|
1306
|
+
type: "string",
|
|
1307
|
+
description: "Only tasks with this status, such as open."
|
|
1308
|
+
},
|
|
1309
|
+
limit: {
|
|
1310
|
+
type: "number",
|
|
1311
|
+
description: "At most this many; 20 by default, 50 at most."
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
name: "get_task",
|
|
1318
|
+
description: "Reads one task: its fields, relations and the discussions linked to it.",
|
|
1319
|
+
inputSchema: {
|
|
1320
|
+
type: "object",
|
|
1321
|
+
properties: { id: {
|
|
1322
|
+
type: "string",
|
|
1323
|
+
description: "The task id."
|
|
1324
|
+
} },
|
|
1325
|
+
required: ["id"]
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
name: "list_discussions",
|
|
1330
|
+
description: "Lists the project's discussions, such as comments on a page of the site, with their first message and the tasks they link.",
|
|
1331
|
+
inputSchema: {
|
|
1332
|
+
type: "object",
|
|
1333
|
+
properties: {
|
|
1334
|
+
page: {
|
|
1335
|
+
type: "string",
|
|
1336
|
+
description: "Only comments on this page path, such as /pricing."
|
|
1337
|
+
},
|
|
1338
|
+
task: {
|
|
1339
|
+
type: "string",
|
|
1340
|
+
description: "Only discussions linked to this task id."
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
name: "recent_changes",
|
|
1347
|
+
description: "Lists the tasks changed most recently, newest first.",
|
|
1348
|
+
inputSchema: {
|
|
1349
|
+
type: "object",
|
|
1350
|
+
properties: { limit: {
|
|
1351
|
+
type: "number",
|
|
1352
|
+
description: "At most this many; 20 by default."
|
|
1353
|
+
} }
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
];
|
|
1357
|
+
function text(value, max = 2e3) {
|
|
1358
|
+
return typeof value === "string" ? value.slice(0, max) : void 0;
|
|
1359
|
+
}
|
|
1360
|
+
function brief(item, schema) {
|
|
1361
|
+
return {
|
|
1362
|
+
id: item.id,
|
|
1363
|
+
title: text(valueByRole(item, schema, "title"), 300),
|
|
1364
|
+
status: text(valueByRole(item, schema, "status"), 100),
|
|
1365
|
+
updated: text(valueByRole(item, schema, "updated"), 100)
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
function discussion(thread, messages = 1) {
|
|
1369
|
+
let anchor = thread.anchor;
|
|
1370
|
+
return {
|
|
1371
|
+
id: thread.id,
|
|
1372
|
+
status: thread.status,
|
|
1373
|
+
page: anchor && !("type" in anchor) ? anchor.pagePath : void 0,
|
|
1374
|
+
file: anchor && "type" in anchor ? anchor.path : void 0,
|
|
1375
|
+
tasks: thread.itemIds,
|
|
1376
|
+
messageCount: thread.messageCount,
|
|
1377
|
+
messages: thread.messages.slice(0, messages).map((message) => ({
|
|
1378
|
+
author: message.author.name,
|
|
1379
|
+
at: message.createdAt,
|
|
1380
|
+
body: message.body.slice(0, 4e3)
|
|
1381
|
+
}))
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
function count(value, fallback, max) {
|
|
1385
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? Math.min(value, max) : fallback;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Runs one of the task database's tools.
|
|
1389
|
+
* @param stores - The project's stores.
|
|
1390
|
+
* @param name - The tool.
|
|
1391
|
+
* @param args - Its arguments, untrusted.
|
|
1392
|
+
* @returns What it found, as JSON-ready data.
|
|
1393
|
+
*/
|
|
1394
|
+
function callAgentTool(stores, name, args) {
|
|
1395
|
+
let { root, work, threads } = stores;
|
|
1396
|
+
return gen(function* () {
|
|
1397
|
+
let schema = yield* work.schema(root);
|
|
1398
|
+
if (name === "search_tasks") return { tasks: (yield* work.list(root, {
|
|
1399
|
+
...text(args.query, 200) ? { search: text(args.query, 200) } : {},
|
|
1400
|
+
...text(args.status, 100) ? { status: text(args.status, 100) } : {},
|
|
1401
|
+
limit: count(args.limit, 20, 50)
|
|
1402
|
+
})).items.map((item) => brief(item, schema)) };
|
|
1403
|
+
if (name === "get_task") {
|
|
1404
|
+
let id = text(args.id, 200);
|
|
1405
|
+
if (!id) return yield* new WorkError({
|
|
1406
|
+
reason: "input",
|
|
1407
|
+
message: "Name the task with id."
|
|
1408
|
+
});
|
|
1409
|
+
let detail = yield* work.get(root, id);
|
|
1410
|
+
let linked = threads ? (yield* threads.list(root, { itemId: id })).threads : [];
|
|
1411
|
+
return {
|
|
1412
|
+
...brief(detail.item, schema),
|
|
1413
|
+
description: text(valueByRole(detail.item, schema, "description"), 2e4),
|
|
1414
|
+
fields: Object.fromEntries(schema.fields.filter((field) => detail.item.values[field.id] !== void 0).map((field) => [field.label, detail.item.values[field.id]])),
|
|
1415
|
+
relations: detail.item.relations,
|
|
1416
|
+
discussions: linked.map((thread) => discussion(thread, 20))
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
if (name === "list_discussions") {
|
|
1420
|
+
if (!threads) return { discussions: [] };
|
|
1421
|
+
let page = text(args.page, 1e3);
|
|
1422
|
+
let task = text(args.task, 200);
|
|
1423
|
+
return { discussions: (yield* threads.list(root, {
|
|
1424
|
+
...page ? { pagePath: page } : {},
|
|
1425
|
+
...task ? { itemId: task } : {}
|
|
1426
|
+
})).threads.slice(0, 100).map((thread) => discussion(thread)) };
|
|
1427
|
+
}
|
|
1428
|
+
if (name === "recent_changes") return { tasks: (yield* work.list(root, { limit: 500 })).items.map((item) => brief(item, schema)).sort((left, right) => (right.updated ?? "").localeCompare(left.updated ?? "")).slice(0, count(args.limit, 20, 100)) };
|
|
1429
|
+
return yield* new WorkError({
|
|
1430
|
+
reason: "input",
|
|
1431
|
+
message: `No tool ${name}.`
|
|
1432
|
+
});
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
function answer(id, result) {
|
|
1436
|
+
return {
|
|
1437
|
+
jsonrpc: "2.0",
|
|
1438
|
+
id,
|
|
1439
|
+
result
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function failure$3(id, code, message) {
|
|
1443
|
+
return {
|
|
1444
|
+
jsonrpc: "2.0",
|
|
1445
|
+
id: id ?? null,
|
|
1446
|
+
error: {
|
|
1447
|
+
code,
|
|
1448
|
+
message
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Answers MCP's JSON-RPC messages for the task database: initialize, ping, the tool list and
|
|
1454
|
+
* tool calls. Notifications get no answer.
|
|
1455
|
+
* @param stores - The project's stores.
|
|
1456
|
+
* @param body - The request body, untrusted.
|
|
1457
|
+
* @returns The response body, or null when every message was a notification.
|
|
1458
|
+
*/
|
|
1459
|
+
function answerAgentTools(stores, body) {
|
|
1460
|
+
let one = (message) => {
|
|
1461
|
+
if (!message || typeof message !== "object" || typeof message.method !== "string") return succeed(failure$3(message?.id, -32600, "Invalid request."));
|
|
1462
|
+
let id = message.id;
|
|
1463
|
+
let notification = id === void 0;
|
|
1464
|
+
let params = message.params ?? {};
|
|
1465
|
+
switch (message.method) {
|
|
1466
|
+
case "initialize": return succeed(answer(id, {
|
|
1467
|
+
protocolVersion: typeof params.protocolVersion === "string" ? params.protocolVersion : "2025-06-18",
|
|
1468
|
+
capabilities: { tools: { listChanged: false } },
|
|
1469
|
+
serverInfo: {
|
|
1470
|
+
name: "bud-tasks",
|
|
1471
|
+
version: "1.0.0"
|
|
1472
|
+
},
|
|
1473
|
+
instructions: "Read-only access to this project's tasks and discussions, for finding related work and earlier decisions."
|
|
1474
|
+
}));
|
|
1475
|
+
case "ping": return succeed(notification ? null : answer(id, {}));
|
|
1476
|
+
case "tools/list": return succeed(answer(id, { tools: AGENT_TOOLS }));
|
|
1477
|
+
case "tools/call": return callAgentTool(stores, typeof params.name === "string" ? params.name : "", params.arguments && typeof params.arguments === "object" ? params.arguments : {}).pipe(match({
|
|
1478
|
+
onSuccess: (result) => answer(id, { content: [{
|
|
1479
|
+
type: "text",
|
|
1480
|
+
text: JSON.stringify(result, null, 2)
|
|
1481
|
+
}] }),
|
|
1482
|
+
onFailure: (error) => answer(id, {
|
|
1483
|
+
content: [{
|
|
1484
|
+
type: "text",
|
|
1485
|
+
text: error.message
|
|
1486
|
+
}],
|
|
1487
|
+
isError: true
|
|
1488
|
+
})
|
|
1489
|
+
}));
|
|
1490
|
+
default: return succeed(notification ? null : failure$3(id, -32601, `No method ${message.method}.`));
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
if (Array.isArray(body)) return map(forEach(body.slice(0, 20), one), (answers) => {
|
|
1494
|
+
let given = answers.filter((value) => value !== null);
|
|
1495
|
+
return given.length ? given : null;
|
|
1496
|
+
});
|
|
1497
|
+
return one(body);
|
|
1498
|
+
}
|
|
1499
|
+
//#endregion
|
|
1500
|
+
//#region app/actions/agent-api/controller.ts
|
|
1501
|
+
var json = (body, status = 200) => Response.json(body, {
|
|
1502
|
+
status,
|
|
1503
|
+
headers: { "Cache-Control": "no-store" }
|
|
1504
|
+
});
|
|
1505
|
+
/**
|
|
1506
|
+
* Serves the task database agents read in their sandboxes, as an MCP server over HTTP: only a
|
|
1507
|
+
* running run's token lets an agent in, and it can only read.
|
|
1508
|
+
* @param project - The trusted consuming project.
|
|
1509
|
+
* @param runtime - The companion's runtime with its stores and runs.
|
|
1510
|
+
* @returns The agent controller.
|
|
1511
|
+
*/
|
|
1512
|
+
function createAgentController(project, runtime) {
|
|
1513
|
+
return createController(routes.agentApi, { actions: {
|
|
1514
|
+
tasksStream() {
|
|
1515
|
+
return new Response(null, {
|
|
1516
|
+
status: 405,
|
|
1517
|
+
headers: { Allow: "POST" }
|
|
1518
|
+
});
|
|
1519
|
+
},
|
|
1520
|
+
tasks({ request }) {
|
|
1521
|
+
if (!project || !runtime) return json({ error: "No project is connected." }, 404);
|
|
1522
|
+
let token = /^Bearer (\S{1,2000})$/.exec(request.headers.get("Authorization") ?? "")?.[1];
|
|
1523
|
+
if (!token) return json({ error: "Send the run’s token." }, 401);
|
|
1524
|
+
return runtime.runPromise(gen(function* () {
|
|
1525
|
+
if (!(yield* (yield* FactoryRuns).agentTask(token))) return json({ error: "This token’s run is over or unknown." }, 401);
|
|
1526
|
+
let { work, threads } = yield* AdapterRegistry;
|
|
1527
|
+
if (!work) return json({ error: "Task storage is off." }, 503);
|
|
1528
|
+
let body = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
|
|
1529
|
+
let answer = yield* answerAgentTools({
|
|
1530
|
+
root: project.root,
|
|
1531
|
+
work,
|
|
1532
|
+
threads
|
|
1533
|
+
}, body);
|
|
1534
|
+
return answer === null ? new Response(null, { status: 202 }) : json(answer);
|
|
1535
|
+
}).pipe(catch_((error) => succeed(json({ error: error.message }, "status" in error ? error.status : 500)))), { signal: request.signal });
|
|
1536
|
+
}
|
|
1537
|
+
} });
|
|
1538
|
+
}
|
|
1539
|
+
//#endregion
|
|
1540
|
+
//#region app/actions/factory-api/controller.ts
|
|
1541
|
+
var TERMINAL_SESSIONS = [
|
|
1542
|
+
"agent",
|
|
1543
|
+
"dev",
|
|
1544
|
+
"shell"
|
|
1545
|
+
];
|
|
1546
|
+
var statuses$5 = {
|
|
1547
|
+
input: 400,
|
|
1548
|
+
unavailable: 503,
|
|
1549
|
+
"not-found": 404,
|
|
1550
|
+
conflict: 409,
|
|
1551
|
+
refused: 422,
|
|
1552
|
+
timeout: 504,
|
|
1553
|
+
uncertain: 409,
|
|
1554
|
+
storage: 500
|
|
1555
|
+
};
|
|
1556
|
+
/**
|
|
1557
|
+
* Compares the factory's statuses with the task store's.
|
|
1558
|
+
* @param factory - The factory config.
|
|
1559
|
+
* @param schema - The task store's schema.
|
|
1560
|
+
* @param canAdd - Whether the store can add statuses.
|
|
1561
|
+
* @returns What is missing and whether adding fixes it.
|
|
1562
|
+
*/
|
|
1563
|
+
function factoryReadiness(factory, schema, canAdd) {
|
|
1564
|
+
let problems = [];
|
|
1565
|
+
for (let role of FACTORY_STATUS_ROLES) {
|
|
1566
|
+
let value = factory.statuses[role];
|
|
1567
|
+
let category = FACTORY_STATUS_CATEGORIES[role];
|
|
1568
|
+
let status = schema.statuses.find((entry) => entry.value === value);
|
|
1569
|
+
if (!status) problems.push({
|
|
1570
|
+
role,
|
|
1571
|
+
value,
|
|
1572
|
+
category,
|
|
1573
|
+
reason: "missing"
|
|
1574
|
+
});
|
|
1575
|
+
else if (status.category !== category) problems.push({
|
|
1576
|
+
role,
|
|
1577
|
+
value,
|
|
1578
|
+
category,
|
|
1579
|
+
reason: "category"
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
return {
|
|
1583
|
+
enabled: true,
|
|
1584
|
+
ready: !problems.length,
|
|
1585
|
+
message: null,
|
|
1586
|
+
problems,
|
|
1587
|
+
canAdd: canAdd && problems.length > 0 && problems.every((problem) => problem.reason === "missing"),
|
|
1588
|
+
statuses: factory.statuses
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
var off = {
|
|
1592
|
+
enabled: false,
|
|
1593
|
+
ready: false,
|
|
1594
|
+
message: null,
|
|
1595
|
+
problems: [],
|
|
1596
|
+
canAdd: false,
|
|
1597
|
+
statuses: null
|
|
1598
|
+
};
|
|
1599
|
+
/**
|
|
1600
|
+
* Serves the factory's readiness and sets up its statuses in the task store.
|
|
1601
|
+
* @param project - The trusted consuming project.
|
|
1602
|
+
* @param runtime - The companion's runtime with the selected adapters.
|
|
1603
|
+
* @returns The factory controller.
|
|
1604
|
+
*/
|
|
1605
|
+
function createFactoryController(project, runtime) {
|
|
1606
|
+
let api = projectApi({
|
|
1607
|
+
project,
|
|
1608
|
+
runtime,
|
|
1609
|
+
missing: "Connect a project with the Bud Vite plugin to use the factory.",
|
|
1610
|
+
failure: (error) => error._tag === "WorkError" ? jsonError(error.message, statuses$5[error.reason], { code: error.reason }) : jsonError(error.message, error.status)
|
|
1611
|
+
});
|
|
1612
|
+
let taskId = (value) => /^[\w.:-]{1,200}$/.test(value) ? succeed(value) : fail$1(new WorkError({
|
|
1613
|
+
reason: "input",
|
|
1614
|
+
message: "Invalid task id."
|
|
1615
|
+
}));
|
|
1616
|
+
let person = (root) => map(commentAuthor(root), (author) => ({
|
|
1617
|
+
name: author.name,
|
|
1618
|
+
email: author.email
|
|
1619
|
+
}));
|
|
1620
|
+
function act(request, id, operation) {
|
|
1621
|
+
return api(request, (project) => gen(function* () {
|
|
1622
|
+
let task = yield* taskId(id);
|
|
1623
|
+
return { run: yield* operation(yield* FactoryRuns, project, task) };
|
|
1624
|
+
}));
|
|
1625
|
+
}
|
|
1626
|
+
function readiness(project) {
|
|
1627
|
+
return gen(function* () {
|
|
1628
|
+
let factory = project.config.factory;
|
|
1629
|
+
if (!factory) return off;
|
|
1630
|
+
let tasks = yield* selected$1(project.root);
|
|
1631
|
+
if (!tasks.service || tasks.connection.state !== "ready") return {
|
|
1632
|
+
...off,
|
|
1633
|
+
enabled: true,
|
|
1634
|
+
message: tasks.connection.state === "ready" ? "Task storage is off, so the factory has nowhere to keep its tasks." : tasks.connection.message ?? "Task storage is unavailable."
|
|
1635
|
+
};
|
|
1636
|
+
return factoryReadiness(factory, tasks.schema, Boolean(tasks.service.addStatuses));
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
let terminals = /* @__PURE__ */ new Map();
|
|
1640
|
+
let bridge = (task, session) => gen(function* () {
|
|
1641
|
+
if (!TERMINAL_SESSIONS.includes(session)) return yield* new WorkError({
|
|
1642
|
+
reason: "not-found",
|
|
1643
|
+
message: "No such terminal."
|
|
1644
|
+
});
|
|
1645
|
+
let cached = terminals.get(task);
|
|
1646
|
+
let terminal = cached && cached.until > Date.now() ? cached.value : yield* (yield* FactoryRuns).terminal(task);
|
|
1647
|
+
if (!terminal) return yield* new WorkError({
|
|
1648
|
+
reason: "not-found",
|
|
1649
|
+
message: "This run has no terminal; it starts with the run’s sandbox."
|
|
1650
|
+
});
|
|
1651
|
+
terminals.set(task, {
|
|
1652
|
+
value: terminal,
|
|
1653
|
+
until: Date.now() + 3e4
|
|
1654
|
+
});
|
|
1655
|
+
let call = (path, init = {}) => tryPromise({
|
|
1656
|
+
try: async () => {
|
|
1657
|
+
let url = new URL(terminal.url);
|
|
1658
|
+
let [pathname, query = ""] = path.split("?");
|
|
1659
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}${pathname}`;
|
|
1660
|
+
for (let [key, value] of new URLSearchParams(query)) url.searchParams.set(key, value);
|
|
1661
|
+
let response = await fetch(url, {
|
|
1662
|
+
...init,
|
|
1663
|
+
headers: {
|
|
1664
|
+
...terminal.headers,
|
|
1665
|
+
Authorization: `Basic ${btoa(`bud:${terminal.password}`)}`,
|
|
1666
|
+
...init.headers
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
if (!response.ok && response.status !== 204) throw new Error(`The terminal answered ${response.status}.`);
|
|
1670
|
+
return response;
|
|
1671
|
+
},
|
|
1672
|
+
catch: (cause) => {
|
|
1673
|
+
terminals.delete(task);
|
|
1674
|
+
return new WorkError({
|
|
1675
|
+
reason: "unavailable",
|
|
1676
|
+
message: "The terminal isn’t answering; its sandbox may be waking.",
|
|
1677
|
+
cause
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
return {
|
|
1682
|
+
name: session,
|
|
1683
|
+
terminal,
|
|
1684
|
+
call
|
|
1685
|
+
};
|
|
1686
|
+
});
|
|
1687
|
+
let size = (value, fallback) => typeof value === "number" && Number.isInteger(value) && value >= 2 && value <= 1e3 ? value : fallback;
|
|
1688
|
+
return createController(routes.factoryApi, { actions: {
|
|
1689
|
+
status({ request }) {
|
|
1690
|
+
return api(request, readiness);
|
|
1691
|
+
},
|
|
1692
|
+
run({ request, params }) {
|
|
1693
|
+
return act(request, params.id, (runs, _project, task) => runs.get(task));
|
|
1694
|
+
},
|
|
1695
|
+
start({ request, params }) {
|
|
1696
|
+
return act(request, params.id, (runs, project, task) => flatMap(person(project.root), (by) => runs.start(task, by)));
|
|
1697
|
+
},
|
|
1698
|
+
changes({ request, params }) {
|
|
1699
|
+
return act(request, params.id, (runs, project, task) => gen(function* () {
|
|
1700
|
+
let body = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
|
|
1701
|
+
let message = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message.trim() : "";
|
|
1702
|
+
if (!message || message.length > 1e4) return yield* new WorkError({
|
|
1703
|
+
reason: "input",
|
|
1704
|
+
message: "Write what should change."
|
|
1705
|
+
});
|
|
1706
|
+
return yield* runs.changes(task, message, yield* person(project.root));
|
|
1707
|
+
}));
|
|
1708
|
+
},
|
|
1709
|
+
answer({ request, params }) {
|
|
1710
|
+
return act(request, params.id, (runs, project, task) => gen(function* () {
|
|
1711
|
+
let body = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
|
|
1712
|
+
let message = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message.trim() : "";
|
|
1713
|
+
if (!message || message.length > 1e4) return yield* new WorkError({
|
|
1714
|
+
reason: "input",
|
|
1715
|
+
message: "Write your answer."
|
|
1716
|
+
});
|
|
1717
|
+
return yield* runs.answer(task, message, yield* person(project.root));
|
|
1718
|
+
}));
|
|
1719
|
+
},
|
|
1720
|
+
approve({ request, params }) {
|
|
1721
|
+
return act(request, params.id, (runs, project, task) => flatMap(person(project.root), (by) => runs.approve(task, by)));
|
|
1722
|
+
},
|
|
1723
|
+
discard({ request, params }) {
|
|
1724
|
+
return act(request, params.id, (runs, _project, task) => runs.discard(task));
|
|
1725
|
+
},
|
|
1726
|
+
retry({ request, params }) {
|
|
1727
|
+
return act(request, params.id, (runs, _project, task) => runs.retry(task));
|
|
1728
|
+
},
|
|
1729
|
+
wake({ request, params }) {
|
|
1730
|
+
return act(request, params.id, (runs, _project, task) => runs.wake(task));
|
|
1731
|
+
},
|
|
1732
|
+
preview({ request, params }) {
|
|
1733
|
+
return api(request, () => gen(function* () {
|
|
1734
|
+
let task = yield* taskId(params.id);
|
|
1735
|
+
return { preview: yield* (yield* FactoryRuns).preview(task) };
|
|
1736
|
+
}));
|
|
1737
|
+
},
|
|
1738
|
+
touch({ request, params }) {
|
|
1739
|
+
return api(request, () => gen(function* () {
|
|
1740
|
+
yield* (yield* FactoryRuns).touch(yield* taskId(params.id));
|
|
1741
|
+
return { ok: true };
|
|
1742
|
+
}));
|
|
1743
|
+
},
|
|
1744
|
+
terminal({ request, params }) {
|
|
1745
|
+
return api(request, () => gen(function* () {
|
|
1746
|
+
let task = yield* taskId(params.id);
|
|
1747
|
+
let { name, terminal, call } = yield* bridge(task, params.session);
|
|
1748
|
+
let session = terminal.sessions[name];
|
|
1749
|
+
if (!session) return yield* new WorkError({
|
|
1750
|
+
reason: "not-found",
|
|
1751
|
+
message: name === "agent" ? "This run’s agent has no interface to attach to." : "This run has no dev server."
|
|
1752
|
+
});
|
|
1753
|
+
let query = new URL(request.url).searchParams;
|
|
1754
|
+
yield* call("/open", {
|
|
1755
|
+
method: "POST",
|
|
1756
|
+
headers: { "Content-Type": "application/json" },
|
|
1757
|
+
body: JSON.stringify({
|
|
1758
|
+
id: name,
|
|
1759
|
+
command: session.command,
|
|
1760
|
+
env: session.env,
|
|
1761
|
+
cols: size(Number(query.get("cols")), 80),
|
|
1762
|
+
rows: size(Number(query.get("rows")), 24)
|
|
1763
|
+
})
|
|
1764
|
+
});
|
|
1765
|
+
yield* ignore((yield* FactoryRuns).touch(task));
|
|
1766
|
+
let stream = yield* call(`/stream?id=${name}&from=${Math.max(0, Number(query.get("from")) || 0)}`, { signal: request.signal });
|
|
1767
|
+
return new Response(stream.body, { headers: {
|
|
1768
|
+
"Content-Type": "text/event-stream",
|
|
1769
|
+
"Cache-Control": "no-store",
|
|
1770
|
+
"X-Content-Type-Options": "nosniff"
|
|
1771
|
+
} });
|
|
1772
|
+
}));
|
|
1773
|
+
},
|
|
1774
|
+
terminalInput({ request, params }) {
|
|
1775
|
+
return api(request, () => gen(function* () {
|
|
1776
|
+
let task = yield* taskId(params.id);
|
|
1777
|
+
let body = yield* readProjectJsonBody(request, { maxBytes: 64e3 });
|
|
1778
|
+
let data = body && typeof body === "object" && "data" in body ? body.data : null;
|
|
1779
|
+
if (typeof data !== "string" || data.length > 32e3) return yield* new WorkError({
|
|
1780
|
+
reason: "input",
|
|
1781
|
+
message: "Invalid input."
|
|
1782
|
+
});
|
|
1783
|
+
let { name, call } = yield* bridge(task, params.session);
|
|
1784
|
+
yield* call(`/input?id=${name}`, {
|
|
1785
|
+
method: "POST",
|
|
1786
|
+
body: data
|
|
1787
|
+
});
|
|
1788
|
+
return { ok: true };
|
|
1789
|
+
}));
|
|
1790
|
+
},
|
|
1791
|
+
terminalResize({ request, params }) {
|
|
1792
|
+
return api(request, () => gen(function* () {
|
|
1793
|
+
let task = yield* taskId(params.id);
|
|
1794
|
+
let body = yield* readProjectJsonBody(request, { maxBytes: 1e3 });
|
|
1795
|
+
let { name, call } = yield* bridge(task, params.session);
|
|
1796
|
+
yield* call(`/resize?id=${name}`, {
|
|
1797
|
+
method: "POST",
|
|
1798
|
+
headers: { "Content-Type": "application/json" },
|
|
1799
|
+
body: JSON.stringify({
|
|
1800
|
+
cols: size(body?.cols, 80),
|
|
1801
|
+
rows: size(body?.rows, 24)
|
|
1802
|
+
})
|
|
1803
|
+
});
|
|
1804
|
+
return { ok: true };
|
|
1805
|
+
}));
|
|
1806
|
+
},
|
|
1807
|
+
addStatuses({ request }) {
|
|
1808
|
+
return api(request, (project) => gen(function* () {
|
|
1809
|
+
let current = yield* readiness(project);
|
|
1810
|
+
if (!current.enabled) return yield* new WorkError({
|
|
1811
|
+
reason: "refused",
|
|
1812
|
+
message: "The factory is off."
|
|
1813
|
+
});
|
|
1814
|
+
if (current.ready) return current;
|
|
1815
|
+
if (!current.canAdd) return yield* new WorkError({
|
|
1816
|
+
reason: "refused",
|
|
1817
|
+
message: current.message ?? "The task store can’t add these statuses; set them up in the store or map other ones in factory.statuses."
|
|
1818
|
+
});
|
|
1819
|
+
let tasks = yield* selected$1(project.root);
|
|
1820
|
+
yield* (yield* ready(tasks)).addStatuses(project.root, current.problems.map((problem) => ({
|
|
1821
|
+
value: problem.value,
|
|
1822
|
+
category: problem.category
|
|
1823
|
+
}))).pipe(ensuring(StoreActivity.use((activity) => activity.notify("work"))));
|
|
1824
|
+
return yield* readiness(project);
|
|
1825
|
+
}));
|
|
1826
|
+
}
|
|
1827
|
+
} });
|
|
1828
|
+
}
|
|
1829
|
+
//#endregion
|
|
1159
1830
|
//#region app/actions/tasks-api/controller.ts
|
|
1160
1831
|
var statuses$4 = {
|
|
1161
1832
|
input: 400,
|
|
@@ -1242,6 +1913,29 @@ function createTasksController(project, runtime) {
|
|
|
1242
1913
|
}));
|
|
1243
1914
|
}
|
|
1244
1915
|
return createController(routes.tasksApi, { actions: {
|
|
1916
|
+
context({ request, params }) {
|
|
1917
|
+
return respond(request, (project) => gen(function* () {
|
|
1918
|
+
let id = yield* validate(() => parseWorkItemRef({ id: params.id }).id, "input");
|
|
1919
|
+
let current = yield* selected$1(project.root);
|
|
1920
|
+
let service = yield* ready(current);
|
|
1921
|
+
if (!service.readItemData) return { context: null };
|
|
1922
|
+
let { value } = yield* service.readItemData(project.root, id, "budContext");
|
|
1923
|
+
return { context: parseTaskContext(value) };
|
|
1924
|
+
}));
|
|
1925
|
+
},
|
|
1926
|
+
attachment({ request, params }) {
|
|
1927
|
+
return respond(request, () => gen(function* () {
|
|
1928
|
+
let bytes = yield* (yield* Attachments).get(params.hash);
|
|
1929
|
+
if (!bytes) return yield* new WorkError({
|
|
1930
|
+
reason: "not-found",
|
|
1931
|
+
message: "No such image."
|
|
1932
|
+
});
|
|
1933
|
+
return new Response(bytes, { headers: {
|
|
1934
|
+
"Content-Type": "image/jpeg",
|
|
1935
|
+
"Cache-Control": "private, max-age=31536000, immutable"
|
|
1936
|
+
} });
|
|
1937
|
+
}));
|
|
1938
|
+
},
|
|
1245
1939
|
schema({ request }) {
|
|
1246
1940
|
return respond(request, (project) => read(project.root, (reader) => inspect(reader).pipe(map(({ schema }) => schema))));
|
|
1247
1941
|
},
|
|
@@ -1957,6 +2651,8 @@ function createApp(template, publicDirectory, project, runtime) {
|
|
|
1957
2651
|
router.map(routes, createAppController(template, project, runtime));
|
|
1958
2652
|
router.map(routes.comments, createCommentsController(project, runtime));
|
|
1959
2653
|
router.map(routes.tasksApi, createTasksController(project, runtime));
|
|
2654
|
+
router.map(routes.factoryApi, createFactoryController(project, runtime));
|
|
2655
|
+
router.map(routes.agentApi, createAgentController(project, runtime));
|
|
1960
2656
|
router.map(routes.filesApi, createFilesController(project, runtime));
|
|
1961
2657
|
router.map(routes.fileSearchApi, createFileSearchController(project, runtime));
|
|
1962
2658
|
router.map(routes.fileReviewApi, createFileReviewController(project, runtime));
|