@edspencer/paddock 0.0.1 → 0.56.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 +512 -0
- package/packages/server/dist/config.js +593 -0
- package/packages/server/dist/curation-config.d.ts +55 -0
- package/packages/server/dist/curation-config.js +64 -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 +572 -0
- package/packages/server/dist/herdctl.js +1218 -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 +135 -0
- package/packages/server/dist/instance-config.js +345 -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-4cQGTj9Y.js +2 -0
- package/packages/web/dist/assets/MarkdownRenderer-CJZFLj_8.js +30 -0
- package/packages/web/dist/assets/ProjectRedirect-BHwVbbqB.js +2 -0
- package/packages/web/dist/assets/ProjectView-BOvuAITg.js +13 -0
- package/packages/web/dist/assets/ProjectsGrid-BCojZOGY.js +2 -0
- package/packages/web/dist/assets/arc-BVyxvnkl.js +2 -0
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-Cd9Puqg1.js +37 -0
- package/packages/web/dist/assets/bash-I8pq0VWm.js +2 -0
- package/packages/web/dist/assets/blockDiagram-GPEHLZMM-B_AQt_Rx.js +133 -0
- package/packages/web/dist/assets/c-D2F1sQUL.js +2 -0
- package/packages/web/dist/assets/c4Diagram-AAUBKEIU-7e0TQtXB.js +11 -0
- package/packages/web/dist/assets/channel-C_1aISNn.js +2 -0
- package/packages/web/dist/assets/chunk-2J33WTMH-Dz4WZmUZ.js +2 -0
- package/packages/web/dist/assets/chunk-4BX2VUAB-DVWYldo6.js +2 -0
- package/packages/web/dist/assets/chunk-55IACEB6-BIMrizQd.js +2 -0
- package/packages/web/dist/assets/chunk-727SXJPM-CL2q9fF1.js +207 -0
- package/packages/web/dist/assets/chunk-AQP2D5EJ-BSq3HlQ9.js +232 -0
- package/packages/web/dist/assets/chunk-FMBD7UC4-Bgt64pLE.js +16 -0
- package/packages/web/dist/assets/chunk-ND2GUHAM-C8Pr2A38.js +2 -0
- package/packages/web/dist/assets/chunk-QZHKN3VN-BAOqh-MN.js +2 -0
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-B8JE7cHJ.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-B8JE7cHJ.js +2 -0
- package/packages/web/dist/assets/core-lJeGACQg.js +4 -0
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-BF4tameA.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-DdsLM6ql.js +5 -0
- package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +2 -0
- package/packages/web/dist/assets/diagram-2AECGRRQ-BQ78S_RM.js +44 -0
- package/packages/web/dist/assets/diagram-5GNKFQAL-CAKE6nHE.js +11 -0
- package/packages/web/dist/assets/diagram-KO2AKTUF-fEvHRXgr.js +4 -0
- package/packages/web/dist/assets/diagram-LMA3HP47-CtWsgeSm.js +25 -0
- package/packages/web/dist/assets/diagram-OG6HWLK6-Buog8k7_.js +25 -0
- package/packages/web/dist/assets/erDiagram-TEJ5UH35-CWvVVPZS.js +86 -0
- package/packages/web/dist/assets/flowDiagram-I6XJVG4X-DsCGXtp4.js +163 -0
- package/packages/web/dist/assets/format-DamJQ9LO.js +2 -0
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-CsdwIvvC.js +293 -0
- package/packages/web/dist/assets/gitGraphDiagram-PVQCEYII-hZvrX1sD.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-CGbZdrm8.js +71 -0
- package/packages/web/dist/assets/index-DJDQ210-.css +1 -0
- package/packages/web/dist/assets/infoDiagram-5YYISTIA-nE_Vtz_l.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-2Nlqs0oH.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-mjU1fMc4.js +140 -0
- package/packages/web/dist/assets/json-DIYVocXf.js +2 -0
- package/packages/web/dist/assets/kanban-definition-UN3LZRKU-C_9xNaa3.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-BxKd91ho.js +2 -0
- package/packages/web/dist/assets/mermaid.core-C-CuhD8I.js +310 -0
- package/packages/web/dist/assets/mindmap-definition-RKZ34NQL-Bs6yfcZv.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-DNdqX7O5.js +31 -0
- package/packages/web/dist/assets/python-kSlZHhJb.js +2 -0
- package/packages/web/dist/assets/quadrantDiagram-W4KKPZXB-ZMJl9OdZ.js +8 -0
- package/packages/web/dist/assets/queued-LamEU0GH.js +2 -0
- package/packages/web/dist/assets/requirementDiagram-4Y6WPE33-RmxMdm2q.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-OAAhWcfK.js +41 -0
- package/packages/web/dist/assets/scss-DZLA7YXz.js +2 -0
- package/packages/web/dist/assets/sequenceDiagram-3UESZ5HK-BWF7rCyN.js +163 -0
- package/packages/web/dist/assets/sql-BC88IN-V.js +2 -0
- package/packages/web/dist/assets/stateDiagram-AJRCARHV-M3b_zqqo.js +2 -0
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CI7saFDa.js +2 -0
- package/packages/web/dist/assets/swift-Dn8kKO_1.js +2 -0
- package/packages/web/dist/assets/timeline-definition-PNZ67QCA-BpOCh63N.js +121 -0
- package/packages/web/dist/assets/typescript-C2FFdlUC.js +2 -0
- package/packages/web/dist/assets/vennDiagram-CIIHVFJN-D_iPH45Q.js +35 -0
- package/packages/web/dist/assets/wardley-L42UT6IY-DVD9I2Om.js +174 -0
- package/packages/web/dist/assets/wardleyDiagram-YWT4CUSO-Bn1IKwjn.js +79 -0
- package/packages/web/dist/assets/xml-BXBhIUeX.js +2 -0
- package/packages/web/dist/assets/xychartDiagram-2RQKCTM6-DwtI0bkR.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,98 @@
|
|
|
1
|
+
import type { PaddockConfig } from "./config.js";
|
|
2
|
+
import type { Project } from "./projects.js";
|
|
3
|
+
import { type PaddockTrigger } from "./trigger-config.js";
|
|
4
|
+
/**
|
|
5
|
+
* A project's keeper agent config. Inherits the fleet `defaults` (runtime,
|
|
6
|
+
* max_turns, permission_mode, allowed/denied tools) via addAgent's deep-merge;
|
|
7
|
+
* only project-specific fields are set here.
|
|
8
|
+
*
|
|
9
|
+
* Model resolution: `modelOverride` (a per-chat override) wins, else the
|
|
10
|
+
* project's persisted `model`, else the keeper default (Opus).
|
|
11
|
+
*
|
|
12
|
+
* System prompt: by default (`nativeSystemPrompt`, issue #176) we set NO
|
|
13
|
+
* `system_prompt`, so Claude Code's full default coding prompt applies — the SDK
|
|
14
|
+
* runtime falls back to the `claude_code` preset and the CLI runtime passes no
|
|
15
|
+
* `--system-prompt`, which amount to the same thing. It applies together with
|
|
16
|
+
* the project's CLAUDE.md hierarchy — the canonical instance-wide
|
|
17
|
+
* `<projectsRoot>/CLAUDE.md`
|
|
18
|
+
* (auto-loaded via the cwd walk-up, since a project dir is a child of the
|
|
19
|
+
* projects root) plus a per-project CLAUDE.md. This is its own decision (issue
|
|
20
|
+
* #176): an instance with no CLAUDE.md files can opt back into the terse replace
|
|
21
|
+
* prompt below with `PADDOCK_NATIVE_PROMPT=false`.
|
|
22
|
+
*
|
|
23
|
+
* This is the reading #512 settled on, and the reason it is `projectsRoot` and
|
|
24
|
+
* not `<dataDir>`: `projectsRoot` IS the instance's backing repo, so the file is
|
|
25
|
+
* version-controlled. Since #516 the ROOT project reaches it the same way —
|
|
26
|
+
* its cwd IS `projectsRoot`, so the walk-up needs no special-casing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildAgentConfig(cfg: PaddockConfig, project: Project, modelOverride?: string): Record<string, unknown> & {
|
|
29
|
+
name: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The sweeper's working directory: a dedicated per-project dir under the data
|
|
33
|
+
* root, deliberately NOT the project dir (issue #548).
|
|
34
|
+
*
|
|
35
|
+
* A CLI agent's working directory is what Claude Code encodes into its
|
|
36
|
+
* transcript path, so two agents sharing a cwd share ONE session directory. The
|
|
37
|
+
* sweeper's cwd used to be `project.dir`, which for a notebook project equals the
|
|
38
|
+
* keeper's `project.workingDir` — so keeper and sweeper wrote their transcripts
|
|
39
|
+
* into the same `.chats/`. herdctl identifies a freshly-spawned session by
|
|
40
|
+
* set-difference against a pre-spawn directory snapshot, which is immune to a
|
|
41
|
+
* co-located agent *appending* to its own session but NOT to one *creating* a new
|
|
42
|
+
* file: whichever brand-new `*.jsonl` appears first is claimed as "ours". A sweep
|
|
43
|
+
* is scheduled after every keeper turn, so the sweeper's spawn raced the next
|
|
44
|
+
* keeper turn and a user's chat could be bound to the sweeper's hidden curation
|
|
45
|
+
* transcript (wrong reply streamed, wrong transcript resumed, chat missing from
|
|
46
|
+
* the project's list).
|
|
47
|
+
*
|
|
48
|
+
* The sweeper is TOOL-LESS, so its cwd is inert — nothing about curation depends
|
|
49
|
+
* on it (SweepService reads the project's files itself and inlines them in the
|
|
50
|
+
* prompt, then writes the results itself). Giving it its own directory removes
|
|
51
|
+
* the shared directory, and with it the whole class of collisions, structurally.
|
|
52
|
+
*
|
|
53
|
+
* Kept OUTSIDE `projectsRoot` on purpose: core's discovery unions every
|
|
54
|
+
* `~/.claude/projects/*` bucket whose decoded path is a strict *descendant* of an
|
|
55
|
+
* agent's cwd, and the root workspace's cwd IS `projectsRoot`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function sweeperWorkingDir(cfg: PaddockConfig, slug: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* A project's sweeper (curator) agent config. TOOL-LESS: the sweeper has NO
|
|
60
|
+
* tools (`allowed_tools: []`) — it never reads or writes files. Instead it
|
|
61
|
+
* RETURNS the curated content as plain assistant text in marked sections
|
|
62
|
+
* (OVERVIEW / CHANGELOG / optional CLAUDE, issue #177); SweepService parses
|
|
63
|
+
* that text and writes OVERVIEW.md / CHANGELOG.md / CLAUDE.md itself.
|
|
64
|
+
*
|
|
65
|
+
* This is cheaper and far more predictable than letting a Haiku agent drive
|
|
66
|
+
* file edits: no tool-loop turns, no partial writes, no permission_mode /
|
|
67
|
+
* denied_tools to reason about (all irrelevant with zero tools).
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildSweeperConfig(cfg: PaddockConfig, project: Project): Record<string, unknown> & {
|
|
70
|
+
name: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* A trigger's scoped herdctl agent config (Epic T) — the unified successor to
|
|
74
|
+
* {@link hookAgentConfig}. A trigger that {@link triggerRunsOnOwnAgent} (every event
|
|
75
|
+
* trigger; a schedule trigger with a non-empty `run.tools` allow-list — T2) is
|
|
76
|
+
* registered as its OWN agent `trigger-<slug>-<name>` whose tool config
|
|
77
|
+
* (`allowed_tools`/`permission_mode`/`model`/`max_turns`, projected by
|
|
78
|
+
* {@link triggerToAgentToolConfig} from the trigger's `run`) IS its capability set.
|
|
79
|
+
* Runs in the project's WORKING dir (so a trigger's Bash/Write act on the same tree
|
|
80
|
+
* the keeper does). A tool-less trigger gets `allowed_tools: []` and can only return text.
|
|
81
|
+
*/
|
|
82
|
+
export declare function buildTriggerConfig(cfg: PaddockConfig, project: Project, triggerName: string, trigger: PaddockTrigger): Record<string, unknown> & {
|
|
83
|
+
name: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Write the minimal herdctl.yaml the FleetManager boots from: a fleet block
|
|
87
|
+
* plus the fleet-wide `defaults` (deep-merged into agents added at runtime),
|
|
88
|
+
* and ZERO agents. All agents are registered programmatically via
|
|
89
|
+
* `fleet.addAgent(...)` in init() — paddock no longer generates per-agent
|
|
90
|
+
* yaml files or calls `reload()`.
|
|
91
|
+
*
|
|
92
|
+
* The `fleet` block is strict (name/description only). `defaults` are deep-
|
|
93
|
+
* merged into each agent by addAgent (mergeDefaults defaults to true), so agents
|
|
94
|
+
* inherit model/max_turns/permission_mode/allowed+denied_tools from here. NOT
|
|
95
|
+
* `runtime`: core's `DefaultsSchema` has no such key, so it is stripped at parse
|
|
96
|
+
* and every agent sets its own.
|
|
97
|
+
*/
|
|
98
|
+
export declare function ensureConfigFile(cfg: PaddockConfig): Promise<void>;
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* herdctl-agent-config — the pure builders that project a Paddock `Project` (+
|
|
3
|
+
* config) into the herdctl agent-config objects `FleetManager.addAgent` accepts,
|
|
4
|
+
* plus {@link ensureConfigFile} which writes the minimal boot herdctl.yaml.
|
|
5
|
+
*
|
|
6
|
+
* Split out of {@link HerdctlService} (issue #403): every function here reads
|
|
7
|
+
* only its arguments (the `PaddockConfig` bag + the project/trigger) and the
|
|
8
|
+
* pure constants from {@link ./herdctl-agent-names.js} — no `fleet`/live-session
|
|
9
|
+
* state — so they are independently readable and testable. {@link HerdctlService}
|
|
10
|
+
* keeps thin private wrappers that delegate here (so `this.cfg` stays the single
|
|
11
|
+
* source of config and the existing private-method test seams are unchanged).
|
|
12
|
+
*/
|
|
13
|
+
import { promises as fs } from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import YAML from "yaml";
|
|
16
|
+
import { DEFAULT_MODEL, SWEEPER_DEFAULT_MODEL, DEFAULT_PERMISSION_MODE, DEFAULT_MAX_TURNS, } from "./models.js";
|
|
17
|
+
import { triggerToAgentToolConfig, triggersToHerdctlSchedules, curatorTriggerOf, } from "./trigger-config.js";
|
|
18
|
+
import { keeperAgentName, sweeperAgentName, triggerAgentName, browserMcpServers, BROWSER_MCP_TOOL, DENIED_TOOLS, KEEPER_MAX_CONCURRENT, KEEPER_SESSION_TIMEOUT, } from "./herdctl-agent-names.js";
|
|
19
|
+
/**
|
|
20
|
+
* A project's keeper agent config. Inherits the fleet `defaults` (runtime,
|
|
21
|
+
* max_turns, permission_mode, allowed/denied tools) via addAgent's deep-merge;
|
|
22
|
+
* only project-specific fields are set here.
|
|
23
|
+
*
|
|
24
|
+
* Model resolution: `modelOverride` (a per-chat override) wins, else the
|
|
25
|
+
* project's persisted `model`, else the keeper default (Opus).
|
|
26
|
+
*
|
|
27
|
+
* System prompt: by default (`nativeSystemPrompt`, issue #176) we set NO
|
|
28
|
+
* `system_prompt`, so Claude Code's full default coding prompt applies — the SDK
|
|
29
|
+
* runtime falls back to the `claude_code` preset and the CLI runtime passes no
|
|
30
|
+
* `--system-prompt`, which amount to the same thing. It applies together with
|
|
31
|
+
* the project's CLAUDE.md hierarchy — the canonical instance-wide
|
|
32
|
+
* `<projectsRoot>/CLAUDE.md`
|
|
33
|
+
* (auto-loaded via the cwd walk-up, since a project dir is a child of the
|
|
34
|
+
* projects root) plus a per-project CLAUDE.md. This is its own decision (issue
|
|
35
|
+
* #176): an instance with no CLAUDE.md files can opt back into the terse replace
|
|
36
|
+
* prompt below with `PADDOCK_NATIVE_PROMPT=false`.
|
|
37
|
+
*
|
|
38
|
+
* This is the reading #512 settled on, and the reason it is `projectsRoot` and
|
|
39
|
+
* not `<dataDir>`: `projectsRoot` IS the instance's backing repo, so the file is
|
|
40
|
+
* version-controlled. Since #516 the ROOT project reaches it the same way —
|
|
41
|
+
* its cwd IS `projectsRoot`, so the walk-up needs no special-casing.
|
|
42
|
+
*/
|
|
43
|
+
export function buildAgentConfig(cfg, project, modelOverride) {
|
|
44
|
+
const config = {
|
|
45
|
+
name: keeperAgentName(project.slug),
|
|
46
|
+
description: project.summary || `Claude Code agent for project ${project.name}.`,
|
|
47
|
+
// Repo-backed projects (issue #187): the keeper runs INSIDE the cloned
|
|
48
|
+
// checkout (project.workingDir), so the repo's own CLAUDE.md + git tooling
|
|
49
|
+
// apply. For a notebook project workingDir === dir, so this is unchanged.
|
|
50
|
+
working_directory: project.workingDir,
|
|
51
|
+
// `runtime` governs the ONE-SHOT `trigger()` path only — it does NOT decide
|
|
52
|
+
// how chats run. `openChatSession` (session drive-mode, the default) hard-codes
|
|
53
|
+
// `new SDKRuntime()` and never reads this field, so a normal chat runs on the
|
|
54
|
+
// Claude Agent SDK regardless. This value bites only when a project is set to
|
|
55
|
+
// `driveMode: batch`, whose turns go through `RuntimeFactory.create`. Set here
|
|
56
|
+
// per-agent because core's `DefaultsSchema` has no `runtime` key: the fleet
|
|
57
|
+
// `defaults.runtime` we write below is stripped when the file is parsed, so
|
|
58
|
+
// there is nothing for addAgent's deep-merge to hand down. Same for the
|
|
59
|
+
// sweeper and trigger copies.
|
|
60
|
+
runtime: "cli",
|
|
61
|
+
model: modelOverride ?? project.model ?? DEFAULT_MODEL,
|
|
62
|
+
// Per-project keeper settings (issue #12). The project DTO always carries
|
|
63
|
+
// concrete values (fleet defaults resolved in projects.ts), so setting
|
|
64
|
+
// them here just overrides the inherited fleet `defaults` per project.
|
|
65
|
+
permission_mode: project.permissionMode ?? DEFAULT_PERMISSION_MODE,
|
|
66
|
+
max_turns: project.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
67
|
+
// Allow parallel chats per project (forks, and just multiple open chats)
|
|
68
|
+
// instead of herdctl's serialize-by-default max_concurrent: 1.
|
|
69
|
+
instances: { max_concurrent: KEEPER_MAX_CONCURRENT },
|
|
70
|
+
// Session retention (Paddock#111): keep an agent-level session alive long
|
|
71
|
+
// enough that a scheduler-fired wake can still resume its transcript. Note
|
|
72
|
+
// Paddock always resumes by EXPLICIT session id, which bypasses this
|
|
73
|
+
// timeout — and the transcript itself is governed by Claude Code's
|
|
74
|
+
// `cleanupPeriodDays` (default 30d, adequate for realistic wake horizons;
|
|
75
|
+
// set out-of-band via .claude/settings.json if longer horizons are needed).
|
|
76
|
+
// So this is defense-in-depth for the fallback-resume path, sized to the
|
|
77
|
+
// reaper's 7-day recurring-wake expiry.
|
|
78
|
+
session: { timeout: KEEPER_SESSION_TIMEOUT },
|
|
79
|
+
default_prompt: "Summarize the current state of this project.",
|
|
80
|
+
};
|
|
81
|
+
// Docker isolation: only set it when the project opts in, so a project that
|
|
82
|
+
// leaves it off keeps inheriting the fleet default (no Docker) unchanged.
|
|
83
|
+
if (project.docker)
|
|
84
|
+
config.docker = { enabled: true };
|
|
85
|
+
// Native by default: omit the replace prompt so the default coding prompt +
|
|
86
|
+
// CLAUDE.md hierarchy apply (issue #176). Only a non-native instance
|
|
87
|
+
// (PADDOCK_NATIVE_PROMPT=false) gets the terse replace prompt.
|
|
88
|
+
if (!cfg.nativeSystemPrompt) {
|
|
89
|
+
config.system_prompt =
|
|
90
|
+
"You are a Claude Code agent for this project directory. " +
|
|
91
|
+
"Honor any CLAUDE.md present. Keep CHANGELOG.md current. " +
|
|
92
|
+
"Create branches for significant changes; never force-push.";
|
|
93
|
+
}
|
|
94
|
+
// Unified triggers (Epic T / T1): SCHEDULE-type triggers are forwarded into the
|
|
95
|
+
// keeper agent's `schedules` block, in herdctl's OWN `ScheduleSchema` shape,
|
|
96
|
+
// UNMOLESTED — herdctl's cron engine reads `agent.schedules` live every tick, so
|
|
97
|
+
// declaring them here arms them with no translation. The Paddock-only `promptFile`
|
|
98
|
+
// is stripped (the schedule-trigger handler resolves it at fire time). Event/webhook
|
|
99
|
+
// triggers are excluded by triggersToHerdctlSchedules. Only set the key when
|
|
100
|
+
// non-empty so a trigger-less project stays byte-identical to before.
|
|
101
|
+
const schedules = triggersToHerdctlSchedules(project.triggers);
|
|
102
|
+
if (schedules)
|
|
103
|
+
config.schedules = schedules;
|
|
104
|
+
// Browser MCP (headless Chromium) when enabled for this box; `mcp__playwright__*`
|
|
105
|
+
// is already on the inherited defaults.allowed_tools.
|
|
106
|
+
const browser = browserMcpServers(cfg.browserMcp);
|
|
107
|
+
if (browser)
|
|
108
|
+
config.mcp_servers = browser;
|
|
109
|
+
return config;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The sweeper's working directory: a dedicated per-project dir under the data
|
|
113
|
+
* root, deliberately NOT the project dir (issue #548).
|
|
114
|
+
*
|
|
115
|
+
* A CLI agent's working directory is what Claude Code encodes into its
|
|
116
|
+
* transcript path, so two agents sharing a cwd share ONE session directory. The
|
|
117
|
+
* sweeper's cwd used to be `project.dir`, which for a notebook project equals the
|
|
118
|
+
* keeper's `project.workingDir` — so keeper and sweeper wrote their transcripts
|
|
119
|
+
* into the same `.chats/`. herdctl identifies a freshly-spawned session by
|
|
120
|
+
* set-difference against a pre-spawn directory snapshot, which is immune to a
|
|
121
|
+
* co-located agent *appending* to its own session but NOT to one *creating* a new
|
|
122
|
+
* file: whichever brand-new `*.jsonl` appears first is claimed as "ours". A sweep
|
|
123
|
+
* is scheduled after every keeper turn, so the sweeper's spawn raced the next
|
|
124
|
+
* keeper turn and a user's chat could be bound to the sweeper's hidden curation
|
|
125
|
+
* transcript (wrong reply streamed, wrong transcript resumed, chat missing from
|
|
126
|
+
* the project's list).
|
|
127
|
+
*
|
|
128
|
+
* The sweeper is TOOL-LESS, so its cwd is inert — nothing about curation depends
|
|
129
|
+
* on it (SweepService reads the project's files itself and inlines them in the
|
|
130
|
+
* prompt, then writes the results itself). Giving it its own directory removes
|
|
131
|
+
* the shared directory, and with it the whole class of collisions, structurally.
|
|
132
|
+
*
|
|
133
|
+
* Kept OUTSIDE `projectsRoot` on purpose: core's discovery unions every
|
|
134
|
+
* `~/.claude/projects/*` bucket whose decoded path is a strict *descendant* of an
|
|
135
|
+
* agent's cwd, and the root workspace's cwd IS `projectsRoot`.
|
|
136
|
+
*/
|
|
137
|
+
export function sweeperWorkingDir(cfg, slug) {
|
|
138
|
+
// The root workspace's key is "" (see project-paths.ts); reuse the same
|
|
139
|
+
// `_root` spelling the agent namespace uses so every sweeper dir is non-empty.
|
|
140
|
+
// Any other slug is SLUG_RE-validated upstream, so it holds no separators.
|
|
141
|
+
return path.join(cfg.dataDir, "sweepers", slug === "" ? "_root" : slug);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A project's sweeper (curator) agent config. TOOL-LESS: the sweeper has NO
|
|
145
|
+
* tools (`allowed_tools: []`) — it never reads or writes files. Instead it
|
|
146
|
+
* RETURNS the curated content as plain assistant text in marked sections
|
|
147
|
+
* (OVERVIEW / CHANGELOG / optional CLAUDE, issue #177); SweepService parses
|
|
148
|
+
* that text and writes OVERVIEW.md / CHANGELOG.md / CLAUDE.md itself.
|
|
149
|
+
*
|
|
150
|
+
* This is cheaper and far more predictable than letting a Haiku agent drive
|
|
151
|
+
* file edits: no tool-loop turns, no partial writes, no permission_mode /
|
|
152
|
+
* denied_tools to reason about (all irrelevant with zero tools).
|
|
153
|
+
*/
|
|
154
|
+
export function buildSweeperConfig(cfg, project) {
|
|
155
|
+
// T5: the sweeper IS the default `curate-overview` (event/afterTurn) trigger. When a
|
|
156
|
+
// project declares that trigger with a `run.model`, honor it as the sweeper agent's
|
|
157
|
+
// model (design §2.1 #4). herdctl's per-fire trigger API has no model override, so
|
|
158
|
+
// the per-project `sweeper-<slug>` agent carries it — applied at (re-)registration
|
|
159
|
+
// (boot / `ensureProjectAgent`). Absent ⇒ the cheap curation default, unchanged.
|
|
160
|
+
const curatorModel = curatorTriggerOf(project.triggers)?.run.model;
|
|
161
|
+
return {
|
|
162
|
+
name: sweeperAgentName(project.slug),
|
|
163
|
+
description: `Overview/changelog curator (sweeper) for project ${project.name}.`,
|
|
164
|
+
// NOT project.dir — see sweeperWorkingDir (issue #548).
|
|
165
|
+
working_directory: sweeperWorkingDir(cfg, project.slug),
|
|
166
|
+
// The sweeper only ever runs via the one-shot `trigger()` path, so unlike the
|
|
167
|
+
// keeper's copy this one really does take effect on every sweep: a `claude -p`
|
|
168
|
+
// CLI subprocess. Set here per-agent because core's `DefaultsSchema` has no
|
|
169
|
+
// `runtime` key — the fleet `defaults.runtime` never survives parsing, so
|
|
170
|
+
// there is nothing to inherit (registering via addAgent is not the reason:
|
|
171
|
+
// addAgent does deep-merge the loaded defaults, they just never carry it).
|
|
172
|
+
runtime: "cli",
|
|
173
|
+
model: curatorModel ?? SWEEPER_DEFAULT_MODEL,
|
|
174
|
+
// Tool-less: a handful of turns is plenty since there are no tool loops.
|
|
175
|
+
max_turns: 4,
|
|
176
|
+
// NO tools. The sweeper returns text only; SweepService does the writing.
|
|
177
|
+
allowed_tools: [],
|
|
178
|
+
system_prompt: "You are a concise project curator. You DO NOT use any tools — you only " +
|
|
179
|
+
"return text. From the recent activity, the current OVERVIEW.md, the " +
|
|
180
|
+
"recent CHANGELOG.md, and the current CLAUDE.md provided in the user " +
|
|
181
|
+
"message, produce these three sections wrapped in these literal markers, " +
|
|
182
|
+
"and NOTHING else:\n" +
|
|
183
|
+
"\n" +
|
|
184
|
+
"<<<OVERVIEW>>>\n" +
|
|
185
|
+
"<the full markdown OVERVIEW.md, which REPLACES the current one wholesale: " +
|
|
186
|
+
"a synthesized snapshot of the project's CURRENT state for an LLM to read " +
|
|
187
|
+
"at the start of a new chat — what the project is, key decisions/facts, " +
|
|
188
|
+
"open questions, and next steps. No changelog or per-session history here. " +
|
|
189
|
+
"Output exactly NOCHANGE only if the current one is already accurate.>\n" +
|
|
190
|
+
"<<<CHANGELOG>>>\n" +
|
|
191
|
+
"<the full markdown CHANGELOG.md, which REPLACES the current one wholesale. " +
|
|
192
|
+
"PRESERVE the existing dated entries and add at most ONE new bullet under a " +
|
|
193
|
+
"`## YYYY-MM-DD` heading at the TOP (newest-first), reusing today's heading " +
|
|
194
|
+
"if present; coalesce near-duplicate recent bullets. What you return becomes " +
|
|
195
|
+
"the ENTIRE file, so never emit a bare sentence or a lone bullet here — that " +
|
|
196
|
+
"would destroy the file's history. If this activity is already captured by a " +
|
|
197
|
+
"recent entry, output exactly NOCHANGE and change nothing.>\n" +
|
|
198
|
+
"<<<CLAUDE>>>\n" +
|
|
199
|
+
"<the full curated-notes body for CLAUDE.md — everything that belongs under " +
|
|
200
|
+
"the `## Curated notes` heading — which REPLACES that managed section " +
|
|
201
|
+
"wholesale (human-authored content above it is preserved for you). ONLY " +
|
|
202
|
+
"long-lived identity/conventions: what the project fundamentally is, key " +
|
|
203
|
+
"decisions, how we work on it. You are shown the whole current file, so " +
|
|
204
|
+
"DEDUP — fold near-duplicate notes into one and drop stale ones. Never " +
|
|
205
|
+
"restate current state/tasks/history (those are OVERVIEW/CHANGELOG). If " +
|
|
206
|
+
"nothing durable changed, output exactly NOCHANGE.>\n" +
|
|
207
|
+
"<<<END>>>\n" +
|
|
208
|
+
"\n" +
|
|
209
|
+
"OVERVIEW.md describes the PROJECT, not the box it runs on: never record " +
|
|
210
|
+
"box/environment operational conventions (how to run/build/expose a dev " +
|
|
211
|
+
"server, ports, localhost vs. dev hostnames/URLs, where to clone, process " +
|
|
212
|
+
"managers) — those are owned by the box's own CLAUDE.md and must not be " +
|
|
213
|
+
"re-described or contradicted here.\n" +
|
|
214
|
+
"\n" +
|
|
215
|
+
"Be factual and terse. Do not invent details not present in the provided " +
|
|
216
|
+
"activity. Output ONLY the three sections between the markers — no preamble, " +
|
|
217
|
+
"no explanation, no tool use.",
|
|
218
|
+
default_prompt: "Curate OVERVIEW.md, CHANGELOG.md and CLAUDE.md from recent activity.",
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* A trigger's scoped herdctl agent config (Epic T) — the unified successor to
|
|
223
|
+
* {@link hookAgentConfig}. A trigger that {@link triggerRunsOnOwnAgent} (every event
|
|
224
|
+
* trigger; a schedule trigger with a non-empty `run.tools` allow-list — T2) is
|
|
225
|
+
* registered as its OWN agent `trigger-<slug>-<name>` whose tool config
|
|
226
|
+
* (`allowed_tools`/`permission_mode`/`model`/`max_turns`, projected by
|
|
227
|
+
* {@link triggerToAgentToolConfig} from the trigger's `run`) IS its capability set.
|
|
228
|
+
* Runs in the project's WORKING dir (so a trigger's Bash/Write act on the same tree
|
|
229
|
+
* the keeper does). A tool-less trigger gets `allowed_tools: []` and can only return text.
|
|
230
|
+
*/
|
|
231
|
+
export function buildTriggerConfig(cfg, project, triggerName, trigger) {
|
|
232
|
+
const config = {
|
|
233
|
+
name: triggerAgentName(project.slug, triggerName),
|
|
234
|
+
description: `Trigger "${triggerName}" (${trigger.trigger.type}) for project ${project.name}.`,
|
|
235
|
+
working_directory: project.workingDir,
|
|
236
|
+
// Triggers fire through the one-shot `trigger()` path, so this takes effect on
|
|
237
|
+
// every fire: a `claude -p` CLI subprocess. Set here per-agent for the same
|
|
238
|
+
// reason as the keeper/sweeper copies: core's `DefaultsSchema` has no
|
|
239
|
+
// `runtime` key, so the fleet `defaults.runtime` is stripped at parse and
|
|
240
|
+
// never merged in.
|
|
241
|
+
runtime: "cli",
|
|
242
|
+
// Model defaults to the keeper default unless the run pins one;
|
|
243
|
+
// triggerToAgentToolConfig sets `model` only when the run specifies it, so
|
|
244
|
+
// provide the fallback here so a trigger never boots without a concrete model.
|
|
245
|
+
model: trigger.run.model ?? project.model ?? DEFAULT_MODEL,
|
|
246
|
+
// run → tool config (allowed tools, permission mode, model, max_turns).
|
|
247
|
+
...triggerToAgentToolConfig(trigger.run),
|
|
248
|
+
};
|
|
249
|
+
if (project.docker)
|
|
250
|
+
config.docker = { enabled: true };
|
|
251
|
+
const browser = browserMcpServers(cfg.browserMcp);
|
|
252
|
+
if (browser)
|
|
253
|
+
config.mcp_servers = browser;
|
|
254
|
+
return config;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Write the minimal herdctl.yaml the FleetManager boots from: a fleet block
|
|
258
|
+
* plus the fleet-wide `defaults` (deep-merged into agents added at runtime),
|
|
259
|
+
* and ZERO agents. All agents are registered programmatically via
|
|
260
|
+
* `fleet.addAgent(...)` in init() — paddock no longer generates per-agent
|
|
261
|
+
* yaml files or calls `reload()`.
|
|
262
|
+
*
|
|
263
|
+
* The `fleet` block is strict (name/description only). `defaults` are deep-
|
|
264
|
+
* merged into each agent by addAgent (mergeDefaults defaults to true), so agents
|
|
265
|
+
* inherit model/max_turns/permission_mode/allowed+denied_tools from here. NOT
|
|
266
|
+
* `runtime`: core's `DefaultsSchema` has no such key, so it is stripped at parse
|
|
267
|
+
* and every agent sets its own.
|
|
268
|
+
*/
|
|
269
|
+
export async function ensureConfigFile(cfg) {
|
|
270
|
+
const configDir = path.dirname(cfg.herdctlConfigPath);
|
|
271
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
272
|
+
const doc = {
|
|
273
|
+
version: 1,
|
|
274
|
+
fleet: {
|
|
275
|
+
name: "paddock",
|
|
276
|
+
description: "Paddock agent fleet (agents registered at runtime).",
|
|
277
|
+
},
|
|
278
|
+
defaults: {
|
|
279
|
+
// Currently INERT: core's `DefaultsSchema` has no `runtime` key, so this is
|
|
280
|
+
// stripped when the file is parsed and never reaches an agent — which is
|
|
281
|
+
// exactly why the keeper/sweeper/trigger configs above each set `runtime`
|
|
282
|
+
// themselves. Left in place because it costs nothing and records the
|
|
283
|
+
// fleet-wide intent; if core ever accepts it, every agent still overrides
|
|
284
|
+
// it. And even honoured it would govern the one-shot `trigger()` path only:
|
|
285
|
+
// chats opened via `openChatSession` always run the SDK runtime.
|
|
286
|
+
runtime: "cli",
|
|
287
|
+
// The default model, so any agent that doesn't set its own runs on it;
|
|
288
|
+
// in practice every agent above sets `model` explicitly anyway.
|
|
289
|
+
model: DEFAULT_MODEL,
|
|
290
|
+
// ~200 turns: enough for real multi-step coding sessions while still
|
|
291
|
+
// bounding runaway agents (CLAUDE.md: always set max_turns). A project
|
|
292
|
+
// can override this per-project (issue #12); this is the inherited
|
|
293
|
+
// default (shared constant so the DTO resolution stays in sync).
|
|
294
|
+
max_turns: DEFAULT_MAX_TURNS,
|
|
295
|
+
// Keeper agents run native (no Docker) with acceptEdits + denied
|
|
296
|
+
// dangerous bash patterns by default; a project can opt into Docker
|
|
297
|
+
// isolation or a different permission mode per-project (issue #12).
|
|
298
|
+
permission_mode: DEFAULT_PERMISSION_MODE,
|
|
299
|
+
// `Skill` MUST be in the allowlist or every skill invocation is
|
|
300
|
+
// permission-denied — both runtimes pass this list through verbatim (CLI:
|
|
301
|
+
// `--allowedTools`; SDK: `options.allowedTools`) and auto-deny any tool not
|
|
302
|
+
// on it, with no prompt. Built-in skills (claude-api,
|
|
303
|
+
// code-review, deep-research, ...) ship inside the CLI binary and are
|
|
304
|
+
// registered/visible regardless of setting-sources, so the ONLY thing
|
|
305
|
+
// blocking them was this missing tool. Skills routinely fan out to
|
|
306
|
+
// sub-agents (`Task`), track progress (`TodoWrite`), and edit notebooks
|
|
307
|
+
// (`NotebookEdit`), each of which is likewise permission-checked against
|
|
308
|
+
// this same allowlist — so include them here to keep skills functional
|
|
309
|
+
// end-to-end (adds no capability the keeper's existing tools don't).
|
|
310
|
+
// BROWSER_MCP_TOOL (mcp__playwright__*) is listed unconditionally: it is a
|
|
311
|
+
// no-op unless the keeper agent actually attaches the playwright
|
|
312
|
+
// server (gated by PADDOCK_BROWSER_MCP), and having it on the allowlist
|
|
313
|
+
// means enabling the browser is a per-box env flip with no code change.
|
|
314
|
+
// Timer-class autonomy tools (Paddock#111): `ScheduleWakeup` + the
|
|
315
|
+
// session-only `Cron*` set + `Monitor` must be on the allowlist or the
|
|
316
|
+
// runtime auto-denies them, so a keeper couldn't schedule a wake even in
|
|
317
|
+
// session mode. `ToolSearch` is the harness's deferred-tool loader —
|
|
318
|
+
// several of these surface as deferred tools reached through it. These
|
|
319
|
+
// only actually DO anything in session drive-mode (the reaper re-fires
|
|
320
|
+
// them); in batch mode they're inert (documented in the box CLAUDE.md).
|
|
321
|
+
allowed_tools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep", "WebFetch", "WebSearch", "Task", "TodoWrite", "Skill", "NotebookEdit", "ToolSearch", "ScheduleWakeup", "Monitor", "CronCreate", "CronList", "CronDelete", BROWSER_MCP_TOOL],
|
|
322
|
+
// Best-effort denylist (#179): narrow, honest catastrophic-wipe patterns
|
|
323
|
+
// that do NOT block legitimate absolute-path cleanup. See DENIED_TOOLS.
|
|
324
|
+
denied_tools: [...DENIED_TOOLS],
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
const header = "# GENERATED by paddock-server. Do NOT hand-edit. Agents are NOT listed\n" +
|
|
328
|
+
"# here — they are registered at runtime via FleetManager.addAgent(). This\n" +
|
|
329
|
+
"# file only carries the fleet identity + the defaults merged into them.\n";
|
|
330
|
+
await fs.writeFile(cfg.herdctlConfigPath, header + YAML.stringify(doc), "utf8");
|
|
331
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* herdctl-agent-names — the pure, stateless name/visibility helpers and
|
|
3
|
+
* constants that map a project slug (+ hook/trigger name) to the deterministic
|
|
4
|
+
* herdctl agent names Paddock registers, plus the small tool/model constants the
|
|
5
|
+
* agent-config builders lean on.
|
|
6
|
+
*
|
|
7
|
+
* These are split out of {@link HerdctlService} (issue #403) because they only
|
|
8
|
+
* read their arguments — no `fleet`/`cfg`/live-session state — so they are
|
|
9
|
+
* independently readable and unit-testable. Everything here is re-exported from
|
|
10
|
+
* `./herdctl.js` so external importers keep resolving via that path unchanged.
|
|
11
|
+
*/
|
|
12
|
+
import type { Project } from "./projects.js";
|
|
13
|
+
/**
|
|
14
|
+
* Maps a workspace key to its keeper agent name. Kept deterministic so the
|
|
15
|
+
* runtime registration and runtime lookups always agree.
|
|
16
|
+
*
|
|
17
|
+
* The key is encoded through {@link agentKeyFor}, because the root workspace's
|
|
18
|
+
* key is `""` and a bare `keeper-` is not a legal herdctl agent name. Every
|
|
19
|
+
* builder below goes through the same encoding, so none of them needs a root
|
|
20
|
+
* branch. See `project-paths.ts` for why the encoding lives at this boundary
|
|
21
|
+
* rather than in the identity.
|
|
22
|
+
*/
|
|
23
|
+
export declare function keeperAgentName(slug: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Inverse of {@link keeperAgentName}: recover a workspace key from a keeper
|
|
26
|
+
* agent name (`keeper-<agentKey>` → `<key>`). Returns `null` for a non-keeper
|
|
27
|
+
* agent (e.g. a sweeper or a trigger agent), which is how a scheduler-fired wake
|
|
28
|
+
* tells "this is a chat to route back to a workspace" from "this is not a chat
|
|
29
|
+
* at all".
|
|
30
|
+
*
|
|
31
|
+
* **Callers must distinguish `null` from `""`.** The root workspace's key IS the
|
|
32
|
+
* empty string, so a falsy check (`if (!key) return;`) silently drops every root
|
|
33
|
+
* chat from wake/turn/job routing. Compare against `null` explicitly.
|
|
34
|
+
*/
|
|
35
|
+
export declare function keeperSlugFromAgent(agentName: string): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* The lightweight curator agent used by the post-turn sweep. Runs on a cheap
|
|
38
|
+
* model (Haiku 4.5) with only read/write tools, no Bash.
|
|
39
|
+
*
|
|
40
|
+
* NOTE: herdctl agents bind working_directory per agent. Because a single
|
|
41
|
+
* shared sweeper can only have one cwd, the SweepService registers ONE sweeper
|
|
42
|
+
* agent PER PROJECT (keeper-style) so each has the right cwd. The name is
|
|
43
|
+
* derived from the slug; the agent reads/writes that project's files.
|
|
44
|
+
*/
|
|
45
|
+
export declare const SWEEPER_PREFIX = "sweeper-";
|
|
46
|
+
/** Maps a project slug to its sweeper agent name. */
|
|
47
|
+
export declare function sweeperAgentName(slug: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* The agent-name prefix for event hooks (Epic G / G1). Each hook is its OWN herdctl
|
|
50
|
+
* agent `hook-<slug>-<name>` (GG-1) — registered alongside the keeper/sweeper — whose
|
|
51
|
+
* tool config IS the hook's capability set.
|
|
52
|
+
*/
|
|
53
|
+
export declare const HOOK_AGENT_PREFIX = "hook-";
|
|
54
|
+
/**
|
|
55
|
+
* Maps a project slug + hook name to its agent name `hook-<slug>-<name>`. Kept
|
|
56
|
+
* deterministic so runtime registration and firing (`startAgentTurn`) always agree.
|
|
57
|
+
* (`slug` is a kebab id and `name` matches `[A-Za-z0-9._-]+`, so the composed name is
|
|
58
|
+
* a valid herdctl agent name; the reverse mapping for visibility (G3) is resolved
|
|
59
|
+
* from a project's declared hooks, not by parsing this string.)
|
|
60
|
+
*/
|
|
61
|
+
export declare function hookAgentName(slug: string, hookName: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* The agent-name prefix for unified triggers (Epic T / T1). An EVENT trigger is its
|
|
64
|
+
* OWN herdctl agent `trigger-<slug>-<name>` (tool config = capability), registered
|
|
65
|
+
* alongside the keeper/sweeper — exactly like a hook agent. Schedule triggers run on
|
|
66
|
+
* the keeper (T1) and webhook triggers are reserved, but every trigger carries this
|
|
67
|
+
* deterministic name in its DTO so the mapping is stable across T2–T5.
|
|
68
|
+
*/
|
|
69
|
+
export declare const TRIGGER_AGENT_PREFIX = "trigger-";
|
|
70
|
+
/**
|
|
71
|
+
* Maps a project slug + trigger name to its agent name `trigger-<slug>-<name>`. Kept
|
|
72
|
+
* deterministic so runtime registration and firing always agree. The reverse mapping
|
|
73
|
+
* (for visibility) is resolved from a project's declared triggers, not by parsing this
|
|
74
|
+
* string (a slug may contain hyphens). Mirrors {@link hookAgentName}.
|
|
75
|
+
*/
|
|
76
|
+
export declare function triggerAgentName(slug: string, triggerName: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* The agents whose chats are VISIBLE in a project's chat list (Epic G / G3, GG-5):
|
|
79
|
+
* the keeper plus every event hook the project declares (`hook-<slug>-<name>`). This
|
|
80
|
+
* is the generalization of the old hard "keeper-only" listing — "all of a project's
|
|
81
|
+
* agents EXCEPT those marked hidden."
|
|
82
|
+
*
|
|
83
|
+
* The **sweeper** (`sweeper-<slug>`) is the one hidden agent: it is deliberately
|
|
84
|
+
* omitted here so its post-turn curation chats never surface (the `hideChats` case),
|
|
85
|
+
* exactly as before. Disabled hooks are still included — a hook chat that already ran should stay
|
|
86
|
+
* visible regardless of whether the hook is currently armed.
|
|
87
|
+
*
|
|
88
|
+
* Kept pure + exported so the listing filter is unit-testable in isolation (the
|
|
89
|
+
* sweeper-stays-hidden regression lives here), and so future callers have ONE place
|
|
90
|
+
* that answers "which of a project's agents' chats do we show?".
|
|
91
|
+
*/
|
|
92
|
+
export declare function visibleProjectAgentNames(project: Project): string[];
|
|
93
|
+
/**
|
|
94
|
+
* The Claude Code tool pattern for the Playwright browser MCP. Must live on the
|
|
95
|
+
* agent allowlist (both runtimes auto-deny any tool not on the allow-list —
|
|
96
|
+
* `--allowedTools` on the CLI, `options.allowedTools` on the SDK — same reason
|
|
97
|
+
* `Skill` is listed) — so it is added to `defaults.allowed_tools`,
|
|
98
|
+
* which the keeper agents inherit and the tool-less sweeper overrides
|
|
99
|
+
* away. Harmless when the server isn't enabled: an allowed-but-absent tool is a
|
|
100
|
+
* no-op.
|
|
101
|
+
*/
|
|
102
|
+
export declare const BROWSER_MCP_TOOL = "mcp__playwright__*";
|
|
103
|
+
/**
|
|
104
|
+
* The keeper's default `denied_tools` — a **best-effort, defence-in-depth**
|
|
105
|
+
* denylist, NOT a sandbox. Real isolation (per-agent filesystem confinement)
|
|
106
|
+
* is tracked in #7; these string patterns are trivially bypassable (a relative
|
|
107
|
+
* path, a `$VAR`, a `find -delete`) and are here only to make the obvious
|
|
108
|
+
* catastrophic footguns require deliberate rephrasing.
|
|
109
|
+
*
|
|
110
|
+
* Claude Code Bash patterns are prefix-with-`*` matches: `Bash(foo *)` denies
|
|
111
|
+
* any command starting with `foo `, and `Bash(foo)` denies EXACTLY `foo`.
|
|
112
|
+
*
|
|
113
|
+
* History (#179): this list used to carry `Bash(rm -rf /*)`, intended as "don't
|
|
114
|
+
* wipe root". But `/*` is a trailing wildcard, so it actually denied
|
|
115
|
+
* `rm -rf /<ANYTHING>` — i.e. every absolute-path delete, including the keeper
|
|
116
|
+
* cleaning up its OWN scratch/clone dirs (`rm -rf /tmp/foo`,
|
|
117
|
+
* `rm -rf /var/lib/.../clones/x`). That was both over-broad (blocked legitimate
|
|
118
|
+
* work, wasted turns, confusing "denied" cards) and false security (the agent
|
|
119
|
+
* just switched to a relative `rm -rf clones/x` and it went through).
|
|
120
|
+
*
|
|
121
|
+
* The honest replacement targets genuinely-catastrophic *bare* roots:
|
|
122
|
+
* - `rm -rf /` and `rm -rf / *` (root, incl. `--no-preserve-root` variants),
|
|
123
|
+
* - `rm -rf ~` / `rm -rf $HOME` (home, exact — a trailing wildcard would block
|
|
124
|
+
* `rm -rf ~/.cache/foo`, which is legitimate),
|
|
125
|
+
* - bare top-level system dirs matched EXACTLY (`Bash(rm -rf /etc)`, …) so that
|
|
126
|
+
* real subpath deletes under them (notably `/var/lib/paddock/...` and
|
|
127
|
+
* `/tmp/...`) are NOT denied.
|
|
128
|
+
*
|
|
129
|
+
* Note the deliberate gaps: a literal `rm -rf /*` glob command and
|
|
130
|
+
* `rm -rf /usr/local/...` are NOT caught, because the only pattern that would
|
|
131
|
+
* catch them (`Bash(rm -rf /*)`, `Bash(rm -rf /usr*)`) also re-blocks the
|
|
132
|
+
* legitimate absolute-path deletes we exist to allow. Narrow-and-honest beats
|
|
133
|
+
* broad-and-leaky; the sandbox (#7) is the real fix.
|
|
134
|
+
*/
|
|
135
|
+
export declare const DENIED_TOOLS: readonly string[];
|
|
136
|
+
/**
|
|
137
|
+
* The Playwright browser MCP server given to the keeper agents so
|
|
138
|
+
* Claude Code can drive a headless Chromium (navigate / click / fill / snapshot
|
|
139
|
+
* / screenshot). Returns `undefined` when `enabled` is false (sourced from
|
|
140
|
+
* `cfg.browserMcp`, i.e. `PADDOCK_BROWSER_MCP=1` — issue #269), so a box WITHOUT
|
|
141
|
+
* the browser stack simply omits the server (no failed spawns) and enabling it
|
|
142
|
+
* is a per-box env flip — no code change.
|
|
143
|
+
*
|
|
144
|
+
* The browser is installed box-side by the homelab `paddock` Ansible role
|
|
145
|
+
* (`npm i -g @playwright/mcp` + `playwright install chromium`, exposing the
|
|
146
|
+
* `playwright-mcp` bin on PATH). The boxes are unprivileged LXCs, so Chromium
|
|
147
|
+
* must run headless + `--no-sandbox` (the container is the isolation boundary);
|
|
148
|
+
* `--isolated` keeps each session's profile in-memory (no persisted user-data
|
|
149
|
+
* dir). `--browser chromium` is REQUIRED: @playwright/mcp defaults to the
|
|
150
|
+
* `chrome` channel (branded Google Chrome), which isn't installed — without this
|
|
151
|
+
* flag the server tries to `playwright install chrome` at first use and stalls.
|
|
152
|
+
* The role installs the open-source `chromium` engine, so we select it here.
|
|
153
|
+
* The tool-less sweeper deliberately never receives this server.
|
|
154
|
+
*/
|
|
155
|
+
export declare function browserMcpServers(enabled: boolean): Record<string, unknown> | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* How many chat turns a project's keeper may run at once. herdctl defaults an
|
|
158
|
+
* agent to `max_concurrent: 1`, which would serialize a project's chats and make
|
|
159
|
+
* a second turn (e.g. the first message of a freshly *forked* chat sent while the
|
|
160
|
+
* parent is still streaming) fail with a ConcurrencyLimitError. Paddock is a
|
|
161
|
+
* single-user box that explicitly wants parallel chats per project — especially
|
|
162
|
+
* forks — so we lift the keeper's limit. (The shared-keeper model is still
|
|
163
|
+
* last-write-wins across concurrent chats of the same project; forks default to
|
|
164
|
+
* the parent's model, so that caveat rarely bites in practice.)
|
|
165
|
+
*/
|
|
166
|
+
export declare const KEEPER_MAX_CONCURRENT = 10;
|
|
167
|
+
/**
|
|
168
|
+
* How long herdctl keeps a keeper's fallback session alive (Paddock#111). Sized
|
|
169
|
+
* to the reaper's 7-day recurring-wake expiry so a fallback resume still finds
|
|
170
|
+
* the session; explicit-id resume (Paddock's norm) bypasses this anyway.
|
|
171
|
+
*/
|
|
172
|
+
export declare const KEEPER_SESSION_TIMEOUT = "168h";
|