@edspencer/paddock 0.63.0 → 0.65.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/package.json +1 -1
- package/packages/server/dist/adoptable.d.ts +18 -0
- package/packages/server/dist/adoptable.js +1 -1
- package/packages/server/dist/app.js +3 -1
- package/packages/server/dist/claude-home.js +9 -1
- package/packages/server/dist/cli/import-chats.js +1 -1
- package/packages/server/dist/git.d.ts +44 -1
- package/packages/server/dist/git.js +79 -11
- package/packages/server/dist/management-ops.js +42 -2
- package/packages/server/dist/management-policy.d.ts +4 -3
- package/packages/server/dist/management-policy.js +5 -2
- package/packages/server/dist/project-paths.d.ts +72 -4
- package/packages/server/dist/project-paths.js +76 -4
- package/packages/server/dist/project-types.d.ts +109 -11
- package/packages/server/dist/projects.d.ts +170 -3
- package/packages/server/dist/projects.js +477 -66
- package/packages/server/dist/routes/git.js +6 -3
- package/packages/server/dist/routes/projects.js +16 -2
- package/packages/server/dist/self-mcp-descriptions.d.ts +1 -0
- package/packages/server/dist/self-mcp-descriptions.js +14 -1
- package/packages/server/dist/self-mcp-types.d.ts +32 -8
- package/packages/server/dist/self-mcp-write.d.ts +21 -0
- package/packages/server/dist/self-mcp-write.js +44 -0
- package/packages/server/dist/self-mcp.d.ts +7 -6
- package/packages/server/dist/self-mcp.js +36 -10
- package/packages/server/dist/sweep.js +10 -5
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-CCk2hVT9.js → InstanceConfigPage-DcprTH2W.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-B6gST_0U.js → MarkdownRenderer-Ri1LwAUN.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-BCmKT4mf.js → ProjectRedirect-Dze5H8Xy.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-CCN98MWu.js → ProjectView-PrA8purE.js} +9 -9
- package/packages/web/dist/assets/{ProjectsGrid-CYzuN_yD.js → ProjectsGrid-BidsM7ah.js} +1 -1
- package/packages/web/dist/assets/{arc-BxvtMCZQ.js → arc-eQ3ryE9c.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-Bp6ffJ_u.js → architectureDiagram-3BPJPVTR-Dc6Tj2Ai.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-BIY2X8nW.js → blockDiagram-GPEHLZMM-DBX5pma_.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-Br9stEJS.js → c4Diagram-AAUBKEIU-CnWrtTpa.js} +1 -1
- package/packages/web/dist/assets/channel-B4sED4OP.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-Djc7uqMV.js → chunk-2J33WTMH-2qmLvtsr.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-yEVMLksS.js → chunk-4BX2VUAB-Dn-LUDMV.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-tJaEdPZg.js → chunk-55IACEB6-DB0augUY.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-bWEqh672.js → chunk-727SXJPM-BPQQM0i8.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-B8U9fB-T.js → chunk-AQP2D5EJ-DOhfwA-x.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-BeEITYig.js → chunk-FMBD7UC4-CatHHdFD.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-L2rpLaoK.js → chunk-ND2GUHAM-B645aAlG.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-qqzPMZL_.js → chunk-QZHKN3VN-D1fAaPSZ.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-BlVeHFir.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-BlVeHFir.js +2 -0
- package/packages/web/dist/assets/{core-CSaNbmLO.js → core-B4t7D8Om.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BRF7Nech.js → cose-bilkent-S5V4N54A-BLdKDAwa.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-D10rJWOR.js → dagre-BM42HDAG-CbTbTVKv.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-DGjT5mAO.js → diagram-2AECGRRQ-CorSS29_.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-DaH3bxHh.js → diagram-5GNKFQAL-PLKivts0.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-QrLW9Wn6.js → diagram-KO2AKTUF-CUSIBacZ.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BX15wgYN.js → diagram-LMA3HP47-CZ69mAZs.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DXQ_p63s.js → diagram-OG6HWLK6-Cmf042Rh.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-C_ioRruE.js → erDiagram-TEJ5UH35-DyIKGj6e.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-7d-xLXLM.js → flowDiagram-I6XJVG4X-B79pLdLD.js} +1 -1
- package/packages/web/dist/assets/{format-BSDZezwD.js → format-C5nhFL8q.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DZYx5xcj.js → ganttDiagram-6RSMTGT7-BFTR_oNA.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-dimo-osm.js → gitGraphDiagram-PVQCEYII-DdGUlJxS.js} +1 -1
- package/packages/web/dist/assets/{index-CUnFKMga.js → index-DVSsta3B.js} +14 -14
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BzApY25x.js → infoDiagram-5YYISTIA-B80FyUOy.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DtX47_9p.js → ishikawaDiagram-YF4QCWOH-_wRlphn9.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-C7fITKg1.js → journeyDiagram-JHISSGLW-qu4yvjW8.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BOrWLf_W.js → kanban-definition-UN3LZRKU-D2DEUp2-.js} +1 -1
- package/packages/web/dist/assets/{linear-DuyAaBu6.js → linear-D-CLRUEW.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-BQlzuVvO.js → mermaid.core-DdihBJl4.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CmP748B5.js → mindmap-definition-RKZ34NQL-BEh_TDBf.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CRd5Pdfq.js → pieDiagram-4H26LBE5-BbrmLVaZ.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-DpJm0Yw_.js → quadrantDiagram-W4KKPZXB-DfVuygAv.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-B0o2PRMC.js → requirementDiagram-4Y6WPE33-BHp2QpZt.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-CBi3Ek3v.js → sankeyDiagram-5OEKKPKP-D2G6lRMY.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C1stxfQg.js → sequenceDiagram-3UESZ5HK-Dj-jzR-c.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-lCkiETWK.js → stateDiagram-AJRCARHV-C5D6bT13.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-C8NDOqTq.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-D3DkWJMC.js → timeline-definition-PNZ67QCA-DE4gGjhP.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CO2-yC7K.js → vennDiagram-CIIHVFJN-C9QNrl92.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-Dfjp0kqX.js → wardley-L42UT6IY-U2fSGy-d.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CIl0OPwS.js → wardleyDiagram-YWT4CUSO-B9RhObdH.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-CuvlQuup.js → xychartDiagram-2RQKCTM6-BnlPrtQ9.js} +1 -1
- package/packages/web/dist/index.html +1 -1
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/channel-CMdgNeFH.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DU4HSESS.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DU4HSESS.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-ZgEZRNqp.js +0 -2
|
@@ -146,7 +146,10 @@ export function registerGitWorkspaceRoutes(app, ctx) {
|
|
|
146
146
|
}, async (req, reply) => {
|
|
147
147
|
try {
|
|
148
148
|
const project = await projects.get(req.params.slug);
|
|
149
|
-
|
|
149
|
+
// The project's WORKING directory, not its metadata dir (#597): for a
|
|
150
|
+
// repo-backed or linked project those differ, and `dir` showed the notes
|
|
151
|
+
// folder rather than the code the project is about.
|
|
152
|
+
return await git.projectStatus(project.workingDir);
|
|
150
153
|
}
|
|
151
154
|
catch (err) {
|
|
152
155
|
return sendProjectError(reply, err);
|
|
@@ -181,7 +184,7 @@ export function registerGitWorkspaceRoutes(app, ctx) {
|
|
|
181
184
|
}, async (req, reply) => {
|
|
182
185
|
try {
|
|
183
186
|
const project = await projects.get(req.params.slug);
|
|
184
|
-
const diff = await git.projectDiff(project.
|
|
187
|
+
const diff = await git.projectDiff(project.workingDir, req.query.file);
|
|
185
188
|
reply.header("content-type", "text/plain; charset=utf-8");
|
|
186
189
|
return diff;
|
|
187
190
|
}
|
|
@@ -227,7 +230,7 @@ export function registerGitWorkspaceRoutes(app, ctx) {
|
|
|
227
230
|
// Optional `files` (project-relative) commits only those changes (#258);
|
|
228
231
|
// omitted ⇒ commit the whole subtree (legacy behavior).
|
|
229
232
|
const files = Array.isArray(req.body?.files) ? req.body?.files : undefined;
|
|
230
|
-
return await git.commitProject(project.
|
|
233
|
+
return await git.commitProject(project.workingDir, message, files);
|
|
231
234
|
}
|
|
232
235
|
catch (err) {
|
|
233
236
|
return sendProjectError(reply, err);
|
|
@@ -79,7 +79,15 @@ export function registerProjectRoutes(app, ctx) {
|
|
|
79
79
|
const projectsOut = await Promise.all(list.map(async (p) => ({
|
|
80
80
|
...p,
|
|
81
81
|
chatTurns: await buildChatTurns(p.slug, turnsByProject, user, readState, unread),
|
|
82
|
-
|
|
82
|
+
// The cheap store-wide sweep above buckets by path segment under
|
|
83
|
+
// `projectsRoot`, so it can only speak for projects whose working dir is
|
|
84
|
+
// the metadata dir. For one pointing somewhere else (a linked checkout,
|
|
85
|
+
// issue #206) ask its own repo directly — a subprocess, but only for the
|
|
86
|
+
// projects the sweep structurally cannot cover, and only for as many of
|
|
87
|
+
// those as actually exist.
|
|
88
|
+
dirty: p.workingDir === p.dir
|
|
89
|
+
? (dirty[p.slug] ?? 0)
|
|
90
|
+
: await git.dirtyCountAt(p.workingDir).catch(() => 0),
|
|
83
91
|
})));
|
|
84
92
|
// No `dirty` for the root: `dirtyCounts()` buckets by first path segment, so
|
|
85
93
|
// a file directly in the projects root has no bucket at all. Reporting 0
|
|
@@ -93,7 +101,7 @@ export function registerProjectRoutes(app, ctx) {
|
|
|
93
101
|
schema: {
|
|
94
102
|
tags: ["Projects"],
|
|
95
103
|
summary: "Create a project",
|
|
96
|
-
description: "Creates a project from `CreateProjectInput` and registers its agent and its sweeper. Set `repo` to create a repo-backed project (the cloned checkout becomes the agent's working directory); omit
|
|
104
|
+
description: "Creates a project from `CreateProjectInput` and registers its agent and its sweeper. Set `repo` to create a repo-backed project (the cloned checkout becomes the agent's working directory), or `path` to LINK an existing on-box git checkout used in place with no copy (that directory becomes the working directory and Paddock writes nothing into it); omit both for a notebook project. Responds 201 with `{ project }`.",
|
|
97
105
|
body: {
|
|
98
106
|
type: ["object", "null"],
|
|
99
107
|
additionalProperties: true,
|
|
@@ -113,6 +121,12 @@ export function registerProjectRoutes(app, ctx) {
|
|
|
113
121
|
repo: {
|
|
114
122
|
description: "External git repo URL to back this project (repo-backed if set).",
|
|
115
123
|
},
|
|
124
|
+
path: {
|
|
125
|
+
description: "Absolute path to the directory this project's content lives in (issue #206). For an unmanaged project this LINKS an existing on-box checkout, used in place with no copy. If it does not exist, Paddock clones `repo` into it when one is given, or creates it for a managed project. Must lie outside the projects root, the data dir, and every other project's working directory. No git repository is required. Immutable once set.",
|
|
126
|
+
},
|
|
127
|
+
managed: {
|
|
128
|
+
description: "Whether Paddock curates this project's own CLAUDE.md/OVERVIEW.md/CHANGELOG.md (issue #206). Omit to derive it: a create naming `repo` or `path` is unmanaged, a plain one is managed. `managed: true` together with `repo` is rejected.",
|
|
129
|
+
},
|
|
116
130
|
},
|
|
117
131
|
required: [],
|
|
118
132
|
},
|
|
@@ -14,6 +14,7 @@ export declare const ARCHIVE_CHAT_DESC: string;
|
|
|
14
14
|
export declare const UNARCHIVE_CHAT_DESC: string;
|
|
15
15
|
export declare const FORK_CHAT_BATCH_DESC: string;
|
|
16
16
|
export declare const CREATE_PROJECT_DESC: string;
|
|
17
|
+
export declare const PROMOTE_PROJECT_DESC: string;
|
|
17
18
|
export declare const SET_TRIGGER_DESC: string;
|
|
18
19
|
export declare const REMOVE_TRIGGER_DESC: string;
|
|
19
20
|
export declare const RUN_TRIGGER_DESC: string;
|
|
@@ -96,7 +96,7 @@ export const FORK_CHAT_BATCH_DESC = "FAN-OUT primitive: fork ONE source chat int
|
|
|
96
96
|
`"<name_prefix> <i>" (1-based). With \`model\`, every fork's kickoff turn runs on ` +
|
|
97
97
|
`that model. Up to ${FORK_BATCH_MAX} forks per call; they run ` +
|
|
98
98
|
"concurrently. Returns the source and every new child's sessionId.";
|
|
99
|
-
// ── Project tools (
|
|
99
|
+
// ── Project tools (issues #467, #470) ───────────────────────────────────────
|
|
100
100
|
export const CREATE_PROJECT_DESC = "Create a BRAND-NEW Paddock project — the workspace a keeper agent lives in — and " +
|
|
101
101
|
"register its keeper. `name` is the only required argument; the kebab-case `slug` " +
|
|
102
102
|
"is derived from it unless you pass one. Two kinds: pass a `repo` git URL for a " +
|
|
@@ -109,6 +109,19 @@ export const CREATE_PROJECT_DESC = "Create a BRAND-NEW Paddock project — the w
|
|
|
109
109
|
"pass that slug to `create_chat` to start working in it. A bad or unreachable " +
|
|
110
110
|
"repo URL fails cleanly and leaves NO half-created project behind, so it is safe " +
|
|
111
111
|
"to retry with a corrected URL.";
|
|
112
|
+
export const PROMOTE_PROJECT_DESC = "Turn an EXISTING notebook project into a REPO-BACKED one, in place — use this " +
|
|
113
|
+
"when a notes-only project has grown into (or was always meant to be) a codebase, " +
|
|
114
|
+
"instead of creating a second project and abandoning the first. `repo` (a git URL) " +
|
|
115
|
+
"is required; `project` defaults to the CURRENT project, so an agent can promote " +
|
|
116
|
+
"the workspace it is running in. The repo is cloned into a nested checkout inside " +
|
|
117
|
+
"the project, that checkout becomes the agent's working directory, and the agent is " +
|
|
118
|
+
"re-registered there. Everything already in the project is KEPT — its existing " +
|
|
119
|
+
"chats stay listed and resumable, and its OVERVIEW/CHANGELOG/settings are " +
|
|
120
|
+
"untouched. Not reversible through this MCP, and only ever notebook → repo-backed: " +
|
|
121
|
+
"a project that is ALREADY repo-backed is refused, as is the root workspace. A bad " +
|
|
122
|
+
"or unreachable repo URL fails cleanly, leaving the notebook exactly as it was, so " +
|
|
123
|
+
"it is safe to retry with a corrected URL. Returns the project's new working " +
|
|
124
|
+
"directory and whether its agent re-registered.";
|
|
112
125
|
// ── Unified trigger tools (Epic T / T3) ─────────────────────────────────────
|
|
113
126
|
export const SET_TRIGGER_DESC = "Create or update a unified project TRIGGER — a standing rule that runs an agent " +
|
|
114
127
|
"turn when something happens, keyed by `name`. Enable/disable is just this call " +
|
|
@@ -39,12 +39,14 @@ export interface SelfMcpCreatedProject {
|
|
|
39
39
|
name: string;
|
|
40
40
|
/** The project's metadata dir (OVERVIEW/CHANGELOG/`.chats` live here). */
|
|
41
41
|
dir: string;
|
|
42
|
-
/** The keeper's cwd — the nested checkout
|
|
42
|
+
/** The keeper's cwd — `path`, else the nested checkout, else `dir` (#206). */
|
|
43
43
|
workingDir: string;
|
|
44
|
-
/** Whether
|
|
45
|
-
|
|
46
|
-
/** The repo URL when
|
|
44
|
+
/** Whether Paddock curates this project's own files (#206, replaced `repoBacked`). */
|
|
45
|
+
managed: boolean;
|
|
46
|
+
/** The repo URL when one is recorded, else undefined. */
|
|
47
47
|
repo?: string;
|
|
48
|
+
/** The nominated content directory when one is set, else undefined (#206). */
|
|
49
|
+
path?: string;
|
|
48
50
|
/**
|
|
49
51
|
* Whether the keeper agent registered. The REST create path treats a registration
|
|
50
52
|
* failure as non-fatal (the project IS created), so this mirrors it — but reports
|
|
@@ -52,6 +54,13 @@ export interface SelfMcpCreatedProject {
|
|
|
52
54
|
*/
|
|
53
55
|
agentRegistered: boolean;
|
|
54
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* A promoted project as surfaced back to the agent (issue #470). Declared as an
|
|
59
|
+
* ALIAS rather than a second copy of the same fields: promotion's whole point is
|
|
60
|
+
* that the project ends up in exactly the state a repo-backed `create_project`
|
|
61
|
+
* would have produced, so the two reports must not be able to drift.
|
|
62
|
+
*/
|
|
63
|
+
export type SelfMcpPromotedProject = SelfMcpCreatedProject;
|
|
55
64
|
/** A chat as surfaced to the agent. */
|
|
56
65
|
export interface SelfMcpChat {
|
|
57
66
|
/** Owning project slug. */
|
|
@@ -190,10 +199,11 @@ export interface SelfMcpWriteContext {
|
|
|
190
199
|
/** Set (or clear) a chat's archived flag (presentational metadata only). */
|
|
191
200
|
setArchived: (projectSlug: string, sessionId: string, archived: boolean) => Promise<void>;
|
|
192
201
|
/**
|
|
193
|
-
* Whether the PROJECT tools (`create_project`, issue #467
|
|
194
|
-
* instance — the `selfMcpProjectsEnabled` gate,
|
|
195
|
-
* the
|
|
196
|
-
* binary discipline as
|
|
202
|
+
* Whether the PROJECT tools (`create_project`, issue #467; `promote_project`,
|
|
203
|
+
* issue #470) are enabled for this instance — the `selfMcpProjectsEnabled` gate,
|
|
204
|
+
* resolved by the caller. When false the tools are NOT injected at all (absent,
|
|
205
|
+
* not present-but-refusing), the same binary discipline as
|
|
206
|
+
* {@link triggersMcpEnabled}.
|
|
197
207
|
*/
|
|
198
208
|
projectsMcpEnabled: boolean;
|
|
199
209
|
/**
|
|
@@ -206,6 +216,20 @@ export interface SelfMcpWriteContext {
|
|
|
206
216
|
* non-fatal (mirroring the route) and reported via `agentRegistered`.
|
|
207
217
|
*/
|
|
208
218
|
createProject: (input: SelfMcpCreateProjectInput) => Promise<SelfMcpCreatedProject>;
|
|
219
|
+
/**
|
|
220
|
+
* Promote an EXISTING notebook project to repo-backed in place, and re-register
|
|
221
|
+
* its agent — the SAME two steps `POST /api/projects/:slug/promote` performs, in
|
|
222
|
+
* the same order (`ProjectStore.promote` then `ensureProjectAgent`), so the REST
|
|
223
|
+
* and MCP paths cannot drift (issue #470/#213). All the real work (the already-
|
|
224
|
+
* repo-backed / root-workspace / bad-URL guards, the clone into the nested
|
|
225
|
+
* checkout, the `writeYaml` commit point and its rollback to an UNTOUCHED
|
|
226
|
+
* notebook on failure) stays in the store. Throws on a promote failure; a
|
|
227
|
+
* re-registration failure is non-fatal (mirroring the route) and reported via
|
|
228
|
+
* `agentRegistered` — though it matters more here than for create, since
|
|
229
|
+
* re-registration is what re-symlinks the new working dir at the project's
|
|
230
|
+
* existing `.chats/` store so its chats stay listed and resumable.
|
|
231
|
+
*/
|
|
232
|
+
promoteProject: (projectSlug: string, repo: string) => Promise<SelfMcpPromotedProject>;
|
|
209
233
|
/**
|
|
210
234
|
* Whether the unified trigger-management MCP is enabled for THIS turn's project
|
|
211
235
|
* (Epic T / T3 — the successor to the G5 hook-MCP gate, resolved from the SAME
|
|
@@ -32,4 +32,25 @@ export declare function archiveChatHandler(write: SelfMcpWriteContext, archived:
|
|
|
32
32
|
* an unclonable repo, and still rolls the whole project dir back on a clone failure.
|
|
33
33
|
*/
|
|
34
34
|
export declare function createProjectHandler(write: SelfMcpWriteContext): (args: Record<string, unknown>) => Promise<McpToolCallResult>;
|
|
35
|
+
/**
|
|
36
|
+
* `promote_project` (issue #470) — turn an EXISTING notebook project into a
|
|
37
|
+
* repo-backed one in place, over the same `ProjectStore.promote` (#213) that
|
|
38
|
+
* `POST /api/projects/:slug/promote` uses. Shares `create_project`'s
|
|
39
|
+
* `projectsMcpEnabled` gate: same blast-radius class (it clones a caller-supplied
|
|
40
|
+
* URL and restructures a project), and this one MUTATES existing state.
|
|
41
|
+
*
|
|
42
|
+
* Pure, like its siblings: it resolves the target (defaulting to the current
|
|
43
|
+
* project, as every other project-targeting write tool does), shape-checks the URL
|
|
44
|
+
* with the very same `isValidRepoUrl` the store uses, and delegates. Everything
|
|
45
|
+
* that makes promotion safe — the already-repo-backed / root-workspace / existing-
|
|
46
|
+
* checkout-dir guards, and the rollback that leaves an untouched notebook when the
|
|
47
|
+
* clone fails — stays in the store; nothing is re-implemented here.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately NO up-front `SLUG_RE` check on the target, unlike `create_project`:
|
|
50
|
+
* there the slug names a project being INVENTED, here it names one that must
|
|
51
|
+
* already exist, so the store's own `not_found` is both the real check and the
|
|
52
|
+
* better message — and `SLUG_RE` would reject the root workspace's legitimate ""
|
|
53
|
+
* key with a misleading "invalid slug" instead of the store's explicit refusal.
|
|
54
|
+
*/
|
|
55
|
+
export declare function promoteProjectHandler(write: SelfMcpWriteContext): (args: Record<string, unknown>) => Promise<McpToolCallResult>;
|
|
35
56
|
export declare function forkChatBatchHandler(write: SelfMcpWriteContext): (args: Record<string, unknown>) => Promise<McpToolCallResult>;
|
|
@@ -171,6 +171,50 @@ export function createProjectHandler(write) {
|
|
|
171
171
|
}
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* `promote_project` (issue #470) — turn an EXISTING notebook project into a
|
|
176
|
+
* repo-backed one in place, over the same `ProjectStore.promote` (#213) that
|
|
177
|
+
* `POST /api/projects/:slug/promote` uses. Shares `create_project`'s
|
|
178
|
+
* `projectsMcpEnabled` gate: same blast-radius class (it clones a caller-supplied
|
|
179
|
+
* URL and restructures a project), and this one MUTATES existing state.
|
|
180
|
+
*
|
|
181
|
+
* Pure, like its siblings: it resolves the target (defaulting to the current
|
|
182
|
+
* project, as every other project-targeting write tool does), shape-checks the URL
|
|
183
|
+
* with the very same `isValidRepoUrl` the store uses, and delegates. Everything
|
|
184
|
+
* that makes promotion safe — the already-repo-backed / root-workspace / existing-
|
|
185
|
+
* checkout-dir guards, and the rollback that leaves an untouched notebook when the
|
|
186
|
+
* clone fails — stays in the store; nothing is re-implemented here.
|
|
187
|
+
*
|
|
188
|
+
* Deliberately NO up-front `SLUG_RE` check on the target, unlike `create_project`:
|
|
189
|
+
* there the slug names a project being INVENTED, here it names one that must
|
|
190
|
+
* already exist, so the store's own `not_found` is both the real check and the
|
|
191
|
+
* better message — and `SLUG_RE` would reject the root workspace's legitimate ""
|
|
192
|
+
* key with a misleading "invalid slug" instead of the store's explicit refusal.
|
|
193
|
+
*/
|
|
194
|
+
export function promoteProjectHandler(write) {
|
|
195
|
+
return async (args) => {
|
|
196
|
+
try {
|
|
197
|
+
const projectArg = typeof args.project === "string" ? args.project.trim() : "";
|
|
198
|
+
const projectSlug = projectArg.length > 0 ? projectArg : write.currentProjectSlug;
|
|
199
|
+
const repo = typeof args.repo === "string" ? args.repo.trim() : "";
|
|
200
|
+
if (!repo) {
|
|
201
|
+
return fail("Error: `repo` is required (the git URL to back the project with).");
|
|
202
|
+
}
|
|
203
|
+
if (!isValidRepoUrl(repo)) {
|
|
204
|
+
return fail(`Error: invalid repo URL "${repo}" — expected an https://, git://, ssh:// or ` +
|
|
205
|
+
"git@host:owner/repo url.");
|
|
206
|
+
}
|
|
207
|
+
const project = await write.promoteProject(projectSlug, repo);
|
|
208
|
+
return ok({ promoted: true, ...project });
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Same hygiene as create_project: a clone failure arrives as git's whole argv
|
|
212
|
+
// (`Command failed: git clone -- <url> <dest>`), and the agent needs the repo
|
|
213
|
+
// URL + git's reason, not our on-disk layout.
|
|
214
|
+
return fail(`Error promoting project: ${truncateText(redactPaths(errText(error)))}`);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
174
218
|
export function forkChatBatchHandler(write) {
|
|
175
219
|
return async (args) => {
|
|
176
220
|
try {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - `self-mcp-util.ts` — result envelopes, clamps, arg coercion + caps
|
|
13
13
|
* - `self-mcp-descriptions.ts` — the agent-facing `*_DESC` prose
|
|
14
14
|
* - `self-mcp-read.ts` — list_projects / list_chats / read_chat handlers
|
|
15
|
-
* - `self-mcp-write.ts` — create/fork/send/archive/fork-batch +
|
|
15
|
+
* - `self-mcp-write.ts` — create/fork/send/archive/fork-batch + project handlers
|
|
16
16
|
* - `self-mcp-triggers.ts` — list/set/remove/run trigger handlers
|
|
17
17
|
*
|
|
18
18
|
* ── How it reaches the keeper ───────────────────────────────────────────────
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
*/
|
|
33
33
|
import type { InjectedMcpServerDef } from "@herdctl/core";
|
|
34
34
|
import type { SelfMcpContext, SelfMcpWriteContext } from "./self-mcp-types.js";
|
|
35
|
-
export type { SelfMcpProject, SelfMcpChat, SelfMcpMessage, SelfMcpTrigger, SelfMcpContext, SelfMcpWriteContext, SelfMcpCreateProjectInput, SelfMcpCreatedProject, } from "./self-mcp-types.js";
|
|
35
|
+
export type { SelfMcpProject, SelfMcpChat, SelfMcpMessage, SelfMcpTrigger, SelfMcpContext, SelfMcpWriteContext, SelfMcpCreateProjectInput, SelfMcpCreatedProject, SelfMcpPromotedProject, } from "./self-mcp-types.js";
|
|
36
36
|
export { FORK_BATCH_MAX, redactPaths, READ_CHAT_DEFAULT_LIMIT, READ_CHAT_MAX_LIMIT, READ_CHAT_MAX_TEXT, clampLimit, truncateText, resolveModelArg, coercePrompts, coerceToolList, coerceBoolean, } from "./self-mcp-util.js";
|
|
37
37
|
/** Assembly-time options for {@link selfMcpServerDef}. */
|
|
38
38
|
export interface SelfMcpServerDefOptions {
|
|
@@ -50,8 +50,8 @@ export interface SelfMcpServerDefOptions {
|
|
|
50
50
|
* write flag is on), the WRITE tools (create_chat/fork_chat/send_message/
|
|
51
51
|
* archive_chat/unarchive_chat/fork_chat_batch) are appended too; omit it for
|
|
52
52
|
* unchanged read-only behavior. Two further blocks hang off independent gates ON
|
|
53
|
-
* that write context: {@link SelfMcpWriteContext.projectsMcpEnabled} (
|
|
54
|
-
* appends the project
|
|
53
|
+
* that write context: {@link SelfMcpWriteContext.projectsMcpEnabled} (issues #467,
|
|
54
|
+
* #470) appends the project tools (create_project/promote_project), and
|
|
55
55
|
* {@link SelfMcpWriteContext.triggersMcpEnabled} (the per-project trigger-MCP
|
|
56
56
|
* opt-in, Epic T / T3) appends the unified trigger-management tools (list_triggers/
|
|
57
57
|
* set_trigger/remove_trigger/run_trigger). Inject under
|
|
@@ -75,11 +75,12 @@ export declare const SELF_MCP_WRITE_TOOL_NAMES: {
|
|
|
75
75
|
readonly forkChatBatch: "mcp__paddock_manage__fork_chat_batch";
|
|
76
76
|
};
|
|
77
77
|
/**
|
|
78
|
-
* The fully-qualified
|
|
79
|
-
* write context has {@link SelfMcpWriteContext.projectsMcpEnabled} on.
|
|
78
|
+
* The fully-qualified names of the PROJECT tools (issues #467, #470), present only
|
|
79
|
+
* when a write context has {@link SelfMcpWriteContext.projectsMcpEnabled} on.
|
|
80
80
|
*/
|
|
81
81
|
export declare const SELF_MCP_PROJECT_TOOL_NAMES: {
|
|
82
82
|
readonly createProject: "mcp__paddock_manage__create_project";
|
|
83
|
+
readonly promoteProject: "mcp__paddock_manage__promote_project";
|
|
83
84
|
};
|
|
84
85
|
/**
|
|
85
86
|
* The fully-qualified names of the Epic T / T3 unified trigger-management tools
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FORK_BATCH_MAX, READ_CHAT_DEFAULT_LIMIT, READ_CHAT_MAX_LIMIT, } from "./self-mcp-util.js";
|
|
2
|
-
import { MODEL_ARG_DESC, LIST_PROJECTS_DESC, LIST_CHATS_DESC, READ_CHAT_DESC, CREATE_CHAT_DESC, FORK_CHAT_DESC, SEND_MESSAGE_DESC, ARCHIVE_CHAT_DESC, UNARCHIVE_CHAT_DESC, FORK_CHAT_BATCH_DESC, CREATE_PROJECT_DESC, SET_TRIGGER_DESC, REMOVE_TRIGGER_DESC, RUN_TRIGGER_DESC, LIST_TRIGGERS_DESC, } from "./self-mcp-descriptions.js";
|
|
2
|
+
import { MODEL_ARG_DESC, LIST_PROJECTS_DESC, LIST_CHATS_DESC, READ_CHAT_DESC, CREATE_CHAT_DESC, FORK_CHAT_DESC, SEND_MESSAGE_DESC, ARCHIVE_CHAT_DESC, UNARCHIVE_CHAT_DESC, FORK_CHAT_BATCH_DESC, CREATE_PROJECT_DESC, PROMOTE_PROJECT_DESC, SET_TRIGGER_DESC, REMOVE_TRIGGER_DESC, RUN_TRIGGER_DESC, LIST_TRIGGERS_DESC, } from "./self-mcp-descriptions.js";
|
|
3
3
|
import { listProjectsHandler, listChatsHandler, readChatHandler } from "./self-mcp-read.js";
|
|
4
|
-
import { createChatHandler, forkChatHandler, sendMessageHandler, archiveChatHandler, forkChatBatchHandler, createProjectHandler, } from "./self-mcp-write.js";
|
|
4
|
+
import { createChatHandler, forkChatHandler, sendMessageHandler, archiveChatHandler, forkChatBatchHandler, createProjectHandler, promoteProjectHandler, } from "./self-mcp-write.js";
|
|
5
5
|
import { PROJECT_STATUSES } from "./project-types.js";
|
|
6
6
|
import { listTriggersHandler, setTriggerHandler, removeTriggerHandler, runTriggerHandler, } from "./self-mcp-triggers.js";
|
|
7
7
|
export { FORK_BATCH_MAX, redactPaths, READ_CHAT_DEFAULT_LIMIT, READ_CHAT_MAX_LIMIT, READ_CHAT_MAX_TEXT, clampLimit, truncateText, resolveModelArg, coercePrompts, coerceToolList, coerceBoolean, } from "./self-mcp-util.js";
|
|
@@ -204,11 +204,15 @@ function writeTools(write) {
|
|
|
204
204
|
];
|
|
205
205
|
}
|
|
206
206
|
/**
|
|
207
|
-
* The PROJECT tools (issue #467 — `create_project
|
|
207
|
+
* The PROJECT tools (issue #467 — `create_project`; issue #470 —
|
|
208
|
+
* `promote_project`). Appended only when
|
|
208
209
|
* {@link SelfMcpWriteContext.projectsMcpEnabled} is on: the same coarse binary gate
|
|
209
|
-
* as the trigger tools (when off the
|
|
210
|
-
* own flag because — unlike every other write tool —
|
|
211
|
-
* instance-level state and
|
|
210
|
+
* as the trigger tools (when off the tools are ABSENT, not present-but-refusing).
|
|
211
|
+
* Their own flag because — unlike every other write tool — these provision or
|
|
212
|
+
* restructure instance-level state and clone a caller-supplied git URL. Promotion
|
|
213
|
+
* shares the flag rather than getting a third one: it is the same blast-radius
|
|
214
|
+
* class, and an operator who has granted "this agent may provision projects" has
|
|
215
|
+
* already made the decision promotion asks for.
|
|
212
216
|
*/
|
|
213
217
|
function projectTools(write) {
|
|
214
218
|
return [
|
|
@@ -244,6 +248,27 @@ function projectTools(write) {
|
|
|
244
248
|
},
|
|
245
249
|
handler: createProjectHandler(write),
|
|
246
250
|
},
|
|
251
|
+
{
|
|
252
|
+
name: "promote_project",
|
|
253
|
+
description: PROMOTE_PROJECT_DESC,
|
|
254
|
+
inputSchema: {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
project: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "Slug of the NOTEBOOK project to promote. Omit to promote the current project.",
|
|
260
|
+
},
|
|
261
|
+
repo: {
|
|
262
|
+
type: "string",
|
|
263
|
+
description: "Git URL to back the project with (https://, git://, ssh:// or " +
|
|
264
|
+
"git@host:owner/repo). Cloned into a nested checkout that becomes the " +
|
|
265
|
+
"agent's working directory. Required.",
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
required: ["repo"],
|
|
269
|
+
},
|
|
270
|
+
handler: promoteProjectHandler(write),
|
|
271
|
+
},
|
|
247
272
|
];
|
|
248
273
|
}
|
|
249
274
|
/**
|
|
@@ -386,8 +411,8 @@ function triggerTools(write) {
|
|
|
386
411
|
* write flag is on), the WRITE tools (create_chat/fork_chat/send_message/
|
|
387
412
|
* archive_chat/unarchive_chat/fork_chat_batch) are appended too; omit it for
|
|
388
413
|
* unchanged read-only behavior. Two further blocks hang off independent gates ON
|
|
389
|
-
* that write context: {@link SelfMcpWriteContext.projectsMcpEnabled} (
|
|
390
|
-
* appends the project
|
|
414
|
+
* that write context: {@link SelfMcpWriteContext.projectsMcpEnabled} (issues #467,
|
|
415
|
+
* #470) appends the project tools (create_project/promote_project), and
|
|
391
416
|
* {@link SelfMcpWriteContext.triggersMcpEnabled} (the per-project trigger-MCP
|
|
392
417
|
* opt-in, Epic T / T3) appends the unified trigger-management tools (list_triggers/
|
|
393
418
|
* set_trigger/remove_trigger/run_trigger). Inject under
|
|
@@ -428,11 +453,12 @@ export const SELF_MCP_WRITE_TOOL_NAMES = {
|
|
|
428
453
|
forkChatBatch: `mcp__${SERVER_NAME}__fork_chat_batch`,
|
|
429
454
|
};
|
|
430
455
|
/**
|
|
431
|
-
* The fully-qualified
|
|
432
|
-
* write context has {@link SelfMcpWriteContext.projectsMcpEnabled} on.
|
|
456
|
+
* The fully-qualified names of the PROJECT tools (issues #467, #470), present only
|
|
457
|
+
* when a write context has {@link SelfMcpWriteContext.projectsMcpEnabled} on.
|
|
433
458
|
*/
|
|
434
459
|
export const SELF_MCP_PROJECT_TOOL_NAMES = {
|
|
435
460
|
createProject: `mcp__${SERVER_NAME}__create_project`,
|
|
461
|
+
promoteProject: `mcp__${SERVER_NAME}__promote_project`,
|
|
436
462
|
};
|
|
437
463
|
/**
|
|
438
464
|
* The fully-qualified names of the Epic T / T3 unified trigger-management tools
|
|
@@ -285,11 +285,16 @@ export class SweepService {
|
|
|
285
285
|
// NOCHANGE leaves it untouched. A write failure is non-fatal (OVERVIEW/
|
|
286
286
|
// CHANGELOG already landed; the watermark should still advance) → warn.
|
|
287
287
|
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// dirty the checkout and, if pushed,
|
|
291
|
-
// CHANGELOG.md are still curated
|
|
292
|
-
|
|
288
|
+
// UNMANAGED projects (issue #206, formerly keyed on `repoBacked`): the
|
|
289
|
+
// CLAUDE.md belongs to the working directory's own source control — the
|
|
290
|
+
// sweeper must NEVER write it (that would dirty the checkout and, if pushed,
|
|
291
|
+
// leak curation upstream). OVERVIEW.md + CHANGELOG.md are still curated
|
|
292
|
+
// (sidecarred into the metadata dir), just not CLAUDE.md.
|
|
293
|
+
//
|
|
294
|
+
// `managed` is the precise fact here, where `repoBacked` was a proxy for it:
|
|
295
|
+
// what decides this is whether Paddock looks after the project's files, not
|
|
296
|
+
// whether a git repo happens to sit behind them.
|
|
297
|
+
if (parsed.claude !== null && project.managed) {
|
|
293
298
|
const bounded = enforceHeadBudget(parsed.claude, budgetChars(budget, "claudeMaxTokens"));
|
|
294
299
|
await this.projects.writeClaudeCurated(project.slug, bounded).catch((err) => {
|
|
295
300
|
this.log.warn({ err, slug: project.slug }, "sweep: CLAUDE.md write failed (non-fatal)");
|
package/packages/web/dist/assets/{InstanceConfigPage-CCk2hVT9.js → InstanceConfigPage-DcprTH2W.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-
|
|
1
|
+
import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-DVSsta3B.js";function $(){const[t,s]=o.useState(null),[l,i]=o.useState(!0),[r,c]=o.useState(null),[d,m]=o.useState({}),[h,k]=o.useState(!1),[j,p]=o.useState(null),[N,u]=o.useState(!1);o.useEffect(()=>{let a=!0;return i(!0),f.getInstanceConfig().then(n=>{a&&(s(n),c(null))}).catch(n=>a&&c(n instanceof Error?n.message:String(n))).finally(()=>a&&i(!1)),()=>{a=!1}},[]);const v=(a,n)=>{u(!1),p(null),m(g=>({...g,[a]:n}))},S=a=>Object.prototype.hasOwnProperty.call(d,a.key)?d[a.key]:a.value,y=o.useMemo(()=>t?t.groups.flatMap(a=>a.fields):[],[t]),x=o.useMemo(()=>y.filter(a=>a.editable&&!a.envOverridden).filter(a=>Object.prototype.hasOwnProperty.call(d,a.key)).filter(a=>!B(d[a.key],a.value)).map(a=>a.key),[y,d]),w=async()=>{if(x.length===0)return;const a={};for(const n of x)a[n]=d[n];k(!0),p(null);try{await f.updateInstanceConfig(a);const n=await f.getInstanceConfig();s(n),m({}),u(!0)}catch(n){p(n instanceof A||n instanceof Error?n.message:String(n))}finally{k(!1)}},C=()=>{m({}),p(null),u(!1)};return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:"mx-auto max-w-2xl",children:[e.jsx(L,{saved:N,configPath:t==null?void 0:t.configPath}),l&&e.jsx("p",{className:"text-sm text-paddock-500",children:"Loading…"}),r&&e.jsxs("p",{className:"text-sm text-red-600 dark:text-red-400",role:"alert",children:["Failed to load settings: ",r]}),t&&t.groups.map(a=>e.jsx(M,{title:a.label,description:a.description,children:e.jsx("div",{className:"grid grid-cols-1 gap-4",children:a.fields.map(n=>e.jsx(P,{field:n,value:S(n),onChange:g=>v(n.key,g)},n.key))})},a.id))]})}),t&&e.jsxs("footer",{className:"flex items-center gap-3 border-t border-paddock-200 px-4 py-3 dark:border-paddock-800 sm:px-6",children:[j&&e.jsxs("span",{className:"flex items-center gap-1.5 text-[13px] text-red-600 dark:text-red-400",role:"alert",children:[e.jsx(b,{width:14,height:14,className:"shrink-0"}),j]}),e.jsx("span",{className:"ml-auto text-[12px] text-paddock-400",children:x.length>0?`${x.length} unsaved change${x.length===1?"":"s"}`:"No changes"}),e.jsx("button",{type:"button",className:"btn-subtle",onClick:C,disabled:x.length===0||h,children:"Reset"}),e.jsx("button",{type:"button",className:"btn-primary",onClick:w,disabled:x.length===0||h,children:h?"Saving…":"Save changes"})]})]})}function L({saved:t,configPath:s}){return e.jsxs("div",{role:"status",className:`mb-6 flex items-start gap-2 rounded-lg border px-3 py-2.5 text-[13px] leading-snug ${t?"border-emerald-300 bg-emerald-50 text-emerald-800 dark:border-emerald-700/60 dark:bg-emerald-900/20 dark:text-emerald-300":"border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300"}`,children:[t?e.jsx(E,{width:15,height:15,className:"mt-0.5 shrink-0"}):e.jsx(b,{width:15,height:15,className:"mt-0.5 shrink-0"}),e.jsx("span",{children:t?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Saved to disk."})," These changes take effect only after the server restarts."]}):e.jsxs(e.Fragment,{children:["Changes here are written to ",e.jsx("code",{className:"font-mono text-[12px]",children:T(s)})," ","and take effect only after the server restarts — the running instance keeps its current config until then."]})})]})}function M({title:t,description:s,children:l}){return e.jsxs("section",{className:"mb-6",children:[e.jsx("h2",{className:"text-sm font-semibold uppercase tracking-wide text-paddock-500",children:t}),s&&e.jsx("p",{className:"mb-3 mt-0.5 text-[13px] text-paddock-500",children:s}),e.jsx("div",{className:`card ${s?"":"mt-2"}`,children:l})]})}function P({field:t,value:s,onChange:l}){const i=!t.editable||t.envOverridden,r=`cfg-${t.key}`;return e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("label",{htmlFor:i?void 0:r,className:"text-sm font-medium",children:[t.label,t.sensitive&&e.jsx("span",{className:"ml-1.5 align-middle text-[10px] font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-400",children:"sensitive"})]}),t.type==="boolean"&&!i&&e.jsx("input",{id:r,type:"checkbox",className:"h-4 w-4 accent-accent",checked:!!s,onChange:c=>l(c.target.checked)})]}),t.help&&e.jsx("p",{className:"mt-0.5 text-[12px] leading-snug text-paddock-500",children:t.help}),i?e.jsx(V,{field:t,value:s}):t.type!=="boolean"&&e.jsx(R,{field:t,value:s,onChange:l,inputId:r}),t.envOverridden&&e.jsxs("p",{className:"mt-1 flex items-start gap-1.5 text-[12px] leading-snug text-amber-600 dark:text-amber-400",children:[e.jsx(b,{width:13,height:13,className:"mt-0.5 shrink-0"}),e.jsxs("span",{children:["Overridden by environment variable ",e.jsx("code",{className:"font-mono",children:t.envVar})," — edit that env var (and restart) to change it."]})]})]})}function R({field:t,value:s,onChange:l,inputId:i}){if(t.type==="enum")return e.jsx("select",{id:i,className:"input mt-1.5",value:String(s??""),onChange:r=>l(r.target.value),children:(t.enumValues??[]).map(r=>e.jsx("option",{value:r,children:r},r))});if(t.type==="number")return e.jsx("input",{id:i,type:"number",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"default":String(t.default),onChange:r=>l(r.target.value===""?null:Number(r.target.value))});if(t.type==="text"){const r=s==null||s===t.default,c=s==null?String(t.default??""):String(s),d=typeof t.default=="string"&&!r;return e.jsxs("div",{className:"mt-1.5",children:[e.jsx("textarea",{id:i,className:"input min-h-[9rem] resize-y font-mono text-[12px] leading-relaxed",rows:10,spellCheck:!1,value:c,onChange:m=>l(m.target.value)}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-[12px] text-paddock-500",children:[e.jsx("span",{children:r?"Using the built-in default.":s===""?"Empty — nothing will be appended.":`${c.length.toLocaleString()} characters.`}),d&&e.jsx("button",{type:"button",className:"shrink-0 underline underline-offset-2 hover:text-paddock-700 dark:hover:text-paddock-300",onClick:()=>l(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const r=Array.isArray(s)?s.join(", "):String(s??"");return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:r,onChange:c=>l(c.target.value.split(",").map(d=>d.trim()).filter(Boolean))})}return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"":String(t.default),onChange:r=>l(r.target.value)})}function V({field:t,value:s}){return e.jsx("div",{className:"mt-1.5 rounded-md border border-dashed border-paddock-300 bg-paddock-50 px-2.5 py-1.5 text-[13px] text-paddock-600 dark:border-paddock-700 dark:bg-paddock-900/40 dark:text-paddock-300",children:t.type==="boolean"?e.jsx("span",{className:"font-mono",children:s?"true":"false"}):s==null||s===""?e.jsx("span",{className:"italic text-paddock-400",children:t.type==="text"&&s===""?"(empty — nothing appended)":"(not set)"}):t.type==="text"?e.jsx("span",{className:"block max-h-64 overflow-y-auto whitespace-pre-wrap font-mono text-[12px] leading-relaxed",children:String(s)}):e.jsx("span",{className:"break-all font-mono",children:Array.isArray(s)?s.join(", "):String(s)})})}function B(t,s){return Array.isArray(t)&&Array.isArray(s)?t.length===s.length&&t.every((l,i)=>l===s[i]):t===s}function T(t){if(!t)return"paddock.config.yaml";const s=t.split("/");return s[s.length-1]||t}function z(){const t=O(),s=(t==null?void 0:t.openNav)??(()=>{});return e.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[e.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-paddock-200 px-3 pb-2.5 dark:border-paddock-800 sm:px-6 lg:py-4",children:[e.jsx("button",{type:"button",onClick:s,"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:e.jsx(F,{width:20,height:20})}),e.jsx(I,{width:18,height:18,className:"shrink-0 text-paddock-400"}),e.jsx("h1",{className:"text-[15px] font-semibold tracking-tight",children:"Config"})]}),e.jsx($,{})]})}export{z as InstanceConfigPage};
|
|
2
2
|
|