@tea-agent/loop-agent 0.40.0-next.1 → 0.40.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 +106 -109
- package/README.md +2 -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 +43 -3
- package/dist/executors/workspace-write-snapshot.js +68 -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/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 +129 -4
- 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 +29 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +17 -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 +238 -51
- 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 +265 -237
- 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
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生产 Source Fidelity Pi 适配器(AC-PROD-001..006 / AC-001..006)
|
|
3
|
+
*
|
|
4
|
+
* 为真实 `task advance` 的 prepare-contract 路径提供两个独立的 Pi 调用面:
|
|
5
|
+
* - reconcile:仅叙述性/歧义 ledger fragment 的顺序 reconciliation executor
|
|
6
|
+
* (结构化片段由 ledger-reconciliation.ts 的 isStructuredMappableFragment
|
|
7
|
+
* 走代码映射,零 Pi 调用);
|
|
8
|
+
* - review:context-only / duplicate / conflict 的独立 reviewer(与 reconciler
|
|
9
|
+
* 是不同调用阶段,最多一次有界修订,LEDGER_REVIEW_MAX_REVISIONS=1)。
|
|
10
|
+
*
|
|
11
|
+
* 模型队列镜像 semantic-intake.ts(MED→HIGH→LOW harness 档位 + Pi SDK 目录),
|
|
12
|
+
* 单次尝试沿用有界超时(SOURCE_FIDELITY_ATTEMPT_TIMEOUT_MS),工具面仅只读
|
|
13
|
+
* read/grep/find/ls(无写工具)。所有模型输出经 validateOutput + zod 严格校验:
|
|
14
|
+
* 无效 JSON/schema、越界 canonical ID、排除类 disposition 缺理由一律抛
|
|
15
|
+
* LedgerReconciliationError / LedgerReviewError,由 prepare.ts 既有 catch 转为
|
|
16
|
+
* 稳定 SOURCE_FIDELITY_* 阻断码,绝不回退确定性 heuristics。队列空 / 执行失败 /
|
|
17
|
+
* 超时同样 fail closed。
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { executePiStep } from "../../executors/pi-executor.js";
|
|
21
|
+
import { resolveDagPiModelConfig } from "../../executors/dag-pi-executor.js";
|
|
22
|
+
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
23
|
+
import { listPiSdkAvailableModels } from "../../executors/pi-sdk-executor.js";
|
|
24
|
+
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
25
|
+
import { loadTaskConfig } from "../runtime.js";
|
|
26
|
+
import { buildSemanticIntakeModelQueue, extractJsonObject, } from "./semantic-intake.js";
|
|
27
|
+
import { LEDGER_RECONCILIATION_DEFAULT_BUDGET_CHARS, LedgerReconciliationError, } from "./ledger-reconciliation.js";
|
|
28
|
+
import { LedgerReviewError, } from "./ledger-review.js";
|
|
29
|
+
/** 只读工具面(AC-003):source fidelity Pi 调用无任何写工具。 */
|
|
30
|
+
export const SOURCE_FIDELITY_PI_TOOL_NAMES = [
|
|
31
|
+
"read",
|
|
32
|
+
"grep",
|
|
33
|
+
"find",
|
|
34
|
+
"ls",
|
|
35
|
+
];
|
|
36
|
+
/** 单次 reconciliation/review Pi 尝试的墙钟上限(镜像 SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS)。 */
|
|
37
|
+
export const SOURCE_FIDELITY_ATTEMPT_TIMEOUT_MS = 300_000;
|
|
38
|
+
/** 接线侧门 taskKind(AC-PROD-005)。 */
|
|
39
|
+
export const SOURCE_FIDELITY_TASK_KIND = "frontend-implementation";
|
|
40
|
+
const FRAGMENT_DISPOSITIONS = [
|
|
41
|
+
"requirement",
|
|
42
|
+
"acceptance",
|
|
43
|
+
"constraint",
|
|
44
|
+
"non-goal",
|
|
45
|
+
"duplicate",
|
|
46
|
+
"context-only",
|
|
47
|
+
"unresolved",
|
|
48
|
+
];
|
|
49
|
+
const EXCLUSION_DISPOSITIONS = new Set([
|
|
50
|
+
"context-only",
|
|
51
|
+
"duplicate",
|
|
52
|
+
"unresolved",
|
|
53
|
+
]);
|
|
54
|
+
const reconciliationResultSchema = z.object({
|
|
55
|
+
fragmentId: z.string().min(1),
|
|
56
|
+
mappedIds: z.array(z.string().min(1)).default([]),
|
|
57
|
+
disposition: z.enum(FRAGMENT_DISPOSITIONS),
|
|
58
|
+
reason: z.string().min(1).optional(),
|
|
59
|
+
});
|
|
60
|
+
const reviewResponseSchema = z.object({
|
|
61
|
+
fragmentId: z.string().min(1),
|
|
62
|
+
approved: z.boolean(),
|
|
63
|
+
reason: z.string().min(1).optional(),
|
|
64
|
+
revision: z
|
|
65
|
+
.object({
|
|
66
|
+
disposition: z.enum(FRAGMENT_DISPOSITIONS).optional(),
|
|
67
|
+
reason: z.string().min(1).optional(),
|
|
68
|
+
})
|
|
69
|
+
.optional(),
|
|
70
|
+
});
|
|
71
|
+
/**
|
|
72
|
+
* 模型队列构建镜像 semantic-intake.ts:MED→HIGH→LOW(harness executors.pi)+
|
|
73
|
+
* Pi SDK 目录(已认证优先、非 chat 模型过滤、额外模型上限沿用
|
|
74
|
+
* SEMANTIC_INTAKE_MAX_EXTRA_MODELS)。队列空即 fail closed。
|
|
75
|
+
*/
|
|
76
|
+
export function buildSourceFidelityModelQueue(input) {
|
|
77
|
+
return buildSemanticIntakeModelQueue(input);
|
|
78
|
+
}
|
|
79
|
+
async function buildProductionModelQueue(repoRoot, listAvailableModels) {
|
|
80
|
+
const manifest = await loadHarnessManifest(repoRoot);
|
|
81
|
+
const models = resolveExecutorModelMatrices(manifest);
|
|
82
|
+
let available = [];
|
|
83
|
+
try {
|
|
84
|
+
available = listAvailableModels
|
|
85
|
+
? await listAvailableModels()
|
|
86
|
+
: await listPiSdkAvailableModels();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
available = [];
|
|
90
|
+
}
|
|
91
|
+
return buildSourceFidelityModelQueue({ harness: models.pi, available });
|
|
92
|
+
}
|
|
93
|
+
function buildReconciliationPrompt(request) {
|
|
94
|
+
return [
|
|
95
|
+
"You are a read-only source-fidelity-reconcile agent for loop-agent task intake.",
|
|
96
|
+
"Map the source fragment to EXISTING canonical requirement ids only.",
|
|
97
|
+
"Return exactly one JSON object (optional json fence); no prose outside it.",
|
|
98
|
+
"Rules:",
|
|
99
|
+
"- mappedIds: canonical requirement ids from the provided list that this fragment directly supports; empty array when none.",
|
|
100
|
+
"- fragmentId: MUST equal the requested fragment id exactly (echo the `fragment id:` line verbatim); identity mismatch is rejected.",
|
|
101
|
+
"- disposition: exactly one of requirement|acceptance|constraint|non-goal|duplicate|context-only|unresolved.",
|
|
102
|
+
"- reason: REQUIRED (non-empty) when disposition is context-only, duplicate or unresolved; optional otherwise.",
|
|
103
|
+
"- Never invent canonical ids; only use ids present in the provided canonical requirements.",
|
|
104
|
+
"- Never invent requirements or human resolutions.",
|
|
105
|
+
JSON.stringify({
|
|
106
|
+
fragmentId: "REQ-SRC-AB12CD34EF56",
|
|
107
|
+
mappedIds: ["AC-001"],
|
|
108
|
+
disposition: "requirement",
|
|
109
|
+
reason: "optional; required for exclusion dispositions",
|
|
110
|
+
}, null, 2),
|
|
111
|
+
`fragment id: ${request.fragment.id}`,
|
|
112
|
+
`fragment path: ${request.fragment.path}`,
|
|
113
|
+
`fragment headingPath: ${request.fragment.headingPath}`,
|
|
114
|
+
`fragment lineRange: ${JSON.stringify(request.fragment.lineRange)}`,
|
|
115
|
+
`budgetChars: ${request.budgetChars}`,
|
|
116
|
+
"fragment text:",
|
|
117
|
+
"```markdown",
|
|
118
|
+
request.fragment.text,
|
|
119
|
+
"```",
|
|
120
|
+
"canonical requirements:",
|
|
121
|
+
JSON.stringify(request.canonicalRequirements, null, 2),
|
|
122
|
+
].join("\n\n");
|
|
123
|
+
}
|
|
124
|
+
function buildReviewPrompt(request) {
|
|
125
|
+
return [
|
|
126
|
+
"You are an independent read-only source-fidelity-review agent for loop-agent task intake.",
|
|
127
|
+
"Review whether the given fragment's context-only / duplicate / conflict disposition is justified.",
|
|
128
|
+
"You are a separate stage from reconciliation: do not re-map fragments; only approve, reject, or propose at most one bounded revision.",
|
|
129
|
+
"Return exactly one JSON object (optional json fence); no prose outside it.",
|
|
130
|
+
"Rules:",
|
|
131
|
+
"- approved: true only when the exclusion/conflict handling is justified.",
|
|
132
|
+
"- fragmentId: MUST equal the requested fragment id exactly (echo the `fragment id:` line verbatim); identity mismatch is rejected.",
|
|
133
|
+
"- reason: recommended; explain the verdict.",
|
|
134
|
+
"- revision: optional single bounded revision {disposition, reason}; never invent requirements or human resolutions.",
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
fragmentId: "REQ-SRC-AB12CD34EF56",
|
|
137
|
+
approved: true,
|
|
138
|
+
reason: "verdict rationale",
|
|
139
|
+
revision: {
|
|
140
|
+
disposition: "duplicate",
|
|
141
|
+
reason: "optional bounded revision",
|
|
142
|
+
},
|
|
143
|
+
}, null, 2),
|
|
144
|
+
`review kind: ${request.kind}`,
|
|
145
|
+
`fragment id: ${request.fragment.id}`,
|
|
146
|
+
`fragment disposition: ${request.fragment.disposition}`,
|
|
147
|
+
...(request.fragment.reason
|
|
148
|
+
? [`fragment reason: ${request.fragment.reason}`]
|
|
149
|
+
: []),
|
|
150
|
+
`fragment risk: ${request.fragment.risk}`,
|
|
151
|
+
`fragment canonicalRequirementIds: ${JSON.stringify(request.fragment.canonicalRequirementIds)}`,
|
|
152
|
+
...(request.fragment.text.trim()
|
|
153
|
+
? [
|
|
154
|
+
"fragment text:",
|
|
155
|
+
"```markdown",
|
|
156
|
+
request.fragment.text,
|
|
157
|
+
"```",
|
|
158
|
+
]
|
|
159
|
+
: [
|
|
160
|
+
"fragment text: (bounded original text unavailable for this record)",
|
|
161
|
+
`fragment id: ${request.fragment.id}`,
|
|
162
|
+
]),
|
|
163
|
+
"canonical requirements:",
|
|
164
|
+
JSON.stringify(request.canonicalRequirements, null, 2),
|
|
165
|
+
...(request.resolutionAvailable
|
|
166
|
+
? ["resolutionAvailable: true (conflict has a valid human resolution)"]
|
|
167
|
+
: []),
|
|
168
|
+
].join("\n\n");
|
|
169
|
+
}
|
|
170
|
+
function validateReconciliationOutput(text, request) {
|
|
171
|
+
let raw;
|
|
172
|
+
try {
|
|
173
|
+
raw = extractJsonObject(text);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw new LedgerReconciliationError(`reconciliation output is not a JSON object: ${error instanceof Error ? error.message : String(error)}`);
|
|
177
|
+
}
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = reconciliationResultSchema.parse(raw);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
throw new LedgerReconciliationError(`reconciliation output failed schema validation: ${error instanceof Error ? error.message : String(error)}`);
|
|
184
|
+
}
|
|
185
|
+
// 生产 fragmentId 绑定(AC-005 / AC-HARD-005):缺失/错配即 fail closed,
|
|
186
|
+
// 保证 reviewer/reconciler 决策绑定到实际请求的 source fragment。
|
|
187
|
+
if (parsed.fragmentId !== request.fragmentId) {
|
|
188
|
+
throw new LedgerReconciliationError(`reconciliation output fragmentId ${parsed.fragmentId} does not match requested fragment ${request.fragmentId}`);
|
|
189
|
+
}
|
|
190
|
+
const outOfBounds = parsed.mappedIds.filter((id) => !request.canonicalIds.has(id));
|
|
191
|
+
if (outOfBounds.length > 0) {
|
|
192
|
+
throw new LedgerReconciliationError(`reconciliation output contains out-of-bounds canonical ids: ${outOfBounds.join(", ")}`);
|
|
193
|
+
}
|
|
194
|
+
if (EXCLUSION_DISPOSITIONS.has(parsed.disposition) && !parsed.reason?.trim()) {
|
|
195
|
+
throw new LedgerReconciliationError(`reconciliation output omitted the required exclusion reason for disposition ${parsed.disposition}`);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
mappedIds: parsed.mappedIds,
|
|
199
|
+
disposition: parsed.disposition,
|
|
200
|
+
...(parsed.reason ? { reason: parsed.reason } : {}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function validateReviewOutput(text, request) {
|
|
204
|
+
let raw;
|
|
205
|
+
try {
|
|
206
|
+
raw = extractJsonObject(text);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
throw new LedgerReviewError(`review output is not a JSON object: ${error instanceof Error ? error.message : String(error)}`);
|
|
210
|
+
}
|
|
211
|
+
let parsed;
|
|
212
|
+
try {
|
|
213
|
+
parsed = reviewResponseSchema.parse(raw);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
throw new LedgerReviewError(`review output failed schema validation: ${error instanceof Error ? error.message : String(error)}`);
|
|
217
|
+
}
|
|
218
|
+
// 生产 fragmentId 绑定(AC-005 / AC-HARD-005):缺失/错配即 fail closed。
|
|
219
|
+
if (parsed.fragmentId !== request.fragmentId) {
|
|
220
|
+
throw new LedgerReviewError(`review output fragmentId ${parsed.fragmentId} does not match requested fragment ${request.fragmentId}`);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
approved: parsed.approved,
|
|
224
|
+
...(parsed.reason ? { reason: parsed.reason } : {}),
|
|
225
|
+
...(parsed.revision ? { revision: parsed.revision } : {}),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 执行一次生产 reconciliation:模型队列串行尝试(默认真实 executePiStep),
|
|
230
|
+
* 每个成功输出都经 validateOutput + zod 严格校验,全部失败抛
|
|
231
|
+
* LedgerReconciliationError(稳定 SOURCE_FIDELITY_RECONCILIATION_FAILED)。
|
|
232
|
+
*/
|
|
233
|
+
export async function runProductionLedgerReconciliation(request, options) {
|
|
234
|
+
const canonicalIds = new Set(request.canonicalRequirements.map((requirement) => requirement.id));
|
|
235
|
+
const parseAndValidate = (text) => validateReconciliationOutput(text, {
|
|
236
|
+
fragmentId: request.fragment.id,
|
|
237
|
+
canonicalIds,
|
|
238
|
+
});
|
|
239
|
+
const queue = options.modelQueue ??
|
|
240
|
+
(await buildProductionModelQueue(options.repoRoot, options.listAvailableModels));
|
|
241
|
+
if (queue.length === 0) {
|
|
242
|
+
throw new LedgerReconciliationError("source fidelity reconciliation has no MED/HIGH/LOW or Pi-supported models to try (fail closed)");
|
|
243
|
+
}
|
|
244
|
+
const prompt = buildReconciliationPrompt(request);
|
|
245
|
+
const execute = options.executePi ?? executePiStep;
|
|
246
|
+
const timeoutMs = options.timeoutMs ?? SOURCE_FIDELITY_ATTEMPT_TIMEOUT_MS;
|
|
247
|
+
let lastFailure = "";
|
|
248
|
+
for (const modelRef of queue) {
|
|
249
|
+
try {
|
|
250
|
+
const result = await execute({
|
|
251
|
+
attachedFiles: [],
|
|
252
|
+
modelConfig: resolveDagPiModelConfig(modelRef),
|
|
253
|
+
prompt,
|
|
254
|
+
repoRoot: options.repoRoot,
|
|
255
|
+
step: "analyze",
|
|
256
|
+
toolNames: [...SOURCE_FIDELITY_PI_TOOL_NAMES],
|
|
257
|
+
timeoutMs,
|
|
258
|
+
stallTimeoutMs: timeoutMs,
|
|
259
|
+
userMessage: "Reconcile the source fragment into the required JSON only.",
|
|
260
|
+
validateOutput: (text) => {
|
|
261
|
+
try {
|
|
262
|
+
parseAndValidate(text);
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
if (!result.ok) {
|
|
271
|
+
lastFailure =
|
|
272
|
+
`${result.failureCategory}: ${(result.stderr || result.assistantText).slice(0, 2000)}`;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
return parseAndValidate(result.assistantText);
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
// 模型返回成功但输出未通过严格校验:视为 invalid-output,尝试下一模型。
|
|
280
|
+
lastFailure =
|
|
281
|
+
`invalid-output: ${error instanceof Error ? error.message : String(error)}`;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
lastFailure =
|
|
286
|
+
`unknown: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2000);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
throw new LedgerReconciliationError(`source fidelity reconciliation pi failed after ${queue.length} model(s): ${lastFailure || "no attempt produced valid output"}`);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* 执行一次生产独立 review:模型队列串行尝试(默认真实 executePiStep),
|
|
293
|
+
* 输出经 validateOutput + zod 严格校验,全部失败抛 LedgerReviewError
|
|
294
|
+
* (稳定 SOURCE_FIDELITY_RECONCILIATION_FAILED,绝不回退默认 reviewer)。
|
|
295
|
+
*/
|
|
296
|
+
export async function runProductionLedgerReview(request, options) {
|
|
297
|
+
const parseAndValidate = (text) => validateReviewOutput(text, { fragmentId: request.fragment.id });
|
|
298
|
+
const queue = options.modelQueue ??
|
|
299
|
+
(await buildProductionModelQueue(options.repoRoot, options.listAvailableModels));
|
|
300
|
+
if (queue.length === 0) {
|
|
301
|
+
throw new LedgerReviewError("source fidelity review has no MED/HIGH/LOW or Pi-supported models to try (fail closed)");
|
|
302
|
+
}
|
|
303
|
+
const prompt = buildReviewPrompt(request);
|
|
304
|
+
const execute = options.executePi ?? executePiStep;
|
|
305
|
+
const timeoutMs = options.timeoutMs ?? SOURCE_FIDELITY_ATTEMPT_TIMEOUT_MS;
|
|
306
|
+
let lastFailure = "";
|
|
307
|
+
for (const modelRef of queue) {
|
|
308
|
+
try {
|
|
309
|
+
const result = await execute({
|
|
310
|
+
attachedFiles: [],
|
|
311
|
+
modelConfig: resolveDagPiModelConfig(modelRef),
|
|
312
|
+
prompt,
|
|
313
|
+
repoRoot: options.repoRoot,
|
|
314
|
+
step: "analyze",
|
|
315
|
+
toolNames: [...SOURCE_FIDELITY_PI_TOOL_NAMES],
|
|
316
|
+
timeoutMs,
|
|
317
|
+
stallTimeoutMs: timeoutMs,
|
|
318
|
+
userMessage: "Review the fragment disposition into the required JSON only.",
|
|
319
|
+
validateOutput: (text) => {
|
|
320
|
+
try {
|
|
321
|
+
parseAndValidate(text);
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
if (!result.ok) {
|
|
330
|
+
lastFailure =
|
|
331
|
+
`${result.failureCategory}: ${(result.stderr || result.assistantText).slice(0, 2000)}`;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
return parseAndValidate(result.assistantText);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
lastFailure =
|
|
339
|
+
`invalid-output: ${error instanceof Error ? error.message : String(error)}`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
lastFailure =
|
|
344
|
+
`unknown: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2000);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
throw new LedgerReviewError(`source fidelity review pi failed after ${queue.length} model(s): ${lastFailure || "no attempt produced valid output"}`);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* 生产适配器工厂:返回 prepare 可注入的 reconcile executor(budgetChars=4000)
|
|
351
|
+
* 与独立 reviewer 两个不同 Pi 调用面。
|
|
352
|
+
*/
|
|
353
|
+
export function createProductionSourceFidelityPi(options) {
|
|
354
|
+
const reconcileExecutor = (request) => runProductionLedgerReconciliation(request, options);
|
|
355
|
+
const reviewer = (request) => runProductionLedgerReview(request, options);
|
|
356
|
+
return {
|
|
357
|
+
reconcile: {
|
|
358
|
+
executor: reconcileExecutor,
|
|
359
|
+
budgetChars: LEDGER_RECONCILIATION_DEFAULT_BUDGET_CHARS,
|
|
360
|
+
},
|
|
361
|
+
reviewer,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* 接线侧门(AC-PROD-005):taskKind === 'frontend-implementation'(input.taskKind
|
|
366
|
+
* 优先,缺省回退 loadTaskConfig(repoRoot, taskId).taskKind)。ineligible 返回
|
|
367
|
+
* undefined 不接线;配合 prepare.ts 的 parseable requirement/acceptance 前置
|
|
368
|
+
* 双重保证非 eligible 路径零 source-fidelity Pi 调用。
|
|
369
|
+
*/
|
|
370
|
+
export async function resolveProductionSourceFidelityPi(input) {
|
|
371
|
+
let effectiveTaskKind = input.taskKind?.trim();
|
|
372
|
+
if (!effectiveTaskKind) {
|
|
373
|
+
try {
|
|
374
|
+
const config = await loadTaskConfig(input.repoRoot, input.taskId);
|
|
375
|
+
effectiveTaskKind = config.taskKind?.trim();
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
effectiveTaskKind = undefined;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (effectiveTaskKind !== SOURCE_FIDELITY_TASK_KIND)
|
|
382
|
+
return undefined;
|
|
383
|
+
return createProductionSourceFidelityPi({ repoRoot: input.repoRoot });
|
|
384
|
+
}
|
|
@@ -5,3 +5,25 @@ export const PREPARE_DEFAULT_FORBIDDEN_PATHS = [
|
|
|
5
5
|
".harness/**",
|
|
6
6
|
"node_modules/**",
|
|
7
7
|
];
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Source Fidelity Ledger (20260824-frontend-source-fidelity-ledger-v1)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// 原始 requirement/acceptance 文档保持语义权威;派生 source/需求.md 仅作导航视图。
|
|
12
|
+
// ledger 是二者之间确定性、可审计的映射账本:完整性由可审计账本、来源绑定和
|
|
13
|
+
// writer 前确定性门禁证明,摘要遗漏不再静默通过。
|
|
14
|
+
/** 稳定机器可判读错误码(SOURCE_FIDELITY_* 类目),writer 前门禁/修复路由消费。 */
|
|
15
|
+
export const SOURCE_FIDELITY_ERROR_CODES = [
|
|
16
|
+
"SOURCE_FIDELITY_LEDGER_MISSING",
|
|
17
|
+
"SOURCE_FIDELITY_LEDGER_STALE",
|
|
18
|
+
"SOURCE_FIDELITY_UNRESOLVED",
|
|
19
|
+
"SOURCE_FIDELITY_CONFLICT",
|
|
20
|
+
"SOURCE_FIDELITY_HIGH_RISK_UNCOVERED",
|
|
21
|
+
"SOURCE_FIDELITY_CONTEXT_ONLY_UNREVIEWED",
|
|
22
|
+
"SOURCE_FIDELITY_REQUIREMENT_NOT_COVERED",
|
|
23
|
+
"SOURCE_FIDELITY_SOURCE_REF_INVALID",
|
|
24
|
+
"SOURCE_FIDELITY_FRAGMENT_INVALID",
|
|
25
|
+
"SOURCE_FIDELITY_RECONCILIATION_FAILED",
|
|
26
|
+
];
|
|
27
|
+
export const LEDGER_SCHEMA_VERSION = 1;
|
|
28
|
+
export const LEDGER_SCHEMA_RECONCILER_VERSION = "ledger-reconciler-v1";
|
|
29
|
+
export const REQUIREMENT_LEDGER_FILE_NAME = "requirement-ledger.json";
|
package/dist/worker/cli.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
5
|
import { Command } from "commander";
|
|
7
|
-
import { createNpmUpdateClient } from "../shared/update/npm-client.js";
|
|
8
|
-
import { runConsoleDailyUpdateCheck } from "../shared/update/console-notifier.js";
|
|
9
6
|
import { readPackageVersion } from "../shared/package-metadata.js";
|
|
10
7
|
import YAML from "yaml";
|
|
11
8
|
import { LoopAgentClient } from "./loop-agent/loop-agent-client.js";
|
|
@@ -844,37 +841,26 @@ function parseOptionalTimeoutMs(raw) {
|
|
|
844
841
|
export async function runConsoleServe(options) {
|
|
845
842
|
const { createConsoleServer } = await import("./console/server.js");
|
|
846
843
|
const { maybeOpenConsoleBrowser } = await import("./console/open-browser.js");
|
|
844
|
+
const { readConsoleHandoff, waitForOldConsoleExit } = await import("./console/console-handoff.js");
|
|
847
845
|
try {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
stdinIsTTY: process.stdin.isTTY === true,
|
|
858
|
-
stderrIsTTY: process.stderr.isTTY === true,
|
|
859
|
-
client: createNpmUpdateClient({
|
|
860
|
-
currentVersion,
|
|
861
|
-
packageRoot,
|
|
862
|
-
cwd: options.repoRoot,
|
|
863
|
-
env: process.env,
|
|
864
|
-
}),
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
catch (error) {
|
|
868
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
869
|
-
process.stderr.write(`loop-agent 更新检查已跳过:${detail}\n`);
|
|
846
|
+
const handoff = readConsoleHandoff(process.env);
|
|
847
|
+
const appDataRoot = options.appDataRoot ?? handoff?.appDataRoot;
|
|
848
|
+
if (handoff) {
|
|
849
|
+
if (path.resolve(handoff.repoRoot) !== path.resolve(options.repoRoot) ||
|
|
850
|
+
handoff.host !== options.host ||
|
|
851
|
+
handoff.port !== options.port) {
|
|
852
|
+
throw new Error("console handoff does not match requested repo/host/port");
|
|
853
|
+
}
|
|
854
|
+
await waitForOldConsoleExit(handoff.oldPid);
|
|
870
855
|
}
|
|
871
856
|
const server = await createConsoleServer({
|
|
872
857
|
repoRoot: options.repoRoot,
|
|
873
858
|
host: options.host,
|
|
874
859
|
port: options.port,
|
|
875
860
|
debug: options.debug === true,
|
|
876
|
-
appDataRoot
|
|
861
|
+
appDataRoot,
|
|
877
862
|
defaultTimeoutMs: parseOptionalTimeoutMs(options.defaultTimeoutMsRaw),
|
|
863
|
+
...(handoff ? { consoleInstanceId: handoff.consoleInstanceId } : {}),
|
|
878
864
|
});
|
|
879
865
|
// Keep stdout bind URL contract (may include 0.0.0.0 / ::); browser URL is normalized separately.
|
|
880
866
|
process.stdout.write(`${server.url}
|
|
@@ -231,6 +231,10 @@ export function projectCompactSnapshot(value) {
|
|
|
231
231
|
Number.isFinite(record.tokensBefore)
|
|
232
232
|
? Math.max(0, Math.floor(record.tokensBefore))
|
|
233
233
|
: undefined;
|
|
234
|
+
const estimatedTokensAfter = typeof record.estimatedTokensAfter === "number" &&
|
|
235
|
+
Number.isFinite(record.estimatedTokensAfter)
|
|
236
|
+
? Math.max(0, Math.floor(record.estimatedTokensAfter))
|
|
237
|
+
: undefined;
|
|
234
238
|
const details = record.details === undefined
|
|
235
239
|
? undefined
|
|
236
240
|
: boundedRedactedJson(record.details).preview;
|
|
@@ -239,9 +243,16 @@ export function projectCompactSnapshot(value) {
|
|
|
239
243
|
summary,
|
|
240
244
|
...(firstKeptEntryId ? { firstKeptEntryId } : {}),
|
|
241
245
|
...(tokensBefore !== undefined ? { tokensBefore } : {}),
|
|
246
|
+
...(estimatedTokensAfter !== undefined ? { estimatedTokensAfter } : {}),
|
|
242
247
|
...(details ? { details } : {}),
|
|
243
248
|
previewHash: createHash("sha256")
|
|
244
|
-
.update(JSON.stringify({
|
|
249
|
+
.update(JSON.stringify({
|
|
250
|
+
summary,
|
|
251
|
+
firstKeptEntryId,
|
|
252
|
+
tokensBefore,
|
|
253
|
+
estimatedTokensAfter,
|
|
254
|
+
details,
|
|
255
|
+
}))
|
|
245
256
|
.digest("hex"),
|
|
246
257
|
};
|
|
247
258
|
}
|
|
@@ -433,6 +444,20 @@ function safeSessionId(sessionId) {
|
|
|
433
444
|
* Link durable Chat refs to the existing canonical operation store/event ring.
|
|
434
445
|
* This owns no operation state and schedules no work; it only projects facts.
|
|
435
446
|
*/
|
|
447
|
+
/** R8 AC-R8-001: operationIds whose operation-ref has been overshadowed by
|
|
448
|
+
* a later operation-unlinked tombstone in the same ring. A tombstoned
|
|
449
|
+
* association must never be restored by recoverSession / replay / fork. */
|
|
450
|
+
function tombstonedOperationIds(events) {
|
|
451
|
+
const tombstones = new Set();
|
|
452
|
+
for (const event of events) {
|
|
453
|
+
if (event.kind !== "operation-unlinked")
|
|
454
|
+
continue;
|
|
455
|
+
const operationId = event.data.operationId;
|
|
456
|
+
if (typeof operationId === "string" && operationId)
|
|
457
|
+
tombstones.add(operationId);
|
|
458
|
+
}
|
|
459
|
+
return tombstones;
|
|
460
|
+
}
|
|
436
461
|
export function createChatOperationLinker(options) {
|
|
437
462
|
const followers = new Map();
|
|
438
463
|
const keyFor = (sessionId, operationId) => `${sessionId}\u0000${operationId}`;
|
|
@@ -535,14 +560,37 @@ export function createChatOperationLinker(options) {
|
|
|
535
560
|
}
|
|
536
561
|
return {
|
|
537
562
|
link,
|
|
563
|
+
async unlink(input) {
|
|
564
|
+
// Idempotent removal (AC-R7-001): drop the live follower and the
|
|
565
|
+
// persisted operation-ref event so the association is truly gone.
|
|
566
|
+
const key = keyFor(input.sessionId, input.operationId);
|
|
567
|
+
followers.get(key)?.();
|
|
568
|
+
followers.delete(key);
|
|
569
|
+
const ring = options.chatEvents.snapshot(input.sessionId);
|
|
570
|
+
const refs = ring.filter((event) => event.kind === "operation-ref" &&
|
|
571
|
+
event.data.operationId ===
|
|
572
|
+
input.operationId);
|
|
573
|
+
for (const event of refs) {
|
|
574
|
+
options.chatEvents.append(input.sessionId, event.turnId, {
|
|
575
|
+
kind: "operation-unlinked",
|
|
576
|
+
data: { operationId: input.operationId, unlinkedAt: new Date().toISOString() },
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
return refs.length > 0;
|
|
580
|
+
},
|
|
538
581
|
async recoverSession(sessionId) {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
582
|
+
const snapshot = options.chatEvents.snapshot(sessionId);
|
|
583
|
+
// R8 AC-R8-001: a tombstoned (unlinked) operationId must NOT be
|
|
584
|
+
// re-linked on recovery — the persisted operation-unlinked event
|
|
585
|
+
// overshadows any earlier operation-ref in the same ring.
|
|
586
|
+
const tombstones = tombstonedOperationIds(snapshot);
|
|
587
|
+
const refs = snapshot.filter((event) => event.kind === "operation-ref");
|
|
542
588
|
for (const event of refs) {
|
|
543
589
|
const data = event.data;
|
|
544
590
|
if (!data.operationId || !data.toolCallId)
|
|
545
591
|
continue;
|
|
592
|
+
if (tombstones.has(data.operationId))
|
|
593
|
+
continue;
|
|
546
594
|
await link({
|
|
547
595
|
sessionId,
|
|
548
596
|
turnId: event.turnId,
|
|
@@ -566,6 +614,19 @@ export function createChatOperationLinker(options) {
|
|
|
566
614
|
},
|
|
567
615
|
};
|
|
568
616
|
}
|
|
617
|
+
/** R8 AC-R8-001 (fork): same tombstone scan as the linker — kept local to
|
|
618
|
+
* the store so forkContextRefs needs no linker dependency. */
|
|
619
|
+
function forkTombstonedOperationIds(events) {
|
|
620
|
+
const tombstones = new Set();
|
|
621
|
+
for (const event of events) {
|
|
622
|
+
if (event.kind !== "operation-unlinked")
|
|
623
|
+
continue;
|
|
624
|
+
const operationId = event.data.operationId;
|
|
625
|
+
if (typeof operationId === "string" && operationId)
|
|
626
|
+
tombstones.add(operationId);
|
|
627
|
+
}
|
|
628
|
+
return tombstones;
|
|
629
|
+
}
|
|
569
630
|
export function createChatEventStore(options) {
|
|
570
631
|
const maxEvents = options?.maxEventsPerSession ?? DEFAULT_MAX_EVENTS;
|
|
571
632
|
const maxBytes = options?.maxBytesPerSession ?? DEFAULT_MAX_BYTES;
|
|
@@ -735,13 +796,22 @@ export function createChatEventStore(options) {
|
|
|
735
796
|
"assessment",
|
|
736
797
|
"taskKind-confirmed",
|
|
737
798
|
"human-gate-card",
|
|
799
|
+
"compaction_end",
|
|
738
800
|
"compact",
|
|
739
801
|
"artifact-ref",
|
|
740
802
|
]);
|
|
741
803
|
let copiedCount = 0;
|
|
742
|
-
|
|
804
|
+
const source = this.snapshot(input.sourceSessionId);
|
|
805
|
+
// R8 AC-R8-001: fork excludes tombstoned associations — an unlinked
|
|
806
|
+
// operationId must not leak its operation-ref into a forked session.
|
|
807
|
+
const tombstones = forkTombstonedOperationIds(source);
|
|
808
|
+
for (const event of source) {
|
|
743
809
|
if (!allowed.has(event.kind))
|
|
744
810
|
continue;
|
|
811
|
+
if ((event.kind === "operation-ref" ||
|
|
812
|
+
event.kind === "operation-status") &&
|
|
813
|
+
tombstones.has(event.data.operationId ?? ""))
|
|
814
|
+
continue;
|
|
745
815
|
const data = structuredClone(event.data);
|
|
746
816
|
if (event.kind === "human-gate-card") {
|
|
747
817
|
delete data.humanGateToken;
|
|
@@ -38,6 +38,13 @@ export function projectTaskContext(input) {
|
|
|
38
38
|
const previous = gates.get(card.cardId);
|
|
39
39
|
gates.set(card.cardId, safeGate({ ...card, ...(previous?.dagSpine && !card.dagSpine ? { dagSpine: previous.dagSpine } : {}) }));
|
|
40
40
|
}
|
|
41
|
+
else if (event.kind === "operation-unlinked") {
|
|
42
|
+
// R8 AC-001/AC-R8-001: tombstone — an operation-unlinked event appended
|
|
43
|
+
// after its operation-ref removes the card from the projection for good.
|
|
44
|
+
const operationId = typeof data.operationId === "string" ? data.operationId : "";
|
|
45
|
+
if (operationId)
|
|
46
|
+
view.operations = view.operations.filter((operation) => operation.operationId !== operationId);
|
|
47
|
+
}
|
|
41
48
|
else if (event.kind === "operation-ref" || event.kind === "operation-status") {
|
|
42
49
|
const operation = operationFromEventPayload(data);
|
|
43
50
|
if (operation && (!activeTaskId || !operation.taskId || operation.taskId === activeTaskId))
|
|
@@ -81,11 +81,23 @@ export function projectDeferredTurnSummary(deferredTurn) {
|
|
|
81
81
|
blockedByTurnId: deferredTurn.blockedByTurnId,
|
|
82
82
|
state: deferredTurn.state,
|
|
83
83
|
updatedAt: deferredTurn.updatedAt,
|
|
84
|
+
text: deferredTurn.text,
|
|
84
85
|
textPreview: deferredTurn.text.replace(/\s+/g, " ").trim().slice(0, 120),
|
|
85
86
|
imageCount: deferredTurn.images.length,
|
|
87
|
+
images: structuredClone(deferredTurn.images),
|
|
86
88
|
...(deferredTurn.acceptedTurnId
|
|
87
89
|
? { acceptedTurnId: deferredTurn.acceptedTurnId }
|
|
88
90
|
: {}),
|
|
89
91
|
...(deferredTurn.lastError ? { lastError: deferredTurn.lastError } : {}),
|
|
90
92
|
};
|
|
91
93
|
}
|
|
94
|
+
export function parseDeferredTurns(value) {
|
|
95
|
+
if (!Array.isArray(value))
|
|
96
|
+
return [];
|
|
97
|
+
return value
|
|
98
|
+
.map((item) => parseDeferredTurn(item))
|
|
99
|
+
.filter((item) => item !== undefined);
|
|
100
|
+
}
|
|
101
|
+
export function projectDeferredTurnSummaries(deferredTurns) {
|
|
102
|
+
return deferredTurns.map((item) => projectDeferredTurnSummary(item));
|
|
103
|
+
}
|