@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.10
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/CHANGELOG.md +379 -416
- package/dist/application/evaluation/budget.js +21 -0
- package/dist/application/evaluation/corpus-hash.js +10 -15
- package/dist/application/evaluation/corpus.js +2 -1
- package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
- package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
- package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
- package/dist/application/evaluation/frontend-pair-registration.js +143 -0
- package/dist/application/evaluation/frontend-paired-summary.js +150 -0
- package/dist/application/evaluation/frontend-run-observation.js +144 -0
- package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
- package/dist/application/evaluation/types.js +45 -12
- package/dist/application/task-lifecycle/observe.js +26 -1
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +11 -0
- package/dist/cli/program.js +4 -0
- package/dist/commands/task-advance.js +3 -5
- package/dist/commands/task-source-prepare.js +13 -1
- package/dist/executors/dag-pi/guards.js +4 -0
- package/dist/executors/dag-pi/model-config.js +30 -0
- package/dist/executors/dag-pi/plan/fact-adoption.js +55 -0
- package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +22 -28
- package/dist/executors/dag-pi/plan/finalize-tools.js +259 -0
- package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
- package/dist/executors/dag-pi/plan/record-tools.js +861 -0
- package/dist/executors/dag-pi/plan/schema.js +129 -0
- package/dist/executors/dag-pi/plan/session.js +1 -0
- package/dist/executors/dag-pi/plan/tool-helpers.js +16 -0
- package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
- package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
- package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
- package/dist/executors/dag-pi-executor.js +398 -3384
- package/dist/executors/pi-executor.js +6 -1
- package/dist/executors/pi-sdk-executor.js +112 -19
- package/dist/executors/shell-executor.js +88 -12
- package/dist/executors/shell-presets.js +1 -1
- package/dist/executors/shell-write-guard.js +26 -3
- package/dist/infrastructure/console/operation-store.js +48 -14
- package/dist/infrastructure/evaluation/corpus-store.js +37 -12
- package/dist/shared/file-identity.js +23 -0
- package/dist/shared/git-workspace-status.js +482 -0
- package/dist/shared/operator/capabilities.js +10 -5
- package/dist/shared/path-safety.js +2 -2
- package/dist/shared/wizard-local-telemetry.js +51 -0
- package/dist/task/config-types.js +22 -8
- package/dist/task/contract/project.js +13 -0
- package/dist/task/contract/schema.js +2 -7
- package/dist/task/frontend-preflight.js +1 -1
- package/dist/task/source-prepare/parse-intent.js +124 -5
- package/dist/task/source-prepare/prepare.js +6 -1
- package/dist/task/source-prepare/semantic-intake.js +18 -3
- package/dist/task/source-prepare/source-execution.js +65 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
- package/dist/task/source-prepare/source-provider-budget.js +290 -0
- package/dist/worker/console/chat/assistant-content.js +5 -34
- package/dist/worker/console/chat/chat-event-store.js +62 -10
- package/dist/worker/console/chat/context-insights.js +723 -0
- package/dist/worker/console/chat/conversation-nodes.js +383 -0
- package/dist/worker/console/chat/db-connections.js +854 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
- package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
- package/dist/worker/console/chat/pi-runtime.js +445 -36
- package/dist/worker/console/chat/provider-error.js +0 -12
- package/dist/worker/console/chat/repo-browser.js +52 -4
- package/dist/worker/console/chat/routes.js +577 -72
- package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
- package/dist/worker/console/chat/session-store.js +147 -19
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/timeline-message-order.js +22 -0
- package/dist/worker/console/chat/tools.js +34 -0
- package/dist/worker/console/chat/turn-process.js +237 -66
- package/dist/worker/console/chat/user-turn-outline.js +19 -0
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/dag-confirmation.js +3 -0
- package/dist/worker/console/dag-execution-receipt.js +3 -0
- package/dist/worker/console/operator-actions.js +32 -3
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/prd-intake-bridge.js +5 -12
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/server.js +5 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
- package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
- package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
- package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
- package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
- package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
- package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
- package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
- package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-context.js +2 -0
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/dist/worker/observe/node-context-insights.js +479 -0
- package/dist/worker/observe/static/console-theme.css +174 -0
- package/dist/worker/observe/static/console-theme.js +36 -0
- package/dist/worker/observe/static/operator-chrome.css +85 -0
- package/dist/worker/observe/static/operator-chrome.js +51 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
- package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
- package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
- package/dist/workflows/dag/budget-enforcement.js +31 -2
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-contract-facts.js +11 -8
- package/dist/workflows/dag/frontend-design-policy.js +6 -6
- package/dist/workflows/dag/frontend-durable-tools.js +90 -70
- package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
- package/dist/workflows/dag/frontend-plan-canary.js +66 -16
- package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
- package/dist/workflows/dag/frontend-plan-progress.js +92 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
- package/dist/workflows/dag/frontend-recovery-run.js +51 -46
- package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
- package/dist/workflows/dag/frontend-review-context.js +29 -1
- package/dist/workflows/dag/frontend-review-scopes.js +92 -2
- package/dist/workflows/dag/frontend-risk.js +8 -3
- package/dist/workflows/dag/frontend-root-observation.js +261 -0
- package/dist/workflows/dag/frontend-session-budget.js +52 -39
- package/dist/workflows/dag/frontend-session-context.js +10 -0
- package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
- package/dist/workflows/dag/frontend-verification-trace.js +6 -0
- package/dist/workflows/dag/frontend-writer-admission.js +3 -1
- package/dist/workflows/dag/init-hybrid.js +108 -37
- package/dist/workflows/dag/node-execution.js +11 -11
- package/dist/workflows/dag/output-protocol.js +1 -0
- package/dist/workflows/dag/rerun-feedback.js +35 -4
- package/dist/workflows/dag/rerun-task.js +62 -66
- package/dist/workflows/dag/runner.js +58 -12
- package/dist/workflows/dag/types.js +15 -8
- package/dist/workflows/loop/benchmark.js +11 -8
- package/docs/README.md +1 -1
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/governance/harness-methodology-verification.md +11 -0
- package/docs/templates/backend-test-dag.json +5 -4
- package/docs/templates/frontend-implementation-contract.schema.json +31 -11
- package/package.json +6 -4
- package/skills/frontend-design-review/SKILL.md +8 -11
- package/skills/frontend-design-review/references/review-checklist.md +3 -4
- package/skills/frontend-review/SKILL.md +5 -1
- package/skills/loop-agent/references/command-reference.md +8 -0
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
|
@@ -265,6 +265,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
265
265
|
const aimaxAuth = new AimaxAuthReader();
|
|
266
266
|
await aimaxAuth.getToken();
|
|
267
267
|
const sddDataAlignment = new SddDataAlignmentService(chatStore, aimaxAuth, deps.fetchImpl);
|
|
268
|
+
actionContext.sddDataAlignment = sddDataAlignment;
|
|
268
269
|
const chatEventEpoch = randomBytes(12).toString("hex");
|
|
269
270
|
const chatEvents = createChatEventStore({
|
|
270
271
|
appData,
|
|
@@ -318,6 +319,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
318
319
|
terminalRegistry,
|
|
319
320
|
scmService,
|
|
320
321
|
browserAutomation,
|
|
322
|
+
globalAppDataRoot: appData.root,
|
|
321
323
|
});
|
|
322
324
|
let chatRouteDeps;
|
|
323
325
|
const goalRoundDriver = new GoalRoundDriver((sessionId) => driveGoalRound(chatRouteDeps, sessionId));
|
|
@@ -2,7 +2,7 @@ import { lstat, readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { acquireUpdateLock, updateLockPath } from "../../shared/update/state.js";
|
|
4
4
|
import os from "node:os";
|
|
5
|
-
import {
|
|
5
|
+
import { isOperationOccupied } from "../../infrastructure/console/operation-store.js";
|
|
6
6
|
import { readInitRuntimeActivity } from "../observability/init-runtime-activity.js";
|
|
7
7
|
const record = (value) => {
|
|
8
8
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -138,7 +138,7 @@ export class WorkspaceInitializationService {
|
|
|
138
138
|
}
|
|
139
139
|
async check(refresh = false) {
|
|
140
140
|
const latest = (await this.options.operations.list()).filter(op => op.action === "workspaceInitialize").at(-1);
|
|
141
|
-
const running = latest &&
|
|
141
|
+
const running = latest && isOperationOccupied(latest.state);
|
|
142
142
|
if (!running && (refresh || !this.cached || Date.now() - this.cached.at > 30_000)) {
|
|
143
143
|
const generation = this.generation;
|
|
144
144
|
this.pending ??= this.inspect().then(value => { if (generation === this.generation)
|
|
@@ -174,7 +174,7 @@ export class WorkspaceInitializationService {
|
|
|
174
174
|
if (activity.workerProjectionError || activity.activeDagRunIds.length || activity.activeWorkerTasks || activity.activeWorkerBatches || this.options.activeChatTurnCount())
|
|
175
175
|
throw new Error("当前工作区有任务或对话正在运行,请结束后重试");
|
|
176
176
|
const operations = await this.options.operations.list();
|
|
177
|
-
if (operations.some(op => op.operationId !== operationId &&
|
|
177
|
+
if (operations.some(op => op.operationId !== operationId && isOperationOccupied(op.state)))
|
|
178
178
|
throw new Error("当前工作区有其他操作正在运行,请稍后重试");
|
|
179
179
|
const before = await this.inspect();
|
|
180
180
|
if (before.state === "error")
|
|
@@ -2176,6 +2176,12 @@ function isExecutedNodeStatus(status) {
|
|
|
2176
2176
|
return false;
|
|
2177
2177
|
return EXECUTED_NODE_STATUSES.has(status.toLowerCase());
|
|
2178
2178
|
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Node statuses that have not reached a terminal outcome. A live node's last
|
|
2181
|
+
* recorded attempt can be a failure that is already being retried, so history
|
|
2182
|
+
* copy must not present it as the final result.
|
|
2183
|
+
*/
|
|
2184
|
+
const LIVE_NODE_STATUSES = new Set(["running", "started", "paused"]);
|
|
2179
2185
|
const PASS_SUCCESS_STATUSES = new Set([
|
|
2180
2186
|
"finished",
|
|
2181
2187
|
"completed",
|
|
@@ -2221,12 +2227,21 @@ export function projectExecutionSummary(options) {
|
|
|
2221
2227
|
const recovered = totalAttemptCount > 1 &&
|
|
2222
2228
|
firstOutcome === "failure" &&
|
|
2223
2229
|
finalOutcome === "success";
|
|
2230
|
+
// A node that is still live (running/started/paused) has not reached a
|
|
2231
|
+
// final outcome: its last *recorded* attempt may have failed while a
|
|
2232
|
+
// retry is already queued. Labelling that "最终失败" told the operator
|
|
2233
|
+
// the run was over when it was still working, so keep the attempt count
|
|
2234
|
+
// and mark the last attempt instead of the node.
|
|
2235
|
+
const stillRunning = options.status !== undefined &&
|
|
2236
|
+
LIVE_NODE_STATUSES.has(options.status.toLowerCase());
|
|
2224
2237
|
// 一次就成功/失败:卡片不展示 historyLabel;Inspector 仍可拿 attempts 元数据。
|
|
2225
2238
|
const historyLabel = totalAttemptCount <= 1
|
|
2226
2239
|
? undefined
|
|
2227
2240
|
: recovered
|
|
2228
2241
|
? `${totalAttemptCount} 次 · 先失败后成功`
|
|
2229
|
-
:
|
|
2242
|
+
: stillRunning && finalOutcome === "failure"
|
|
2243
|
+
? `${totalAttemptCount} 次 · 最近一次失败,仍在重试`
|
|
2244
|
+
: `${totalAttemptCount} 次 · 最终${finalOutcome === "success" ? "成功" : "失败"}`;
|
|
2230
2245
|
return {
|
|
2231
2246
|
totalAttemptCount,
|
|
2232
2247
|
...(recovered ? { recovered: true } : {}),
|
|
@@ -2266,12 +2281,18 @@ export function projectExecutionSummary(options) {
|
|
|
2266
2281
|
}
|
|
2267
2282
|
// 仅一轮 pass 时不在卡片展示(无叙事价值);多轮才展示中文「修复轮次 N」
|
|
2268
2283
|
// 主文案,裸 P1/P2 编号不再作为主标题(原始编号仅 hover/title/诊断)。
|
|
2284
|
+
// 与 attempt 分支同理:节点仍在运行时,最后一个已记录 pass 的失败只是
|
|
2285
|
+
// 阶段性事实,不能当作最终结论。
|
|
2286
|
+
const livePasses = options.status !== undefined &&
|
|
2287
|
+
LIVE_NODE_STATUSES.has(options.status.toLowerCase());
|
|
2269
2288
|
const historyLabel = passes.length <= 1
|
|
2270
2289
|
? undefined
|
|
2271
2290
|
: `修复轮次 ${passes.join(",")}${finalOutcome === "success"
|
|
2272
2291
|
? " · 最终成功"
|
|
2273
2292
|
: finalOutcome === "failure"
|
|
2274
|
-
?
|
|
2293
|
+
? livePasses
|
|
2294
|
+
? " · 最近一轮失败,仍在重试"
|
|
2295
|
+
: " · 最终失败"
|
|
2275
2296
|
: ""}`;
|
|
2276
2297
|
return {
|
|
2277
2298
|
totalAttemptCount: passes.length,
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only context insights projection for a DAG node attempt — the
|
|
3
|
+
* loop-agent counterpart of the dsh-context dashboard, built exclusively from
|
|
4
|
+
* recorded run facts:
|
|
5
|
+
*
|
|
6
|
+
* - context receipt (`<node>/context-receipts/attempt-N.json`) → composition
|
|
7
|
+
* - session events (`<node>/session-events.jsonl`) → per-request usage
|
|
8
|
+
* trend, tool activity, file footprint, timing
|
|
9
|
+
* - node record (`<node>.json`) → skill injection footprint, headline totals
|
|
10
|
+
*
|
|
11
|
+
* Everything is derived deterministically from persisted facts; nothing is
|
|
12
|
+
* estimated beyond what the recorded bytes already contain. All projections are
|
|
13
|
+
* read-only, bounded, and redaction-preserving (paths already pass through
|
|
14
|
+
* redactSecrets in node-transparency before reaching here for receipt data;
|
|
15
|
+
* file-activity paths are redacted in this module).
|
|
16
|
+
*/
|
|
17
|
+
import { readFile } from "node:fs/promises";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { redactSecrets } from "../../shared/preview.js";
|
|
20
|
+
import { isSafeObservabilityIdentifier } from "../observability/event-store.js";
|
|
21
|
+
import { resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
|
|
22
|
+
export const NODE_CONTEXT_INSIGHTS_SCHEMA_VERSION = 1;
|
|
23
|
+
/** Upper bound on session-event lines scanned per attempt (defensive). */
|
|
24
|
+
const SESSION_EVENT_LINE_LIMIT = 50_000;
|
|
25
|
+
function isRecord(value) {
|
|
26
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function asInt(value) {
|
|
29
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
30
|
+
? value
|
|
31
|
+
: null;
|
|
32
|
+
}
|
|
33
|
+
function asString(value) {
|
|
34
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
35
|
+
}
|
|
36
|
+
/** Minimal per-line JSON parse (session events are JSONL). */
|
|
37
|
+
function parseSessionEventLine(line) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(line);
|
|
40
|
+
return isRecord(parsed) ? parsed : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function messageOf(event) {
|
|
47
|
+
const message = event.message;
|
|
48
|
+
return isRecord(message) ? message : null;
|
|
49
|
+
}
|
|
50
|
+
function contentBlocksOf(message) {
|
|
51
|
+
const content = message?.content;
|
|
52
|
+
return Array.isArray(content) ? content : [];
|
|
53
|
+
}
|
|
54
|
+
function usageOf(message) {
|
|
55
|
+
const usage = message?.usage;
|
|
56
|
+
return isRecord(usage) ? usage : null;
|
|
57
|
+
}
|
|
58
|
+
function toolNameOfArgs(args) {
|
|
59
|
+
for (const key of ["path", "file", "file_path", "filePath"]) {
|
|
60
|
+
const value = args[key];
|
|
61
|
+
if (typeof value === "string" && value.length > 0)
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
function searchTargetsOfArgs(args) {
|
|
67
|
+
const targets = [];
|
|
68
|
+
const push = (value) => {
|
|
69
|
+
if (typeof value === "string" && value.length > 0)
|
|
70
|
+
targets.push(value);
|
|
71
|
+
};
|
|
72
|
+
push(args.path);
|
|
73
|
+
push(args.directory);
|
|
74
|
+
push(args.dir);
|
|
75
|
+
const pattern = asString(args.pattern) ?? asString(args.query);
|
|
76
|
+
if (pattern)
|
|
77
|
+
targets.push(`pattern:${pattern.slice(0, 120)}`);
|
|
78
|
+
return targets;
|
|
79
|
+
}
|
|
80
|
+
/** Extract referenced file paths from a bash command string (best effort). */
|
|
81
|
+
function filesFromCommand(command) {
|
|
82
|
+
const files = [];
|
|
83
|
+
const patterns = [
|
|
84
|
+
/(?:^|\s)(?:cat|head|tail|less|more)\s+([^\s;&|]+)/g,
|
|
85
|
+
/(?:^|\s)(?:rg|grep|find|fd|ls)\s+.*?([^\s;&|]*\.[A-Za-z0-9]{1,8})/g,
|
|
86
|
+
/(?:^|\s)(?:sed|awk|sort|uniq|wc)\s+.*?([^\s;&|]*\.[A-Za-z0-9]{1,8})/g,
|
|
87
|
+
];
|
|
88
|
+
for (const pattern of patterns) {
|
|
89
|
+
let match;
|
|
90
|
+
while ((match = pattern.exec(command)) !== null) {
|
|
91
|
+
const candidate = match[1];
|
|
92
|
+
if (candidate && candidate.length > 1)
|
|
93
|
+
files.push(candidate);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return files;
|
|
97
|
+
}
|
|
98
|
+
function classifyFileOp(toolName) {
|
|
99
|
+
const name = toolName.toLowerCase();
|
|
100
|
+
if (["read", "read_file", "readfile", "view"].includes(name))
|
|
101
|
+
return "read";
|
|
102
|
+
if (["write", "write_file", "writefile", "create"].includes(name))
|
|
103
|
+
return "write";
|
|
104
|
+
if (["edit", "str_replace", "str_replace_editor", "apply_patch"].includes(name))
|
|
105
|
+
return "edit";
|
|
106
|
+
if (["grep", "rg", "find", "fd", "search", "glob", "list", "ls"].includes(name))
|
|
107
|
+
return "search";
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function normalizeRepoPath(raw, repoRoot) {
|
|
111
|
+
let value = raw;
|
|
112
|
+
const resolvedRoot = path.resolve(repoRoot);
|
|
113
|
+
const rootVariants = [
|
|
114
|
+
resolvedRoot,
|
|
115
|
+
resolvedRoot.replace(/\\/g, "/"),
|
|
116
|
+
resolvedRoot.replace(/\\/g, "/").toLowerCase(),
|
|
117
|
+
];
|
|
118
|
+
for (const variant of rootVariants) {
|
|
119
|
+
if (value.toLowerCase().startsWith(`${variant.toLowerCase()}\\`) ||
|
|
120
|
+
value.toLowerCase().startsWith(`${variant.toLowerCase()}/`)) {
|
|
121
|
+
value = value.slice(variant.length + 1);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
if (value.toLowerCase() === variant.toLowerCase()) {
|
|
125
|
+
value = ".";
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return value.replace(/\\/g, "/");
|
|
130
|
+
}
|
|
131
|
+
function accumulateFileActivity(fileActivity, key, op) {
|
|
132
|
+
const existing = fileActivity.get(key) ?? {
|
|
133
|
+
path: key,
|
|
134
|
+
reads: 0,
|
|
135
|
+
writes: 0,
|
|
136
|
+
edits: 0,
|
|
137
|
+
searches: 0,
|
|
138
|
+
};
|
|
139
|
+
if (op === "read")
|
|
140
|
+
existing.reads += 1;
|
|
141
|
+
else if (op === "write")
|
|
142
|
+
existing.writes += 1;
|
|
143
|
+
else if (op === "edit")
|
|
144
|
+
existing.edits += 1;
|
|
145
|
+
else
|
|
146
|
+
existing.searches += 1;
|
|
147
|
+
fileActivity.set(key, existing);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Pure projection over parsed session events. Exported for unit tests.
|
|
151
|
+
*/
|
|
152
|
+
export function projectSessionInsights(events, repoRoot) {
|
|
153
|
+
let requestCount = 0;
|
|
154
|
+
let toolCalls = 0;
|
|
155
|
+
let toolErrors = 0;
|
|
156
|
+
let humanInputs = 0;
|
|
157
|
+
let thinkingSteps = 0;
|
|
158
|
+
let inputTokens = 0;
|
|
159
|
+
let outputTokens = 0;
|
|
160
|
+
let cacheReadTokens = 0;
|
|
161
|
+
let cacheWriteTokens = 0;
|
|
162
|
+
let cacheSamples = 0;
|
|
163
|
+
let cacheHits = 0;
|
|
164
|
+
const requests = [];
|
|
165
|
+
const toolCounters = new Map();
|
|
166
|
+
const fileActivity = new Map();
|
|
167
|
+
// Tool calls issued in the current in-flight model request (reset per
|
|
168
|
+
// assistant message_end) so each request row attributes its own calls.
|
|
169
|
+
let pendingToolCalls = 0;
|
|
170
|
+
let pendingThinking = false;
|
|
171
|
+
for (const event of events) {
|
|
172
|
+
if (!event)
|
|
173
|
+
continue;
|
|
174
|
+
const type = asString(event.type);
|
|
175
|
+
if (type === "tool_execution_start") {
|
|
176
|
+
toolCalls += 1;
|
|
177
|
+
pendingToolCalls += 1;
|
|
178
|
+
const toolName = asString(event.toolName) ?? "unknown";
|
|
179
|
+
const row = toolCounters.get(toolName) ?? {
|
|
180
|
+
toolName,
|
|
181
|
+
calls: 0,
|
|
182
|
+
errors: 0,
|
|
183
|
+
};
|
|
184
|
+
row.calls += 1;
|
|
185
|
+
toolCounters.set(toolName, row);
|
|
186
|
+
const args = isRecord(event.args) ? event.args : {};
|
|
187
|
+
const op = classifyFileOp(toolName);
|
|
188
|
+
if (op === "read" || op === "write" || op === "edit") {
|
|
189
|
+
const target = toolNameOfArgs(args);
|
|
190
|
+
if (target) {
|
|
191
|
+
accumulateFileActivity(fileActivity, normalizeRepoPath(redactSecrets(target), repoRoot), op);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (op === "search") {
|
|
195
|
+
for (const target of searchTargetsOfArgs(args)) {
|
|
196
|
+
accumulateFileActivity(fileActivity, normalizeRepoPath(redactSecrets(target), repoRoot), "search");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (toolName.toLowerCase() === "bash") {
|
|
200
|
+
const command = asString(args.command) ?? "";
|
|
201
|
+
for (const file of filesFromCommand(command)) {
|
|
202
|
+
accumulateFileActivity(fileActivity, normalizeRepoPath(redactSecrets(file), repoRoot), "search");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (type === "tool_execution_end") {
|
|
208
|
+
if (event.isError === true)
|
|
209
|
+
toolErrors += 1;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const message = messageOf(event);
|
|
213
|
+
if (!message)
|
|
214
|
+
continue;
|
|
215
|
+
const role = asString(message.role);
|
|
216
|
+
if (type === "message_start" && role === "assistant") {
|
|
217
|
+
// A new model request begins.
|
|
218
|
+
requestCount += 1;
|
|
219
|
+
pendingToolCalls = 0;
|
|
220
|
+
pendingThinking = false;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (type === "message_end" && role === "assistant") {
|
|
224
|
+
for (const block of contentBlocksOf(message)) {
|
|
225
|
+
if (isRecord(block) && asString(block.type) === "thinking") {
|
|
226
|
+
thinkingSteps += 1;
|
|
227
|
+
pendingThinking = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const usage = usageOf(message);
|
|
231
|
+
const input = asInt(usage?.input);
|
|
232
|
+
const output = asInt(usage?.output);
|
|
233
|
+
const cacheRead = asInt(usage?.cacheRead);
|
|
234
|
+
const cacheWrite = asInt(usage?.cacheWrite);
|
|
235
|
+
if (usage && (input ?? 0) + (cacheRead ?? 0) > 0) {
|
|
236
|
+
cacheSamples += 1;
|
|
237
|
+
if ((cacheRead ?? 0) > 0)
|
|
238
|
+
cacheHits += 1;
|
|
239
|
+
}
|
|
240
|
+
if (input != null)
|
|
241
|
+
inputTokens += input;
|
|
242
|
+
if (output != null)
|
|
243
|
+
outputTokens += output;
|
|
244
|
+
if (cacheRead != null)
|
|
245
|
+
cacheReadTokens += cacheRead;
|
|
246
|
+
if (cacheWrite != null)
|
|
247
|
+
cacheWriteTokens += cacheWrite;
|
|
248
|
+
requests.push({
|
|
249
|
+
request: requestCount,
|
|
250
|
+
recordedAt: asString(event.recordedAt),
|
|
251
|
+
model: asString(message.model),
|
|
252
|
+
provider: asString(message.provider),
|
|
253
|
+
usage: usage == null
|
|
254
|
+
? null
|
|
255
|
+
: {
|
|
256
|
+
input,
|
|
257
|
+
output,
|
|
258
|
+
cacheRead,
|
|
259
|
+
cacheWrite,
|
|
260
|
+
totalTokens: asInt(usage.totalTokens),
|
|
261
|
+
},
|
|
262
|
+
stopReason: asString(message.stopReason),
|
|
263
|
+
toolCalls: pendingToolCalls,
|
|
264
|
+
thinking: pendingThinking,
|
|
265
|
+
});
|
|
266
|
+
pendingToolCalls = 0;
|
|
267
|
+
pendingThinking = false;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (type === "message_end" && role === "user") {
|
|
271
|
+
humanInputs += 1;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const toolActivity = [...toolCounters.values()].sort((a, b) => b.calls - a.calls || a.toolName.localeCompare(b.toolName));
|
|
275
|
+
const fileActivityRows = [...fileActivity.values()].sort((a, b) => b.reads + b.writes + b.edits + b.searches -
|
|
276
|
+
(a.reads + a.writes + a.edits + a.searches) ||
|
|
277
|
+
a.path.localeCompare(b.path));
|
|
278
|
+
return {
|
|
279
|
+
requests,
|
|
280
|
+
toolActivity,
|
|
281
|
+
fileActivity: fileActivityRows,
|
|
282
|
+
stats: {
|
|
283
|
+
requests: requestCount,
|
|
284
|
+
toolCalls,
|
|
285
|
+
toolErrors,
|
|
286
|
+
humanInputs,
|
|
287
|
+
thinkingSteps,
|
|
288
|
+
cacheHitRate: cacheSamples > 0 ? cacheHits / cacheSamples : null,
|
|
289
|
+
inputTokens,
|
|
290
|
+
outputTokens,
|
|
291
|
+
cacheReadTokens,
|
|
292
|
+
cacheWriteTokens,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
export async function loadDagNodeContextInsights(repoRoot, dagRunId, nodeId, attempt) {
|
|
297
|
+
if (!isSafeObservabilityIdentifier(dagRunId)) {
|
|
298
|
+
return {
|
|
299
|
+
status: 400,
|
|
300
|
+
body: { error: "Invalid DAG run identifier", reason: "invalid-id" },
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (!isSafeObservabilityIdentifier(nodeId)) {
|
|
304
|
+
return {
|
|
305
|
+
status: 400,
|
|
306
|
+
body: { error: "Invalid node identifier", reason: "invalid-id" },
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const resolvedRun = resolveDagRunJson(repoRoot, dagRunId);
|
|
310
|
+
if (!resolvedRun.ok) {
|
|
311
|
+
return {
|
|
312
|
+
status: resolvedRun.reason === "invalid-id" ? 400 : 404,
|
|
313
|
+
body: { error: "DAG run not found", reason: resolvedRun.reason },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
// Node record (<nodeId>.json) carries attempt metadata + headline totals.
|
|
317
|
+
const recordResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
318
|
+
`${nodeId}.json`,
|
|
319
|
+
]);
|
|
320
|
+
let nodeRecord = null;
|
|
321
|
+
if (recordResolved.ok) {
|
|
322
|
+
try {
|
|
323
|
+
const raw = JSON.parse(await readFile(recordResolved.result.absolutePath, "utf8"));
|
|
324
|
+
if (isRecord(raw))
|
|
325
|
+
nodeRecord = raw;
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
nodeRecord = null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const selectedAttempt = normalizeAttemptNumber(attempt, nodeRecord);
|
|
332
|
+
// Context receipt composition.
|
|
333
|
+
const receiptResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
334
|
+
nodeId,
|
|
335
|
+
"context-receipts",
|
|
336
|
+
`attempt-${selectedAttempt}.json`,
|
|
337
|
+
]);
|
|
338
|
+
let composition = null;
|
|
339
|
+
if (receiptResolved.ok) {
|
|
340
|
+
try {
|
|
341
|
+
const raw = JSON.parse(await readFile(receiptResolved.result.absolutePath, "utf8"));
|
|
342
|
+
if (isRecord(raw) && Array.isArray(raw.orderedComponents)) {
|
|
343
|
+
const byKind = new Map();
|
|
344
|
+
for (const entry of raw.orderedComponents) {
|
|
345
|
+
if (!isRecord(entry))
|
|
346
|
+
continue;
|
|
347
|
+
const kind = asString(entry.kind);
|
|
348
|
+
const bytes = asInt(entry.bytes) ?? 0;
|
|
349
|
+
if (!kind)
|
|
350
|
+
continue;
|
|
351
|
+
const slice = byKind.get(kind) ?? { kind, items: 0, bytes: 0 };
|
|
352
|
+
slice.items += 1;
|
|
353
|
+
slice.bytes += bytes;
|
|
354
|
+
byKind.set(kind, slice);
|
|
355
|
+
}
|
|
356
|
+
composition = [...byKind.values()].sort((a, b) => b.bytes - a.bytes);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
composition = null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Session events for the node (single file across attempts in v1 format).
|
|
364
|
+
const sessionResolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
365
|
+
nodeId,
|
|
366
|
+
"session-events.jsonl",
|
|
367
|
+
]);
|
|
368
|
+
let sessionProjection = null;
|
|
369
|
+
if (sessionResolved.ok) {
|
|
370
|
+
try {
|
|
371
|
+
const raw = await readFile(sessionResolved.result.absolutePath, "utf8");
|
|
372
|
+
const lines = raw.split("\n").slice(0, SESSION_EVENT_LINE_LIMIT);
|
|
373
|
+
const events = lines
|
|
374
|
+
.map((line) => line.trim())
|
|
375
|
+
.filter(Boolean)
|
|
376
|
+
.map(parseSessionEventLine);
|
|
377
|
+
sessionProjection = projectSessionInsights(events, repoRoot);
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
sessionProjection = null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const attemptRecord = findAttemptRecord(nodeRecord, selectedAttempt);
|
|
384
|
+
const timing = timingFromRecords(nodeRecord, attemptRecord);
|
|
385
|
+
const skills = skillInjectionsOf(nodeRecord);
|
|
386
|
+
const body = {
|
|
387
|
+
schemaVersion: NODE_CONTEXT_INSIGHTS_SCHEMA_VERSION,
|
|
388
|
+
dagRunId,
|
|
389
|
+
nodeId,
|
|
390
|
+
attempt: selectedAttempt,
|
|
391
|
+
generatedFrom: {
|
|
392
|
+
sessionEvents: sessionProjection != null,
|
|
393
|
+
contextReceipt: composition != null,
|
|
394
|
+
nodeRecord: nodeRecord != null,
|
|
395
|
+
},
|
|
396
|
+
stats: {
|
|
397
|
+
requests: sessionProjection?.stats.requests ?? 0,
|
|
398
|
+
toolCalls: sessionProjection?.stats.toolCalls ?? 0,
|
|
399
|
+
toolErrors: sessionProjection?.stats.toolErrors ?? 0,
|
|
400
|
+
humanInputs: sessionProjection?.stats.humanInputs ?? 0,
|
|
401
|
+
thinkingSteps: sessionProjection?.stats.thinkingSteps ?? 0,
|
|
402
|
+
tokensUsed: asInt(attemptRecord?.tokensUsed) ?? asInt(nodeRecord?.tokensUsed),
|
|
403
|
+
parsedEvents: asInt(attemptRecord?.parsedEvents) ?? asInt(nodeRecord?.parsedEvents),
|
|
404
|
+
writeToolCallCount: asInt(attemptRecord?.writeToolCallCount) ??
|
|
405
|
+
asInt(nodeRecord?.writeToolCallCount),
|
|
406
|
+
cacheHitRate: sessionProjection?.stats.cacheHitRate ?? null,
|
|
407
|
+
inputTokens: sessionProjection?.stats.inputTokens ?? null,
|
|
408
|
+
outputTokens: sessionProjection?.stats.outputTokens ?? null,
|
|
409
|
+
cacheReadTokens: sessionProjection?.stats.cacheReadTokens ?? null,
|
|
410
|
+
cacheWriteTokens: sessionProjection?.stats.cacheWriteTokens ?? null,
|
|
411
|
+
},
|
|
412
|
+
timing,
|
|
413
|
+
composition,
|
|
414
|
+
skills,
|
|
415
|
+
toolActivity: sessionProjection?.toolActivity ?? [],
|
|
416
|
+
fileActivity: sessionProjection?.fileActivity ?? [],
|
|
417
|
+
usageTrend: sessionProjection?.requests ?? [],
|
|
418
|
+
};
|
|
419
|
+
return { status: 200, body };
|
|
420
|
+
}
|
|
421
|
+
function normalizeAttemptNumber(rawAttempt, nodeRecord) {
|
|
422
|
+
if (Number.isSafeInteger(rawAttempt) && (rawAttempt ?? 0) > 0) {
|
|
423
|
+
return rawAttempt;
|
|
424
|
+
}
|
|
425
|
+
if (typeof nodeRecord?.currentAttempt === "number" &&
|
|
426
|
+
Number.isSafeInteger(nodeRecord.currentAttempt) &&
|
|
427
|
+
nodeRecord.currentAttempt > 0) {
|
|
428
|
+
return nodeRecord.currentAttempt;
|
|
429
|
+
}
|
|
430
|
+
return 1;
|
|
431
|
+
}
|
|
432
|
+
function findAttemptRecord(nodeRecord, attempt) {
|
|
433
|
+
if (!nodeRecord)
|
|
434
|
+
return null;
|
|
435
|
+
const attempts = nodeRecord.attempts;
|
|
436
|
+
if (!Array.isArray(attempts))
|
|
437
|
+
return null;
|
|
438
|
+
for (const entry of attempts) {
|
|
439
|
+
if (isRecord(entry) && entry.attempt === attempt)
|
|
440
|
+
return entry;
|
|
441
|
+
}
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
function timingFromRecords(nodeRecord, attemptRecord) {
|
|
445
|
+
const attemptTiming = isRecord(attemptRecord?.timing)
|
|
446
|
+
? attemptRecord.timing
|
|
447
|
+
: null;
|
|
448
|
+
const nodeTiming = isRecord(nodeRecord?.timing)
|
|
449
|
+
? nodeRecord.timing
|
|
450
|
+
: null;
|
|
451
|
+
return {
|
|
452
|
+
startedAt: asString(attemptRecord?.startedAt) ?? asString(nodeRecord?.startedAt),
|
|
453
|
+
finishedAt: asString(attemptRecord?.finishedAt) ?? asString(nodeRecord?.finishedAt),
|
|
454
|
+
durationMs: asInt(attemptRecord?.durationMs) ?? asInt(nodeRecord?.durationMs),
|
|
455
|
+
executorDurationMs: asInt(attemptTiming?.executorDurationMs) ??
|
|
456
|
+
asInt(nodeTiming?.executorDurationMs),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function skillInjectionsOf(nodeRecord) {
|
|
460
|
+
const raw = nodeRecord?.resolvedSkills;
|
|
461
|
+
if (!Array.isArray(raw))
|
|
462
|
+
return [];
|
|
463
|
+
const skills = [];
|
|
464
|
+
for (const entry of raw) {
|
|
465
|
+
if (!isRecord(entry))
|
|
466
|
+
continue;
|
|
467
|
+
const name = asString(entry.name);
|
|
468
|
+
if (!name)
|
|
469
|
+
continue;
|
|
470
|
+
skills.push({
|
|
471
|
+
name,
|
|
472
|
+
resolution: asString(entry.resolution) ?? "unknown",
|
|
473
|
+
bytes: asInt(entry.bytes),
|
|
474
|
+
truncated: entry.truncated === true,
|
|
475
|
+
injectionMode: asString(entry.injectionMode),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return skills;
|
|
479
|
+
}
|