@tea-agent/loop-agent 0.41.0-next.3 → 0.41.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +264 -32
- package/dist/application/dag/generate-task-dag.js +75 -19
- 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 +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3784 -231
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +945 -217
- package/dist/executors/shell-write-guard.js +7 -0
- 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 +34 -7
- 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 +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/pi-runtime.js +2 -3
- package/dist/worker/console/chat/routes.js +3 -12
- package/dist/worker/console/chat/semantic-activity.js +11 -12
- package/dist/worker/console/chat/session-stats.js +0 -96
- package/dist/worker/console/chat/turn-process.js +4 -28
- 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/operation-store.js +226 -6
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/pi-plugins.js +1 -3
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DwVJxUxz.js → abnfDiagram-N423BO3Z-Bho4we4c.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DqfCcXdu.js → arc-TBDkTeK0.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-qMVtggRv.js → architectureDiagram-T3A2C74G-BF_fZrn2.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-ECrV38dh.js → blockDiagram-VBNYF7ZC-CCnPgUe2.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DBaaLqQv.js → c4Diagram-5PPSVZJV-BCYZJLAT.js} +1 -1
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BvfNHkAz.js → chunk-2GRJ4B5K-D8K6adhU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5StiIbc.js → chunk-2Q5K7J3B-DkT1Q78b.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-HabGiJkS.js → chunk-5RXB4S5H-DuhvpIr7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BiTg127i.js → chunk-5VM5RSS4-BnK6dC49.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CZsXBPuR.js → chunk-6Q2QTUOP-DPz__z7r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D-r1MxLK.js → chunk-GF5L2VYU-0kkppM2j.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DQxb9plr.js → chunk-JWPE2WC7-BB41iK5t.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMndGvCT.js → chunk-KBJHAD2P-CiVrxKHU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-ByL4BgOJ.js → chunk-RYQCIY6F-C2h9Xje1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVe1b1gL.js → chunk-XXDRQBXY-3iOSHdbM.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BAyGzN8j.js → cose-bilkent-JH36ORCC-BN3A7tkk.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C5yVYU7E.js → cynefin-VYW2F7L2-CTuBy0Or.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-x-MwsEm9.js → cynefinDiagram-MW4NZA55-wb6tUlMN.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CT5Tle5F.js → dagre-VZM6K2ZE-GP1Ivx8B.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BARERhlS.js → diagram-7IWD3JNH-ejrcK6L0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BwfUz0VM.js → diagram-B4RE2ZJO-YFkCCvUU.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Mk4r2WvJ.js → diagram-LBJQPF4R-B4rrvIaL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KUrxuBs0.js → diagram-Q27KOJAE-C-YIZcUf.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CjZOceZn.js → diagram-UB23O5K3-BPF5wcL1.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-rnwUCiEI.js → ebnfDiagram-BXEA7PRR-CU5SzXjO.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DM4j23FM.js → erDiagram-JOGREHBK-CpL414DE.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DD5HdDEs.js → flowDiagram-UKHOOZJN-MrhLzNau.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CIAGSF47.js → ganttDiagram-PKOTCBZU-Bg4qFuUo.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DbwFbVEL.js → gitGraphDiagram-DS77QQ5N-ColxSXLR.js} +1 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +1 -0
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +437 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-B-Pb0T66.js → infoDiagram-6WML65LV-Czvtm6gc.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B9Unk-YX.js → ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C2F3JkPt.js → journeyDiagram-NVQOT4AX-DWYZcvph.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Bl8-Xcoz.js → kanban-definition-27J2QSJJ-KTLZRUjx.js} +1 -1
- package/dist/worker/console/static/assets/{linear-yK4uZmC5.js → linear-_Exn7bfl.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Bpa6PmuE.js → mermaid.core-Cxg8kX8V.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BfGRoCSe.js → mindmap-definition-FAOFIHXS-oKrlo2Bt.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bz6_HtST.js → pegDiagram-VL7TDLO6-XAeteiNd.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-i0Umbsby.js → pieDiagram-7S7Q4E2Y-BTf1K-lp.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DaVQBobe.js → quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DMTGnSq7.js → railroadDiagram-AXF67PYL-BVbKtZhW.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-2ao_DaSq.js → requirementDiagram-LRYGKXZP-C5e4JFcr.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-2SG1lTB9.js → sankeyDiagram-W5VNT64P-Cz30hc29.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BQYOLybR.js → sequenceDiagram-SI44F4Z6-DS33n6d0.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C8g3yjuI.js → sizeCapture-X5ZJPWSS-46w7v-9T.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-q_F8nAQG.js → stateDiagram-OKZ733FA-pbcD5SsZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-8LrWpH2s.js → swimlanes-SLNWSIFB-DuivYBSj.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BQoY3qzl.js → timeline-definition-Z64GVDOM-Cb9VZVIl.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BDu1UjSp.js → vennDiagram-T6HMQDX7-D2-mfOgR.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C48M7Y0a.js → wardleyDiagram-T6FBY63Y-DeS2aC76.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YU6FTl_F.js → xychartDiagram-ELKLHX3M-WGsrFjxH.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/useRunProgress.js +2 -9
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +0 -2
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +0 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +3 -28
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +0 -106
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observability/read-model.js +0 -4
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/views/dag-inspector.js +7 -2
- package/dist/worker/observe/static/views/session-timeline.js +15 -46
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- 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 +932 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-read-budgets.js +12 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- 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 +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -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 +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +685 -494
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +732 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +10 -2
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +214 -104
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- 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/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 +3 -3
- package/package.json +1 -1
- 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-CL1NRAmS.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-PBGzzjZA.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-PBGzzjZA.js +0 -1
- package/dist/worker/console/static/assets/index-BfleX-qj.css +0 -1
- package/dist/worker/console/static/assets/index-Bih9w9h9.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrxGBDJu.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BhpDxzAf.js +0 -8
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +0 -22
- 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
|
@@ -175,8 +175,11 @@ export function splitDocumentIntoFragments(doc) {
|
|
|
175
175
|
const explicitSectionAc = matchExplicitAcHeading(lastHeadingSegment);
|
|
176
176
|
const sectionKind = classifySectionHeading(section.headingPath);
|
|
177
177
|
const effectiveKind = explicitSectionAc ? "acceptance" : sectionKind;
|
|
178
|
+
// V4 显式 AC 标题行已在下方入口 emit(explicitSectionAc 分支);正文循环
|
|
179
|
+
// 必须从标题行的下一行开始,否则同一标题行会被正文循环的 heading 分支
|
|
180
|
+
// 再次 emit,产生结构性 duplicate(每个 AC 标题式片段出现两次)。
|
|
178
181
|
const bodyStart = section.headingPath
|
|
179
|
-
? section.startLine +
|
|
182
|
+
? section.startLine + 1
|
|
180
183
|
: section.startLine;
|
|
181
184
|
const bodyEnd = section.endLine;
|
|
182
185
|
const bodyLines = lines.slice(Math.max(0, bodyStart - 1), Math.min(bodyEnd, lines.length));
|
|
@@ -438,14 +441,17 @@ export function buildFragmentInventory(input) {
|
|
|
438
441
|
disposition = "non-goal";
|
|
439
442
|
}
|
|
440
443
|
else {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
444
|
+
// Non-goal/constraint/acceptance/objective sections are classified
|
|
445
|
+
// above. An "other" section (背景 / 概述 / 说明 …) is contextual
|
|
446
|
+
// description, not an executable requirement: even when it mentions
|
|
447
|
+
// implementation verbs ("需要", "渲染", "组件"), it must not be
|
|
448
|
+
// promoted to a high-risk unresolved executable fragment — that
|
|
449
|
+
// blocks PRDs whose 背景 prose happens to use such words (merge
|
|
450
|
+
// regression: "需要一个小组件" was flagged executable via 需要).
|
|
451
|
+
// Explicit AC ids are already handled by the branches above, so
|
|
452
|
+
// this branch is always contextual.
|
|
453
|
+
disposition = "context-only";
|
|
454
|
+
reason = "context-only fragment; excluded from canonical requirement coverage";
|
|
449
455
|
}
|
|
450
456
|
const priorId = seenText.get(text);
|
|
451
457
|
if (priorId) {
|
|
@@ -5,6 +5,9 @@ import { ContractMutationError, applyTaskContract, logicalRevisionForState, obse
|
|
|
5
5
|
import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
|
|
6
6
|
import { getTaskContractPaths } from "../contract/paths.js";
|
|
7
7
|
import { loadTaskConfig } from "../runtime.js";
|
|
8
|
+
import { discoverFrontendProjectCapability, resolveFrontendSpecRootAliases, } from "../frontend-project-capability.js";
|
|
9
|
+
import { classifyTaskDemand } from "../task-demand-routing.js";
|
|
10
|
+
import { DEFAULT_FRONTEND_SPEC_ROOTS, extractDeclaredFrontendSpecRoots, } from "../../shared/openspec-spec.js";
|
|
8
11
|
import { buildPrepareDraft } from "./build-draft.js";
|
|
9
12
|
import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, appendLedgerGaps, } from "./completeness.js";
|
|
10
13
|
import { detectProductArtifactMeta, isIntakeSoftGapCode, } from "./artifact-meta.js";
|
|
@@ -681,7 +684,7 @@ export async function prepareTaskSource(input) {
|
|
|
681
684
|
facts = importedFacts ?? mergedSupplemental;
|
|
682
685
|
}
|
|
683
686
|
}
|
|
684
|
-
|
|
687
|
+
let built = buildPrepareDraft({
|
|
685
688
|
taskId,
|
|
686
689
|
existingTaskConfig,
|
|
687
690
|
baseDraft,
|
|
@@ -689,6 +692,91 @@ export async function prepareTaskSource(input) {
|
|
|
689
692
|
flags: input.flags,
|
|
690
693
|
references,
|
|
691
694
|
});
|
|
695
|
+
// Resolve an implicit frontend workflow before the first managed contract is
|
|
696
|
+
// projected. Explicit taskKind (including explicit standard) remains a hard
|
|
697
|
+
// contract; only a standard task with strong source/path/project evidence may
|
|
698
|
+
// be promoted here. This keeps task.json, contract.json, and the first DAG
|
|
699
|
+
// generation on the same workflow without a later hash-changing re-projection.
|
|
700
|
+
if (built.draft.taskKind === "standard" &&
|
|
701
|
+
!built.draft.taskKindExplicit) {
|
|
702
|
+
const routingRequirement = [
|
|
703
|
+
...(sourceIntegrity?.documents ?? [])
|
|
704
|
+
.map((document) => document.content)
|
|
705
|
+
.filter((content) => Boolean(content?.trim())),
|
|
706
|
+
...(input.intent.kind === "facts" && input.intent.text
|
|
707
|
+
? [input.intent.text]
|
|
708
|
+
: []),
|
|
709
|
+
built.draft.requirement.objective,
|
|
710
|
+
...built.draft.requirement.scope,
|
|
711
|
+
...built.draft.requirement.acceptanceCriteria.map((item) => item.text),
|
|
712
|
+
].join("\n");
|
|
713
|
+
let frontendProject;
|
|
714
|
+
try {
|
|
715
|
+
frontendProject = await discoverFrontendProjectCapability(repoRoot);
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
// Capability discovery is advisory for routing. A failed probe must not
|
|
719
|
+
// turn an otherwise valid standard contract preparation into an error.
|
|
720
|
+
}
|
|
721
|
+
const classification = classifyTaskDemand({
|
|
722
|
+
title: built.draft.title,
|
|
723
|
+
requirementMarkdown: routingRequirement,
|
|
724
|
+
allowedPaths: built.draft.constraints.allowedPaths,
|
|
725
|
+
frontendProject: frontendProject
|
|
726
|
+
? {
|
|
727
|
+
framework: frontendProject.framework,
|
|
728
|
+
evidencePaths: frontendProject.evidencePaths,
|
|
729
|
+
}
|
|
730
|
+
: undefined,
|
|
731
|
+
});
|
|
732
|
+
if (classification.kind === "frontend") {
|
|
733
|
+
built = buildPrepareDraft({
|
|
734
|
+
taskId,
|
|
735
|
+
existingTaskConfig,
|
|
736
|
+
baseDraft,
|
|
737
|
+
facts,
|
|
738
|
+
flags: {
|
|
739
|
+
...input.flags,
|
|
740
|
+
taskKind: "frontend-implementation",
|
|
741
|
+
},
|
|
742
|
+
references,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// A user can explicitly name a non-default normative directory in the task
|
|
747
|
+
// prompt (for example “规范目录 `design-system`”). Freeze that root into
|
|
748
|
+
// the managed task config before the DAG exists; normal DAG selection consumes
|
|
749
|
+
// the already-resolved task kind instead of mutating task.json later.
|
|
750
|
+
if (built.draft.taskKind === "frontend-implementation") {
|
|
751
|
+
// An imported PRD is materialized under sourceIntegrity rather than copied
|
|
752
|
+
// into intent.text. Use both inputs so a `--prd` invocation can declare a
|
|
753
|
+
// task-owned spec root just like supplemental free-form text can.
|
|
754
|
+
const sourceText = [
|
|
755
|
+
...(sourceIntegrity?.documents ?? []).flatMap((document) => document.content ? [document.content] : []),
|
|
756
|
+
...(input.intent.kind === "facts" && input.intent.text
|
|
757
|
+
? [input.intent.text]
|
|
758
|
+
: []),
|
|
759
|
+
].join("\n");
|
|
760
|
+
const inferredRoots = extractDeclaredFrontendSpecRoots(sourceText);
|
|
761
|
+
if (inferredRoots.length > 0) {
|
|
762
|
+
const current = existingTaskConfig?.frontendOpenspec;
|
|
763
|
+
const configuredRoots = current?.specRoots ?? [...DEFAULT_FRONTEND_SPEC_ROOTS];
|
|
764
|
+
// Include roots declared by this source before resolving physical-target
|
|
765
|
+
// aliases. A newly declared root may itself be a symlink (for example
|
|
766
|
+
// `project-specs -> .../ai_doc`), so resolving aliases against only the
|
|
767
|
+
// previous config cannot map `ai_doc/rules.md` back to project-specs.
|
|
768
|
+
const provisionalRoots = [
|
|
769
|
+
...new Set([...configuredRoots, ...inferredRoots]),
|
|
770
|
+
].sort();
|
|
771
|
+
const aliases = await resolveFrontendSpecRootAliases(repoRoot, provisionalRoots);
|
|
772
|
+
const aliasMap = new Map(aliases.map(({ alias, logicalRoot }) => [alias, logicalRoot]));
|
|
773
|
+
built.draft.frontendOpenspec = {
|
|
774
|
+
policy: current?.policy ?? "cited",
|
|
775
|
+
specRoots: [...new Set([...configuredRoots, ...inferredRoots.map((root) => aliasMap.get(root) ?? root)])].sort(),
|
|
776
|
+
requiredReadPaths: current?.requiredReadPaths ?? [],
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
}
|
|
692
780
|
const { gaps, risks } = listPrepareGaps({
|
|
693
781
|
draft: built.draft,
|
|
694
782
|
state,
|
|
@@ -457,8 +457,12 @@ async function buildPrompt(input) {
|
|
|
457
457
|
"Do NOT invent product behavior that is not supported by the documents.",
|
|
458
458
|
"You MAY recommend an optional engineering block when the repo layout hint supports it:",
|
|
459
459
|
"- allowedPaths / forbiddenPaths as minimal precise globs (e.g. source/packages/**/*.test.ts)",
|
|
460
|
-
"- verifyCommands as real package scripts (e.g. pnpm -C source test)",
|
|
461
|
-
"- taskKind when clearly implied
|
|
460
|
+
"- verifyCommands as real package scripts (e.g. pnpm -C source test); when acceptance criteria name a specific test file to run (e.g. 定向 vitest(test/.../xxx.test.ts)), emit the corresponding targeted command as its own entry (e.g. npx vitest run test/.../xxx.test.ts) instead of only the full-suite script — targeted commands are what a frontend implementation plan maps to behavior verification targets",
|
|
461
|
+
"- taskKind when clearly implied, choosing from {standard / frontend-implementation / frontend-test / backend-test / knowledge-sync}:",
|
|
462
|
+
" - frontend-implementation: the deliverable is a frontend component/page/UI feature (even when it ships with its own tests)",
|
|
463
|
+
" - frontend-test: the deliverable is a browser/UI automation test suite for EXISTING functionality (no implementation work)",
|
|
464
|
+
" - backend-test: pytest/backend test suite; standard: general repo implementation",
|
|
465
|
+
" A task that IMPLEMENTS a component and adds tests for it is frontend-implementation, never frontend-test.",
|
|
462
466
|
"Prefer smallest correct write boundary; do not invent product paths that are not in the layout hint.",
|
|
463
467
|
"Do NOT rewrite or suggest editing the original PRD files.",
|
|
464
468
|
"Prefer explicit text; use provenance derived for rephrasing; use inferred only when clearly implied — if you must invent ACs, leave acceptanceCriteria empty so the system can stop for clarification.",
|
|
@@ -22,6 +22,18 @@ function slugifyName(value) {
|
|
|
22
22
|
.replace(/-{2,}/g, "-");
|
|
23
23
|
return slug || "requirement";
|
|
24
24
|
}
|
|
25
|
+
/** Stable identity of a manifest document for change detection. Timestamps and
|
|
26
|
+
* byte sizes are excluded: identical content re-imported later must not appear
|
|
27
|
+
* "changed" (the canonical hash includes manifest bytes). */
|
|
28
|
+
function documentIdentity(doc) {
|
|
29
|
+
return `${doc.role}|${doc.name}|${doc.materializedPath}|${doc.sha256}`;
|
|
30
|
+
}
|
|
31
|
+
function sameDocumentSet(left, right) {
|
|
32
|
+
if (left.length !== right.length)
|
|
33
|
+
return false;
|
|
34
|
+
const rightIds = new Set(right.map(documentIdentity));
|
|
35
|
+
return left.every((doc) => rightIds.has(documentIdentity(doc)));
|
|
36
|
+
}
|
|
25
37
|
function ensureUniqueReferenceName(existing, baseName, extension) {
|
|
26
38
|
const used = new Set(existing);
|
|
27
39
|
const candidate = `${baseName}${extension}`;
|
|
@@ -149,10 +161,19 @@ export async function importPrdDocument(input) {
|
|
|
149
161
|
const documents = [...(existingManifest?.documents ?? [])].filter((entry) => entry.materializedPath !== materializedRelativePath);
|
|
150
162
|
documents.push(document);
|
|
151
163
|
documents.sort((left, right) => left.materializedPath.localeCompare(right.materializedPath));
|
|
164
|
+
// Keep `updatedAt` stable across re-imports of the same content: the
|
|
165
|
+
// canonical contract hash includes the manifest bytes, so a fresh
|
|
166
|
+
// timestamp on every identical re-import would make the observed
|
|
167
|
+
// canonical hash drift and fail task verification. Only bump it when the
|
|
168
|
+
// document set actually changed (a new/changed materialized file).
|
|
169
|
+
const documentSetChanged = !existingManifest ||
|
|
170
|
+
!sameDocumentSet(existingManifest.documents, documents);
|
|
152
171
|
const manifest = {
|
|
153
172
|
schemaVersion: 1,
|
|
154
173
|
taskId: input.taskId,
|
|
155
|
-
updatedAt:
|
|
174
|
+
updatedAt: documentSetChanged
|
|
175
|
+
? importedAt
|
|
176
|
+
: (existingManifest?.updatedAt ?? importedAt),
|
|
156
177
|
documents,
|
|
157
178
|
};
|
|
158
179
|
await writeSourceManifest(paths.sourceDir, manifest);
|
|
@@ -67,11 +67,10 @@ export const OPERATOR_CHAT_SYSTEM_PROMPT_BASE = [
|
|
|
67
67
|
"You are General Operator Chat.",
|
|
68
68
|
"Use operator tools first (dagReport, dagRerunPlan, dagRerun). Pi read/write/edit/bash/grep/find/ls are available; obey repository AGENTS.md. apply_patch, full-tools, shell and coding-chat are denied. Prefer safe-read/safe-grep for probes.",
|
|
69
69
|
"No harness and asked to init: `loop-agent init instructions --repo-root .`, then `loop-agent init --repo-root . --profile full --merge`; verify with doctor, inspect and docs audit. Updates: `loop-agent init upgrade --repo-root . --json` until stable; `loop-agent init check-update` is read-only.",
|
|
70
|
-
"Long DAGs: prepareDagExecution → runDag → operationId. Never foreground-Bash task advance, tail/head pipes or nohup/Start-Process/start; do not end on accepted/queued/running or an operationId — NOT completion. Supervise operationGet/status/dagReport/dagDoctor/operationWait
|
|
71
|
-
"
|
|
70
|
+
"Long DAGs: prepareDagExecution → runDag → operationId. Never foreground-Bash task advance, tail/head pipes or nohup/Start-Process/start; do not end on accepted/queued/running or an operationId — NOT completion. Supervise operationGet/status/dagReport/dagDoctor/operationWait with 60s → 90s → 120s → 180s backoff; reset to 60s on state change; 30-60s re-checks when stall suspected. Every operationWait continuation MUST use afterSeq=previous nextSeq (recommendedNextCall); never reuse older cursors. Heartbeats: not meaningful progress. Run id 20260829-slug ≠ task id 2026-08-29-slug.",
|
|
71
|
+
"On failure consume terminal runSummary first. If progress exists, wait; else dagReport then dagRerunPlan(fromNode=primaryFailure.nodeId); eligible → dagRerun (node rerun only: reruns the failed subgraph, keeps prior successes). If plan ineligible (unsafe-shell subgraph, binding/workspace drift): Worker Failed → workerTaskRetry; standalone → standaloneTaskRerun after contract adopt (regenerates the whole task, not node continue). Do not call dagResume. Run-failed task without open gate is terminal: do not re-advance a run-failed task. Before run start, two identical lifecycleState/blocker/revision/DAG-hash observations mean no progress: stop and report after one bounded probe; never loop identical bash/read/grep. Auto-approve only bounded writeSet inside allowedPaths, outside forbiddenPaths, with structured verification.",
|
|
72
72
|
"Iterate toward the goal in bounded rounds: first summarize the previous round's error in one line, fix its root cause — not the symptom — then rerun the failed node with a fresh plan. Never resubmit an unchanged fix; after two rounds failing the same way, stop and report evidence and remaining routes.",
|
|
73
73
|
"Continue until success, user stop, or no safe recovery remains; then stop and report failure/evidence. Never create a new task for a transient failure or replace repair-pi with direct edits.",
|
|
74
|
-
"Narrate long-DAG progress briefly at start, meaningful phase changes/findings, verification failure/recovery, prolonged quiet and terminal state: say what finished and what is next. Never narrate unchanged operationWait polls or heartbeats, or claim beyond structured facts.",
|
|
75
74
|
].join("\n");
|
|
76
75
|
/** Compose the inspectable system prompt actually injected into Operator Chat. */
|
|
77
76
|
export function composeOperatorChatSystemPrompt(input) {
|
|
@@ -34,7 +34,7 @@ import { dispatchOperatorAction } from "../operator-actions.js";
|
|
|
34
34
|
import { makeHumanGateCard, } from "./human-gate-card.js";
|
|
35
35
|
import { ContractApplyReceiptStore } from "./contract-apply-receipt-store.js";
|
|
36
36
|
import { projectTaskContext } from "./context-panel.js";
|
|
37
|
-
import { deriveChatSessionTelemetry
|
|
37
|
+
import { deriveChatSessionTelemetry } from "./session-stats.js";
|
|
38
38
|
import { contractApplyPayloadHash, issueHumanGateToken, verifyHumanGateToken, } from "../human-gate-token.js";
|
|
39
39
|
import { sendJson } from "../routes.js";
|
|
40
40
|
import { openSseResponse, writeSseEvent } from "../operation-sse.js";
|
|
@@ -99,16 +99,8 @@ export function projectStoredMessagesWithEventOrder(messages, events) {
|
|
|
99
99
|
}
|
|
100
100
|
return {
|
|
101
101
|
...message,
|
|
102
|
-
...(start
|
|
103
|
-
|
|
104
|
-
eventSeq: start.seq,
|
|
105
|
-
turnId: start.turnId,
|
|
106
|
-
startedAt: start.at,
|
|
107
|
-
}
|
|
108
|
-
: {}),
|
|
109
|
-
...(finish
|
|
110
|
-
? { finishedSeq: finish.seq, finishedAt: finish.at }
|
|
111
|
-
: {}),
|
|
102
|
+
...(start ? { eventSeq: start.seq, turnId: start.turnId } : {}),
|
|
103
|
+
...(finish ? { finishedSeq: finish.seq } : {}),
|
|
112
104
|
};
|
|
113
105
|
});
|
|
114
106
|
}
|
|
@@ -836,7 +828,6 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
836
828
|
hasBash: true,
|
|
837
829
|
sessionStats: telemetry.stats,
|
|
838
830
|
usage: telemetry.usage,
|
|
839
|
-
turnTelemetry: deriveChatTurnTelemetry(snapshot),
|
|
840
831
|
contextPressure: typeof deps.runtime.getContextPressureTelemetry === "function"
|
|
841
832
|
? deps.runtime.getContextPressureTelemetry(sessionId)
|
|
842
833
|
: undefined,
|
|
@@ -296,6 +296,13 @@ function operationIdFromArgs(args) {
|
|
|
296
296
|
const operationId = record?.operationId;
|
|
297
297
|
return typeof operationId === "string" && operationId ? operationId : undefined;
|
|
298
298
|
}
|
|
299
|
+
function maxWaitSeconds(args) {
|
|
300
|
+
const record = parseJsonRecord(args);
|
|
301
|
+
const raw = record?.maxWaitMs;
|
|
302
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0)
|
|
303
|
+
return undefined;
|
|
304
|
+
return Math.max(1, Math.round(raw / 1000));
|
|
305
|
+
}
|
|
299
306
|
function activityRefForTool(tool) {
|
|
300
307
|
// Plan A (2026-08-16): server backfills operationId on the persisted tool
|
|
301
308
|
// entry (and live tool_result frames carry it in the result payload), so
|
|
@@ -306,17 +313,6 @@ function activityRefForTool(tool) {
|
|
|
306
313
|
operationIdFromArgs(tool.resultText);
|
|
307
314
|
return operationId ? `operation:${operationId}` : `tool:${tool.id}`;
|
|
308
315
|
}
|
|
309
|
-
export function dagProgressActivityForTool(tool) {
|
|
310
|
-
if (tool.toolName !== "operationWait")
|
|
311
|
-
return undefined;
|
|
312
|
-
const activityRef = activityRefForTool(tool);
|
|
313
|
-
if (!activityRef.startsWith("operation:"))
|
|
314
|
-
return undefined;
|
|
315
|
-
return {
|
|
316
|
-
id: `dag-progress:${activityRef}`,
|
|
317
|
-
activityRef,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
316
|
function semanticKindForStatus(status) {
|
|
321
317
|
if (status === "running")
|
|
322
318
|
return "progress";
|
|
@@ -381,11 +377,14 @@ function nodeActivityFromMessages(messages, id, activityRef) {
|
|
|
381
377
|
function operationWaitActivity(tool) {
|
|
382
378
|
const activityRef = activityRefForTool(tool);
|
|
383
379
|
const id = `activity:${tool.id}`;
|
|
380
|
+
const seconds = maxWaitSeconds(tool.args);
|
|
384
381
|
if (tool.status === "running") {
|
|
385
382
|
return {
|
|
386
383
|
id,
|
|
387
384
|
kind: "progress",
|
|
388
|
-
title:
|
|
385
|
+
title: seconds
|
|
386
|
+
? `正在等待新的执行进展,最长等待时间 ${seconds} 秒`
|
|
387
|
+
: "正在等待新的执行进展",
|
|
389
388
|
activityRef,
|
|
390
389
|
};
|
|
391
390
|
}
|
|
@@ -156,102 +156,6 @@ export function deriveChatSessionTelemetry(events) {
|
|
|
156
156
|
export function billedInputTokens(usage) {
|
|
157
157
|
return usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
158
158
|
}
|
|
159
|
-
export function deriveChatTurnTelemetry(events) {
|
|
160
|
-
const byTurn = new Map();
|
|
161
|
-
const turnOf = (turnId) => {
|
|
162
|
-
let entry = byTurn.get(turnId);
|
|
163
|
-
if (!entry) {
|
|
164
|
-
entry = {
|
|
165
|
-
turnId,
|
|
166
|
-
steps: 0,
|
|
167
|
-
llmMs: 0,
|
|
168
|
-
toolMs: 0,
|
|
169
|
-
toolCalls: 0,
|
|
170
|
-
samples: [],
|
|
171
|
-
};
|
|
172
|
-
byTurn.set(turnId, entry);
|
|
173
|
-
}
|
|
174
|
-
return entry;
|
|
175
|
-
};
|
|
176
|
-
const toolStarts = new Map();
|
|
177
|
-
for (const event of events) {
|
|
178
|
-
const at = eventTime(event);
|
|
179
|
-
const turn = turnOf(event.turnId);
|
|
180
|
-
if (event.kind === "usage") {
|
|
181
|
-
const sample = usageSample(event.data);
|
|
182
|
-
if (sample)
|
|
183
|
-
turn.samples.push(sample);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (event.kind === "user_message") {
|
|
187
|
-
if (turn.startedAt === undefined && at !== undefined)
|
|
188
|
-
turn.startedAt = at;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (event.kind === "agent_start") {
|
|
192
|
-
if (at !== undefined)
|
|
193
|
-
turn.stepStart = at;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (event.kind === "tool_call") {
|
|
197
|
-
turn.toolCalls += 1;
|
|
198
|
-
const toolCallId = event.data.toolCallId;
|
|
199
|
-
if (typeof toolCallId === "string" && toolCallId && at !== undefined) {
|
|
200
|
-
toolStarts.set(`${event.turnId}\u0000${toolCallId}`, at);
|
|
201
|
-
}
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
if (event.kind === "tool_result") {
|
|
205
|
-
const toolCallId = event.data.toolCallId;
|
|
206
|
-
if (typeof toolCallId === "string" && toolCallId && at !== undefined) {
|
|
207
|
-
const key = `${event.turnId}\u0000${toolCallId}`;
|
|
208
|
-
const startedAt = toolStarts.get(key);
|
|
209
|
-
if (startedAt !== undefined && at >= startedAt) {
|
|
210
|
-
turn.toolMs += at - startedAt;
|
|
211
|
-
}
|
|
212
|
-
toolStarts.delete(key);
|
|
213
|
-
// Next assistant step within the same turn starts after this tool.
|
|
214
|
-
turn.stepStart = at;
|
|
215
|
-
}
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (event.kind !== "message_end")
|
|
219
|
-
continue;
|
|
220
|
-
turn.steps += 1;
|
|
221
|
-
const recorded = stepTiming(event.data);
|
|
222
|
-
if (recorded?.llmMs !== undefined)
|
|
223
|
-
turn.llmMs += recorded.llmMs;
|
|
224
|
-
else if (at !== undefined &&
|
|
225
|
-
turn.stepStart !== undefined &&
|
|
226
|
-
at >= turn.stepStart) {
|
|
227
|
-
turn.llmMs += at - turn.stepStart;
|
|
228
|
-
}
|
|
229
|
-
if (recorded?.ttftMs !== undefined) {
|
|
230
|
-
turn.ttftMs = (turn.ttftMs ?? 0) + recorded.ttftMs;
|
|
231
|
-
}
|
|
232
|
-
if (recorded?.decodeMs !== undefined &&
|
|
233
|
-
recorded.decodeMs > 0 &&
|
|
234
|
-
recorded.outputTokens !== undefined) {
|
|
235
|
-
turn.decodeMs = (turn.decodeMs ?? 0) + recorded.decodeMs;
|
|
236
|
-
turn.decodeTokens = (turn.decodeTokens ?? 0) + recorded.outputTokens;
|
|
237
|
-
}
|
|
238
|
-
turn.stepStart = undefined;
|
|
239
|
-
}
|
|
240
|
-
return [...byTurn.values()].map((entry) => ({
|
|
241
|
-
turnId: entry.turnId,
|
|
242
|
-
steps: entry.steps,
|
|
243
|
-
llmMs: entry.llmMs,
|
|
244
|
-
toolMs: entry.toolMs,
|
|
245
|
-
toolCalls: entry.toolCalls,
|
|
246
|
-
...(entry.ttftMs !== undefined ? { ttftMs: entry.ttftMs } : {}),
|
|
247
|
-
...(entry.decodeMs !== undefined ? { decodeMs: entry.decodeMs } : {}),
|
|
248
|
-
...(entry.decodeTokens !== undefined
|
|
249
|
-
? { decodeTokens: entry.decodeTokens }
|
|
250
|
-
: {}),
|
|
251
|
-
usage: aggregateUsage(entry.samples),
|
|
252
|
-
...(entry.startedAt !== undefined ? { startedAt: entry.startedAt } : {}),
|
|
253
|
-
}));
|
|
254
|
-
}
|
|
255
159
|
export function cacheHitPercent(usage) {
|
|
256
160
|
const billed = billedInputTokens(usage);
|
|
257
161
|
return billed > 0 ? Math.round((usage.cacheReadTokens / billed) * 100) : null;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Turn grouping for Operator Chat inline process UX (pi-web ProcessDetails mind).
|
|
3
3
|
*/
|
|
4
4
|
import { getDisplayableAssistantBlocks, joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
|
|
5
|
-
import {
|
|
5
|
+
import { semanticActivityForTool, } from "./semantic-activity.js";
|
|
6
6
|
function emptyGroup(key) {
|
|
7
7
|
return {
|
|
8
8
|
key,
|
|
@@ -72,22 +72,14 @@ export function hydrateSessionThread(raw) {
|
|
|
72
72
|
turnId = item.turnId;
|
|
73
73
|
if (!turnId)
|
|
74
74
|
turnId = `restored-tool-${item.id}`;
|
|
75
|
-
// Prefer event-ring timestamps (tool_call at → tool_result at) so
|
|
76
|
-
// refreshed sessions keep real elapsed durations instead of 0ms.
|
|
77
|
-
const startedAtMs = item.startedAt ? Date.parse(item.startedAt) : NaN;
|
|
78
|
-
const finishedAtMs = item.finishedAt ? Date.parse(item.finishedAt) : NaN;
|
|
79
75
|
const entry = {
|
|
80
76
|
id: item.toolCallId || item.id,
|
|
81
77
|
toolName: item.toolName || "tool",
|
|
82
78
|
status: item.isError === true ? "error" : "ok",
|
|
83
79
|
resultText: item.text,
|
|
84
80
|
result: item.text,
|
|
85
|
-
createdAt
|
|
86
|
-
|
|
87
|
-
: createdAt,
|
|
88
|
-
finishedAt: Number.isFinite(finishedAtMs)
|
|
89
|
-
? finishedAtMs
|
|
90
|
-
: createdAt,
|
|
81
|
+
createdAt,
|
|
82
|
+
finishedAt: createdAt,
|
|
91
83
|
turnId: item.turnId ?? turnId,
|
|
92
84
|
...(item.eventSeq === undefined ? {} : { eventSeq: item.eventSeq }),
|
|
93
85
|
...(item.finishedSeq === undefined
|
|
@@ -126,11 +118,6 @@ function flushToolBuffer(processItems, buffer) {
|
|
|
126
118
|
return;
|
|
127
119
|
const sorted = sortToolsStable(buffer);
|
|
128
120
|
for (const tool of sorted) {
|
|
129
|
-
const dagProgress = dagProgressActivityForTool(tool);
|
|
130
|
-
if (dagProgress) {
|
|
131
|
-
processItems.push({ kind: "dag-progress", progress: dagProgress });
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
121
|
const activity = semanticActivityForTool(tool);
|
|
135
122
|
if (activity) {
|
|
136
123
|
processItems.push({ kind: "activity", activity });
|
|
@@ -140,17 +127,6 @@ function flushToolBuffer(processItems, buffer) {
|
|
|
140
127
|
}
|
|
141
128
|
}
|
|
142
129
|
}
|
|
143
|
-
function collapseDagProgressItems(items) {
|
|
144
|
-
const seen = new Set();
|
|
145
|
-
return items.filter((item) => {
|
|
146
|
-
if (item.kind !== "dag-progress")
|
|
147
|
-
return true;
|
|
148
|
-
if (seen.has(item.progress.activityRef))
|
|
149
|
-
return false;
|
|
150
|
-
seen.add(item.progress.activityRef);
|
|
151
|
-
return true;
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
130
|
function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
|
|
155
131
|
let buffer = [];
|
|
156
132
|
for (const block of blocks) {
|
|
@@ -225,7 +201,7 @@ function fillProcessDetails(group) {
|
|
|
225
201
|
const orphans = group.tools.filter((tool) => !usedToolIds.has(tool.id));
|
|
226
202
|
if (orphans.length > 0)
|
|
227
203
|
flushToolBuffer(processItems, orphans);
|
|
228
|
-
group.processItems =
|
|
204
|
+
group.processItems = processItems;
|
|
229
205
|
if (!finalAssistant)
|
|
230
206
|
return;
|
|
231
207
|
if (blocks.length === 0) {
|
|
@@ -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 "./app-data.js";
|
|
13
13
|
export { OperationStore } from "./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";
|