stageflow 0.35.0 → 0.39.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 +18 -8
- package/dist/agent/claudeAdapter.js +12 -0
- package/dist/agent/piAdapter.d.ts +9 -1
- package/dist/agent/piAdapter.js +30 -2
- package/dist/agent/piIsolatedMcp.js +14 -2
- package/dist/agent/piIsolatedMcpProbe.d.ts +5 -1
- package/dist/agent/piIsolatedMcpProbe.js +16 -30
- package/dist/agent/port.d.ts +7 -0
- package/dist/agents/agentId.d.ts +8 -0
- package/dist/agents/agentId.js +27 -0
- package/dist/agents/approvalBroker.d.ts +16 -0
- package/dist/agents/approvalBroker.js +45 -0
- package/dist/agents/authorChat.d.ts +19 -0
- package/dist/agents/authorChat.js +63 -0
- package/dist/agents/chatTurn.d.ts +23 -0
- package/dist/agents/chatTurn.js +77 -0
- package/dist/agents/communications/agentmail.d.ts +19 -0
- package/dist/agents/communications/agentmail.js +46 -0
- package/dist/agents/communications/agentmailListener.d.ts +59 -0
- package/dist/agents/communications/agentmailListener.js +183 -0
- package/dist/agents/communications/allowlist.d.ts +10 -0
- package/dist/agents/communications/allowlist.js +36 -0
- package/dist/agents/communications/channelConnections.d.ts +3 -0
- package/dist/agents/communications/channelConnections.js +51 -0
- package/dist/agents/communications/delivery.d.ts +53 -0
- package/dist/agents/communications/delivery.js +142 -0
- package/dist/agents/communications/email.d.ts +26 -0
- package/dist/agents/communications/email.js +20 -0
- package/dist/agents/communications/identity.d.ts +13 -0
- package/dist/agents/communications/identity.js +18 -0
- package/dist/agents/communications/imessageListener.d.ts +41 -0
- package/dist/agents/communications/imessageListener.js +92 -0
- package/dist/agents/communications/inbound.d.ts +63 -0
- package/dist/agents/communications/inbound.js +143 -0
- package/dist/agents/communications/listener.d.ts +36 -0
- package/dist/agents/communications/listener.js +38 -0
- package/dist/agents/communications/photon.d.ts +19 -0
- package/dist/agents/communications/photon.js +38 -0
- package/dist/agents/communications/port.d.ts +27 -0
- package/dist/agents/communications/port.js +6 -0
- package/dist/agents/communications/slack.d.ts +28 -0
- package/dist/agents/communications/slack.js +65 -0
- package/dist/agents/communications/supervisor.d.ts +36 -0
- package/dist/agents/communications/supervisor.js +96 -0
- package/dist/agents/communications/telegram.d.ts +19 -0
- package/dist/agents/communications/telegram.js +52 -0
- package/dist/agents/definitionSummary.d.ts +13 -0
- package/dist/agents/definitionSummary.js +91 -0
- package/dist/agents/delegate.d.ts +3 -0
- package/dist/agents/delegate.js +8 -0
- package/dist/agents/errors.d.ts +10 -0
- package/dist/agents/errors.js +32 -0
- package/dist/agents/guardrails.d.ts +24 -0
- package/dist/agents/guardrails.js +39 -0
- package/dist/agents/historyFit.d.ts +10 -0
- package/dist/agents/historyFit.js +26 -0
- package/dist/agents/hostContext.d.ts +54 -0
- package/dist/agents/hostContext.js +156 -0
- package/dist/agents/ledgers.d.ts +29 -0
- package/dist/agents/ledgers.js +31 -0
- package/dist/agents/lineDiff.d.ts +12 -0
- package/dist/agents/lineDiff.js +46 -0
- package/dist/agents/ndjson.d.ts +9 -0
- package/dist/agents/ndjson.js +47 -0
- package/dist/agents/otlpExport.d.ts +9 -0
- package/dist/agents/otlpExport.js +58 -0
- package/dist/agents/otlpSink.d.ts +7 -0
- package/dist/agents/otlpSink.js +23 -0
- package/dist/agents/policy.d.ts +31 -0
- package/dist/agents/policy.js +84 -0
- package/dist/agents/projectRoot.d.ts +5 -0
- package/dist/agents/projectRoot.js +15 -0
- package/dist/agents/runtime/piRuntime.d.ts +52 -0
- package/dist/agents/runtime/piRuntime.js +474 -0
- package/dist/agents/runtime/types.d.ts +77 -0
- package/dist/agents/runtime/types.js +26 -0
- package/dist/agents/sandbox.d.ts +17 -0
- package/dist/agents/sandbox.js +10 -0
- package/dist/agents/scheduleSource.d.ts +32 -0
- package/dist/agents/scheduleSource.js +100 -0
- package/dist/agents/service.d.ts +298 -0
- package/dist/agents/service.js +1105 -0
- package/dist/agents/sessions.d.ts +38 -0
- package/dist/agents/sessions.js +321 -0
- package/dist/agents/store.d.ts +277 -0
- package/dist/agents/store.js +643 -0
- package/dist/agents/trace.d.ts +27 -0
- package/dist/agents/trace.js +29 -0
- package/dist/agents/turnLifecycle.d.ts +72 -0
- package/dist/agents/turnLifecycle.js +157 -0
- package/dist/agents/webChat/publicChat.d.ts +36 -0
- package/dist/agents/webChat/publicChat.js +290 -0
- package/dist/agents/webChat/rateLimit.d.ts +15 -0
- package/dist/agents/webChat/rateLimit.js +33 -0
- package/dist/agents/webChat/slug.d.ts +2 -0
- package/dist/agents/webChat/slug.js +9 -0
- package/dist/channels/config.d.ts +11 -0
- package/dist/channels/config.js +95 -0
- package/dist/channels/configWriter.d.ts +40 -0
- package/dist/channels/configWriter.js +148 -0
- package/dist/channels/connectionEdits.d.ts +34 -0
- package/dist/channels/connectionEdits.js +53 -0
- package/dist/channels/errorInfo.d.ts +18 -0
- package/dist/channels/errorInfo.js +71 -0
- package/dist/channels/errors.d.ts +12 -0
- package/dist/channels/errors.js +13 -0
- package/dist/channels/health.d.ts +41 -0
- package/dist/channels/health.js +99 -0
- package/dist/channels/host.d.ts +55 -0
- package/dist/channels/host.js +185 -0
- package/dist/channels/http.d.ts +31 -0
- package/dist/channels/http.js +79 -0
- package/dist/channels/index.d.ts +10 -0
- package/dist/channels/index.js +9 -0
- package/dist/channels/ledger.d.ts +37 -0
- package/dist/channels/ledger.js +95 -0
- package/dist/channels/providers/agentmail.d.ts +26 -0
- package/dist/channels/providers/agentmail.js +94 -0
- package/dist/channels/providers/kit.d.ts +40 -0
- package/dist/channels/providers/kit.js +78 -0
- package/dist/channels/providers/photonImessage.d.ts +45 -0
- package/dist/channels/providers/photonImessage.js +188 -0
- package/dist/channels/providers/photonWhatsapp.d.ts +49 -0
- package/dist/channels/providers/photonWhatsapp.js +146 -0
- package/dist/channels/providers/slack.d.ts +25 -0
- package/dist/channels/providers/slack.js +102 -0
- package/dist/channels/providers/telegram.d.ts +21 -0
- package/dist/channels/providers/telegram.js +101 -0
- package/dist/channels/providers.d.ts +5 -0
- package/dist/channels/providers.js +22 -0
- package/dist/channels/registry.d.ts +54 -0
- package/dist/channels/registry.js +186 -0
- package/dist/channels/secretStore.d.ts +21 -0
- package/dist/channels/secretStore.js +75 -0
- package/dist/channels/secrets.d.ts +15 -0
- package/dist/channels/secrets.js +50 -0
- package/dist/channels/service.d.ts +54 -0
- package/dist/channels/service.js +307 -0
- package/dist/channels/stageChannels.d.ts +45 -0
- package/dist/channels/stageChannels.js +110 -0
- package/dist/channels/types.d.ts +169 -0
- package/dist/channels/types.js +1 -0
- package/dist/cli/agentsCommand.d.ts +14 -0
- package/dist/cli/agentsCommand.js +291 -0
- package/dist/cli/doctorCommand.js +3 -1
- package/dist/cli.js +9 -0
- package/dist/config/browseCatalog.js +4 -11
- package/dist/config/draftPackage.d.ts +2 -0
- package/dist/config/draftPackage.js +8 -0
- package/dist/config/hostConfig.js +8 -0
- package/dist/config/loadAgent.d.ts +266 -0
- package/dist/config/loadAgent.js +374 -0
- package/dist/config/loadPipeline.js +31 -4
- package/dist/config/loadStage.js +15 -0
- package/dist/config/mcpHostCatalog.d.ts +50 -0
- package/dist/config/mcpHostCatalog.js +405 -0
- package/dist/config/mcpOAuth.d.ts +37 -0
- package/dist/config/mcpOAuth.js +263 -0
- package/dist/config/mcpRedaction.d.ts +42 -0
- package/dist/config/mcpRedaction.js +314 -0
- package/dist/config/mcpToolCache.d.ts +20 -0
- package/dist/config/mcpToolCache.js +43 -0
- package/dist/config/mergePipelineIncludes.d.ts +5 -0
- package/dist/config/mergePipelineIncludes.js +52 -0
- package/dist/config/pipelineStageKeys.js +1 -0
- package/dist/config/resolveStageMcpServers.d.ts +35 -3
- package/dist/config/resolveStageMcpServers.js +124 -9
- package/dist/config/validateCatalog.d.ts +4 -1
- package/dist/config/validateCatalog.js +45 -1
- package/dist/errors/codes.d.ts +1 -1
- package/dist/errors/codes.js +1 -0
- package/dist/mcp/agentTools.d.ts +8 -0
- package/dist/mcp/agentTools.js +132 -0
- package/dist/mcp/catalogTools.js +7 -9
- package/dist/mcp/deps.d.ts +6 -0
- package/dist/mcp/pipelineAuthorSupport.d.ts +19 -0
- package/dist/mcp/pipelineAuthorSupport.js +10 -0
- package/dist/mcp/pipelineAuthorTools.d.ts +8 -0
- package/dist/mcp/pipelineAuthorTools.js +109 -0
- package/dist/mcp/server.js +5 -0
- package/dist/mcp/tools.js +4 -0
- package/dist/mcp/triggerTools.js +32 -0
- package/dist/memory/access.d.ts +36 -0
- package/dist/memory/access.js +116 -0
- package/dist/memory/agentIssues.d.ts +3 -0
- package/dist/memory/agentIssues.js +23 -0
- package/dist/memory/bashDeny.d.ts +6 -0
- package/dist/memory/bashDeny.js +35 -0
- package/dist/memory/host.d.ts +51 -0
- package/dist/memory/host.js +394 -0
- package/dist/memory/ids.d.ts +15 -0
- package/dist/memory/ids.js +31 -0
- package/dist/memory/worker.d.ts +1 -0
- package/dist/memory/worker.js +84 -0
- package/dist/models/catalog.d.ts +113 -0
- package/dist/models/catalog.js +238 -0
- package/dist/models/response.d.ts +29 -0
- package/dist/models/response.js +25 -0
- package/dist/operatorAgent/agentAuthorContext.d.ts +17 -0
- package/dist/operatorAgent/agentAuthorContext.js +32 -0
- package/dist/operatorAgent/fakeHost.js +2 -0
- package/dist/operatorAgent/host.js +6 -3
- package/dist/operatorAgent/index.d.ts +4 -0
- package/dist/operatorAgent/index.js +4 -0
- package/dist/operatorAgent/piModel.js +2 -0
- package/dist/operatorAgent/profiles/agentAuthor.d.ts +11 -0
- package/dist/operatorAgent/profiles/agentAuthor.js +192 -0
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -0
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +29 -0
- package/dist/operatorAgent/profiles/workshopAuthor.js +2 -2
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +3 -3
- package/dist/operatorAgent/proposals.d.ts +1 -0
- package/dist/operatorAgent/proposals.js +23 -2
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/preflight/channelChecks.d.ts +17 -0
- package/dist/preflight/channelChecks.js +106 -0
- package/dist/preflight/pipelinePreflight.d.ts +9 -2
- package/dist/preflight/pipelinePreflight.js +10 -1
- package/dist/runstore/createStore.d.ts +2 -0
- package/dist/runstore/createStore.js +8 -0
- package/dist/runstore/overviewSummary.d.ts +56 -0
- package/dist/runstore/overviewSummary.js +134 -0
- package/dist/runstore/port.d.ts +3 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +18 -0
- package/dist/runstore/sqlite/migrations/013-agents.d.ts +12 -0
- package/dist/runstore/sqlite/migrations/013-agents.js +156 -0
- package/dist/runstore/sqlite/migrations/014-agent-deliveries.d.ts +11 -0
- package/dist/runstore/sqlite/migrations/014-agent-deliveries.js +32 -0
- package/dist/runstore/sqlite/migrations/index.js +4 -0
- package/dist/runtime/pipelineRunner.js +3 -1
- package/dist/runtime/repositoryMaterialize.d.ts +1 -1
- package/dist/runtime/runManager.js +4 -1
- package/dist/runtime/stageAttemptBootstrap.js +31 -1
- package/dist/runtime/stageEnvironment.d.ts +2 -0
- package/dist/runtime/stageEnvironment.js +6 -1
- package/dist/runtime/stageProcessLauncher.js +54 -1
- package/dist/runtime/stageSecrets.d.ts +6 -1
- package/dist/runtime/stageSecrets.js +34 -1
- package/dist/server/agentAuthorRoutes.d.ts +5 -0
- package/dist/server/agentAuthorRoutes.js +64 -0
- package/dist/server/agentRoutes.d.ts +5 -0
- package/dist/server/agentRoutes.js +323 -0
- package/dist/server/bootstrap.d.ts +11 -0
- package/dist/server/bootstrap.js +40 -10
- package/dist/server/channelRoutes.d.ts +9 -0
- package/dist/server/channelRoutes.js +88 -0
- package/dist/server/controlToken.js +8 -0
- package/dist/server/http.d.ts +8 -1
- package/dist/server/http.js +183 -20
- package/dist/server/mcpHost.js +1 -0
- package/dist/server/memoryRoutes.d.ts +5 -0
- package/dist/server/memoryRoutes.js +146 -0
- package/dist/server/projectMcpRoutes.d.ts +1 -0
- package/dist/server/projectMcpRoutes.js +241 -3
- package/dist/toolkit/guard.d.ts +27 -0
- package/dist/toolkit/guard.js +98 -0
- package/dist/toolkit/index.d.ts +48 -0
- package/dist/toolkit/index.js +82 -0
- package/dist/toolkit/meta.d.ts +36 -0
- package/dist/toolkit/meta.js +117 -0
- package/dist/toolkit/profiles.d.ts +13 -0
- package/dist/toolkit/profiles.js +25 -0
- package/dist/toolkit/record.d.ts +15 -0
- package/dist/toolkit/record.js +17 -0
- package/dist/tools/memoryTools.d.ts +95 -0
- package/dist/tools/memoryTools.js +179 -0
- package/dist/tools/sendMessage.d.ts +21 -0
- package/dist/tools/sendMessage.js +25 -0
- package/dist/types/pipeline.d.ts +7 -1
- package/dist/types/stage.d.ts +15 -0
- package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +9 -0
- package/dist/ui/assets/MarkdownText-D7tYaIFO.js +29 -0
- package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +6 -0
- package/dist/ui/assets/Tooltip-DrpoRRNi.js +1 -0
- package/dist/ui/assets/chat-BbLukq9W.js +5 -0
- package/dist/ui/assets/chat-zIZU_bP1.css +2 -0
- package/dist/ui/assets/jsx-runtime-iVoFAvQ_.js +1 -0
- package/dist/ui/assets/{index-CIPbbBUi.css → main-BFo79krC.css} +1 -1
- package/dist/ui/assets/main-CijYMeM0.js +259 -0
- package/dist/ui/chat.html +25 -0
- package/dist/ui/index.html +6 -2
- package/dist/workshop/modelSettings.d.ts +2 -2
- package/dist/workshop/modelSettings.js +5 -11
- package/examples/README.md +1 -0
- package/examples/channels-smoke/README.md +23 -0
- package/examples/channels-smoke/channels-smoke.pipeline.yaml +11 -0
- package/examples/channels-smoke/channels-smoke.task.yaml +3 -0
- package/examples/channels-smoke/send-email.yaml +32 -0
- package/examples/channels-smoke/send-text.yaml +31 -0
- package/examples/github-release/README.md +1 -1
- package/examples/memory/README.md +99 -0
- package/examples/memory/desk-outage.task.yaml +15 -0
- package/examples/memory/desk-refund.task.yaml +15 -0
- package/examples/memory/desk-revisit-outage.task.yaml +11 -0
- package/examples/memory/desk-revisit.pipeline.yaml +123 -0
- package/examples/memory/desk.pipeline.yaml +192 -0
- package/examples/memory/file-card.pipeline.yaml +39 -0
- package/examples/memory/file-ledger.task.yaml +22 -0
- package/examples/memory/file-release.task.yaml +24 -0
- package/examples/memory/file-rich.pipeline.yaml +40 -0
- package/examples/memory/file-ticket.task.yaml +7 -0
- package/examples/memory/file-timeline.task.yaml +28 -0
- package/examples/memory/find-card.pipeline.yaml +46 -0
- package/examples/memory/find-ticket.task.yaml +7 -0
- package/examples/memory/missing.pipeline.yaml +16 -0
- package/examples/memory/recall.pipeline.yaml +24 -0
- package/examples/memory/recall.task.yaml +5 -0
- package/examples/memory/remember-ada.task.yaml +6 -0
- package/examples/memory/remember-grace.task.yaml +6 -0
- package/examples/memory/remember.pipeline.yaml +58 -0
- package/examples/memory/stageflow.yaml +12 -0
- package/examples/memory/update-card.pipeline.yaml +43 -0
- package/examples/memory/update-ledger.task.yaml +23 -0
- package/examples/memory/update-rich.pipeline.yaml +44 -0
- package/examples/memory/update-ticket.task.yaml +7 -0
- package/examples/stage-mcp/README.md +1 -1
- package/package.json +9 -3
- package/dist/ui/assets/index-BoFPkKFg.js +0 -256
|
@@ -2,6 +2,7 @@ import type { PayloadSchemaMap } from "../envelope/payloadSchema.js";
|
|
|
2
2
|
import type { InlinePipelineDefinition } from "../types/pipeline.js";
|
|
3
3
|
import { type LoadIssue, type LoadOutcome } from "./loadOutcome.js";
|
|
4
4
|
import { type ToolRequirement } from "./toolRequires.js";
|
|
5
|
+
import type { StageChannel } from "../types/stage.js";
|
|
5
6
|
export type RawMergedEntry = {
|
|
6
7
|
raw: unknown;
|
|
7
8
|
declaringPath: string;
|
|
@@ -13,6 +14,8 @@ export declare function mergePipelineStages(rootPath: string): Promise<LoadOutco
|
|
|
13
14
|
model?: string;
|
|
14
15
|
schemas?: PayloadSchemaMap;
|
|
15
16
|
requires?: ToolRequirement[];
|
|
17
|
+
channels?: StageChannel[];
|
|
18
|
+
memories?: string[];
|
|
16
19
|
warnings: LoadIssue[];
|
|
17
20
|
}>>;
|
|
18
21
|
/** Synthetic `declaringPath` for stage entries with no backing file. */
|
|
@@ -24,5 +27,7 @@ export declare function mergeInlinePipelineStages(raw: InlinePipelineDefinition)
|
|
|
24
27
|
model?: string;
|
|
25
28
|
schemas?: PayloadSchemaMap;
|
|
26
29
|
requires?: ToolRequirement[];
|
|
30
|
+
channels?: StageChannel[];
|
|
31
|
+
memories?: string[];
|
|
27
32
|
warnings: LoadIssue[];
|
|
28
33
|
}>>;
|
|
@@ -6,6 +6,8 @@ import { parseModelField } from "./modelField.js";
|
|
|
6
6
|
import { readYamlObject } from "./readYamlObject.js";
|
|
7
7
|
import { yamlParsePosition } from "./yamlParsePosition.js";
|
|
8
8
|
import { parseToolRequires } from "./toolRequires.js";
|
|
9
|
+
import { parseChannels } from "../channels/stageChannels.js";
|
|
10
|
+
import { parseMemoryIdList } from "../memory/ids.js";
|
|
9
11
|
import { classifyYamlDocument, collectDocumentKeys, mixedDialectIssue, } from "./yamlDialect.js";
|
|
10
12
|
function isPlainObject(value) {
|
|
11
13
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -189,6 +191,14 @@ export async function mergePipelineStages(rootPath) {
|
|
|
189
191
|
if (!requiresOutcome.ok)
|
|
190
192
|
return requiresOutcome;
|
|
191
193
|
const requires = requiresOutcome.value;
|
|
194
|
+
const channelsOutcome = parsePipelineChannels(raw.channels, `pipeline ${absRoot}`, pipelineId);
|
|
195
|
+
if (!channelsOutcome.ok)
|
|
196
|
+
return channelsOutcome;
|
|
197
|
+
const channels = channelsOutcome.value;
|
|
198
|
+
const memoriesOutcome = parsePipelineMemories(raw.memories, `pipeline ${absRoot}`, pipelineId);
|
|
199
|
+
if (!memoriesOutcome.ok)
|
|
200
|
+
return memoriesOutcome;
|
|
201
|
+
const memories = memoriesOutcome.value;
|
|
192
202
|
const idLocations = new Map();
|
|
193
203
|
const entries = [];
|
|
194
204
|
const warnings = [];
|
|
@@ -211,6 +221,8 @@ export async function mergePipelineStages(rootPath) {
|
|
|
211
221
|
...(model !== undefined ? { model } : {}),
|
|
212
222
|
...(schemas !== undefined ? { schemas } : {}),
|
|
213
223
|
...(requires !== undefined ? { requires } : {}),
|
|
224
|
+
...(channels !== undefined ? { channels } : {}),
|
|
225
|
+
...(memories !== undefined ? { memories } : {}),
|
|
214
226
|
warnings,
|
|
215
227
|
});
|
|
216
228
|
}
|
|
@@ -273,6 +285,14 @@ export async function mergeInlinePipelineStages(raw) {
|
|
|
273
285
|
if (!requiresOutcome.ok)
|
|
274
286
|
return requiresOutcome;
|
|
275
287
|
const requires = requiresOutcome.value;
|
|
288
|
+
const channelsOutcome = parsePipelineChannels(raw.channels, `inline pipeline ${pipelineId}`, pipelineId);
|
|
289
|
+
if (!channelsOutcome.ok)
|
|
290
|
+
return channelsOutcome;
|
|
291
|
+
const channels = channelsOutcome.value;
|
|
292
|
+
const memoriesOutcome = parsePipelineMemories(raw.memories, `inline pipeline ${pipelineId}`, pipelineId);
|
|
293
|
+
if (!memoriesOutcome.ok)
|
|
294
|
+
return memoriesOutcome;
|
|
295
|
+
const memories = memoriesOutcome.value;
|
|
276
296
|
const entries = [];
|
|
277
297
|
for (let index = 0; index < raw.stages.length; index++) {
|
|
278
298
|
const entry = raw.stages[index];
|
|
@@ -295,9 +315,41 @@ export async function mergeInlinePipelineStages(raw) {
|
|
|
295
315
|
...(model !== undefined ? { model } : {}),
|
|
296
316
|
...(schemas !== undefined ? { schemas } : {}),
|
|
297
317
|
...(requires !== undefined ? { requires } : {}),
|
|
318
|
+
...(channels !== undefined ? { channels } : {}),
|
|
319
|
+
...(memories !== undefined ? { memories } : {}),
|
|
298
320
|
warnings: [],
|
|
299
321
|
});
|
|
300
322
|
}
|
|
323
|
+
function parsePipelineMemories(raw, label, pipelineId) {
|
|
324
|
+
const parsed = parseMemoryIdList(raw);
|
|
325
|
+
if (!parsed.ok) {
|
|
326
|
+
return loadFailure([
|
|
327
|
+
{
|
|
328
|
+
code: "pipeline.invalid_memories",
|
|
329
|
+
message: `Invalid ${label}: ${parsed.message}`,
|
|
330
|
+
category: "pipeline",
|
|
331
|
+
pipelineId,
|
|
332
|
+
},
|
|
333
|
+
]);
|
|
334
|
+
}
|
|
335
|
+
return loadSuccess(parsed.value);
|
|
336
|
+
}
|
|
337
|
+
function parsePipelineChannels(raw, label, pipelineId) {
|
|
338
|
+
if (raw === undefined)
|
|
339
|
+
return loadSuccess(undefined);
|
|
340
|
+
const parsed = parseChannels(raw);
|
|
341
|
+
if (!parsed.ok) {
|
|
342
|
+
return loadFailure([
|
|
343
|
+
{
|
|
344
|
+
code: "pipeline.invalid_channels",
|
|
345
|
+
message: `Invalid ${label}: ${parsed.message}`,
|
|
346
|
+
category: "pipeline",
|
|
347
|
+
pipelineId,
|
|
348
|
+
},
|
|
349
|
+
]);
|
|
350
|
+
}
|
|
351
|
+
return loadSuccess(parsed.value);
|
|
352
|
+
}
|
|
301
353
|
function parsePipelineSchemas(raw, absPath, pipelineId) {
|
|
302
354
|
if (raw === undefined)
|
|
303
355
|
return loadSuccess(undefined);
|
|
@@ -29,6 +29,7 @@ export const WIRING_KEYS = new Set([
|
|
|
29
29
|
/** Stage body keys. Target: `io` / `verify`. IR/legacy: `payload_schema`, `pre_emit_checks`, `clone_input_schema`. */
|
|
30
30
|
export const BODY_KEYS = new Set([
|
|
31
31
|
"email",
|
|
32
|
+
"channels",
|
|
32
33
|
"system_prompt",
|
|
33
34
|
"model",
|
|
34
35
|
"payload_schema",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type StageMcpErrorCode = "missing_catalog" | "unknown_server" | "reserved_name" | "unresolved_var" | "invalid_config" | "untrusted_config_origin" | "connect_failed";
|
|
1
|
+
export type StageMcpErrorCode = "missing_catalog" | "unknown_server" | "reserved_name" | "unresolved_var" | "invalid_config" | "untrusted_config_origin" | "connect_failed" | "host_state_corrupt";
|
|
2
2
|
export declare class StageMcpError extends Error {
|
|
3
3
|
readonly code: StageMcpErrorCode;
|
|
4
4
|
constructor(message: string, code: StageMcpErrorCode);
|
|
@@ -9,18 +9,45 @@ export declare const MCP_CATALOG_FILENAME = ".mcp.json";
|
|
|
9
9
|
export declare const STAGEFLOW_STAGE_ARTIFACTS_DIR_ENV = "STAGEFLOW_STAGE_ARTIFACTS_DIR";
|
|
10
10
|
export declare function stampStagePromptArtifactsDir(prompt: string, artifactsDir: string): string;
|
|
11
11
|
export type McpCatalogServers = Record<string, Record<string, unknown>>;
|
|
12
|
+
export type McpServerOrigin = "project" | "console";
|
|
12
13
|
export type InspectedMcpCatalog = {
|
|
13
14
|
path: string;
|
|
14
15
|
servers: McpCatalogServers;
|
|
16
|
+
/** Where each server came from; `console` = saved from the UI under $STAGEFLOW_HOME. */
|
|
17
|
+
origins: Record<string, McpServerOrigin>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Who is asking. Without a scope only the project `.mcp.json` is visible; with one, servers saved
|
|
21
|
+
* from the console are merged in (console entry wins on a name clash) and secrets are filled by
|
|
22
|
+
* the rules of that scope:
|
|
23
|
+
* - stage: console secrets whose names the stage declared in `secrets:`, plus the stage env
|
|
24
|
+
* - agent: console secrets plus Host env names listed in STAGEFLOW_MCP_ENV_ALLOW
|
|
25
|
+
* - probe: same as agent, so a probe and an agent run resolve one config identically
|
|
26
|
+
* - validate: placeholders are checked but no secret value is ever read into the output
|
|
27
|
+
* Console secrets always win over Host env for the same name.
|
|
28
|
+
*/
|
|
29
|
+
export type McpResolveScope = {
|
|
30
|
+
kind: "stage";
|
|
31
|
+
secrets: readonly string[];
|
|
32
|
+
} | {
|
|
33
|
+
kind: "agent";
|
|
34
|
+
} | {
|
|
35
|
+
kind: "probe";
|
|
36
|
+
} | {
|
|
37
|
+
kind: "validate";
|
|
38
|
+
};
|
|
39
|
+
export type McpCatalogLoadOptions = {
|
|
40
|
+
scope?: McpResolveScope;
|
|
15
41
|
};
|
|
16
42
|
export declare function mcpCatalogPath(projectRoot: string): string;
|
|
17
43
|
export declare function parseMcpCatalog(raw: string, catalogPath?: string): McpCatalogServers;
|
|
18
|
-
export declare function mcpCatalogExists(projectRoot: string): Promise<boolean>;
|
|
19
|
-
export declare function loadMcpCatalog(projectRoot: string): Promise<InspectedMcpCatalog>;
|
|
44
|
+
export declare function mcpCatalogExists(projectRoot: string, options?: McpCatalogLoadOptions): Promise<boolean>;
|
|
45
|
+
export declare function loadMcpCatalog(projectRoot: string, options?: McpCatalogLoadOptions): Promise<InspectedMcpCatalog>;
|
|
20
46
|
export type ProjectMcpCatalogTransport = "stdio" | "http";
|
|
21
47
|
export type ProjectMcpCatalogEntry = {
|
|
22
48
|
name: string;
|
|
23
49
|
transport: ProjectMcpCatalogTransport;
|
|
50
|
+
origin: McpServerOrigin;
|
|
24
51
|
};
|
|
25
52
|
export type ProjectMcpCatalogListStatus = "ok" | "missing_catalog" | "invalid_config";
|
|
26
53
|
export type ProjectMcpCatalogList = {
|
|
@@ -32,7 +59,10 @@ export declare function assertMcpAllowlistKnown(servers: McpCatalogServers, allo
|
|
|
32
59
|
export declare function resolveStageMcpServers(options: {
|
|
33
60
|
projectRoot: string;
|
|
34
61
|
allowlist?: readonly string[];
|
|
62
|
+
/** Stage env for the project-only and stage scopes; agent/probe/validate do not read it as a secret source. */
|
|
35
63
|
env: NodeJS.ProcessEnv;
|
|
64
|
+
/** Host env that STAGEFLOW_MCP_ENV_ALLOW may draw from (agent/probe scopes). Defaults to process.env. */
|
|
65
|
+
hostEnv?: NodeJS.ProcessEnv;
|
|
36
66
|
stageId?: string;
|
|
37
67
|
bindingKind?: "repository" | "checkout" | "none";
|
|
38
68
|
trustWorkspaceConfig?: string[];
|
|
@@ -40,4 +70,6 @@ export declare function resolveStageMcpServers(options: {
|
|
|
40
70
|
trustProjectRoot?: string;
|
|
41
71
|
/** When true, .mcp.json is treated as workspace-sourced. */
|
|
42
72
|
workspaceSourced?: boolean;
|
|
73
|
+
/** Merge console-saved servers and fill `${VAR}` by the scope's rules. Omit for project-only. */
|
|
74
|
+
scope?: McpResolveScope;
|
|
43
75
|
}): Promise<ResolvedMcpServers>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { STAGEFLOW_MCP_SERVER_NAME } from "../agent/claudeTools.js";
|
|
4
|
+
import { isMcpHostStateError, mcpEnvAllowNames, readHostMcpServers, readMcpSecrets, } from "./mcpHostCatalog.js";
|
|
5
|
+
import { assertSafeMcpUrl, McpUrlPolicyError } from "./mcpRedaction.js";
|
|
4
6
|
export class StageMcpError extends Error {
|
|
5
7
|
code;
|
|
6
8
|
constructor(message, code) {
|
|
@@ -51,7 +53,31 @@ export function parseMcpCatalog(raw, catalogPath = MCP_CATALOG_FILENAME) {
|
|
|
51
53
|
}
|
|
52
54
|
return servers;
|
|
53
55
|
}
|
|
54
|
-
|
|
56
|
+
/** Console servers; a corrupt host file surfaces as a StageMcpError instead of reading as empty. */
|
|
57
|
+
function hostServersOrThrow() {
|
|
58
|
+
try {
|
|
59
|
+
return readHostMcpServers();
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (isMcpHostStateError(err))
|
|
63
|
+
throw new StageMcpError(err.message, "host_state_corrupt");
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function hostSecretsOrThrow() {
|
|
68
|
+
try {
|
|
69
|
+
return readMcpSecrets();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
if (isMcpHostStateError(err))
|
|
73
|
+
throw new StageMcpError(err.message, "host_state_corrupt");
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export async function mcpCatalogExists(projectRoot, options = {}) {
|
|
78
|
+
if (options.scope !== undefined && Object.keys(hostServersOrThrow()).length > 0) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
55
81
|
try {
|
|
56
82
|
await readFile(mcpCatalogPath(projectRoot), "utf8");
|
|
57
83
|
return true;
|
|
@@ -63,20 +89,31 @@ export async function mcpCatalogExists(projectRoot) {
|
|
|
63
89
|
throw err;
|
|
64
90
|
}
|
|
65
91
|
}
|
|
66
|
-
export async function loadMcpCatalog(projectRoot) {
|
|
92
|
+
export async function loadMcpCatalog(projectRoot, options = {}) {
|
|
67
93
|
const catalogPath = mcpCatalogPath(projectRoot);
|
|
94
|
+
const hostServers = options.scope !== undefined ? hostServersOrThrow() : {};
|
|
68
95
|
let raw;
|
|
69
96
|
try {
|
|
70
97
|
raw = await readFile(catalogPath, "utf8");
|
|
71
98
|
}
|
|
72
99
|
catch (err) {
|
|
73
100
|
const code = err.code;
|
|
74
|
-
if (code
|
|
101
|
+
if (code !== "ENOENT") {
|
|
102
|
+
throw new StageMcpError(`MCP catalog "${MCP_CATALOG_FILENAME}" could not be read`, "invalid_config");
|
|
103
|
+
}
|
|
104
|
+
if (Object.keys(hostServers).length === 0) {
|
|
75
105
|
throw new StageMcpError(`MCP catalog "${MCP_CATALOG_FILENAME}" is missing`, "missing_catalog");
|
|
76
106
|
}
|
|
77
|
-
throw new StageMcpError(`MCP catalog "${MCP_CATALOG_FILENAME}" could not be read`, "invalid_config");
|
|
78
107
|
}
|
|
79
|
-
|
|
108
|
+
const servers = raw !== undefined ? parseMcpCatalog(raw, catalogPath) : {};
|
|
109
|
+
const origins = {};
|
|
110
|
+
for (const name of Object.keys(servers))
|
|
111
|
+
origins[name] = "project";
|
|
112
|
+
for (const [name, entry] of Object.entries(hostServers)) {
|
|
113
|
+
servers[name] = entry;
|
|
114
|
+
origins[name] = "console";
|
|
115
|
+
}
|
|
116
|
+
return { path: catalogPath, servers, origins };
|
|
80
117
|
}
|
|
81
118
|
function coarseMcpTransport(entry) {
|
|
82
119
|
if (Object.hasOwn(entry, "command")) {
|
|
@@ -89,14 +126,20 @@ function coarseMcpTransport(entry) {
|
|
|
89
126
|
}
|
|
90
127
|
export async function listProjectMcpCatalog(projectRoot) {
|
|
91
128
|
try {
|
|
92
|
-
const catalog = await loadMcpCatalog(projectRoot
|
|
129
|
+
const catalog = await loadMcpCatalog(projectRoot, {
|
|
130
|
+
scope: { kind: "validate" },
|
|
131
|
+
});
|
|
93
132
|
const servers = [];
|
|
94
133
|
for (const [name, entry] of Object.entries(catalog.servers)) {
|
|
95
134
|
const transport = coarseMcpTransport(entry);
|
|
96
135
|
if (transport === undefined) {
|
|
97
136
|
return { status: "invalid_config", servers: [] };
|
|
98
137
|
}
|
|
99
|
-
servers.push({
|
|
138
|
+
servers.push({
|
|
139
|
+
name,
|
|
140
|
+
transport,
|
|
141
|
+
origin: catalog.origins[name] ?? "project",
|
|
142
|
+
});
|
|
100
143
|
}
|
|
101
144
|
return { status: "ok", servers };
|
|
102
145
|
}
|
|
@@ -207,6 +250,69 @@ function interpolateServer(entry, env, serverName, projectRoot, stageId) {
|
|
|
207
250
|
}
|
|
208
251
|
return stampSpawnRoot(resolved, projectRoot, serverName);
|
|
209
252
|
}
|
|
253
|
+
function bagOfStrings(value) {
|
|
254
|
+
return isPlainObject(value) && Object.values(value).every((v) => typeof v === "string");
|
|
255
|
+
}
|
|
256
|
+
/** Shape check for any entry about to be used (project or console). */
|
|
257
|
+
function assertEntryShape(name, entry) {
|
|
258
|
+
const bad = (what) => {
|
|
259
|
+
throw new StageMcpError(`MCP catalog server "${name}" ${what}`, "invalid_config");
|
|
260
|
+
};
|
|
261
|
+
const hasCommand = Object.hasOwn(entry, "command");
|
|
262
|
+
const hasUrl = Object.hasOwn(entry, "url");
|
|
263
|
+
if (hasCommand === hasUrl)
|
|
264
|
+
bad('needs exactly one of "command" or "url"');
|
|
265
|
+
if (hasCommand && (typeof entry.command !== "string" || entry.command.length === 0)) {
|
|
266
|
+
bad('"command" must be a non-empty string');
|
|
267
|
+
}
|
|
268
|
+
if (hasUrl && (typeof entry.url !== "string" || entry.url.length === 0)) {
|
|
269
|
+
bad('"url" must be a non-empty string');
|
|
270
|
+
}
|
|
271
|
+
if (entry.args !== undefined &&
|
|
272
|
+
(!Array.isArray(entry.args) || entry.args.some((a) => typeof a !== "string"))) {
|
|
273
|
+
bad('"args" must be an array of strings');
|
|
274
|
+
}
|
|
275
|
+
for (const key of ["env", "headers"]) {
|
|
276
|
+
if (entry[key] !== undefined && !bagOfStrings(entry[key]))
|
|
277
|
+
bad(`"${key}" must map names to strings`);
|
|
278
|
+
}
|
|
279
|
+
if (entry.cwd !== undefined && typeof entry.cwd !== "string")
|
|
280
|
+
bad('"cwd" must be a string');
|
|
281
|
+
}
|
|
282
|
+
function assertResolvedUrlSafe(name, resolved) {
|
|
283
|
+
if (typeof resolved.url !== "string")
|
|
284
|
+
return;
|
|
285
|
+
try {
|
|
286
|
+
assertSafeMcpUrl(resolved.url);
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
if (err instanceof McpUrlPolicyError) {
|
|
290
|
+
throw new StageMcpError(`MCP catalog server "${name}": ${err.message}`, "invalid_config");
|
|
291
|
+
}
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const VALIDATE_PLACEHOLDER = "<redacted>";
|
|
296
|
+
/** The values `${NAME}` may be filled from, by scope. Console secrets win over Host env. */
|
|
297
|
+
function envForScope(scope, stageEnv, hostEnv) {
|
|
298
|
+
const secrets = hostSecretsOrThrow();
|
|
299
|
+
if (scope.kind === "stage") {
|
|
300
|
+
const declared = new Set(scope.secrets);
|
|
301
|
+
const own = Object.fromEntries(Object.entries(secrets).filter(([n]) => declared.has(n)));
|
|
302
|
+
return { ...stageEnv, ...own };
|
|
303
|
+
}
|
|
304
|
+
const allowed = {};
|
|
305
|
+
for (const n of mcpEnvAllowNames(hostEnv)) {
|
|
306
|
+
if (hostEnv[n] !== undefined)
|
|
307
|
+
allowed[n] = hostEnv[n];
|
|
308
|
+
}
|
|
309
|
+
if (scope.kind === "validate") {
|
|
310
|
+
const names = new Set([...Object.keys(secrets), ...Object.keys(allowed), ...Object.keys(stageEnv)]);
|
|
311
|
+
return Object.fromEntries([...names].map((n) => [n, VALIDATE_PLACEHOLDER]));
|
|
312
|
+
}
|
|
313
|
+
// agent and probe: one rule, so the same config resolves the same way in both.
|
|
314
|
+
return { ...allowed, ...secrets };
|
|
315
|
+
}
|
|
210
316
|
export async function resolveStageMcpServers(options) {
|
|
211
317
|
const allowlist = options.allowlist ?? [];
|
|
212
318
|
if (allowlist.length === 0) {
|
|
@@ -224,11 +330,20 @@ export async function resolveStageMcpServers(options) {
|
|
|
224
330
|
throw new StageMcpError(decision.message ?? "untrusted_config_origin", "untrusted_config_origin");
|
|
225
331
|
}
|
|
226
332
|
}
|
|
227
|
-
|
|
333
|
+
// Workspace-sourced catalogs never see console servers or secrets.
|
|
334
|
+
const scope = options.workspaceSourced === true ? undefined : options.scope;
|
|
335
|
+
const catalog = await loadMcpCatalog(options.projectRoot, scope ? { scope } : {});
|
|
228
336
|
assertMcpAllowlistKnown(catalog.servers, allowlist);
|
|
337
|
+
const env = scope
|
|
338
|
+
? envForScope(scope, options.env, options.hostEnv ?? process.env)
|
|
339
|
+
: options.env;
|
|
229
340
|
const resolved = {};
|
|
230
341
|
for (const name of allowlist) {
|
|
231
|
-
|
|
342
|
+
const entry = catalog.servers[name];
|
|
343
|
+
assertEntryShape(name, entry);
|
|
344
|
+
resolved[name] = interpolateServer(entry, env, name, options.projectRoot, options.stageId);
|
|
345
|
+
if (scope?.kind !== "validate")
|
|
346
|
+
assertResolvedUrlSafe(name, resolved[name]);
|
|
232
347
|
}
|
|
233
348
|
return resolved;
|
|
234
349
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { InlinePipelineDefinition, LoadedPipeline } from "../types/pipeline.js";
|
|
2
2
|
import type { TriggerFile } from "../types/trigger.js";
|
|
3
3
|
import type { LoadIssue } from "./loadOutcome.js";
|
|
4
|
+
import type { AnyChannelProvider } from "../channels/types.js";
|
|
4
5
|
export type ValidationSeverity = "error" | "warning";
|
|
5
6
|
export type ValidationCategory = "pipeline" | "stage" | "catalog" | "task" | "trigger";
|
|
6
|
-
export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "pipeline.string_stage_ref" | "pipeline.stage_uses_inline_conflict" | "pipeline.stage_missing_body" | "pipeline.stage_id_mismatch" | "pipeline.stage_wrapper_skill_rejected" | "pipeline.invalid_completion" | "catalog.path_case_mismatch" | "pipeline.invalid_recovery" | "pipeline.include_cycle" | "pipeline.include_invalid" | "pipeline.include_duplicate_stage" | "pipeline.invalid_agent" | "pipeline.invalid_model" | "pipeline.invalid_verify" | "pipeline.io_incompatible" | "pipeline.model_applies" | "pipeline.route_if_invalid" | "pipeline.route_all_gated" | "pipeline.invalid_requires" | "pipeline.requires_conflict" | "stage.invalid_shape" | "stage.invalid_model" | "stage.missing_model" | "stage.invalid_payload_schema" | "stage.invalid_clone_input_schema" | "stage.unresolved_schema_ref" | "stage.invalid_clone_actions" | "stage.invalid_gate_kinds" | "stage.invalid_pre_emit_checks" | "stage.invalid_timeout_ms" | "stage.invalid_skill" | "stage.invalid_mcp" | "stage.invalid_browser" | "stage.invalid_secrets" | "stage.unknown_secret" | "stage.denied_secret" | "stage.invalid_requires" | "stage.invalid_agent" | "stage.invalid_io" | "stage.load_error" | "stage.id_filename_mismatch" | "task.invalid_shape" | "task.load_error" | "task.entry_input_unmet" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid" | "trigger.invalid_shape" | "trigger.load_error" | "trigger.unknown_pipeline" | "trigger.unknown_task" | "catalog.duplicate_pipeline_id" | "catalog.manifest_missing" | "catalog.manifest_invalid" | "catalog.empty_catalog" | "catalog.manifest_load_error" | "catalog.invalid_mcp" | "catalog.mcp_command_missing" | "catalog.mixed_yaml_dialect" | "catalog.legacy_yaml" | "catalog.stageflow_home_absolute_path";
|
|
7
|
+
export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "pipeline.string_stage_ref" | "pipeline.stage_uses_inline_conflict" | "pipeline.stage_missing_body" | "pipeline.stage_id_mismatch" | "pipeline.stage_wrapper_skill_rejected" | "pipeline.invalid_completion" | "catalog.path_case_mismatch" | "pipeline.invalid_recovery" | "pipeline.include_cycle" | "pipeline.include_invalid" | "pipeline.include_duplicate_stage" | "pipeline.invalid_agent" | "pipeline.invalid_model" | "pipeline.invalid_verify" | "pipeline.io_incompatible" | "pipeline.model_applies" | "pipeline.route_if_invalid" | "pipeline.route_all_gated" | "pipeline.invalid_requires" | "pipeline.requires_conflict" | "pipeline.invalid_channels" | "pipeline.invalid_memories" | "pipeline.memory_unavailable" | "stage.invalid_shape" | "stage.invalid_model" | "stage.missing_model" | "stage.invalid_payload_schema" | "stage.invalid_clone_input_schema" | "stage.unresolved_schema_ref" | "stage.invalid_clone_actions" | "stage.invalid_gate_kinds" | "stage.invalid_pre_emit_checks" | "stage.invalid_timeout_ms" | "stage.invalid_skill" | "stage.invalid_mcp" | "stage.invalid_channels" | "stage.channel_unavailable" | "stage.invalid_browser" | "stage.invalid_secrets" | "stage.unknown_secret" | "stage.denied_secret" | "stage.invalid_requires" | "stage.invalid_agent" | "stage.invalid_io" | "stage.load_error" | "stage.id_filename_mismatch" | "task.invalid_shape" | "task.load_error" | "task.entry_input_unmet" | "task.binding_conflict" | "task.repository_ref_required" | "task.ref_without_repository" | "task.repository_invalid" | "trigger.invalid_shape" | "trigger.load_error" | "trigger.unknown_pipeline" | "trigger.unknown_task" | "catalog.duplicate_pipeline_id" | "catalog.manifest_missing" | "catalog.manifest_invalid" | "catalog.empty_catalog" | "catalog.manifest_load_error" | "catalog.invalid_mcp" | "catalog.mcp_command_missing" | "catalog.mixed_yaml_dialect" | "catalog.legacy_yaml" | "catalog.stageflow_home_absolute_path";
|
|
7
8
|
export type ValidationFinding = {
|
|
8
9
|
severity: ValidationSeverity;
|
|
9
10
|
code: ValidationFindingCode | string;
|
|
@@ -50,6 +51,7 @@ export type ValidatePipelineOptions = {
|
|
|
50
51
|
projectRoot?: string;
|
|
51
52
|
validateStages?: boolean;
|
|
52
53
|
strict?: boolean;
|
|
54
|
+
channelProviders?: readonly AnyChannelProvider[];
|
|
53
55
|
};
|
|
54
56
|
export declare function validatePipeline(nameOrPath: string, options?: ValidatePipelineOptions): Promise<ValidationResult>;
|
|
55
57
|
export type LoadPipelineValidatedResult = {
|
|
@@ -64,6 +66,7 @@ export declare function loadPipelineValidated(nameOrPath: string | InlinePipelin
|
|
|
64
66
|
cwd?: string;
|
|
65
67
|
projectRoot?: string;
|
|
66
68
|
validateStages?: boolean;
|
|
69
|
+
channelProviders?: readonly AnyChannelProvider[];
|
|
67
70
|
}): Promise<LoadPipelineValidatedResult>;
|
|
68
71
|
export declare function collectPipelineIdsFromPaths(pipelinePaths: string[]): Promise<Map<string, string[]>>;
|
|
69
72
|
export declare function collectTaskIdsFromPaths(taskPaths: string[]): Promise<Set<string>>;
|
|
@@ -14,6 +14,10 @@ import { MCP_CATALOG_FILENAME, StageMcpError, assertMcpAllowlistKnown, loadMcpCa
|
|
|
14
14
|
import { loadSecretRegistry } from "../runtime/stageSecrets.js";
|
|
15
15
|
import { isForeverDeniedSecret } from "../runtime/stageEnvironment.js";
|
|
16
16
|
import { findMissingMcpCommands } from "../preflight/mcpCommands.js";
|
|
17
|
+
import { ChannelError } from "../channels/errors.js";
|
|
18
|
+
import { ChannelRegistry } from "../channels/registry.js";
|
|
19
|
+
import { validateStageChannels } from "../channels/stageChannels.js";
|
|
20
|
+
import { inspectMemorySync, memoryHome } from "../memory/host.js";
|
|
17
21
|
export function relPath(cwd, absPath) {
|
|
18
22
|
return path.relative(cwd, absPath).replace(/\\/g, "/");
|
|
19
23
|
}
|
|
@@ -256,6 +260,42 @@ function collectMcpAllowlistNames(loaded) {
|
|
|
256
260
|
}
|
|
257
261
|
return [...names];
|
|
258
262
|
}
|
|
263
|
+
function findingsForStageChannels(cwd, pipelinePath, loaded, projectRoot, providers) {
|
|
264
|
+
const users = loaded.stages.filter((stage) => (stage.channels?.length ?? 0) > 0);
|
|
265
|
+
if (users.length === 0)
|
|
266
|
+
return [];
|
|
267
|
+
let registry;
|
|
268
|
+
try {
|
|
269
|
+
registry = new ChannelRegistry({ cwd: projectRoot, providers });
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
const code = error instanceof ChannelError ? error.code : "CHANNEL_INVALID_INPUT";
|
|
273
|
+
const field = error instanceof ChannelError && error.detail?.connectionId
|
|
274
|
+
? ` (connection "${error.detail.connectionId}"${error.detail.field ? `, field "${error.detail.field}"` : ""})`
|
|
275
|
+
: "";
|
|
276
|
+
return users.map((stage) => findingStageError(cwd, pipelinePath, `connections.yaml could not be loaded: ${code}${field}`, "stage.channel_unavailable", stage.id));
|
|
277
|
+
}
|
|
278
|
+
return users.flatMap((stage) => validateStageChannels(registry, stage).map((issue) => findingStageError(cwd, pipelinePath, issue.message, "stage.channel_unavailable", issue.stageId)));
|
|
279
|
+
}
|
|
280
|
+
const MEMORY_FINDING = {
|
|
281
|
+
MEMORY_NOT_FOUND: (id) => `Memory "${id}" does not exist. Create it before attaching it.`,
|
|
282
|
+
MEMORY_MISMATCH: (id) => `Memory "${id}" does not match the id in its file.`,
|
|
283
|
+
MEMORY_UNREADABLE: (id) => `Memory "${id}" could not be read.`,
|
|
284
|
+
MEMORY_INVALID_ID: (id) => `Memory "${id}" is not a valid id.`,
|
|
285
|
+
};
|
|
286
|
+
function findingsForMemories(cwd, pipelinePath, loaded) {
|
|
287
|
+
const ids = loaded.pipeline.memories;
|
|
288
|
+
if (!ids?.length)
|
|
289
|
+
return [];
|
|
290
|
+
const home = memoryHome();
|
|
291
|
+
return ids.flatMap((id) => {
|
|
292
|
+
const inspected = inspectMemorySync(home, id);
|
|
293
|
+
if (inspected.ok)
|
|
294
|
+
return [];
|
|
295
|
+
const message = (MEMORY_FINDING[inspected.code] ?? MEMORY_FINDING.MEMORY_UNREADABLE)(id);
|
|
296
|
+
return [findingPipelineError(cwd, pipelinePath, message, "pipeline.memory_unavailable", loaded.pipeline.id)];
|
|
297
|
+
});
|
|
298
|
+
}
|
|
259
299
|
function findingsForStageSecrets(cwd, pipelinePath, loaded) {
|
|
260
300
|
const registry = loadSecretRegistry(process.env);
|
|
261
301
|
const findings = [];
|
|
@@ -366,7 +406,7 @@ async function findingsForStageMcpCatalog(cwd, loaded) {
|
|
|
366
406
|
}
|
|
367
407
|
let catalog;
|
|
368
408
|
try {
|
|
369
|
-
catalog = await loadMcpCatalog(projectRoot);
|
|
409
|
+
catalog = await loadMcpCatalog(projectRoot, { scope: { kind: "validate" } });
|
|
370
410
|
}
|
|
371
411
|
catch (err) {
|
|
372
412
|
if (!(err instanceof StageMcpError))
|
|
@@ -438,6 +478,8 @@ async function runPipelineValidation(nameOrPath, options) {
|
|
|
438
478
|
}
|
|
439
479
|
findings.push(...(await findingsForStageMcpCatalog(cwd, outcome.value)));
|
|
440
480
|
findings.push(...findingsForStageSecrets(cwd, pipelinePath, outcome.value));
|
|
481
|
+
findings.push(...findingsForStageChannels(cwd, pipelinePath, outcome.value, projectRoot, options.channelProviders));
|
|
482
|
+
findings.push(...findingsForMemories(cwd, pipelinePath, outcome.value));
|
|
441
483
|
findings.push(...findingsForStageflowHomeAbsolutePaths(cwd, outcome.value));
|
|
442
484
|
if (validateStages && outcome.value.stageSources) {
|
|
443
485
|
for (const source of Object.values(outcome.value.stageSources)) {
|
|
@@ -461,6 +503,7 @@ export async function validatePipeline(nameOrPath, options = {}) {
|
|
|
461
503
|
cwd,
|
|
462
504
|
projectRoot,
|
|
463
505
|
validateStages,
|
|
506
|
+
channelProviders: options.channelProviders,
|
|
464
507
|
});
|
|
465
508
|
return buildValidationResult("pipeline", core.findings, strict);
|
|
466
509
|
}
|
|
@@ -472,6 +515,7 @@ export async function loadPipelineValidated(nameOrPath, options = {}) {
|
|
|
472
515
|
cwd,
|
|
473
516
|
projectRoot,
|
|
474
517
|
validateStages,
|
|
518
|
+
channelProviders: options.channelProviders,
|
|
475
519
|
});
|
|
476
520
|
if (core.ok) {
|
|
477
521
|
return { ok: true, loaded: core.loaded, findings: core.findings };
|
package/dist/errors/codes.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare const BUSY_CHECKOUT: "busy_checkout";
|
|
|
4
4
|
/** Per-caller concurrency quota: queued when under global capacity; reject when queue full. */
|
|
5
5
|
export declare const BUSY_CALLER_QUOTA: "busy_caller_quota";
|
|
6
6
|
export declare const ABORTED: "aborted";
|
|
7
|
-
export declare const NETWORK_ERROR_CODES: readonly ["busy_capacity", "busy_checkout", "busy_caller_quota", "aborted", "unknown_project_root", "absolute_path_not_allowed", "path_outside_project_root", "catalog_root_unreadable", "ensure_project_not_allowed", "config_invalid", "config_unknown_key", "provider_not_configured", "a2a_configuration_error", "untrusted_config_origin", "command_not_on_path", "missing_tool", "tool_version_mismatch", "unknown_version", "secret_unavailable", "not_ready", "internal_error", "retry_in_progress", "hitl_not_retriable", "run_not_retryable", "stage_not_failed", "manual_recovery_required", "run_not_found", "stage_not_found", "artifact_not_found", "artifact_path_denied", "envelope_not_found", "insufficient_disk", "disk_check_failed", "shutting_down", "start.token_rejected", "inline_pipeline_too_large", "skills_invalid_name", "skills_invalid_path", "skills_missing_skill_md", "skills_payload_too_large", "pinned_sha_unavailable", "store_integrity_failed", "store_unsupported_filesystem", "tmpdir_unusable", "backup_insufficient_disk"];
|
|
7
|
+
export declare const NETWORK_ERROR_CODES: readonly ["busy_capacity", "busy_checkout", "busy_caller_quota", "aborted", "unknown_project_root", "absolute_path_not_allowed", "path_outside_project_root", "catalog_root_unreadable", "ensure_project_not_allowed", "config_invalid", "config_unknown_key", "provider_not_configured", "a2a_configuration_error", "untrusted_config_origin", "command_not_on_path", "missing_tool", "tool_version_mismatch", "unknown_version", "secret_unavailable", "channel_check_failed", "not_ready", "internal_error", "retry_in_progress", "hitl_not_retriable", "run_not_retryable", "stage_not_failed", "manual_recovery_required", "run_not_found", "stage_not_found", "artifact_not_found", "artifact_path_denied", "envelope_not_found", "insufficient_disk", "disk_check_failed", "shutting_down", "start.token_rejected", "inline_pipeline_too_large", "skills_invalid_name", "skills_invalid_path", "skills_missing_skill_md", "skills_payload_too_large", "pinned_sha_unavailable", "store_integrity_failed", "store_unsupported_filesystem", "tmpdir_unusable", "backup_insufficient_disk"];
|
|
8
8
|
export type NetworkErrorCode = (typeof NETWORK_ERROR_CODES)[number] | string;
|
|
9
9
|
export type NetworkErrorBody = {
|
|
10
10
|
code: NetworkErrorCode;
|
package/dist/errors/codes.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { McpToolDeps } from "./deps.js";
|
|
3
|
+
/**
|
|
4
|
+
* Agent management for operators driving Stageflow over MCP. A running agent reaches these
|
|
5
|
+
* only through the Stageflow tool groups it is granted (`agents.*`); the guard in
|
|
6
|
+
* `toolkit/guard.ts` stops it acting on its own sessions.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerAgentTools(server: McpServer, deps: McpToolDeps): void;
|