stageflow 0.42.0 → 0.44.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 +12 -3
- package/dist/agent/activity.d.ts +18 -0
- package/dist/agent/claudeAdapter.js +7 -0
- package/dist/agent/claudeTools.d.ts +27 -18
- package/dist/agent/claudeTools.js +54 -15
- package/dist/agent/compactionEvents.d.ts +34 -0
- package/dist/agent/compactionEvents.js +37 -0
- package/dist/agent/openRouterRouting.d.ts +7 -0
- package/dist/agent/openRouterRouting.js +23 -0
- package/dist/agent/piAdapter.d.ts +6 -0
- package/dist/agent/piAdapter.js +128 -27
- package/dist/agent/piSettings.d.ts +7 -0
- package/dist/agent/piSettings.js +9 -0
- package/dist/agent/sandboxPiTools.d.ts +3 -1
- package/dist/agent/sandboxPiTools.js +8 -2
- package/dist/agent/stageTurnDriver.d.ts +11 -2
- package/dist/agent/stageTurnDriver.js +45 -4
- package/dist/agent/toolOutputBudget.d.ts +15 -1
- package/dist/agent/toolOutputBudget.js +18 -0
- package/dist/agent/toolOutputBudgetExtension.d.ts +2 -0
- package/dist/agent/toolOutputBudgetExtension.js +40 -3
- package/dist/agents/authorChat.d.ts +1 -0
- package/dist/agents/authorChat.js +7 -0
- package/dist/agents/authorSessionStore.d.ts +3 -0
- package/dist/agents/authorSessionStore.js +17 -2
- package/dist/agents/hostContext.d.ts +1 -1
- package/dist/agents/runtime/piRuntime.d.ts +5 -1
- package/dist/agents/runtime/piRuntime.js +46 -12
- package/dist/agents/service.js +3 -2
- package/dist/agents/sessions.js +3 -1
- package/dist/compaction/directives.d.ts +18 -0
- package/dist/compaction/directives.js +119 -0
- package/dist/compaction/extension.d.ts +20 -0
- package/dist/compaction/extension.js +78 -0
- package/dist/compaction/mask.d.ts +18 -0
- package/dist/compaction/mask.js +155 -0
- package/dist/compaction/pin.d.ts +21 -0
- package/dist/compaction/pin.js +270 -0
- package/dist/compaction/scrub.d.ts +7 -0
- package/dist/compaction/scrub.js +26 -0
- package/dist/compaction/sessionWiring.d.ts +22 -0
- package/dist/compaction/sessionWiring.js +26 -0
- package/dist/compaction/summarize.d.ts +79 -0
- package/dist/compaction/summarize.js +171 -0
- package/dist/compaction/toolPolicy.d.ts +3 -0
- package/dist/compaction/toolPolicy.js +33 -0
- package/dist/compaction/types.d.ts +45 -0
- package/dist/compaction/types.js +7 -0
- package/dist/compaction/workingState.d.ts +56 -0
- package/dist/compaction/workingState.js +198 -0
- package/dist/config/createPipeline.js +2 -0
- package/dist/config/createTrigger.js +7 -1
- package/dist/config/ensureCatalogScanRoot.d.ts +2 -0
- package/dist/config/ensureCatalogScanRoot.js +6 -0
- package/dist/config/evalCatalog.js +30 -2
- package/dist/config/githubTriggerAction.d.ts +8 -0
- package/dist/config/githubTriggerAction.js +29 -0
- package/dist/config/hostConfig.js +2 -0
- package/dist/config/loadEval.js +21 -3
- package/dist/config/pipelineNeeds.d.ts +2 -0
- package/dist/config/pipelineNeeds.js +16 -3
- package/dist/config/pipelineRoute.js +5 -4
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +12 -0
- package/dist/config/validateEval.js +43 -2
- package/dist/config/writeEval.js +2 -2
- package/dist/envelope/assertEmittedArtifacts.d.ts +12 -0
- package/dist/envelope/assertEmittedArtifacts.js +43 -0
- package/dist/envelope/preEmitChecks.d.ts +3 -0
- package/dist/evals/constants.d.ts +3 -1
- package/dist/evals/constants.js +3 -1
- package/dist/evals/engine/FakeEngine.d.ts +1 -1
- package/dist/evals/engine/FakeEngine.js +2 -1
- package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +1 -1
- package/dist/evals/engine/promptfoo/PromptfooEngine.js +2 -1
- package/dist/evals/engine/promptfoo/generate.d.ts +1 -1
- package/dist/evals/engine/promptfoo/generate.js +11 -8
- package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +1 -1
- package/dist/evals/engine/promptfoo/rubricPrompt.js +5 -3
- package/dist/evals/manager.d.ts +13 -0
- package/dist/evals/manager.js +10 -2
- package/dist/evals/plan.d.ts +8 -1
- package/dist/evals/plan.js +28 -9
- package/dist/evals/planInternalAgent.d.ts +19 -0
- package/dist/evals/planInternalAgent.js +205 -0
- package/dist/evals/read/api.d.ts +12 -0
- package/dist/evals/read/api.js +5 -0
- package/dist/evals/read/list.js +10 -1
- package/dist/evals/read/runs.d.ts +5 -0
- package/dist/evals/read/runs.js +7 -1
- package/dist/evals/runner/caseRunner.d.ts +2 -1
- package/dist/evals/runner/caseRunner.js +16 -5
- package/dist/evals/runner/internalAgentCaseRunner.d.ts +6 -0
- package/dist/evals/runner/internalAgentCaseRunner.js +250 -0
- package/dist/evals/snapshot.d.ts +10 -2
- package/dist/evals/snapshot.js +8 -3
- package/dist/evals/types.d.ts +12 -2
- package/dist/evals/types.js +4 -1
- package/dist/guide/cardContract.d.ts +133 -0
- package/dist/guide/cardContract.js +156 -0
- package/dist/guide/chat.d.ts +45 -0
- package/dist/guide/chat.js +378 -0
- package/dist/guide/knowledge/index.d.ts +14 -0
- package/dist/guide/knowledge/index.js +109 -0
- package/dist/guide/knowledge/topics/agents.d.ts +2 -0
- package/dist/guide/knowledge/topics/agents.js +58 -0
- package/dist/guide/knowledge/topics/bestPractices.d.ts +2 -0
- package/dist/guide/knowledge/topics/bestPractices.js +67 -0
- package/dist/guide/knowledge/topics/connections.d.ts +2 -0
- package/dist/guide/knowledge/topics/connections.js +66 -0
- package/dist/guide/knowledge/topics/consoleMap.d.ts +2 -0
- package/dist/guide/knowledge/topics/consoleMap.js +55 -0
- package/dist/guide/knowledge/topics/evals.d.ts +2 -0
- package/dist/guide/knowledge/topics/evals.js +72 -0
- package/dist/guide/knowledge/topics/limitations.d.ts +2 -0
- package/dist/guide/knowledge/topics/limitations.js +84 -0
- package/dist/guide/knowledge/topics/mcpSkillsBrowser.d.ts +2 -0
- package/dist/guide/knowledge/topics/mcpSkillsBrowser.js +57 -0
- package/dist/guide/knowledge/topics/memories.d.ts +2 -0
- package/dist/guide/knowledge/topics/memories.js +63 -0
- package/dist/guide/knowledge/topics/modelsAndProviders.d.ts +2 -0
- package/dist/guide/knowledge/topics/modelsAndProviders.js +56 -0
- package/dist/guide/knowledge/topics/overview.d.ts +2 -0
- package/dist/guide/knowledge/topics/overview.js +56 -0
- package/dist/guide/knowledge/topics/pipelines.d.ts +2 -0
- package/dist/guide/knowledge/topics/pipelines.js +93 -0
- package/dist/guide/knowledge/topics/stages.d.ts +2 -0
- package/dist/guide/knowledge/topics/stages.js +54 -0
- package/dist/guide/knowledge/topics/tasks.d.ts +2 -0
- package/dist/guide/knowledge/topics/tasks.js +52 -0
- package/dist/guide/knowledge/topics/triggers.d.ts +2 -0
- package/dist/guide/knowledge/topics/triggers.js +68 -0
- package/dist/guide/knowledge/topics/troubleshooting.d.ts +2 -0
- package/dist/guide/knowledge/topics/troubleshooting.js +85 -0
- package/dist/guide/knowledgeTools.d.ts +2 -0
- package/dist/guide/knowledgeTools.js +56 -0
- package/dist/guide/showUi.d.ts +6 -0
- package/dist/guide/showUi.js +546 -0
- package/dist/guide/threadStore.d.ts +48 -0
- package/dist/guide/threadStore.js +189 -0
- package/dist/guide/writeRoot.d.ts +11 -0
- package/dist/guide/writeRoot.js +70 -0
- package/dist/hosted/translate.js +2 -0
- package/dist/mcp/agentTools.js +12 -0
- package/dist/mcp/catalogTools.js +18 -20
- package/dist/mcp/catalogWriteTools.d.ts +8 -0
- package/dist/mcp/catalogWriteTools.js +225 -0
- package/dist/mcp/connectionTools.d.ts +3 -0
- package/dist/mcp/connectionTools.js +85 -0
- package/dist/mcp/controlTools.js +34 -1
- package/dist/mcp/deps.d.ts +9 -0
- package/dist/mcp/evalTools.js +684 -21
- package/dist/mcp/memoryHostTools.d.ts +3 -0
- package/dist/mcp/memoryHostTools.js +72 -0
- package/dist/mcp/pipelineAuthorTools.js +14 -1
- package/dist/mcp/server.js +5 -0
- package/dist/mcp/tools.js +6 -0
- package/dist/mcp/triggerTools.js +77 -31
- package/dist/operatorAgent/evalGate.d.ts +22 -0
- package/dist/operatorAgent/evalGate.js +77 -0
- package/dist/operatorAgent/fakeHost.js +10 -2
- package/dist/operatorAgent/host.js +18 -2
- package/dist/operatorAgent/index.d.ts +1 -0
- package/dist/operatorAgent/index.js +1 -0
- package/dist/operatorAgent/internalAgents.d.ts +11 -0
- package/dist/operatorAgent/internalAgents.js +13 -0
- package/dist/operatorAgent/piModel.d.ts +5 -1
- package/dist/operatorAgent/piModel.js +15 -10
- package/dist/operatorAgent/profiles/agentAuthor.js +66 -22
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +1 -1
- package/dist/operatorAgent/profiles/guideConfirm.d.ts +21 -0
- package/dist/operatorAgent/profiles/guideConfirm.js +100 -0
- package/dist/operatorAgent/profiles/guideTools.d.ts +7 -0
- package/dist/operatorAgent/profiles/guideTools.js +123 -0
- package/dist/operatorAgent/profiles/stageflowGuide.d.ts +7 -0
- package/dist/operatorAgent/profiles/stageflowGuide.js +132 -0
- package/dist/operatorAgent/profiles/workshopAuthor.js +23 -0
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +9 -1
- package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +2 -0
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +197 -9
- package/dist/operatorAgent/types.d.ts +23 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/retention.d.ts +2 -1
- package/dist/runstore/retention.js +3 -7
- package/dist/runstore/runStatusClock.d.ts +17 -0
- package/dist/runstore/runStatusClock.js +19 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +1 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +45 -45
- package/dist/runstore/workspaceLayout.js +6 -1
- package/dist/runtime/artifactDiskInspect.d.ts +14 -0
- package/dist/runtime/artifactDiskInspect.js +52 -0
- package/dist/runtime/artifactPath.d.ts +24 -0
- package/dist/runtime/artifactPath.js +84 -0
- package/dist/runtime/completionCheckRunner.js +15 -43
- package/dist/runtime/githubPollSource.d.ts +22 -4
- package/dist/runtime/githubPollSource.js +93 -8
- package/dist/runtime/priorArtifactGrants.d.ts +8 -0
- package/dist/runtime/priorArtifactGrants.js +105 -0
- package/dist/runtime/runActionEligibility.d.ts +46 -0
- package/dist/runtime/runActionEligibility.js +123 -0
- package/dist/runtime/runManager.d.ts +4 -1
- package/dist/runtime/runManager.js +52 -10
- package/dist/runtime/runRetryCoordinator.d.ts +2 -14
- package/dist/runtime/runRetryCoordinator.js +3 -67
- package/dist/runtime/sandboxContainer.d.ts +7 -0
- package/dist/runtime/sandboxContainer.js +48 -6
- package/dist/runtime/sandboxDesk.d.ts +2 -1
- package/dist/runtime/sandboxDesk.js +11 -12
- package/dist/runtime/stageEnvironment.js +1 -0
- package/dist/runtime/stageRoots.d.ts +12 -0
- package/dist/runtime/stageSandbox.d.ts +2 -1
- package/dist/runtime/stageSandbox.js +18 -2
- package/dist/runtime/stageWorker.js +20 -0
- package/dist/server/agentAuthorRoutes.js +12 -3
- package/dist/server/bootstrap.d.ts +5 -0
- package/dist/server/bootstrap.js +14 -1
- package/dist/server/evalRoutes.js +38 -9
- package/dist/server/guideRoutes.d.ts +5 -0
- package/dist/server/guideRoutes.js +166 -0
- package/dist/server/http.d.ts +10 -0
- package/dist/server/http.js +35 -1
- package/dist/server/internalAgentRoutes.d.ts +6 -0
- package/dist/server/internalAgentRoutes.js +12 -0
- package/dist/server/mcpHost.js +2 -0
- package/dist/toolkit/meta.d.ts +1 -1
- package/dist/toolkit/meta.js +39 -1
- package/dist/toolkit/profiles.d.ts +5 -0
- package/dist/toolkit/profiles.js +12 -0
- package/dist/tools/askOperator.js +2 -1
- package/dist/tools/emitStageEnvelope.d.ts +17 -16
- package/dist/tools/emitStageEnvelope.js +46 -32
- package/dist/tools/stageToolNames.d.ts +4 -0
- package/dist/tools/stageToolNames.js +4 -0
- package/dist/tools/workingState.d.ts +40 -0
- package/dist/tools/workingState.js +108 -0
- package/dist/tools/writeStageArtifact.d.ts +0 -9
- package/dist/tools/writeStageArtifact.js +2 -1
- package/dist/ui/assets/{MarkdownText-BTrOqu5J.js → MarkdownText-S5C_vemc.js} +2 -2
- package/dist/ui/assets/adapters-C0l4ruiR.js +17 -0
- package/dist/ui/assets/chat-CjnsmvqR.js +2 -0
- package/dist/ui/assets/chat-DFDd4UjS.css +2 -0
- package/dist/ui/assets/{main-CHlmTNJe.css → main-Del1GeLf.css} +1 -1
- package/dist/ui/assets/main-_yXVqcW9.js +277 -0
- package/dist/ui/chat.html +3 -4
- package/dist/ui/index.html +3 -4
- package/dist/workshop/chatTurn.js +1 -1
- package/dist/workshop/workshopPin.d.ts +3 -3
- package/dist/workshop/workshopPin.js +16 -15
- package/examples/compaction-stress/README.md +30 -0
- package/examples/compaction-stress/check-run.mjs +53 -0
- package/examples/compaction-stress/collect-evidence.yaml +60 -0
- package/examples/compaction-stress/compaction-stress.pipeline.yaml +141 -0
- package/examples/compaction-stress/compaction-stress.task.yaml +15 -0
- package/examples/compaction-stress/generate-evidence.mjs +110 -0
- package/examples/compaction-stress/root-cause.yaml +26 -0
- package/examples/compaction-stress/stageflow.yaml +9 -0
- package/examples/compaction-stress/write-postmortem.yaml +40 -0
- package/examples/evals/README.md +18 -0
- package/examples/evals/guide-behavior.dataset.jsonl +15 -0
- package/examples/evals/guide-behavior.eval.yaml +35 -0
- package/examples/evals/guide-depth.dataset.jsonl +35 -0
- package/examples/evals/guide-depth.eval.yaml +169 -0
- package/examples/memory/evals/guide-behavior.dataset.jsonl +15 -0
- package/examples/memory/evals/guide-behavior.eval.yaml +35 -0
- package/examples/memory/evals/guide-depth.dataset.jsonl +35 -0
- package/examples/memory/evals/guide-depth.eval.yaml +169 -0
- package/package.json +1 -1
- package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +0 -9
- package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +0 -6
- package/dist/ui/assets/chat-Df_wCpak.js +0 -5
- package/dist/ui/assets/chat-P7KQznHm.css +0 -2
- package/dist/ui/assets/main-CvVfxmMd.js +0 -271
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { globalStageflowHome } from "../project/globalHome.js";
|
|
5
|
+
function guardThreadId(threadId) {
|
|
6
|
+
const trimmed = threadId.trim();
|
|
7
|
+
if (!trimmed || trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("..") || trimmed === ".") {
|
|
8
|
+
throw new Error("guide.invalid");
|
|
9
|
+
}
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
12
|
+
export function guideThreadsDir(storeRoot = globalStageflowHome()) {
|
|
13
|
+
return path.join(storeRoot, "guide", "threads");
|
|
14
|
+
}
|
|
15
|
+
export function guideThreadFilePath(threadId, storeRoot = globalStageflowHome()) {
|
|
16
|
+
return path.join(guideThreadsDir(storeRoot), guardThreadId(threadId), "thread.json");
|
|
17
|
+
}
|
|
18
|
+
function isPlainObject(value) {
|
|
19
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
function parseMessage(value) {
|
|
22
|
+
if (!isPlainObject(value))
|
|
23
|
+
return null;
|
|
24
|
+
if (typeof value.id !== "string" || !value.id)
|
|
25
|
+
return null;
|
|
26
|
+
if (value.role !== "user" && value.role !== "assistant")
|
|
27
|
+
return null;
|
|
28
|
+
if (typeof value.text !== "string")
|
|
29
|
+
return null;
|
|
30
|
+
if (typeof value.createdAt !== "string" || !value.createdAt)
|
|
31
|
+
return null;
|
|
32
|
+
const attachments = attachmentMeta(value.attachments);
|
|
33
|
+
const parts = guideParts(value.parts);
|
|
34
|
+
return {
|
|
35
|
+
id: value.id,
|
|
36
|
+
role: value.role,
|
|
37
|
+
text: value.text,
|
|
38
|
+
createdAt: value.createdAt,
|
|
39
|
+
...(attachments.length > 0 ? { attachments } : {}),
|
|
40
|
+
...(parts.length > 0 ? { parts } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function guideParts(value) {
|
|
44
|
+
if (!Array.isArray(value))
|
|
45
|
+
return [];
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const entry of value) {
|
|
48
|
+
if (!isPlainObject(entry))
|
|
49
|
+
continue;
|
|
50
|
+
if (entry.type === "text" && typeof entry.text === "string") {
|
|
51
|
+
out.push({ type: "text", text: entry.text });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (entry.type !== "tool" || typeof entry.id !== "string" || typeof entry.name !== "string")
|
|
55
|
+
continue;
|
|
56
|
+
if (entry.status !== "complete" && entry.status !== "error")
|
|
57
|
+
continue;
|
|
58
|
+
out.push({
|
|
59
|
+
type: "tool",
|
|
60
|
+
id: entry.id,
|
|
61
|
+
name: entry.name,
|
|
62
|
+
status: entry.status,
|
|
63
|
+
...(isPlainObject(entry.args) ? { args: entry.args } : {}),
|
|
64
|
+
...(entry.result !== undefined ? { result: entry.result } : {}),
|
|
65
|
+
...(typeof entry.error === "string" ? { error: entry.error } : {}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function attachmentMeta(value) {
|
|
71
|
+
if (!Array.isArray(value))
|
|
72
|
+
return [];
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const entry of value) {
|
|
75
|
+
if (!isPlainObject(entry) || typeof entry.name !== "string" || !entry.name)
|
|
76
|
+
continue;
|
|
77
|
+
out.push({
|
|
78
|
+
name: entry.name,
|
|
79
|
+
size: typeof entry.size === "number" ? entry.size : 0,
|
|
80
|
+
mediaType: typeof entry.mediaType === "string" && entry.mediaType ? entry.mediaType : "text/plain",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
export function parseGuideThread(value) {
|
|
86
|
+
if (!isPlainObject(value) || value.version !== 1)
|
|
87
|
+
return null;
|
|
88
|
+
if (typeof value.id !== "string" || !value.id)
|
|
89
|
+
return null;
|
|
90
|
+
if (typeof value.title !== "string")
|
|
91
|
+
return null;
|
|
92
|
+
if (typeof value.createdAt !== "string" || typeof value.updatedAt !== "string")
|
|
93
|
+
return null;
|
|
94
|
+
if (!Array.isArray(value.messages))
|
|
95
|
+
return null;
|
|
96
|
+
const messages = [];
|
|
97
|
+
for (const entry of value.messages) {
|
|
98
|
+
const message = parseMessage(entry);
|
|
99
|
+
if (!message)
|
|
100
|
+
return null;
|
|
101
|
+
messages.push(message);
|
|
102
|
+
}
|
|
103
|
+
const model = typeof value.model === "string" && value.model.trim() ? value.model.trim() : undefined;
|
|
104
|
+
return {
|
|
105
|
+
version: 1,
|
|
106
|
+
id: value.id,
|
|
107
|
+
title: value.title,
|
|
108
|
+
createdAt: value.createdAt,
|
|
109
|
+
updatedAt: value.updatedAt,
|
|
110
|
+
messages,
|
|
111
|
+
...(model ? { model } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function writeRecord(record, storeRoot) {
|
|
115
|
+
const file = guideThreadFilePath(record.id, storeRoot);
|
|
116
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
117
|
+
writeFileSync(file, JSON.stringify(record));
|
|
118
|
+
}
|
|
119
|
+
export function createGuideThread(storeRoot) {
|
|
120
|
+
const now = new Date().toISOString();
|
|
121
|
+
const record = {
|
|
122
|
+
version: 1,
|
|
123
|
+
id: randomUUID(),
|
|
124
|
+
title: "",
|
|
125
|
+
createdAt: now,
|
|
126
|
+
updatedAt: now,
|
|
127
|
+
messages: [],
|
|
128
|
+
};
|
|
129
|
+
writeRecord(record, storeRoot);
|
|
130
|
+
return record;
|
|
131
|
+
}
|
|
132
|
+
export function readGuideThread(threadId, storeRoot) {
|
|
133
|
+
let file;
|
|
134
|
+
try {
|
|
135
|
+
file = guideThreadFilePath(threadId, storeRoot);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (!existsSync(file))
|
|
141
|
+
return null;
|
|
142
|
+
try {
|
|
143
|
+
return parseGuideThread(JSON.parse(readFileSync(file, "utf8")));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function saveGuideThread(record, storeRoot) {
|
|
150
|
+
writeRecord(record, storeRoot);
|
|
151
|
+
return record;
|
|
152
|
+
}
|
|
153
|
+
export function setGuideThreadModel(threadId, model, storeRoot) {
|
|
154
|
+
const record = readGuideThread(threadId, storeRoot);
|
|
155
|
+
if (!record)
|
|
156
|
+
return null;
|
|
157
|
+
const trimmed = typeof model === "string" ? model.trim() : "";
|
|
158
|
+
const next = {
|
|
159
|
+
version: 1,
|
|
160
|
+
id: record.id,
|
|
161
|
+
title: record.title,
|
|
162
|
+
createdAt: record.createdAt,
|
|
163
|
+
updatedAt: new Date().toISOString(),
|
|
164
|
+
messages: record.messages,
|
|
165
|
+
...(trimmed ? { model: trimmed } : {}),
|
|
166
|
+
};
|
|
167
|
+
writeRecord(next, storeRoot);
|
|
168
|
+
return next;
|
|
169
|
+
}
|
|
170
|
+
export function listGuideThreads(storeRoot) {
|
|
171
|
+
const dir = guideThreadsDir(storeRoot);
|
|
172
|
+
if (!existsSync(dir))
|
|
173
|
+
return [];
|
|
174
|
+
const summaries = [];
|
|
175
|
+
for (const entry of readdirSync(dir)) {
|
|
176
|
+
const record = readGuideThread(entry, storeRoot);
|
|
177
|
+
if (!record)
|
|
178
|
+
continue;
|
|
179
|
+
summaries.push({ id: record.id, title: record.title, updatedAt: record.updatedAt });
|
|
180
|
+
}
|
|
181
|
+
summaries.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0));
|
|
182
|
+
return summaries;
|
|
183
|
+
}
|
|
184
|
+
export function guideTitleFrom(text) {
|
|
185
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
186
|
+
if (!flat)
|
|
187
|
+
return "New chat";
|
|
188
|
+
return flat.length > 48 ? `${flat.slice(0, 47).trimEnd()}…` : flat;
|
|
189
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Where the Guide saves. A project with stageflow.yaml keeps its own catalog. Otherwise the workspace catalog, which Pipelines and Catalog list. */
|
|
2
|
+
export declare function guideWriteRoot(hostProjectRoot: string): string;
|
|
3
|
+
/** One folder per pipeline. Stage files are written beside the pipeline file. */
|
|
4
|
+
export declare function guidePipelineDirectory(id: string): string;
|
|
5
|
+
export declare function guidePipelineFile(id: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Pipeline and stage files always land in pipelines/<id>/.
|
|
8
|
+
* A path that already exists under the catalog is left alone so an edit keeps that package.
|
|
9
|
+
* Any other path, including one the person named, is rewritten.
|
|
10
|
+
*/
|
|
11
|
+
export declare function placeGuideWriteArgs(name: string, args: Record<string, unknown>, fileExists?: (relPath: string) => boolean): Record<string, unknown>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { accessSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { GUIDE_PIPELINES_ROOT } from "../config/ensureCatalogScanRoot.js";
|
|
4
|
+
import { STAGE_ID_PATTERN } from "../config/createStage.js";
|
|
5
|
+
import { ensureWorkspaceCatalog, workspaceCatalogPath } from "../project/workspaceCatalog.js";
|
|
6
|
+
/** Where the Guide saves. A project with stageflow.yaml keeps its own catalog. Otherwise the workspace catalog, which Pipelines and Catalog list. */
|
|
7
|
+
export function guideWriteRoot(hostProjectRoot) {
|
|
8
|
+
try {
|
|
9
|
+
accessSync(path.join(hostProjectRoot, "stageflow.yaml"));
|
|
10
|
+
return hostProjectRoot;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
ensureWorkspaceCatalog();
|
|
14
|
+
return workspaceCatalogPath();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** One folder per pipeline. Stage files are written beside the pipeline file. */
|
|
18
|
+
export function guidePipelineDirectory(id) {
|
|
19
|
+
return `${GUIDE_PIPELINES_ROOT}/${id}`;
|
|
20
|
+
}
|
|
21
|
+
export function guidePipelineFile(id) {
|
|
22
|
+
return `${guidePipelineDirectory(id)}/${id}.pipeline.yaml`;
|
|
23
|
+
}
|
|
24
|
+
const PACKAGE_PATH_TOOLS = new Set([
|
|
25
|
+
"create_stage",
|
|
26
|
+
"edit_stage",
|
|
27
|
+
"edit_pipeline",
|
|
28
|
+
"create_task",
|
|
29
|
+
"edit_task",
|
|
30
|
+
"read_pipeline",
|
|
31
|
+
]);
|
|
32
|
+
function pipelineIdFromFile(relPath) {
|
|
33
|
+
const base = relPath.replace(/\\/g, "/").split("/").pop() ?? "";
|
|
34
|
+
if (!base.endsWith(".pipeline.yaml"))
|
|
35
|
+
return undefined;
|
|
36
|
+
const id = base.slice(0, -".pipeline.yaml".length);
|
|
37
|
+
return STAGE_ID_PATTERN.test(id) ? id : undefined;
|
|
38
|
+
}
|
|
39
|
+
function escapesProject(relPath) {
|
|
40
|
+
const norm = relPath.replace(/\\/g, "/");
|
|
41
|
+
return path.isAbsolute(relPath) || norm.startsWith("/") || norm.split("/").includes("..");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pipeline and stage files always land in pipelines/<id>/.
|
|
45
|
+
* A path that already exists under the catalog is left alone so an edit keeps that package.
|
|
46
|
+
* Any other path, including one the person named, is rewritten.
|
|
47
|
+
*/
|
|
48
|
+
export function placeGuideWriteArgs(name, args, fileExists = () => false) {
|
|
49
|
+
const next = { ...args };
|
|
50
|
+
delete next.project_root;
|
|
51
|
+
const id = typeof next.id === "string" ? next.id.trim() : "";
|
|
52
|
+
if (STAGE_ID_PATTERN.test(id)) {
|
|
53
|
+
if (name === "create_pipeline")
|
|
54
|
+
next.directory = guidePipelineDirectory(id);
|
|
55
|
+
if (name === "create_stage_file")
|
|
56
|
+
next.pipeline_directory = guidePipelineDirectory(id);
|
|
57
|
+
}
|
|
58
|
+
if (!PACKAGE_PATH_TOOLS.has(name) || typeof next.path !== "string")
|
|
59
|
+
return next;
|
|
60
|
+
const given = next.path.trim();
|
|
61
|
+
const pipelineId = pipelineIdFromFile(given);
|
|
62
|
+
if (!pipelineId)
|
|
63
|
+
return next;
|
|
64
|
+
const canonical = guidePipelineFile(pipelineId);
|
|
65
|
+
if (given.replace(/\\/g, "/") === canonical)
|
|
66
|
+
return next;
|
|
67
|
+
if (escapesProject(given) || !fileExists(given))
|
|
68
|
+
next.path = canonical;
|
|
69
|
+
return next;
|
|
70
|
+
}
|
package/dist/hosted/translate.js
CHANGED
package/dist/mcp/agentTools.js
CHANGED
|
@@ -67,6 +67,18 @@ export function registerAgentTools(server, deps) {
|
|
|
67
67
|
inputSchema: z.object({ id: agentId }),
|
|
68
68
|
}, ({ id }) => call(() => service().setEnabled(root(), id, false, MCP_ACTOR)));
|
|
69
69
|
server.registerTool("list_agent_revisions", { description: "List an agent's revisions, newest first.", inputSchema: z.object({ id: agentId }) }, ({ id }) => call(async () => ({ revisions: await service().listRevisions(root(), id) })));
|
|
70
|
+
server.registerTool("get_agent_revision", {
|
|
71
|
+
description: "Get one revision of an agent, including its definition and canonical YAML.",
|
|
72
|
+
inputSchema: z.object({ id: agentId, revision: z.number().int().positive() }),
|
|
73
|
+
}, ({ id, revision }) => call(() => service().getRevision(root(), id, revision)));
|
|
74
|
+
server.registerTool("diff_agent_revisions", {
|
|
75
|
+
description: "Diff two revisions of an agent, from one revision number to another.",
|
|
76
|
+
inputSchema: z.object({
|
|
77
|
+
id: agentId,
|
|
78
|
+
from: z.number().int().positive(),
|
|
79
|
+
to: z.number().int().positive(),
|
|
80
|
+
}),
|
|
81
|
+
}, ({ id, from, to }) => call(() => service().diffRevisions(root(), id, from, to)));
|
|
70
82
|
server.registerTool("restore_agent_revision", {
|
|
71
83
|
description: "Save an earlier revision's definition as the newest revision.",
|
|
72
84
|
inputSchema: z.object({
|
package/dist/mcp/catalogTools.js
CHANGED
|
@@ -77,13 +77,10 @@ const inlinePipelineSchema = z
|
|
|
77
77
|
channels: z.unknown().optional(),
|
|
78
78
|
})
|
|
79
79
|
.strict();
|
|
80
|
-
const
|
|
81
|
-
.object({
|
|
80
|
+
const startRunCommon = {
|
|
82
81
|
pipeline: z
|
|
83
82
|
.union([z.string(), inlinePipelineSchema])
|
|
84
83
|
.describe("Catalog-relative pipeline path, or an inline pipeline definition object ({ id, stages: [...] }) authored directly in this call"),
|
|
85
|
-
task_path: z.string().optional(),
|
|
86
|
-
task: taskFileSchema.optional(),
|
|
87
84
|
project_root: z.string().optional(),
|
|
88
85
|
checkout: z
|
|
89
86
|
.string()
|
|
@@ -101,11 +98,11 @@ const startRunSchema = z
|
|
|
101
98
|
.record(z.string(), z.record(z.string(), z.string()))
|
|
102
99
|
.optional()
|
|
103
100
|
.describe("Run-scoped skills: name → { relativePath: utf-8 contents }. Requires SKILL.md per name; materialised under the run workspace skills dir (never the worktree)"),
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
};
|
|
102
|
+
const startRunSchema = z.union([
|
|
103
|
+
z.strictObject({ ...startRunCommon, task_path: z.string() }),
|
|
104
|
+
z.strictObject({ ...startRunCommon, task: taskFileSchema }),
|
|
105
|
+
]);
|
|
109
106
|
/**
|
|
110
107
|
* Deliberately thin, same reasoning as inlinePipelineSchema above: real
|
|
111
108
|
* structural validation of the stage body happens downstream through the
|
|
@@ -116,8 +113,7 @@ const envelopeRefSchema = z.object({
|
|
|
116
113
|
stageId: z.string(),
|
|
117
114
|
attempt: z.number().int().positive().optional(),
|
|
118
115
|
});
|
|
119
|
-
const
|
|
120
|
-
.object({
|
|
116
|
+
const runStageCommon = {
|
|
121
117
|
stage: z
|
|
122
118
|
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
123
119
|
.describe("Catalog-relative path to a stage YAML file (same path contract as start_run — absolute paths rejected, must be inside a registered project_root), or a bare inline stage body object ({ id, system_prompt, io, model?, gate_kinds?, mcp?, verify?, timeout_ms? } — no uses:/route/pipeline wrapper) authored directly in this call"),
|
|
@@ -125,12 +121,6 @@ const runStageSchema = z
|
|
|
125
121
|
.string()
|
|
126
122
|
.optional()
|
|
127
123
|
.describe("Catalog project root to resolve a string stage path against; required when multiple catalog roots are configured and no sole non-seeded root exists"),
|
|
128
|
-
task_path: z.string().optional(),
|
|
129
|
-
task: taskFileSchema.optional(),
|
|
130
|
-
envelope_ref: z
|
|
131
|
-
.union([envelopeRefSchema, z.array(envelopeRefSchema).min(1)])
|
|
132
|
-
.optional()
|
|
133
|
-
.describe("Resolve one or more previously stored StageEnvelopes (from another run_stage call or from any stage inside a full pipeline run) and use them as this stage's input, instead of an inline task/task_path. A single ref: its payload becomes input, its summary becomes goal. Multiple refs (an array): each resolved payload is namespaced under its stageId in input (disambiguated by runId on a stageId collision), and summaries are combined into goal."),
|
|
134
124
|
checkout: z
|
|
135
125
|
.string()
|
|
136
126
|
.optional()
|
|
@@ -147,8 +137,15 @@ const runStageSchema = z
|
|
|
147
137
|
.number()
|
|
148
138
|
.optional()
|
|
149
139
|
.describe("Wait budget in ms when blocking is true (same bounds as wait_run)"),
|
|
150
|
-
}
|
|
151
|
-
|
|
140
|
+
};
|
|
141
|
+
const envelopeRefField = z
|
|
142
|
+
.union([envelopeRefSchema, z.array(envelopeRefSchema).min(1)])
|
|
143
|
+
.describe("Resolve one or more previously stored StageEnvelopes (from another run_stage call or from any stage inside a full pipeline run) and use them as this stage's input, instead of an inline task/task_path. A single ref: its payload becomes input, its summary becomes goal. Multiple refs (an array): each resolved payload is namespaced under its stageId in input (disambiguated by runId on a stageId collision), and summaries are combined into goal.");
|
|
144
|
+
const runStageSchema = z.union([
|
|
145
|
+
z.strictObject({ ...runStageCommon, task_path: z.string() }),
|
|
146
|
+
z.strictObject({ ...runStageCommon, task: taskFileSchema }),
|
|
147
|
+
z.strictObject({ ...runStageCommon, envelope_ref: envelopeRefField }),
|
|
148
|
+
]);
|
|
152
149
|
const runStatusSchema = z.enum([
|
|
153
150
|
"created",
|
|
154
151
|
"queued",
|
|
@@ -545,7 +542,8 @@ export function registerCatalogTools(server, deps) {
|
|
|
545
542
|
server.registerTool("run_stage", {
|
|
546
543
|
description: "Run a single stage directly, without authoring a pipeline. `stage` is a filesystem path to a stage YAML file, or a bare inline stage body ({ id, system_prompt, io, model?, gate_kinds?, mcp?, verify?, timeout_ms? } — no uses:/route/pipeline wrapper), and exactly one of task_path, an inline task, or envelope_ref ({ runId, stageId, attempt? }) to resolve a previously stored envelope — from another run_stage call or any stage in a full pipeline run — as this stage's input. Internally this synthesizes a one-stage pipeline and executes it through the normal run path, so it shows up in list_runs/get_run and is polled with wait_run / get_envelope exactly like any other run. By default returns { runId, stageId } immediately (async); pass blocking:true to wait in this same call and get back { runId, stageId, status: \"completed\"|\"needs_input\"|\"timeout\", envelope? , pending_prompt? }.",
|
|
547
544
|
inputSchema: runStageSchema,
|
|
548
|
-
}, async (
|
|
545
|
+
}, async (args) => {
|
|
546
|
+
const { stage, project_root, task_path, task, envelope_ref, checkout, model, blocking, timeout_ms } = args;
|
|
549
547
|
let taskInput = task_path ?? task;
|
|
550
548
|
if (envelope_ref) {
|
|
551
549
|
try {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { type IssueFetch } from "../config/importTask.js";
|
|
3
|
+
import type { McpToolDeps } from "./deps.js";
|
|
4
|
+
type CatalogWriteDeps = McpToolDeps & {
|
|
5
|
+
issueFetch?: IssueFetch;
|
|
6
|
+
};
|
|
7
|
+
export declare function registerCatalogWriteTools(server: McpServer, deps: CatalogWriteDeps): void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { CatalogPathError, catalogPathErrorBody, } from "../config/catalogRelativePath.js";
|
|
5
|
+
import { createStage, parseCreateStageBody } from "../config/createStage.js";
|
|
6
|
+
import { createTask, parseCreateTaskBody } from "../config/createTask.js";
|
|
7
|
+
import { parseDraftPackageBody, validateDraftPackage } from "../config/draftPackage.js";
|
|
8
|
+
import { importTaskFromIssue, parseImportTaskBody, } from "../config/importTask.js";
|
|
9
|
+
import { parseUpdateTaskBody, updateTask } from "../config/updateTask.js";
|
|
10
|
+
import { writeCatalogFileForHttp } from "../server/readCatalogFile.js";
|
|
11
|
+
import { STAGE_GATE_KINDS } from "../types/stage.js";
|
|
12
|
+
import { textResult } from "./toolResults.js";
|
|
13
|
+
const projectRootField = z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Catalog project root. When omitted, the server project root is used, then the process cwd.");
|
|
17
|
+
const record = z.record(z.string(), z.unknown());
|
|
18
|
+
const draftPipelineSchema = z.object({
|
|
19
|
+
id: z.string(),
|
|
20
|
+
stages: z.array(record),
|
|
21
|
+
agent: z.unknown().optional(),
|
|
22
|
+
model: z.unknown().optional(),
|
|
23
|
+
schemas: z.unknown().optional(),
|
|
24
|
+
requires: z.unknown().optional(),
|
|
25
|
+
channels: z.unknown().optional(),
|
|
26
|
+
memories: z.unknown().optional(),
|
|
27
|
+
});
|
|
28
|
+
const draftStageSchema = z.object({
|
|
29
|
+
path: z.string(),
|
|
30
|
+
body: record,
|
|
31
|
+
});
|
|
32
|
+
const draftTaskSchema = z.object({
|
|
33
|
+
filename: z.string(),
|
|
34
|
+
body: record,
|
|
35
|
+
});
|
|
36
|
+
const optionalTaskString = z
|
|
37
|
+
.string()
|
|
38
|
+
.nullable()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("String value, or null or empty string to remove the field");
|
|
41
|
+
function rejected(result) {
|
|
42
|
+
return textResult({
|
|
43
|
+
error: result.error,
|
|
44
|
+
status: result.status,
|
|
45
|
+
...(result.code !== undefined ? { code: result.code } : {}),
|
|
46
|
+
...(result.findings !== undefined ? { findings: result.findings } : {}),
|
|
47
|
+
...(result.project_roots !== undefined ? { project_roots: result.project_roots } : {}),
|
|
48
|
+
...(result.registered_roots !== undefined ? { registered_roots: result.registered_roots } : {}),
|
|
49
|
+
...(result.hint !== undefined ? { hint: result.hint } : {}),
|
|
50
|
+
}, true);
|
|
51
|
+
}
|
|
52
|
+
async function projectRootOf(deps, projectRoot) {
|
|
53
|
+
const resolved = path.resolve(projectRoot ?? deps.projectRoot ?? deps.cwd);
|
|
54
|
+
return realpath(resolved).catch(() => resolved);
|
|
55
|
+
}
|
|
56
|
+
export function registerCatalogWriteTools(server, deps) {
|
|
57
|
+
server.registerTool("create_task_file", {
|
|
58
|
+
description: "Create a standalone catalog task file (POST /api/tasks). The task is not attached to a pipeline; use create_task to add one beside a pipeline package.",
|
|
59
|
+
inputSchema: z.object({
|
|
60
|
+
project_root: projectRootField,
|
|
61
|
+
directory: z.string().describe("Directory relative to the project root, e.g. tasks"),
|
|
62
|
+
id: z.string().describe("Lowercase kebab-case task id, 1-64 characters"),
|
|
63
|
+
goal: z.string(),
|
|
64
|
+
context: z.string().optional(),
|
|
65
|
+
constraints: z.string().optional(),
|
|
66
|
+
checkout: z.string().optional(),
|
|
67
|
+
repository: z.string().optional(),
|
|
68
|
+
ref: z.string().optional(),
|
|
69
|
+
}),
|
|
70
|
+
}, async ({ project_root, ...body }) => {
|
|
71
|
+
const parsed = parseCreateTaskBody(body);
|
|
72
|
+
if ("ok" in parsed)
|
|
73
|
+
return rejected(parsed);
|
|
74
|
+
const result = await createTask(await projectRootOf(deps, project_root), parsed);
|
|
75
|
+
if (!result.ok)
|
|
76
|
+
return rejected(result);
|
|
77
|
+
return textResult({ task: result.task });
|
|
78
|
+
});
|
|
79
|
+
server.registerTool("update_task_file", {
|
|
80
|
+
description: "Update a standalone catalog task by id (PUT /api/tasks/:id). goal is required. Pass null or an empty string for context, constraints, checkout, repository, or ref to remove that field.",
|
|
81
|
+
inputSchema: z.object({
|
|
82
|
+
project_root: projectRootField,
|
|
83
|
+
id: z.string().describe("Task id"),
|
|
84
|
+
goal: z.string(),
|
|
85
|
+
context: optionalTaskString,
|
|
86
|
+
constraints: optionalTaskString,
|
|
87
|
+
checkout: optionalTaskString,
|
|
88
|
+
repository: optionalTaskString,
|
|
89
|
+
ref: optionalTaskString,
|
|
90
|
+
}),
|
|
91
|
+
}, async ({ project_root, id, ...body }) => {
|
|
92
|
+
const parsed = parseUpdateTaskBody(body);
|
|
93
|
+
if ("ok" in parsed)
|
|
94
|
+
return rejected(parsed);
|
|
95
|
+
const result = await updateTask({
|
|
96
|
+
store: deps.store,
|
|
97
|
+
bootCwd: deps.cwd,
|
|
98
|
+
taskId: id,
|
|
99
|
+
input: parsed,
|
|
100
|
+
projectRootFilter: await projectRootOf(deps, project_root),
|
|
101
|
+
});
|
|
102
|
+
if (!result.ok)
|
|
103
|
+
return rejected(result);
|
|
104
|
+
return textResult({ task: result.task });
|
|
105
|
+
});
|
|
106
|
+
server.registerTool("import_task_file", {
|
|
107
|
+
description: "Import a GitHub issue as a standalone catalog task file (POST /api/tasks/import). The issue title becomes the goal and the task id.",
|
|
108
|
+
inputSchema: z.object({
|
|
109
|
+
project_root: projectRootField,
|
|
110
|
+
repo: z.string().describe("GitHub owner/repo"),
|
|
111
|
+
number: z.number().describe("Positive GitHub issue number"),
|
|
112
|
+
directory: z.string().describe("Directory relative to the project root, e.g. tasks"),
|
|
113
|
+
}),
|
|
114
|
+
}, async ({ project_root, ...body }) => {
|
|
115
|
+
const parsed = parseImportTaskBody(body);
|
|
116
|
+
if ("ok" in parsed)
|
|
117
|
+
return rejected(parsed);
|
|
118
|
+
const result = await importTaskFromIssue(await projectRootOf(deps, project_root), parsed, deps.issueFetch);
|
|
119
|
+
if (!result.ok)
|
|
120
|
+
return rejected(result);
|
|
121
|
+
return textResult({ task: result.task });
|
|
122
|
+
});
|
|
123
|
+
server.registerTool("create_stage_file", {
|
|
124
|
+
description: "Create an orphan stage YAML file that is not wired into a pipeline (POST /api/stages). Use create_stage to add a stage to a pipeline package.",
|
|
125
|
+
inputSchema: z.object({
|
|
126
|
+
project_root: projectRootField,
|
|
127
|
+
pipeline_directory: z.string().describe("Directory relative to the project root"),
|
|
128
|
+
filename: z.string().describe("Stage filename ending in .yaml or .yml"),
|
|
129
|
+
id: z.string().describe("Lowercase kebab-case stage id, 1-64 characters"),
|
|
130
|
+
system_prompt: z.string(),
|
|
131
|
+
model: z.string().optional(),
|
|
132
|
+
gate_kinds: z
|
|
133
|
+
.array(z.string())
|
|
134
|
+
.optional()
|
|
135
|
+
.describe(`Stage gate kinds. Each entry must be one of: ${STAGE_GATE_KINDS.join(", ")}`),
|
|
136
|
+
skill: z.string().optional(),
|
|
137
|
+
}),
|
|
138
|
+
}, async ({ project_root, ...body }) => {
|
|
139
|
+
const parsed = parseCreateStageBody(body);
|
|
140
|
+
if ("ok" in parsed)
|
|
141
|
+
return rejected(parsed);
|
|
142
|
+
const result = await createStage(await projectRootOf(deps, project_root), parsed);
|
|
143
|
+
if (!result.ok)
|
|
144
|
+
return rejected(result);
|
|
145
|
+
return textResult(result.stage);
|
|
146
|
+
});
|
|
147
|
+
server.registerTool("write_catalog_file", {
|
|
148
|
+
description: "Overwrite an existing stage catalog file (PUT /api/catalog/file). The file must already exist and remain valid stage YAML; an invalid write is restored.",
|
|
149
|
+
inputSchema: z.object({
|
|
150
|
+
project_root: projectRootField,
|
|
151
|
+
path: z.string().describe("Catalog-relative .yaml or .yml path"),
|
|
152
|
+
content: z.string().describe("Full stage YAML contents"),
|
|
153
|
+
}),
|
|
154
|
+
}, async ({ project_root, path: relPath, content }) => {
|
|
155
|
+
if (typeof relPath !== "string" || relPath.trim().length === 0) {
|
|
156
|
+
return rejected({ error: "path is required", status: 400 });
|
|
157
|
+
}
|
|
158
|
+
if (typeof content !== "string") {
|
|
159
|
+
return rejected({ error: "content must be a string", status: 400 });
|
|
160
|
+
}
|
|
161
|
+
const root = await projectRootOf(deps, project_root);
|
|
162
|
+
const roots = [
|
|
163
|
+
{
|
|
164
|
+
project_root: root,
|
|
165
|
+
path: root,
|
|
166
|
+
kind: "registered",
|
|
167
|
+
read_only: false,
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
try {
|
|
171
|
+
const result = await writeCatalogFileForHttp({
|
|
172
|
+
inputPath: relPath.trim(),
|
|
173
|
+
content,
|
|
174
|
+
projectRoot: root,
|
|
175
|
+
roots,
|
|
176
|
+
fieldName: "path",
|
|
177
|
+
});
|
|
178
|
+
if (!result.ok)
|
|
179
|
+
return rejected(result);
|
|
180
|
+
return textResult({ path: result.path, content: result.content });
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
if (err instanceof CatalogPathError) {
|
|
184
|
+
return rejected({
|
|
185
|
+
...catalogPathErrorBody(err),
|
|
186
|
+
status: err.code === "catalog_root_read_only" ? 403 : 400,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
server.registerTool("validate_catalog_draft", {
|
|
193
|
+
description: "Validate an in-memory pipeline draft package (POST /api/drafts/validate). Nothing is written. Pass the package as draft, or as top-level pipeline, stages, and task.",
|
|
194
|
+
inputSchema: z.union([
|
|
195
|
+
z.object({
|
|
196
|
+
project_root: projectRootField,
|
|
197
|
+
draft: z
|
|
198
|
+
.object({
|
|
199
|
+
pipeline: draftPipelineSchema,
|
|
200
|
+
stages: z.array(draftStageSchema).optional(),
|
|
201
|
+
task: draftTaskSchema.optional(),
|
|
202
|
+
})
|
|
203
|
+
.describe("Draft package."),
|
|
204
|
+
}),
|
|
205
|
+
z.object({
|
|
206
|
+
project_root: projectRootField,
|
|
207
|
+
pipeline: draftPipelineSchema,
|
|
208
|
+
stages: z.array(draftStageSchema).optional(),
|
|
209
|
+
task: draftTaskSchema.optional(),
|
|
210
|
+
}),
|
|
211
|
+
]),
|
|
212
|
+
annotations: { readOnlyHint: true },
|
|
213
|
+
}, async (body) => {
|
|
214
|
+
const parsed = parseDraftPackageBody(body);
|
|
215
|
+
if ("ok" in parsed)
|
|
216
|
+
return rejected(parsed);
|
|
217
|
+
const root = await projectRootOf(deps, body.project_root);
|
|
218
|
+
const result = await validateDraftPackage(parsed, {
|
|
219
|
+
cwd: root,
|
|
220
|
+
projectRoot: root,
|
|
221
|
+
strict: true,
|
|
222
|
+
});
|
|
223
|
+
return textResult(result);
|
|
224
|
+
});
|
|
225
|
+
}
|