@edspencer/paddock 0.64.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/management-ops.js +40 -1
- package/packages/server/dist/management-policy.d.ts +4 -3
- package/packages/server/dist/management-policy.js +5 -2
- package/packages/server/dist/projects.d.ts +12 -3
- package/packages/server/dist/projects.js +14 -5
- 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 +26 -4
- 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/package.json +1 -1
- package/packages/web/dist/assets/{InstanceConfigPage-Blr1a-hx.js → InstanceConfigPage-DcprTH2W.js} +1 -1
- package/packages/web/dist/assets/{MarkdownRenderer-R6YzfI9Y.js → MarkdownRenderer-Ri1LwAUN.js} +1 -1
- package/packages/web/dist/assets/{ProjectRedirect-Cy-7Thc-.js → ProjectRedirect-Dze5H8Xy.js} +1 -1
- package/packages/web/dist/assets/{ProjectView-bMX4GEau.js → ProjectView-PrA8purE.js} +3 -3
- package/packages/web/dist/assets/{ProjectsGrid-C9XHf4eS.js → ProjectsGrid-BidsM7ah.js} +1 -1
- package/packages/web/dist/assets/{arc-B_42F7Yk.js → arc-eQ3ryE9c.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BAXzSOyw.js → architectureDiagram-3BPJPVTR-Dc6Tj2Ai.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-BdYEdA-3.js → blockDiagram-GPEHLZMM-DBX5pma_.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-Cc4o15R5.js → c4Diagram-AAUBKEIU-CnWrtTpa.js} +1 -1
- package/packages/web/dist/assets/channel-B4sED4OP.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-CeuxHjIm.js → chunk-2J33WTMH-2qmLvtsr.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-DnZfrAaV.js → chunk-4BX2VUAB-Dn-LUDMV.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-DFE6UYOT.js → chunk-55IACEB6-DB0augUY.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-YVHQ5cHc.js → chunk-727SXJPM-BPQQM0i8.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-5BYSbg-F.js → chunk-AQP2D5EJ-DOhfwA-x.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-HBcelndR.js → chunk-FMBD7UC4-CatHHdFD.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-2AwEsdxJ.js → chunk-ND2GUHAM-B645aAlG.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-BPpaZ6ct.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-Cu_4vw1U.js → core-B4t7D8Om.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-DWkF0NrF.js → cose-bilkent-S5V4N54A-BLdKDAwa.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-DTgNmO9I.js → dagre-BM42HDAG-CbTbTVKv.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-DC28b7YS.js → diagram-2AECGRRQ-CorSS29_.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-_WvuLno6.js → diagram-5GNKFQAL-PLKivts0.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-BEYxPfWq.js → diagram-KO2AKTUF-CUSIBacZ.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-Biyev7x0.js → diagram-LMA3HP47-CZ69mAZs.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-u47NLW3S.js → diagram-OG6HWLK6-Cmf042Rh.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-Bud2ISlK.js → erDiagram-TEJ5UH35-DyIKGj6e.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-D-18vLQ1.js → flowDiagram-I6XJVG4X-B79pLdLD.js} +1 -1
- package/packages/web/dist/assets/{format-CQ2BDlby.js → format-C5nhFL8q.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-CzyRX39N.js → ganttDiagram-6RSMTGT7-BFTR_oNA.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-DLogfrjg.js → gitGraphDiagram-PVQCEYII-DdGUlJxS.js} +1 -1
- package/packages/web/dist/assets/{index-B0gJ0qWe.js → index-DVSsta3B.js} +2 -2
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-OgNPt6RG.js → infoDiagram-5YYISTIA-B80FyUOy.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-CrM0t0_X.js → ishikawaDiagram-YF4QCWOH-_wRlphn9.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-xbxfHfwR.js → journeyDiagram-JHISSGLW-qu4yvjW8.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-DpszBaZb.js → kanban-definition-UN3LZRKU-D2DEUp2-.js} +1 -1
- package/packages/web/dist/assets/{linear-BLujdbjQ.js → linear-D-CLRUEW.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-DzEhy9Qq.js → mermaid.core-DdihBJl4.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-DaH3Lmoo.js → mindmap-definition-RKZ34NQL-BEh_TDBf.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-BxeW1kuf.js → pieDiagram-4H26LBE5-BbrmLVaZ.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-BKs2CT0r.js → quadrantDiagram-W4KKPZXB-DfVuygAv.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CVdO-hXD.js → requirementDiagram-4Y6WPE33-BHp2QpZt.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-yusj_kJC.js → sankeyDiagram-5OEKKPKP-D2G6lRMY.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C0h-9c8Y.js → sequenceDiagram-3UESZ5HK-Dj-jzR-c.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-BSBY8Psb.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-DEFUdNb8.js → timeline-definition-PNZ67QCA-DE4gGjhP.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-DunhMqxK.js → vennDiagram-CIIHVFJN-C9QNrl92.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-CXJsBGKl.js → wardley-L42UT6IY-U2fSGy-d.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DfT_-dqX.js → wardleyDiagram-YWT4CUSO-B9RhObdH.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-BysTtULW.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-BZnRb_8C.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-BGCpL0-K.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-BGCpL0-K.js +0 -2
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-4DGE0Z1g.js +0 -2
package/package.json
CHANGED
|
@@ -314,6 +314,37 @@ export function buildManagementOps(ctx, params) {
|
|
|
314
314
|
agentRegistered,
|
|
315
315
|
};
|
|
316
316
|
},
|
|
317
|
+
// Promotion (#470), sharing create's gate. Same discipline: the SAME two calls,
|
|
318
|
+
// in the same order, that `POST /api/projects/:slug/promote` makes — store
|
|
319
|
+
// promote, then agent re-registration. Every guard (already unmanaged, the
|
|
320
|
+
// root workspace, an existing checkout dir, a bad URL) and the rollback to an
|
|
321
|
+
// untouched notebook on clone failure live in `ProjectStore.promote` (#213).
|
|
322
|
+
promoteProject: async (projectSlug, repo) => {
|
|
323
|
+
const project = await deps.projects.promote(projectSlug, repo);
|
|
324
|
+
// Non-fatal, as in the route — but it matters more than it does for create:
|
|
325
|
+
// re-registering is what re-symlinks the NEW working dir at the project's
|
|
326
|
+
// existing `.chats/` store, which is what keeps its chats listed + resumable.
|
|
327
|
+
let agentRegistered = true;
|
|
328
|
+
try {
|
|
329
|
+
await deps.herdctl.ensureProjectAgent(project);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
agentRegistered = false;
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
slug: project.slug,
|
|
336
|
+
name: project.name,
|
|
337
|
+
dir: project.dir,
|
|
338
|
+
workingDir: project.workingDir,
|
|
339
|
+
// #206 replaced `repoBacked` with the `managed` axis. Promotion always
|
|
340
|
+
// lands on `managed: false` — that IS what promoting means now — but read
|
|
341
|
+
// it off the project rather than hard-coding, so this reports what the
|
|
342
|
+
// store actually did.
|
|
343
|
+
managed: project.managed,
|
|
344
|
+
...(project.repo ? { repo: project.repo } : {}),
|
|
345
|
+
agentRegistered,
|
|
346
|
+
};
|
|
347
|
+
},
|
|
317
348
|
triggersMcpEnabled: includeTriggers,
|
|
318
349
|
listTriggers: async (projectSlug) => {
|
|
319
350
|
if (!deps.triggers)
|
|
@@ -405,12 +436,20 @@ export function enforceManagementPolicy(ops, principal) {
|
|
|
405
436
|
const write = {
|
|
406
437
|
currentProjectSlug: w.currentProjectSlug,
|
|
407
438
|
currentSessionId: w.currentSessionId,
|
|
439
|
+
// The project block is offered when EITHER of its verbs is granted; the
|
|
440
|
+
// per-tool filter (`managementToolFilter`) is what hides the other one, so a
|
|
441
|
+
// client scoped to just one of them isn't denied both.
|
|
442
|
+
projectsMcpEnabled: w.projectsMcpEnabled &&
|
|
443
|
+
(isOperationAllowed(scope, "create_project") || isOperationAllowed(scope, "promote_project")),
|
|
408
444
|
// Instance-level, so there is no project to scope-check — only the verb.
|
|
409
|
-
projectsMcpEnabled: w.projectsMcpEnabled && isOperationAllowed(scope, "create_project"),
|
|
410
445
|
createProject: (input) => {
|
|
411
446
|
assertOperation(principal, "create_project");
|
|
412
447
|
return w.createProject(input);
|
|
413
448
|
},
|
|
449
|
+
// Promotion DOES name an existing project, so unlike create it is scope-checked
|
|
450
|
+
// against `projects` too — a client confined to one project must not restructure
|
|
451
|
+
// another.
|
|
452
|
+
promoteProject: (projectSlug, repo) => guard("promote_project", projectSlug, () => w.promoteProject(projectSlug, repo)),
|
|
414
453
|
createChat: (projectSlug, prompt, o) => guard("create_chat", projectSlug, () => w.createChat(projectSlug, prompt, o)),
|
|
415
454
|
// NOTE: `fork_chat_batch` is a convenience wrapper that calls this same
|
|
416
455
|
// callback N times, so it is gated on `fork_chat` here AND on
|
|
@@ -66,7 +66,7 @@ export interface ManagementPrincipal {
|
|
|
66
66
|
/** Read-only operations: no state change, no turn started. */
|
|
67
67
|
export declare const READ_OPERATIONS: readonly ["list_projects", "list_chats", "read_chat"];
|
|
68
68
|
/** Chat write operations. The first four START TURNS (see the RCE note above). */
|
|
69
|
-
export declare const WRITE_OPERATIONS: readonly ["create_project", "create_chat", "fork_chat", "send_message", "fork_chat_batch", "archive_chat", "unarchive_chat"];
|
|
69
|
+
export declare const WRITE_OPERATIONS: readonly ["create_project", "promote_project", "create_chat", "fork_chat", "send_message", "fork_chat_batch", "archive_chat", "unarchive_chat"];
|
|
70
70
|
/** Unified trigger operations (Epic T / T3). `list_triggers` is the read one. */
|
|
71
71
|
export declare const TRIGGER_OPERATIONS: readonly ["list_triggers", "set_trigger", "remove_trigger", "run_trigger"];
|
|
72
72
|
/** Every operation the management surface knows about. */
|
|
@@ -81,8 +81,9 @@ export declare const ALL_OPERATIONS: readonly string[];
|
|
|
81
81
|
/**
|
|
82
82
|
* Operations that grant code execution on the host, and so define the risk class
|
|
83
83
|
* a write scope really is. A superset of {@link TURN_SPAWNING_OPERATIONS}:
|
|
84
|
-
* `create_project`
|
|
85
|
-
*
|
|
84
|
+
* `create_project`/`promote_project` start no turn, but they clone a
|
|
85
|
+
* CALLER-SUPPLIED git URL and create (or restructure) instance-level state, which
|
|
86
|
+
* belongs in the same warning.
|
|
86
87
|
*/
|
|
87
88
|
export declare const HIGH_RISK_OPERATIONS: readonly string[];
|
|
88
89
|
export declare const TURN_SPAWNING_OPERATIONS: readonly string[];
|
|
@@ -34,6 +34,7 @@ export const READ_OPERATIONS = ["list_projects", "list_chats", "read_chat"];
|
|
|
34
34
|
/** Chat write operations. The first four START TURNS (see the RCE note above). */
|
|
35
35
|
export const WRITE_OPERATIONS = [
|
|
36
36
|
"create_project",
|
|
37
|
+
"promote_project",
|
|
37
38
|
"create_chat",
|
|
38
39
|
"fork_chat",
|
|
39
40
|
"send_message",
|
|
@@ -64,11 +65,13 @@ export const ALL_OPERATIONS = [
|
|
|
64
65
|
/**
|
|
65
66
|
* Operations that grant code execution on the host, and so define the risk class
|
|
66
67
|
* a write scope really is. A superset of {@link TURN_SPAWNING_OPERATIONS}:
|
|
67
|
-
* `create_project`
|
|
68
|
-
*
|
|
68
|
+
* `create_project`/`promote_project` start no turn, but they clone a
|
|
69
|
+
* CALLER-SUPPLIED git URL and create (or restructure) instance-level state, which
|
|
70
|
+
* belongs in the same warning.
|
|
69
71
|
*/
|
|
70
72
|
export const HIGH_RISK_OPERATIONS = [
|
|
71
73
|
"create_project",
|
|
74
|
+
"promote_project",
|
|
72
75
|
"create_chat",
|
|
73
76
|
"fork_chat",
|
|
74
77
|
"fork_chat_batch",
|
|
@@ -67,9 +67,18 @@ export declare class ProjectStore {
|
|
|
67
67
|
* root, and that implicit safety is gone.
|
|
68
68
|
*
|
|
69
69
|
* So the check is made explicit and centralised here rather than re-derived at
|
|
70
|
-
* each call site: an `fs.rm` that escapes throws instead of deleting.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* each call site: an `fs.rm` that escapes throws instead of deleting. Every
|
|
71
|
+
* caller already only passes in-root paths — this exists so that stays true
|
|
72
|
+
* when someone later adds another one.
|
|
73
|
+
*
|
|
74
|
+
* `promote()`'s two rollbacks route through here as well. Their target is
|
|
75
|
+
* `<projectsRoot>/<slug>/<checkoutName>`, which is in-root by construction, so
|
|
76
|
+
* the guard is inert there today — that is the point. This is the choke point
|
|
77
|
+
* the comment above promised, and a recursive delete that skips it is exactly
|
|
78
|
+
* the drift it exists to prevent. {@link rollback} is the deliberate exception:
|
|
79
|
+
* it deletes paths that may legitimately sit OUTSIDE the root (a user-nominated
|
|
80
|
+
* clone target), and is safe by a different mechanism — it only ever removes
|
|
81
|
+
* directories this operation created.
|
|
73
82
|
*/
|
|
74
83
|
private rmInsideRoot;
|
|
75
84
|
/**
|
|
@@ -202,9 +202,18 @@ export class ProjectStore {
|
|
|
202
202
|
* root, and that implicit safety is gone.
|
|
203
203
|
*
|
|
204
204
|
* So the check is made explicit and centralised here rather than re-derived at
|
|
205
|
-
* each call site: an `fs.rm` that escapes throws instead of deleting.
|
|
206
|
-
*
|
|
207
|
-
*
|
|
205
|
+
* each call site: an `fs.rm` that escapes throws instead of deleting. Every
|
|
206
|
+
* caller already only passes in-root paths — this exists so that stays true
|
|
207
|
+
* when someone later adds another one.
|
|
208
|
+
*
|
|
209
|
+
* `promote()`'s two rollbacks route through here as well. Their target is
|
|
210
|
+
* `<projectsRoot>/<slug>/<checkoutName>`, which is in-root by construction, so
|
|
211
|
+
* the guard is inert there today — that is the point. This is the choke point
|
|
212
|
+
* the comment above promised, and a recursive delete that skips it is exactly
|
|
213
|
+
* the drift it exists to prevent. {@link rollback} is the deliberate exception:
|
|
214
|
+
* it deletes paths that may legitimately sit OUTSIDE the root (a user-nominated
|
|
215
|
+
* clone target), and is safe by a different mechanism — it only ever removes
|
|
216
|
+
* directories this operation created.
|
|
208
217
|
*/
|
|
209
218
|
async rmInsideRoot(target) {
|
|
210
219
|
const resolved = path.resolve(target);
|
|
@@ -641,7 +650,7 @@ export class ProjectStore {
|
|
|
641
650
|
await cloneRepo(repo, checkoutDir);
|
|
642
651
|
}
|
|
643
652
|
catch (err) {
|
|
644
|
-
await
|
|
653
|
+
await this.rmInsideRoot(checkoutDir).catch(() => undefined);
|
|
645
654
|
throw new ProjectError(err instanceof Error ? err.message : `Failed to clone ${repo}`, "invalid");
|
|
646
655
|
}
|
|
647
656
|
// `writeYaml` is the atomic COMMIT point: until it succeeds the notebook is
|
|
@@ -666,7 +675,7 @@ export class ProjectStore {
|
|
|
666
675
|
await this.writeYaml(slug, next);
|
|
667
676
|
}
|
|
668
677
|
catch (err) {
|
|
669
|
-
await
|
|
678
|
+
await this.rmInsideRoot(checkoutDir).catch(() => undefined);
|
|
670
679
|
throw err;
|
|
671
680
|
}
|
|
672
681
|
// Post-commit finalization — the project is now validly repo-backed; these
|
|
@@ -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 " +
|
|
@@ -54,6 +54,13 @@ export interface SelfMcpCreatedProject {
|
|
|
54
54
|
*/
|
|
55
55
|
agentRegistered: boolean;
|
|
56
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;
|
|
57
64
|
/** A chat as surfaced to the agent. */
|
|
58
65
|
export interface SelfMcpChat {
|
|
59
66
|
/** Owning project slug. */
|
|
@@ -192,10 +199,11 @@ export interface SelfMcpWriteContext {
|
|
|
192
199
|
/** Set (or clear) a chat's archived flag (presentational metadata only). */
|
|
193
200
|
setArchived: (projectSlug: string, sessionId: string, archived: boolean) => Promise<void>;
|
|
194
201
|
/**
|
|
195
|
-
* Whether the PROJECT tools (`create_project`, issue #467
|
|
196
|
-
* instance — the `selfMcpProjectsEnabled` gate,
|
|
197
|
-
* the
|
|
198
|
-
* 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}.
|
|
199
207
|
*/
|
|
200
208
|
projectsMcpEnabled: boolean;
|
|
201
209
|
/**
|
|
@@ -208,6 +216,20 @@ export interface SelfMcpWriteContext {
|
|
|
208
216
|
* non-fatal (mirroring the route) and reported via `agentRegistered`.
|
|
209
217
|
*/
|
|
210
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>;
|
|
211
233
|
/**
|
|
212
234
|
* Whether the unified trigger-management MCP is enabled for THIS turn's project
|
|
213
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
|
package/packages/web/dist/assets/{InstanceConfigPage-Blr1a-hx.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
|
|