@tea-agent/loop-agent 0.40.0-next.1 → 0.40.0-next.11
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/AGENTS.md +1 -1
- package/CHANGELOG.md +114 -109
- package/README.md +2 -2
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/commands/dag-rerun.js +1 -1
- package/dist/executors/dag-pi-executor.js +69 -7
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +46 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +107 -20
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/governance/checks.js +1 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-prompt-override.js +4 -5
- package/dist/shared/update/npm-client.js +33 -11
- package/dist/task/config-types.js +10 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +15 -1
- package/dist/task/contract/schema.js +2 -0
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/prepare.js +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +12 -26
- package/dist/worker/console/chat/chat-event-store.js +75 -5
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +20 -2
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +133 -7
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +312 -1
- package/dist/worker/console/security.js +4 -4
- package/dist/worker/console/server.js +583 -172
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Br92EGb7.js → abnfDiagram-N423BO3Z-Bs-CDAXM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-oulImtQq.js → arc-CAkA3We3.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-tyHc6aqO.js → architectureDiagram-T3A2C74G-DEmI_zqr.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CY86BSpT.js → blockDiagram-VBNYF7ZC-uDHANebE.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BKKsDmNs.js → c4Diagram-5PPSVZJV-Bv97b4_B.js} +1 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CXZdjl3B.js → chunk-2GRJ4B5K-shmQKsFz.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B-aIFDQ5.js → chunk-2Q5K7J3B-BAraNvcN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BDgAW_B2.js → chunk-5RXB4S5H-BirhxUop.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DABa4UPR.js → chunk-5VM5RSS4-C9LgUJpR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-aGbysVOx.js → chunk-6Q2QTUOP-DrU-s-t7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B8MSZDVs.js → chunk-GF5L2VYU-Tx-H_FA2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CpelV73g.js → chunk-JWPE2WC7-BjLtNFrR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BE04ty3z.js → chunk-KBJHAD2P-UNLVwSsy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BUxTkaaR.js → chunk-RYQCIY6F-CBEUX8XD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CYYBVKfX.js → chunk-XXDRQBXY-WTL7IZOq.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CEQF_xmQ.js → cose-bilkent-JH36ORCC-CkHG6W3s.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CqpxN5IT.js → cynefin-VYW2F7L2-BRzFqTaL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-B3PzanVi.js → cynefinDiagram-MW4NZA55-2_CFp_i7.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BSUGCNx1.js → dagre-VZM6K2ZE-BJtsmdkE.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CdLevU5T.js → diagram-7IWD3JNH-DNEFFo6w.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BjpF_Kok.js → diagram-B4RE2ZJO-B_IWV7eo.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C4I6NJny.js → diagram-LBJQPF4R-DvvRx2dz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C8hecCr5.js → diagram-Q27KOJAE-CsibsWLg.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Daod6tOS.js → diagram-UB23O5K3-les_djBQ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BvvmazGv.js → ebnfDiagram-BXEA7PRR-BEm2n7qJ.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DeN_vfV4.js → erDiagram-JOGREHBK-BI7vHHVm.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D6dq1t9z.js → flowDiagram-UKHOOZJN-CUFCERrB.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DziIdNY4.js → ganttDiagram-PKOTCBZU-wxyLiGWt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dy4s5CXr.js → gitGraphDiagram-DS77QQ5N-Bt897K8Z.js} +1 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +1 -0
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czv5eYx2.js → infoDiagram-6WML65LV-DYNx_IPv.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Bsk3nFRT.js → ishikawaDiagram-WSZJBQD7-CzpLNEu6.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DxGmNj5K.js → journeyDiagram-NVQOT4AX-97f9owNU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CYCFqkO_.js → kanban-definition-27J2QSJJ-BxM9hyBn.js} +1 -1
- package/dist/worker/console/static/assets/{linear-B0YzRwrd.js → linear-ByuxcHvp.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-t3EZtIGh.js → mermaid.core-DwPGvpCv.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BTXpTv0T.js → mindmap-definition-FAOFIHXS-DCoqG9RT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DU8UkKqA.js → pegDiagram-VL7TDLO6-BLSBmBMx.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DP4y8ohp.js → pieDiagram-7S7Q4E2Y-B0t_0oJW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-B07vNaYR.js → quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-xDWktb2i.js → railroadDiagram-AXF67PYL-B3jRdATC.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BLNMKqeS.js → requirementDiagram-LRYGKXZP-D-cTtXEN.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-n0adalxH.js → sankeyDiagram-W5VNT64P-B25igJRE.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BbnS-2DL.js → sequenceDiagram-SI44F4Z6-jW_lM1_D.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4uPpCLu.js → sizeCapture-X5ZJPWSS-CrzTgsHE.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA--MMbB0fX.js → stateDiagram-OKZ733FA-B01MvlqE.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BXUgp2QY.js → swimlanes-SLNWSIFB-YcF12FiU.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CsaMxki7.js → timeline-definition-Z64GVDOM-ClnRcg2s.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-C2IuG9iH.js → vennDiagram-T6HMQDX7-B6gr_z-q.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CmEWOXCW.js → wardleyDiagram-T6FBY63Y-CvSgguGH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-D_9LE0MG.js → xychartDiagram-ELKLHX3M-23OtSZCb.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +234 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observe/node-input.js +88 -2
- package/dist/worker/observe/routes.js +188 -0
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +29 -9
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +203 -0
- package/dist/worker/observe/static/format.js +37 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +174 -2
- package/dist/worker/observe/static/styles.css +357 -35
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +533 -79
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +6 -4
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +186 -8
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +168 -16
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +28 -17
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +88 -2
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +197 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/init-hybrid.js +239 -52
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +45 -0
- package/dist/workflows/dag/types.js +62 -12
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +18 -0
- package/docs/templates/backend-test-dag.json +267 -238
- package/docs/templates/frontend-implementation-contract.schema.json +681 -24
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- package/dist/worker/console/static/assets/channel-TY36zt-i.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Dk7QCChM.js +0 -1
- package/dist/worker/console/static/assets/index-C3GgHg-g.css +0 -1
- package/dist/worker/console/static/assets/index-Ci8ksvBm.js +0 -406
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BqOigMQ4.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Caxz9UA2.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -29,6 +29,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
29
29
|
import { access } from "node:fs/promises";
|
|
30
30
|
import path from "node:path";
|
|
31
31
|
import { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage, } from "./assistant-content.js";
|
|
32
|
+
import { parseProviderError, } from "./provider-error.js";
|
|
32
33
|
import { projectCompactSnapshot, } from "./chat-event-store.js";
|
|
33
34
|
import { extractUsageSample } from "./usage.js";
|
|
34
35
|
import { OPERATOR_CHAT_ALLOWED_TOOLS, authorizeOperatorChatTool, assertNoWriteToolInList, } from "./tools.js";
|
|
@@ -38,6 +39,7 @@ import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
|
38
39
|
import { resolveDefaultChatModel, resolveLowChatModel, } from "./model-resolver.js";
|
|
39
40
|
import { filterActiveInterviewTools } from "../interview/tools.js";
|
|
40
41
|
import { RUNTIME_CONTEXT_TEXT_MAX, redactRuntimeText, } from "./runtime-context.js";
|
|
42
|
+
import { buildTodoContinuationContext, } from "./todos.js";
|
|
41
43
|
/**
|
|
42
44
|
* Base system prompt fragment every General Operator Chat session receives.
|
|
43
45
|
*
|
|
@@ -210,6 +212,51 @@ async function bindSessionExtensions(session) {
|
|
|
210
212
|
function pinOperatorChatTools(session) {
|
|
211
213
|
session.setActiveToolsByName(computeOperatorChatActiveToolNames(session));
|
|
212
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Pi has no hook result that merely appends instructions to its built-in
|
|
217
|
+
* automatic compactor: session_before_compact must either cancel or replace
|
|
218
|
+
* the whole compaction result. Keep the SDK algorithm intact and inject the
|
|
219
|
+
* current Todo as ephemeral context only after a successful compaction. The
|
|
220
|
+
* next turn resets the marker and TodoListRegistry clears the list.
|
|
221
|
+
*/
|
|
222
|
+
export function createTodoCompactionContextExtension(options) {
|
|
223
|
+
return {
|
|
224
|
+
name: "operator-chat-todo-compaction-context",
|
|
225
|
+
hidden: true,
|
|
226
|
+
factory: (api) => {
|
|
227
|
+
let compactedInCurrentTurn = false;
|
|
228
|
+
api.on("turn_start", () => {
|
|
229
|
+
compactedInCurrentTurn = false;
|
|
230
|
+
});
|
|
231
|
+
api.on("session_compact", () => {
|
|
232
|
+
compactedInCurrentTurn = true;
|
|
233
|
+
});
|
|
234
|
+
api.on("context", (rawEvent) => {
|
|
235
|
+
if (!compactedInCurrentTurn)
|
|
236
|
+
return undefined;
|
|
237
|
+
const context = buildTodoContinuationContext(options.registry.get(options.sessionId));
|
|
238
|
+
if (!context)
|
|
239
|
+
return undefined;
|
|
240
|
+
const messages = Array.isArray(rawEvent.messages)
|
|
241
|
+
? rawEvent.messages
|
|
242
|
+
: [];
|
|
243
|
+
return {
|
|
244
|
+
messages: [
|
|
245
|
+
...messages,
|
|
246
|
+
{
|
|
247
|
+
role: "custom",
|
|
248
|
+
customType: "operator-chat-todo-context",
|
|
249
|
+
content: context,
|
|
250
|
+
display: false,
|
|
251
|
+
details: { todos: options.registry.get(options.sessionId) },
|
|
252
|
+
timestamp: Date.now(),
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
213
260
|
/**
|
|
214
261
|
* Build Pi custom ToolDefinition objects for every whitelisted operator action.
|
|
215
262
|
* Each tool's execute() routes into the operator action dispatcher (Gate 3).
|
|
@@ -289,6 +336,76 @@ async function buildCustomOperatorTools(options) {
|
|
|
289
336
|
});
|
|
290
337
|
});
|
|
291
338
|
}
|
|
339
|
+
const ASK_USER_QUESTION_DESCRIPTION = "Ask the user concise questions when a confirmation, choice, or missing detail is required before proceeding. " +
|
|
340
|
+
"The tool pauses until the user answers. Use it instead of writing questions as ordinary assistant text. " +
|
|
341
|
+
"Send one or more questions with stable ids; recommended options should be first and end with (Recommended).";
|
|
342
|
+
async function buildAskUserQuestionTool(options) {
|
|
343
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
344
|
+
import("typebox"),
|
|
345
|
+
import("@earendil-works/pi-coding-agent"),
|
|
346
|
+
]);
|
|
347
|
+
return defineTool({
|
|
348
|
+
name: "ask_user_question",
|
|
349
|
+
label: "Ask user",
|
|
350
|
+
description: ASK_USER_QUESTION_DESCRIPTION,
|
|
351
|
+
promptSnippet: ASK_USER_QUESTION_DESCRIPTION,
|
|
352
|
+
parameters: Type.Object({
|
|
353
|
+
questions: Type.Array(Type.Object({
|
|
354
|
+
id: Type.String({ description: "Stable question id echoed in the answer." }),
|
|
355
|
+
question: Type.String({ description: "Specific user-facing question." }),
|
|
356
|
+
header: Type.Optional(Type.String({ description: "Optional short heading." })),
|
|
357
|
+
detail: Type.Optional(Type.String({ description: "Optional Markdown context shown below the question." })),
|
|
358
|
+
options: Type.Optional(Type.Array(Type.Object({
|
|
359
|
+
label: Type.String({ description: "Short user-facing option label." }),
|
|
360
|
+
description: Type.Optional(Type.String({ description: "One sentence explaining impact or tradeoff." })),
|
|
361
|
+
}, { additionalProperties: false }))),
|
|
362
|
+
multi_select: Type.Optional(Type.Boolean({ description: "Allow more than one option." })),
|
|
363
|
+
}, { additionalProperties: false }), { minItems: 1, maxItems: 20 }),
|
|
364
|
+
}, { additionalProperties: false }),
|
|
365
|
+
async execute(toolCallId, params, signal) {
|
|
366
|
+
const answer = await options.registry.ask({
|
|
367
|
+
sessionId: options.sessionId,
|
|
368
|
+
toolCallId,
|
|
369
|
+
questions: params.questions,
|
|
370
|
+
signal,
|
|
371
|
+
});
|
|
372
|
+
return {
|
|
373
|
+
content: [{ type: "text", text: JSON.stringify(answer) }],
|
|
374
|
+
details: answer,
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
const TODO_WRITE_DESCRIPTION = "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (no partial updates, no per-item edits). Before starting multi-step work, add one todo per concrete step. Keep at least one todo `in_progress` while work remains. Mark a todo `completed` immediately when it is done (do not batch completions) and resend the full list as progress or DAG state advances. Skip the list for simple Q&A, read-only lookups, and single-step tasks. Use it for multi-step implementation and whenever you prepare, run, or supervise a DAG — call `todo_write` before entering the DAG tool chain. Phrase DAG todos as user-facing phases users can understand, not copies of internal DAG nodes. Statuses: `pending` / `in_progress` / `completed`.";
|
|
380
|
+
async function buildTodoWriteTool(options) {
|
|
381
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
382
|
+
import("typebox"),
|
|
383
|
+
import("@earendil-works/pi-coding-agent"),
|
|
384
|
+
]);
|
|
385
|
+
return defineTool({
|
|
386
|
+
name: "todo_write",
|
|
387
|
+
label: "Update todo list",
|
|
388
|
+
description: TODO_WRITE_DESCRIPTION,
|
|
389
|
+
promptSnippet: TODO_WRITE_DESCRIPTION,
|
|
390
|
+
parameters: Type.Object({
|
|
391
|
+
todos: Type.Array(Type.Object({
|
|
392
|
+
content: Type.String({ description: "Short imperative task description." }),
|
|
393
|
+
status: Type.Union([
|
|
394
|
+
Type.Literal("pending"),
|
|
395
|
+
Type.Literal("in_progress"),
|
|
396
|
+
Type.Literal("completed"),
|
|
397
|
+
]),
|
|
398
|
+
}, { additionalProperties: false })),
|
|
399
|
+
}, { additionalProperties: false }),
|
|
400
|
+
async execute(toolCallId, params) {
|
|
401
|
+
const result = options.registry.write(options.sessionId, toolCallId, params.todos);
|
|
402
|
+
return {
|
|
403
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
404
|
+
details: result,
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
}
|
|
292
409
|
/**
|
|
293
410
|
* Build the safe explore custom tools (roadmap M0-A / G16): safe-read,
|
|
294
411
|
* safe-grep, gitStatus, gitDiff. These replace the bare SDK bash/read/grep so
|
|
@@ -765,9 +882,26 @@ export class ConsolePiRuntime {
|
|
|
765
882
|
* after a PATCH sees the latest resource-preferences.json (not create-time
|
|
766
883
|
* closure state).
|
|
767
884
|
*/
|
|
768
|
-
async buildResourceLoaderOptions(base = {}) {
|
|
885
|
+
async buildResourceLoaderOptions(base = {}, sessionId) {
|
|
886
|
+
const inheritedFactories = Array.isArray(base.extensionFactories)
|
|
887
|
+
? base.extensionFactories
|
|
888
|
+
: [];
|
|
889
|
+
const todoContextExtension = sessionId && this.options.todoRegistry
|
|
890
|
+
? createTodoCompactionContextExtension({
|
|
891
|
+
sessionId,
|
|
892
|
+
registry: this.options.todoRegistry,
|
|
893
|
+
})
|
|
894
|
+
: undefined;
|
|
769
895
|
return {
|
|
770
896
|
...base,
|
|
897
|
+
...(todoContextExtension
|
|
898
|
+
? {
|
|
899
|
+
extensionFactories: [
|
|
900
|
+
...inheritedFactories,
|
|
901
|
+
todoContextExtension,
|
|
902
|
+
],
|
|
903
|
+
}
|
|
904
|
+
: {}),
|
|
771
905
|
skillsOverride: (current) => {
|
|
772
906
|
// Synchronous override surface: use last-known prefs via a sync
|
|
773
907
|
// fail-closed path. Prefer async cache filled by ensurePrefs.
|
|
@@ -969,9 +1103,7 @@ export class ConsolePiRuntime {
|
|
|
969
1103
|
const { services } = await this.bindings.createServices({
|
|
970
1104
|
cwd: this.options.cwd,
|
|
971
1105
|
agentDir,
|
|
972
|
-
resourceLoaderOptions: await this.buildResourceLoaderOptions({
|
|
973
|
-
appendSystemPrompt,
|
|
974
|
-
}),
|
|
1106
|
+
resourceLoaderOptions: await this.buildResourceLoaderOptions({ appendSystemPrompt }, sessionId),
|
|
975
1107
|
});
|
|
976
1108
|
if (this.disposedSessions.has(sessionId))
|
|
977
1109
|
return;
|
|
@@ -988,7 +1120,7 @@ export class ConsolePiRuntime {
|
|
|
988
1120
|
}) ?? undefined)
|
|
989
1121
|
: undefined;
|
|
990
1122
|
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
991
|
-
const customTools = await this.loadCustomTools();
|
|
1123
|
+
const customTools = await this.loadCustomTools(sessionId);
|
|
992
1124
|
if (this.disposedSessions.has(sessionId))
|
|
993
1125
|
return;
|
|
994
1126
|
// pi-web parity: do NOT pass `tools`. A closed allowlist becomes
|
|
@@ -1045,12 +1177,26 @@ export class ConsolePiRuntime {
|
|
|
1045
1177
|
}
|
|
1046
1178
|
}
|
|
1047
1179
|
/** Cached session-less custom tool definitions (operator + explore). */
|
|
1048
|
-
async loadCustomTools() {
|
|
1180
|
+
async loadCustomTools(sessionId) {
|
|
1049
1181
|
const [operatorCustomTools, exploreCustomTools] = await Promise.all([
|
|
1050
1182
|
this.loadOperatorCustomTools(),
|
|
1051
1183
|
this.loadExploreCustomTools(),
|
|
1052
1184
|
]);
|
|
1053
|
-
|
|
1185
|
+
const questionTool = this.options.questionRegistry
|
|
1186
|
+
? await buildAskUserQuestionTool({
|
|
1187
|
+
sessionId,
|
|
1188
|
+
registry: this.options.questionRegistry,
|
|
1189
|
+
})
|
|
1190
|
+
: undefined;
|
|
1191
|
+
const todoTool = this.options.todoRegistry
|
|
1192
|
+
? await buildTodoWriteTool({ sessionId, registry: this.options.todoRegistry })
|
|
1193
|
+
: undefined;
|
|
1194
|
+
return [
|
|
1195
|
+
...operatorCustomTools,
|
|
1196
|
+
...exploreCustomTools,
|
|
1197
|
+
...(questionTool ? [questionTool] : []),
|
|
1198
|
+
...(todoTool ? [todoTool] : []),
|
|
1199
|
+
];
|
|
1054
1200
|
}
|
|
1055
1201
|
loadOperatorCustomTools() {
|
|
1056
1202
|
if (!this.options.actionContext)
|
|
@@ -1090,6 +1236,7 @@ export class ConsolePiRuntime {
|
|
|
1090
1236
|
targetSessionId: sessionId,
|
|
1091
1237
|
});
|
|
1092
1238
|
const built = await this.buildSessionWithServices({
|
|
1239
|
+
sessionId,
|
|
1093
1240
|
sessionManager,
|
|
1094
1241
|
model: init.model,
|
|
1095
1242
|
});
|
|
@@ -1817,6 +1964,7 @@ export class ConsolePiRuntime {
|
|
|
1817
1964
|
}
|
|
1818
1965
|
try {
|
|
1819
1966
|
const built = await this.buildSessionWithServices({
|
|
1967
|
+
sessionId,
|
|
1820
1968
|
sessionManager,
|
|
1821
1969
|
model: init.model,
|
|
1822
1970
|
systemPromptSuffix: init.systemPromptSuffix,
|
|
@@ -1907,9 +2055,7 @@ export class ConsolePiRuntime {
|
|
|
1907
2055
|
const { services } = await this.bindings.createServices({
|
|
1908
2056
|
cwd: this.options.cwd,
|
|
1909
2057
|
agentDir,
|
|
1910
|
-
resourceLoaderOptions: await this.buildResourceLoaderOptions({
|
|
1911
|
-
appendSystemPrompt,
|
|
1912
|
-
}),
|
|
2058
|
+
resourceLoaderOptions: await this.buildResourceLoaderOptions({ appendSystemPrompt }, init.sessionId),
|
|
1913
2059
|
});
|
|
1914
2060
|
const modelRuntime = services.modelRuntime;
|
|
1915
2061
|
const resolvedModel = init.model
|
|
@@ -1920,7 +2066,7 @@ export class ConsolePiRuntime {
|
|
|
1920
2066
|
}) ?? undefined)
|
|
1921
2067
|
: undefined;
|
|
1922
2068
|
assertNoWriteToolInList(OPERATOR_CHAT_ALLOWED_TOOLS);
|
|
1923
|
-
const customTools = await this.loadCustomTools();
|
|
2069
|
+
const customTools = await this.loadCustomTools(init.sessionId);
|
|
1924
2070
|
const { session } = await this.bindings.createSessionFromServices({
|
|
1925
2071
|
services,
|
|
1926
2072
|
sessionManager: init.sessionManager,
|
|
@@ -1943,6 +2089,16 @@ export class ConsolePiRuntime {
|
|
|
1943
2089
|
open(sessionId) {
|
|
1944
2090
|
return this.sessions.get(sessionId);
|
|
1945
2091
|
}
|
|
2092
|
+
/** Inject one queued message into the currently running Pi turn. */
|
|
2093
|
+
async steer(sessionId, text, images) {
|
|
2094
|
+
const session = this.sessions.get(sessionId);
|
|
2095
|
+
if (!session || !this.promptOwners.has(sessionId) || session.isStreaming === false) {
|
|
2096
|
+
throw new Error("CHAT_TURN_NOT_ACTIVE");
|
|
2097
|
+
}
|
|
2098
|
+
if (typeof session.steer !== "function")
|
|
2099
|
+
throw new Error("CHAT_STEER_UNAVAILABLE");
|
|
2100
|
+
await session.steer(text, images);
|
|
2101
|
+
}
|
|
1946
2102
|
hasSession(sessionId) {
|
|
1947
2103
|
return this.sessions.has(sessionId);
|
|
1948
2104
|
}
|
|
@@ -1997,6 +2153,12 @@ export class ConsolePiRuntime {
|
|
|
1997
2153
|
}
|
|
1998
2154
|
};
|
|
1999
2155
|
try {
|
|
2156
|
+
if (options?.turnId && this.options.questionRegistry) {
|
|
2157
|
+
this.options.questionRegistry.setTurn(sessionId, options.turnId);
|
|
2158
|
+
}
|
|
2159
|
+
if (options?.turnId && this.options.todoRegistry) {
|
|
2160
|
+
this.options.todoRegistry.startTurn(sessionId, options.turnId);
|
|
2161
|
+
}
|
|
2000
2162
|
// AC-FIX-002: if Stop already fired before readiness, never start model work.
|
|
2001
2163
|
if (options?.signal?.aborted) {
|
|
2002
2164
|
return;
|
|
@@ -2050,19 +2212,96 @@ export class ConsolePiRuntime {
|
|
|
2050
2212
|
}
|
|
2051
2213
|
};
|
|
2052
2214
|
emitContextUsage();
|
|
2215
|
+
let stepStartedAt;
|
|
2216
|
+
let firstTokenAt;
|
|
2217
|
+
let pendingProviderError;
|
|
2218
|
+
let providerErrorEmitted = false;
|
|
2219
|
+
let activeCompaction;
|
|
2220
|
+
const emitPendingProviderError = () => {
|
|
2221
|
+
if (!pendingProviderError)
|
|
2222
|
+
return;
|
|
2223
|
+
onEvent(pendingProviderError);
|
|
2224
|
+
pendingProviderError = undefined;
|
|
2225
|
+
providerErrorEmitted = true;
|
|
2226
|
+
};
|
|
2053
2227
|
unsub = session.subscribe((event) => {
|
|
2054
|
-
const
|
|
2055
|
-
if (
|
|
2056
|
-
|
|
2228
|
+
const observedAt = Date.now();
|
|
2229
|
+
if (event.type === "compaction_start") {
|
|
2230
|
+
activeCompaction = {
|
|
2231
|
+
compactionId: newCompactionId(sessionId),
|
|
2232
|
+
reason: event.reason,
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
const compactionId = event.type === "compaction_start" || event.type === "compaction_end"
|
|
2236
|
+
? activeCompaction?.compactionId ?? newCompactionId(sessionId)
|
|
2237
|
+
: undefined;
|
|
2238
|
+
const mapped = mapSdkEvent(sessionId, event, compactionId);
|
|
2239
|
+
if (event.type === "compaction_end")
|
|
2240
|
+
activeCompaction = undefined;
|
|
2241
|
+
if (mapped?.type === "error") {
|
|
2242
|
+
// Pi represents provider failures as an assistant message_end and may
|
|
2243
|
+
// retry internally. Hold the candidate until the SDK says the attempt
|
|
2244
|
+
// is terminal; a later successful message_end supersedes it.
|
|
2245
|
+
pendingProviderError = mapped;
|
|
2246
|
+
stepStartedAt = undefined;
|
|
2247
|
+
firstTokenAt = undefined;
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
if (mapped?.type === "message_end")
|
|
2251
|
+
pendingProviderError = undefined;
|
|
2252
|
+
if (mapped?.type === "compaction_end" &&
|
|
2253
|
+
mapped.reason === "overflow" &&
|
|
2254
|
+
mapped.status === "completed" &&
|
|
2255
|
+
mapped.willRetry) {
|
|
2256
|
+
// The overflow error was a recoverable attempt fact. Pi has already
|
|
2257
|
+
// committed the compacted session and scheduled the continuation.
|
|
2258
|
+
pendingProviderError = undefined;
|
|
2259
|
+
}
|
|
2260
|
+
if (mapped?.type === "agent_settled") {
|
|
2261
|
+
emitPendingProviderError();
|
|
2262
|
+
}
|
|
2263
|
+
if (mapped?.type === "agent_start") {
|
|
2264
|
+
stepStartedAt = observedAt;
|
|
2265
|
+
firstTokenAt = undefined;
|
|
2266
|
+
}
|
|
2267
|
+
if (mapped?.type === "message_update" && firstTokenAt === undefined) {
|
|
2268
|
+
firstTokenAt = observedAt;
|
|
2269
|
+
}
|
|
2057
2270
|
// Pi puts per-assistant-message usage on `message.usage` ({ input, output,
|
|
2058
2271
|
// cacheRead, cacheWrite, totalTokens, cost }). Emit on message_end so
|
|
2059
2272
|
// multi-step tool turns accumulate like pi-web SessionInfoBar; turn_end
|
|
2060
2273
|
// repeats the final message and must not double-count.
|
|
2061
|
-
if (
|
|
2274
|
+
if (mapped?.type === "message_end") {
|
|
2062
2275
|
const usage = extractUsageSample(event);
|
|
2276
|
+
const timing = stepStartedAt === undefined
|
|
2277
|
+
? undefined
|
|
2278
|
+
: {
|
|
2279
|
+
llmMs: Math.max(0, observedAt - stepStartedAt),
|
|
2280
|
+
...(firstTokenAt === undefined
|
|
2281
|
+
? {}
|
|
2282
|
+
: {
|
|
2283
|
+
ttftMs: Math.max(0, firstTokenAt - stepStartedAt),
|
|
2284
|
+
decodeMs: Math.max(0, observedAt - firstTokenAt),
|
|
2285
|
+
}),
|
|
2286
|
+
...(usage?.outputTokens === undefined
|
|
2287
|
+
? {}
|
|
2288
|
+
: { outputTokens: usage.outputTokens }),
|
|
2289
|
+
};
|
|
2290
|
+
onEvent({ ...mapped, ...(timing ? { timing } : {}) });
|
|
2063
2291
|
if (usage)
|
|
2064
2292
|
onEvent({ type: "usage", sessionId, usage });
|
|
2065
2293
|
emitContextUsage();
|
|
2294
|
+
stepStartedAt = undefined;
|
|
2295
|
+
firstTokenAt = undefined;
|
|
2296
|
+
}
|
|
2297
|
+
else if (mapped) {
|
|
2298
|
+
onEvent(mapped);
|
|
2299
|
+
if (mapped.type === "tool_result") {
|
|
2300
|
+
// The next model request starts after the final completed tool. If
|
|
2301
|
+
// tools run concurrently, the last result naturally wins.
|
|
2302
|
+
stepStartedAt = observedAt;
|
|
2303
|
+
firstTokenAt = undefined;
|
|
2304
|
+
}
|
|
2066
2305
|
}
|
|
2067
2306
|
// Keep the context fill bar live through the turn (pi-web SessionInfoBar).
|
|
2068
2307
|
if (event.type === "agent_start" ||
|
|
@@ -2094,6 +2333,7 @@ export class ConsolePiRuntime {
|
|
|
2094
2333
|
// User abort is closed out as Turn state `aborted`; do not publish a
|
|
2095
2334
|
// red ordinary error event for intentional Stop.
|
|
2096
2335
|
if (!options?.signal?.aborted) {
|
|
2336
|
+
emitPendingProviderError();
|
|
2097
2337
|
onEvent({ type: "agent_settled", sessionId });
|
|
2098
2338
|
}
|
|
2099
2339
|
}
|
|
@@ -2104,8 +2344,16 @@ export class ConsolePiRuntime {
|
|
|
2104
2344
|
// User abort: swallow ordinary error publication (AC-FIX-003).
|
|
2105
2345
|
return;
|
|
2106
2346
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2347
|
+
emitPendingProviderError();
|
|
2348
|
+
if (!providerErrorEmitted) {
|
|
2349
|
+
const providerError = parseProviderError(error);
|
|
2350
|
+
onEvent({
|
|
2351
|
+
type: "error",
|
|
2352
|
+
sessionId,
|
|
2353
|
+
message: providerError.message,
|
|
2354
|
+
providerError,
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2109
2357
|
throw error;
|
|
2110
2358
|
}
|
|
2111
2359
|
finally {
|
|
@@ -2114,6 +2362,8 @@ export class ConsolePiRuntime {
|
|
|
2114
2362
|
}
|
|
2115
2363
|
}
|
|
2116
2364
|
finally {
|
|
2365
|
+
if (this.options.questionRegistry)
|
|
2366
|
+
this.options.questionRegistry.clearTurn(sessionId);
|
|
2117
2367
|
this.promptOwners.delete(sessionId);
|
|
2118
2368
|
}
|
|
2119
2369
|
}
|
|
@@ -2243,6 +2493,23 @@ export class ConsolePiRuntime {
|
|
|
2243
2493
|
}
|
|
2244
2494
|
}
|
|
2245
2495
|
function mapAssistantMessageEvent(type, sessionId, message) {
|
|
2496
|
+
const record = message !== null && typeof message === "object"
|
|
2497
|
+
? message
|
|
2498
|
+
: undefined;
|
|
2499
|
+
if (type === "message_end" &&
|
|
2500
|
+
record?.stopReason === "error") {
|
|
2501
|
+
const content = extractAssistantContentBlocks(message);
|
|
2502
|
+
const providerError = parseProviderError(typeof record.errorMessage === "string"
|
|
2503
|
+
? record.errorMessage
|
|
2504
|
+
: "Provider request failed");
|
|
2505
|
+
return {
|
|
2506
|
+
type: "error",
|
|
2507
|
+
sessionId,
|
|
2508
|
+
message: providerError.message,
|
|
2509
|
+
providerError,
|
|
2510
|
+
...(content.length > 0 ? { content } : {}),
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2246
2513
|
const content = extractAssistantContentBlocks(message);
|
|
2247
2514
|
return {
|
|
2248
2515
|
type,
|
|
@@ -2251,7 +2518,7 @@ function mapAssistantMessageEvent(type, sessionId, message) {
|
|
|
2251
2518
|
...(content.length > 0 ? { content } : {}),
|
|
2252
2519
|
};
|
|
2253
2520
|
}
|
|
2254
|
-
function mapSdkEvent(sessionId, event) {
|
|
2521
|
+
function mapSdkEvent(sessionId, event, compactionId) {
|
|
2255
2522
|
switch (event.type) {
|
|
2256
2523
|
case "agent_start":
|
|
2257
2524
|
return { type: "agent_start", sessionId };
|
|
@@ -2292,10 +2559,56 @@ function mapSdkEvent(sessionId, event) {
|
|
|
2292
2559
|
};
|
|
2293
2560
|
case "agent_settled":
|
|
2294
2561
|
return { type: "agent_settled", sessionId };
|
|
2562
|
+
case "compaction_start":
|
|
2563
|
+
return {
|
|
2564
|
+
type: "compaction_start",
|
|
2565
|
+
sessionId,
|
|
2566
|
+
compactionId: compactionId ?? newCompactionId(sessionId),
|
|
2567
|
+
reason: event.reason,
|
|
2568
|
+
};
|
|
2569
|
+
case "compaction_end": {
|
|
2570
|
+
const snapshot = event.result
|
|
2571
|
+
? projectCompactSnapshot(event.result)
|
|
2572
|
+
: undefined;
|
|
2573
|
+
return {
|
|
2574
|
+
type: "compaction_end",
|
|
2575
|
+
sessionId,
|
|
2576
|
+
compactionId: compactionId ?? newCompactionId(sessionId),
|
|
2577
|
+
reason: event.reason,
|
|
2578
|
+
status: event.aborted
|
|
2579
|
+
? "aborted"
|
|
2580
|
+
: snapshot
|
|
2581
|
+
? "completed"
|
|
2582
|
+
: "failed",
|
|
2583
|
+
...(snapshot
|
|
2584
|
+
? {
|
|
2585
|
+
summary: snapshot.summary,
|
|
2586
|
+
...(snapshot.firstKeptEntryId
|
|
2587
|
+
? { firstKeptEntryId: snapshot.firstKeptEntryId }
|
|
2588
|
+
: {}),
|
|
2589
|
+
...(snapshot.tokensBefore === undefined
|
|
2590
|
+
? {}
|
|
2591
|
+
: { tokensBefore: snapshot.tokensBefore }),
|
|
2592
|
+
...(snapshot.estimatedTokensAfter === undefined
|
|
2593
|
+
? {}
|
|
2594
|
+
: {
|
|
2595
|
+
estimatedTokensAfter: snapshot.estimatedTokensAfter,
|
|
2596
|
+
}),
|
|
2597
|
+
}
|
|
2598
|
+
: {}),
|
|
2599
|
+
willRetry: Boolean(event.willRetry),
|
|
2600
|
+
...(event.errorMessage
|
|
2601
|
+
? { errorMessage: redactRuntimeText(event.errorMessage, 600) }
|
|
2602
|
+
: {}),
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2295
2605
|
default:
|
|
2296
2606
|
return undefined;
|
|
2297
2607
|
}
|
|
2298
2608
|
}
|
|
2609
|
+
function newCompactionId(sessionId) {
|
|
2610
|
+
return `${sessionId}:compaction:${randomBytes(8).toString("hex")}`;
|
|
2611
|
+
}
|
|
2299
2612
|
async function waitForIdle(session, options) {
|
|
2300
2613
|
// Default 45s — shorter than the previous 120s so a stuck SDK session does
|
|
2301
2614
|
// not pin an HTTP connection for two full minutes. The SDK agent turn
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const MAX_RAW_CHARS = 8_000;
|
|
2
|
+
const MAX_MESSAGE_CHARS = 1_000;
|
|
3
|
+
function asRecord(value) {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
5
|
+
? value
|
|
6
|
+
: undefined;
|
|
7
|
+
}
|
|
8
|
+
function bounded(value, limit) {
|
|
9
|
+
return value.length <= limit ? value : `${value.slice(0, limit - 1)}…`;
|
|
10
|
+
}
|
|
11
|
+
function scrub(value) {
|
|
12
|
+
return value
|
|
13
|
+
.replace(/((?:authorization|api[-_]?key|access[-_]?token|refresh[-_]?token|secret)["']?\s*[:=]\s*["']?)([^"'\s,}]+)/gi, "$1[REDACTED]")
|
|
14
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]");
|
|
15
|
+
}
|
|
16
|
+
function parseEnvelope(raw) {
|
|
17
|
+
const candidates = [raw];
|
|
18
|
+
const start = raw.indexOf("{");
|
|
19
|
+
const end = raw.lastIndexOf("}");
|
|
20
|
+
if (start >= 0 && end > start)
|
|
21
|
+
candidates.push(raw.slice(start, end + 1));
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
try {
|
|
24
|
+
const record = asRecord(JSON.parse(candidate));
|
|
25
|
+
if (record)
|
|
26
|
+
return record;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Plain text and HTTP-prefixed provider errors are expected here.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
function stringField(record, key) {
|
|
35
|
+
const value = record?.[key];
|
|
36
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
37
|
+
}
|
|
38
|
+
function codeField(record) {
|
|
39
|
+
const value = record?.code ?? record?.status ?? record?.statusCode;
|
|
40
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
41
|
+
return value;
|
|
42
|
+
if (typeof value === "string" && value.trim()) {
|
|
43
|
+
const trimmed = value.trim();
|
|
44
|
+
return /^\d{3}$/.test(trimmed) ? Number(trimmed) : trimmed;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
function classify(input) {
|
|
49
|
+
const haystack = `${input.message}\n${input.raw}`.toLowerCase();
|
|
50
|
+
const status = typeof input.code === "number" ? input.code : undefined;
|
|
51
|
+
if (status === 401 ||
|
|
52
|
+
status === 403 ||
|
|
53
|
+
/认证|鉴权|unauthori[sz]ed|forbidden|authentication|invalid api[-_ ]?key/.test(haystack))
|
|
54
|
+
return "authentication";
|
|
55
|
+
if (/请求上下文过大|上下文.{0,12}(?:过大|太大|过长|超限)|context_length_exceeded|maximum context|context.{0,24}(?:length|window|limit|large|long)|too many tokens/.test(haystack))
|
|
56
|
+
return "context-overflow";
|
|
57
|
+
if (status === 429 ||
|
|
58
|
+
/rate.?limit|too many requests|请求.{0,8}(?:频繁|过多)|限流/.test(haystack))
|
|
59
|
+
return "rate-limit";
|
|
60
|
+
return "provider";
|
|
61
|
+
}
|
|
62
|
+
/** Convert an SDK/provider error into a bounded durable timeline projection. */
|
|
63
|
+
export function parseProviderError(value) {
|
|
64
|
+
const source = typeof value === "string"
|
|
65
|
+
? value
|
|
66
|
+
: value instanceof Error
|
|
67
|
+
? value.message
|
|
68
|
+
: String(value ?? "Provider request failed");
|
|
69
|
+
const raw = bounded(scrub(source.trim()), MAX_RAW_CHARS);
|
|
70
|
+
const envelope = parseEnvelope(raw);
|
|
71
|
+
const nested = asRecord(envelope?.error);
|
|
72
|
+
const firstLine = raw.split(/\r?\n/, 1)[0]?.trim();
|
|
73
|
+
const message = bounded(stringField(nested, "message") ??
|
|
74
|
+
stringField(envelope, "message") ??
|
|
75
|
+
(firstLine || undefined) ??
|
|
76
|
+
"Provider request failed", MAX_MESSAGE_CHARS);
|
|
77
|
+
const code = codeField(nested) ?? codeField(envelope);
|
|
78
|
+
const type = stringField(nested, "type") ?? stringField(envelope, "type");
|
|
79
|
+
return {
|
|
80
|
+
message,
|
|
81
|
+
raw,
|
|
82
|
+
...(code === undefined ? {} : { code }),
|
|
83
|
+
...(type ? { type } : {}),
|
|
84
|
+
category: classify({ message, raw, code }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function providerErrorTitle(category) {
|
|
88
|
+
switch (category) {
|
|
89
|
+
case "authentication":
|
|
90
|
+
return "认证失败";
|
|
91
|
+
case "context-overflow":
|
|
92
|
+
return "上下文过大";
|
|
93
|
+
case "rate-limit":
|
|
94
|
+
return "请求过于频繁";
|
|
95
|
+
default:
|
|
96
|
+
return "Provider 请求失败";
|
|
97
|
+
}
|
|
98
|
+
}
|