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.
Files changed (315) hide show
  1. package/README.md +12 -3
  2. package/dist/agent/activity.d.ts +18 -0
  3. package/dist/agent/claudeAdapter.js +7 -0
  4. package/dist/agent/claudeTools.d.ts +27 -18
  5. package/dist/agent/claudeTools.js +54 -15
  6. package/dist/agent/compactionEvents.d.ts +34 -0
  7. package/dist/agent/compactionEvents.js +37 -0
  8. package/dist/agent/openRouterRouting.d.ts +7 -0
  9. package/dist/agent/openRouterRouting.js +23 -0
  10. package/dist/agent/piAdapter.d.ts +6 -0
  11. package/dist/agent/piAdapter.js +128 -27
  12. package/dist/agent/piSettings.d.ts +7 -0
  13. package/dist/agent/piSettings.js +9 -0
  14. package/dist/agent/sandboxPiTools.d.ts +3 -1
  15. package/dist/agent/sandboxPiTools.js +8 -2
  16. package/dist/agent/stageTurnDriver.d.ts +11 -2
  17. package/dist/agent/stageTurnDriver.js +45 -4
  18. package/dist/agent/toolOutputBudget.d.ts +15 -1
  19. package/dist/agent/toolOutputBudget.js +18 -0
  20. package/dist/agent/toolOutputBudgetExtension.d.ts +2 -0
  21. package/dist/agent/toolOutputBudgetExtension.js +40 -3
  22. package/dist/agents/authorChat.d.ts +1 -0
  23. package/dist/agents/authorChat.js +7 -0
  24. package/dist/agents/authorSessionStore.d.ts +3 -0
  25. package/dist/agents/authorSessionStore.js +17 -2
  26. package/dist/agents/evalGate.js +1 -0
  27. package/dist/agents/hostContext.d.ts +1 -1
  28. package/dist/agents/runtime/piRuntime.d.ts +5 -1
  29. package/dist/agents/runtime/piRuntime.js +46 -12
  30. package/dist/agents/service.js +3 -2
  31. package/dist/agents/sessions.js +3 -1
  32. package/dist/cli/evalCommand.d.ts +1 -1
  33. package/dist/cli/evalCommand.js +39 -2
  34. package/dist/cli/hostClient.d.ts +6 -0
  35. package/dist/cli/hostClient.js +4 -0
  36. package/dist/compaction/directives.d.ts +18 -0
  37. package/dist/compaction/directives.js +119 -0
  38. package/dist/compaction/extension.d.ts +20 -0
  39. package/dist/compaction/extension.js +78 -0
  40. package/dist/compaction/mask.d.ts +18 -0
  41. package/dist/compaction/mask.js +155 -0
  42. package/dist/compaction/pin.d.ts +21 -0
  43. package/dist/compaction/pin.js +270 -0
  44. package/dist/compaction/scrub.d.ts +7 -0
  45. package/dist/compaction/scrub.js +26 -0
  46. package/dist/compaction/sessionWiring.d.ts +22 -0
  47. package/dist/compaction/sessionWiring.js +26 -0
  48. package/dist/compaction/summarize.d.ts +79 -0
  49. package/dist/compaction/summarize.js +171 -0
  50. package/dist/compaction/toolPolicy.d.ts +3 -0
  51. package/dist/compaction/toolPolicy.js +33 -0
  52. package/dist/compaction/types.d.ts +45 -0
  53. package/dist/compaction/types.js +7 -0
  54. package/dist/compaction/workingState.d.ts +56 -0
  55. package/dist/compaction/workingState.js +198 -0
  56. package/dist/config/createPipeline.js +2 -0
  57. package/dist/config/createTrigger.js +7 -1
  58. package/dist/config/ensureCatalogScanRoot.d.ts +2 -0
  59. package/dist/config/ensureCatalogScanRoot.js +6 -0
  60. package/dist/config/evalCatalog.js +30 -2
  61. package/dist/config/githubTriggerAction.d.ts +8 -0
  62. package/dist/config/githubTriggerAction.js +29 -0
  63. package/dist/config/hostConfig.js +2 -0
  64. package/dist/config/loadEval.d.ts +3 -1
  65. package/dist/config/loadEval.js +96 -10
  66. package/dist/config/pipelineNeeds.d.ts +2 -0
  67. package/dist/config/pipelineNeeds.js +16 -3
  68. package/dist/config/pipelineRoute.js +5 -4
  69. package/dist/config/validateCatalog.d.ts +1 -1
  70. package/dist/config/validateCatalog.js +12 -0
  71. package/dist/config/validateEval.js +60 -3
  72. package/dist/config/writeEval.js +2 -2
  73. package/dist/envelope/assertEmittedArtifacts.d.ts +12 -0
  74. package/dist/envelope/assertEmittedArtifacts.js +43 -0
  75. package/dist/envelope/preEmitChecks.d.ts +3 -0
  76. package/dist/evals/calibration.d.ts +21 -0
  77. package/dist/evals/calibration.js +69 -0
  78. package/dist/evals/calibrationSuggest.d.ts +29 -0
  79. package/dist/evals/calibrationSuggest.js +72 -0
  80. package/dist/evals/calibrator.d.ts +46 -0
  81. package/dist/evals/calibrator.js +148 -0
  82. package/dist/evals/compare.d.ts +1 -1
  83. package/dist/evals/compare.js +3 -0
  84. package/dist/evals/constants.d.ts +7 -3
  85. package/dist/evals/constants.js +7 -1
  86. package/dist/evals/engine/FakeEngine.d.ts +2 -1
  87. package/dist/evals/engine/FakeEngine.js +9 -3
  88. package/dist/evals/engine/promptfoo/PromptfooEngine.d.ts +1 -1
  89. package/dist/evals/engine/promptfoo/PromptfooEngine.js +4 -2
  90. package/dist/evals/engine/promptfoo/generate.d.ts +3 -1
  91. package/dist/evals/engine/promptfoo/generate.js +43 -23
  92. package/dist/evals/engine/promptfoo/judgeProviders.js +2 -1
  93. package/dist/evals/engine/promptfoo/normalize.d.ts +4 -1
  94. package/dist/evals/engine/promptfoo/normalize.js +16 -3
  95. package/dist/evals/engine/promptfoo/rubricPrompt.d.ts +3 -1
  96. package/dist/evals/engine/promptfoo/rubricPrompt.js +15 -5
  97. package/dist/evals/errors.d.ts +7 -0
  98. package/dist/evals/errors.js +10 -0
  99. package/dist/evals/judgeProviders.d.ts +6 -0
  100. package/dist/evals/judgeProviders.js +14 -0
  101. package/dist/evals/judged.d.ts +7 -0
  102. package/dist/evals/judged.js +7 -0
  103. package/dist/evals/manager.d.ts +27 -8
  104. package/dist/evals/manager.js +73 -12
  105. package/dist/evals/plan.d.ts +10 -1
  106. package/dist/evals/plan.js +33 -13
  107. package/dist/evals/planInternalAgent.d.ts +19 -0
  108. package/dist/evals/planInternalAgent.js +201 -0
  109. package/dist/evals/read/api.d.ts +18 -0
  110. package/dist/evals/read/api.js +10 -1
  111. package/dist/evals/read/labelQueue.d.ts +45 -0
  112. package/dist/evals/read/labelQueue.js +83 -0
  113. package/dist/evals/read/list.js +10 -1
  114. package/dist/evals/read/runs.d.ts +5 -0
  115. package/dist/evals/read/runs.js +7 -1
  116. package/dist/evals/runner/caseRunner.d.ts +2 -1
  117. package/dist/evals/runner/caseRunner.js +16 -5
  118. package/dist/evals/runner/internalAgentCaseRunner.d.ts +6 -0
  119. package/dist/evals/runner/internalAgentCaseRunner.js +250 -0
  120. package/dist/evals/snapshot.d.ts +10 -2
  121. package/dist/evals/snapshot.js +8 -3
  122. package/dist/evals/stats.d.ts +40 -0
  123. package/dist/evals/stats.js +69 -0
  124. package/dist/evals/storeTypes.d.ts +42 -0
  125. package/dist/evals/types.d.ts +25 -2
  126. package/dist/evals/types.js +4 -1
  127. package/dist/evals/votes.d.ts +10 -0
  128. package/dist/evals/votes.js +60 -0
  129. package/dist/guide/cardContract.d.ts +133 -0
  130. package/dist/guide/cardContract.js +156 -0
  131. package/dist/guide/chat.d.ts +45 -0
  132. package/dist/guide/chat.js +378 -0
  133. package/dist/guide/knowledge/index.d.ts +14 -0
  134. package/dist/guide/knowledge/index.js +109 -0
  135. package/dist/guide/knowledge/topics/agents.d.ts +2 -0
  136. package/dist/guide/knowledge/topics/agents.js +58 -0
  137. package/dist/guide/knowledge/topics/bestPractices.d.ts +2 -0
  138. package/dist/guide/knowledge/topics/bestPractices.js +67 -0
  139. package/dist/guide/knowledge/topics/connections.d.ts +2 -0
  140. package/dist/guide/knowledge/topics/connections.js +66 -0
  141. package/dist/guide/knowledge/topics/consoleMap.d.ts +2 -0
  142. package/dist/guide/knowledge/topics/consoleMap.js +55 -0
  143. package/dist/guide/knowledge/topics/evals.d.ts +2 -0
  144. package/dist/guide/knowledge/topics/evals.js +72 -0
  145. package/dist/guide/knowledge/topics/limitations.d.ts +2 -0
  146. package/dist/guide/knowledge/topics/limitations.js +84 -0
  147. package/dist/guide/knowledge/topics/mcpSkillsBrowser.d.ts +2 -0
  148. package/dist/guide/knowledge/topics/mcpSkillsBrowser.js +57 -0
  149. package/dist/guide/knowledge/topics/memories.d.ts +2 -0
  150. package/dist/guide/knowledge/topics/memories.js +63 -0
  151. package/dist/guide/knowledge/topics/modelsAndProviders.d.ts +2 -0
  152. package/dist/guide/knowledge/topics/modelsAndProviders.js +56 -0
  153. package/dist/guide/knowledge/topics/overview.d.ts +2 -0
  154. package/dist/guide/knowledge/topics/overview.js +56 -0
  155. package/dist/guide/knowledge/topics/pipelines.d.ts +2 -0
  156. package/dist/guide/knowledge/topics/pipelines.js +93 -0
  157. package/dist/guide/knowledge/topics/stages.d.ts +2 -0
  158. package/dist/guide/knowledge/topics/stages.js +54 -0
  159. package/dist/guide/knowledge/topics/tasks.d.ts +2 -0
  160. package/dist/guide/knowledge/topics/tasks.js +52 -0
  161. package/dist/guide/knowledge/topics/triggers.d.ts +2 -0
  162. package/dist/guide/knowledge/topics/triggers.js +68 -0
  163. package/dist/guide/knowledge/topics/troubleshooting.d.ts +2 -0
  164. package/dist/guide/knowledge/topics/troubleshooting.js +85 -0
  165. package/dist/guide/knowledgeTools.d.ts +2 -0
  166. package/dist/guide/knowledgeTools.js +56 -0
  167. package/dist/guide/showUi.d.ts +6 -0
  168. package/dist/guide/showUi.js +546 -0
  169. package/dist/guide/threadStore.d.ts +48 -0
  170. package/dist/guide/threadStore.js +189 -0
  171. package/dist/guide/writeRoot.d.ts +11 -0
  172. package/dist/guide/writeRoot.js +70 -0
  173. package/dist/hosted/translate.js +2 -0
  174. package/dist/mcp/agentTools.js +12 -0
  175. package/dist/mcp/catalogTools.js +18 -20
  176. package/dist/mcp/catalogWriteTools.d.ts +8 -0
  177. package/dist/mcp/catalogWriteTools.js +225 -0
  178. package/dist/mcp/connectionTools.d.ts +3 -0
  179. package/dist/mcp/connectionTools.js +85 -0
  180. package/dist/mcp/controlTools.js +34 -1
  181. package/dist/mcp/deps.d.ts +9 -0
  182. package/dist/mcp/evalTools.js +723 -21
  183. package/dist/mcp/memoryHostTools.d.ts +3 -0
  184. package/dist/mcp/memoryHostTools.js +72 -0
  185. package/dist/mcp/pipelineAuthorTools.js +14 -1
  186. package/dist/mcp/server.js +5 -0
  187. package/dist/mcp/tools.js +6 -0
  188. package/dist/mcp/triggerTools.js +77 -31
  189. package/dist/operatorAgent/evalGate.d.ts +22 -0
  190. package/dist/operatorAgent/evalGate.js +77 -0
  191. package/dist/operatorAgent/fakeHost.js +10 -2
  192. package/dist/operatorAgent/host.js +18 -2
  193. package/dist/operatorAgent/index.d.ts +1 -0
  194. package/dist/operatorAgent/index.js +1 -0
  195. package/dist/operatorAgent/internalAgents.d.ts +11 -0
  196. package/dist/operatorAgent/internalAgents.js +13 -0
  197. package/dist/operatorAgent/piModel.d.ts +5 -1
  198. package/dist/operatorAgent/piModel.js +15 -10
  199. package/dist/operatorAgent/profiles/agentAuthor.js +66 -22
  200. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -1
  201. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +1 -1
  202. package/dist/operatorAgent/profiles/guideConfirm.d.ts +21 -0
  203. package/dist/operatorAgent/profiles/guideConfirm.js +100 -0
  204. package/dist/operatorAgent/profiles/guideTools.d.ts +7 -0
  205. package/dist/operatorAgent/profiles/guideTools.js +123 -0
  206. package/dist/operatorAgent/profiles/stageflowGuide.d.ts +7 -0
  207. package/dist/operatorAgent/profiles/stageflowGuide.js +133 -0
  208. package/dist/operatorAgent/profiles/workshopAuthor.js +23 -0
  209. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  210. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +9 -1
  211. package/dist/operatorAgent/profiles/workshopAuthorTools.d.ts +2 -0
  212. package/dist/operatorAgent/profiles/workshopAuthorTools.js +197 -9
  213. package/dist/operatorAgent/types.d.ts +23 -0
  214. package/dist/package-meta.d.ts +1 -1
  215. package/dist/package-meta.js +1 -1
  216. package/dist/runstore/port.d.ts +13 -1
  217. package/dist/runstore/retention.d.ts +2 -1
  218. package/dist/runstore/retention.js +3 -7
  219. package/dist/runstore/runStatusClock.d.ts +17 -0
  220. package/dist/runstore/runStatusClock.js +19 -0
  221. package/dist/runstore/sqlite/SqliteRunStore.d.ts +11 -1
  222. package/dist/runstore/sqlite/SqliteRunStore.js +70 -45
  223. package/dist/runstore/sqlite/calibrationStore.d.ts +10 -0
  224. package/dist/runstore/sqlite/calibrationStore.js +106 -0
  225. package/dist/runstore/sqlite/evalStore.d.ts +5 -0
  226. package/dist/runstore/sqlite/evalStore.js +7 -0
  227. package/dist/runstore/sqlite/migrations/018-eval-grader-labels.d.ts +11 -0
  228. package/dist/runstore/sqlite/migrations/018-eval-grader-labels.js +43 -0
  229. package/dist/runstore/sqlite/migrations/index.js +2 -0
  230. package/dist/runstore/workspaceLayout.js +6 -1
  231. package/dist/runtime/artifactDiskInspect.d.ts +14 -0
  232. package/dist/runtime/artifactDiskInspect.js +52 -0
  233. package/dist/runtime/artifactPath.d.ts +24 -0
  234. package/dist/runtime/artifactPath.js +84 -0
  235. package/dist/runtime/completionCheckRunner.js +15 -43
  236. package/dist/runtime/githubPollSource.d.ts +22 -4
  237. package/dist/runtime/githubPollSource.js +93 -8
  238. package/dist/runtime/priorArtifactGrants.d.ts +8 -0
  239. package/dist/runtime/priorArtifactGrants.js +105 -0
  240. package/dist/runtime/runActionEligibility.d.ts +46 -0
  241. package/dist/runtime/runActionEligibility.js +123 -0
  242. package/dist/runtime/runManager.d.ts +4 -1
  243. package/dist/runtime/runManager.js +52 -10
  244. package/dist/runtime/runRetryCoordinator.d.ts +2 -14
  245. package/dist/runtime/runRetryCoordinator.js +3 -67
  246. package/dist/runtime/sandboxContainer.d.ts +7 -0
  247. package/dist/runtime/sandboxContainer.js +48 -6
  248. package/dist/runtime/sandboxDesk.d.ts +2 -1
  249. package/dist/runtime/sandboxDesk.js +11 -12
  250. package/dist/runtime/stageEnvironment.js +1 -0
  251. package/dist/runtime/stageRoots.d.ts +12 -0
  252. package/dist/runtime/stageSandbox.d.ts +2 -1
  253. package/dist/runtime/stageSandbox.js +18 -2
  254. package/dist/runtime/stageWorker.js +20 -0
  255. package/dist/server/agentAuthorRoutes.js +12 -3
  256. package/dist/server/bootstrap.d.ts +5 -0
  257. package/dist/server/bootstrap.js +14 -1
  258. package/dist/server/evalRoutes.js +251 -12
  259. package/dist/server/guideRoutes.d.ts +5 -0
  260. package/dist/server/guideRoutes.js +166 -0
  261. package/dist/server/http.d.ts +10 -0
  262. package/dist/server/http.js +35 -1
  263. package/dist/server/internalAgentRoutes.d.ts +6 -0
  264. package/dist/server/internalAgentRoutes.js +12 -0
  265. package/dist/server/mcpHost.js +2 -0
  266. package/dist/toolkit/meta.d.ts +1 -1
  267. package/dist/toolkit/meta.js +41 -1
  268. package/dist/toolkit/profiles.d.ts +5 -0
  269. package/dist/toolkit/profiles.js +12 -0
  270. package/dist/tools/askOperator.js +2 -1
  271. package/dist/tools/emitStageEnvelope.d.ts +17 -16
  272. package/dist/tools/emitStageEnvelope.js +46 -32
  273. package/dist/tools/stageToolNames.d.ts +4 -0
  274. package/dist/tools/stageToolNames.js +4 -0
  275. package/dist/tools/workingState.d.ts +40 -0
  276. package/dist/tools/workingState.js +108 -0
  277. package/dist/tools/writeStageArtifact.d.ts +0 -9
  278. package/dist/tools/writeStageArtifact.js +2 -1
  279. package/dist/ui/assets/{MarkdownText-BTrOqu5J.js → MarkdownText-BS2BBbCx.js} +2 -2
  280. package/dist/ui/assets/adapters-DPD-cAsy.js +17 -0
  281. package/dist/ui/assets/chat-BD1-VTT6.js +2 -0
  282. package/dist/ui/assets/chat-DXnQv9st.css +2 -0
  283. package/dist/ui/assets/main-AX5XNLtX.js +278 -0
  284. package/dist/ui/assets/{main-CHlmTNJe.css → main-CFX0_n8J.css} +1 -1
  285. package/dist/ui/chat.html +3 -4
  286. package/dist/ui/index.html +3 -4
  287. package/dist/workshop/chatTurn.js +1 -1
  288. package/dist/workshop/workshopPin.d.ts +3 -3
  289. package/dist/workshop/workshopPin.js +16 -15
  290. package/examples/compaction-stress/README.md +30 -0
  291. package/examples/compaction-stress/check-run.mjs +53 -0
  292. package/examples/compaction-stress/collect-evidence.yaml +60 -0
  293. package/examples/compaction-stress/compaction-stress.pipeline.yaml +141 -0
  294. package/examples/compaction-stress/compaction-stress.task.yaml +15 -0
  295. package/examples/compaction-stress/generate-evidence.mjs +110 -0
  296. package/examples/compaction-stress/root-cause.yaml +26 -0
  297. package/examples/compaction-stress/stageflow.yaml +9 -0
  298. package/examples/compaction-stress/write-postmortem.yaml +40 -0
  299. package/examples/evals/README.md +18 -0
  300. package/examples/evals/guide-behavior.dataset.jsonl +15 -0
  301. package/examples/evals/guide-behavior.eval.yaml +35 -0
  302. package/examples/evals/guide-depth.dataset.jsonl +35 -0
  303. package/examples/evals/guide-depth.eval.yaml +169 -0
  304. package/examples/memory/evals/guide-behavior.dataset.jsonl +15 -0
  305. package/examples/memory/evals/guide-behavior.eval.yaml +35 -0
  306. package/examples/memory/evals/guide-depth.dataset.jsonl +35 -0
  307. package/examples/memory/evals/guide-depth.eval.yaml +169 -0
  308. package/package.json +1 -1
  309. package/skills/stageflow/references/control-surface.md +1 -1
  310. package/skills/stageflow/references/evals.md +1 -0
  311. package/dist/ui/assets/AssistantRuntimeProvider-qIX1xNoH.js +0 -9
  312. package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +0 -6
  313. package/dist/ui/assets/chat-Df_wCpak.js +0 -5
  314. package/dist/ui/assets/chat-P7KQznHm.css +0 -2
  315. 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"]) {
@@ -9,6 +9,7 @@ const ALWAYS_BLOCKED = new Set([
9
9
  "stageflow.agent_chat",
10
10
  "stageflow.fire_trigger",
11
11
  "stageflow.sort_eval_failures",
12
+ "stageflow.calibrate_eval_grader",
12
13
  "stageflow.cancel_eval_run",
13
14
  ]);
14
15
  const BLOCKED_UNLESS_REAL = new Set(["stageflow.probe_project_mcp"]);
@@ -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, SettingsManager, } from "@earendil-works/pi-coding-agent";
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 = SettingsManager.inMemory({ compaction: { enabled: true } });
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 gateExtension = (pi) => {
101
- pi.on("tool_call", async (event) => {
102
- const outcome = await input.gate({ toolCallId: event.toolCallId, toolName: event.toolName, input: event.input });
103
- return outcome;
104
- });
105
- };
106
- const extensionFactories = [{ name: "stageflow-agent-gate", factory: gateExtension }, ...(attached.extensionFactories ?? [])];
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(definition));
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 === "message_start" && e.message?.role === "assistant")
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
  });
@@ -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
  });
@@ -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
  },
@@ -1,6 +1,6 @@
1
1
  import { type RunNpm } from "../evals/engine/promptfoo/install.js";
2
2
  import { type EnsureGlobalServiceResult } from "../server/ensureGlobalService.js";
3
- export declare const EVAL_USAGE = "Usage:\n sf eval list [--json]\n sf eval show <id|path> [--json]\n sf eval validate [<id|path>] [--json] [--strict]\n sf eval run <id|path> [--repeats <n>] [--judge <model>] [--model <id>] [--user-model <model>] [--allow-real-tools] [--max-cost <usd>] [--case <id> ...] [--tag <tag> ...] [--label <text>] [--no-wait] [--json]\n sf eval runs [<evalId>] [--json]\n sf eval runs show <evalRunId> [--json]\n sf eval runs cancel <evalRunId>\n sf eval runs delete <evalRunId>\n sf eval sort <evalRunId> [--classifier auto|jev|judge] [--limit <n>] [--resort] [--json]\n sf eval suggestions <evalRunId> [--status pending|accepted|dismissed] [--json]\n sf eval compare <evalRunIdA> <evalRunIdB> [--json]\n sf eval datasets [--json]\n sf eval engine status [--json]\n sf eval engine install\n\n Agent evals: --model overrides the agent's model, --user-model the model that plays the user. An eval whose\n conversation.tools is real also needs --allow-real-tools.\n\n Every subcommand also accepts --project-root <id-or-path>.";
3
+ export declare const EVAL_USAGE = "Usage:\n sf eval list [--json]\n sf eval show <id|path> [--json]\n sf eval validate [<id|path>] [--json] [--strict]\n sf eval run <id|path> [--repeats <n>] [--judge <model>] [--model <id>] [--user-model <model>] [--allow-real-tools] [--max-cost <usd>] [--case <id> ...] [--tag <tag> ...] [--label <text>] [--no-wait] [--json]\n sf eval runs [<evalId>] [--json]\n sf eval runs show <evalRunId> [--json]\n sf eval runs cancel <evalRunId>\n sf eval runs delete <evalRunId>\n sf eval sort <evalRunId> [--classifier auto|jev|judge] [--limit <n>] [--resort] [--json]\n sf eval calibrate <evalId> <graderId> [--split all|dev|held_out] [--max-cost <usd>] [--stability] [--json]\n sf eval suggestions <evalRunId> [--status pending|accepted|dismissed] [--json]\n sf eval compare <evalRunIdA> <evalRunIdB> [--json]\n sf eval datasets [--json]\n sf eval engine status [--json]\n sf eval engine install\n\n Agent evals: --model overrides the agent's model, --user-model the model that plays the user. An eval whose\n conversation.tools is real also needs --allow-real-tools.\n\n Every subcommand also accepts --project-root <id-or-path>.";
4
4
  export type EvalCommandIo = {
5
5
  log: (line: string) => void;
6
6
  error: (line: string) => void;
@@ -9,7 +9,7 @@ import { installPromptfoo } from "../evals/engine/promptfoo/install.js";
9
9
  import { engineStatus } from "../evals/engine/promptfoo/status.js";
10
10
  import { globalStageflowHome } from "../project/globalHome.js";
11
11
  import { ensureGlobalService, hostBaseUrl, } from "../server/ensureGlobalService.js";
12
- import { httpCancelEvalRun, httpCompareEvalRuns, httpDeleteEvalRun, httpEnsureProject, httpGetEval, httpGetEvalRun, httpListDatasets, httpListEvalRuns, httpListEvalSuggestions, httpSortEvalFailures, httpListEvals, httpStartEvalRun, } from "./hostClient.js";
12
+ import { httpCancelEvalRun, httpCompareEvalRuns, httpDeleteEvalRun, httpEnsureProject, httpGetEval, httpGetEvalRun, httpListDatasets, httpListEvalRuns, httpListEvalSuggestions, httpCalibrateEvalGrader, httpSortEvalFailures, httpListEvals, httpStartEvalRun, } from "./hostClient.js";
13
13
  import { resolveCliProjectRootLocal } from "./resolveCliProjectRoot.js";
14
14
  import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
15
15
  export const EVAL_USAGE = `Usage:
@@ -22,6 +22,7 @@ export const EVAL_USAGE = `Usage:
22
22
  sf eval runs cancel <evalRunId>
23
23
  sf eval runs delete <evalRunId>
24
24
  sf eval sort <evalRunId> [--classifier auto|jev|judge] [--limit <n>] [--resort] [--json]
25
+ sf eval calibrate <evalId> <graderId> [--split all|dev|held_out] [--max-cost <usd>] [--stability] [--json]
25
26
  sf eval suggestions <evalRunId> [--status pending|accepted|dismissed] [--json]
26
27
  sf eval compare <evalRunIdA> <evalRunIdB> [--json]
27
28
  sf eval datasets [--json]
@@ -61,6 +62,7 @@ const FLAGS = {
61
62
  runs: {},
62
63
  sort: { "--classifier": { value: true }, "--limit": { value: true }, "--resort": {} },
63
64
  suggestions: { "--status": { value: true } },
65
+ calibrate: { "--split": { value: true }, "--max-cost": { value: true }, "--stability": {} },
64
66
  compare: {},
65
67
  datasets: {},
66
68
  engine: {},
@@ -76,6 +78,7 @@ function parseArgs(command, rest) {
76
78
  noWait: false,
77
79
  allowRealTools: false,
78
80
  resort: false,
81
+ stability: false,
79
82
  cases: [],
80
83
  tags: [],
81
84
  positionals: [],
@@ -150,6 +153,15 @@ function parseArgs(command, rest) {
150
153
  case "--resort":
151
154
  parsed.resort = true;
152
155
  break;
156
+ case "--stability":
157
+ parsed.stability = true;
158
+ break;
159
+ case "--split":
160
+ if (value !== "all" && value !== "dev" && value !== "held_out") {
161
+ throw new UsageError("--split must be all, dev or held_out");
162
+ }
163
+ parsed.split = value;
164
+ break;
153
165
  case "--status":
154
166
  if (value !== "pending" && value !== "accepted" && value !== "dismissed") {
155
167
  throw new UsageError("--status must be pending, accepted or dismissed");
@@ -404,7 +416,9 @@ export async function runEvalCommand(args, options = {}) {
404
416
  ? expectArgs(2, 2, "<evalRunIdA> <evalRunIdB>")
405
417
  : command === "sort" || command === "suggestions"
406
418
  ? expectArgs(1, 1, "<evalRunId>")
407
- : null;
419
+ : command === "calibrate"
420
+ ? expectArgs(2, 2, "<evalId> <graderId>")
421
+ : null;
408
422
  if (argError)
409
423
  return usageError(argError);
410
424
  let runsSub;
@@ -554,6 +568,29 @@ export async function runEvalCommand(args, options = {}) {
554
568
  out.log(`Review and accept in the console, or POST /api/eval-runs/${positionals[0]}/failures/suggestions/accept`);
555
569
  });
556
570
  }
571
+ case "calibrate": {
572
+ const result = await httpCalibrateEvalGrader(base, positionals[0], positionals[1], {
573
+ project_root: wireRoot,
574
+ ...(parsed.split !== undefined ? { split: parsed.split } : {}),
575
+ ...(parsed.maxCost !== undefined ? { max_cost_usd: parsed.maxCost } : {}),
576
+ ...(parsed.stability ? { stability: true } : {}),
577
+ });
578
+ return show(result, (b) => {
579
+ const st = b.stats;
580
+ const num = (v) => (typeof v === "number" ? v.toFixed(2) : "n/a");
581
+ out.log(`${b.grader_id} (${b.judge}): ${st.n} labels, agreement ${num(st.agreement)}, kappa ${num(st.cohen_kappa)}, pass recall ${num(st.tpr)}, fail recall ${num(st.tnr)}; cost ${usd(b.cost_usd)}`);
582
+ const cm = st.confusion;
583
+ out.log(`judge pass / human pass ${cm.tp}\tjudge pass / human fail ${cm.fp}\tjudge fail / human fail ${cm.tn}\tjudge fail / human pass ${cm.fn}`);
584
+ if (st.stability) {
585
+ out.log(`stability over ${st.stability.k} judge calls per output: ${st.stability.unstable} of ${st.stability.n} outputs split (flip rate ${num(st.stability.flip_rate)})`);
586
+ }
587
+ for (const w of st.warnings)
588
+ out.log(`warning: ${w}`);
589
+ for (const d of b.disagreements) {
590
+ out.log(`${d.case_id}#${d.repeat_index}\thuman ${d.label}\tjudge ${d.judge}\t${d.reason ?? ""}`);
591
+ }
592
+ });
593
+ }
557
594
  case "suggestions":
558
595
  return show(await httpListEvalSuggestions(base, positionals[0], parsed.status), (b) => {
559
596
  for (const s of b.items)
@@ -119,6 +119,12 @@ export declare function httpSortEvalFailures(base: string, evalRunId: string, in
119
119
  only_unsorted?: boolean;
120
120
  limit?: number;
121
121
  }): Promise<HttpEvalResult<Record<string, any>>>;
122
+ export declare function httpCalibrateEvalGrader(base: string, evalId: string, graderId: string, input: {
123
+ project_root?: string;
124
+ split?: "all" | "dev" | "held_out";
125
+ max_cost_usd?: number;
126
+ stability?: boolean;
127
+ }): Promise<HttpEvalResult<Record<string, any>>>;
122
128
  export declare function httpListEvalSuggestions(base: string, evalRunId: string, status?: string): Promise<HttpEvalResult<{
123
129
  items: Array<Record<string, any>>;
124
130
  summary: Record<string, any>;
@@ -342,6 +342,10 @@ export async function httpSortEvalFailures(base, evalRunId, input) {
342
342
  const { status, body } = await postJson(base, `/api/eval-runs/${enc(evalRunId)}/failures/sort`, input);
343
343
  return status === 200 ? { ok: true, body: body } : evalFailure(status, body);
344
344
  }
345
+ export async function httpCalibrateEvalGrader(base, evalId, graderId, input) {
346
+ const { status, body } = await postJson(base, `/api/evals/${enc(evalId)}/graders/${enc(graderId)}/calibrate`, input);
347
+ return status === 200 ? { ok: true, body: body } : evalFailure(status, body);
348
+ }
345
349
  export function httpListEvalSuggestions(base, evalRunId, status) {
346
350
  return evalGet(base, `/api/eval-runs/${enc(evalRunId)}/failures/suggestions${evalQuery({ status })}`);
347
351
  }
@@ -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;