stageflow 0.42.0 → 0.46.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/evalGate.js +1 -0
- 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/cli/evalCommand.d.ts +1 -1
- package/dist/cli/evalCommand.js +39 -2
- package/dist/cli/hostClient.d.ts +6 -0
- package/dist/cli/hostClient.js +4 -0
- 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.d.ts +3 -1
- package/dist/config/loadEval.js +96 -10
- 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 +60 -3
- 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/calibration.d.ts +21 -0
- package/dist/evals/calibration.js +69 -0
- package/dist/evals/calibrationSuggest.d.ts +29 -0
- package/dist/evals/calibrationSuggest.js +72 -0
- package/dist/evals/calibrator.d.ts +46 -0
- package/dist/evals/calibrator.js +148 -0
- package/dist/evals/compare.d.ts +1 -1
- package/dist/evals/compare.js +3 -0
- package/dist/evals/constants.d.ts +7 -3
- package/dist/evals/constants.js +7 -1
- package/dist/evals/engine/FakeEngine.d.ts +2 -1
- package/dist/evals/engine/FakeEngine.js +9 -3
- package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +1 -1
- package/dist/evals/engine/promptfoo/PromptfooEngine.js +4 -2
- package/dist/evals/engine/promptfoo/generate.d.ts +3 -1
- package/dist/evals/engine/promptfoo/generate.js +43 -23
- package/dist/evals/engine/promptfoo/judgeProviders.js +2 -1
- package/dist/evals/engine/promptfoo/normalize.d.ts +4 -1
- package/dist/evals/engine/promptfoo/normalize.js +16 -3
- package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +3 -1
- package/dist/evals/engine/promptfoo/rubricPrompt.js +15 -5
- package/dist/evals/errors.d.ts +7 -0
- package/dist/evals/errors.js +10 -0
- package/dist/evals/judgeProviders.d.ts +6 -0
- package/dist/evals/judgeProviders.js +14 -0
- package/dist/evals/judged.d.ts +7 -0
- package/dist/evals/judged.js +7 -0
- package/dist/evals/manager.d.ts +27 -8
- package/dist/evals/manager.js +73 -12
- package/dist/evals/plan.d.ts +10 -1
- package/dist/evals/plan.js +33 -13
- package/dist/evals/planInternalAgent.d.ts +19 -0
- package/dist/evals/planInternalAgent.js +201 -0
- package/dist/evals/read/api.d.ts +18 -0
- package/dist/evals/read/api.js +10 -1
- package/dist/evals/read/labelQueue.d.ts +45 -0
- package/dist/evals/read/labelQueue.js +83 -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/stats.d.ts +40 -0
- package/dist/evals/stats.js +69 -0
- package/dist/evals/storeTypes.d.ts +42 -0
- package/dist/evals/types.d.ts +25 -2
- package/dist/evals/types.js +4 -1
- package/dist/evals/votes.d.ts +10 -0
- package/dist/evals/votes.js +60 -0
- 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 +723 -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 +133 -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/port.d.ts +13 -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 +11 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +70 -45
- package/dist/runstore/sqlite/calibrationStore.d.ts +10 -0
- package/dist/runstore/sqlite/calibrationStore.js +106 -0
- package/dist/runstore/sqlite/evalStore.d.ts +5 -0
- package/dist/runstore/sqlite/evalStore.js +7 -0
- package/dist/runstore/sqlite/migrations/018-eval-grader-labels.d.ts +11 -0
- package/dist/runstore/sqlite/migrations/018-eval-grader-labels.js +43 -0
- package/dist/runstore/sqlite/migrations/index.js +2 -0
- 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 +251 -12
- 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 +41 -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-BS2BBbCx.js} +2 -2
- package/dist/ui/assets/adapters-DPD-cAsy.js +17 -0
- package/dist/ui/assets/chat-BD1-VTT6.js +2 -0
- package/dist/ui/assets/chat-DXnQv9st.css +2 -0
- package/dist/ui/assets/main-AX5XNLtX.js +278 -0
- package/dist/ui/assets/{main-CHlmTNJe.css → main-CFX0_n8J.css} +1 -1
- 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/skills/stageflow/references/control-surface.md +1 -1
- package/skills/stageflow/references/evals.md +1 -0
- package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +0 -9
- package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +0 -6
- package/dist/ui/assets/chat-Df_wCpak.js +0 -5
- package/dist/ui/assets/chat-P7KQznHm.css +0 -2
- package/dist/ui/assets/main-CvVfxmMd.js +0 -271
|
@@ -0,0 +1,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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PinnedFact } from "./types.js";
|
|
2
|
+
/** Minimal message shape (Pi session messages); only role and content are read. */
|
|
3
|
+
export type LooseMessage = {
|
|
4
|
+
role?: unknown;
|
|
5
|
+
content?: unknown;
|
|
6
|
+
toolName?: unknown;
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
export type RankedPinnedFact = PinnedFact & {
|
|
10
|
+
count: number;
|
|
11
|
+
score: number;
|
|
12
|
+
};
|
|
13
|
+
export type ExtractPinnedOptions = {
|
|
14
|
+
maxFactsPerMessage?: number;
|
|
15
|
+
maxFacts?: number;
|
|
16
|
+
maxLinesPerMessage?: number;
|
|
17
|
+
maxLineChars?: number;
|
|
18
|
+
noiseThreshold?: number;
|
|
19
|
+
};
|
|
20
|
+
export declare function extractPinnedFacts(messages: readonly LooseMessage[], options?: ExtractPinnedOptions): RankedPinnedFact[];
|
|
21
|
+
export declare function renderPinned(facts: readonly PinnedFact[], maxChars: number): string;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { scrubSecrets, SECRET_KEY_RE } from "./scrub.js";
|
|
2
|
+
const DEFAULTS = {
|
|
3
|
+
maxFactsPerMessage: 300,
|
|
4
|
+
maxFacts: 1500,
|
|
5
|
+
maxLinesPerMessage: 20_000,
|
|
6
|
+
maxLineChars: 2000,
|
|
7
|
+
noiseThreshold: 20,
|
|
8
|
+
};
|
|
9
|
+
const MAX_VALUE_CHARS = 200;
|
|
10
|
+
const MAX_CONTEXT_CHARS = 140;
|
|
11
|
+
const MAX_FACTS_PER_LINE = 12;
|
|
12
|
+
const BASE_SCORE = {
|
|
13
|
+
error: 100,
|
|
14
|
+
id: 60,
|
|
15
|
+
number: 40,
|
|
16
|
+
url: 35,
|
|
17
|
+
timestamp: 28,
|
|
18
|
+
path: 26,
|
|
19
|
+
other: 18,
|
|
20
|
+
};
|
|
21
|
+
const KIND_ORDER = ["error", "id", "number", "timestamp", "url", "path", "other"];
|
|
22
|
+
const KIND_TITLE = {
|
|
23
|
+
error: "Errors",
|
|
24
|
+
id: "Identifiers",
|
|
25
|
+
number: "Numbers",
|
|
26
|
+
timestamp: "Timestamps",
|
|
27
|
+
url: "URLs",
|
|
28
|
+
path: "Paths",
|
|
29
|
+
other: "Other",
|
|
30
|
+
};
|
|
31
|
+
const ERROR_RE = /\b(?:errors?|fail(?:ed|s|ure|ures|ing)?|exceptions?|denied|time[d ]?-?outs?|fatal|panic|traceback|refused|unauthori[sz]ed|forbidden|exit(?:ed)?(?: with)? (?:code|status):? ?[1-9]\d*|exit code:? ?[1-9]\d*|status:? ?[45]\d\d|HTTP\/\d(?:\.\d)? [45]\d\d|\b[45]\d\d (?:error|bad|not|internal|service|gateway|forbidden|unauthorized))\b/i;
|
|
32
|
+
const URL_RE = /https?:\/\/[^\s"'<>`)\]},]+/g;
|
|
33
|
+
const ISO_TS_RE = /\b\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?/g;
|
|
34
|
+
const TIME_RE = /(?<![\d:.])\d{1,2}:\d{2}(?::\d{2})?\s?(?:[AP]M|UTC|GMT)\b|(?<![\d:.\-T])\d{2}:\d{2}:\d{2}(?:\.\d+)?(?![\d:])/gi;
|
|
35
|
+
const UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
|
|
36
|
+
const TICKET_RE = /\b[A-Z][A-Z0-9]{1,9}-\d{1,7}\b/g;
|
|
37
|
+
const TICKET_STOP = new Set(["UTF", "SHA", "ISO", "MD", "AES", "RSA", "HTTP", "TLS", "SSL", "CVE", "RFC", "X", "IPV", "TCP", "UDP", "SSE", "GPT", "COVID"]);
|
|
38
|
+
const PR_RE = /\b(?:PR|pull request|pull|issue|MR)\s*#?\d{1,7}\b|(?<![\w&/#])#\d{2,7}\b/gi;
|
|
39
|
+
const HASH_RE = /\b(?:build|commit|sha\d*|rev|revision|hash|digest|checksum|image|release|tag)\b[^\n]{0,24}?\b([0-9a-f]{7,40})\b/gi;
|
|
40
|
+
const PATH_ABS_RE = /(?<![\w:/.])\/(?:[\w.@+~-]+\/)+[\w.@+~-]+/g;
|
|
41
|
+
const PATH_REL_RE = /(?<![\w:/.@])(?:\.{1,2}\/)?(?:[\w.@+~-]+\/)+[\w.@+~-]*\.[A-Za-z0-9]{1,8}\b/g;
|
|
42
|
+
const KV_EQ_RE = /\b([A-Za-z_][\w.-]{1,40})=("[^"\n]{1,80}"|'[^'\n]{1,80}'|[^\s,;&)}\]"']{1,80})/g;
|
|
43
|
+
const KV_JSON_RE = /"([A-Za-z_][\w.-]{1,40})"\s*:\s*("[^"\n]{1,80}"|-?\d[\d.eE+-]*|true|false|null)/g;
|
|
44
|
+
const KV_COLON_NUM_RE = /\b([A-Za-z_][\w.-]{1,40}):\s+(-?\d[\d,]*(?:\.\d+)?%?)(?![\d:])/g;
|
|
45
|
+
const UNIT_RE = /(?<![\w.:-])\d[\d,]*(?:\.\d+)?\s?(?:ms|sec|secs|seconds|minutes|mins|hours|days|KB|MB|GB|TB|bytes|requests|reqs|errors|failures|users|orders|rows|records|retries|events|messages|items|files|tests|connections|jobs|calls|shards)\b/gi;
|
|
46
|
+
const CURRENCY_RE = /(?<![\w])[$€£]\s?\d[\d,]*(?:\.\d+)?(?:[kKmMbB]\b)?|\b\d[\d,]*(?:\.\d+)?\s?(?:USD|EUR|GBP)\b/g;
|
|
47
|
+
const PERCENT_RE = /(?<![\w.])\d+(?:\.\d+)?%/g;
|
|
48
|
+
const ID_KEY_RE = /(?:^|[_.-])(?:id|uuid|key|ref|no|number|num|sha|hash|rev|build|ticket|incident|order|user|account|customer|request|trace)$/i;
|
|
49
|
+
function messageText(message) {
|
|
50
|
+
const role = message.role;
|
|
51
|
+
if (role !== "user" && role !== "toolResult")
|
|
52
|
+
return undefined;
|
|
53
|
+
const content = message.content;
|
|
54
|
+
if (typeof content === "string")
|
|
55
|
+
return content;
|
|
56
|
+
if (!Array.isArray(content))
|
|
57
|
+
return undefined;
|
|
58
|
+
const parts = [];
|
|
59
|
+
for (const block of content) {
|
|
60
|
+
if (block !== null && typeof block === "object" && block.type === "text") {
|
|
61
|
+
const text = block.text;
|
|
62
|
+
if (typeof text === "string")
|
|
63
|
+
parts.push(text);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return parts.join("\n");
|
|
67
|
+
}
|
|
68
|
+
function lineTemplate(line) {
|
|
69
|
+
return line
|
|
70
|
+
.replace(/\b[0-9a-f]{6,}\b/gi, "#")
|
|
71
|
+
.replace(/\d+/g, "#")
|
|
72
|
+
.replace(/"[^"]*"/g, '"#"')
|
|
73
|
+
.replace(/\s+/g, " ")
|
|
74
|
+
.trim()
|
|
75
|
+
.slice(0, 100);
|
|
76
|
+
}
|
|
77
|
+
function singleLine(text) {
|
|
78
|
+
return text.replace(/\s+/g, " ").trim();
|
|
79
|
+
}
|
|
80
|
+
function contextAround(line, index, length) {
|
|
81
|
+
const flat = line.length > 400 ? line.slice(Math.max(0, index - 200), index + length + 200) : line;
|
|
82
|
+
const offset = line.length > 400 ? Math.min(index, 200) : index;
|
|
83
|
+
const start = Math.max(0, offset - 50);
|
|
84
|
+
const end = Math.min(flat.length, offset + length + 60);
|
|
85
|
+
let out = singleLine(flat.slice(start, end));
|
|
86
|
+
if (out.length > MAX_CONTEXT_CHARS)
|
|
87
|
+
out = out.slice(0, MAX_CONTEXT_CHARS);
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function numericLike(value) {
|
|
91
|
+
return /^-?\d[\d,]*(?:\.\d+)?(?:[eE][+-]?\d+)?%?$/.test(value);
|
|
92
|
+
}
|
|
93
|
+
function collectCandidates(line, isErrorLine, keyFreq, noiseThreshold) {
|
|
94
|
+
const out = [];
|
|
95
|
+
const claimed = [];
|
|
96
|
+
const free = (start, end) => !claimed.some(([s, e]) => start < e && end > s);
|
|
97
|
+
const claim = (start, end) => claimed.push([start, end]);
|
|
98
|
+
const run = (re, on, takeSpan = true) => {
|
|
99
|
+
re.lastIndex = 0;
|
|
100
|
+
let m;
|
|
101
|
+
let guard = 0;
|
|
102
|
+
while ((m = re.exec(line)) !== null && guard++ < 50) {
|
|
103
|
+
if (m[0].length === 0) {
|
|
104
|
+
re.lastIndex++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const start = m.index;
|
|
108
|
+
const end = start + m[0].length;
|
|
109
|
+
if (!free(start, end))
|
|
110
|
+
continue;
|
|
111
|
+
const cand = on(m);
|
|
112
|
+
if (cand) {
|
|
113
|
+
out.push(cand);
|
|
114
|
+
if (takeSpan)
|
|
115
|
+
claim(start, end);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
run(URL_RE, (m) => ({ kind: "url", value: m[0].replace(/[.;:!?]+$/, ""), index: m.index }));
|
|
120
|
+
run(ISO_TS_RE, (m) => ({ kind: "timestamp", value: m[0], index: m.index }));
|
|
121
|
+
run(UUID_RE, (m) => ({ kind: "id", value: m[0], index: m.index }));
|
|
122
|
+
run(PR_RE, (m) => ({ kind: "id", value: m[0].replace(/\s+/g, " "), index: m.index }));
|
|
123
|
+
run(TICKET_RE, (m) => {
|
|
124
|
+
const prefix = m[0].slice(0, m[0].indexOf("-"));
|
|
125
|
+
if (TICKET_STOP.has(prefix))
|
|
126
|
+
return undefined;
|
|
127
|
+
return { kind: "id", value: m[0], index: m.index };
|
|
128
|
+
});
|
|
129
|
+
run(HASH_RE, (m) => {
|
|
130
|
+
const hex = m[1];
|
|
131
|
+
if (!/\d/.test(hex) || !/[a-f]/i.test(hex))
|
|
132
|
+
return undefined;
|
|
133
|
+
return { kind: "id", value: hex, index: m.index + m[0].length - hex.length };
|
|
134
|
+
}, false);
|
|
135
|
+
run(TIME_RE, (m) => ({ kind: "timestamp", value: m[0].trim(), index: m.index }));
|
|
136
|
+
run(PATH_ABS_RE, (m) => (m[0].length >= 5 ? { kind: "path", value: m[0], index: m.index } : undefined));
|
|
137
|
+
run(PATH_REL_RE, (m) => ({ kind: "path", value: m[0], index: m.index }));
|
|
138
|
+
const kv = (key, raw, index) => {
|
|
139
|
+
if (SECRET_KEY_RE.test(key))
|
|
140
|
+
return undefined;
|
|
141
|
+
const value = raw.replace(/^["']|["']$/g, "");
|
|
142
|
+
if (value.length === 0 || value === "null" || value === "true" || value === "false")
|
|
143
|
+
return undefined;
|
|
144
|
+
const common = (keyFreq.get(key) ?? 0) > noiseThreshold;
|
|
145
|
+
const numeric = numericLike(value);
|
|
146
|
+
const kind = ID_KEY_RE.test(key) && !numeric ? "id" : ID_KEY_RE.test(key) ? "id" : numeric ? "number" : "other";
|
|
147
|
+
return { kind, value: `${key}=${value}`, index, weight: common ? -25 : 0 };
|
|
148
|
+
};
|
|
149
|
+
run(KV_EQ_RE, (m) => kv(m[1], m[2], m.index));
|
|
150
|
+
run(KV_JSON_RE, (m) => kv(m[1], m[2], m.index));
|
|
151
|
+
run(KV_COLON_NUM_RE, (m) => kv(m[1], m[2], m.index));
|
|
152
|
+
run(CURRENCY_RE, (m) => ({ kind: "number", value: m[0].trim(), index: m.index }));
|
|
153
|
+
run(PERCENT_RE, (m) => ({ kind: "number", value: m[0], index: m.index }));
|
|
154
|
+
run(UNIT_RE, (m) => ({ kind: "number", value: m[0].trim(), index: m.index }));
|
|
155
|
+
if (isErrorLine) {
|
|
156
|
+
const value = singleLine(line);
|
|
157
|
+
out.push({ kind: "error", value: value.length > MAX_VALUE_CHARS ? value.slice(0, MAX_VALUE_CHARS) : value, index: 0 });
|
|
158
|
+
}
|
|
159
|
+
return out.slice(0, MAX_FACTS_PER_LINE + (isErrorLine ? 1 : 0));
|
|
160
|
+
}
|
|
161
|
+
export function extractPinnedFacts(messages, options = {}) {
|
|
162
|
+
const cfg = { ...DEFAULTS, ...options };
|
|
163
|
+
const facts = new Map();
|
|
164
|
+
let order = 0;
|
|
165
|
+
messages.forEach((message, messageIndex) => {
|
|
166
|
+
const text = messageText(message);
|
|
167
|
+
if (!text)
|
|
168
|
+
return;
|
|
169
|
+
const isUser = message.role === "user";
|
|
170
|
+
const toolName = !isUser && typeof message.toolName === "string" ? message.toolName : undefined;
|
|
171
|
+
const lines = text.split("\n", cfg.maxLinesPerMessage + 1).slice(0, cfg.maxLinesPerMessage);
|
|
172
|
+
const templates = new Map();
|
|
173
|
+
const keyFreq = new Map();
|
|
174
|
+
const trimmed = [];
|
|
175
|
+
for (const raw of lines) {
|
|
176
|
+
const line = raw.length > cfg.maxLineChars ? raw.slice(0, cfg.maxLineChars) : raw;
|
|
177
|
+
trimmed.push(line);
|
|
178
|
+
if (line.trim().length === 0)
|
|
179
|
+
continue;
|
|
180
|
+
const tpl = lineTemplate(line);
|
|
181
|
+
templates.set(tpl, (templates.get(tpl) ?? 0) + 1);
|
|
182
|
+
if (!isUser) {
|
|
183
|
+
KV_EQ_RE.lastIndex = 0;
|
|
184
|
+
let m;
|
|
185
|
+
let g = 0;
|
|
186
|
+
while ((m = KV_EQ_RE.exec(line)) !== null && g++ < 8)
|
|
187
|
+
keyFreq.set(m[1], (keyFreq.get(m[1]) ?? 0) + 1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
let messageFacts = 0;
|
|
191
|
+
for (const line of trimmed) {
|
|
192
|
+
if (messageFacts >= cfg.maxFactsPerMessage)
|
|
193
|
+
break;
|
|
194
|
+
if (line.trim().length === 0)
|
|
195
|
+
continue;
|
|
196
|
+
const errorLine = ERROR_RE.test(line);
|
|
197
|
+
const repeats = templates.get(lineTemplate(line)) ?? 1;
|
|
198
|
+
if (!isUser && !errorLine && repeats > cfg.noiseThreshold)
|
|
199
|
+
continue;
|
|
200
|
+
const rarity = errorLine ? 0 : repeats > 1 ? -Math.min(15, Math.log2(repeats) * 3) : 0;
|
|
201
|
+
for (const cand of collectCandidates(line, errorLine, keyFreq, cfg.noiseThreshold)) {
|
|
202
|
+
const value = cand.value.length > MAX_VALUE_CHARS ? cand.value.slice(0, MAX_VALUE_CHARS) : cand.value;
|
|
203
|
+
if (scrubSecrets(value) !== value)
|
|
204
|
+
continue;
|
|
205
|
+
const key = `${cand.kind}\u0000${value}`;
|
|
206
|
+
const existing = facts.get(key);
|
|
207
|
+
if (existing) {
|
|
208
|
+
existing.count++;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (facts.size >= cfg.maxFacts * 2)
|
|
212
|
+
continue;
|
|
213
|
+
const context = scrubSecrets(contextAround(line, cand.index, value.length));
|
|
214
|
+
const score = BASE_SCORE[cand.kind] + (isUser ? 15 : 0) + rarity + (cand.weight ?? 0);
|
|
215
|
+
facts.set(key, {
|
|
216
|
+
kind: cand.kind,
|
|
217
|
+
value,
|
|
218
|
+
context,
|
|
219
|
+
source: toolName ? { messageIndex, toolName } : { messageIndex },
|
|
220
|
+
count: 1,
|
|
221
|
+
score,
|
|
222
|
+
order: order++,
|
|
223
|
+
});
|
|
224
|
+
messageFacts++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const ranked = [...facts.values()].map((fact) => ({
|
|
229
|
+
...fact,
|
|
230
|
+
score: fact.score - Math.min(12, Math.log2(fact.count) * 3),
|
|
231
|
+
}));
|
|
232
|
+
ranked.sort((a, b) => b.score - a.score || a.order - b.order);
|
|
233
|
+
return ranked.slice(0, cfg.maxFacts).map(({ order: _order, ...fact }) => fact);
|
|
234
|
+
}
|
|
235
|
+
export function renderPinned(facts, maxChars) {
|
|
236
|
+
const title = "## Pinned facts";
|
|
237
|
+
const omittedLine = (n) => `- (${n} more omitted)`;
|
|
238
|
+
const base = `${title}\n${omittedLine(facts.length)}`;
|
|
239
|
+
if (maxChars < base.length)
|
|
240
|
+
return base.slice(0, Math.max(0, maxChars)).replace(/\n[^\n]*$/, "").slice(0, Math.max(0, maxChars));
|
|
241
|
+
const chosen = new Map();
|
|
242
|
+
let used = title.length + 1;
|
|
243
|
+
let included = 0;
|
|
244
|
+
const reserve = omittedLine(facts.length).length + 1;
|
|
245
|
+
for (const fact of facts) {
|
|
246
|
+
const value = singleLine(scrubSecrets(fact.value));
|
|
247
|
+
const context = singleLine(scrubSecrets(fact.context));
|
|
248
|
+
if (value.length === 0)
|
|
249
|
+
continue;
|
|
250
|
+
const line = context.length > 0 && context !== value ? `- ${value} — ${context}` : `- ${value}`;
|
|
251
|
+
const headerCost = chosen.has(fact.kind) ? 0 : `### ${KIND_TITLE[fact.kind]}`.length + 1;
|
|
252
|
+
const cost = headerCost + line.length + 1;
|
|
253
|
+
if (used + cost + reserve > maxChars)
|
|
254
|
+
continue;
|
|
255
|
+
const bucket = chosen.get(fact.kind) ?? [];
|
|
256
|
+
bucket.push(line);
|
|
257
|
+
chosen.set(fact.kind, bucket);
|
|
258
|
+
used += cost;
|
|
259
|
+
included++;
|
|
260
|
+
}
|
|
261
|
+
const out = [title];
|
|
262
|
+
for (const kind of KIND_ORDER) {
|
|
263
|
+
const bucket = chosen.get(kind);
|
|
264
|
+
if (!bucket)
|
|
265
|
+
continue;
|
|
266
|
+
out.push(`### ${KIND_TITLE[kind]}`, ...bucket);
|
|
267
|
+
}
|
|
268
|
+
out.push(omittedLine(facts.length - included));
|
|
269
|
+
return out.join("\n");
|
|
270
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything the compaction code removes before text reaches a summarizer, a pinned fact, a directive or the
|
|
3
|
+
* working state. Stricter than redactString: it also catches key=value forms, cookies and session ids.
|
|
4
|
+
* It can over-redact harmless text such as `token_count=5`; callers that store model-written text say so.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SECRET_KEY_RE: RegExp;
|
|
7
|
+
export declare function scrubSecrets(text: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { redactString } from "../logging/redact.js";
|
|
2
|
+
/**
|
|
3
|
+
* Everything the compaction code removes before text reaches a summarizer, a pinned fact, a directive or the
|
|
4
|
+
* working state. Stricter than redactString: it also catches key=value forms, cookies and session ids.
|
|
5
|
+
* It can over-redact harmless text such as `token_count=5`; callers that store model-written text say so.
|
|
6
|
+
*/
|
|
7
|
+
export const SECRET_KEY_RE = /secret|token|passw|pwd|api[_-]?key|auth|credential|bearer|cookie|private|session|\bsid\b|csrf|xsrf|jwt|signature/i;
|
|
8
|
+
const EXTRA_SECRET_RES = [
|
|
9
|
+
/\b(?:api[_-]?key|secret|token|password|passwd|authorization|bearer)\b["']?\s*[:=]\s*["']?[^\s"',;]+/gi,
|
|
10
|
+
/\bBearer\s+[A-Za-z0-9\-._~+/]{8,}=*/gi,
|
|
11
|
+
/\b(?:Set-)?Cookie:[^\n]*/gi,
|
|
12
|
+
/\b(?:session|sess|sid|csrf|xsrf|jsessionid|phpsessid)[\w-]*=[^\s;,"']+/gi,
|
|
13
|
+
/\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{8,}/g,
|
|
14
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
15
|
+
/\bxox[abprs]-[\w-]{8,}/g,
|
|
16
|
+
/\b(?:sk|pk|rk)[-_](?:live|test|proj|ant)?[-_]?[A-Za-z0-9_-]{16,}/g,
|
|
17
|
+
/[?&](?:token|key|api_?key|access_token|auth|signature|sig)=[^\s&"']+/gi,
|
|
18
|
+
];
|
|
19
|
+
export function scrubSecrets(text) {
|
|
20
|
+
let out = redactString(text);
|
|
21
|
+
for (const re of EXTRA_SECRET_RES) {
|
|
22
|
+
re.lastIndex = 0;
|
|
23
|
+
out = out.replace(re, "[redacted]");
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InlineExtension } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export type SessionContextWiring = {
|
|
3
|
+
/** Inline extensions to register, in order. */
|
|
4
|
+
extensions: InlineExtension[];
|
|
5
|
+
/** Tool names to add to the session's allowlist. */
|
|
6
|
+
toolNames: string[];
|
|
7
|
+
/** Text for the system prompt; empty when the session has no working state. */
|
|
8
|
+
guidance: string;
|
|
9
|
+
/** Tools whose output the tool-output budget must never spill or cap. */
|
|
10
|
+
budgetExemptNames: string[];
|
|
11
|
+
/** Tools the agent tool gate lets through without a grant or an approval. */
|
|
12
|
+
isInternalTool(toolName: string): boolean;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* What a Pi session needs for context management, owned in one place: OpenRouter routing, compaction and
|
|
16
|
+
* (unless `workingState` is false) the working_state tool with its allowlist entry, prompt guidance and
|
|
17
|
+
* gate and budget exemptions. Stage sessions, project agents and the operator agent all call this, so the
|
|
18
|
+
* pieces cannot drift apart.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createSessionContextWiring(options: {
|
|
21
|
+
workingState: boolean;
|
|
22
|
+
}): SessionContextWiring;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createOpenRouterRoutingExtension } from "../agent/openRouterRouting.js";
|
|
2
|
+
import { createWorkingStateExtension } from "../tools/workingState.js";
|
|
3
|
+
import { createCompactionExtension } from "./extension.js";
|
|
4
|
+
import { WORKING_STATE_TOOL_NAME } from "./types.js";
|
|
5
|
+
import { WORKING_STATE_GUIDANCE } from "./workingState.js";
|
|
6
|
+
const warn = (message) => console.error(message);
|
|
7
|
+
/**
|
|
8
|
+
* What a Pi session needs for context management, owned in one place: OpenRouter routing, compaction and
|
|
9
|
+
* (unless `workingState` is false) the working_state tool with its allowlist entry, prompt guidance and
|
|
10
|
+
* gate and budget exemptions. Stage sessions, project agents and the operator agent all call this, so the
|
|
11
|
+
* pieces cannot drift apart.
|
|
12
|
+
*/
|
|
13
|
+
export function createSessionContextWiring(options) {
|
|
14
|
+
const withState = options.workingState;
|
|
15
|
+
return {
|
|
16
|
+
extensions: [
|
|
17
|
+
{ name: "stageflow-openrouter-routing", factory: createOpenRouterRoutingExtension() },
|
|
18
|
+
{ name: "stageflow-compaction", factory: createCompactionExtension({ onWarn: warn }) },
|
|
19
|
+
...(withState ? [{ name: "stageflow-working-state", factory: createWorkingStateExtension() }] : []),
|
|
20
|
+
],
|
|
21
|
+
toolNames: withState ? [WORKING_STATE_TOOL_NAME] : [],
|
|
22
|
+
guidance: withState ? WORKING_STATE_GUIDANCE : "",
|
|
23
|
+
budgetExemptNames: withState ? [WORKING_STATE_TOOL_NAME] : [],
|
|
24
|
+
isInternalTool: (toolName) => withState && toolName === WORKING_STATE_TOOL_NAME,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type ClearingPolicy } from "./mask.js";
|
|
2
|
+
import { type Directive } from "./directives.js";
|
|
3
|
+
import { type LooseMessage } from "./pin.js";
|
|
4
|
+
import { type ClearedResult, type PinnedFact, type WorkingStateEntry } from "./types.js";
|
|
5
|
+
export declare const COMPACTION_FOCUS: string;
|
|
6
|
+
export declare const SECTIONS_MARKER = "<!-- stageflow:compaction-sections -->";
|
|
7
|
+
export declare const PINNED_RENDER_CHARS = 6000;
|
|
8
|
+
export declare const CLEARED_RENDER_CHARS = 3000;
|
|
9
|
+
export declare const STATE_RENDER_CHARS = 6000;
|
|
10
|
+
export declare const DIRECTIVES_RENDER_CHARS = 5000;
|
|
11
|
+
export declare const PINNED_CARRY_MAX = 400;
|
|
12
|
+
export declare const CLEARED_CARRY_MAX = 200;
|
|
13
|
+
export type StageflowCompactionDetails = {
|
|
14
|
+
readFiles?: string[];
|
|
15
|
+
modifiedFiles?: string[];
|
|
16
|
+
stageflowPinned?: PinnedFact[];
|
|
17
|
+
stageflowCleared?: ClearedResult[];
|
|
18
|
+
stageflowDirectives?: Directive[];
|
|
19
|
+
stageflowPromptVersion?: number;
|
|
20
|
+
};
|
|
21
|
+
/** The sections are always appended last, so the last marker at the start of a line is the real one. */
|
|
22
|
+
export declare function splitSummary(summary: string): {
|
|
23
|
+
core: string;
|
|
24
|
+
sections: string;
|
|
25
|
+
};
|
|
26
|
+
/** Details come from the session file, so anything not shaped like ours is dropped rather than trusted. */
|
|
27
|
+
export declare function readStageflowDetails(value: unknown): StageflowCompactionDetails | undefined;
|
|
28
|
+
/** Newest facts first win a tie; capped so repeated compactions cannot grow without bound. */
|
|
29
|
+
export declare function mergePinned(previous: readonly PinnedFact[], fresh: readonly PinnedFact[], max?: number): PinnedFact[];
|
|
30
|
+
export declare function mergeCleared(previous: readonly ClearedResult[], fresh: readonly ClearedResult[], max?: number): ClearedResult[];
|
|
31
|
+
export declare function renderSections(input: {
|
|
32
|
+
pinned: readonly PinnedFact[];
|
|
33
|
+
cleared: readonly ClearedResult[];
|
|
34
|
+
state: readonly WorkingStateEntry[];
|
|
35
|
+
directives?: readonly Directive[];
|
|
36
|
+
}): string;
|
|
37
|
+
/** What the summarizer reads: message text, thinking and tool-call arguments with secrets removed. */
|
|
38
|
+
export declare function redactMessages<T extends LooseMessage>(messages: readonly T[]): T[];
|
|
39
|
+
export type SummaryInputs = {
|
|
40
|
+
messages: readonly LooseMessage[];
|
|
41
|
+
previousDetails?: StageflowCompactionDetails;
|
|
42
|
+
clearInSummary?: ClearingPolicy;
|
|
43
|
+
};
|
|
44
|
+
export type PreparedSummaryInputs = {
|
|
45
|
+
messagesForSummarizer: LooseMessage[];
|
|
46
|
+
pinned: PinnedFact[];
|
|
47
|
+
cleared: ClearedResult[];
|
|
48
|
+
directives: Directive[];
|
|
49
|
+
};
|
|
50
|
+
/** Everything deterministic that happens before the model-written summary is requested. */
|
|
51
|
+
export declare function prepareSummaryInputs(input: SummaryInputs): PreparedSummaryInputs;
|
|
52
|
+
export declare function stageflowDetails(piDetails: {
|
|
53
|
+
readFiles?: string[];
|
|
54
|
+
modifiedFiles?: string[];
|
|
55
|
+
} | undefined, previous: StageflowCompactionDetails | undefined, pinned: readonly PinnedFact[], cleared: readonly ClearedResult[], directives?: readonly Directive[]): StageflowCompactionDetails;
|
|
56
|
+
export type SummarizeCall = {
|
|
57
|
+
messages: LooseMessage[];
|
|
58
|
+
previousSummary: string | undefined;
|
|
59
|
+
};
|
|
60
|
+
export type ComposedSummary<R> = {
|
|
61
|
+
summary: string;
|
|
62
|
+
result: R;
|
|
63
|
+
prepared: PreparedSummaryInputs;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The whole summary pipeline in one place: pin and redact the input, ask the model for the written part,
|
|
67
|
+
* scrub it, then append the deterministic sections. The Pi hook and the evaluation harness both call this,
|
|
68
|
+
* so what the harness measures is what ships.
|
|
69
|
+
*/
|
|
70
|
+
export declare function composeCompactionSummary<R extends {
|
|
71
|
+
summary: string;
|
|
72
|
+
}>(input: {
|
|
73
|
+
messages: readonly LooseMessage[];
|
|
74
|
+
previousSummary?: string;
|
|
75
|
+
previousDetails?: StageflowCompactionDetails;
|
|
76
|
+
state: readonly WorkingStateEntry[];
|
|
77
|
+
clearInSummary?: ClearingPolicy;
|
|
78
|
+
summarize: (call: SummarizeCall) => Promise<R>;
|
|
79
|
+
}): Promise<ComposedSummary<R>>;
|