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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type AgentDefinition } from "../../config/loadAgent.js";
|
|
3
|
+
import { type StageMemory } from "../../memory/access.js";
|
|
4
|
+
import { type SandboxRunner } from "../sandbox.js";
|
|
5
|
+
import { type ToolkitTool } from "../../toolkit/index.js";
|
|
6
|
+
import { type ConversationBackend, type TurnHooks } from "./types.js";
|
|
7
|
+
export type PiRuntimeOptions = {
|
|
8
|
+
cwd: string;
|
|
9
|
+
agentDir: string;
|
|
10
|
+
sandbox?: SandboxRunner;
|
|
11
|
+
/** Stageflow's own tools, built lazily because they need Host services that start after the runtime. */
|
|
12
|
+
toolkit?: () => ToolkitTool[];
|
|
13
|
+
/**
|
|
14
|
+
* How long a session may sit unused before its Pi session and isolated MCP processes are released. A later message
|
|
15
|
+
* reopens it from the stored transcript. Default 30 minutes; 0 keeps sessions until released or disposed.
|
|
16
|
+
*/
|
|
17
|
+
idleReleaseMs?: number;
|
|
18
|
+
/** Test seam: replaces session construction. */
|
|
19
|
+
openSession?: (input: OpenSessionInput) => Promise<OpenedSession>;
|
|
20
|
+
};
|
|
21
|
+
export type OpenSessionInput = {
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
definition: AgentDefinition;
|
|
24
|
+
systemPrompt: string;
|
|
25
|
+
/** Pi tool names the session may expose. */
|
|
26
|
+
toolNames: string[];
|
|
27
|
+
/** Runs before each tool call; the session wires it to `pi.on("tool_call")`. */
|
|
28
|
+
gate: (call: {
|
|
29
|
+
toolCallId: string;
|
|
30
|
+
toolName: string;
|
|
31
|
+
input: Record<string, unknown>;
|
|
32
|
+
}) => Promise<{
|
|
33
|
+
block: true;
|
|
34
|
+
reason: string;
|
|
35
|
+
terminate?: boolean;
|
|
36
|
+
} | undefined>;
|
|
37
|
+
delegateTool?: (agentId: string, message: string) => Promise<string>;
|
|
38
|
+
/** Stageflow tools this agent is granted. */
|
|
39
|
+
stageflowTools?: ToolkitTool[];
|
|
40
|
+
memory?: StageMemory;
|
|
41
|
+
hooks: () => TurnHooks | undefined;
|
|
42
|
+
};
|
|
43
|
+
export type OpenedSession = {
|
|
44
|
+
session: Pick<AgentSession, "prompt" | "subscribe" | "dispose"> & {
|
|
45
|
+
abort?: AgentSession["abort"];
|
|
46
|
+
};
|
|
47
|
+
/** Tool names that exist on the session (builtins + MCP + delegation) for grant mapping. */
|
|
48
|
+
toolNames: string[];
|
|
49
|
+
shutdown(): Promise<void>;
|
|
50
|
+
};
|
|
51
|
+
export declare const DEFAULT_IDLE_RELEASE_MS: number;
|
|
52
|
+
export declare function createPiAgentRuntime(options: PiRuntimeOptions): ConversationBackend;
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { defineTool, ModelRuntime, resolveCliModel, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import { collectMcpExtensionToolNames, mcpToolMetadataMissing } from "../../agent/piAdapter.js";
|
|
6
|
+
import { attachIsolatedMcp } from "../../agent/piIsolatedMcp.js";
|
|
7
|
+
import { warmIsolatedMcpMetadata } from "../../agent/piIsolatedMcpProbe.js";
|
|
8
|
+
import { createPiAgentSession, createSealedResourceLoader } from "../../agent/piSessionFactory.js";
|
|
9
|
+
import { bindCursorApiKey, isCursorModelRef } from "../../agent/cursorProvider.js";
|
|
10
|
+
import { findProviderSupport } from "../../agent/providerSupport.js";
|
|
11
|
+
import { grantedBuiltinTools } from "../../config/loadAgent.js";
|
|
12
|
+
import { bindStageMemory } from "../../memory/access.js";
|
|
13
|
+
import { MemoryError, memoryHostFor } from "../../memory/host.js";
|
|
14
|
+
import { memorySession } from "../../tools/memoryTools.js";
|
|
15
|
+
import { resolveSkillByName } from "../../config/listSkills.js";
|
|
16
|
+
import { resolveStageMcpServers } from "../../config/resolveStageMcpServers.js";
|
|
17
|
+
import { globalStageflowHome } from "../../project/globalHome.js";
|
|
18
|
+
import { isUsableAuthFile, resolveCredentialBinding } from "../../runtime/credentialBinding.js";
|
|
19
|
+
import { getModelCatalog } from "../../models/catalog.js";
|
|
20
|
+
import { AgentError } from "../errors.js";
|
|
21
|
+
import { MEMORY_STORE_BASH_DENIED, memoryStorePathInCommand } from "../../memory/bashDeny.js";
|
|
22
|
+
import { memoryHome } from "../../memory/host.js";
|
|
23
|
+
import { forbiddenWriteTarget } from "../policy.js";
|
|
24
|
+
import { prepareExecutionLaunch } from "../sandbox.js";
|
|
25
|
+
import { grantedStageflowTools, isOperatorOnlyTool } from "../../toolkit/index.js";
|
|
26
|
+
import { grantIdForPiTool, STAGEFLOW_PI_PREFIX } from "./types.js";
|
|
27
|
+
function piToolNames(definition) {
|
|
28
|
+
return grantedBuiltinTools(definition);
|
|
29
|
+
}
|
|
30
|
+
async function projectContext(projectRoot) {
|
|
31
|
+
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
|
|
32
|
+
try {
|
|
33
|
+
const text = await readFile(path.join(projectRoot, name), "utf8");
|
|
34
|
+
return `\n\n# Project context (${name})\n${text.slice(0, 20_000)}`;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
function textOf(content) {
|
|
43
|
+
if (!Array.isArray(content))
|
|
44
|
+
return "";
|
|
45
|
+
return content
|
|
46
|
+
.map((block) => (block && typeof block === "object" && block.type === "text" ? String(block.text ?? "") : ""))
|
|
47
|
+
.join("");
|
|
48
|
+
}
|
|
49
|
+
function replayPrompt(prior) {
|
|
50
|
+
return [
|
|
51
|
+
"Earlier conversation in this session (restored after a restart). Continue from it; do not re-ask answered questions.",
|
|
52
|
+
"",
|
|
53
|
+
...prior.map((m) => `${m.role}: ${m.text.trim()}`),
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
async function openPiSession(options, input) {
|
|
57
|
+
const { definition, projectRoot } = input;
|
|
58
|
+
const modelId = definition.model.id ?? (await getModelCatalog(options.cwd).defaultModel());
|
|
59
|
+
// Cursor's SDK agent runs its own file and shell tools beside Pi's, so grants and ask-first cannot hold.
|
|
60
|
+
if (isCursorModelRef(modelId) && process.env.STAGEFLOW_AGENTS_ALLOW_CURSOR_NATIVE_TOOLS !== "1") {
|
|
61
|
+
throw new AgentError("agent.backend_unavailable", "Cursor models run their own file and shell tools outside this agent's grants and approvals. Choose another provider's model, or set STAGEFLOW_AGENTS_ALLOW_CURSOR_NATIVE_TOOLS=1 on the Host to accept that.");
|
|
62
|
+
}
|
|
63
|
+
const provider = findProviderSupport(modelId);
|
|
64
|
+
const authPath = resolveCredentialBinding(options.cwd).authPath;
|
|
65
|
+
if (!provider && !isUsableAuthFile(authPath)) {
|
|
66
|
+
throw new AgentError("agent.backend_unavailable", "Provider auth is not configured. Add credentials in Providers, then retry.");
|
|
67
|
+
}
|
|
68
|
+
const restoreKey = bindCursorApiKey(modelId);
|
|
69
|
+
const extensionPaths = [];
|
|
70
|
+
let restoreProvider;
|
|
71
|
+
if (provider) {
|
|
72
|
+
const prepared = provider.prepare(modelId);
|
|
73
|
+
if (prepared.error)
|
|
74
|
+
throw new AgentError("agent.backend_unavailable", prepared.error);
|
|
75
|
+
extensionPaths.push(...prepared.extensionPaths);
|
|
76
|
+
restoreProvider = prepared.restore;
|
|
77
|
+
}
|
|
78
|
+
const cleanup = () => {
|
|
79
|
+
restoreKey?.();
|
|
80
|
+
restoreProvider?.();
|
|
81
|
+
};
|
|
82
|
+
let attached;
|
|
83
|
+
let session;
|
|
84
|
+
try {
|
|
85
|
+
await mkdir(options.agentDir, { recursive: true });
|
|
86
|
+
const modelRuntime = await ModelRuntime.create({ authPath, modelsPath: path.join(path.dirname(authPath), "models.json") });
|
|
87
|
+
const settingsManager = SettingsManager.inMemory({ compaction: { enabled: true } });
|
|
88
|
+
const mcpServers = Object.keys(definition.tools.mcp);
|
|
89
|
+
const resolvedMcp = mcpServers.length > 0
|
|
90
|
+
? await resolveStageMcpServers({ projectRoot, allowlist: mcpServers, env: {}, scope: { kind: "agent" } })
|
|
91
|
+
: undefined;
|
|
92
|
+
attached = await attachIsolatedMcp(resolvedMcp, { lifecycle: "lazy" });
|
|
93
|
+
const skillPaths = [];
|
|
94
|
+
for (const name of definition.tools.skills) {
|
|
95
|
+
const skill = await resolveSkillByName(name, { cwd: projectRoot, agentDir: options.agentDir });
|
|
96
|
+
if (!skill)
|
|
97
|
+
throw new AgentError("agent.backend_unavailable", `Skill "${name}" is not installed`);
|
|
98
|
+
skillPaths.push(skill.filePath);
|
|
99
|
+
}
|
|
100
|
+
const gateExtension = (pi) => {
|
|
101
|
+
pi.on("tool_call", async (event) => {
|
|
102
|
+
const outcome = await input.gate({ toolCallId: event.toolCallId, toolName: event.toolName, input: event.input });
|
|
103
|
+
return outcome;
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const extensionFactories = [{ name: "stageflow-agent-gate", factory: gateExtension }, ...(attached.extensionFactories ?? [])];
|
|
107
|
+
const loader = createSealedResourceLoader({
|
|
108
|
+
cwd: projectRoot,
|
|
109
|
+
agentDir: options.agentDir,
|
|
110
|
+
settingsManager,
|
|
111
|
+
systemPrompt: input.systemPrompt,
|
|
112
|
+
additionalExtensionPaths: extensionPaths,
|
|
113
|
+
...(skillPaths.length > 0 ? { additionalSkillPaths: skillPaths } : {}),
|
|
114
|
+
extensionFactories,
|
|
115
|
+
...(attached.eventBus ? { eventBus: attached.eventBus } : {}),
|
|
116
|
+
});
|
|
117
|
+
await loader.reload();
|
|
118
|
+
if (resolvedMcp && mcpToolMetadataMissing(loader)) {
|
|
119
|
+
await warmIsolatedMcpMetadata(resolvedMcp, { cwd: projectRoot });
|
|
120
|
+
await loader.reload();
|
|
121
|
+
if (mcpToolMetadataMissing(loader)) {
|
|
122
|
+
throw new AgentError("agent.backend_unavailable", `MCP tool metadata unavailable for: ${mcpServers.join(", ")}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const errors = loader.getExtensions().errors;
|
|
126
|
+
if (errors.length > 0) {
|
|
127
|
+
throw new AgentError("agent.backend_unavailable", `Failed to load extension(s): ${errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
128
|
+
}
|
|
129
|
+
const toolNames = [...input.toolNames];
|
|
130
|
+
if (attached.extensionFactories) {
|
|
131
|
+
const mcp = collectMcpExtensionToolNames(loader);
|
|
132
|
+
if (!mcp.ok)
|
|
133
|
+
throw new AgentError("agent.backend_unavailable", `MCP adapter registered unrequested tool(s): ${mcp.unrequested.join(", ")}`);
|
|
134
|
+
// Only tools the definition grants are exposed; the gate repeats this check on every call.
|
|
135
|
+
toolNames.push(...mcp.names.filter((name) => {
|
|
136
|
+
const id = grantIdForPiTool(name, mcpServers, []);
|
|
137
|
+
const dot = id.indexOf(".");
|
|
138
|
+
if (dot < 0)
|
|
139
|
+
return false;
|
|
140
|
+
const allowed = definition.tools.mcp[id.slice(0, dot)];
|
|
141
|
+
return allowed?.includes("*") || allowed?.includes(id.slice(dot + 1));
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
const customTools = [];
|
|
145
|
+
if (input.delegateTool) {
|
|
146
|
+
for (const agentId of definition.sub_agents) {
|
|
147
|
+
customTools.push(defineTool({
|
|
148
|
+
name: `agent_${agentId}`,
|
|
149
|
+
label: `Ask ${agentId}`,
|
|
150
|
+
description: `Hand a task to the "${agentId}" agent and get its reply.`,
|
|
151
|
+
parameters: Type.Object({ message: Type.String({ description: "The task or question for the agent." }) }),
|
|
152
|
+
execute: async (_id, args) => {
|
|
153
|
+
const reply = await input.delegateTool(agentId, args.message);
|
|
154
|
+
return { content: [{ type: "text", text: reply }], details: {} };
|
|
155
|
+
},
|
|
156
|
+
}));
|
|
157
|
+
toolNames.push(`agent_${agentId}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (input.memory) {
|
|
161
|
+
for (const definition of memorySession(input.memory).tools) {
|
|
162
|
+
customTools.push(defineTool(definition));
|
|
163
|
+
toolNames.push(definition.name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const tool of input.stageflowTools ?? []) {
|
|
167
|
+
customTools.push(defineTool({
|
|
168
|
+
name: `${STAGEFLOW_PI_PREFIX}${tool.name}`,
|
|
169
|
+
label: tool.name,
|
|
170
|
+
description: tool.description,
|
|
171
|
+
parameters: Type.Unsafe(tool.parameters),
|
|
172
|
+
execute: async (_id, args, signal) => {
|
|
173
|
+
const result = await tool.run(args ?? {}, { signal: signal ?? new AbortController().signal, caller: { agentId: definition.id, definition } });
|
|
174
|
+
if (!result.ok)
|
|
175
|
+
throw new Error(result.text);
|
|
176
|
+
return { content: [{ type: "text", text: result.text }], details: {} };
|
|
177
|
+
},
|
|
178
|
+
}));
|
|
179
|
+
toolNames.push(`${STAGEFLOW_PI_PREFIX}${tool.name}`);
|
|
180
|
+
}
|
|
181
|
+
const sessionDir = path.join(globalStageflowHome(), "agents", "pi-sessions");
|
|
182
|
+
await mkdir(sessionDir, { recursive: true });
|
|
183
|
+
const created = await createPiAgentSession({
|
|
184
|
+
cwd: projectRoot,
|
|
185
|
+
agentDir: options.agentDir,
|
|
186
|
+
modelRuntime,
|
|
187
|
+
sessionManager: SessionManager.create(projectRoot, sessionDir),
|
|
188
|
+
settingsManager,
|
|
189
|
+
resourceLoader: loader,
|
|
190
|
+
tools: toolNames,
|
|
191
|
+
customTools,
|
|
192
|
+
});
|
|
193
|
+
session = created.session;
|
|
194
|
+
await session.bindExtensions({});
|
|
195
|
+
const resolved = resolveCliModel({ cliModel: modelId, modelRuntime });
|
|
196
|
+
if (resolved.error || !resolved.model)
|
|
197
|
+
throw new AgentError("agent.backend_unavailable", resolved.error ?? `Model not found: ${modelId}`);
|
|
198
|
+
await session.setModel(resolved.model);
|
|
199
|
+
session.setThinkingLevel((resolved.thinkingLevel ?? definition.model.thinking));
|
|
200
|
+
const live = session;
|
|
201
|
+
const live_attached = attached;
|
|
202
|
+
return {
|
|
203
|
+
session: live,
|
|
204
|
+
toolNames,
|
|
205
|
+
shutdown: async () => {
|
|
206
|
+
try {
|
|
207
|
+
live.dispose();
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// best-effort
|
|
211
|
+
}
|
|
212
|
+
live_attached.cancel?.();
|
|
213
|
+
cleanup();
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
try {
|
|
219
|
+
session?.dispose();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// best-effort
|
|
223
|
+
}
|
|
224
|
+
attached?.cancel?.();
|
|
225
|
+
cleanup();
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Seconds of silence from the model before a turn is failed. 0 turns the watchdog off. */
|
|
230
|
+
function stallTimeoutMs() {
|
|
231
|
+
const seconds = Number(process.env.STAGEFLOW_AGENT_STALL_SECONDS ?? 180);
|
|
232
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : 180_000;
|
|
233
|
+
}
|
|
234
|
+
export const DEFAULT_IDLE_RELEASE_MS = 30 * 60_000;
|
|
235
|
+
/** Idle window from the option, else STAGEFLOW_AGENT_SESSION_IDLE_MINUTES, else 30 minutes. 0 disables. */
|
|
236
|
+
function idleReleaseMs(options) {
|
|
237
|
+
if (options.idleReleaseMs !== undefined)
|
|
238
|
+
return Math.max(0, options.idleReleaseMs);
|
|
239
|
+
const minutes = Number(process.env.STAGEFLOW_AGENT_SESSION_IDLE_MINUTES);
|
|
240
|
+
return process.env.STAGEFLOW_AGENT_SESSION_IDLE_MINUTES !== undefined && Number.isFinite(minutes) && minutes >= 0 ? minutes * 60_000 : DEFAULT_IDLE_RELEASE_MS;
|
|
241
|
+
}
|
|
242
|
+
export function createPiAgentRuntime(options) {
|
|
243
|
+
const bound = new Map();
|
|
244
|
+
const idleMs = idleReleaseMs(options);
|
|
245
|
+
const disarmIdle = (state) => {
|
|
246
|
+
if (state.idleTimer)
|
|
247
|
+
clearTimeout(state.idleTimer);
|
|
248
|
+
state.idleTimer = undefined;
|
|
249
|
+
};
|
|
250
|
+
const armIdle = (sessionId, state) => {
|
|
251
|
+
disarmIdle(state);
|
|
252
|
+
if (idleMs <= 0)
|
|
253
|
+
return;
|
|
254
|
+
state.idleTimer = setTimeout(() => {
|
|
255
|
+
state.idleTimer = undefined;
|
|
256
|
+
// A turn that started since is not idle; its end re-arms the timer.
|
|
257
|
+
if (state.hooks || bound.get(sessionId) !== state)
|
|
258
|
+
return;
|
|
259
|
+
bound.delete(sessionId);
|
|
260
|
+
void state.opened.shutdown().catch(() => undefined);
|
|
261
|
+
}, idleMs);
|
|
262
|
+
state.idleTimer.unref?.();
|
|
263
|
+
};
|
|
264
|
+
const open = options.openSession ?? ((input) => openPiSession(options, input));
|
|
265
|
+
async function ensure(input) {
|
|
266
|
+
const existing = bound.get(input.sessionId);
|
|
267
|
+
if (existing)
|
|
268
|
+
return { state: existing, fresh: false };
|
|
269
|
+
const { definition, projectRoot } = input;
|
|
270
|
+
if (definition.model.backend !== "pi") {
|
|
271
|
+
throw new AgentError("agent.backend_unavailable", `The ${definition.model.backend} backend is not available for agents yet.`);
|
|
272
|
+
}
|
|
273
|
+
await prepareExecutionLaunch(definition, projectRoot, options.sandbox);
|
|
274
|
+
const mcpServers = Object.keys(definition.tools.mcp);
|
|
275
|
+
const subAgents = [...definition.sub_agents];
|
|
276
|
+
const grantedSf = new Set(grantedStageflowTools(definition.tools.stageflow));
|
|
277
|
+
const stageflowTools = (options.toolkit?.() ?? []).filter((tool) => grantedSf.has(tool.name) && !isOperatorOnlyTool(tool.name));
|
|
278
|
+
const state = { opened: undefined, definitionKey: definition.id, hooks: undefined, mcpServers, subAgents };
|
|
279
|
+
const context = definition.project_context ? await projectContext(projectRoot) : "";
|
|
280
|
+
let memory;
|
|
281
|
+
if (definition.memories?.length) {
|
|
282
|
+
try {
|
|
283
|
+
memory = await bindStageMemory(memoryHostFor(), definition.memories);
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
const code = error instanceof MemoryError ? error.code : "MEMORY_UNREADABLE";
|
|
287
|
+
throw new AgentError("agent.invalid", `Memory could not be opened (${code})`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const systemPrompt = `${definition.instructions}${context}${memory ? `\n\n${memorySession(memory).prompt}` : ""}`;
|
|
291
|
+
state.opened = await open({
|
|
292
|
+
projectRoot,
|
|
293
|
+
definition,
|
|
294
|
+
systemPrompt,
|
|
295
|
+
toolNames: piToolNames(definition),
|
|
296
|
+
hooks: () => state.hooks,
|
|
297
|
+
...(memory ? { memory } : {}),
|
|
298
|
+
...(stageflowTools.length > 0 ? { stageflowTools } : {}),
|
|
299
|
+
...(subAgents.length > 0 ? { delegateTool: (agentId, message) => state.hooks.delegate(agentId, message) } : {}),
|
|
300
|
+
gate: async (call) => {
|
|
301
|
+
const hooks = state.hooks;
|
|
302
|
+
if (!hooks)
|
|
303
|
+
return { block: true, reason: "No turn is active" };
|
|
304
|
+
const tool = grantIdForPiTool(call.toolName, mcpServers, subAgents);
|
|
305
|
+
// Every tool whose arguments name a path is checked, not only write/edit. `bash` cannot be: see forbiddenWriteTarget.
|
|
306
|
+
if (forbiddenWriteTarget(projectRoot, globalStageflowHome(), call.toolName, call.input) !== undefined) {
|
|
307
|
+
return { block: true, reason: "Agent definitions and Stageflow state cannot be written by an agent." };
|
|
308
|
+
}
|
|
309
|
+
if (memory && call.toolName === "bash") {
|
|
310
|
+
const command = call.input.command;
|
|
311
|
+
if (typeof command === "string" && memoryStorePathInCommand(command, memoryHome())) {
|
|
312
|
+
return { block: true, reason: MEMORY_STORE_BASH_DENIED };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const release = state.stall?.hold();
|
|
316
|
+
const decision = await hooks.gate({ id: call.toolCallId, tool, input: call.input }).finally(() => release?.());
|
|
317
|
+
return decision.allow ? undefined : { block: true, reason: decision.reason, ...(decision.stopTurn ? { terminate: true } : {}) };
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
bound.set(input.sessionId, state);
|
|
321
|
+
return { state, fresh: true };
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
id: "pi",
|
|
325
|
+
async complete(input) {
|
|
326
|
+
const { state, fresh } = await ensure(input);
|
|
327
|
+
disarmIdle(state);
|
|
328
|
+
state.hooks = input.hooks;
|
|
329
|
+
const user = input.messages.at(-1);
|
|
330
|
+
if (!user || user.role !== "user")
|
|
331
|
+
return { text: "", toolOutputs: [] };
|
|
332
|
+
const { session } = state.opened;
|
|
333
|
+
const toolOutputs = [];
|
|
334
|
+
let text = "";
|
|
335
|
+
const usage = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
336
|
+
let startedAt = new Date();
|
|
337
|
+
const calls = new Map();
|
|
338
|
+
// A model call that goes quiet (provider queue, dropped connection) must not hold the session forever.
|
|
339
|
+
// Waiting on a person (approval), a running tool or a sub-agent is not a stall.
|
|
340
|
+
const stallMs = stallTimeoutMs();
|
|
341
|
+
let holds = 0;
|
|
342
|
+
let stalled = false;
|
|
343
|
+
let stallTimer;
|
|
344
|
+
const arm = () => {
|
|
345
|
+
if (stallTimer)
|
|
346
|
+
clearTimeout(stallTimer);
|
|
347
|
+
stallTimer = undefined;
|
|
348
|
+
if (holds > 0 || stallMs <= 0)
|
|
349
|
+
return;
|
|
350
|
+
stallTimer = setTimeout(() => {
|
|
351
|
+
stalled = true;
|
|
352
|
+
void session.abort?.();
|
|
353
|
+
}, stallMs);
|
|
354
|
+
};
|
|
355
|
+
state.stall = {
|
|
356
|
+
hold() {
|
|
357
|
+
holds += 1;
|
|
358
|
+
arm();
|
|
359
|
+
let released = false;
|
|
360
|
+
return () => {
|
|
361
|
+
if (released)
|
|
362
|
+
return;
|
|
363
|
+
released = true;
|
|
364
|
+
holds -= 1;
|
|
365
|
+
arm();
|
|
366
|
+
};
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
const toolHolds = new Map();
|
|
370
|
+
const unsubscribe = session.subscribe((event) => {
|
|
371
|
+
arm();
|
|
372
|
+
const e = event;
|
|
373
|
+
if (e.type === "message_start" && e.message?.role === "assistant")
|
|
374
|
+
startedAt = new Date();
|
|
375
|
+
else if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta" && typeof e.assistantMessageEvent.delta === "string") {
|
|
376
|
+
text += e.assistantMessageEvent.delta;
|
|
377
|
+
input.onDelta(e.assistantMessageEvent.delta);
|
|
378
|
+
}
|
|
379
|
+
else if (e.type === "message_end" && e.message?.role === "assistant") {
|
|
380
|
+
const u = e.message.usage;
|
|
381
|
+
const info = { inputTokens: u?.input ?? 0, outputTokens: u?.output ?? 0, costUsd: u?.cost?.total ?? 0 };
|
|
382
|
+
usage.inputTokens += info.inputTokens;
|
|
383
|
+
usage.outputTokens += info.outputTokens;
|
|
384
|
+
usage.costUsd += info.costUsd;
|
|
385
|
+
input.hooks.modelFinished({ startedAt, endedAt: new Date(), ...info, ...(e.message.model ? { model: e.message.model } : {}) });
|
|
386
|
+
const final = textOf(e.message.content);
|
|
387
|
+
if (final && !text)
|
|
388
|
+
text = final;
|
|
389
|
+
}
|
|
390
|
+
else if (e.type === "tool_execution_start" && e.toolCallId && e.toolName) {
|
|
391
|
+
const call = { id: e.toolCallId, tool: grantIdForPiTool(e.toolName, state.mcpServers, state.subAgents), input: e.args ?? {} };
|
|
392
|
+
calls.set(e.toolCallId, call);
|
|
393
|
+
toolHolds.set(e.toolCallId, state.stall.hold());
|
|
394
|
+
input.hooks.toolStarted(call);
|
|
395
|
+
}
|
|
396
|
+
else if (e.type === "tool_execution_end" && e.toolCallId) {
|
|
397
|
+
toolHolds.get(e.toolCallId)?.();
|
|
398
|
+
toolHolds.delete(e.toolCallId);
|
|
399
|
+
const call = calls.get(e.toolCallId);
|
|
400
|
+
if (call) {
|
|
401
|
+
const output = textOf(e.result?.content);
|
|
402
|
+
toolOutputs.push(output);
|
|
403
|
+
input.hooks.toolFinished(call, { ok: !(e.isError ?? e.result?.isError), output });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
const onAbort = () => void session.abort?.();
|
|
408
|
+
input.signal.addEventListener("abort", onAbort, { once: true });
|
|
409
|
+
try {
|
|
410
|
+
const prior = input.messages.slice(0, -1).map((m) => ({ role: m.role, text: m.text }));
|
|
411
|
+
const prompt = fresh && prior.length > 0 ? `${replayPrompt(prior)}\n\nuser: ${user.text}` : user.text;
|
|
412
|
+
arm();
|
|
413
|
+
await session.prompt(prompt);
|
|
414
|
+
}
|
|
415
|
+
finally {
|
|
416
|
+
if (stallTimer)
|
|
417
|
+
clearTimeout(stallTimer);
|
|
418
|
+
for (const release of toolHolds.values())
|
|
419
|
+
release();
|
|
420
|
+
input.signal.removeEventListener("abort", onAbort);
|
|
421
|
+
unsubscribe();
|
|
422
|
+
state.hooks = undefined;
|
|
423
|
+
state.stall = undefined;
|
|
424
|
+
if (bound.get(input.sessionId) === state)
|
|
425
|
+
armIdle(input.sessionId, state);
|
|
426
|
+
}
|
|
427
|
+
if (stalled) {
|
|
428
|
+
throw new AgentError("agent.model_timeout", `The model stopped responding for ${Math.round(stallMs / 1000)} seconds. Try again, or pick a faster model or a lower thinking level.`);
|
|
429
|
+
}
|
|
430
|
+
return { text, toolOutputs, usage };
|
|
431
|
+
},
|
|
432
|
+
async judge(input) {
|
|
433
|
+
const opened = await open({
|
|
434
|
+
projectRoot: input.projectRoot,
|
|
435
|
+
definition: { ...input.definition, tools: { ...input.definition.tools, builtin: [], builtin_tools: [], mcp: {}, skills: [] }, sub_agents: [] },
|
|
436
|
+
systemPrompt: "You are a strict evaluator. Answer only with the requested number.",
|
|
437
|
+
toolNames: [],
|
|
438
|
+
gate: async () => ({ block: true, reason: "No tools" }),
|
|
439
|
+
hooks: () => undefined,
|
|
440
|
+
});
|
|
441
|
+
let out = "";
|
|
442
|
+
const unsubscribe = opened.session.subscribe((event) => {
|
|
443
|
+
const e = event;
|
|
444
|
+
if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta")
|
|
445
|
+
out += e.assistantMessageEvent.delta ?? "";
|
|
446
|
+
});
|
|
447
|
+
const onAbort = () => void opened.session.abort?.();
|
|
448
|
+
input.signal.addEventListener("abort", onAbort, { once: true });
|
|
449
|
+
try {
|
|
450
|
+
await opened.session.prompt(input.prompt);
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
input.signal.removeEventListener("abort", onAbort);
|
|
454
|
+
unsubscribe();
|
|
455
|
+
await opened.shutdown();
|
|
456
|
+
}
|
|
457
|
+
return out;
|
|
458
|
+
},
|
|
459
|
+
async release(sessionId) {
|
|
460
|
+
const state = bound.get(sessionId);
|
|
461
|
+
bound.delete(sessionId);
|
|
462
|
+
if (state)
|
|
463
|
+
disarmIdle(state);
|
|
464
|
+
await state?.opened.shutdown();
|
|
465
|
+
},
|
|
466
|
+
async dispose() {
|
|
467
|
+
const all = [...bound.values()];
|
|
468
|
+
bound.clear();
|
|
469
|
+
for (const state of all)
|
|
470
|
+
disarmIdle(state);
|
|
471
|
+
await Promise.all(all.map((state) => state.opened.shutdown()));
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AgentDefinition } from "../../config/loadAgent.js";
|
|
2
|
+
export type ConversationMessage = {
|
|
3
|
+
role: "user" | "assistant";
|
|
4
|
+
text: string;
|
|
5
|
+
};
|
|
6
|
+
/** One tool call by its grant id: a builtin name, `<server>.<tool>`, or `agent:<id>`. */
|
|
7
|
+
export type ToolCall = {
|
|
8
|
+
id: string;
|
|
9
|
+
tool: string;
|
|
10
|
+
input: Record<string, unknown>;
|
|
11
|
+
};
|
|
12
|
+
export type GateDecision = {
|
|
13
|
+
allow: true;
|
|
14
|
+
} | {
|
|
15
|
+
allow: false;
|
|
16
|
+
reason: string;
|
|
17
|
+
stopTurn?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export type RuntimeUsage = {
|
|
20
|
+
inputTokens: number;
|
|
21
|
+
outputTokens: number;
|
|
22
|
+
costUsd: number;
|
|
23
|
+
};
|
|
24
|
+
/** What the runtime reports back to the conversation while a turn runs. */
|
|
25
|
+
export type TurnHooks = {
|
|
26
|
+
/** Called before every tool call: grants, write guards, and ask-first approval. */
|
|
27
|
+
gate(call: ToolCall): Promise<GateDecision>;
|
|
28
|
+
toolStarted(call: ToolCall): void;
|
|
29
|
+
toolFinished(call: ToolCall, result: {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
output: string;
|
|
32
|
+
}): void;
|
|
33
|
+
modelFinished(info: {
|
|
34
|
+
startedAt: Date;
|
|
35
|
+
endedAt: Date;
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
outputTokens: number;
|
|
38
|
+
costUsd: number;
|
|
39
|
+
model?: string;
|
|
40
|
+
}): void;
|
|
41
|
+
/** Run another agent for a `agent:<id>` call and return its reply. */
|
|
42
|
+
delegate(agentId: string, message: string): Promise<string>;
|
|
43
|
+
};
|
|
44
|
+
export type CompleteInput = {
|
|
45
|
+
sessionId: string;
|
|
46
|
+
projectRoot: string;
|
|
47
|
+
definition: AgentDefinition;
|
|
48
|
+
/** Prior transcript followed by the new user message. */
|
|
49
|
+
messages: ConversationMessage[];
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
onDelta: (text: string) => void;
|
|
52
|
+
hooks: TurnHooks;
|
|
53
|
+
};
|
|
54
|
+
export type CompleteResult = {
|
|
55
|
+
text: string;
|
|
56
|
+
usage?: RuntimeUsage;
|
|
57
|
+
toolOutputs: string[];
|
|
58
|
+
};
|
|
59
|
+
export type ConversationBackend = {
|
|
60
|
+
id: "pi" | "claude";
|
|
61
|
+
complete(input: CompleteInput): Promise<CompleteResult>;
|
|
62
|
+
/** One-shot judgement with no tools (groundedness scoring). */
|
|
63
|
+
judge?(input: {
|
|
64
|
+
projectRoot: string;
|
|
65
|
+
definition: AgentDefinition;
|
|
66
|
+
prompt: string;
|
|
67
|
+
signal: AbortSignal;
|
|
68
|
+
}): Promise<string>;
|
|
69
|
+
/** Drop any cached model session for a conversation session. */
|
|
70
|
+
release?(sessionId: string): Promise<void>;
|
|
71
|
+
dispose?(): Promise<void>;
|
|
72
|
+
};
|
|
73
|
+
export declare const BUILTIN_TOOL_GROUP_OF: Record<string, "read" | "write" | "shell">;
|
|
74
|
+
/** Pi tool names for Stageflow's own tools are `stageflow_<tool>`. */
|
|
75
|
+
export declare const STAGEFLOW_PI_PREFIX = "stageflow_";
|
|
76
|
+
/** Map a Pi tool name to its grant id given the granted MCP servers and sub-agents. */
|
|
77
|
+
export declare function grantIdForPiTool(piTool: string, mcpServers: readonly string[], subAgents: readonly string[]): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { stageflowGrantId, TOOL_META } from "../../toolkit/meta.js";
|
|
2
|
+
export const BUILTIN_TOOL_GROUP_OF = {
|
|
3
|
+
read: "read",
|
|
4
|
+
grep: "read",
|
|
5
|
+
find: "read",
|
|
6
|
+
ls: "read",
|
|
7
|
+
write: "write",
|
|
8
|
+
edit: "write",
|
|
9
|
+
bash: "shell",
|
|
10
|
+
};
|
|
11
|
+
/** Pi tool names for Stageflow's own tools are `stageflow_<tool>`. */
|
|
12
|
+
export const STAGEFLOW_PI_PREFIX = "stageflow_";
|
|
13
|
+
/** Map a Pi tool name to its grant id given the granted MCP servers and sub-agents. */
|
|
14
|
+
export function grantIdForPiTool(piTool, mcpServers, subAgents) {
|
|
15
|
+
if (piTool in BUILTIN_TOOL_GROUP_OF)
|
|
16
|
+
return piTool;
|
|
17
|
+
if (piTool.startsWith(STAGEFLOW_PI_PREFIX) && piTool.slice(STAGEFLOW_PI_PREFIX.length) in TOOL_META)
|
|
18
|
+
return stageflowGrantId(piTool.slice(STAGEFLOW_PI_PREFIX.length));
|
|
19
|
+
const agentMatch = /^agent_(.+)$/.exec(piTool);
|
|
20
|
+
if (agentMatch && subAgents.includes(agentMatch[1]))
|
|
21
|
+
return `agent:${agentMatch[1]}`;
|
|
22
|
+
const server = [...mcpServers].sort((a, b) => b.length - a.length).find((name) => piTool.startsWith(`${name}_`));
|
|
23
|
+
if (server)
|
|
24
|
+
return `${server}.${piTool.slice(server.length + 1)}`;
|
|
25
|
+
return piTool;
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentDefinition } from "../config/loadAgent.js";
|
|
2
|
+
export type SandboxRunner = {
|
|
3
|
+
prepareLaunch(input: {
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
definition: AgentDefinition;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
ref: string;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
export type ExecutionLaunch = {
|
|
11
|
+
mode: "trusted-local";
|
|
12
|
+
} | {
|
|
13
|
+
mode: "sandbox";
|
|
14
|
+
ref: string;
|
|
15
|
+
};
|
|
16
|
+
/** A sandboxed agent never falls back to trusted-local when no sandbox is available. */
|
|
17
|
+
export declare function prepareExecutionLaunch(definition: AgentDefinition, projectRoot: string, runner: SandboxRunner | undefined): Promise<ExecutionLaunch>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AgentError } from "./errors.js";
|
|
2
|
+
/** A sandboxed agent never falls back to trusted-local when no sandbox is available. */
|
|
3
|
+
export async function prepareExecutionLaunch(definition, projectRoot, runner) {
|
|
4
|
+
if (definition.execution === "trusted-local")
|
|
5
|
+
return { mode: "trusted-local" };
|
|
6
|
+
if (!runner)
|
|
7
|
+
throw new AgentError("agent.sandbox_unavailable");
|
|
8
|
+
const launched = await runner.prepareLaunch({ projectRoot, definition });
|
|
9
|
+
return { mode: "sandbox", ref: launched.ref };
|
|
10
|
+
}
|