@edspencer/paddock 0.0.1 → 0.57.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 +69 -0
- package/install-notice.mjs +29 -0
- package/package.json +58 -5
- package/packages/server/dist/adoptable.d.ts +127 -0
- package/packages/server/dist/adoptable.js +315 -0
- package/packages/server/dist/app.d.ts +67 -0
- package/packages/server/dist/app.js +325 -0
- package/packages/server/dist/archive.d.ts +42 -0
- package/packages/server/dist/archive.js +125 -0
- package/packages/server/dist/attachments-config.d.ts +114 -0
- package/packages/server/dist/attachments-config.js +165 -0
- package/packages/server/dist/attachments-hint.d.ts +53 -0
- package/packages/server/dist/attachments-hint.js +140 -0
- package/packages/server/dist/attachments.d.ts +33 -0
- package/packages/server/dist/attachments.js +117 -0
- package/packages/server/dist/auth.d.ts +84 -0
- package/packages/server/dist/auth.js +247 -0
- package/packages/server/dist/background.d.ts +36 -0
- package/packages/server/dist/background.js +137 -0
- package/packages/server/dist/bind-safety.d.ts +56 -0
- package/packages/server/dist/bind-safety.js +51 -0
- package/packages/server/dist/brand.d.ts +44 -0
- package/packages/server/dist/brand.js +88 -0
- package/packages/server/dist/chat-dto.d.ts +210 -0
- package/packages/server/dist/chat-dto.js +308 -0
- package/packages/server/dist/cli/import-chats.d.ts +21 -0
- package/packages/server/dist/cli/import-chats.js +197 -0
- package/packages/server/dist/cli/paddock.d.ts +2 -0
- package/packages/server/dist/cli/paddock.js +247 -0
- package/packages/server/dist/concurrency.d.ts +57 -0
- package/packages/server/dist/concurrency.js +71 -0
- package/packages/server/dist/config.d.ts +538 -0
- package/packages/server/dist/config.js +625 -0
- package/packages/server/dist/curation-config.d.ts +55 -0
- package/packages/server/dist/curation-config.js +64 -0
- package/packages/server/dist/environment-prompt.d.ts +52 -0
- package/packages/server/dist/environment-prompt.js +61 -0
- package/packages/server/dist/event-bus.d.ts +87 -0
- package/packages/server/dist/event-bus.js +48 -0
- package/packages/server/dist/git.d.ts +128 -0
- package/packages/server/dist/git.js +446 -0
- package/packages/server/dist/github-auth.d.ts +44 -0
- package/packages/server/dist/github-auth.js +169 -0
- package/packages/server/dist/herdctl-agent-config.d.ts +98 -0
- package/packages/server/dist/herdctl-agent-config.js +331 -0
- package/packages/server/dist/herdctl-agent-names.d.ts +172 -0
- package/packages/server/dist/herdctl-agent-names.js +228 -0
- package/packages/server/dist/herdctl-jobs-index.d.ts +65 -0
- package/packages/server/dist/herdctl-jobs-index.js +225 -0
- package/packages/server/dist/herdctl-jobs.d.ts +98 -0
- package/packages/server/dist/herdctl-jobs.js +225 -0
- package/packages/server/dist/herdctl.d.ts +623 -0
- package/packages/server/dist/herdctl.js +1279 -0
- package/packages/server/dist/hook-config.d.ts +217 -0
- package/packages/server/dist/hook-config.js +224 -0
- package/packages/server/dist/http-bytes.d.ts +24 -0
- package/packages/server/dist/http-bytes.js +52 -0
- package/packages/server/dist/index.d.ts +1 -0
- package/packages/server/dist/index.js +14 -0
- package/packages/server/dist/instance-config.d.ts +143 -0
- package/packages/server/dist/instance-config.js +385 -0
- package/packages/server/dist/localcommand.d.ts +23 -0
- package/packages/server/dist/localcommand.js +140 -0
- package/packages/server/dist/management-auth.d.ts +99 -0
- package/packages/server/dist/management-auth.js +165 -0
- package/packages/server/dist/management-config.d.ts +164 -0
- package/packages/server/dist/management-config.js +286 -0
- package/packages/server/dist/management-mcp-server.d.ts +20 -0
- package/packages/server/dist/management-mcp-server.js +95 -0
- package/packages/server/dist/management-metadata.d.ts +67 -0
- package/packages/server/dist/management-metadata.js +56 -0
- package/packages/server/dist/management-ops.d.ts +110 -0
- package/packages/server/dist/management-ops.js +449 -0
- package/packages/server/dist/management-policy.d.ts +172 -0
- package/packages/server/dist/management-policy.js +259 -0
- package/packages/server/dist/message-provenance.d.ts +112 -0
- package/packages/server/dist/message-provenance.js +260 -0
- package/packages/server/dist/models.d.ts +164 -0
- package/packages/server/dist/models.js +208 -0
- package/packages/server/dist/openapi.d.ts +66 -0
- package/packages/server/dist/openapi.js +183 -0
- package/packages/server/dist/parent-detach.d.ts +30 -0
- package/packages/server/dist/parent-detach.js +109 -0
- package/packages/server/dist/preload.d.ts +43 -0
- package/packages/server/dist/preload.js +55 -0
- package/packages/server/dist/project-files.d.ts +76 -0
- package/packages/server/dist/project-files.js +191 -0
- package/packages/server/dist/project-mime.d.ts +35 -0
- package/packages/server/dist/project-mime.js +55 -0
- package/packages/server/dist/project-paths.d.ts +88 -0
- package/packages/server/dist/project-paths.js +126 -0
- package/packages/server/dist/project-types.d.ts +316 -0
- package/packages/server/dist/project-types.js +48 -0
- package/packages/server/dist/projects.d.ts +202 -0
- package/packages/server/dist/projects.js +894 -0
- package/packages/server/dist/queued-message.d.ts +35 -0
- package/packages/server/dist/queued-message.js +116 -0
- package/packages/server/dist/read-state.d.ts +46 -0
- package/packages/server/dist/read-state.js +139 -0
- package/packages/server/dist/recovery-config.d.ts +111 -0
- package/packages/server/dist/recovery-config.js +105 -0
- package/packages/server/dist/recovery.d.ts +262 -0
- package/packages/server/dist/recovery.js +490 -0
- package/packages/server/dist/resume-drain.d.ts +122 -0
- package/packages/server/dist/resume-drain.js +158 -0
- package/packages/server/dist/route-context.d.ts +168 -0
- package/packages/server/dist/route-context.js +39 -0
- package/packages/server/dist/route-errors.d.ts +8 -0
- package/packages/server/dist/route-errors.js +13 -0
- package/packages/server/dist/routes/chats.d.ts +12 -0
- package/packages/server/dist/routes/chats.js +1406 -0
- package/packages/server/dist/routes/git.d.ts +13 -0
- package/packages/server/dist/routes/git.js +236 -0
- package/packages/server/dist/routes/mcp.d.ts +83 -0
- package/packages/server/dist/routes/mcp.js +250 -0
- package/packages/server/dist/routes/meta.d.ts +15 -0
- package/packages/server/dist/routes/meta.js +447 -0
- package/packages/server/dist/routes/projects.d.ts +25 -0
- package/packages/server/dist/routes/projects.js +736 -0
- package/packages/server/dist/routes/triggers.d.ts +21 -0
- package/packages/server/dist/routes/triggers.js +324 -0
- package/packages/server/dist/routes/workspace-mount.d.ts +48 -0
- package/packages/server/dist/routes/workspace-mount.js +32 -0
- package/packages/server/dist/routes.d.ts +26 -0
- package/packages/server/dist/routes.js +29 -0
- package/packages/server/dist/run-provenance.d.ts +109 -0
- package/packages/server/dist/run-provenance.js +206 -0
- package/packages/server/dist/runs.d.ts +92 -0
- package/packages/server/dist/runs.js +72 -0
- package/packages/server/dist/schedule-config.d.ts +68 -0
- package/packages/server/dist/schedule-config.js +75 -0
- package/packages/server/dist/schedule-session.d.ts +34 -0
- package/packages/server/dist/schedule-session.js +120 -0
- package/packages/server/dist/self-mcp-descriptions.d.ts +20 -0
- package/packages/server/dist/self-mcp-descriptions.js +145 -0
- package/packages/server/dist/self-mcp-read.d.ts +15 -0
- package/packages/server/dist/self-mcp-read.js +97 -0
- package/packages/server/dist/self-mcp-triggers.d.ts +30 -0
- package/packages/server/dist/self-mcp-triggers.js +144 -0
- package/packages/server/dist/self-mcp-types.d.ts +248 -0
- package/packages/server/dist/self-mcp-types.js +10 -0
- package/packages/server/dist/self-mcp-util.d.ts +73 -0
- package/packages/server/dist/self-mcp-util.js +157 -0
- package/packages/server/dist/self-mcp-write.d.ts +35 -0
- package/packages/server/dist/self-mcp-write.js +215 -0
- package/packages/server/dist/self-mcp.d.ts +95 -0
- package/packages/server/dist/self-mcp.js +448 -0
- package/packages/server/dist/send-file-mcp.d.ts +46 -0
- package/packages/server/dist/send-file-mcp.js +281 -0
- package/packages/server/dist/session-hub.d.ts +186 -0
- package/packages/server/dist/session-hub.js +291 -0
- package/packages/server/dist/spawn-capability.d.ts +99 -0
- package/packages/server/dist/spawn-capability.js +106 -0
- package/packages/server/dist/star.d.ts +26 -0
- package/packages/server/dist/star.js +94 -0
- package/packages/server/dist/start.d.ts +1 -0
- package/packages/server/dist/start.js +21 -0
- package/packages/server/dist/subagents.d.ts +337 -0
- package/packages/server/dist/subagents.js +626 -0
- package/packages/server/dist/sweep.d.ts +122 -0
- package/packages/server/dist/sweep.js +696 -0
- package/packages/server/dist/tooldetails.d.ts +33 -0
- package/packages/server/dist/tooldetails.js +466 -0
- package/packages/server/dist/transcribe.d.ts +69 -0
- package/packages/server/dist/transcribe.js +119 -0
- package/packages/server/dist/transcripts.d.ts +42 -0
- package/packages/server/dist/transcripts.js +164 -0
- package/packages/server/dist/trigger-config.d.ts +278 -0
- package/packages/server/dist/trigger-config.js +417 -0
- package/packages/server/dist/trigger-runtime.d.ts +94 -0
- package/packages/server/dist/trigger-runtime.js +82 -0
- package/packages/server/dist/trigger-session.d.ts +34 -0
- package/packages/server/dist/trigger-session.js +122 -0
- package/packages/server/dist/triggers.d.ts +83 -0
- package/packages/server/dist/triggers.js +124 -0
- package/packages/server/dist/trusted-proxy.d.ts +57 -0
- package/packages/server/dist/trusted-proxy.js +157 -0
- package/packages/server/dist/turn-notice.d.ts +154 -0
- package/packages/server/dist/turn-notice.js +271 -0
- package/packages/server/dist/unread.d.ts +36 -0
- package/packages/server/dist/unread.js +123 -0
- package/packages/server/dist/usage.d.ts +66 -0
- package/packages/server/dist/usage.js +243 -0
- package/packages/server/dist/wake-injection.d.ts +156 -0
- package/packages/server/dist/wake-injection.js +136 -0
- package/packages/server/dist/whisper-local.d.ts +9 -0
- package/packages/server/dist/whisper-local.js +122 -0
- package/packages/server/dist/ws-context.d.ts +158 -0
- package/packages/server/dist/ws-context.js +1 -0
- package/packages/server/dist/ws-protocol.d.ts +423 -0
- package/packages/server/dist/ws-protocol.js +251 -0
- package/packages/server/dist/ws-self-mcp.d.ts +52 -0
- package/packages/server/dist/ws-self-mcp.js +28 -0
- package/packages/server/dist/ws-triggers.d.ts +29 -0
- package/packages/server/dist/ws-triggers.js +261 -0
- package/packages/server/dist/ws-turn.d.ts +86 -0
- package/packages/server/dist/ws-turn.js +751 -0
- package/packages/server/dist/ws.d.ts +62 -0
- package/packages/server/dist/ws.js +1024 -0
- package/packages/server/package.json +6 -0
- package/packages/web/dist/assets/InstanceConfigPage-Bypd_quL.js +2 -0
- package/packages/web/dist/assets/MarkdownRenderer-Co8w9iuX.js +30 -0
- package/packages/web/dist/assets/ProjectRedirect-DXrA24Cy.js +2 -0
- package/packages/web/dist/assets/ProjectView-B9opP7Sv.js +13 -0
- package/packages/web/dist/assets/ProjectsGrid-D2cwb2jY.js +2 -0
- package/packages/web/dist/assets/arc-B--FIRB8.js +2 -0
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-B6dRWZZ6.js +37 -0
- package/packages/web/dist/assets/bash-I8pq0VWm.js +2 -0
- package/packages/web/dist/assets/blockDiagram-GPEHLZMM-DLxFAvvf.js +133 -0
- package/packages/web/dist/assets/c-D2F1sQUL.js +2 -0
- package/packages/web/dist/assets/c4Diagram-AAUBKEIU-CNee5ks4.js +11 -0
- package/packages/web/dist/assets/channel-Bw0A52CB.js +2 -0
- package/packages/web/dist/assets/chunk-2J33WTMH-DObw03Xl.js +2 -0
- package/packages/web/dist/assets/chunk-4BX2VUAB-CH2SR6nN.js +2 -0
- package/packages/web/dist/assets/chunk-55IACEB6-WlVpegT3.js +2 -0
- package/packages/web/dist/assets/chunk-727SXJPM-g8GT9Rgd.js +207 -0
- package/packages/web/dist/assets/chunk-AQP2D5EJ-DbN4PsDF.js +232 -0
- package/packages/web/dist/assets/chunk-FMBD7UC4-DSN3BjFG.js +16 -0
- package/packages/web/dist/assets/chunk-ND2GUHAM-D9R4le0z.js +2 -0
- package/packages/web/dist/assets/chunk-QZHKN3VN-D4YgRaKH.js +2 -0
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-itf_kt-q.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-itf_kt-q.js +2 -0
- package/packages/web/dist/assets/core-CysJ2gLj.js +4 -0
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-D1PB0KNu.js +2 -0
- package/packages/web/dist/assets/cpp-Bm3ii6kY.js +2 -0
- package/packages/web/dist/assets/csharp-BNpuYZMG.js +2 -0
- package/packages/web/dist/assets/css-DazXZka4.js +2 -0
- package/packages/web/dist/assets/cytoscape.esm-CUqq0XTU.js +332 -0
- package/packages/web/dist/assets/dagre-BM42HDAG-C6Uuq-dq.js +5 -0
- package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
- package/packages/web/dist/assets/diagram-2AECGRRQ-C9JvQ425.js +44 -0
- package/packages/web/dist/assets/diagram-5GNKFQAL-Cu6pdh2d.js +11 -0
- package/packages/web/dist/assets/diagram-KO2AKTUF-PmXcUQhY.js +4 -0
- package/packages/web/dist/assets/diagram-LMA3HP47-9ww6_9Iq.js +25 -0
- package/packages/web/dist/assets/diagram-OG6HWLK6-DWQVjv_n.js +25 -0
- package/packages/web/dist/assets/erDiagram-TEJ5UH35-6F7xTuff.js +86 -0
- package/packages/web/dist/assets/flowDiagram-I6XJVG4X-SCiGu7m6.js +163 -0
- package/packages/web/dist/assets/format-yf7Vf9kR.js +2 -0
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BNpPwxkL.js +293 -0
- package/packages/web/dist/assets/gitGraphDiagram-PVQCEYII-C0gyAJjW.js +107 -0
- package/packages/web/dist/assets/go-BPx_y7j8.js +2 -0
- package/packages/web/dist/assets/graph-BlwPajkw.js +2 -0
- package/packages/web/dist/assets/index-B-YuzkF6.js +71 -0
- package/packages/web/dist/assets/index-CvGiQddO.css +1 -0
- package/packages/web/dist/assets/infoDiagram-5YYISTIA-CJ2CFPfy.js +3 -0
- package/packages/web/dist/assets/ini-B3BVgEsG.js +2 -0
- package/packages/web/dist/assets/init-Gi6I4Gst.js +2 -0
- package/packages/web/dist/assets/ishikawaDiagram-YF4QCWOH-Cs0D32Ry.js +71 -0
- package/packages/web/dist/assets/java-B5VKqtH2.js +2 -0
- package/packages/web/dist/assets/javascript-BKRaQes9.js +2 -0
- package/packages/web/dist/assets/journeyDiagram-JHISSGLW-Bc9fuI2i.js +140 -0
- package/packages/web/dist/assets/json-DIYVocXf.js +2 -0
- package/packages/web/dist/assets/kanban-definition-UN3LZRKU-D_tC7QfC.js +90 -0
- package/packages/web/dist/assets/katex-C5jXJg4s.js +258 -0
- package/packages/web/dist/assets/kotlin-Cw-eF_bm.js +3 -0
- package/packages/web/dist/assets/lastTab-CU1zsF0j.js +2 -0
- package/packages/web/dist/assets/layout-Boy8vhSP.js +2 -0
- package/packages/web/dist/assets/linear-BzGHkF9K.js +2 -0
- package/packages/web/dist/assets/mermaid.core-DlbsYzE-.js +310 -0
- package/packages/web/dist/assets/mindmap-definition-RKZ34NQL-Bxeh-ToD.js +97 -0
- package/packages/web/dist/assets/ordinal-Cboi1Yqb.js +2 -0
- package/packages/web/dist/assets/php-CRdd4dfS.js +3 -0
- package/packages/web/dist/assets/pieDiagram-4H26LBE5-CDmilo8S.js +31 -0
- package/packages/web/dist/assets/python-kSlZHhJb.js +2 -0
- package/packages/web/dist/assets/quadrantDiagram-W4KKPZXB-D14-JyE9.js +8 -0
- package/packages/web/dist/assets/queued-LamEU0GH.js +2 -0
- package/packages/web/dist/assets/requirementDiagram-4Y6WPE33-CJ1PUGu-.js +85 -0
- package/packages/web/dist/assets/ruby-7T4ktxyv.js +2 -0
- package/packages/web/dist/assets/rust-HC3MBUhY.js +2 -0
- package/packages/web/dist/assets/sankeyDiagram-5OEKKPKP-BkUK73W0.js +41 -0
- package/packages/web/dist/assets/scss-DZLA7YXz.js +2 -0
- package/packages/web/dist/assets/sequenceDiagram-3UESZ5HK-sbcvsuLp.js +163 -0
- package/packages/web/dist/assets/sql-BC88IN-V.js +2 -0
- package/packages/web/dist/assets/stateDiagram-AJRCARHV-DV-7BdHK.js +2 -0
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BlkGdXKW.js +2 -0
- package/packages/web/dist/assets/swift-Dn8kKO_1.js +2 -0
- package/packages/web/dist/assets/timeline-definition-PNZ67QCA-C5su21G_.js +121 -0
- package/packages/web/dist/assets/typescript-C2FFdlUC.js +2 -0
- package/packages/web/dist/assets/vennDiagram-CIIHVFJN-JNkIJAaQ.js +35 -0
- package/packages/web/dist/assets/wardley-L42UT6IY-qcz6t2vR.js +174 -0
- package/packages/web/dist/assets/wardleyDiagram-YWT4CUSO-CMrOIZ-c.js +79 -0
- package/packages/web/dist/assets/xml-BXBhIUeX.js +2 -0
- package/packages/web/dist/assets/xychartDiagram-2RQKCTM6-C9q6Oy9u.js +8 -0
- package/packages/web/dist/assets/yaml-drsDnIKT.js +2 -0
- package/packages/web/dist/favicon.ico +0 -0
- package/packages/web/dist/fonts/inter-latin.woff2 +0 -0
- package/packages/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/packages/web/dist/icons/apple-touch-icon.png +0 -0
- package/packages/web/dist/icons/favicon-16.png +0 -0
- package/packages/web/dist/icons/favicon-32.png +0 -0
- package/packages/web/dist/icons/icon-192.png +0 -0
- package/packages/web/dist/icons/icon-512.png +0 -0
- package/packages/web/dist/icons/maskable-512.png +0 -0
- package/packages/web/dist/index.html +56 -0
- package/packages/web/dist/manifest.webmanifest +32 -0
- package/packages/web/dist/sw.js +139 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trigger routes (Epic T "Unify Triggers" / T3) — the per-project UNIFIED trigger
|
|
3
|
+
* management surface the Triggers tab drives, the sole successor that collapses the
|
|
4
|
+
* retired hooks + schedules REST/verbs onto ONE `TriggerService` over
|
|
5
|
+
* `project.yaml`'s single `triggers` block. A trigger is WHEN (`trigger`, a
|
|
6
|
+
* discriminated union `schedule|event|webhook`) + WHAT (`run`, the shared agent-run
|
|
7
|
+
* definition) + `enabled`. Each route delegates to TriggerService, which persists to
|
|
8
|
+
* project.yaml FIRST (the source of truth, re-armed on restart) THEN arms it,
|
|
9
|
+
* warning-but-not-failing if the runtime arm hiccups.
|
|
10
|
+
*
|
|
11
|
+
* Verb collapse (GG-3): enable/disable is NOT a separate route — it's `set` (PUT)
|
|
12
|
+
* with the `enabled` field flipped; new triggers default disabled.
|
|
13
|
+
*/
|
|
14
|
+
import type { FastifyInstance } from "fastify";
|
|
15
|
+
import type { RouteCtx } from "../route-context.js";
|
|
16
|
+
/**
|
|
17
|
+
* The workspace-scoped trigger surface: registered once per workspace mount
|
|
18
|
+
* (root and per project) — see `workspace-mount.ts`. Paths are relative to the
|
|
19
|
+
* mount.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerTriggerWorkspaceRoutes(app: FastifyInstance, ctx: RouteCtx): void;
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { keeperAgentName } from "../herdctl.js";
|
|
2
|
+
import { GRANTABLE_TOOLS } from "../hook-config.js";
|
|
3
|
+
import { buildTriggerRuntime } from "../trigger-runtime.js";
|
|
4
|
+
import { TRIGGER_EVENTS, TRIGGER_TYPES, isValidTriggerName, isCuratorTrigger, sanitizeTrigger, } from "../trigger-config.js";
|
|
5
|
+
import { sendProjectError } from "../route-errors.js";
|
|
6
|
+
/**
|
|
7
|
+
* The workspace-scoped trigger surface: registered once per workspace mount
|
|
8
|
+
* (root and per project) — see `workspace-mount.ts`. Paths are relative to the
|
|
9
|
+
* mount.
|
|
10
|
+
*/
|
|
11
|
+
export function registerTriggerWorkspaceRoutes(app, ctx) {
|
|
12
|
+
const { projects, herdctl, triggers, fireTrigger } = ctx;
|
|
13
|
+
const triggersGuard = (reply) => {
|
|
14
|
+
if (!triggers) {
|
|
15
|
+
reply.code(503).send({ error: "Trigger management is unavailable", code: "unavailable" });
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
20
|
+
// List a project's triggers (DTOs) + the picker catalog: the grantable tools, the
|
|
21
|
+
// events an event-trigger can fire on, and the trigger types — so the Triggers tab
|
|
22
|
+
// renders a precise capability + type picker without hard-coding them client-side
|
|
23
|
+
// (folds in the G4 `GRANTABLE_TOOLS` list).
|
|
24
|
+
app.get("/triggers", {
|
|
25
|
+
schema: {
|
|
26
|
+
tags: ["Triggers"],
|
|
27
|
+
summary: "List a project's triggers plus the picker catalog",
|
|
28
|
+
description: "Returns the project's trigger DTOs together with the Triggers-tab picker catalog. " +
|
|
29
|
+
"Success (200) is an object with `triggers` (array of trigger DTOs, each combining the " +
|
|
30
|
+
"`trigger` when-clause, the `run` definition, `enabled`, and derived fields like `agentName`), " +
|
|
31
|
+
"`grantableTools` (the tools an event/webhook trigger may be granted), `events` (the lifecycle " +
|
|
32
|
+
"events an event trigger can fire on), and `triggerTypes` (the supported trigger kinds).",
|
|
33
|
+
params: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
slug: { type: "string", description: "Project slug." },
|
|
37
|
+
},
|
|
38
|
+
required: ["slug"],
|
|
39
|
+
},
|
|
40
|
+
response: {
|
|
41
|
+
200: {
|
|
42
|
+
description: "The trigger DTO list plus picker catalog (`triggers`, `grantableTools`, `events`, `triggerTypes`).",
|
|
43
|
+
type: "object",
|
|
44
|
+
additionalProperties: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}, async (req, reply) => {
|
|
49
|
+
if (!triggersGuard(reply))
|
|
50
|
+
return reply;
|
|
51
|
+
try {
|
|
52
|
+
const list = await triggers.list(req.params.slug); // throws not_found
|
|
53
|
+
return {
|
|
54
|
+
triggers: list,
|
|
55
|
+
grantableTools: GRANTABLE_TOOLS,
|
|
56
|
+
events: [...TRIGGER_EVENTS],
|
|
57
|
+
triggerTypes: [...TRIGGER_TYPES],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return sendProjectError(reply, err);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Get one trigger by name (404 when the project declares no such trigger).
|
|
65
|
+
app.get("/triggers/:name", {
|
|
66
|
+
schema: {
|
|
67
|
+
tags: ["Triggers"],
|
|
68
|
+
summary: "Get one trigger by name",
|
|
69
|
+
description: "Fetches a single trigger's config by name. Success (200) is an object with a `trigger` field " +
|
|
70
|
+
"holding the trigger DTO (`trigger` when-clause + `run` + `enabled` + derived fields). Returns 404 " +
|
|
71
|
+
"when the project declares no trigger with that name.",
|
|
72
|
+
params: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
slug: { type: "string", description: "Project slug." },
|
|
76
|
+
name: { type: "string", description: "Trigger name." },
|
|
77
|
+
},
|
|
78
|
+
required: ["slug", "name"],
|
|
79
|
+
},
|
|
80
|
+
response: {
|
|
81
|
+
200: {
|
|
82
|
+
description: "An object with a `trigger` field holding the trigger DTO.",
|
|
83
|
+
type: "object",
|
|
84
|
+
additionalProperties: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}, async (req, reply) => {
|
|
89
|
+
if (!triggersGuard(reply))
|
|
90
|
+
return reply;
|
|
91
|
+
const { slug, name } = req.params;
|
|
92
|
+
try {
|
|
93
|
+
const trigger = await triggers.get(slug, name); // throws not_found (project)
|
|
94
|
+
if (!trigger) {
|
|
95
|
+
return reply.code(404).send({ error: `No such trigger: ${name}`, code: "not_found" });
|
|
96
|
+
}
|
|
97
|
+
return { trigger };
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return sendProjectError(reply, err);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// Create or replace one trigger (keyed by name). Persists to project.yaml, then
|
|
104
|
+
// arms it so it's immediately fireable. Enabling/disabling is this same route with
|
|
105
|
+
// `enabled` flipped (GG-3). The body is the FULL record `{ trigger, run, enabled }`
|
|
106
|
+
// — a full replace (unlike the self-MCP set_trigger, which patches partial edits).
|
|
107
|
+
app.put("/triggers/:name", {
|
|
108
|
+
schema: {
|
|
109
|
+
tags: ["Triggers"],
|
|
110
|
+
summary: "Create or replace one trigger",
|
|
111
|
+
description: "Creates or fully replaces a trigger keyed by name, persisting it to project.yaml then arming it. " +
|
|
112
|
+
"Enabling/disabling is this same route with `enabled` flipped. The body is the FULL trigger record: " +
|
|
113
|
+
"`trigger` is the WHEN clause — a discriminated union on `trigger.type` (`schedule` with `cron` xor " +
|
|
114
|
+
"`interval`; `event` with an `on` value such as `onArchive`/`afterTurn`; `webhook` with a `path`, " +
|
|
115
|
+
"shape-reserved) — `run` is the shared agent-run definition (prompt/promptFile, model, tools, etc.), " +
|
|
116
|
+
"and `enabled` is the on/off flag. Success (200) is an object with a `trigger` field holding the " +
|
|
117
|
+
"persisted trigger DTO. Returns 400 for an invalid name or malformed trigger definition.",
|
|
118
|
+
params: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
slug: { type: "string", description: "Project slug." },
|
|
122
|
+
name: { type: "string", description: "Trigger name." },
|
|
123
|
+
},
|
|
124
|
+
required: ["slug", "name"],
|
|
125
|
+
},
|
|
126
|
+
body: {
|
|
127
|
+
// Documentation-only (no validation/coercion): accept any/empty body.
|
|
128
|
+
type: ["object", "null"],
|
|
129
|
+
additionalProperties: true,
|
|
130
|
+
properties: {
|
|
131
|
+
trigger: {
|
|
132
|
+
description: "The WHEN clause: a discriminated union on `trigger.type` (`schedule`/`event`/`webhook`) with type-specific fields.",
|
|
133
|
+
},
|
|
134
|
+
run: {
|
|
135
|
+
description: "The WHAT clause: the shared agent-run definition (prompt/promptFile, model, tools, session mode, etc.).",
|
|
136
|
+
},
|
|
137
|
+
enabled: { description: "Whether the trigger is armed; new triggers default disabled." },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
response: {
|
|
141
|
+
200: {
|
|
142
|
+
description: "An object with a `trigger` field holding the persisted trigger DTO.",
|
|
143
|
+
type: "object",
|
|
144
|
+
additionalProperties: true,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}, async (req, reply) => {
|
|
149
|
+
if (!triggersGuard(reply))
|
|
150
|
+
return reply;
|
|
151
|
+
const { slug, name } = req.params;
|
|
152
|
+
if (!isValidTriggerName(name)) {
|
|
153
|
+
return reply.code(400).send({ error: `Invalid trigger name: ${name}`, code: "invalid" });
|
|
154
|
+
}
|
|
155
|
+
// Reject a malformed record early (bad discriminant, both/neither cron+interval,
|
|
156
|
+
// unknown event, both/neither prompt+promptFile) so the client gets a 400
|
|
157
|
+
// instead of the store's generic error; TriggerService.set re-validates.
|
|
158
|
+
if (!sanitizeTrigger(req.body)) {
|
|
159
|
+
return reply.code(400).send({ error: "Invalid trigger definition", code: "invalid" });
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const trigger = await triggers.set(slug, name, req.body); // throws not_found/invalid
|
|
163
|
+
return { trigger };
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return sendProjectError(reply, err);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// Delete one trigger. Removes it from project.yaml AND disarms it (an event
|
|
170
|
+
// trigger's agent is torn down; a schedule trigger's forwarded entry is dropped).
|
|
171
|
+
app.delete("/triggers/:name", {
|
|
172
|
+
schema: {
|
|
173
|
+
tags: ["Triggers"],
|
|
174
|
+
summary: "Delete one trigger",
|
|
175
|
+
description: "Removes a trigger from project.yaml AND disarms it (an event trigger's agent is torn down; a " +
|
|
176
|
+
"schedule trigger's forwarded entry is dropped). Success (200) is an object with `ok: true`, the " +
|
|
177
|
+
"`name`, and `removed` describing what was removed.",
|
|
178
|
+
params: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
slug: { type: "string", description: "Project slug." },
|
|
182
|
+
name: { type: "string", description: "Trigger name." },
|
|
183
|
+
},
|
|
184
|
+
required: ["slug", "name"],
|
|
185
|
+
},
|
|
186
|
+
response: {
|
|
187
|
+
200: {
|
|
188
|
+
description: "An object with `ok`, `name`, and `removed`.",
|
|
189
|
+
type: "object",
|
|
190
|
+
additionalProperties: true,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
}, async (req, reply) => {
|
|
195
|
+
if (!triggersGuard(reply))
|
|
196
|
+
return reply;
|
|
197
|
+
const { slug, name } = req.params;
|
|
198
|
+
try {
|
|
199
|
+
const removed = await triggers.remove(slug, name); // throws not_found (project)
|
|
200
|
+
return reply.code(200).send({ ok: true, name, removed });
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
return sendProjectError(reply, err);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
// Per-trigger RUNTIME state (Epic T follow-up / #327) — the live "last-run / next-run
|
|
207
|
+
// / status" the Triggers tab renders alongside each trigger's config. `TriggerDto`
|
|
208
|
+
// carries config only, so this JOINS it with herdctl runtime state the tab lost when
|
|
209
|
+
// the Schedules section folded in: the cron scheduler's `ScheduleInfo` (next-fire,
|
|
210
|
+
// status — schedule triggers) + job records (last run, per the E3/#268 pattern) for
|
|
211
|
+
// each trigger's own scoped agent. Served as its OWN endpoint (not folded into the
|
|
212
|
+
// config list) so the tab can POLL it cheaply without re-fetching the config + picker
|
|
213
|
+
// catalog. A static path segment — matched before `/:name` — so no trigger shadows it.
|
|
214
|
+
app.get("/triggers/runtime", {
|
|
215
|
+
schema: {
|
|
216
|
+
tags: ["Triggers"],
|
|
217
|
+
summary: "Live runtime state for a project's triggers",
|
|
218
|
+
description: "Per-trigger live last-run / next-run / status, joining each trigger's config with herdctl runtime " +
|
|
219
|
+
"state (the cron scheduler's next-fire + status for schedule triggers, plus job records for last run). " +
|
|
220
|
+
"This is a static path segment, matched BEFORE `/:name`, so no trigger name shadows it. It is served " +
|
|
221
|
+
"as its own pollable endpoint (separate from the config list). Success (200) is an object with a " +
|
|
222
|
+
"`runtime` field describing each trigger's live state.",
|
|
223
|
+
params: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
slug: { type: "string", description: "Project slug." },
|
|
227
|
+
},
|
|
228
|
+
required: ["slug"],
|
|
229
|
+
},
|
|
230
|
+
response: {
|
|
231
|
+
200: {
|
|
232
|
+
description: "An object with a `runtime` field describing each trigger's live last-run/next-run/status.",
|
|
233
|
+
type: "object",
|
|
234
|
+
additionalProperties: true,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
}, async (req, reply) => {
|
|
239
|
+
if (!triggersGuard(reply))
|
|
240
|
+
return reply;
|
|
241
|
+
try {
|
|
242
|
+
const project = await projects.get(req.params.slug); // throws not_found
|
|
243
|
+
const dtos = await triggers.list(project.slug);
|
|
244
|
+
// The agents a trigger's runs land under: the keeper (unscoped schedule
|
|
245
|
+
// triggers) + every trigger's own scoped `trigger-<slug>-<name>` agent.
|
|
246
|
+
const agents = [keeperAgentName(project.slug), ...dtos.map((d) => d.agentName)];
|
|
247
|
+
const [runs, schedules] = await Promise.all([
|
|
248
|
+
herdctl.listRunsForAgents(agents).catch(() => []),
|
|
249
|
+
herdctl.listAgentSchedules(project).catch(() => []),
|
|
250
|
+
]);
|
|
251
|
+
return { runtime: buildTriggerRuntime(dtos, runs, schedules, project.slug) };
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
return sendProjectError(reply, err);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
// Fire a trigger NOW (Epic T follow-up / #327) — "Run now". Runs it through the same
|
|
258
|
+
// hub path a cron / event fire uses, so the resulting chat is a first-class, badged
|
|
259
|
+
// run (indistinguishable from an automatic fire). Fires ANY trigger type regardless
|
|
260
|
+
// of its `enabled` flag — a manual run is deliberate (mirrors the schedule DD-1 rule).
|
|
261
|
+
// 503 when the trigger fire entrypoint isn't wired (tests may omit it); 404 for an
|
|
262
|
+
// unknown trigger; 502 if the fire started no chat. Responds 202 with the session id.
|
|
263
|
+
app.post("/triggers/:name/run", {
|
|
264
|
+
schema: {
|
|
265
|
+
tags: ["Triggers"],
|
|
266
|
+
summary: "Run a trigger now",
|
|
267
|
+
description: "Fires a trigger immediately (\"Run now\") through the same hub path a cron/event fire uses, producing " +
|
|
268
|
+
"a first-class badged run. Fires any trigger type regardless of its `enabled` flag. Success is 202 with " +
|
|
269
|
+
"an object containing `ok: true`, the `name`, and the started `sessionId`. Returns 503 when firing is " +
|
|
270
|
+
"unavailable, 404 for an unknown trigger, 409 for the post-turn curator trigger (not runnable on demand), " +
|
|
271
|
+
"and 502 if the fire started no chat.",
|
|
272
|
+
params: {
|
|
273
|
+
type: "object",
|
|
274
|
+
properties: {
|
|
275
|
+
slug: { type: "string", description: "Project slug." },
|
|
276
|
+
name: { type: "string", description: "Trigger name." },
|
|
277
|
+
},
|
|
278
|
+
required: ["slug", "name"],
|
|
279
|
+
},
|
|
280
|
+
response: {
|
|
281
|
+
202: {
|
|
282
|
+
description: "An object with `ok`, `name`, and the started `sessionId`.",
|
|
283
|
+
type: "object",
|
|
284
|
+
additionalProperties: true,
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
}, async (req, reply) => {
|
|
289
|
+
if (!triggersGuard(reply))
|
|
290
|
+
return reply;
|
|
291
|
+
const { slug, name } = req.params;
|
|
292
|
+
if (!fireTrigger) {
|
|
293
|
+
return reply.code(503).send({ error: "Trigger firing is unavailable", code: "unavailable" });
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const project = await projects.get(slug); // throws not_found
|
|
297
|
+
const rec = project.triggers?.[name];
|
|
298
|
+
if (!rec) {
|
|
299
|
+
return reply.code(404).send({ error: `No such trigger: ${name}`, code: "not_found" });
|
|
300
|
+
}
|
|
301
|
+
// The post-turn CURATOR (the folded-in sweeper — any `event`/`afterTurn` trigger,
|
|
302
|
+
// T5) is NOT a generic fireable trigger: it has no scoped `trigger-<slug>-<name>`
|
|
303
|
+
// agent (it runs via SweepService on the `afterTurn` event, needing a just-
|
|
304
|
+
// completed turn's context), so the generic fire path can't run it. Reject it with
|
|
305
|
+
// a clear 409 rather than letting the fire fail opaquely as a 502.
|
|
306
|
+
if (isCuratorTrigger(rec)) {
|
|
307
|
+
return reply.code(409).send({
|
|
308
|
+
error: "The post-turn curator trigger runs automatically after each turn and can't be run on demand.",
|
|
309
|
+
code: "not_runnable",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
const sessionId = await fireTrigger(slug, name);
|
|
313
|
+
if (!sessionId) {
|
|
314
|
+
return reply
|
|
315
|
+
.code(502)
|
|
316
|
+
.send({ error: "Trigger fire did not start a chat", code: "trigger_failed" });
|
|
317
|
+
}
|
|
318
|
+
return reply.code(202).send({ ok: true, name, sessionId });
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
return sendProjectError(reply, err);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace-mount — the seam that makes the root workspace and a project
|
|
3
|
+
* workspace run literally the same route handlers.
|
|
4
|
+
*
|
|
5
|
+
* A workspace is identified by its path relative to `projectsRoot`, and the root
|
|
6
|
+
* workspace's key is the empty string (see `project-paths.ts`). An empty string
|
|
7
|
+
* cannot ride in a URL path segment — `/api/projects//chats` does not match — so
|
|
8
|
+
* rather than smuggle a sentinel like `__root` into the wire, the
|
|
9
|
+
* workspace-scoped routes are a Fastify plugin **registered twice**:
|
|
10
|
+
*
|
|
11
|
+
* | mount | workspace key |
|
|
12
|
+
* |-------------------------|------------------|
|
|
13
|
+
* | `/api/root` | `""` |
|
|
14
|
+
* | `/api/projects/:slug` | `params.slug` |
|
|
15
|
+
*
|
|
16
|
+
* **This is the architectural guarantee, not a convenience.** "The root behaves
|
|
17
|
+
* exactly like a project" is true by construction — same handlers, same schemas,
|
|
18
|
+
* same error paths — instead of being a property someone has to remember to
|
|
19
|
+
* preserve. The previous design made the root a project with a reserved slug and
|
|
20
|
+
* then had to keep two resolution branches in step; one of them was missed and
|
|
21
|
+
* every root file route 404'd.
|
|
22
|
+
*
|
|
23
|
+
* Handlers are untouched by the split: they still read `req.params.slug`. On the
|
|
24
|
+
* root mount there is no `:slug` segment to match, so {@link rootParamsHook}
|
|
25
|
+
* injects `slug: ""` in `onRequest` — which runs BEFORE schema validation, so
|
|
26
|
+
* the existing `required: ["slug"]` params schemas keep validating unchanged.
|
|
27
|
+
* (Verified against Fastify directly rather than assumed.)
|
|
28
|
+
*/
|
|
29
|
+
import type { FastifyInstance } from "fastify";
|
|
30
|
+
/** URL prefix for the root workspace's scoped routes. */
|
|
31
|
+
export declare const ROOT_MOUNT_PREFIX = "/api/root";
|
|
32
|
+
/** URL prefix (parametric) for a project workspace's scoped routes. */
|
|
33
|
+
export declare const PROJECT_MOUNT_PREFIX = "/api/projects/:slug";
|
|
34
|
+
/**
|
|
35
|
+
* Give the root mount the `slug` param its handlers and schemas expect.
|
|
36
|
+
*
|
|
37
|
+
* `onRequest` is deliberate: it is the earliest lifecycle hook, and in
|
|
38
|
+
* particular it runs before params validation, so a schema declaring
|
|
39
|
+
* `required: ["slug"]` still passes on a path that has no `:slug` segment.
|
|
40
|
+
*/
|
|
41
|
+
export declare function rootParamsHook(app: FastifyInstance): void;
|
|
42
|
+
/**
|
|
43
|
+
* Register one workspace-scoped route group at both mounts.
|
|
44
|
+
*
|
|
45
|
+
* `group` is a plain Fastify plugin that declares its paths RELATIVE to the
|
|
46
|
+
* workspace (e.g. `/chats/:sessionId`, not `/api/projects/:slug/chats/:sessionId`).
|
|
47
|
+
*/
|
|
48
|
+
export declare function mountWorkspaceRoutes(app: FastifyInstance, group: (app: FastifyInstance) => void): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** URL prefix for the root workspace's scoped routes. */
|
|
2
|
+
export const ROOT_MOUNT_PREFIX = "/api/root";
|
|
3
|
+
/** URL prefix (parametric) for a project workspace's scoped routes. */
|
|
4
|
+
export const PROJECT_MOUNT_PREFIX = "/api/projects/:slug";
|
|
5
|
+
/**
|
|
6
|
+
* Give the root mount the `slug` param its handlers and schemas expect.
|
|
7
|
+
*
|
|
8
|
+
* `onRequest` is deliberate: it is the earliest lifecycle hook, and in
|
|
9
|
+
* particular it runs before params validation, so a schema declaring
|
|
10
|
+
* `required: ["slug"]` still passes on a path that has no `:slug` segment.
|
|
11
|
+
*/
|
|
12
|
+
export function rootParamsHook(app) {
|
|
13
|
+
app.addHook("onRequest", (req, _reply, done) => {
|
|
14
|
+
req.params = { ...(req.params ?? {}), slug: "" };
|
|
15
|
+
done();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Register one workspace-scoped route group at both mounts.
|
|
20
|
+
*
|
|
21
|
+
* `group` is a plain Fastify plugin that declares its paths RELATIVE to the
|
|
22
|
+
* workspace (e.g. `/chats/:sessionId`, not `/api/projects/:slug/chats/:sessionId`).
|
|
23
|
+
*/
|
|
24
|
+
export async function mountWorkspaceRoutes(app, group) {
|
|
25
|
+
await app.register(async (scoped) => {
|
|
26
|
+
rootParamsHook(scoped);
|
|
27
|
+
group(scoped);
|
|
28
|
+
}, { prefix: ROOT_MOUNT_PREFIX });
|
|
29
|
+
await app.register(async (scoped) => {
|
|
30
|
+
group(scoped);
|
|
31
|
+
}, { prefix: PROJECT_MOUNT_PREFIX });
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST routes — composition root.
|
|
3
|
+
*
|
|
4
|
+
* The Fastify REST surface is one `registerRoutes(app, deps)` that builds the
|
|
5
|
+
* shared route context once (see {@link buildRouteContext}) and then hangs each
|
|
6
|
+
* per-group cluster off the app via a `registerXRoutes(app, ctx)` call. The
|
|
7
|
+
* handlers themselves live in the sibling group modules; the pure DTO/HTTP
|
|
8
|
+
* helpers live in `chat-dto.ts` / `http-bytes.ts` / `route-errors.ts` (issue
|
|
9
|
+
* #403 — break up oversized files). Each group registers directly off `app`
|
|
10
|
+
* (no Fastify plugins), preserving the original wiring shape.
|
|
11
|
+
*
|
|
12
|
+
* The route map, by group:
|
|
13
|
+
* meta — /api/transcription, /api/transcribe, /api/me, /api/health,
|
|
14
|
+
* /api/instance-config, /api/models, /api/fleet,
|
|
15
|
+
* /api/chat-files/:id, project chat upload
|
|
16
|
+
* git — /api/git[/push|/github/*], project git status/diff/commit
|
|
17
|
+
* projects — projects CRUD + promote, pins, files/changelog/overview/commands
|
|
18
|
+
* triggers — project triggers CRUD + runtime + run-now
|
|
19
|
+
* chats — workspace chat lifecycle (list/runs/usage/create/messages/
|
|
20
|
+
* subagents/context/delete/rename/fork/archive/star/seen/promote)
|
|
21
|
+
*/
|
|
22
|
+
import type { FastifyInstance } from "fastify";
|
|
23
|
+
import { type RouteDeps } from "./route-context.js";
|
|
24
|
+
export type { RouteDeps } from "./route-context.js";
|
|
25
|
+
export { parseRangeHeader } from "./http-bytes.js";
|
|
26
|
+
export declare function registerRoutes(app: FastifyInstance, deps: RouteDeps): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildRouteContext } from "./route-context.js";
|
|
2
|
+
import { registerMetaRoutes, registerMetaWorkspaceRoutes } from "./routes/meta.js";
|
|
3
|
+
import { registerGitRoutes, registerGitWorkspaceRoutes } from "./routes/git.js";
|
|
4
|
+
import { registerProjectRoutes, registerProjectWorkspaceRoutes } from "./routes/projects.js";
|
|
5
|
+
import { registerTriggerWorkspaceRoutes } from "./routes/triggers.js";
|
|
6
|
+
import { registerChatWorkspaceRoutes } from "./routes/chats.js";
|
|
7
|
+
import { registerMcpRoutes } from "./routes/mcp.js";
|
|
8
|
+
import { mountWorkspaceRoutes } from "./routes/workspace-mount.js";
|
|
9
|
+
export { parseRangeHeader } from "./http-bytes.js";
|
|
10
|
+
export async function registerRoutes(app, deps) {
|
|
11
|
+
const ctx = buildRouteContext(deps);
|
|
12
|
+
registerMetaRoutes(app, ctx);
|
|
13
|
+
registerGitRoutes(app, ctx);
|
|
14
|
+
registerProjectRoutes(app, ctx);
|
|
15
|
+
// The workspace-scoped half of every group, mounted twice: `/api/root` (the
|
|
16
|
+
// root workspace, key `""`) and `/api/projects/:slug` (a project). Same
|
|
17
|
+
// handlers both times — that identity is the point, not an optimisation.
|
|
18
|
+
await mountWorkspaceRoutes(app, (scoped) => {
|
|
19
|
+
registerProjectWorkspaceRoutes(scoped, ctx);
|
|
20
|
+
registerChatWorkspaceRoutes(scoped, ctx);
|
|
21
|
+
registerGitWorkspaceRoutes(scoped, ctx);
|
|
22
|
+
registerTriggerWorkspaceRoutes(scoped, ctx);
|
|
23
|
+
registerMetaWorkspaceRoutes(scoped, ctx);
|
|
24
|
+
});
|
|
25
|
+
// #312 M1: the external Management API gate. Registered unconditionally (it
|
|
26
|
+
// answers 404 when unconfigured) so an unconfigured `/mcp` can never fall
|
|
27
|
+
// through to the SPA not-found handler and be served the app shell.
|
|
28
|
+
registerMcpRoutes(app, ctx);
|
|
29
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every origin, as VALUES — the single source of truth for both the type and the
|
|
3
|
+
* runtime allowlist {@link coerce} validates against.
|
|
4
|
+
*
|
|
5
|
+
* Declared this way round on purpose (#588). It used to be a hand-written union
|
|
6
|
+
* with a separate `readonly TurnOrigin[]` list, and `readonly TurnOrigin[]` only
|
|
7
|
+
* asserts that each element IS an origin — never that every origin is an
|
|
8
|
+
* element. So when the union grew, the allowlist silently lagged: `coerce()`
|
|
9
|
+
* returned null for the new member, and every chat carrying it lost BOTH its
|
|
10
|
+
* badge and its parent edge on read, with no error anywhere. Deriving the type
|
|
11
|
+
* from the list makes that impossible rather than merely documented.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ORIGINS: readonly ["human", "scheduled", "spawned", "hook", "adopted"];
|
|
14
|
+
/**
|
|
15
|
+
* How a turn/chat came to exist — the provenance dimension #267 badges on.
|
|
16
|
+
*
|
|
17
|
+
* `adopted` (#588) is the odd one out: the chat was not created by Paddock at
|
|
18
|
+
* all. It is a terminal `claude` session the user ran themselves and later
|
|
19
|
+
* imported, so it has no creating turn, no parent, and nothing ever "ran" it
|
|
20
|
+
* here. It is therefore a ROOT and it is ATTENDED — see `ORIGIN_IS_ROOT` in
|
|
21
|
+
* ./chat-dto.ts and `ORIGIN_IS_UNATTENDED` in ./runs.ts, both of which are
|
|
22
|
+
* `Record<TurnOrigin, boolean>` so that adding a member here forces a
|
|
23
|
+
* DECISION at each of them instead of a silent fallthrough.
|
|
24
|
+
*/
|
|
25
|
+
export type TurnOrigin = (typeof ORIGINS)[number];
|
|
26
|
+
/** A chat's provenance marker: how it was created + how deep in a spawn tree. */
|
|
27
|
+
export interface RunProvenance {
|
|
28
|
+
origin: TurnOrigin;
|
|
29
|
+
/** Spawn hops from the human/scheduled root (0 = root). #262 gates on this. */
|
|
30
|
+
depth: number;
|
|
31
|
+
/**
|
|
32
|
+
* The chat that CREATED this one — the parent edge the nested chat list draws.
|
|
33
|
+
* `depth` alone says a chat was spawned and how deep, but never BY WHOM, so the
|
|
34
|
+
* list could badge a child yet not file it under its parent.
|
|
35
|
+
*
|
|
36
|
+
* Absent for roots, for chats created before this field existed, and for the
|
|
37
|
+
* external `/mcp` transport (no calling chat). Historical chats are backfilled
|
|
38
|
+
* at read time from MessageProvenanceStore — see `parentChat()` there.
|
|
39
|
+
*/
|
|
40
|
+
parentSessionId?: string;
|
|
41
|
+
/** The parent's project slug — a parent can live in ANOTHER project. */
|
|
42
|
+
parentProject?: string;
|
|
43
|
+
}
|
|
44
|
+
/** A reference to the chat that created another one. */
|
|
45
|
+
export interface ParentChatRef {
|
|
46
|
+
project: string;
|
|
47
|
+
sessionId: string;
|
|
48
|
+
}
|
|
49
|
+
/** The root of any chat tree a human starts: origin `human`, depth 0. */
|
|
50
|
+
export declare const HUMAN_ROOT: RunProvenance;
|
|
51
|
+
/**
|
|
52
|
+
* A schedule-fired chat: origin `scheduled`, depth 0. A cron is a root trigger,
|
|
53
|
+
* exactly like a human — there is no scheduler wired yet (that's Epic D), but the
|
|
54
|
+
* marker path accepts it now.
|
|
55
|
+
*/
|
|
56
|
+
export declare const SCHEDULED_ROOT: RunProvenance;
|
|
57
|
+
/**
|
|
58
|
+
* An IMPORTED terminal chat (#588): origin `adopted`, depth 0.
|
|
59
|
+
*
|
|
60
|
+
* Depth 0 because the session was never spawned by anything in this instance —
|
|
61
|
+
* the user ran it themselves in a terminal and Paddock only adopted the
|
|
62
|
+
* transcript. Stamped with `stampIfAbsent`, never `stamp`: if a chat somehow
|
|
63
|
+
* already carries a marker, whatever created it here knows more about its
|
|
64
|
+
* origin than the import does.
|
|
65
|
+
*/
|
|
66
|
+
export declare const ADOPTED_ROOT: RunProvenance;
|
|
67
|
+
/**
|
|
68
|
+
* The provenance a child inherits when `parent` spawns it via a self-MCP write
|
|
69
|
+
* tool: origin becomes `spawned` and depth is one deeper. So a fan-out tree's
|
|
70
|
+
* depth is always the number of spawn hops from its human/scheduled root, which
|
|
71
|
+
* is exactly what #262 bounds with `maxSpawnDepth`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function childOf(parent: RunProvenance, parentRef?: ParentChatRef | null): RunProvenance;
|
|
74
|
+
export declare class RunProvenanceStore {
|
|
75
|
+
private readonly stateFile;
|
|
76
|
+
/** In-memory map of sessionId -> provenance (loaded once, written through). */
|
|
77
|
+
private state;
|
|
78
|
+
/**
|
|
79
|
+
* The in-flight load, cached so concurrent first-callers share ONE read.
|
|
80
|
+
* Caching only the resolved `state` would let several stamps that begin before
|
|
81
|
+
* the first `fs.readFile` resolves each build their own map and race on the
|
|
82
|
+
* assignment — a lost update (last writer's map wins, dropping the others'
|
|
83
|
+
* keys). Reachable when a keeper spawns several children right after startup.
|
|
84
|
+
*/
|
|
85
|
+
private loadPromise;
|
|
86
|
+
/** Serialises concurrent writes so the file never interleaves. */
|
|
87
|
+
private writing;
|
|
88
|
+
constructor(dataDir: string);
|
|
89
|
+
/** Load the persisted map (lazily, deduped; tolerant of a missing/corrupt file). */
|
|
90
|
+
private ensureLoaded;
|
|
91
|
+
/** This chat's recorded provenance, or undefined if none. Non-throwing. */
|
|
92
|
+
get(sessionId: string): Promise<RunProvenance | undefined>;
|
|
93
|
+
/**
|
|
94
|
+
* Record a chat's provenance. Idempotent (a repeat of the same marker is a
|
|
95
|
+
* no-op), and a blank/unsafe session id or malformed marker is ignored. Callers
|
|
96
|
+
* stamp a chat when they CREATE it, so this is a fresh key in practice; it does
|
|
97
|
+
* overwrite if called again (unlike {@link stampIfAbsent}).
|
|
98
|
+
*/
|
|
99
|
+
stamp(sessionId: string, provenance: RunProvenance): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Record provenance ONLY if this chat has none yet — never clobbers an existing
|
|
102
|
+
* marker. Used on resume/wake paths (a scheduler wake resumes a chat that may
|
|
103
|
+
* already have been created by a human or a spawn), so its creation provenance
|
|
104
|
+
* survives.
|
|
105
|
+
*/
|
|
106
|
+
stampIfAbsent(sessionId: string, provenance: RunProvenance): Promise<void>;
|
|
107
|
+
/** Write-through, serialised so overlapping stamps can't corrupt the file. */
|
|
108
|
+
private persist;
|
|
109
|
+
}
|