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
package/README.md
CHANGED
|
@@ -34,8 +34,11 @@ The same pipeline runs three ways without rewriting anything:
|
|
|
34
34
|
- **Envelope handoffs** — typed stage payloads and artifacts via `write_stage_artifact` / `emit_stage_envelope`
|
|
35
35
|
- **Verified stage execution** — after-phase `verify` checks with durable evidence, plus `on_verify_fail` repair or manual operator recovery ([guide](docs/verified-stage-execution.md))
|
|
36
36
|
- **HITL gates** — operator questions in the console; CI exits `2` when a run is waiting
|
|
37
|
-
- **
|
|
37
|
+
- **Agents** — project-owned, revisioned agents (`agents/*.agent.yaml`) with granted tools, ask-first approvals, traces, channels, schedules and a public web chat page ([guide](docs/agents.md))
|
|
38
|
+
- **Operator console** — an Inbox for gates and agent approvals, pipeline runs, agents and their sessions, connections, MCP servers and settings at `http://127.0.0.1:3847` ([guide](docs/operator-console.md))
|
|
38
39
|
- **MCP endpoint** — Streamable HTTP at `/mcp` when `sf ui` or `sf mcp` is running
|
|
40
|
+
- **MCP servers for agents and stages** — add servers, secrets and OAuth sign-in from Settings → Project MCP, or in the project `.mcp.json` ([guide](docs/mcp-servers.md))
|
|
41
|
+
- **Connections** — Slack, Telegram, iMessage, WhatsApp and AgentMail channels, and email accounts, managed from one page ([guide](docs/channels.md))
|
|
39
42
|
- **Standalone stage execution** — run a single stage directly with `run_stage` (MCP tool, `sf run-stage` CLI, or the A2A `run_stage` operation), no pipeline file required; chain calls with `envelope_ref` (one or many prior results) instead of a pipeline DAG
|
|
40
43
|
- **CI / headless** — `sf validate --strict --json`, `sf run --json` with exit codes `0` / `1` / `2`
|
|
41
44
|
- **Parallel stages** — pipeline DAG with fan-out, join, and Clone Chains (one Clone Instance per Clone Array element; see [YAML catalog](docs/yaml-catalog.md#clone-chain))
|
|
@@ -97,7 +100,7 @@ sf ui # operator console at http://127.0.0.1:3847
|
|
|
97
100
|
sf run --pipeline pipelines/hello.pipeline.yaml --task tasks/hello.task.yaml
|
|
98
101
|
```
|
|
99
102
|
|
|
100
|
-
**Workshop** (
|
|
103
|
+
**Workshop** (**New pipeline** on the Pipelines page, or `#/workshop`; it has no rail item) drafts a pipeline in chat and saves it under `workshop/<pipeline-id>/` so it shows up on Run. Guide: [docs/workshop.md](docs/workshop.md).
|
|
101
104
|
|
|
102
105
|
Expanded walkthrough: [docs/quickstart.md](docs/quickstart.md)
|
|
103
106
|
|
|
@@ -140,11 +143,14 @@ Full reference: [docs/providers.md](docs/providers.md)
|
|
|
140
143
|
|
|
141
144
|
Start the console with `sf ui` (default `http://127.0.0.1:3847`).
|
|
142
145
|
|
|
143
|
-
- **
|
|
144
|
-
- **
|
|
145
|
-
- **
|
|
146
|
-
- **Pipelines** — browse
|
|
147
|
-
- **
|
|
146
|
+
- **Inbox** — pipeline gates and agent approvals that wait on you, then failed and finished runs; Approve or Deny without leaving the page, with optional desktop notifications
|
|
147
|
+
- **Pipeline runs** — active and recent runs, capacity, and status at a glance
|
|
148
|
+
- **Run detail** — Timeline, Graph or List of the stages; select a stage for its transcript, events, envelope and artifacts. Created runs show **not started** with a **Start run** action until history exists
|
|
149
|
+
- **Pipelines, Tasks, Catalog, Triggers** — browse and edit the YAML, start runs; **New pipeline** drafts one in the Workshop
|
|
150
|
+
- **Agents** and **Agent sessions** — a directory with each agent's reach (web chat link, channel, schedule), cost and status; create and edit with version history; sessions, approvals and a run inspector
|
|
151
|
+
- **Connections** — channels, email accounts and tool servers
|
|
152
|
+
- **Settings** — Providers (`pi_home` or `sf_owned` credential storage), Concurrency, MCP server, Project MCP (servers, secrets, sign-in) and Notifications
|
|
153
|
+
- **Command palette** — `⌘K` from any page. Page-by-page guide: [docs/operator-console.md](docs/operator-console.md)
|
|
148
154
|
|
|
149
155
|
Brand assets live under `docs/img/` (`stageflow-og.svg`, `stageflow-icon.svg`).
|
|
150
156
|
|
|
@@ -266,7 +272,11 @@ Full docs: **[tejasghutukade.github.io/stageflow](https://tejasghutukade.github.
|
|
|
266
272
|
| [docs/a2a.md](docs/a2a.md) | Publish pipelines for other agents to call over JSON-RPC (A2A), plus the wildcard-access `run_stage` operation |
|
|
267
273
|
| [docs/providers.md](docs/providers.md) | Pi providers, `sf providers` |
|
|
268
274
|
| [docs/docker.md](docs/docker.md) | Docker Compose local try, image pull/run, backup, self-hosting |
|
|
269
|
-
| [docs/operator-console.md](docs/operator-console.md) | Console
|
|
275
|
+
| [docs/operator-console.md](docs/operator-console.md) | Console navigation, command palette, shortcuts and routes; one page per area ([Inbox](docs/console-inbox.md), [Runs](docs/console-runs.md), [Agents](docs/console-agents.md), [Connections](docs/console-connections.md), [Settings](docs/console-settings.md)) |
|
|
276
|
+
| [docs/agents.md](docs/agents.md) | Agents: definition, runtime, approvals, channels, schedules, web chat |
|
|
277
|
+
| [docs/mcp-servers.md](docs/mcp-servers.md) | Add MCP servers, secrets and OAuth; use them from agents and stages |
|
|
278
|
+
| [docs/channels.md](docs/channels.md) | Connections and communication channels |
|
|
279
|
+
| [docs/environment-variables.md](docs/environment-variables.md) | Every `STAGEFLOW_*` environment variable |
|
|
270
280
|
| [docs/workshop.md](docs/workshop.md) | Workshop Author — chat-draft a pipeline, save it, and run it |
|
|
271
281
|
| [docs/skills-suite.md](docs/skills-suite.md) | Harness skills — router + jobs for Cursor, Claude Code, Codex, Pi, OpenCode |
|
|
272
282
|
|
|
@@ -78,6 +78,18 @@ function preflight(input) {
|
|
|
78
78
|
reason: `stage declares skill "${input.stage.skill}"; the claude agent backend does not support skills yet`,
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
+
if (input.stage.channels?.length) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
reason: "stage declares channels; the claude agent backend does not provide send_message yet, so the stage would run without it",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (input.stage.memories?.length) {
|
|
88
|
+
return {
|
|
89
|
+
ok: false,
|
|
90
|
+
reason: "stage pipeline declares memories; the claude agent backend does not provide read_memory, replace_memory, add_memory_card, update_memory_card, read_memory_card, and search_memory_cards yet, so the stage would run without them",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
81
93
|
return resolveClaudeModel(input.stage.model);
|
|
82
94
|
}
|
|
83
95
|
function resultFromCapture(capture, providerError) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentSession, type ExtensionFactory, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { type AgentSession, type ExtensionFactory, DefaultResourceLoader, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { StageRoots } from "../runtime/stageRoots.js";
|
|
3
3
|
import { type AskOperatorPrompt, type AskOperatorWaitBridge } from "../tools/askOperator.js";
|
|
4
4
|
import "./cursorProvider.js";
|
|
@@ -76,6 +76,14 @@ export type RouteSessionEventToProgressOptions = {
|
|
|
76
76
|
export declare function routeSessionEventToProgress(event: Record<string, unknown>, options: RouteSessionEventToProgressOptions): void;
|
|
77
77
|
export declare function composeStageUserPrompt(input: StageRunInput, emitToolName: string, emitHintOverride?: string, artifactToolName?: string): string;
|
|
78
78
|
export declare function composeFeedbackResumePrompt(input: StageRunInput): string;
|
|
79
|
+
export declare function mcpToolMetadataMissing(loader: DefaultResourceLoader): boolean;
|
|
80
|
+
export declare function collectMcpExtensionToolNames(loader: DefaultResourceLoader): {
|
|
81
|
+
ok: true;
|
|
82
|
+
names: string[];
|
|
83
|
+
} | {
|
|
84
|
+
ok: false;
|
|
85
|
+
unrequested: string[];
|
|
86
|
+
};
|
|
79
87
|
/** Stable Pi session JSONL path under the run workspace stage dir. */
|
|
80
88
|
export declare function stageSessionFilePath(roots: Pick<StageRoots, "runWorkspaceDir" | "attempt">, stageId: string): string;
|
|
81
89
|
export declare function resolveStageSessionFile(input: StageRunInput): string;
|
package/dist/agent/piAdapter.js
CHANGED
|
@@ -49,6 +49,9 @@ import { remindUntilEmitted } from "./stageTurnDriver.js";
|
|
|
49
49
|
import { createAskOperatorTool, } from "../tools/askOperator.js";
|
|
50
50
|
import { createWriteStageArtifactTool } from "../tools/writeStageArtifact.js";
|
|
51
51
|
import { createSendEmailTool } from "../tools/sendEmail.js";
|
|
52
|
+
import { createSendMessageTool } from "../tools/sendMessage.js";
|
|
53
|
+
import { createMemoryStoreBashDenyExtension } from "../memory/bashDeny.js";
|
|
54
|
+
import { memorySession } from "../tools/memoryTools.js";
|
|
52
55
|
import { createReplyEmailTool } from "../tools/replyEmail.js";
|
|
53
56
|
import { createGetEmailTool, createSearchEmailTool, createDownloadEmailAttachmentTool } from "../tools/readEmail.js";
|
|
54
57
|
import "./cursorProvider.js";
|
|
@@ -493,6 +496,14 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
|
|
|
493
496
|
].join("\n")
|
|
494
497
|
: `Create factory stage artifacts under ${attemptArtifactsPath} relative to the run folder.`;
|
|
495
498
|
return [
|
|
499
|
+
...(input.stage.channels?.length ? [
|
|
500
|
+
`Stageflow channels: ${JSON.stringify(input.stage.channels.map(channel => ({ channel: channel.alias ?? channel.connection, operations: channel.operations })))}. Use send_message with the channel name. Recipients are fixed by configuration and you cannot change them. Use a stable operationKey for each intended message. Provider acceptance does not prove delivery. Never resend an unknown submission automatically.`,
|
|
501
|
+
] : []),
|
|
502
|
+
...(input.memoryPrompt !== undefined
|
|
503
|
+
? [input.memoryPrompt]
|
|
504
|
+
: input.memory
|
|
505
|
+
? [memorySession(input.memory).prompt]
|
|
506
|
+
: []),
|
|
496
507
|
...(input.stage.email?.length ? [
|
|
497
508
|
`Stageflow email permissions: ${JSON.stringify(input.stage.email)}. Use only declared accounts and operations. Use a stable operationKey for each intended message. Provider acceptance does not prove delivery. Never resend an unknown submission automatically.`,
|
|
498
509
|
] : []),
|
|
@@ -562,10 +573,10 @@ function mcpExtensionTools(loader) {
|
|
|
562
573
|
.extensions.filter((ext) => ext.path === inlinePath)
|
|
563
574
|
.flatMap((ext) => [...ext.tools.keys()]);
|
|
564
575
|
}
|
|
565
|
-
function mcpToolMetadataMissing(loader) {
|
|
576
|
+
export function mcpToolMetadataMissing(loader) {
|
|
566
577
|
return mcpExtensionTools(loader).includes("mcp");
|
|
567
578
|
}
|
|
568
|
-
function collectMcpExtensionToolNames(loader) {
|
|
579
|
+
export function collectMcpExtensionToolNames(loader) {
|
|
569
580
|
const names = [];
|
|
570
581
|
const unrequested = [];
|
|
571
582
|
for (const name of mcpExtensionTools(loader)) {
|
|
@@ -924,6 +935,12 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
|
|
|
924
935
|
: {}),
|
|
925
936
|
}),
|
|
926
937
|
},
|
|
938
|
+
...(input.stage.memories?.length
|
|
939
|
+
? [{
|
|
940
|
+
name: "stageflow-memory-bash-deny",
|
|
941
|
+
factory: createMemoryStoreBashDenyExtension({ home: globalStageflowHome() }),
|
|
942
|
+
}]
|
|
943
|
+
: []),
|
|
927
944
|
{
|
|
928
945
|
name: STAGEFLOW_TOOL_OUTPUT_EXTENSION_NAME,
|
|
929
946
|
factory: createToolOutputBudgetExtension({
|
|
@@ -998,6 +1015,17 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
|
|
|
998
1015
|
}
|
|
999
1016
|
tools.push(...mcpTools.names);
|
|
1000
1017
|
}
|
|
1018
|
+
if (input.channels && input.stage.channels?.some(channel => channel.operations.includes("send"))) {
|
|
1019
|
+
const definition = createSendMessageTool(input.channels);
|
|
1020
|
+
customTools.push(defineTool(definition));
|
|
1021
|
+
tools.push(definition.name);
|
|
1022
|
+
}
|
|
1023
|
+
if (input.memory) {
|
|
1024
|
+
for (const definition of memorySession(input.memory).tools) {
|
|
1025
|
+
customTools.push(defineTool(definition));
|
|
1026
|
+
tools.push(definition.name);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1001
1029
|
if (input.email) {
|
|
1002
1030
|
const permitted = new Set(input.stage.email?.flatMap(permission => permission.operations));
|
|
1003
1031
|
const definitions = [
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { createEventBus, } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { prepareMcpOAuth } from "../config/mcpOAuth.js";
|
|
3
4
|
import { StageMcpError, } from "../config/resolveStageMcpServers.js";
|
|
4
5
|
import { globalStageflowHome } from "../project/globalHome.js";
|
|
5
6
|
import { TOOL_OUTPUT_HARD_CEILING_BYTES } from "./toolOutputBudget.js";
|
|
@@ -48,7 +49,7 @@ const EMPTY_ATTACH = {
|
|
|
48
49
|
connecting: undefined,
|
|
49
50
|
};
|
|
50
51
|
let cachedCreateMcpAdapter;
|
|
51
|
-
function toIsolatedMcpConfig(snapshot, lifecycle = "lazy") {
|
|
52
|
+
function toIsolatedMcpConfig(snapshot, lifecycle = "lazy", oauth = false) {
|
|
52
53
|
const mcpServers = {};
|
|
53
54
|
for (const [name, entry] of Object.entries(snapshot)) {
|
|
54
55
|
const isolated = {
|
|
@@ -65,6 +66,7 @@ function toIsolatedMcpConfig(snapshot, lifecycle = "lazy") {
|
|
|
65
66
|
mcpServers,
|
|
66
67
|
settings: {
|
|
67
68
|
...ISOLATED_MCP_SETTINGS,
|
|
69
|
+
...(oauth ? { oauthCredentialStore: "encrypted-file" } : {}),
|
|
68
70
|
outputGuard: { ...ISOLATED_MCP_SETTINGS.outputGuard },
|
|
69
71
|
},
|
|
70
72
|
};
|
|
@@ -235,11 +237,21 @@ export async function attachIsolatedMcp(snapshot, options) {
|
|
|
235
237
|
return EMPTY_ATTACH;
|
|
236
238
|
}
|
|
237
239
|
const createMcpAdapter = await loadCreateMcpAdapter();
|
|
240
|
+
let oauth = false;
|
|
241
|
+
if (Object.values(snapshot).some((entry) => typeof entry.url === "string")) {
|
|
242
|
+
try {
|
|
243
|
+
prepareMcpOAuth();
|
|
244
|
+
oauth = true;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Sign-in is optional; a store that cannot be prepared must not stop servers that need none.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
238
250
|
const extensionFactories = [
|
|
239
251
|
{
|
|
240
252
|
name: STAGEFLOW_PI_MCP_EXTENSION_NAME,
|
|
241
253
|
factory: createMcpAdapter({
|
|
242
|
-
config: toIsolatedMcpConfig(snapshot, options?.lifecycle ?? "lazy"),
|
|
254
|
+
config: toIsolatedMcpConfig(snapshot, options?.lifecycle ?? "lazy", oauth),
|
|
243
255
|
}),
|
|
244
256
|
},
|
|
245
257
|
];
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { type ResolvedMcpServers } from "../config/resolveStageMcpServers.js";
|
|
2
|
+
import { type McpToolInfo } from "../config/mcpToolCache.js";
|
|
2
3
|
export declare const PROJECT_MCP_PROBE_TIMEOUT_MS = 30000;
|
|
3
4
|
export type ProjectMcpProbeStatus = "connected" | "needs_auth" | "connect_failed" | "unresolved_var" | "invalid_config" | "missing_catalog" | "cancelled";
|
|
4
5
|
export type ProjectMcpProbeResult = {
|
|
5
6
|
name: string;
|
|
6
7
|
status: ProjectMcpProbeStatus;
|
|
7
8
|
error?: string;
|
|
9
|
+
/** Tools the server lists, set when the probe connected. */
|
|
10
|
+
tools?: McpToolInfo[];
|
|
8
11
|
};
|
|
9
12
|
export type ProbeProjectMcpServerOptions = {
|
|
10
13
|
projectRoot: string;
|
|
11
14
|
name: string;
|
|
12
|
-
env
|
|
15
|
+
/** Host env that STAGEFLOW_MCP_ENV_ALLOW may draw from. Defaults to process.env; nothing else is read. */
|
|
16
|
+
hostEnv?: NodeJS.ProcessEnv;
|
|
13
17
|
signal?: AbortSignal;
|
|
14
18
|
timeoutMs?: number;
|
|
15
19
|
};
|
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
import { StageMcpError, resolveStageMcpServers, } from "../config/resolveStageMcpServers.js";
|
|
2
|
+
import { collectSecretValues, scrubText } from "../config/mcpRedaction.js";
|
|
3
|
+
import { listCachedMcpTools } from "../config/mcpToolCache.js";
|
|
2
4
|
import * as piIsolatedMcp from "./piIsolatedMcp.js";
|
|
3
5
|
export const PROJECT_MCP_PROBE_TIMEOUT_MS = 30_000;
|
|
4
6
|
const MCP_STATUS_EVENT = "pi-mcp-adapter/status/v1";
|
|
5
7
|
function scrub(message, secrets) {
|
|
6
|
-
|
|
7
|
-
for (const secret of secrets) {
|
|
8
|
-
if (secret.length === 0)
|
|
9
|
-
continue;
|
|
10
|
-
out = out.split(secret).join("[redacted]");
|
|
11
|
-
}
|
|
12
|
-
return out;
|
|
13
|
-
}
|
|
14
|
-
function collectInterpolatedSecrets(entry) {
|
|
15
|
-
const secrets = [];
|
|
16
|
-
for (const field of ["env", "headers"]) {
|
|
17
|
-
const value = entry[field];
|
|
18
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
for (const item of Object.values(value)) {
|
|
22
|
-
if (typeof item === "string" && item.length > 0) {
|
|
23
|
-
secrets.push(item);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return secrets;
|
|
8
|
+
return scrubText(message, secrets);
|
|
28
9
|
}
|
|
29
10
|
function catalogErrorResult(name, err) {
|
|
30
11
|
if (!(err instanceof StageMcpError))
|
|
@@ -35,6 +16,9 @@ function catalogErrorResult(name, err) {
|
|
|
35
16
|
if (err.code === "missing_catalog") {
|
|
36
17
|
return { name, status: "missing_catalog" };
|
|
37
18
|
}
|
|
19
|
+
if (err.code === "host_state_corrupt") {
|
|
20
|
+
return { name, status: "invalid_config", error: err.message };
|
|
21
|
+
}
|
|
38
22
|
if (err.code === "invalid_config" ||
|
|
39
23
|
err.code === "reserved_name" ||
|
|
40
24
|
err.code === "unknown_server") {
|
|
@@ -136,7 +120,7 @@ function instantiateProbeFactories(attached, projectRoot) {
|
|
|
136
120
|
probeApi.emitSessionStart(projectRoot);
|
|
137
121
|
return () => probeApi.emitSessionShutdown();
|
|
138
122
|
}
|
|
139
|
-
function mapConnectingError(err, aborted) {
|
|
123
|
+
function mapConnectingError(err, aborted, secrets = []) {
|
|
140
124
|
const message = err instanceof Error ? err.message : String(err);
|
|
141
125
|
if (aborted || /cancelled/i.test(message)) {
|
|
142
126
|
return { kind: "cancelled" };
|
|
@@ -147,7 +131,7 @@ function mapConnectingError(err, aborted) {
|
|
|
147
131
|
return {
|
|
148
132
|
kind: "mapped",
|
|
149
133
|
status: "connect_failed",
|
|
150
|
-
error: message.length > 0 ? message : undefined,
|
|
134
|
+
error: message.length > 0 ? scrubText(message, secrets) : undefined,
|
|
151
135
|
};
|
|
152
136
|
}
|
|
153
137
|
async function waitForProbeStatus(eventBus, serverName, connecting, options) {
|
|
@@ -193,7 +177,7 @@ async function waitForProbeStatus(eventBus, serverName, connecting, options) {
|
|
|
193
177
|
const maybeUnsub = eventBus.on(MCP_STATUS_EVENT, handler);
|
|
194
178
|
unsubscribe = typeof maybeUnsub === "function" ? maybeUnsub : undefined;
|
|
195
179
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
196
|
-
void (connecting ?? Promise.resolve()).then(() => finish({ kind: "mapped", status: "connected" }), (err) => finish(mapConnectingError(err, options.signal?.aborted === true)));
|
|
180
|
+
void (connecting ?? Promise.resolve()).then(() => finish({ kind: "mapped", status: "connected" }), (err) => finish(mapConnectingError(err, options.signal?.aborted === true, options.secrets)));
|
|
197
181
|
timer = setTimeout(() => {
|
|
198
182
|
finish({
|
|
199
183
|
kind: "mapped",
|
|
@@ -206,7 +190,6 @@ async function waitForProbeStatus(eventBus, serverName, connecting, options) {
|
|
|
206
190
|
export async function probeProjectMcpServer(options) {
|
|
207
191
|
const name = options.name;
|
|
208
192
|
const timeoutMs = options.timeoutMs ?? PROJECT_MCP_PROBE_TIMEOUT_MS;
|
|
209
|
-
const env = options.env ?? process.env;
|
|
210
193
|
if (options.signal?.aborted) {
|
|
211
194
|
return { name, status: "cancelled" };
|
|
212
195
|
}
|
|
@@ -215,7 +198,9 @@ export async function probeProjectMcpServer(options) {
|
|
|
215
198
|
snapshot = await resolveStageMcpServers({
|
|
216
199
|
projectRoot: options.projectRoot,
|
|
217
200
|
allowlist: [name],
|
|
218
|
-
env,
|
|
201
|
+
env: {},
|
|
202
|
+
hostEnv: options.hostEnv ?? process.env,
|
|
203
|
+
scope: { kind: "probe" },
|
|
219
204
|
});
|
|
220
205
|
}
|
|
221
206
|
catch (err) {
|
|
@@ -228,7 +213,7 @@ export async function probeProjectMcpServer(options) {
|
|
|
228
213
|
if (resolved === undefined) {
|
|
229
214
|
return { name, status: "invalid_config" };
|
|
230
215
|
}
|
|
231
|
-
const secrets =
|
|
216
|
+
const secrets = collectSecretValues(resolved);
|
|
232
217
|
let attached;
|
|
233
218
|
let shutdownFactories;
|
|
234
219
|
const dispose = async () => {
|
|
@@ -254,7 +239,7 @@ export async function probeProjectMcpServer(options) {
|
|
|
254
239
|
return { name, status: "invalid_config" };
|
|
255
240
|
}
|
|
256
241
|
shutdownFactories = instantiateProbeFactories(attached, options.projectRoot);
|
|
257
|
-
const waited = await waitForProbeStatus(attached.eventBus, name, attached.connecting, { timeoutMs, signal: options.signal });
|
|
242
|
+
const waited = await waitForProbeStatus(attached.eventBus, name, attached.connecting, { timeoutMs, signal: options.signal, secrets });
|
|
258
243
|
if (waited.kind === "cancelled") {
|
|
259
244
|
return { name, status: "cancelled" };
|
|
260
245
|
}
|
|
@@ -267,7 +252,8 @@ export async function probeProjectMcpServer(options) {
|
|
|
267
252
|
: {}),
|
|
268
253
|
};
|
|
269
254
|
}
|
|
270
|
-
|
|
255
|
+
const tools = waited.status === "connected" ? listCachedMcpTools(name)?.tools : undefined;
|
|
256
|
+
return { name, status: waited.status, ...(tools ? { tools } : {}) };
|
|
271
257
|
}
|
|
272
258
|
catch (err) {
|
|
273
259
|
if (options.signal?.aborted) {
|
package/dist/agent/port.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type { StageActivityEvent } from "./activity.js";
|
|
|
8
8
|
import type { ResolvedMcpServers } from "../config/resolveStageMcpServers.js";
|
|
9
9
|
import type { StageRoots } from "../runtime/stageRoots.js";
|
|
10
10
|
import type { StageEmail } from "../email/host.js";
|
|
11
|
+
import type { StageChannels } from "../channels/host.js";
|
|
12
|
+
import type { StageMemory } from "../memory/access.js";
|
|
11
13
|
import type { QaExchange } from "../hitl/qaTrail.js";
|
|
12
14
|
import type { StageUsage } from "../types/usage.js";
|
|
13
15
|
/** Opaque to runtime; adapters interpret. */
|
|
@@ -65,6 +67,11 @@ export type StageRunInput = {
|
|
|
65
67
|
onAssistantTextDelta?: (delta: string) => void;
|
|
66
68
|
skillFilePath?: string;
|
|
67
69
|
email?: StageEmail;
|
|
70
|
+
channels?: StageChannels;
|
|
71
|
+
/** Memories attached to this stage. The host reads and replaces the page. */
|
|
72
|
+
memory?: StageMemory;
|
|
73
|
+
/** Refreshed memory prompt; preferred over deriving from `memory` at prompt build time. */
|
|
74
|
+
memoryPrompt?: string;
|
|
68
75
|
/** Bundled browser skill; set by the runtime when the stage has `browser`. */
|
|
69
76
|
browserSkillFilePath?: string;
|
|
70
77
|
forkEmitContext?: ForkEmitContext;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** What an agent id looks like. Lives here, not in the loader, so the loader can import the reserved list without a cycle. */
|
|
2
|
+
export declare const AGENT_ID_PATTERN: RegExp;
|
|
3
|
+
/** Ids that collide with fixed `/api/agents/<word>` routes. The single list: the loader and the generator both read it. */
|
|
4
|
+
export declare const RESERVED_AGENT_IDS: ReadonlySet<string>;
|
|
5
|
+
export declare function isReservedAgentId(id: string): boolean;
|
|
6
|
+
export declare function slugAgentId(name: string): string;
|
|
7
|
+
/** An id from the agent's name, made unique with a numeric suffix. Names with no usable letters become `agent`. */
|
|
8
|
+
export declare function generateAgentId(name: string, isTaken: (id: string) => Promise<boolean>): Promise<string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** What an agent id looks like. Lives here, not in the loader, so the loader can import the reserved list without a cycle. */
|
|
2
|
+
export const AGENT_ID_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
|
|
3
|
+
/** Ids that collide with fixed `/api/agents/<word>` routes. The single list: the loader and the generator both read it. */
|
|
4
|
+
export const RESERVED_AGENT_IDS = new Set(["sessions", "approvals", "new"]);
|
|
5
|
+
export function isReservedAgentId(id) {
|
|
6
|
+
return RESERVED_AGENT_IDS.has(id);
|
|
7
|
+
}
|
|
8
|
+
export function slugAgentId(name) {
|
|
9
|
+
const slug = name
|
|
10
|
+
.trim()
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
13
|
+
.replace(/^_+|_+$/g, "")
|
|
14
|
+
.replace(/^[^a-z]+/, "")
|
|
15
|
+
.slice(0, 56);
|
|
16
|
+
return AGENT_ID_PATTERN.test(slug) ? slug : "";
|
|
17
|
+
}
|
|
18
|
+
/** An id from the agent's name, made unique with a numeric suffix. Names with no usable letters become `agent`. */
|
|
19
|
+
export async function generateAgentId(name, isTaken) {
|
|
20
|
+
const base = slugAgentId(name) || "agent";
|
|
21
|
+
for (let attempt = 1; attempt < 1000; attempt += 1) {
|
|
22
|
+
const candidate = attempt === 1 ? base : `${base}_${attempt}`;
|
|
23
|
+
if (!RESERVED_AGENT_IDS.has(candidate) && !(await isTaken(candidate)))
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
throw new Error("Could not generate a unique agent id");
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ApprovalOutcome = "approved" | "denied" | "expired";
|
|
2
|
+
/**
|
|
3
|
+
* In-process rendezvous between a turn paused at an ask-first tool and the
|
|
4
|
+
* decision that arrives later (console, CLI, MCP or a channel). A waiter only
|
|
5
|
+
* exists while the Host process that started the turn is alive.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ApprovalBroker {
|
|
8
|
+
private readonly waiters;
|
|
9
|
+
wait(approvalId: string, options: {
|
|
10
|
+
expiresAt: string;
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
}): Promise<ApprovalOutcome | "aborted">;
|
|
14
|
+
settle(approvalId: string, outcome: ApprovalOutcome): void;
|
|
15
|
+
pending(): number;
|
|
16
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process rendezvous between a turn paused at an ask-first tool and the
|
|
3
|
+
* decision that arrives later (console, CLI, MCP or a channel). A waiter only
|
|
4
|
+
* exists while the Host process that started the turn is alive.
|
|
5
|
+
*/
|
|
6
|
+
export class ApprovalBroker {
|
|
7
|
+
waiters = new Map();
|
|
8
|
+
wait(approvalId, options) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
let timer;
|
|
11
|
+
const finish = (outcome) => {
|
|
12
|
+
if (timer)
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
15
|
+
this.waiters.get(approvalId)?.delete(listener);
|
|
16
|
+
if (this.waiters.get(approvalId)?.size === 0)
|
|
17
|
+
this.waiters.delete(approvalId);
|
|
18
|
+
resolve(outcome);
|
|
19
|
+
};
|
|
20
|
+
const listener = (outcome) => finish(outcome);
|
|
21
|
+
const onAbort = () => finish("aborted");
|
|
22
|
+
if (options.signal.aborted)
|
|
23
|
+
return resolve("aborted");
|
|
24
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
25
|
+
const set = this.waiters.get(approvalId) ?? new Set();
|
|
26
|
+
set.add(listener);
|
|
27
|
+
this.waiters.set(approvalId, set);
|
|
28
|
+
const arm = () => {
|
|
29
|
+
const remaining = Date.parse(options.expiresAt) - (options.now?.() ?? Date.now());
|
|
30
|
+
if (remaining <= 0)
|
|
31
|
+
return finish("expired");
|
|
32
|
+
// setTimeout caps at ~24.8 days; longer waits re-arm until the real expiry.
|
|
33
|
+
timer = setTimeout(arm, Math.min(remaining, 2 ** 31 - 1));
|
|
34
|
+
};
|
|
35
|
+
timer = setTimeout(arm, 0);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
settle(approvalId, outcome) {
|
|
39
|
+
for (const listener of [...(this.waiters.get(approvalId) ?? [])])
|
|
40
|
+
listener(outcome);
|
|
41
|
+
}
|
|
42
|
+
pending() {
|
|
43
|
+
return this.waiters.size;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SummaryLine } from "./definitionSummary.js";
|
|
2
|
+
import { type AgentAuthorDraft, type OperatorAgentHost } from "../operatorAgent/index.js";
|
|
3
|
+
export type AgentAuthorTurnResult = {
|
|
4
|
+
sessionId: string;
|
|
5
|
+
text: string;
|
|
6
|
+
draft: AgentAuthorDraft;
|
|
7
|
+
/** One line per config section this turn changed. Empty when the turn only talked. */
|
|
8
|
+
summaryLines: SummaryLine[];
|
|
9
|
+
};
|
|
10
|
+
export declare function resetAgentAuthorSessionsForTests(): void;
|
|
11
|
+
export declare function runAgentAuthorTurn(input: {
|
|
12
|
+
host: OperatorAgentHost;
|
|
13
|
+
projectRoot: string;
|
|
14
|
+
message: string;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
draft?: AgentAuthorDraft;
|
|
17
|
+
}): Promise<AgentAuthorTurnResult>;
|
|
18
|
+
export declare function undoAgentAuthorTurn(sessionId: string): AgentAuthorDraft;
|
|
19
|
+
export declare function parseAgentAuthorDraft(body: unknown): AgentAuthorDraft;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { describeChanges } from "./definitionSummary.js";
|
|
3
|
+
import { AGENT_AUTHOR_PROFILE_ID, createAgentAuthorContext, isAgentAuthorContext, readAgentAuthorDraft, } from "../operatorAgent/index.js";
|
|
4
|
+
const sessions = new Map();
|
|
5
|
+
export function resetAgentAuthorSessionsForTests() {
|
|
6
|
+
for (const session of sessions.values())
|
|
7
|
+
session.close();
|
|
8
|
+
sessions.clear();
|
|
9
|
+
}
|
|
10
|
+
export async function runAgentAuthorTurn(input) {
|
|
11
|
+
const message = input.message.trim();
|
|
12
|
+
if (!message)
|
|
13
|
+
throw new Error("agent.invalid");
|
|
14
|
+
let sessionId = input.sessionId?.trim();
|
|
15
|
+
let session = sessionId ? sessions.get(sessionId) : undefined;
|
|
16
|
+
if (sessionId && !session)
|
|
17
|
+
throw new Error("agent.session_not_found");
|
|
18
|
+
const posted = createAgentAuthorContext({
|
|
19
|
+
projectRoot: input.projectRoot,
|
|
20
|
+
draft: input.draft,
|
|
21
|
+
});
|
|
22
|
+
if (!session) {
|
|
23
|
+
sessionId = randomUUID();
|
|
24
|
+
session = input.host.openSession({
|
|
25
|
+
profileId: AGENT_AUTHOR_PROFILE_ID,
|
|
26
|
+
context: posted,
|
|
27
|
+
});
|
|
28
|
+
sessions.set(sessionId, session);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
session.setContext(posted);
|
|
32
|
+
}
|
|
33
|
+
const before = readAgentAuthorDraft(session.getContext());
|
|
34
|
+
const events = await session.send(message);
|
|
35
|
+
const text = events
|
|
36
|
+
.filter((event) => event.type === "message" && event.role === "assistant")
|
|
37
|
+
.map((event) => (event.type === "message" ? event.text : ""))
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join("\n\n");
|
|
40
|
+
const error = events.find((event) => event.type === "error");
|
|
41
|
+
const draft = readAgentAuthorDraft(session.getContext());
|
|
42
|
+
return {
|
|
43
|
+
sessionId: sessionId,
|
|
44
|
+
text: text || (error && error.type === "error" ? error.message : ""),
|
|
45
|
+
draft,
|
|
46
|
+
summaryLines: describeChanges(before, draft),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function undoAgentAuthorTurn(sessionId) {
|
|
50
|
+
const session = sessions.get(sessionId);
|
|
51
|
+
if (!session)
|
|
52
|
+
throw new Error("agent.session_not_found");
|
|
53
|
+
const undone = session.undoMutation();
|
|
54
|
+
if (!undone.ok)
|
|
55
|
+
throw new Error(undone.reason === "conflict" ? "agent.conflict" : "agent.invalid");
|
|
56
|
+
const context = session.getContext();
|
|
57
|
+
if (!isAgentAuthorContext(context))
|
|
58
|
+
throw new Error("agent.invalid");
|
|
59
|
+
return readAgentAuthorDraft(context);
|
|
60
|
+
}
|
|
61
|
+
export function parseAgentAuthorDraft(body) {
|
|
62
|
+
return body !== null && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AgentSessionDetail } from "./service.js";
|
|
2
|
+
import { type ConversationOptions, type SendTurnInput } from "./sessions.js";
|
|
3
|
+
/**
|
|
4
|
+
* Serializes turns per session (or per agent while a session is being
|
|
5
|
+
* opened) and keeps a handle on the running turn so it can be stopped.
|
|
6
|
+
*/
|
|
7
|
+
export declare class AgentChatController {
|
|
8
|
+
private readonly options;
|
|
9
|
+
private readonly turnChains;
|
|
10
|
+
private readonly activeBySession;
|
|
11
|
+
private readonly activeByAgent;
|
|
12
|
+
/** Sessions whose turn was stopped: their runtime session is released once the turn has wound down. */
|
|
13
|
+
private readonly stopped;
|
|
14
|
+
constructor(options: ConversationOptions);
|
|
15
|
+
private runExclusive;
|
|
16
|
+
send(input: SendTurnInput): Promise<AgentSessionDetail>;
|
|
17
|
+
/** Stop the running turn on a session. Returns false when nothing was running. */
|
|
18
|
+
stop(sessionId: string): boolean;
|
|
19
|
+
/** Let the runtime drop what it holds for a session (model session, isolated MCP processes). Never throws. */
|
|
20
|
+
release(sessionId: string): Promise<void>;
|
|
21
|
+
/** Stop a turn that is still opening its session (client left before the session id arrived). */
|
|
22
|
+
stopOpening(agentId: string): boolean;
|
|
23
|
+
}
|