@tea-agent/loop-agent 0.39.0-next.3 → 0.39.0-next.30
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 +4 -1
- package/CHANGELOG.md +267 -65
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli.js +2 -1
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +118 -14
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +8 -1
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +533 -29
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +181 -27
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +22 -0
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/pi-runtime.js +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +478 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +17 -30
- package/dist/worker/console/harness-pi-model-matrix.js +137 -0
- package/dist/worker/console/operator-actions.js +244 -4
- package/dist/worker/console/operator-user-error.js +19 -0
- package/dist/worker/console/pi-readiness.js +12 -5
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +83 -10
- package/dist/worker/console/server.js +42 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-CiGOV5eG.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-Ds9Gyfd8.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-C6V6uC98.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-DezF7Ol9.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-BCWk3gCT.js} +1 -1
- package/dist/worker/console/static/assets/channel-DMvu1nmi.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-DbYr-Cxy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-XSlusW4G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-BtcKAj9i.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BF57lhXj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-DJIlR25o.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-DklNc7dm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-fZNtEdQZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-BBgLqV4h.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-Dl9e5n_J.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-B0JK51NT.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DVzM-02m.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DVzM-02m.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-Ch-ajv4O.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-xn_NxcfB.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-DRD0fyga.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-COtnKp4B.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-BQp_CAqG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-x0y-vNgq.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-N2ezwL31.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-CSaxRH6Q.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-6Ro7Kpbd.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-CrIIQEAI.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-DivKcq8g.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-BXrfDo8y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-LTTQmMRJ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-DP3o_5qd.js} +1 -1
- package/dist/worker/console/static/assets/index-C2jmYBfu.js +404 -0
- package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-wVfSY1WH.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-BRKfFL0I.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-DEml3jLb.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-DTzBmOqN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-Cpy1d873.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-C1cSavOy.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-BEBSKvYM.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-BrLs4Oac.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-B47AZcLa.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-s0dVeYR2.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-lW8aJ7Q6.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-BcHJPf6Q.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-DdegpUUf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-BxFWqNv8.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-cOdUDPbR.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-DpxlYl8P.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BwecGAkM.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DQOfurE0.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D5WW_LbN.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-CIp3tC6V.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-C-adIPKn.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-CiYrar9M.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-WHrdTV0-.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +39 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +82 -22
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +216 -10
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/format.js +23 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- package/dist/worker/observe/static/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +827 -53
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +12 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +493 -2
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +221 -31
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +10 -18
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/init-hybrid.js +805 -113
- package/dist/workflows/dag/node-execution.js +194 -6
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +224 -27
- package/dist/workflows/dag/rerun-run.js +61 -6
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +113 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +157 -1
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-implementation-contract.schema.json +9 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/harness.json +3 -1
- package/package.json +12 -4
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/frontend-design-review/references/review-checklist.md +2 -0
- package/skills/frontend-implementation/references/design-spec.md +16 -19
- package/skills/frontend-implementation/references/node-contracts.md +9 -6
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/index-DQxiso1w.js +0 -325
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
|
@@ -17,6 +17,7 @@ import { dispatchOperatorAction } from "../operator-actions.js";
|
|
|
17
17
|
import { authorizeOperatorChatTool } from "./tools.js";
|
|
18
18
|
import { projectOperationForChat } from "./chat-event-store.js";
|
|
19
19
|
import { scrubSecrets } from "./explore-tools.js";
|
|
20
|
+
import { hashDeferredTurnPayload, parseDeferredTurn, } from "./deferred-turn.js";
|
|
20
21
|
const CHAT_TITLE_MAX_CHARS = 80;
|
|
21
22
|
/** Safe, one-line bounded title used for placeholders and model output. */
|
|
22
23
|
export function cleanChatTitle(value) {
|
|
@@ -53,6 +54,7 @@ export class ChatSessionStore {
|
|
|
53
54
|
const raw = await readJsonIfExists(recordPath(this.appData, sessionId));
|
|
54
55
|
if (!raw || raw.sessionId !== sessionId)
|
|
55
56
|
return undefined;
|
|
57
|
+
const deferredTurn = parseDeferredTurn(raw.deferredTurn);
|
|
56
58
|
return {
|
|
57
59
|
schemaVersion: 2,
|
|
58
60
|
sessionId,
|
|
@@ -67,6 +69,7 @@ export class ChatSessionStore {
|
|
|
67
69
|
...(typeof raw.modelProvider === "string" ? { modelProvider: raw.modelProvider } : {}),
|
|
68
70
|
...(typeof raw.modelId === "string" ? { modelId: raw.modelId } : {}),
|
|
69
71
|
...(typeof raw.thinkingLevel === "string" ? { thinkingLevel: raw.thinkingLevel } : {}),
|
|
72
|
+
...(deferredTurn ? { deferredTurn } : {}),
|
|
70
73
|
messages: Array.isArray(raw.messages) ? raw.messages : [],
|
|
71
74
|
toolInvocations: Array.isArray(raw.toolInvocations) ? raw.toolInvocations : [],
|
|
72
75
|
};
|
|
@@ -147,6 +150,7 @@ export class ChatSessionStore {
|
|
|
147
150
|
...(input.targetSessionFile ? { sessionFile: input.targetSessionFile } : { sessionFile: undefined }),
|
|
148
151
|
createdAt: now,
|
|
149
152
|
updatedAt: now,
|
|
153
|
+
deferredTurn: undefined,
|
|
150
154
|
messages: structuredClone(source.messages),
|
|
151
155
|
toolInvocations: structuredClone(source.toolInvocations),
|
|
152
156
|
};
|
|
@@ -216,6 +220,146 @@ export class ChatSessionStore {
|
|
|
216
220
|
return true;
|
|
217
221
|
});
|
|
218
222
|
}
|
|
223
|
+
async putDeferredTurn(sessionId, input) {
|
|
224
|
+
return this.serialize(sessionId, async () => {
|
|
225
|
+
const record = await this.get(sessionId);
|
|
226
|
+
if (!record)
|
|
227
|
+
return { ok: false, code: "NOT_FOUND" };
|
|
228
|
+
const clientRequestId = input.clientRequestId.trim();
|
|
229
|
+
const blockedByTurnId = input.blockedByTurnId.trim();
|
|
230
|
+
const text = input.text.trim() || (input.images.length > 0 ? "请查看附件图片。" : "");
|
|
231
|
+
if (!clientRequestId || !blockedByTurnId || !text) {
|
|
232
|
+
return { ok: false, code: "INVALID_INPUT" };
|
|
233
|
+
}
|
|
234
|
+
const payloadHash = hashDeferredTurnPayload({ text, images: input.images });
|
|
235
|
+
const existing = record.deferredTurn;
|
|
236
|
+
if (existing) {
|
|
237
|
+
if (existing.clientRequestId === clientRequestId) {
|
|
238
|
+
if (existing.payloadHash !== payloadHash) {
|
|
239
|
+
return {
|
|
240
|
+
ok: false,
|
|
241
|
+
code: "REQUEST_ID_REUSE_CONFLICT",
|
|
242
|
+
deferredTurn: existing,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return { ok: true, deferredTurn: existing, idempotent: true };
|
|
246
|
+
}
|
|
247
|
+
if (existing.state !== "queued") {
|
|
248
|
+
return {
|
|
249
|
+
ok: false,
|
|
250
|
+
code: "NOT_REPLACEABLE",
|
|
251
|
+
deferredTurn: existing,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (input.expectedRevision !== existing.revision) {
|
|
255
|
+
return {
|
|
256
|
+
ok: false,
|
|
257
|
+
code: "REVISION_CONFLICT",
|
|
258
|
+
deferredTurn: existing,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (input.expectedRevision !== undefined &&
|
|
263
|
+
input.expectedRevision !== 0) {
|
|
264
|
+
return { ok: false, code: "REVISION_CONFLICT" };
|
|
265
|
+
}
|
|
266
|
+
const now = new Date().toISOString();
|
|
267
|
+
const deferredTurn = {
|
|
268
|
+
schemaVersion: 1,
|
|
269
|
+
revision: (existing?.revision ?? 0) + 1,
|
|
270
|
+
clientRequestId,
|
|
271
|
+
payloadHash,
|
|
272
|
+
blockedByTurnId,
|
|
273
|
+
text,
|
|
274
|
+
images: structuredClone(input.images),
|
|
275
|
+
state: "queued",
|
|
276
|
+
createdAt: existing?.createdAt ?? now,
|
|
277
|
+
updatedAt: now,
|
|
278
|
+
};
|
|
279
|
+
record.deferredTurn = deferredTurn;
|
|
280
|
+
record.updatedAt = now;
|
|
281
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
282
|
+
return { ok: true, deferredTurn, idempotent: false };
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async claimDeferredTurn(sessionId, expectedRevision) {
|
|
286
|
+
return this.serialize(sessionId, async () => {
|
|
287
|
+
const record = await this.get(sessionId);
|
|
288
|
+
const existing = record?.deferredTurn;
|
|
289
|
+
if (!record || !existing)
|
|
290
|
+
return { ok: false, code: "NOT_FOUND" };
|
|
291
|
+
if (existing.revision !== expectedRevision) {
|
|
292
|
+
return { ok: false, code: "REVISION_CONFLICT", deferredTurn: existing };
|
|
293
|
+
}
|
|
294
|
+
if (existing.state !== "queued") {
|
|
295
|
+
return { ok: false, code: "NOT_CLAIMABLE", deferredTurn: existing };
|
|
296
|
+
}
|
|
297
|
+
const next = {
|
|
298
|
+
...existing,
|
|
299
|
+
revision: existing.revision + 1,
|
|
300
|
+
state: "dispatching",
|
|
301
|
+
updatedAt: new Date().toISOString(),
|
|
302
|
+
};
|
|
303
|
+
record.deferredTurn = next;
|
|
304
|
+
record.updatedAt = next.updatedAt;
|
|
305
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
306
|
+
return { ok: true, deferredTurn: next };
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
async transitionDeferredTurn(sessionId, input) {
|
|
310
|
+
return this.serialize(sessionId, async () => {
|
|
311
|
+
const record = await this.get(sessionId);
|
|
312
|
+
const existing = record?.deferredTurn;
|
|
313
|
+
if (!record || !existing || existing.revision !== input.expectedRevision)
|
|
314
|
+
return undefined;
|
|
315
|
+
const next = {
|
|
316
|
+
...existing,
|
|
317
|
+
revision: existing.revision + 1,
|
|
318
|
+
state: input.state,
|
|
319
|
+
updatedAt: new Date().toISOString(),
|
|
320
|
+
...(input.acceptedTurnId ? { acceptedTurnId: input.acceptedTurnId } : {}),
|
|
321
|
+
...(input.lastError ? { lastError: input.lastError } : {}),
|
|
322
|
+
};
|
|
323
|
+
if (!input.lastError)
|
|
324
|
+
delete next.lastError;
|
|
325
|
+
record.deferredTurn = next;
|
|
326
|
+
record.updatedAt = next.updatedAt;
|
|
327
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
328
|
+
return next;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async deleteDeferredTurn(sessionId, input) {
|
|
332
|
+
return this.serialize(sessionId, async () => {
|
|
333
|
+
const record = await this.get(sessionId);
|
|
334
|
+
const existing = record?.deferredTurn;
|
|
335
|
+
if (!record || !existing || existing.clientRequestId !== input.clientRequestId) {
|
|
336
|
+
return { ok: false, code: "NOT_FOUND" };
|
|
337
|
+
}
|
|
338
|
+
if (existing.state !== "queued" && existing.state !== "failed") {
|
|
339
|
+
return { ok: false, code: "NOT_CANCELLABLE", deferredTurn: existing };
|
|
340
|
+
}
|
|
341
|
+
if (existing.revision !== input.expectedRevision) {
|
|
342
|
+
return { ok: false, code: "REVISION_CONFLICT", deferredTurn: existing };
|
|
343
|
+
}
|
|
344
|
+
delete record.deferredTurn;
|
|
345
|
+
record.updatedAt = new Date().toISOString();
|
|
346
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
347
|
+
return { ok: true, deferredTurn: existing };
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
async clearDeferredTurnForRequest(sessionId, clientRequestId) {
|
|
351
|
+
return this.serialize(sessionId, async () => {
|
|
352
|
+
const record = await this.get(sessionId);
|
|
353
|
+
const existing = record?.deferredTurn;
|
|
354
|
+
if (!record || !existing || existing.clientRequestId !== clientRequestId) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
delete record.deferredTurn;
|
|
358
|
+
record.updatedAt = new Date().toISOString();
|
|
359
|
+
await writeSecureJson(recordPath(this.appData, sessionId), record);
|
|
360
|
+
return { deferredTurn: existing, clearedRevision: existing.revision + 1 };
|
|
361
|
+
});
|
|
362
|
+
}
|
|
219
363
|
async appendMessage(sessionId, message) {
|
|
220
364
|
return this.serialize(sessionId, async () => {
|
|
221
365
|
const record = await this.get(sessionId);
|
|
@@ -340,10 +484,10 @@ export async function runChatTurn(options) {
|
|
|
340
484
|
},
|
|
341
485
|
};
|
|
342
486
|
}
|
|
343
|
-
await store.appendMessage(sessionId, { role: "user", text });
|
|
487
|
+
const userMessage = await store.appendMessage(sessionId, { role: "user", text });
|
|
344
488
|
const afterUserMessage = await store.get(sessionId);
|
|
345
489
|
if (afterUserMessage)
|
|
346
|
-
await options.onUserMessagePersisted?.(afterUserMessage);
|
|
490
|
+
await options.onUserMessagePersisted?.(afterUserMessage, userMessage);
|
|
347
491
|
let turnError;
|
|
348
492
|
// Collect persistence promises so runChatTurn does not resolve before the
|
|
349
493
|
// chat history / audit trail is durably written (also makes the outcome
|
|
@@ -92,36 +92,25 @@ function sortToolsStable(tools) {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* Flush buffered consecutive tools into process items
|
|
96
|
-
*
|
|
97
|
-
*
|
|
95
|
+
* Flush buffered consecutive tools into process items (pi-web aligned:
|
|
96
|
+
* strict sequential flow, no synthetic grouping). Semantic operator tools
|
|
97
|
+
* stay as individual activity events; plain tools each render as their own
|
|
98
|
+
* card in stable server order (eventSeq first, createdAt fallback).
|
|
98
99
|
* Thinking/text/activity boundaries break the buffer upstream.
|
|
99
100
|
*/
|
|
100
101
|
function flushToolBuffer(processItems, buffer) {
|
|
101
102
|
if (buffer.length === 0)
|
|
102
103
|
return;
|
|
103
104
|
const sorted = sortToolsStable(buffer);
|
|
104
|
-
let plainGroup = [];
|
|
105
|
-
const flushPlain = () => {
|
|
106
|
-
if (plainGroup.length === 1) {
|
|
107
|
-
processItems.push({ kind: "tool", tool: plainGroup[0] });
|
|
108
|
-
}
|
|
109
|
-
else if (plainGroup.length > 1) {
|
|
110
|
-
processItems.push({ kind: "tool-group", tools: plainGroup });
|
|
111
|
-
}
|
|
112
|
-
plainGroup = [];
|
|
113
|
-
};
|
|
114
105
|
for (const tool of sorted) {
|
|
115
106
|
const activity = semanticActivityForTool(tool);
|
|
116
107
|
if (activity) {
|
|
117
|
-
flushPlain();
|
|
118
108
|
processItems.push({ kind: "activity", activity });
|
|
119
109
|
}
|
|
120
110
|
else {
|
|
121
|
-
|
|
111
|
+
processItems.push({ kind: "tool", tool });
|
|
122
112
|
}
|
|
123
113
|
}
|
|
124
|
-
flushPlain();
|
|
125
114
|
}
|
|
126
115
|
function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
|
|
127
116
|
let buffer = [];
|
|
@@ -227,9 +216,6 @@ export function buildTurnProcessGroups(input) {
|
|
|
227
216
|
current = startGroup(`assistant-${message.id}`);
|
|
228
217
|
current.assistants.push(message);
|
|
229
218
|
}
|
|
230
|
-
if (groups.length === 0 && input.toolCalls.length > 0) {
|
|
231
|
-
groups.push(emptyGroup("orphan-tools"));
|
|
232
|
-
}
|
|
233
219
|
const assigned = new Set();
|
|
234
220
|
const assistantToGroup = new Map();
|
|
235
221
|
for (const group of groups) {
|
|
@@ -276,17 +262,12 @@ export function buildTurnProcessGroups(input) {
|
|
|
276
262
|
const end = next?.user?.createdAt ?? Number.POSITIVE_INFINITY;
|
|
277
263
|
return tool.createdAt >= start && tool.createdAt < end;
|
|
278
264
|
});
|
|
265
|
+
// Time window only. Never dump unmatched tools onto the latest group:
|
|
266
|
+
// that is how a previous turn's bash cards jump under a new prompt.
|
|
279
267
|
if (windowGroup) {
|
|
280
268
|
windowGroup.tools.push(tool);
|
|
281
269
|
assigned.add(tool.id);
|
|
282
270
|
}
|
|
283
|
-
else {
|
|
284
|
-
const lastGroup = groups.at(-1);
|
|
285
|
-
if (lastGroup) {
|
|
286
|
-
lastGroup.tools.push(tool);
|
|
287
|
-
assigned.add(tool.id);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
271
|
}
|
|
291
272
|
for (const group of groups) {
|
|
292
273
|
group.tools.sort((a, b) => {
|
|
@@ -303,6 +284,14 @@ export function buildTurnProcessGroups(input) {
|
|
|
303
284
|
}
|
|
304
285
|
return groups;
|
|
305
286
|
}
|
|
287
|
+
/** Keep the most recent complete turns. Slice groups, never raw messages. */
|
|
288
|
+
export function selectRecentTurnGroups(groups, limit) {
|
|
289
|
+
if (limit <= 0)
|
|
290
|
+
return [];
|
|
291
|
+
if (groups.length <= limit)
|
|
292
|
+
return [...groups];
|
|
293
|
+
return groups.slice(-limit);
|
|
294
|
+
}
|
|
306
295
|
export function processGroupSummary(tools, options) {
|
|
307
296
|
const count = tools.length;
|
|
308
297
|
const thinkingCount = options?.thinkingCount ?? 0;
|
|
@@ -324,7 +313,7 @@ export function processGroupSummary(tools, options) {
|
|
|
324
313
|
parts.push("运行中");
|
|
325
314
|
return { count, errorCount, runningCount, stepCount, label: parts.join(" · ") };
|
|
326
315
|
}
|
|
327
|
-
/** Summary over rendered process items
|
|
316
|
+
/** Summary over rendered process items. */
|
|
328
317
|
export function summarizeProcessItems(items) {
|
|
329
318
|
const tools = [];
|
|
330
319
|
let thinkingCount = 0;
|
|
@@ -344,10 +333,8 @@ export function summarizeProcessItems(items) {
|
|
|
344
333
|
else if (item.activity.kind === "progress")
|
|
345
334
|
activityRunningCount += 1;
|
|
346
335
|
}
|
|
347
|
-
else if (item.kind === "tool")
|
|
348
|
-
tools.push(item.tool);
|
|
349
336
|
else
|
|
350
|
-
tools.push(
|
|
337
|
+
tools.push(item.tool);
|
|
351
338
|
}
|
|
352
339
|
return processGroupSummary(tools, {
|
|
353
340
|
thinkingCount,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* harness.json executors.pi LOW/MED/HIGH 模型矩阵的读写与校验。
|
|
3
|
+
*
|
|
4
|
+
* 该模块只服务于 Console「模型与认证」页的模型矩阵编辑器:
|
|
5
|
+
* - `validatePiModelMatrixTiers`:写入前拒绝空串 / default 哨兵 / 非字符串 /
|
|
6
|
+
* 畸形 provider/model(如 provider/、/model、a//b)。
|
|
7
|
+
* - `readPiModelMatrixProjection`:读全文档并投影三档的 provider/model 或裸
|
|
8
|
+
* model id(供 readiness 结构化投影 piModelMatrix 使用)。
|
|
9
|
+
* - `writePiModelMatrix`:读全文档 → 校验 → 原子写 tmp + fsync + rename,
|
|
10
|
+
* 保留 $schema 与其余所有字段。
|
|
11
|
+
*
|
|
12
|
+
* 本模块在 worker/console(Node 侧),不得被浏览器静态资源直接 import
|
|
13
|
+
* (依赖 node:fs / node:crypto / node:path)。
|
|
14
|
+
*/
|
|
15
|
+
import { randomBytes } from "node:crypto";
|
|
16
|
+
import { open, readFile, rename, writeFile } from "node:fs/promises";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { normalizeExecutorTierValue } from "../../governance/manifest-types.js";
|
|
19
|
+
export const PI_MODEL_TIERS = ["LOW", "MED", "HIGH"];
|
|
20
|
+
/** 校验单档字符串值,返回错误文案;合法返回 undefined。 */
|
|
21
|
+
export function piModelTierValueError(value) {
|
|
22
|
+
if (typeof value !== "string")
|
|
23
|
+
return "模型值必须是字符串";
|
|
24
|
+
if (value.length === 0)
|
|
25
|
+
return "模型值不能为空";
|
|
26
|
+
if (value.trim() !== value)
|
|
27
|
+
return "模型值不能包含首尾空白";
|
|
28
|
+
if (value === "default")
|
|
29
|
+
return "default 是保留哨兵,不能作为模型值";
|
|
30
|
+
// 比 normalizeExecutorTierValue 更严格:拒绝 provider/、/model、a//b。
|
|
31
|
+
if (value.startsWith("/") || value.endsWith("/") || value.includes("//")) {
|
|
32
|
+
return "provider/model 格式非法:不允许 /model、provider/ 或 a//b";
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 写入前校验三档字符串值。任何一档非法即整体拒绝,保证不落盘。
|
|
38
|
+
*/
|
|
39
|
+
export function validatePiModelMatrixTiers(input) {
|
|
40
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
code: "INVALID_INPUT",
|
|
44
|
+
message: "模型矩阵必须是 { LOW, MED, HIGH } 对象",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const record = input;
|
|
48
|
+
const tiers = {};
|
|
49
|
+
for (const tier of PI_MODEL_TIERS) {
|
|
50
|
+
const raw = record[tier];
|
|
51
|
+
const error = piModelTierValueError(raw);
|
|
52
|
+
if (error) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
code: "INVALID_INPUT",
|
|
56
|
+
message: `${tier}: ${error}`,
|
|
57
|
+
tier,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
tiers[tier] = raw;
|
|
61
|
+
}
|
|
62
|
+
return { ok: true, tiers };
|
|
63
|
+
}
|
|
64
|
+
async function readHarnessJson(repoRoot) {
|
|
65
|
+
try {
|
|
66
|
+
const raw = await readFile(path.join(repoRoot, "harness.json"), "utf8");
|
|
67
|
+
const parsed = JSON.parse(raw);
|
|
68
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 投影 harness.json executors.pi 三档(provider/model 或裸 model id)。
|
|
79
|
+
* 缺文件 / 非法 JSON / 未配置档位均优雅降级为缺省,绝不抛错。
|
|
80
|
+
*/
|
|
81
|
+
export async function readPiModelMatrixProjection(repoRoot) {
|
|
82
|
+
const manifest = await readHarnessJson(repoRoot);
|
|
83
|
+
const pi = manifest?.executors?.pi;
|
|
84
|
+
const projection = {};
|
|
85
|
+
if (!pi || typeof pi !== "object")
|
|
86
|
+
return projection;
|
|
87
|
+
for (const tier of PI_MODEL_TIERS) {
|
|
88
|
+
const selection = normalizeExecutorTierValue(pi[tier]);
|
|
89
|
+
if (!selection)
|
|
90
|
+
continue;
|
|
91
|
+
projection[tier] = {
|
|
92
|
+
model: selection.model,
|
|
93
|
+
...(selection.provider ? { provider: selection.provider } : {}),
|
|
94
|
+
value: selection.provider
|
|
95
|
+
? `${selection.provider}/${selection.model}`
|
|
96
|
+
: selection.model,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return projection;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 原子更新 harness.json executors.pi.LOW/MED/HIGH:
|
|
103
|
+
* 读全文档 → 校验 → 保留其余字段 → 写 tmp + fsync + rename。
|
|
104
|
+
*/
|
|
105
|
+
export async function writePiModelMatrix(repoRoot, tiers) {
|
|
106
|
+
const validation = validatePiModelMatrixTiers(tiers);
|
|
107
|
+
if (!validation.ok) {
|
|
108
|
+
throw Object.assign(new Error(validation.message), {
|
|
109
|
+
code: validation.code,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const file = path.join(repoRoot, "harness.json");
|
|
113
|
+
const manifest = await readHarnessJson(repoRoot);
|
|
114
|
+
if (!manifest) {
|
|
115
|
+
throw Object.assign(new Error("harness.json 不存在或不是合法 JSON"), {
|
|
116
|
+
code: "HARNESS_NOT_FOUND",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const executors = (manifest.executors ?? {});
|
|
120
|
+
const pi = (executors.pi ?? {});
|
|
121
|
+
const nextPi = { ...pi };
|
|
122
|
+
for (const tier of PI_MODEL_TIERS) {
|
|
123
|
+
nextPi[tier] = validation.tiers[tier];
|
|
124
|
+
}
|
|
125
|
+
const nextManifest = { ...manifest, executors: { ...executors, pi: nextPi } };
|
|
126
|
+
const body = `${JSON.stringify(nextManifest, null, 2)}\n`;
|
|
127
|
+
const tmp = path.join(repoRoot, `.harness.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
|
|
128
|
+
await writeFile(tmp, body, { encoding: "utf8" });
|
|
129
|
+
const handle = await open(tmp, "r+");
|
|
130
|
+
try {
|
|
131
|
+
await handle.sync();
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
await handle.close();
|
|
135
|
+
}
|
|
136
|
+
await rename(tmp, file);
|
|
137
|
+
}
|