@tea-agent/loop-agent 0.42.0-next.1 → 0.42.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/AGENTS.md +1 -1
- package/CHANGELOG.md +319 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/dag/run-dag.js +41 -0
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +6 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3976 -76
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +978 -188
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +105 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +64 -25
- package/dist/task/source-prepare/prepare.js +126 -21
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +258 -11
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +165 -71
- package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
- package/dist/worker/console/chat/session-catalog.js +2 -0
- package/dist/worker/console/chat/session-store.js +159 -70
- package/dist/worker/console/chat/shortcuts.js +19 -3
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/server.js +23 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
- package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
- package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
- package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/console/workspace-context.js +115 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/dag-helpers.js +4 -2
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/inspect-workspace.js +23 -0
- package/dist/worker/observe/static/kpi.js +2 -2
- package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
- package/dist/worker/observe/static/operator-chrome.js +37 -23
- package/dist/worker/observe/static/relations.js +2 -2
- package/dist/worker/observe/static/router.d.ts +12 -1
- package/dist/worker/observe/static/router.js +48 -6
- package/dist/worker/observe/static/run-processing.js +4 -2
- package/dist/worker/observe/static/shell-chrome.js +2 -2
- package/dist/worker/observe/static/state.js +5 -0
- package/dist/worker/observe/static/styles.css +95 -0
- package/dist/worker/observe/static/views/dag-inspector.js +14 -7
- package/dist/worker/observe/static/views/dag.js +10 -2
- package/dist/worker/observe/static/views/dags.js +3 -1
- package/dist/worker/observe/static/views/dashboard.js +14 -6
- package/dist/worker/observe/static/views/pool.js +7 -2
- package/dist/worker/observe/static/views/run.js +12 -3
- package/dist/worker/observe/static/views/session-timeline.js +69 -10
- package/dist/worker/observe/static/views/task.js +7 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
- package/dist/workflows/dag/frontend-recovery-run.js +166 -34
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +252 -25
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +319 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +1028 -562
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +828 -4
- package/dist/workflows/dag/prompt.js +70 -3
- package/dist/workflows/dag/recovery-lease.js +170 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +315 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +44 -6
- package/dist/workflows/dag/rerun-task.js +251 -13
- package/dist/workflows/dag/retry-policy.js +219 -104
- package/dist/workflows/dag/runner.js +580 -124
- package/dist/workflows/dag/scheduler.js +133 -20
- package/dist/workflows/dag/types.js +244 -16
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/backend-test-dag.json +23 -20
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +4 -3
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -18,6 +18,15 @@ import { authorizeOperatorChatTool } from "./tools.js";
|
|
|
18
18
|
import { projectOperationForChat } from "./chat-event-store.js";
|
|
19
19
|
import { scrubSecrets } from "./explore-tools.js";
|
|
20
20
|
import { hashDeferredTurnPayload, parseDeferredTurns, } from "./deferred-turn.js";
|
|
21
|
+
/** True only for a durable session that has never received user work. */
|
|
22
|
+
export function isBlankChatSession(record) {
|
|
23
|
+
return (record.state === "active" &&
|
|
24
|
+
!record.title &&
|
|
25
|
+
!record.displayTitle &&
|
|
26
|
+
record.messages.length === 0 &&
|
|
27
|
+
record.toolInvocations.length === 0 &&
|
|
28
|
+
(record.deferredTurns?.length ?? 0) === 0);
|
|
29
|
+
}
|
|
21
30
|
const CHAT_TITLE_MAX_CHARS = 80;
|
|
22
31
|
/** Safe, one-line bounded title used for placeholders and model output. */
|
|
23
32
|
export function cleanChatTitle(value) {
|
|
@@ -38,6 +47,16 @@ export function cleanChatTitle(value) {
|
|
|
38
47
|
function recordPath(appData, sessionId) {
|
|
39
48
|
return path.join(appData.chats, `${sessionId}.json`);
|
|
40
49
|
}
|
|
50
|
+
/** True when `sessionFile` lives inside this store's dedicated Pi JSONL dir. */
|
|
51
|
+
export function isConsoleOwnedSessionFile(appData, sessionFile) {
|
|
52
|
+
const value = sessionFile?.trim();
|
|
53
|
+
if (!value)
|
|
54
|
+
return false;
|
|
55
|
+
const root = path.resolve(appData.chatSessions);
|
|
56
|
+
const target = path.resolve(value);
|
|
57
|
+
const relative = path.relative(root, target);
|
|
58
|
+
return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
59
|
+
}
|
|
41
60
|
export class ChatSessionStore {
|
|
42
61
|
appData;
|
|
43
62
|
options;
|
|
@@ -81,6 +100,9 @@ export class ChatSessionStore {
|
|
|
81
100
|
...(typeof raw.modelProvider === "string" ? { modelProvider: raw.modelProvider } : {}),
|
|
82
101
|
...(typeof raw.modelId === "string" ? { modelId: raw.modelId } : {}),
|
|
83
102
|
...(typeof raw.thinkingLevel === "string" ? { thinkingLevel: raw.thinkingLevel } : {}),
|
|
103
|
+
...(typeof raw.telemetrySessionId === "string" && raw.telemetrySessionId
|
|
104
|
+
? { telemetrySessionId: raw.telemetrySessionId }
|
|
105
|
+
: {}),
|
|
84
106
|
...(typeof raw.workspaceId === "string" && raw.workspaceId
|
|
85
107
|
? { workspaceId: raw.workspaceId }
|
|
86
108
|
: {}),
|
|
@@ -165,6 +187,7 @@ export class ChatSessionStore {
|
|
|
165
187
|
const now = new Date().toISOString();
|
|
166
188
|
const record = {
|
|
167
189
|
...source,
|
|
190
|
+
telemetrySessionId: undefined,
|
|
168
191
|
sessionId: input.targetSessionId,
|
|
169
192
|
state: "active",
|
|
170
193
|
...(input.targetSessionFile ? { sessionFile: input.targetSessionFile } : { sessionFile: undefined }),
|
|
@@ -179,6 +202,19 @@ export class ChatSessionStore {
|
|
|
179
202
|
return record;
|
|
180
203
|
});
|
|
181
204
|
}
|
|
205
|
+
/** Atomically backfill the per-conversation simulated identity on first use. */
|
|
206
|
+
async ensureTelemetrySessionId(sessionId, createId) {
|
|
207
|
+
return this.serialize(sessionId, async () => {
|
|
208
|
+
const record = await this.get(sessionId);
|
|
209
|
+
if (!record)
|
|
210
|
+
return undefined;
|
|
211
|
+
if (record.telemetrySessionId)
|
|
212
|
+
return record.telemetrySessionId;
|
|
213
|
+
record.telemetrySessionId = createId();
|
|
214
|
+
await this.persist(record);
|
|
215
|
+
return record.telemetrySessionId;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
182
218
|
async rename(sessionId, title) {
|
|
183
219
|
return this.serialize(sessionId, async () => {
|
|
184
220
|
const record = await this.get(sessionId);
|
|
@@ -237,8 +273,14 @@ export class ChatSessionStore {
|
|
|
237
273
|
const record = await this.get(sessionId);
|
|
238
274
|
if (!record)
|
|
239
275
|
return false;
|
|
276
|
+
const sessionFile = record.sessionFile;
|
|
240
277
|
await rm(recordPath(this.appData, sessionId), { force: true });
|
|
241
278
|
this.options.onRecordChange?.(sessionId, undefined);
|
|
279
|
+
// Record first, then best-effort JSONL unlink. Final consistency is
|
|
280
|
+
// "both gone" — a leftover JSONL is harmless, a leftover record is not.
|
|
281
|
+
if (isConsoleOwnedSessionFile(this.appData, sessionFile) && sessionFile) {
|
|
282
|
+
await rm(sessionFile, { force: true }).catch(() => undefined);
|
|
283
|
+
}
|
|
242
284
|
return true;
|
|
243
285
|
});
|
|
244
286
|
}
|
|
@@ -384,6 +426,44 @@ export class ChatSessionStore {
|
|
|
384
426
|
return { deferredTurn: existing, clearedRevision: existing.revision + 1 };
|
|
385
427
|
});
|
|
386
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Commit Pi's steering acceptance as one durable record transition. Removing
|
|
431
|
+
* the dispatching queue item and appending its user message together prevents
|
|
432
|
+
* a crash between two writes from replaying or losing the accepted input.
|
|
433
|
+
*/
|
|
434
|
+
async acceptSteeringMessage(sessionId, clientRequestId, text) {
|
|
435
|
+
return this.serialize(sessionId, async () => {
|
|
436
|
+
const record = await this.get(sessionId);
|
|
437
|
+
if (!record)
|
|
438
|
+
throw new Error(`chat session not found: ${sessionId}`);
|
|
439
|
+
const queue = [...(record.deferredTurns ?? [])];
|
|
440
|
+
const index = queue.findIndex((item) => item.clientRequestId === clientRequestId);
|
|
441
|
+
const deferredTurn = index >= 0 ? queue[index] : undefined;
|
|
442
|
+
if (!deferredTurn || deferredTurn.state !== "dispatching")
|
|
443
|
+
return undefined;
|
|
444
|
+
const message = {
|
|
445
|
+
id: `m_${record.messages.length + 1}_${Math.random().toString(36).slice(2, 8)}`,
|
|
446
|
+
role: "user",
|
|
447
|
+
text,
|
|
448
|
+
at: new Date().toISOString(),
|
|
449
|
+
};
|
|
450
|
+
queue.splice(index, 1);
|
|
451
|
+
record.messages.push(message);
|
|
452
|
+
record.deferredTurns = queue;
|
|
453
|
+
record.deferredTurn = queue[0];
|
|
454
|
+
record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
|
|
455
|
+
if (!record.displayTitle)
|
|
456
|
+
record.displayTitle = cleanChatTitle(message.text);
|
|
457
|
+
record.updatedAt = message.at;
|
|
458
|
+
await this.persist(record);
|
|
459
|
+
return {
|
|
460
|
+
record,
|
|
461
|
+
message,
|
|
462
|
+
deferredTurn,
|
|
463
|
+
clearedRevision: deferredTurn.revision + 1,
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
}
|
|
387
467
|
async appendMessage(sessionId, message) {
|
|
388
468
|
return this.serialize(sessionId, async () => {
|
|
389
469
|
const record = await this.get(sessionId);
|
|
@@ -520,84 +600,91 @@ export async function runChatTurn(options) {
|
|
|
520
600
|
// observable to tests and to a reconnecting client reading the record).
|
|
521
601
|
const pending = [];
|
|
522
602
|
let settledEvent;
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
else {
|
|
532
|
-
options.onEvent?.(event);
|
|
603
|
+
let eventChain = Promise.resolve();
|
|
604
|
+
const processEvent = async (event) => {
|
|
605
|
+
if (event.type === "steering_accepted") {
|
|
606
|
+
const committed = await store.acceptSteeringMessage(sessionId, event.clientRequestId, event.text);
|
|
607
|
+
if (committed) {
|
|
608
|
+
await options.onSteeringMessagePersisted?.(committed.record, committed.message, {
|
|
609
|
+
clientRequestId: event.clientRequestId,
|
|
610
|
+
});
|
|
533
611
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
// execution happens in the SDK's customTools[].execute(), which
|
|
550
|
-
// routes to dispatchChatToolCall (Gate 3). Do NOT dispatch here —
|
|
551
|
-
// that would double-execute every tool call (P0.2).
|
|
552
|
-
break;
|
|
553
|
-
case "tool_result": {
|
|
554
|
-
const acceptedOperationId = acceptedOperationIdFromResult(event.toolName, event.result);
|
|
555
|
-
if (acceptedOperationId && options.onOperationAccepted) {
|
|
556
|
-
pending.push(Promise.resolve(options.onOperationAccepted({
|
|
557
|
-
sessionId,
|
|
558
|
-
toolCallId: event.toolCallId,
|
|
559
|
-
toolName: event.toolName,
|
|
560
|
-
operationId: acceptedOperationId,
|
|
561
|
-
})));
|
|
562
|
-
}
|
|
563
|
-
// tool_execution_end carries the real result of the tool call.
|
|
564
|
-
// Persist exactly once (the tool message + audit trail). This is
|
|
565
|
-
// the single point of record for tool outcomes (P0.2).
|
|
566
|
-
const resultText = safeStringifyResult(event.result);
|
|
567
|
-
// ChatSessionStore serializes writes per session, so these two calls
|
|
568
|
-
// are safe to fire concurrently — neither clobbers the other.
|
|
569
|
-
pending.push(store.appendMessage(sessionId, {
|
|
570
|
-
role: "tool",
|
|
571
|
-
text: resultText,
|
|
572
|
-
toolCallId: event.toolCallId,
|
|
573
|
-
toolName: event.toolName,
|
|
574
|
-
isError: event.isError,
|
|
575
|
-
// Durable tool→operation link (plan A): the restored timeline can
|
|
576
|
-
// resolve operation:<id> refs without the live operation-ref event.
|
|
577
|
-
...(acceptedOperationId
|
|
578
|
-
? { operationId: acceptedOperationId }
|
|
579
|
-
: {}),
|
|
580
|
-
}));
|
|
581
|
-
pending.push(store.recordToolInvocation(sessionId, {
|
|
582
|
-
toolCallId: event.toolCallId,
|
|
583
|
-
toolName: event.toolName,
|
|
584
|
-
ok: !event.isError,
|
|
585
|
-
}));
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
case "error":
|
|
589
|
-
turnError = { code: "CHAT_TURN_ERROR", message: event.message };
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (event.type === "agent_settled") {
|
|
615
|
+
// The durable operation-ref linker may perform an async canonical
|
|
616
|
+
// operation read. Hold settled until those writes finish so replay
|
|
617
|
+
// never observes agent_settled before its accepted operation ref.
|
|
618
|
+
settledEvent = event;
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
options.onEvent?.(event);
|
|
622
|
+
}
|
|
623
|
+
switch (event.type) {
|
|
624
|
+
case "message_update":
|
|
625
|
+
case "message_end":
|
|
626
|
+
if (event.type === "message_end") {
|
|
590
627
|
pending.push(store.appendMessage(sessionId, {
|
|
591
628
|
role: "assistant",
|
|
592
|
-
|
|
593
|
-
text: event.providerError.message,
|
|
594
|
-
providerError: event.providerError,
|
|
629
|
+
text: event.text,
|
|
595
630
|
...(event.content && event.content.length > 0
|
|
596
631
|
? { content: event.content }
|
|
597
632
|
: {}),
|
|
598
633
|
}));
|
|
599
|
-
|
|
634
|
+
}
|
|
635
|
+
break;
|
|
636
|
+
case "tool_call":
|
|
637
|
+
// Forwarded to the caller (SSE) for UI feedback. The actual tool
|
|
638
|
+
// execution happens in the SDK's customTools[].execute(), which
|
|
639
|
+
// routes to dispatchChatToolCall (Gate 3). Do NOT dispatch here —
|
|
640
|
+
// that would double-execute every tool call (P0.2).
|
|
641
|
+
break;
|
|
642
|
+
case "tool_result": {
|
|
643
|
+
const acceptedOperationId = acceptedOperationIdFromResult(event.toolName, event.result);
|
|
644
|
+
if (acceptedOperationId && options.onOperationAccepted) {
|
|
645
|
+
pending.push(Promise.resolve(options.onOperationAccepted({
|
|
646
|
+
sessionId,
|
|
647
|
+
toolCallId: event.toolCallId,
|
|
648
|
+
toolName: event.toolName,
|
|
649
|
+
operationId: acceptedOperationId,
|
|
650
|
+
})));
|
|
651
|
+
}
|
|
652
|
+
const resultText = safeStringifyResult(event.result);
|
|
653
|
+
pending.push(store.appendMessage(sessionId, {
|
|
654
|
+
role: "tool",
|
|
655
|
+
text: resultText,
|
|
656
|
+
toolCallId: event.toolCallId,
|
|
657
|
+
toolName: event.toolName,
|
|
658
|
+
isError: event.isError,
|
|
659
|
+
...(acceptedOperationId ? { operationId: acceptedOperationId } : {}),
|
|
660
|
+
}));
|
|
661
|
+
pending.push(store.recordToolInvocation(sessionId, {
|
|
662
|
+
toolCallId: event.toolCallId,
|
|
663
|
+
toolName: event.toolName,
|
|
664
|
+
ok: !event.isError,
|
|
665
|
+
}));
|
|
666
|
+
break;
|
|
600
667
|
}
|
|
668
|
+
case "error":
|
|
669
|
+
turnError = { code: "CHAT_TURN_ERROR", message: event.message };
|
|
670
|
+
pending.push(store.appendMessage(sessionId, {
|
|
671
|
+
role: "assistant",
|
|
672
|
+
kind: "provider-error",
|
|
673
|
+
text: event.providerError.message,
|
|
674
|
+
providerError: event.providerError,
|
|
675
|
+
...(event.content && event.content.length > 0
|
|
676
|
+
? { content: event.content }
|
|
677
|
+
: {}),
|
|
678
|
+
}));
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
try {
|
|
683
|
+
await runtime.prompt(sessionId, text, (event) => {
|
|
684
|
+
// Pi's event callback is synchronous. Serialize projection work so the
|
|
685
|
+
// accepted steering user_message is durable and sequenced before any
|
|
686
|
+
// assistant/tool event that follows it.
|
|
687
|
+
eventChain = eventChain.then(() => processEvent(event)).catch(() => undefined);
|
|
601
688
|
}, {
|
|
602
689
|
signal: options.signal,
|
|
603
690
|
images: options.images,
|
|
@@ -605,10 +692,12 @@ export async function runChatTurn(options) {
|
|
|
605
692
|
});
|
|
606
693
|
}
|
|
607
694
|
catch (error) {
|
|
695
|
+
await eventChain;
|
|
608
696
|
await Promise.allSettled(pending);
|
|
609
697
|
const message = error instanceof Error ? error.message : String(error);
|
|
610
698
|
return { ok: false, error: { code: "CHAT_TURN_FAILED", message } };
|
|
611
699
|
}
|
|
700
|
+
await eventChain;
|
|
612
701
|
await Promise.allSettled(pending);
|
|
613
702
|
if (settledEvent)
|
|
614
703
|
options.onEvent?.(settledEvent);
|
|
@@ -37,11 +37,18 @@ export const SHORTCUTS = [
|
|
|
37
37
|
{
|
|
38
38
|
command: "/reload",
|
|
39
39
|
label: "Reload session",
|
|
40
|
-
description: "重新加载当前 Pi
|
|
40
|
+
description: "重新加载当前 Pi 会话的设置、扩展、技能、提示词、主题、上下文文件及模型与认证配置(不产生消息)",
|
|
41
41
|
action: "reload",
|
|
42
42
|
source: "operator",
|
|
43
43
|
},
|
|
44
44
|
];
|
|
45
|
+
export const SDD_BINGO_SHORTCUT = {
|
|
46
|
+
command: "/sdd-bingo",
|
|
47
|
+
label: "sdd-bingo",
|
|
48
|
+
description: "SDD数据对齐",
|
|
49
|
+
action: "sdd-bingo",
|
|
50
|
+
source: "operator",
|
|
51
|
+
};
|
|
45
52
|
/** Pi Web commands with real browser-equivalent behavior (UI-11). */
|
|
46
53
|
export const WEB_SLASH_COMMANDS = [
|
|
47
54
|
{
|
|
@@ -98,7 +105,7 @@ export function resolveShortcut(input) {
|
|
|
98
105
|
const token = input.trim().split(/\s+/, 1)[0]?.toLowerCase();
|
|
99
106
|
if (!token?.startsWith("/"))
|
|
100
107
|
return undefined;
|
|
101
|
-
const local = [...SHORTCUTS, ...WEB_SLASH_COMMANDS].find((shortcut) => shortcut.command === token);
|
|
108
|
+
const local = [...SHORTCUTS, ...WEB_SLASH_COMMANDS, SDD_BINGO_SHORTCUT].find((shortcut) => shortcut.command === token);
|
|
102
109
|
if (local)
|
|
103
110
|
return { command: local.command, action: local.action };
|
|
104
111
|
return { command: token, action: "unknown" };
|
|
@@ -116,7 +123,7 @@ export function workspaceForShortcut(action) {
|
|
|
116
123
|
* Merge Operator, Web, and dynamic Session commands with conflict dedupe.
|
|
117
124
|
* Priority: operator/web > extension > skill > prompt. /reload is single-card.
|
|
118
125
|
*/
|
|
119
|
-
export function mergeSlashCommands(dynamic = []) {
|
|
126
|
+
export function mergeSlashCommands(dynamic = [], capabilities = {}) {
|
|
120
127
|
const byName = new Map();
|
|
121
128
|
const upsert = (item) => {
|
|
122
129
|
const key = normalizeCommandName(item.command);
|
|
@@ -149,6 +156,15 @@ export function mergeSlashCommands(dynamic = []) {
|
|
|
149
156
|
action: item.action,
|
|
150
157
|
});
|
|
151
158
|
}
|
|
159
|
+
if (capabilities.sddBingo) {
|
|
160
|
+
upsert({
|
|
161
|
+
command: SDD_BINGO_SHORTCUT.command,
|
|
162
|
+
label: SDD_BINGO_SHORTCUT.label,
|
|
163
|
+
description: SDD_BINGO_SHORTCUT.description,
|
|
164
|
+
source: SDD_BINGO_SHORTCUT.source,
|
|
165
|
+
action: SDD_BINGO_SHORTCUT.action,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
152
168
|
for (const item of dynamic) {
|
|
153
169
|
const command = normalizeCommandName(item.command);
|
|
154
170
|
if (!command)
|
|
@@ -96,7 +96,10 @@ export function hydrateSessionThread(raw) {
|
|
|
96
96
|
...(item.operationId ? { operationId: item.operationId } : {}),
|
|
97
97
|
};
|
|
98
98
|
toolCalls.push(entry);
|
|
99
|
-
|
|
99
|
+
// Durable rows with an event sequence are already self-ordering. Only
|
|
100
|
+
// legacy rows need the historical "next assistant" attribution fallback.
|
|
101
|
+
if (item.eventSeq === undefined)
|
|
102
|
+
pendingTools.push(entry);
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
return { messages, toolCalls };
|
|
@@ -151,48 +154,81 @@ function collapseDagProgressItems(items) {
|
|
|
151
154
|
return true;
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
function fillProcessDetails(group) {
|
|
158
|
+
const usedToolIds = new Set();
|
|
159
|
+
const nodes = [];
|
|
160
|
+
let insertionOrder = 0;
|
|
161
|
+
const addNode = (input) => {
|
|
162
|
+
if (input.items.length === 0)
|
|
163
|
+
return;
|
|
164
|
+
nodes.push({
|
|
165
|
+
...(input.eventSeq === undefined ? {} : { eventSeq: input.eventSeq }),
|
|
166
|
+
createdAt: input.createdAt,
|
|
167
|
+
insertionOrder: insertionOrder++,
|
|
168
|
+
items: input.items,
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
const addAssistantNode = (assistant, blocks, includeProviderError = false) => {
|
|
172
|
+
let items = [];
|
|
173
|
+
let anchorSeq = assistant.eventSeq;
|
|
174
|
+
let anchorAt = assistant.createdAt;
|
|
175
|
+
const flushItems = () => {
|
|
176
|
+
addNode({
|
|
177
|
+
items,
|
|
178
|
+
...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
|
|
179
|
+
createdAt: anchorAt,
|
|
180
|
+
});
|
|
181
|
+
items = [];
|
|
182
|
+
};
|
|
183
|
+
for (const block of blocks) {
|
|
184
|
+
if (block.type === "thinking") {
|
|
185
|
+
items.push({ kind: "thinking", thinking: block.thinking });
|
|
186
|
+
continue;
|
|
168
187
|
}
|
|
169
|
-
|
|
188
|
+
if (block.type === "text") {
|
|
189
|
+
if (block.text.trim())
|
|
190
|
+
items.push({ kind: "text", text: block.text });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const tool = group.tools.find((entry) => entry.id === block.toolCallId);
|
|
194
|
+
if (!tool || usedToolIds.has(tool.id))
|
|
195
|
+
continue;
|
|
196
|
+
flushItems();
|
|
197
|
+
usedToolIds.add(tool.id);
|
|
198
|
+
const toolItems = [];
|
|
199
|
+
flushToolBuffer(toolItems, [tool]);
|
|
200
|
+
addNode({
|
|
201
|
+
items: toolItems,
|
|
202
|
+
...(tool.eventSeq === undefined
|
|
203
|
+
? anchorSeq === undefined
|
|
204
|
+
? {}
|
|
205
|
+
: { eventSeq: anchorSeq }
|
|
206
|
+
: { eventSeq: tool.eventSeq }),
|
|
207
|
+
createdAt: tool.createdAt,
|
|
208
|
+
});
|
|
209
|
+
anchorSeq = tool.eventSeq ?? anchorSeq;
|
|
210
|
+
anchorAt = tool.createdAt;
|
|
170
211
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
if (includeProviderError && assistant.providerError) {
|
|
213
|
+
items.push({ kind: "provider-error", error: assistant.providerError });
|
|
214
|
+
}
|
|
215
|
+
flushItems();
|
|
216
|
+
// Preserve the old-data fallback after this stage. Sequenced unmatched
|
|
217
|
+
// tools stay independent and are merged by their own eventSeq below.
|
|
218
|
+
const legacyUnmatched = group.tools.filter((tool) => !usedToolIds.has(tool.id) &&
|
|
219
|
+
tool.eventSeq === undefined &&
|
|
220
|
+
tool.assistantMessageId === assistant.id);
|
|
221
|
+
for (const tool of sortToolsStable(legacyUnmatched)) {
|
|
174
222
|
usedToolIds.add(tool.id);
|
|
223
|
+
const toolItems = [];
|
|
224
|
+
flushToolBuffer(toolItems, [tool]);
|
|
225
|
+
addNode({
|
|
226
|
+
items: toolItems,
|
|
227
|
+
...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
|
|
228
|
+
createdAt: Math.max(anchorAt, tool.createdAt),
|
|
229
|
+
});
|
|
175
230
|
}
|
|
176
|
-
}
|
|
177
|
-
flushToolBuffer(processItems, buffer);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Insert tools attributed to this assistant stage but without a matching
|
|
181
|
-
* content toolCall block (live SSE tool_call frames often arrive before the
|
|
182
|
-
* assistant content does). They follow the stage's thinking/text instead of
|
|
183
|
-
* sinking to the end of the whole turn.
|
|
184
|
-
*/
|
|
185
|
-
function appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds) {
|
|
186
|
-
const unmatched = group.tools.filter((tool) => !usedToolIds.has(tool.id) && tool.assistantMessageId === assistant.id);
|
|
187
|
-
if (unmatched.length === 0)
|
|
188
|
-
return;
|
|
189
|
-
for (const tool of unmatched)
|
|
190
|
-
usedToolIds.add(tool.id);
|
|
191
|
-
flushToolBuffer(processItems, unmatched);
|
|
192
|
-
}
|
|
193
|
-
function fillProcessDetails(group) {
|
|
194
|
-
const usedToolIds = new Set();
|
|
195
|
-
const processItems = [];
|
|
231
|
+
};
|
|
196
232
|
const lastAssistant = group.assistants.at(-1);
|
|
197
233
|
const finalAssistant = lastAssistant?.kind === "provider-error" ? undefined : lastAssistant;
|
|
198
234
|
const finalIndex = finalAssistant
|
|
@@ -202,29 +238,46 @@ function fillProcessDetails(group) {
|
|
|
202
238
|
if (index === finalIndex)
|
|
203
239
|
continue;
|
|
204
240
|
if (assistant.kind === "provider-error" && assistant.providerError) {
|
|
205
|
-
|
|
206
|
-
appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds);
|
|
207
|
-
processItems.push({
|
|
208
|
-
kind: "provider-error",
|
|
209
|
-
error: assistant.providerError,
|
|
210
|
-
});
|
|
241
|
+
addAssistantNode(assistant, getDisplayableAssistantBlocks(assistant.content ?? []), true);
|
|
211
242
|
continue;
|
|
212
243
|
}
|
|
213
|
-
|
|
214
|
-
appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds);
|
|
244
|
+
addAssistantNode(assistant, getDisplayableAssistantBlocks(assistant.content ?? []));
|
|
215
245
|
}
|
|
216
246
|
const blocks = finalAssistant?.content ?? [];
|
|
217
247
|
const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
|
|
218
|
-
emitProcessBlocks(split.processBlocks, group, processItems, usedToolIds);
|
|
219
248
|
if (finalAssistant) {
|
|
220
|
-
|
|
249
|
+
addAssistantNode(finalAssistant, split.processBlocks);
|
|
221
250
|
}
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
251
|
+
// DSH-style peer nodes: a sequenced tool keeps its own anchor instead of
|
|
252
|
+
// being appended to whichever assistant id happens to own it at render time.
|
|
253
|
+
for (const tool of group.tools) {
|
|
254
|
+
if (usedToolIds.has(tool.id) || tool.eventSeq === undefined)
|
|
255
|
+
continue;
|
|
256
|
+
usedToolIds.add(tool.id);
|
|
257
|
+
const items = [];
|
|
258
|
+
flushToolBuffer(items, [tool]);
|
|
259
|
+
nodes.push({
|
|
260
|
+
eventSeq: tool.eventSeq,
|
|
261
|
+
createdAt: tool.createdAt,
|
|
262
|
+
insertionOrder: insertionOrder++,
|
|
263
|
+
items,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const fullySequenced = nodes.every((node) => node.eventSeq !== undefined);
|
|
267
|
+
nodes.sort((a, b) => {
|
|
268
|
+
if (fullySequenced && a.eventSeq !== b.eventSeq) {
|
|
269
|
+
return a.eventSeq - b.eventSeq;
|
|
270
|
+
}
|
|
271
|
+
if (a.createdAt !== b.createdAt)
|
|
272
|
+
return a.createdAt - b.createdAt;
|
|
273
|
+
return a.insertionOrder - b.insertionOrder;
|
|
274
|
+
});
|
|
275
|
+
const processItems = nodes.flatMap((node) => node.items);
|
|
276
|
+
// Bounded fallback for legacy restored rows with neither event sequence nor
|
|
277
|
+
// assistant attribution: keep them at the turn end in stable local order.
|
|
278
|
+
const legacyOrphans = group.tools.filter((tool) => !usedToolIds.has(tool.id));
|
|
279
|
+
if (legacyOrphans.length > 0)
|
|
280
|
+
flushToolBuffer(processItems, legacyOrphans);
|
|
228
281
|
group.processItems = collapseDagProgressItems(processItems);
|
|
229
282
|
if (!finalAssistant)
|
|
230
283
|
return;
|
|
@@ -306,7 +359,9 @@ export function buildTurnProcessGroups(input) {
|
|
|
306
359
|
if (assigned.has(tool.id))
|
|
307
360
|
continue;
|
|
308
361
|
if (tool.turnId) {
|
|
309
|
-
const byTurn = groups.find((group) => group.
|
|
362
|
+
const byTurn = groups.find((group) => group.user?.turnId === tool.turnId ||
|
|
363
|
+
group.assistants.some((assistant) => assistant.turnId === tool.turnId) ||
|
|
364
|
+
group.tools.some((t) => t.turnId === tool.turnId) ||
|
|
310
365
|
group.key === `turn-${tool.turnId}` ||
|
|
311
366
|
(group.user && `restored-${group.user.id}` === tool.turnId));
|
|
312
367
|
if (byTurn) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parseFrontendHumanDecisionProjection } from "../../workflows/dag/frontend-human-decision.js";
|
|
2
|
+
/** Worker/Console persistence seam. Canonical validation and freshness remain in the workflow service. */
|
|
3
|
+
export function createFrontendHumanDecisionAdapter(store) {
|
|
4
|
+
return {
|
|
5
|
+
async load(operationId) {
|
|
6
|
+
const record = await store.get(operationId);
|
|
7
|
+
if (!record || record.revision === undefined || !record.frontendHumanDecision)
|
|
8
|
+
return undefined;
|
|
9
|
+
return { revision: record.revision, projection: parseFrontendHumanDecisionProjection(record.frontendHumanDecision) };
|
|
10
|
+
},
|
|
11
|
+
materialize(input) {
|
|
12
|
+
return store.materializeFrontendHumanDecision(input);
|
|
13
|
+
},
|
|
14
|
+
consume(input) {
|
|
15
|
+
return store.consumeFrontendHumanDecision(input);
|
|
16
|
+
},
|
|
17
|
+
initializeSplit: store.initializeFrontendHumanApprovedSplit.bind(store),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FrontendSplitOrchestrator, frontendSplitProjectionSchema, } from "../../workflows/dag/frontend-split-application-service.js";
|
|
2
|
+
/** Worker/Console boundary adapter. All orchestration policy remains canonical. */
|
|
3
|
+
export function createFrontendSplitOperationAdapter(dependencies) {
|
|
4
|
+
const ports = {
|
|
5
|
+
load: async (operationId) => {
|
|
6
|
+
const record = await dependencies.store.get(operationId);
|
|
7
|
+
return record?.frontendSplit === undefined ? undefined : frontendSplitProjectionSchema.parse(record.frontendSplit);
|
|
8
|
+
},
|
|
9
|
+
compareAndSwap: (input) => dependencies.store.compareAndSwapFrontendSplit(input),
|
|
10
|
+
materialize: async (request) => {
|
|
11
|
+
const result = await dependencies.materializer.materialize(request);
|
|
12
|
+
return { taskId: result.taskId, operationId: result.operationId };
|
|
13
|
+
},
|
|
14
|
+
schedule: dependencies.schedule,
|
|
15
|
+
auditStale: dependencies.auditStale,
|
|
16
|
+
loadChildCloseoutFacts: (input) => dependencies.store.loadChildCloseoutFacts(input),
|
|
17
|
+
settleAggregate: (input) => dependencies.store.settleFrontendSplitAggregate({ ...input, prepareAggregate: dependencies.prepareAggregate }),
|
|
18
|
+
};
|
|
19
|
+
return new FrontendSplitOrchestrator(ports);
|
|
20
|
+
}
|
|
@@ -11,6 +11,9 @@ export { createClosedResourceLoader, createInterviewResourceLoader, tryActivateT
|
|
|
11
11
|
export { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
|
|
12
12
|
export { openConsoleAppData, defaultConsoleAppDataRoot } from "../../infrastructure/console/app-data.js";
|
|
13
13
|
export { OperationStore } from "../../infrastructure/console/operation-store.js";
|
|
14
|
+
export { createFrontendSplitOperationAdapter } from "./frontend-split-operation-adapter.js";
|
|
15
|
+
export { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
|
|
16
|
+
export { FrontendSplitTaskMaterializer } from "../materialize/frontend-split-task-materializer.js";
|
|
14
17
|
export { createOperationEventStore } from "./operation-sse.js";
|
|
15
18
|
export { DagConfirmationStore } from "./dag-confirmation.js";
|
|
16
19
|
export { DraftStore } from "./draft-store.js";
|