stageflow 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/agent/activity.d.ts +18 -0
- package/dist/agent/claudeAdapter.js +7 -0
- package/dist/agent/claudeTools.d.ts +27 -18
- package/dist/agent/claudeTools.js +54 -15
- package/dist/agent/compactionEvents.d.ts +34 -0
- package/dist/agent/compactionEvents.js +37 -0
- package/dist/agent/openRouterRouting.d.ts +7 -0
- package/dist/agent/openRouterRouting.js +23 -0
- package/dist/agent/piAdapter.d.ts +6 -0
- package/dist/agent/piAdapter.js +128 -27
- package/dist/agent/piSettings.d.ts +7 -0
- package/dist/agent/piSettings.js +9 -0
- package/dist/agent/sandboxPiTools.d.ts +3 -1
- package/dist/agent/sandboxPiTools.js +8 -2
- package/dist/agent/stageTurnDriver.d.ts +11 -2
- package/dist/agent/stageTurnDriver.js +45 -4
- package/dist/agent/toolOutputBudget.d.ts +15 -1
- package/dist/agent/toolOutputBudget.js +18 -0
- package/dist/agent/toolOutputBudgetExtension.d.ts +2 -0
- package/dist/agent/toolOutputBudgetExtension.js +40 -3
- package/dist/agents/authorChat.d.ts +1 -0
- package/dist/agents/authorChat.js +7 -0
- package/dist/agents/authorSessionStore.d.ts +3 -0
- package/dist/agents/authorSessionStore.js +17 -2
- package/dist/agents/hostContext.d.ts +1 -1
- package/dist/agents/runtime/piRuntime.d.ts +5 -1
- package/dist/agents/runtime/piRuntime.js +46 -12
- package/dist/agents/service.js +3 -2
- package/dist/agents/sessions.js +3 -1
- package/dist/compaction/directives.d.ts +18 -0
- package/dist/compaction/directives.js +119 -0
- package/dist/compaction/extension.d.ts +20 -0
- package/dist/compaction/extension.js +78 -0
- package/dist/compaction/mask.d.ts +18 -0
- package/dist/compaction/mask.js +155 -0
- package/dist/compaction/pin.d.ts +21 -0
- package/dist/compaction/pin.js +270 -0
- package/dist/compaction/scrub.d.ts +7 -0
- package/dist/compaction/scrub.js +26 -0
- package/dist/compaction/sessionWiring.d.ts +22 -0
- package/dist/compaction/sessionWiring.js +26 -0
- package/dist/compaction/summarize.d.ts +79 -0
- package/dist/compaction/summarize.js +171 -0
- package/dist/compaction/toolPolicy.d.ts +3 -0
- package/dist/compaction/toolPolicy.js +33 -0
- package/dist/compaction/types.d.ts +45 -0
- package/dist/compaction/types.js +7 -0
- package/dist/compaction/workingState.d.ts +56 -0
- package/dist/compaction/workingState.js +198 -0
- package/dist/config/createPipeline.js +2 -0
- package/dist/config/createTrigger.js +7 -1
- package/dist/config/ensureCatalogScanRoot.d.ts +2 -0
- package/dist/config/ensureCatalogScanRoot.js +6 -0
- package/dist/config/evalCatalog.js +30 -2
- package/dist/config/githubTriggerAction.d.ts +8 -0
- package/dist/config/githubTriggerAction.js +29 -0
- package/dist/config/hostConfig.js +2 -0
- package/dist/config/loadEval.js +21 -3
- package/dist/config/pipelineNeeds.d.ts +2 -0
- package/dist/config/pipelineNeeds.js +16 -3
- package/dist/config/pipelineRoute.js +5 -4
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCatalog.js +12 -0
- package/dist/config/validateEval.js +43 -2
- package/dist/config/writeEval.js +2 -2
- package/dist/envelope/assertEmittedArtifacts.d.ts +12 -0
- package/dist/envelope/assertEmittedArtifacts.js +43 -0
- package/dist/envelope/preEmitChecks.d.ts +3 -0
- package/dist/evals/constants.d.ts +3 -1
- package/dist/evals/constants.js +3 -1
- package/dist/evals/engine/FakeEngine.d.ts +1 -1
- package/dist/evals/engine/FakeEngine.js +2 -1
- package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +1 -1
- package/dist/evals/engine/promptfoo/PromptfooEngine.js +2 -1
- package/dist/evals/engine/promptfoo/generate.d.ts +1 -1
- package/dist/evals/engine/promptfoo/generate.js +11 -8
- package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +1 -1
- package/dist/evals/engine/promptfoo/rubricPrompt.js +5 -3
- package/dist/evals/manager.d.ts +13 -0
- package/dist/evals/manager.js +10 -2
- package/dist/evals/plan.d.ts +8 -1
- package/dist/evals/plan.js +28 -9
- package/dist/evals/planInternalAgent.d.ts +19 -0
- package/dist/evals/planInternalAgent.js +205 -0
- package/dist/evals/read/api.d.ts +12 -0
- package/dist/evals/read/api.js +5 -0
- package/dist/evals/read/list.js +10 -1
- package/dist/evals/read/runs.d.ts +5 -0
- package/dist/evals/read/runs.js +7 -1
- package/dist/evals/runner/caseRunner.d.ts +2 -1
- package/dist/evals/runner/caseRunner.js +16 -5
- package/dist/evals/runner/internalAgentCaseRunner.d.ts +6 -0
- package/dist/evals/runner/internalAgentCaseRunner.js +250 -0
- package/dist/evals/snapshot.d.ts +10 -2
- package/dist/evals/snapshot.js +8 -3
- package/dist/evals/types.d.ts +12 -2
- package/dist/evals/types.js +4 -1
- package/dist/guide/cardContract.d.ts +133 -0
- package/dist/guide/cardContract.js +156 -0
- package/dist/guide/chat.d.ts +45 -0
- package/dist/guide/chat.js +378 -0
- package/dist/guide/knowledge/index.d.ts +14 -0
- package/dist/guide/knowledge/index.js +109 -0
- package/dist/guide/knowledge/topics/agents.d.ts +2 -0
- package/dist/guide/knowledge/topics/agents.js +58 -0
- package/dist/guide/knowledge/topics/bestPractices.d.ts +2 -0
- package/dist/guide/knowledge/topics/bestPractices.js +67 -0
- package/dist/guide/knowledge/topics/connections.d.ts +2 -0
- package/dist/guide/knowledge/topics/connections.js +66 -0
- package/dist/guide/knowledge/topics/consoleMap.d.ts +2 -0
- package/dist/guide/knowledge/topics/consoleMap.js +55 -0
- package/dist/guide/knowledge/topics/evals.d.ts +2 -0
- package/dist/guide/knowledge/topics/evals.js +72 -0
- package/dist/guide/knowledge/topics/limitations.d.ts +2 -0
- package/dist/guide/knowledge/topics/limitations.js +84 -0
- package/dist/guide/knowledge/topics/mcpSkillsBrowser.d.ts +2 -0
- package/dist/guide/knowledge/topics/mcpSkillsBrowser.js +57 -0
- package/dist/guide/knowledge/topics/memories.d.ts +2 -0
- package/dist/guide/knowledge/topics/memories.js +63 -0
- package/dist/guide/knowledge/topics/modelsAndProviders.d.ts +2 -0
- package/dist/guide/knowledge/topics/modelsAndProviders.js +56 -0
- package/dist/guide/knowledge/topics/overview.d.ts +2 -0
- package/dist/guide/knowledge/topics/overview.js +56 -0
- package/dist/guide/knowledge/topics/pipelines.d.ts +2 -0
- package/dist/guide/knowledge/topics/pipelines.js +93 -0
- package/dist/guide/knowledge/topics/stages.d.ts +2 -0
- package/dist/guide/knowledge/topics/stages.js +54 -0
- package/dist/guide/knowledge/topics/tasks.d.ts +2 -0
- package/dist/guide/knowledge/topics/tasks.js +52 -0
- package/dist/guide/knowledge/topics/triggers.d.ts +2 -0
- package/dist/guide/knowledge/topics/triggers.js +68 -0
- package/dist/guide/knowledge/topics/troubleshooting.d.ts +2 -0
- package/dist/guide/knowledge/topics/troubleshooting.js +85 -0
- package/dist/guide/knowledgeTools.d.ts +2 -0
- package/dist/guide/knowledgeTools.js +56 -0
- package/dist/guide/showUi.d.ts +6 -0
- package/dist/guide/showUi.js +546 -0
- package/dist/guide/threadStore.d.ts +48 -0
- package/dist/guide/threadStore.js +189 -0
- package/dist/guide/writeRoot.d.ts +11 -0
- package/dist/guide/writeRoot.js +70 -0
- package/dist/hosted/translate.js +2 -0
- package/dist/mcp/agentTools.js +12 -0
- package/dist/mcp/catalogTools.js +18 -20
- package/dist/mcp/catalogWriteTools.d.ts +8 -0
- package/dist/mcp/catalogWriteTools.js +225 -0
- package/dist/mcp/connectionTools.d.ts +3 -0
- package/dist/mcp/connectionTools.js +85 -0
- package/dist/mcp/controlTools.js +34 -1
- package/dist/mcp/deps.d.ts +9 -0
- package/dist/mcp/evalTools.js +684 -21
- package/dist/mcp/memoryHostTools.d.ts +3 -0
- package/dist/mcp/memoryHostTools.js +72 -0
- package/dist/mcp/pipelineAuthorTools.js +14 -1
- package/dist/mcp/server.js +5 -0
- package/dist/mcp/tools.js +6 -0
- package/dist/mcp/triggerTools.js +77 -31
- package/dist/operatorAgent/evalGate.d.ts +22 -0
- package/dist/operatorAgent/evalGate.js +77 -0
- package/dist/operatorAgent/fakeHost.js +10 -2
- package/dist/operatorAgent/host.js +18 -2
- package/dist/operatorAgent/index.d.ts +1 -0
- package/dist/operatorAgent/index.js +1 -0
- package/dist/operatorAgent/internalAgents.d.ts +11 -0
- package/dist/operatorAgent/internalAgents.js +13 -0
- package/dist/operatorAgent/piModel.d.ts +5 -1
- package/dist/operatorAgent/piModel.js +15 -10
- package/dist/operatorAgent/profiles/agentAuthor.js +66 -22
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
- package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +1 -1
- package/dist/operatorAgent/profiles/guideConfirm.d.ts +21 -0
- package/dist/operatorAgent/profiles/guideConfirm.js +100 -0
- package/dist/operatorAgent/profiles/guideTools.d.ts +7 -0
- package/dist/operatorAgent/profiles/guideTools.js +123 -0
- package/dist/operatorAgent/profiles/stageflowGuide.d.ts +7 -0
- package/dist/operatorAgent/profiles/stageflowGuide.js +132 -0
- package/dist/operatorAgent/profiles/workshopAuthor.js +23 -0
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +9 -1
- package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +2 -0
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +197 -9
- package/dist/operatorAgent/types.d.ts +23 -0
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/retention.d.ts +2 -1
- package/dist/runstore/retention.js +3 -7
- package/dist/runstore/runStatusClock.d.ts +17 -0
- package/dist/runstore/runStatusClock.js +19 -0
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +1 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +45 -45
- package/dist/runstore/workspaceLayout.js +6 -1
- package/dist/runtime/artifactDiskInspect.d.ts +14 -0
- package/dist/runtime/artifactDiskInspect.js +52 -0
- package/dist/runtime/artifactPath.d.ts +24 -0
- package/dist/runtime/artifactPath.js +84 -0
- package/dist/runtime/completionCheckRunner.js +15 -43
- package/dist/runtime/githubPollSource.d.ts +22 -4
- package/dist/runtime/githubPollSource.js +93 -8
- package/dist/runtime/priorArtifactGrants.d.ts +8 -0
- package/dist/runtime/priorArtifactGrants.js +105 -0
- package/dist/runtime/runActionEligibility.d.ts +46 -0
- package/dist/runtime/runActionEligibility.js +123 -0
- package/dist/runtime/runManager.d.ts +4 -1
- package/dist/runtime/runManager.js +52 -10
- package/dist/runtime/runRetryCoordinator.d.ts +2 -14
- package/dist/runtime/runRetryCoordinator.js +3 -67
- package/dist/runtime/sandboxContainer.d.ts +7 -0
- package/dist/runtime/sandboxContainer.js +48 -6
- package/dist/runtime/sandboxDesk.d.ts +2 -1
- package/dist/runtime/sandboxDesk.js +11 -12
- package/dist/runtime/stageEnvironment.js +1 -0
- package/dist/runtime/stageRoots.d.ts +12 -0
- package/dist/runtime/stageSandbox.d.ts +2 -1
- package/dist/runtime/stageSandbox.js +18 -2
- package/dist/runtime/stageWorker.js +20 -0
- package/dist/server/agentAuthorRoutes.js +12 -3
- package/dist/server/bootstrap.d.ts +5 -0
- package/dist/server/bootstrap.js +14 -1
- package/dist/server/evalRoutes.js +38 -9
- package/dist/server/guideRoutes.d.ts +5 -0
- package/dist/server/guideRoutes.js +166 -0
- package/dist/server/http.d.ts +10 -0
- package/dist/server/http.js +35 -1
- package/dist/server/internalAgentRoutes.d.ts +6 -0
- package/dist/server/internalAgentRoutes.js +12 -0
- package/dist/server/mcpHost.js +2 -0
- package/dist/toolkit/meta.d.ts +1 -1
- package/dist/toolkit/meta.js +39 -1
- package/dist/toolkit/profiles.d.ts +5 -0
- package/dist/toolkit/profiles.js +12 -0
- package/dist/tools/askOperator.js +2 -1
- package/dist/tools/emitStageEnvelope.d.ts +17 -16
- package/dist/tools/emitStageEnvelope.js +46 -32
- package/dist/tools/stageToolNames.d.ts +4 -0
- package/dist/tools/stageToolNames.js +4 -0
- package/dist/tools/workingState.d.ts +40 -0
- package/dist/tools/workingState.js +108 -0
- package/dist/tools/writeStageArtifact.d.ts +0 -9
- package/dist/tools/writeStageArtifact.js +2 -1
- package/dist/ui/assets/{MarkdownText-BTrOqu5J.js → MarkdownText-S5C_vemc.js} +2 -2
- package/dist/ui/assets/adapters-C0l4ruiR.js +17 -0
- package/dist/ui/assets/chat-CjnsmvqR.js +2 -0
- package/dist/ui/assets/chat-DFDd4UjS.css +2 -0
- package/dist/ui/assets/{main-CHlmTNJe.css → main-Del1GeLf.css} +1 -1
- package/dist/ui/assets/main-_yXVqcW9.js +277 -0
- package/dist/ui/chat.html +3 -4
- package/dist/ui/index.html +3 -4
- package/dist/workshop/chatTurn.js +1 -1
- package/dist/workshop/workshopPin.d.ts +3 -3
- package/dist/workshop/workshopPin.js +16 -15
- package/examples/compaction-stress/README.md +30 -0
- package/examples/compaction-stress/check-run.mjs +53 -0
- package/examples/compaction-stress/collect-evidence.yaml +60 -0
- package/examples/compaction-stress/compaction-stress.pipeline.yaml +141 -0
- package/examples/compaction-stress/compaction-stress.task.yaml +15 -0
- package/examples/compaction-stress/generate-evidence.mjs +110 -0
- package/examples/compaction-stress/root-cause.yaml +26 -0
- package/examples/compaction-stress/stageflow.yaml +9 -0
- package/examples/compaction-stress/write-postmortem.yaml +40 -0
- package/examples/evals/README.md +18 -0
- package/examples/evals/guide-behavior.dataset.jsonl +15 -0
- package/examples/evals/guide-behavior.eval.yaml +35 -0
- package/examples/evals/guide-depth.dataset.jsonl +35 -0
- package/examples/evals/guide-depth.eval.yaml +169 -0
- package/examples/memory/evals/guide-behavior.dataset.jsonl +15 -0
- package/examples/memory/evals/guide-behavior.eval.yaml +35 -0
- package/examples/memory/evals/guide-depth.dataset.jsonl +35 -0
- package/examples/memory/evals/guide-depth.eval.yaml +169 -0
- package/package.json +1 -1
- package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +0 -9
- package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +0 -6
- package/dist/ui/assets/chat-Df_wCpak.js +0 -5
- package/dist/ui/assets/chat-P7KQznHm.css +0 -2
- package/dist/ui/assets/main-CvVfxmMd.js +0 -271
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { globalStageflowHome } from "../project/globalHome.js";
|
|
5
5
|
function guardSessionId(sessionId) {
|
|
@@ -114,11 +114,26 @@ export function saveAuthorSession(record, storeRoot) {
|
|
|
114
114
|
writeRecord(next, storeRoot);
|
|
115
115
|
return next;
|
|
116
116
|
}
|
|
117
|
+
/** Removes the session directory. Returns false when the id is invalid or missing. */
|
|
118
|
+
export function deleteAuthorSession(sessionId, storeRoot) {
|
|
119
|
+
let file;
|
|
120
|
+
try {
|
|
121
|
+
file = authorSessionFilePath(sessionId, storeRoot);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const dir = path.dirname(file);
|
|
127
|
+
if (!existsSync(dir))
|
|
128
|
+
return false;
|
|
129
|
+
rmSync(dir, { recursive: true, force: true });
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
117
132
|
function draftTitle(draft) {
|
|
118
133
|
const name = typeof draft.name === "string" ? draft.name.trim() : "";
|
|
119
134
|
return name || "Untitled draft";
|
|
120
135
|
}
|
|
121
|
-
function draftHasWork(record) {
|
|
136
|
+
export function draftHasWork(record) {
|
|
122
137
|
if (record.transcript.length > 0)
|
|
123
138
|
return true;
|
|
124
139
|
for (const key of ["name", "instructions", "description"]) {
|
|
@@ -53,5 +53,5 @@ export declare function createAgentHostContext(options: {
|
|
|
53
53
|
/** Host services the Stageflow tools need; agent services are added here. Without it agents get no Stageflow tools. */
|
|
54
54
|
/** Hold streams open to AgentMail and iMessage for agents linked to them. Only a running Host does this. */
|
|
55
55
|
listenOnChannels?: boolean;
|
|
56
|
-
toolkitDeps?: Pick<McpToolDeps, "manager" | "store" | "agentDir" | "providerAuthContext" | "projectRoot" | "providerBoot">;
|
|
56
|
+
toolkitDeps?: Pick<McpToolDeps, "manager" | "store" | "agentDir" | "providerAuthContext" | "projectRoot" | "providerBoot" | "channels" | "memories" | "evalRunManager">;
|
|
57
57
|
}): AgentHostContext;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentSession } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import { type AgentSession, type ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { type AgentDefinition } from "../../config/loadAgent.js";
|
|
3
3
|
import { type StageMemory } from "../../memory/access.js";
|
|
4
4
|
import { type SandboxRunner } from "../sandbox.js";
|
|
@@ -38,6 +38,8 @@ export type OpenSessionInput = {
|
|
|
38
38
|
/** Stageflow tools this agent is granted. */
|
|
39
39
|
stageflowTools?: ToolkitTool[];
|
|
40
40
|
memory?: StageMemory;
|
|
41
|
+
/** Defaults to true; one-shot judge sessions have no use for it. */
|
|
42
|
+
workingState?: boolean;
|
|
41
43
|
hooks: () => TurnHooks | undefined;
|
|
42
44
|
};
|
|
43
45
|
export type OpenedSession = {
|
|
@@ -48,5 +50,7 @@ export type OpenedSession = {
|
|
|
48
50
|
toolNames: string[];
|
|
49
51
|
shutdown(): Promise<void>;
|
|
50
52
|
};
|
|
53
|
+
/** Runs the agent tool gate before every call, except for tools the session wiring marks as internal. */
|
|
54
|
+
export declare function createAgentGateExtension(gate: OpenSessionInput["gate"], isInternalTool: (toolName: string) => boolean): ExtensionFactory;
|
|
51
55
|
export declare const DEFAULT_IDLE_RELEASE_MS: number;
|
|
52
56
|
export declare function createPiAgentRuntime(options: PiRuntimeOptions): ConversationBackend;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { defineTool, ModelRuntime, resolveCliModel, SessionManager,
|
|
3
|
+
import { defineTool, ModelRuntime, resolveCliModel, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { collectMcpExtensionToolNames, mcpToolMetadataMissing } from "../../agent/piAdapter.js";
|
|
6
6
|
import { attachIsolatedMcp } from "../../agent/piIsolatedMcp.js";
|
|
7
7
|
import { warmIsolatedMcpMetadata } from "../../agent/piIsolatedMcpProbe.js";
|
|
8
8
|
import { createPiAgentSession, createSealedResourceLoader } from "../../agent/piSessionFactory.js";
|
|
9
|
+
import { createPiSettingsManager } from "../../agent/piSettings.js";
|
|
10
|
+
import { createSessionContextWiring } from "../../compaction/sessionWiring.js";
|
|
9
11
|
import { bindCursorApiKey, isCursorModelRef } from "../../agent/cursorProvider.js";
|
|
10
12
|
import { findProviderSupport } from "../../agent/providerSupport.js";
|
|
11
13
|
import { grantedBuiltinTools } from "../../config/loadAgent.js";
|
|
@@ -19,11 +21,22 @@ import { isUsableAuthFile, resolveCredentialBinding } from "../../runtime/creden
|
|
|
19
21
|
import { getModelCatalog } from "../../models/catalog.js";
|
|
20
22
|
import { AgentError } from "../errors.js";
|
|
21
23
|
import { MEMORY_STORE_BASH_DENIED, memoryStorePathInCommand } from "../../memory/bashDeny.js";
|
|
24
|
+
import { compactionUsageCost } from "../../agent/compactionEvents.js";
|
|
22
25
|
import { memoryHome } from "../../memory/host.js";
|
|
23
26
|
import { forbiddenWriteTarget } from "../policy.js";
|
|
24
27
|
import { prepareExecutionLaunch } from "../sandbox.js";
|
|
25
28
|
import { grantedStageflowTools, isOperatorOnlyTool } from "../../toolkit/index.js";
|
|
26
29
|
import { grantIdForPiTool, STAGEFLOW_PI_PREFIX } from "./types.js";
|
|
30
|
+
/** Runs the agent tool gate before every call, except for tools the session wiring marks as internal. */
|
|
31
|
+
export function createAgentGateExtension(gate, isInternalTool) {
|
|
32
|
+
return (pi) => {
|
|
33
|
+
pi.on("tool_call", async (event) => {
|
|
34
|
+
if (isInternalTool(event.toolName))
|
|
35
|
+
return undefined;
|
|
36
|
+
return gate({ toolCallId: event.toolCallId, toolName: event.toolName, input: event.input });
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
27
40
|
function piToolNames(definition) {
|
|
28
41
|
return grantedBuiltinTools(definition);
|
|
29
42
|
}
|
|
@@ -84,7 +97,7 @@ async function openPiSession(options, input) {
|
|
|
84
97
|
try {
|
|
85
98
|
await mkdir(options.agentDir, { recursive: true });
|
|
86
99
|
const modelRuntime = await ModelRuntime.create({ authPath, modelsPath: path.join(path.dirname(authPath), "models.json") });
|
|
87
|
-
const settingsManager =
|
|
100
|
+
const settingsManager = createPiSettingsManager();
|
|
88
101
|
const mcpServers = Object.keys(definition.tools.mcp);
|
|
89
102
|
const resolvedMcp = mcpServers.length > 0
|
|
90
103
|
? await resolveStageMcpServers({ projectRoot, allowlist: mcpServers, env: {}, scope: { kind: "agent" } })
|
|
@@ -97,18 +110,18 @@ async function openPiSession(options, input) {
|
|
|
97
110
|
throw new AgentError("agent.backend_unavailable", `Skill "${name}" is not installed`);
|
|
98
111
|
skillPaths.push(skill.filePath);
|
|
99
112
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
113
|
+
const contextWiring = createSessionContextWiring({ workingState: input.workingState !== false });
|
|
114
|
+
const gateExtension = createAgentGateExtension(input.gate, contextWiring.isInternalTool);
|
|
115
|
+
const extensionFactories = [
|
|
116
|
+
{ name: "stageflow-agent-gate", factory: gateExtension },
|
|
117
|
+
...contextWiring.extensions,
|
|
118
|
+
...(attached.extensionFactories ?? []),
|
|
119
|
+
];
|
|
107
120
|
const loader = createSealedResourceLoader({
|
|
108
121
|
cwd: projectRoot,
|
|
109
122
|
agentDir: options.agentDir,
|
|
110
123
|
settingsManager,
|
|
111
|
-
systemPrompt: input.systemPrompt,
|
|
124
|
+
systemPrompt: contextWiring.guidance ? `${input.systemPrompt}\n\n${contextWiring.guidance}` : input.systemPrompt,
|
|
112
125
|
additionalExtensionPaths: extensionPaths,
|
|
113
126
|
...(skillPaths.length > 0 ? { additionalSkillPaths: skillPaths } : {}),
|
|
114
127
|
extensionFactories,
|
|
@@ -127,6 +140,7 @@ async function openPiSession(options, input) {
|
|
|
127
140
|
throw new AgentError("agent.backend_unavailable", `Failed to load extension(s): ${errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
128
141
|
}
|
|
129
142
|
const toolNames = [...input.toolNames];
|
|
143
|
+
toolNames.push(...contextWiring.toolNames);
|
|
130
144
|
if (attached.extensionFactories) {
|
|
131
145
|
const mcp = collectMcpExtensionToolNames(loader);
|
|
132
146
|
if (!mcp.ok)
|
|
@@ -149,6 +163,7 @@ async function openPiSession(options, input) {
|
|
|
149
163
|
label: `Ask ${agentId}`,
|
|
150
164
|
description: `Hand a task to the "${agentId}" agent and get its reply.`,
|
|
151
165
|
parameters: Type.Object({ message: Type.String({ description: "The task or question for the agent." }) }),
|
|
166
|
+
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
152
167
|
execute: async (callId, args) => {
|
|
153
168
|
const reply = await input.delegateTool(agentId, args.message, callId);
|
|
154
169
|
return { content: [{ type: "text", text: reply }], details: {} };
|
|
@@ -159,7 +174,10 @@ async function openPiSession(options, input) {
|
|
|
159
174
|
}
|
|
160
175
|
if (input.memory) {
|
|
161
176
|
for (const definition of memorySession(input.memory).tools) {
|
|
162
|
-
customTools.push(defineTool(
|
|
177
|
+
customTools.push(defineTool({
|
|
178
|
+
...definition,
|
|
179
|
+
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
180
|
+
}));
|
|
163
181
|
toolNames.push(definition.name);
|
|
164
182
|
}
|
|
165
183
|
}
|
|
@@ -169,6 +187,7 @@ async function openPiSession(options, input) {
|
|
|
169
187
|
label: tool.name,
|
|
170
188
|
description: tool.description,
|
|
171
189
|
parameters: Type.Unsafe(tool.parameters),
|
|
190
|
+
constrainedSampling: { type: "json_schema", strict: "prefer" },
|
|
172
191
|
execute: async (_id, args, signal) => {
|
|
173
192
|
const result = await tool.run(args ?? {}, { signal: signal ?? new AbortController().signal, caller: { agentId: definition.id, definition } });
|
|
174
193
|
if (!result.ok)
|
|
@@ -334,6 +353,7 @@ export function createPiAgentRuntime(options) {
|
|
|
334
353
|
let text = "";
|
|
335
354
|
const usage = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
336
355
|
let startedAt = new Date();
|
|
356
|
+
let compactionStartedAt;
|
|
337
357
|
const calls = new Map();
|
|
338
358
|
// A model call that goes quiet (provider queue, dropped connection) must not hold the session forever.
|
|
339
359
|
// Waiting on a person (approval), a running tool or a sub-agent is not a stall.
|
|
@@ -370,7 +390,20 @@ export function createPiAgentRuntime(options) {
|
|
|
370
390
|
const unsubscribe = session.subscribe((event) => {
|
|
371
391
|
arm();
|
|
372
392
|
const e = event;
|
|
373
|
-
if (e.type === "
|
|
393
|
+
if (e.type === "compaction_start")
|
|
394
|
+
compactionStartedAt = new Date();
|
|
395
|
+
else if (e.type === "compaction_end") {
|
|
396
|
+
const end = event;
|
|
397
|
+
const info = compactionUsageCost(end);
|
|
398
|
+
usage.inputTokens += info.inputTokens;
|
|
399
|
+
usage.outputTokens += info.outputTokens;
|
|
400
|
+
usage.costUsd += info.costUsd;
|
|
401
|
+
const model = session.model?.id;
|
|
402
|
+
if (end.result?.usage) {
|
|
403
|
+
input.hooks.modelFinished({ startedAt: compactionStartedAt ?? new Date(), endedAt: new Date(), ...info, ...(model ? { model } : {}) });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
else if (e.type === "message_start" && e.message?.role === "assistant")
|
|
374
407
|
startedAt = new Date();
|
|
375
408
|
else if (e.type === "message_update" && e.assistantMessageEvent?.type === "text_delta" && typeof e.assistantMessageEvent.delta === "string") {
|
|
376
409
|
text += e.assistantMessageEvent.delta;
|
|
@@ -435,6 +468,7 @@ export function createPiAgentRuntime(options) {
|
|
|
435
468
|
definition: { ...input.definition, tools: { ...input.definition.tools, builtin: [], builtin_tools: [], mcp: {}, skills: [] }, sub_agents: [] },
|
|
436
469
|
systemPrompt: input.systemPrompt ?? "You are a strict evaluator. Answer only with the requested number.",
|
|
437
470
|
toolNames: [],
|
|
471
|
+
workingState: false,
|
|
438
472
|
gate: async () => ({ block: true, reason: "No tools" }),
|
|
439
473
|
hooks: () => undefined,
|
|
440
474
|
});
|
package/dist/agents/service.js
CHANGED
|
@@ -576,8 +576,9 @@ export class AgentService {
|
|
|
576
576
|
return false;
|
|
577
577
|
throw error;
|
|
578
578
|
});
|
|
579
|
-
if (onDisk || (existing && !existing.archived))
|
|
580
|
-
throw new AgentError("agent.exists");
|
|
579
|
+
if (onDisk || (existing && !existing.archived)) {
|
|
580
|
+
throw new AgentError("agent.exists", `An agent with id "${definition.id}" already exists`);
|
|
581
|
+
}
|
|
581
582
|
// A new agent always starts disabled (R1); enabling is its own revision.
|
|
582
583
|
await this.writeRevision(projectRoot, { ...definition, enabled: false }, actor, "Created");
|
|
583
584
|
});
|
package/dist/agents/sessions.js
CHANGED
|
@@ -5,6 +5,8 @@ import { EVAL_SESSION_SOURCE, isEvalSession } from "./evalSession.js";
|
|
|
5
5
|
import { guardedStream } from "./guardrails.js";
|
|
6
6
|
import { fitHistory } from "./historyFit.js";
|
|
7
7
|
import { isToolGranted, requiresAskFirst } from "./policy.js";
|
|
8
|
+
import { scrubSecrets } from "../compaction/scrub.js";
|
|
9
|
+
import { WORKING_STATE_TOOL_NAME } from "../compaction/types.js";
|
|
8
10
|
import { prepareSpan } from "./trace.js";
|
|
9
11
|
const GROUNDEDNESS_PROMPT = (reply, evidence) => [
|
|
10
12
|
"Rate how well the ANSWER is supported by the TOOL OUTPUTS, from 0 (unsupported or contradicted) to 1 (fully supported).",
|
|
@@ -202,7 +204,7 @@ export class AgentConversation {
|
|
|
202
204
|
...(isEvalSession(active.session) ? { "gen_ai.tool.call.id": call.id } : {}),
|
|
203
205
|
...(childSessionOf(call) ? { "stageflow.sub_agent.session_id": childSessionOf(call) } : {}),
|
|
204
206
|
},
|
|
205
|
-
input: toJson(call.input),
|
|
207
|
+
input: call.tool === WORKING_STATE_TOOL_NAME ? scrubSecrets(toJson(call.input)) : toJson(call.input),
|
|
206
208
|
output: result.output,
|
|
207
209
|
});
|
|
208
210
|
},
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LooseMessage } from "./pin.js";
|
|
2
|
+
export type DirectiveSource = "operator" | "user" | "rejection";
|
|
3
|
+
/** Words from the people running the work, copied verbatim so a summary cannot reword a decision. */
|
|
4
|
+
export type Directive = {
|
|
5
|
+
source: DirectiveSource;
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const DIRECTIVE_MAX_CHARS = 600;
|
|
9
|
+
export declare const USER_DIRECTIVE_MAX_CHARS = 280;
|
|
10
|
+
export declare const DIRECTIVES_CARRY_MAX = 60;
|
|
11
|
+
/**
|
|
12
|
+
* Operator answers, rejected emits and user messages, in order. The stage kickoff and reminder prompts are
|
|
13
|
+
* skipped: they carry no decision.
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractDirectives(messages: readonly LooseMessage[]): Directive[];
|
|
16
|
+
export declare function mergeDirectives(previous: readonly Directive[], fresh: readonly Directive[], max?: number): Directive[];
|
|
17
|
+
/** Directive-like entries first (newest up to 60% of the budget, then oldest), then the rest if room remains. */
|
|
18
|
+
export declare function renderDirectives(directives: readonly Directive[], maxChars: number): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ASK_OPERATOR_TOOL_NAME, EMIT_STAGE_ENVELOPE_TOOL_NAME } from "../tools/stageToolNames.js";
|
|
2
|
+
import { scrubSecrets } from "./scrub.js";
|
|
3
|
+
export const DIRECTIVE_MAX_CHARS = 600;
|
|
4
|
+
export const USER_DIRECTIVE_MAX_CHARS = 280;
|
|
5
|
+
export const DIRECTIVES_CARRY_MAX = 60;
|
|
6
|
+
const NOISE_USER = /^(?:Begin this stage\b|Reminder \d+ of \d+)/i;
|
|
7
|
+
const FEEDBACK_RESUME_PREFIX = /^Continue this stage after feedback-loop send-back\.\s*Incorporate the operator\/source feedback below[^\n]*\n*/i;
|
|
8
|
+
const OPERATOR_TOOLS = new Set([ASK_OPERATOR_TOOL_NAME]);
|
|
9
|
+
const ENVELOPE_TOOLS = new Set([EMIT_STAGE_ENVELOPE_TOOL_NAME]);
|
|
10
|
+
function blocksText(content) {
|
|
11
|
+
if (typeof content === "string")
|
|
12
|
+
return content;
|
|
13
|
+
if (!Array.isArray(content))
|
|
14
|
+
return "";
|
|
15
|
+
return content
|
|
16
|
+
.map((b) => (b !== null && typeof b === "object" && b.type === "text" && typeof b.text === "string" ? b.text : ""))
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join("\n");
|
|
19
|
+
}
|
|
20
|
+
function clip(text, max = DIRECTIVE_MAX_CHARS) {
|
|
21
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
22
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Operator answers, rejected emits and user messages, in order. The stage kickoff and reminder prompts are
|
|
26
|
+
* skipped: they carry no decision.
|
|
27
|
+
*/
|
|
28
|
+
export function extractDirectives(messages) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const m of messages) {
|
|
31
|
+
if (m.role === "user") {
|
|
32
|
+
const text = blocksText(m.content).trim().replace(FEEDBACK_RESUME_PREFIX, "");
|
|
33
|
+
if (!text.trim() || NOISE_USER.test(text.trim()))
|
|
34
|
+
continue;
|
|
35
|
+
out.push({ source: "user", text: clip(scrubSecrets(text), USER_DIRECTIVE_MAX_CHARS) });
|
|
36
|
+
}
|
|
37
|
+
else if (m.role === "toolResult") {
|
|
38
|
+
const name = typeof m.toolName === "string" ? m.toolName : "";
|
|
39
|
+
const text = blocksText(m.content);
|
|
40
|
+
if (!text.trim())
|
|
41
|
+
continue;
|
|
42
|
+
if (OPERATOR_TOOLS.has(name))
|
|
43
|
+
out.push({ source: "operator", text: clip(scrubSecrets(text)) });
|
|
44
|
+
else if (ENVELOPE_TOOLS.has(name) && m.isError === true)
|
|
45
|
+
out.push({ source: "rejection", text: clip(scrubSecrets(text)) });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
export function mergeDirectives(previous, fresh, max = DIRECTIVES_CARRY_MAX) {
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const merged = [];
|
|
53
|
+
for (const d of [...previous, ...fresh]) {
|
|
54
|
+
const key = `${d.source}\u0000${d.text}`;
|
|
55
|
+
if (seen.has(key))
|
|
56
|
+
continue;
|
|
57
|
+
seen.add(key);
|
|
58
|
+
merged.push(d);
|
|
59
|
+
}
|
|
60
|
+
return merged.slice(-max);
|
|
61
|
+
}
|
|
62
|
+
const LABEL = { operator: "Operator", user: "User", rejection: "Rejected" };
|
|
63
|
+
const DIRECTIVE_WORDS = /\b(?:always|never|must|should|shall|don'?t|do not|cannot|can'?t|only|prefer|require[sd]?|approved?|rejected?|decid(?:e|ed|ion)|deadline|due|before|after|until|instead|budget|limit|forbidden|allowed|use|avoid|ensure|remember|important)\b/i;
|
|
64
|
+
const SHORT_MESSAGE_CHARS = 160;
|
|
65
|
+
/** Instruction-like or short messages outrank long prose, so filler cannot crowd out a rule. */
|
|
66
|
+
function isDirectiveLike(d) {
|
|
67
|
+
if (d.source !== "user")
|
|
68
|
+
return true;
|
|
69
|
+
return d.text.length <= SHORT_MESSAGE_CHARS || DIRECTIVE_WORDS.test(d.text);
|
|
70
|
+
}
|
|
71
|
+
/** Directive-like entries first (newest up to 60% of the budget, then oldest), then the rest if room remains. */
|
|
72
|
+
export function renderDirectives(directives, maxChars) {
|
|
73
|
+
if (directives.length === 0)
|
|
74
|
+
return "";
|
|
75
|
+
const header = "## Operator and user directives (verbatim, oldest first)";
|
|
76
|
+
const lineOf = (d) => `- ${LABEL[d.source]}: ${d.text}`;
|
|
77
|
+
const picked = new Set();
|
|
78
|
+
let used = header.length;
|
|
79
|
+
const take = (i, limit) => {
|
|
80
|
+
const cost = lineOf(directives[i]).length + 1;
|
|
81
|
+
if (used + cost > limit)
|
|
82
|
+
return false;
|
|
83
|
+
picked.add(i);
|
|
84
|
+
used += cost;
|
|
85
|
+
return true;
|
|
86
|
+
};
|
|
87
|
+
const primary = directives.map((d, i) => (isDirectiveLike(d) ? i : -1)).filter((i) => i >= 0);
|
|
88
|
+
const secondary = directives.map((d, i) => (isDirectiveLike(d) ? -1 : i)).filter((i) => i >= 0);
|
|
89
|
+
const newestLimit = Math.floor(maxChars * 0.6);
|
|
90
|
+
for (let k = primary.length - 1; k >= 0; k--)
|
|
91
|
+
if (!take(primary[k], newestLimit))
|
|
92
|
+
break;
|
|
93
|
+
for (const i of primary) {
|
|
94
|
+
if (picked.has(i))
|
|
95
|
+
continue;
|
|
96
|
+
if (!take(i, maxChars))
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
for (const i of secondary) {
|
|
100
|
+
if (!take(i, maxChars))
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (picked.size === 0)
|
|
104
|
+
return "";
|
|
105
|
+
const lines = [];
|
|
106
|
+
let gap = false;
|
|
107
|
+
for (let i = 0; i < directives.length; i++) {
|
|
108
|
+
if (picked.has(i)) {
|
|
109
|
+
if (gap)
|
|
110
|
+
lines.push("- (…)");
|
|
111
|
+
gap = false;
|
|
112
|
+
lines.push(lineOf(directives[i]));
|
|
113
|
+
}
|
|
114
|
+
else if (lines.length > 0)
|
|
115
|
+
gap = true;
|
|
116
|
+
}
|
|
117
|
+
const omitted = directives.length - picked.size;
|
|
118
|
+
return [header, ...lines, ...(omitted > 0 ? [`- (${omitted} omitted for space)`] : [])].join("\n");
|
|
119
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { compact as piCompact, type ExtensionFactory } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type ClearingPolicy } from "./mask.js";
|
|
3
|
+
export type CompactionPolicy = {
|
|
4
|
+
/** Clear stale re-fetchable tool results in the text the summarizer reads. */
|
|
5
|
+
clearInSummary?: ClearingPolicy;
|
|
6
|
+
/** Clear stale re-fetchable tool results in every request once the window is this full (0-1). */
|
|
7
|
+
preemptive?: {
|
|
8
|
+
usageFraction: number;
|
|
9
|
+
policy: ClearingPolicy;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare const DEFAULT_COMPACTION_POLICY: CompactionPolicy;
|
|
13
|
+
type CompactFn = typeof piCompact;
|
|
14
|
+
export type CompactionExtensionOptions = {
|
|
15
|
+
policy?: CompactionPolicy;
|
|
16
|
+
compact?: CompactFn;
|
|
17
|
+
onWarn?: (message: string) => void;
|
|
18
|
+
};
|
|
19
|
+
export declare function createCompactionExtension(options?: CompactionExtensionOptions): ExtensionFactory;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { compact as piCompact, } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { applyClearing, planClearing } from "./mask.js";
|
|
3
|
+
import { COMPACTION_FOCUS, composeCompactionSummary, readStageflowDetails, stageflowDetails, } from "./summarize.js";
|
|
4
|
+
import { getWorkingStateSections } from "./workingState.js";
|
|
5
|
+
export const DEFAULT_COMPACTION_POLICY = {};
|
|
6
|
+
function lastCompactionDetails(branch) {
|
|
7
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
8
|
+
const e = branch[i];
|
|
9
|
+
if (e?.type === "compaction")
|
|
10
|
+
return readStageflowDetails(e.details);
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function createCompactionExtension(options = {}) {
|
|
15
|
+
const policy = options.policy ?? DEFAULT_COMPACTION_POLICY;
|
|
16
|
+
const compact = options.compact ?? piCompact;
|
|
17
|
+
const warn = options.onWarn ?? (() => { });
|
|
18
|
+
return (pi) => {
|
|
19
|
+
pi.on("session_before_compact", async (event, rawCtx) => {
|
|
20
|
+
const ctx = rawCtx;
|
|
21
|
+
const model = ctx.model;
|
|
22
|
+
if (!model)
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
26
|
+
if (!auth.ok)
|
|
27
|
+
return undefined;
|
|
28
|
+
const { preparation } = event;
|
|
29
|
+
const previousDetails = lastCompactionDetails(event.branchEntries);
|
|
30
|
+
const historyCount = preparation.messagesToSummarize.length;
|
|
31
|
+
const composed = await composeCompactionSummary({
|
|
32
|
+
messages: [...preparation.messagesToSummarize, ...preparation.turnPrefixMessages],
|
|
33
|
+
...(preparation.previousSummary !== undefined ? { previousSummary: preparation.previousSummary } : {}),
|
|
34
|
+
...(previousDetails ? { previousDetails } : {}),
|
|
35
|
+
state: getWorkingStateSections(ctx.sessionManager.getEntries()),
|
|
36
|
+
...(policy.clearInSummary ? { clearInSummary: policy.clearInSummary } : {}),
|
|
37
|
+
summarize: ({ messages, previousSummary }) => compact({
|
|
38
|
+
...preparation,
|
|
39
|
+
messagesToSummarize: messages.slice(0, historyCount),
|
|
40
|
+
turnPrefixMessages: messages.slice(historyCount),
|
|
41
|
+
...(previousSummary !== undefined ? { previousSummary } : {}),
|
|
42
|
+
}, model, auth.apiKey, auth.headers, COMPACTION_FOCUS, event.signal, ctx.thinkingLevel, undefined, auth.env),
|
|
43
|
+
});
|
|
44
|
+
const { result, prepared } = composed;
|
|
45
|
+
return {
|
|
46
|
+
compaction: {
|
|
47
|
+
summary: composed.summary,
|
|
48
|
+
firstKeptEntryId: result.firstKeptEntryId,
|
|
49
|
+
tokensBefore: result.tokensBefore,
|
|
50
|
+
...(result.usage ? { usage: result.usage } : {}),
|
|
51
|
+
details: stageflowDetails(result.details, previousDetails, prepared.pinned, prepared.cleared, prepared.directives),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (event.signal.aborted)
|
|
57
|
+
throw err;
|
|
58
|
+
warn(`stageflow compaction fell back to Pi default: ${err instanceof Error ? err.message : String(err)}`);
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const preemptive = policy.preemptive;
|
|
63
|
+
if (preemptive) {
|
|
64
|
+
pi.on("context", async (event, rawCtx) => {
|
|
65
|
+
const ctx = rawCtx;
|
|
66
|
+
const usage = ctx.getContextUsage();
|
|
67
|
+
if (!usage || usage.tokens === null || usage.contextWindow <= 0)
|
|
68
|
+
return undefined;
|
|
69
|
+
if (usage.tokens / usage.contextWindow < preemptive.usageFraction)
|
|
70
|
+
return undefined;
|
|
71
|
+
const plan = planClearing(event.messages, preemptive.policy);
|
|
72
|
+
if (plan.cleared.length === 0)
|
|
73
|
+
return undefined;
|
|
74
|
+
return { messages: applyClearing(event.messages, plan) };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LooseMessage } from "./pin.js";
|
|
2
|
+
import type { ClearedResult } from "./types.js";
|
|
3
|
+
export type ClearingPolicy = {
|
|
4
|
+
/** Never touch results at or after the Nth assistant turn from the end. */
|
|
5
|
+
keepRecentTurns: number;
|
|
6
|
+
/** Ignore results smaller than this many UTF-8 bytes. */
|
|
7
|
+
minBytes: number;
|
|
8
|
+
/** Overrides the default recover hint when it returns a non-empty string. */
|
|
9
|
+
recoverHintFor?: (message: LooseMessage) => string | undefined;
|
|
10
|
+
};
|
|
11
|
+
export type ClearingPlan = {
|
|
12
|
+
cleared: ClearedResult[];
|
|
13
|
+
clearIds: Set<string>;
|
|
14
|
+
};
|
|
15
|
+
export declare function planClearing(messages: readonly LooseMessage[], policy: ClearingPolicy): ClearingPlan;
|
|
16
|
+
export declare function placeholderFor(item: ClearedResult): string;
|
|
17
|
+
export declare function applyClearing<T extends LooseMessage>(messages: readonly T[], plan: ClearingPlan): T[];
|
|
18
|
+
export declare function renderClearedIndex(cleared: readonly ClearedResult[], maxChars: number): string;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { scrubSecrets } from "./scrub.js";
|
|
2
|
+
import { classifyForClearing } from "./toolPolicy.js";
|
|
3
|
+
const PLACEHOLDER_PREFIX = "[Tool result cleared to save context:";
|
|
4
|
+
const DESCRIPTOR_MAX = 100;
|
|
5
|
+
function record(value) {
|
|
6
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
function resultText(message) {
|
|
9
|
+
const content = message.content;
|
|
10
|
+
if (typeof content === "string")
|
|
11
|
+
return content;
|
|
12
|
+
if (!Array.isArray(content))
|
|
13
|
+
return "";
|
|
14
|
+
let out = "";
|
|
15
|
+
for (const block of content) {
|
|
16
|
+
const b = record(block);
|
|
17
|
+
if (b?.type === "text" && typeof b.text === "string")
|
|
18
|
+
out += (out ? "\n" : "") + b.text;
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
function oneLine(text, max) {
|
|
23
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
24
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
25
|
+
}
|
|
26
|
+
function findToolCall(messages, before, toolCallId) {
|
|
27
|
+
for (let i = before - 1; i >= 0; i--) {
|
|
28
|
+
const m = messages[i];
|
|
29
|
+
if (m.role !== "assistant" || !Array.isArray(m.content))
|
|
30
|
+
continue;
|
|
31
|
+
for (const block of m.content) {
|
|
32
|
+
const b = record(block);
|
|
33
|
+
if (b?.type === "toolCall" && b.id === toolCallId)
|
|
34
|
+
return b;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
function callArgs(call) {
|
|
40
|
+
return record(call?.arguments) ?? {};
|
|
41
|
+
}
|
|
42
|
+
function describe(toolName, args) {
|
|
43
|
+
const pathArg = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : undefined;
|
|
44
|
+
if (pathArg)
|
|
45
|
+
return oneLine(`${toolName} ${pathArg}`, DESCRIPTOR_MAX);
|
|
46
|
+
if (typeof args.command === "string")
|
|
47
|
+
return oneLine(`${toolName}: ${args.command}`, DESCRIPTOR_MAX);
|
|
48
|
+
if (typeof args.pattern === "string")
|
|
49
|
+
return oneLine(`${toolName} ${args.pattern}${typeof args.path === "string" ? ` in ${args.path}` : ""}`, DESCRIPTOR_MAX);
|
|
50
|
+
const keys = Object.keys(args);
|
|
51
|
+
if (keys.length === 0)
|
|
52
|
+
return toolName;
|
|
53
|
+
let json = "";
|
|
54
|
+
try {
|
|
55
|
+
json = JSON.stringify(args);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
json = "";
|
|
59
|
+
}
|
|
60
|
+
return oneLine(json ? `${toolName} ${json}` : toolName, DESCRIPTOR_MAX);
|
|
61
|
+
}
|
|
62
|
+
function defaultHint(message, toolName, args) {
|
|
63
|
+
const spill = record(record(message.details)?.stageflowToolOutput);
|
|
64
|
+
const spillPath = typeof spill?.path === "string" ? spill.path : undefined;
|
|
65
|
+
if (spillPath)
|
|
66
|
+
return `read ${spillPath}`;
|
|
67
|
+
if (toolName === "read") {
|
|
68
|
+
const p = typeof args.path === "string" ? args.path : typeof args.file_path === "string" ? args.file_path : undefined;
|
|
69
|
+
if (p)
|
|
70
|
+
return `read ${p} (offset/limit as before)`;
|
|
71
|
+
}
|
|
72
|
+
return `re-run ${toolName} with the same arguments`;
|
|
73
|
+
}
|
|
74
|
+
function isPlaceholder(text) {
|
|
75
|
+
return text.startsWith(PLACEHOLDER_PREFIX);
|
|
76
|
+
}
|
|
77
|
+
export function planClearing(messages, policy) {
|
|
78
|
+
const cleared = [];
|
|
79
|
+
const clearIds = new Set();
|
|
80
|
+
let boundary = messages.length;
|
|
81
|
+
if (policy.keepRecentTurns > 0) {
|
|
82
|
+
let seen = 0;
|
|
83
|
+
boundary = 0;
|
|
84
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
85
|
+
if (messages[i].role === "assistant" && ++seen === policy.keepRecentTurns) {
|
|
86
|
+
boundary = i;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < boundary; i++) {
|
|
92
|
+
const m = messages[i];
|
|
93
|
+
if (m.role !== "toolResult")
|
|
94
|
+
continue;
|
|
95
|
+
const toolCallId = typeof m.toolCallId === "string" ? m.toolCallId : undefined;
|
|
96
|
+
const toolName = typeof m.toolName === "string" ? m.toolName : undefined;
|
|
97
|
+
if (!toolCallId || !toolName || clearIds.has(toolCallId))
|
|
98
|
+
continue;
|
|
99
|
+
if (classifyForClearing(toolName, m.isError === true, m.details) !== "clearable")
|
|
100
|
+
continue;
|
|
101
|
+
const text = resultText(m);
|
|
102
|
+
if (isPlaceholder(text))
|
|
103
|
+
continue;
|
|
104
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
105
|
+
if (bytes < policy.minBytes)
|
|
106
|
+
continue;
|
|
107
|
+
const args = callArgs(findToolCall(messages, i, toolCallId));
|
|
108
|
+
const custom = policy.recoverHintFor?.(m);
|
|
109
|
+
cleared.push({
|
|
110
|
+
toolName,
|
|
111
|
+
toolCallId,
|
|
112
|
+
bytes,
|
|
113
|
+
descriptor: scrubSecrets(describe(toolName, args)),
|
|
114
|
+
recoverHint: scrubSecrets(custom && custom.trim().length > 0 ? oneLine(custom, 300) : defaultHint(m, toolName, args)),
|
|
115
|
+
});
|
|
116
|
+
clearIds.add(toolCallId);
|
|
117
|
+
}
|
|
118
|
+
return { cleared, clearIds };
|
|
119
|
+
}
|
|
120
|
+
export function placeholderFor(item) {
|
|
121
|
+
return `${PLACEHOLDER_PREFIX} ${item.descriptor}, ${item.bytes} bytes. To get it again: ${item.recoverHint}]`;
|
|
122
|
+
}
|
|
123
|
+
export function applyClearing(messages, plan) {
|
|
124
|
+
if (plan.clearIds.size === 0)
|
|
125
|
+
return [...messages];
|
|
126
|
+
const byId = new Map(plan.cleared.map((c) => [c.toolCallId, c]));
|
|
127
|
+
return messages.map((m) => {
|
|
128
|
+
if (m.role !== "toolResult" || typeof m.toolCallId !== "string" || !plan.clearIds.has(m.toolCallId))
|
|
129
|
+
return m;
|
|
130
|
+
const item = byId.get(m.toolCallId);
|
|
131
|
+
if (!item || isPlaceholder(resultText(m)))
|
|
132
|
+
return m;
|
|
133
|
+
return { ...m, content: [{ type: "text", text: placeholderFor(item) }] };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export function renderClearedIndex(cleared, maxChars) {
|
|
137
|
+
const title = "## Cleared tool results (re-fetch if needed)";
|
|
138
|
+
const omitted = (n) => `- (${n} more omitted)`;
|
|
139
|
+
if (cleared.length === 0)
|
|
140
|
+
return title;
|
|
141
|
+
const reserve = omitted(cleared.length).length + 1;
|
|
142
|
+
const lines = [];
|
|
143
|
+
let used = title.length;
|
|
144
|
+
for (const item of cleared) {
|
|
145
|
+
const line = `- ${item.descriptor} (${item.bytes} bytes) — ${oneLine(item.recoverHint, 200)}`;
|
|
146
|
+
if (used + line.length + 1 + reserve > maxChars)
|
|
147
|
+
break;
|
|
148
|
+
lines.push(line);
|
|
149
|
+
used += line.length + 1;
|
|
150
|
+
}
|
|
151
|
+
const out = [title, ...lines];
|
|
152
|
+
if (lines.length < cleared.length)
|
|
153
|
+
out.push(omitted(cleared.length - lines.length));
|
|
154
|
+
return out.join("\n").slice(0, Math.max(maxChars, 0));
|
|
155
|
+
}
|