@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 { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_
|
|
|
17
17
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
18
18
|
import { cleanChatTitle, acceptedOperationIdFromResult } from "./session-store.js";
|
|
19
19
|
import { hashChatTurnPayload, projectChatToolEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
|
|
20
|
+
import { projectDeferredTurnSummary, } from "./deferred-turn.js";
|
|
20
21
|
import { THINKING_LEVELS, RuntimeSnapshotUnavailableError, } from "./pi-runtime.js";
|
|
21
22
|
import { ResourcePreferencesError, setSkillAutoInvocationPreference, } from "./resource-preferences-store.js";
|
|
22
23
|
import { ComposerDraftStore } from "./composer-draft-store.js";
|
|
@@ -51,7 +52,7 @@ function turnSummary(turn) {
|
|
|
51
52
|
function isTerminalTurnState(state) {
|
|
52
53
|
return state === "settled" || state === "aborted" || state === "failed";
|
|
53
54
|
}
|
|
54
|
-
const DEFAULT_ABORT_WAIT_MS =
|
|
55
|
+
const DEFAULT_ABORT_WAIT_MS = 2_500;
|
|
55
56
|
/** Parse Chat's `<epoch>:<seq>` SSE cursor. Legacy `<sessionId>:<seq>` has
|
|
56
57
|
* the same wire shape; its prefix intentionally fails the current process
|
|
57
58
|
* epoch comparison and triggers safe snapshot recovery (M5.1). */
|
|
@@ -269,6 +270,7 @@ export function parseChatImages(value) {
|
|
|
269
270
|
}
|
|
270
271
|
function gateMutation(req, deps) {
|
|
271
272
|
const failure = evaluateMutationGate(req, {
|
|
273
|
+
cookieName: deps.bootToken.cookieName,
|
|
272
274
|
bootToken: deps.bootToken.value,
|
|
273
275
|
confirmationToken: deps.bootToken.confirmationToken,
|
|
274
276
|
consoleOrigin: deps.getConsoleOrigin(),
|
|
@@ -645,19 +647,195 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
645
647
|
const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
|
|
646
648
|
const snapshot = deps.events.snapshot(sessionId);
|
|
647
649
|
const lastEvent = snapshot.at(-1);
|
|
650
|
+
const activeTurn = deps.events.getActiveTurn(sessionId);
|
|
648
651
|
sendJson(res, 200, {
|
|
649
652
|
ok: true,
|
|
650
653
|
eventEpoch: deps.events.epoch(sessionId),
|
|
651
654
|
session: {
|
|
652
655
|
...record,
|
|
653
656
|
composerDraft,
|
|
654
|
-
activeTurnId:
|
|
657
|
+
activeTurnId: activeTurn?.turnId,
|
|
658
|
+
turnPhase: activeTurn
|
|
659
|
+
? activeTurn.abortRequestedAt
|
|
660
|
+
? "stopping"
|
|
661
|
+
: "running"
|
|
662
|
+
: "idle",
|
|
655
663
|
},
|
|
656
664
|
hasBash: true,
|
|
657
665
|
lastEventId: lastEvent?.eventId ?? null,
|
|
658
666
|
lastEventSeq: lastEvent?.seq ?? 0,
|
|
659
667
|
});
|
|
660
668
|
}
|
|
669
|
+
function parseDeferredImages(value) {
|
|
670
|
+
const parsed = parseChatImages(value);
|
|
671
|
+
const raw = Array.isArray(value) ? value : [];
|
|
672
|
+
return parsed.map((image, index) => {
|
|
673
|
+
const candidate = raw[index];
|
|
674
|
+
const name = candidate &&
|
|
675
|
+
typeof candidate === "object" &&
|
|
676
|
+
typeof candidate.name === "string"
|
|
677
|
+
? candidate.name.trim().slice(0, 180)
|
|
678
|
+
: `image-${index + 1}`;
|
|
679
|
+
return { ...image, name: name || `image-${index + 1}` };
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
export async function handlePutDeferredTurn(req, res, deps, sessionId) {
|
|
683
|
+
const gate = gateMutation(req, deps);
|
|
684
|
+
if (!gate.ok) {
|
|
685
|
+
sendJson(res, gate.status, {
|
|
686
|
+
ok: false,
|
|
687
|
+
accepted: false,
|
|
688
|
+
error: { code: gate.code, message: gate.message },
|
|
689
|
+
});
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
let body;
|
|
693
|
+
try {
|
|
694
|
+
body = await readJsonBody(req);
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
sendJson(res, 400, {
|
|
698
|
+
ok: false,
|
|
699
|
+
accepted: false,
|
|
700
|
+
error: {
|
|
701
|
+
code: "INVALID_INPUT",
|
|
702
|
+
message: error instanceof Error ? error.message : String(error),
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
const clientRequestId = typeof body.clientRequestId === "string" ? body.clientRequestId.trim() : "";
|
|
708
|
+
const blockedByTurnId = typeof body.blockedByTurnId === "string" ? body.blockedByTurnId.trim() : "";
|
|
709
|
+
const text = typeof body.text === "string" ? body.text : "";
|
|
710
|
+
const expectedRevision = typeof body.expectedRevision === "number" &&
|
|
711
|
+
Number.isSafeInteger(body.expectedRevision) &&
|
|
712
|
+
body.expectedRevision >= 0
|
|
713
|
+
? body.expectedRevision
|
|
714
|
+
: undefined;
|
|
715
|
+
let images;
|
|
716
|
+
try {
|
|
717
|
+
images = parseDeferredImages(body.images);
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
sendJson(res, 400, {
|
|
721
|
+
ok: false,
|
|
722
|
+
accepted: false,
|
|
723
|
+
error: {
|
|
724
|
+
code: "INVALID_ATTACHMENT",
|
|
725
|
+
message: error instanceof Error ? error.message : String(error),
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const active = deps.events.getActiveTurn(sessionId);
|
|
731
|
+
if (!active || !active.abortRequestedAt || active.turnId !== blockedByTurnId) {
|
|
732
|
+
sendJson(res, 409, {
|
|
733
|
+
ok: false,
|
|
734
|
+
accepted: false,
|
|
735
|
+
error: {
|
|
736
|
+
code: "TURN_NOT_STOPPING",
|
|
737
|
+
message: "只有仍在停止中的当前 Turn 可以保存停止后发送消息",
|
|
738
|
+
},
|
|
739
|
+
activeTurn: active ? turnSummary(active) : null,
|
|
740
|
+
});
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
const result = await deps.store.putDeferredTurn(sessionId, {
|
|
744
|
+
clientRequestId,
|
|
745
|
+
blockedByTurnId,
|
|
746
|
+
text,
|
|
747
|
+
images,
|
|
748
|
+
...(expectedRevision !== undefined ? { expectedRevision } : {}),
|
|
749
|
+
});
|
|
750
|
+
if (!result.ok) {
|
|
751
|
+
const status = result.code === "NOT_FOUND"
|
|
752
|
+
? 404
|
|
753
|
+
: result.code === "INVALID_INPUT"
|
|
754
|
+
? 400
|
|
755
|
+
: 409;
|
|
756
|
+
sendJson(res, status, {
|
|
757
|
+
ok: false,
|
|
758
|
+
accepted: false,
|
|
759
|
+
error: {
|
|
760
|
+
code: result.code,
|
|
761
|
+
message: result.code === "REVISION_CONFLICT"
|
|
762
|
+
? "待发送消息已在其他页面更新,请使用服务端最新状态"
|
|
763
|
+
: result.code === "NOT_REPLACEABLE"
|
|
764
|
+
? "待发送消息已经开始发送,不能再覆盖"
|
|
765
|
+
: result.code === "REQUEST_ID_REUSE_CONFLICT"
|
|
766
|
+
? "clientRequestId 已绑定到不同消息"
|
|
767
|
+
: "待发送消息无效或会话不存在",
|
|
768
|
+
},
|
|
769
|
+
deferredTurn: result.deferredTurn ?? null,
|
|
770
|
+
summary: projectDeferredTurnSummary(result.deferredTurn),
|
|
771
|
+
});
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
emitDeferredTurnUpdate(deps, sessionId, blockedByTurnId, result.deferredTurn, result.deferredTurn.revision);
|
|
775
|
+
sendJson(res, 200, {
|
|
776
|
+
ok: true,
|
|
777
|
+
accepted: true,
|
|
778
|
+
idempotent: result.idempotent,
|
|
779
|
+
state: result.deferredTurn.state,
|
|
780
|
+
revision: result.deferredTurn.revision,
|
|
781
|
+
deferredTurn: result.deferredTurn,
|
|
782
|
+
summary: projectDeferredTurnSummary(result.deferredTurn),
|
|
783
|
+
});
|
|
784
|
+
void dispatchDeferredTurn(deps, sessionId);
|
|
785
|
+
}
|
|
786
|
+
export async function handleDeleteDeferredTurn(req, res, deps, sessionId) {
|
|
787
|
+
const gate = gateMutation(req, deps);
|
|
788
|
+
if (!gate.ok) {
|
|
789
|
+
sendJson(res, gate.status, {
|
|
790
|
+
ok: false,
|
|
791
|
+
error: { code: gate.code, message: gate.message },
|
|
792
|
+
});
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
let body;
|
|
796
|
+
try {
|
|
797
|
+
body = await readJsonBody(req);
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
sendJson(res, 400, {
|
|
801
|
+
ok: false,
|
|
802
|
+
error: {
|
|
803
|
+
code: "INVALID_INPUT",
|
|
804
|
+
message: error instanceof Error ? error.message : String(error),
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const clientRequestId = typeof body.clientRequestId === "string" ? body.clientRequestId.trim() : "";
|
|
810
|
+
const expectedRevision = typeof body.expectedRevision === "number" && Number.isSafeInteger(body.expectedRevision)
|
|
811
|
+
? body.expectedRevision
|
|
812
|
+
: -1;
|
|
813
|
+
const result = await deps.store.deleteDeferredTurn(sessionId, {
|
|
814
|
+
clientRequestId,
|
|
815
|
+
expectedRevision,
|
|
816
|
+
});
|
|
817
|
+
if (!result.ok) {
|
|
818
|
+
sendJson(res, result.code === "NOT_FOUND" ? 404 : 409, {
|
|
819
|
+
ok: false,
|
|
820
|
+
error: {
|
|
821
|
+
code: result.code,
|
|
822
|
+
message: result.code === "NOT_CANCELLABLE"
|
|
823
|
+
? "消息已经开始发送,不能再撤回"
|
|
824
|
+
: "待发送消息已变化,请刷新后重试",
|
|
825
|
+
},
|
|
826
|
+
deferredTurn: result.deferredTurn ?? null,
|
|
827
|
+
});
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const clearedRevision = result.deferredTurn.revision + 1;
|
|
831
|
+
emitDeferredTurnUpdate(deps, sessionId, result.deferredTurn.blockedByTurnId, undefined, clearedRevision);
|
|
832
|
+
sendJson(res, 200, {
|
|
833
|
+
ok: true,
|
|
834
|
+
cancelled: true,
|
|
835
|
+
deferredTurn: result.deferredTurn,
|
|
836
|
+
revision: clearedRevision,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
661
839
|
/** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
|
|
662
840
|
* get_state equivalent): exposes the active turn + the latest turn's durable
|
|
663
841
|
* outcome WITHOUT the full message payload, so the UI can poll it on an
|
|
@@ -684,6 +862,14 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
684
862
|
isCompacting: false,
|
|
685
863
|
isBashRunning: false,
|
|
686
864
|
};
|
|
865
|
+
const runtimeBusy = Object.values(runtimeFacts).some((value) => value === true);
|
|
866
|
+
const turnPhase = active
|
|
867
|
+
? active.abortRequestedAt
|
|
868
|
+
? "stopping"
|
|
869
|
+
: "running"
|
|
870
|
+
: runtimeBusy
|
|
871
|
+
? "running"
|
|
872
|
+
: "idle";
|
|
687
873
|
sendJson(res, 200, {
|
|
688
874
|
ok: true,
|
|
689
875
|
state: {
|
|
@@ -695,6 +881,8 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
695
881
|
activeTurn: active ? turnSummary(active) : null,
|
|
696
882
|
latestTurn: latest ? turnSummary(latest) : null,
|
|
697
883
|
runtime: runtimeFacts,
|
|
884
|
+
turnPhase,
|
|
885
|
+
deferredTurn: projectDeferredTurnSummary(record.deferredTurn),
|
|
698
886
|
},
|
|
699
887
|
});
|
|
700
888
|
}
|
|
@@ -703,10 +891,19 @@ export async function handleListChatSessions(_req, res, deps) {
|
|
|
703
891
|
sendJson(res, 200, {
|
|
704
892
|
ok: true,
|
|
705
893
|
schemaVersion: 2,
|
|
706
|
-
sessions: sessions.map((session) =>
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
894
|
+
sessions: sessions.map((session) => {
|
|
895
|
+
const active = deps.events.getActiveTurn(session.sessionId);
|
|
896
|
+
return {
|
|
897
|
+
...session,
|
|
898
|
+
activeTurnId: active?.turnId,
|
|
899
|
+
turnPhase: active?.abortRequestedAt
|
|
900
|
+
? "stopping"
|
|
901
|
+
: active
|
|
902
|
+
? "running"
|
|
903
|
+
: "idle",
|
|
904
|
+
deferredTurn: projectDeferredTurnSummary(session.deferredTurn),
|
|
905
|
+
};
|
|
906
|
+
}),
|
|
710
907
|
hasBash: true,
|
|
711
908
|
});
|
|
712
909
|
}
|
|
@@ -1057,49 +1254,63 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1057
1254
|
* Pi prompt continues independently of this HTTP response and publishes to the
|
|
1058
1255
|
* persisted event ring consumed by GET /events.
|
|
1059
1256
|
*/
|
|
1060
|
-
export async function handleCreateChatTurn(req, res, deps, sessionId) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
sendJson(res, gate.status, {
|
|
1064
|
-
ok: false,
|
|
1065
|
-
accepted: false,
|
|
1066
|
-
error: { code: gate.code, message: gate.message },
|
|
1067
|
-
});
|
|
1068
|
-
return;
|
|
1069
|
-
}
|
|
1070
|
-
let body;
|
|
1071
|
-
try {
|
|
1072
|
-
body = await readJsonBody(req);
|
|
1073
|
-
}
|
|
1074
|
-
catch (error) {
|
|
1075
|
-
sendJson(res, 400, {
|
|
1076
|
-
ok: false,
|
|
1077
|
-
accepted: false,
|
|
1078
|
-
error: {
|
|
1079
|
-
code: "INVALID_INPUT",
|
|
1080
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1081
|
-
},
|
|
1082
|
-
});
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
const text = typeof body.text === "string" ? body.text.trim() : "";
|
|
1086
|
-
const clientRequestId = typeof body.clientRequestId === "string" && body.clientRequestId.trim()
|
|
1087
|
-
? body.clientRequestId.trim()
|
|
1088
|
-
: undefined;
|
|
1257
|
+
export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPayload) {
|
|
1258
|
+
let text;
|
|
1259
|
+
let clientRequestId;
|
|
1089
1260
|
let images;
|
|
1090
|
-
|
|
1091
|
-
|
|
1261
|
+
if (trustedPayload) {
|
|
1262
|
+
text = trustedPayload.text.trim();
|
|
1263
|
+
clientRequestId = trustedPayload.clientRequestId.trim();
|
|
1264
|
+
images = trustedPayload.images.map(({ type, mimeType, data }) => ({
|
|
1265
|
+
type,
|
|
1266
|
+
mimeType,
|
|
1267
|
+
data,
|
|
1268
|
+
}));
|
|
1092
1269
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1270
|
+
else {
|
|
1271
|
+
const gate = gateMutation(req, deps);
|
|
1272
|
+
if (!gate.ok) {
|
|
1273
|
+
sendJson(res, gate.status, {
|
|
1274
|
+
ok: false,
|
|
1275
|
+
accepted: false,
|
|
1276
|
+
error: { code: gate.code, message: gate.message },
|
|
1277
|
+
});
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
let body;
|
|
1281
|
+
try {
|
|
1282
|
+
body = await readJsonBody(req);
|
|
1283
|
+
}
|
|
1284
|
+
catch (error) {
|
|
1285
|
+
sendJson(res, 400, {
|
|
1286
|
+
ok: false,
|
|
1287
|
+
accepted: false,
|
|
1288
|
+
error: {
|
|
1289
|
+
code: "INVALID_INPUT",
|
|
1290
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1291
|
+
},
|
|
1292
|
+
});
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
text = typeof body.text === "string" ? body.text.trim() : "";
|
|
1296
|
+
clientRequestId =
|
|
1297
|
+
typeof body.clientRequestId === "string" && body.clientRequestId.trim()
|
|
1298
|
+
? body.clientRequestId.trim()
|
|
1299
|
+
: undefined;
|
|
1300
|
+
try {
|
|
1301
|
+
images = parseChatImages(body.images);
|
|
1302
|
+
}
|
|
1303
|
+
catch (error) {
|
|
1304
|
+
sendJson(res, 400, {
|
|
1305
|
+
ok: false,
|
|
1306
|
+
accepted: false,
|
|
1307
|
+
error: {
|
|
1308
|
+
code: "INVALID_ATTACHMENT",
|
|
1309
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1310
|
+
},
|
|
1311
|
+
});
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1103
1314
|
}
|
|
1104
1315
|
if (!text) {
|
|
1105
1316
|
sendJson(res, 400, {
|
|
@@ -1235,7 +1446,8 @@ export async function handleCreateChatTurn(req, res, deps, sessionId) {
|
|
|
1235
1446
|
text,
|
|
1236
1447
|
images,
|
|
1237
1448
|
signal: controller.signal,
|
|
1238
|
-
onUserMessagePersisted: (record) => {
|
|
1449
|
+
onUserMessagePersisted: async (record, userMessage) => {
|
|
1450
|
+
await trustedPayload?.onUserMessagePersisted?.(turn.turnId, userMessage);
|
|
1239
1451
|
const users = record.messages.filter((message) => message.role === "user");
|
|
1240
1452
|
if (users.length !== 1)
|
|
1241
1453
|
return;
|
|
@@ -1312,9 +1524,200 @@ export async function handleCreateChatTurn(req, res, deps, sessionId) {
|
|
|
1312
1524
|
error: terminalError,
|
|
1313
1525
|
});
|
|
1314
1526
|
deps.turnExecutions?.clear(sessionId, turn.turnId);
|
|
1527
|
+
void dispatchDeferredTurn(deps, sessionId);
|
|
1315
1528
|
}
|
|
1316
1529
|
})();
|
|
1317
1530
|
}
|
|
1531
|
+
function emitDeferredTurnUpdate(deps, sessionId, turnId, deferredTurn, revision) {
|
|
1532
|
+
deps.events.append(sessionId, turnId, {
|
|
1533
|
+
kind: "queue_update",
|
|
1534
|
+
data: {
|
|
1535
|
+
deferredTurn: projectDeferredTurnSummary(deferredTurn),
|
|
1536
|
+
revision,
|
|
1537
|
+
},
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
async function startTrustedChatTurn(deps, sessionId, payload) {
|
|
1541
|
+
let status = 500;
|
|
1542
|
+
let raw = "";
|
|
1543
|
+
const response = {
|
|
1544
|
+
writeHead(code) {
|
|
1545
|
+
status = code;
|
|
1546
|
+
return this;
|
|
1547
|
+
},
|
|
1548
|
+
end(chunk) {
|
|
1549
|
+
if (chunk !== undefined)
|
|
1550
|
+
raw += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
1551
|
+
return this;
|
|
1552
|
+
},
|
|
1553
|
+
};
|
|
1554
|
+
await handleCreateChatTurn({}, response, deps, sessionId, payload);
|
|
1555
|
+
let body = {};
|
|
1556
|
+
try {
|
|
1557
|
+
body = raw ? JSON.parse(raw) : {};
|
|
1558
|
+
}
|
|
1559
|
+
catch {
|
|
1560
|
+
body = {};
|
|
1561
|
+
}
|
|
1562
|
+
return { status, body };
|
|
1563
|
+
}
|
|
1564
|
+
const deferredDispatchingSessions = new Set();
|
|
1565
|
+
/** Dispatch one durable follow-up through the exact normal Turn creation path. */
|
|
1566
|
+
export async function dispatchDeferredTurn(deps, sessionId) {
|
|
1567
|
+
if (deferredDispatchingSessions.has(sessionId))
|
|
1568
|
+
return;
|
|
1569
|
+
deferredDispatchingSessions.add(sessionId);
|
|
1570
|
+
try {
|
|
1571
|
+
let record = await deps.store.get(sessionId);
|
|
1572
|
+
let deferred = record?.deferredTurn;
|
|
1573
|
+
if (!deferred)
|
|
1574
|
+
return;
|
|
1575
|
+
if (deferred.state === "accepted")
|
|
1576
|
+
return;
|
|
1577
|
+
if (deferred.state === "failed")
|
|
1578
|
+
return;
|
|
1579
|
+
if (deferred.state === "dispatching") {
|
|
1580
|
+
const existing = deps.events.findTurnByClientRequestId(sessionId, deferred.clientRequestId);
|
|
1581
|
+
if (existing) {
|
|
1582
|
+
const accepted = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1583
|
+
expectedRevision: deferred.revision,
|
|
1584
|
+
state: "accepted",
|
|
1585
|
+
acceptedTurnId: existing.turnId,
|
|
1586
|
+
});
|
|
1587
|
+
if (accepted)
|
|
1588
|
+
emitDeferredTurnUpdate(deps, sessionId, existing.turnId, accepted, accepted.revision);
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
if (deps.events.getActiveTurn(sessionId))
|
|
1592
|
+
return;
|
|
1593
|
+
const requeued = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1594
|
+
expectedRevision: deferred.revision,
|
|
1595
|
+
state: "queued",
|
|
1596
|
+
});
|
|
1597
|
+
if (!requeued)
|
|
1598
|
+
return;
|
|
1599
|
+
deferred = requeued;
|
|
1600
|
+
}
|
|
1601
|
+
const runtimeFacts = deps.runtime.getSessionBusyFacts?.(sessionId);
|
|
1602
|
+
const runtimeBusy = Boolean(runtimeFacts && Object.values(runtimeFacts).some((value) => value === true));
|
|
1603
|
+
if (deps.events.getActiveTurn(sessionId) || runtimeBusy)
|
|
1604
|
+
return;
|
|
1605
|
+
const claim = await deps.store.claimDeferredTurn(sessionId, deferred.revision);
|
|
1606
|
+
if (!claim.ok)
|
|
1607
|
+
return;
|
|
1608
|
+
deferred = claim.deferredTurn;
|
|
1609
|
+
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, deferred, deferred.revision);
|
|
1610
|
+
const started = await startTrustedChatTurn(deps, sessionId, {
|
|
1611
|
+
clientRequestId: deferred.clientRequestId,
|
|
1612
|
+
text: deferred.text,
|
|
1613
|
+
images: deferred.images,
|
|
1614
|
+
onUserMessagePersisted: async (turnId, userMessage) => {
|
|
1615
|
+
deps.events.append(sessionId, turnId, {
|
|
1616
|
+
kind: "user_message",
|
|
1617
|
+
data: {
|
|
1618
|
+
clientRequestId: deferred.clientRequestId,
|
|
1619
|
+
message: {
|
|
1620
|
+
id: userMessage.id,
|
|
1621
|
+
text: userMessage.text,
|
|
1622
|
+
at: userMessage.at,
|
|
1623
|
+
},
|
|
1624
|
+
},
|
|
1625
|
+
});
|
|
1626
|
+
const cleared = await deps.store.clearDeferredTurnForRequest(sessionId, deferred.clientRequestId);
|
|
1627
|
+
if (cleared)
|
|
1628
|
+
emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, cleared.clearedRevision);
|
|
1629
|
+
},
|
|
1630
|
+
});
|
|
1631
|
+
const accepted = started.body.accepted === true;
|
|
1632
|
+
const turn = started.body.turn && typeof started.body.turn === "object"
|
|
1633
|
+
? started.body.turn
|
|
1634
|
+
: undefined;
|
|
1635
|
+
if (accepted && turn?.turnId) {
|
|
1636
|
+
const next = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1637
|
+
expectedRevision: deferred.revision,
|
|
1638
|
+
state: "accepted",
|
|
1639
|
+
acceptedTurnId: turn.turnId,
|
|
1640
|
+
});
|
|
1641
|
+
if (next)
|
|
1642
|
+
emitDeferredTurnUpdate(deps, sessionId, turn.turnId, next, next.revision);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const error = started.body.error && typeof started.body.error === "object"
|
|
1646
|
+
? started.body.error
|
|
1647
|
+
: {};
|
|
1648
|
+
if (error.code === "TURN_ACTIVE") {
|
|
1649
|
+
const next = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1650
|
+
expectedRevision: deferred.revision,
|
|
1651
|
+
state: "queued",
|
|
1652
|
+
});
|
|
1653
|
+
if (next)
|
|
1654
|
+
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, next, next.revision);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
const failed = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1658
|
+
expectedRevision: deferred.revision,
|
|
1659
|
+
state: "failed",
|
|
1660
|
+
lastError: {
|
|
1661
|
+
code: error.code ?? `HTTP_${started.status}`,
|
|
1662
|
+
message: error.message ?? "停止后的消息未能自动发送",
|
|
1663
|
+
},
|
|
1664
|
+
});
|
|
1665
|
+
if (failed)
|
|
1666
|
+
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, failed, failed.revision);
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
// Keep fire-and-forget callers safe and make a dispatch failure visible and
|
|
1670
|
+
// recoverable from the composer instead of leaving the slot indefinitely
|
|
1671
|
+
// in an ambiguous queued/dispatching state.
|
|
1672
|
+
try {
|
|
1673
|
+
const current = (await deps.store.get(sessionId))?.deferredTurn;
|
|
1674
|
+
if (current && (current.state === "queued" || current.state === "dispatching")) {
|
|
1675
|
+
const failed = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1676
|
+
expectedRevision: current.revision,
|
|
1677
|
+
state: "failed",
|
|
1678
|
+
lastError: {
|
|
1679
|
+
code: "DEFERRED_TURN_DISPATCH_FAILED",
|
|
1680
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1681
|
+
},
|
|
1682
|
+
});
|
|
1683
|
+
if (failed)
|
|
1684
|
+
emitDeferredTurnUpdate(deps, sessionId, failed.blockedByTurnId, failed, failed.revision);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
catch {
|
|
1688
|
+
// The durable store itself is unavailable. Leave the original record in
|
|
1689
|
+
// place so a later Console boot can recover it.
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
finally {
|
|
1693
|
+
deferredDispatchingSessions.delete(sessionId);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
export async function recoverDeferredTurns(deps) {
|
|
1697
|
+
const sessions = await deps.store.list({ includeArchived: false });
|
|
1698
|
+
await Promise.all(sessions
|
|
1699
|
+
.filter((session) => session.deferredTurn
|
|
1700
|
+
? session.deferredTurn.state === "queued" ||
|
|
1701
|
+
session.deferredTurn.state === "dispatching"
|
|
1702
|
+
: false)
|
|
1703
|
+
.map(async (session) => {
|
|
1704
|
+
const deferred = session.deferredTurn;
|
|
1705
|
+
const active = deps.events.getActiveTurn(session.sessionId);
|
|
1706
|
+
// A process restart cannot retain AbortController ownership. When the
|
|
1707
|
+
// durable blocker was already stopping and no execution owns it now,
|
|
1708
|
+
// close that orphan before dispatching the server-owned follow-up.
|
|
1709
|
+
if (active?.turnId === deferred.blockedByTurnId &&
|
|
1710
|
+
active.abortRequestedAt &&
|
|
1711
|
+
deps.turnExecutions &&
|
|
1712
|
+
!deps.turnExecutions.get(session.sessionId, active.turnId)) {
|
|
1713
|
+
deps.events.setTurnState(session.sessionId, active.turnId, "failed", {
|
|
1714
|
+
code: "CHAT_TURN_OWNER_LOST",
|
|
1715
|
+
message: "turn ownership was lost during Console restart",
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
await dispatchDeferredTurn(deps, session.sessionId);
|
|
1719
|
+
}));
|
|
1720
|
+
}
|
|
1318
1721
|
/** Real server-side abort: invokes Pi session.abort via the execution registry. */
|
|
1319
1722
|
export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
|
|
1320
1723
|
const gate = gateMutation(req, deps);
|
|
@@ -1642,16 +2045,27 @@ async function handleChatFiles(res, deps, sessionId, query) {
|
|
|
1642
2045
|
if (!(await requireChatSession(res, deps, sessionId)))
|
|
1643
2046
|
return;
|
|
1644
2047
|
const q = query.trim().toLowerCase();
|
|
1645
|
-
const
|
|
2048
|
+
const walked = await walkRepoFiles(deps.appData.repoRoot, {
|
|
1646
2049
|
skipSensitive: true,
|
|
1647
2050
|
maxFiles: 5000,
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
.
|
|
2051
|
+
includeDirs: true,
|
|
2052
|
+
});
|
|
2053
|
+
const entries = walked
|
|
2054
|
+
.filter((entry) => !isSensitivePath(entry.repoRelative) &&
|
|
2055
|
+
(!q || entry.repoRelative.toLowerCase().includes(q)))
|
|
2056
|
+
.sort((a, b) => {
|
|
2057
|
+
const aDir = a.repoRelative.endsWith("/");
|
|
2058
|
+
const bDir = b.repoRelative.endsWith("/");
|
|
2059
|
+
if (aDir !== bDir)
|
|
2060
|
+
return aDir ? -1 : 1;
|
|
2061
|
+
return a.repoRelative.localeCompare(b.repoRelative);
|
|
2062
|
+
})
|
|
1652
2063
|
.slice(0, 50)
|
|
1653
|
-
.map((
|
|
1654
|
-
|
|
2064
|
+
.map((entry) => ({
|
|
2065
|
+
path: entry.repoRelative,
|
|
2066
|
+
isDir: entry.repoRelative.endsWith("/"),
|
|
2067
|
+
}));
|
|
2068
|
+
sendJson(res, 200, { ok: true, files: entries });
|
|
1655
2069
|
}
|
|
1656
2070
|
export async function handleChatCommands(res, deps, sessionId) {
|
|
1657
2071
|
try {
|
|
@@ -3043,6 +3457,15 @@ export async function handleChatRequest(req, res, deps, pathname) {
|
|
|
3043
3457
|
await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
|
|
3044
3458
|
return true;
|
|
3045
3459
|
}
|
|
3460
|
+
const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
|
|
3461
|
+
if (method === "PUT" && deferredTurn) {
|
|
3462
|
+
await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|
|
3463
|
+
return true;
|
|
3464
|
+
}
|
|
3465
|
+
if (method === "DELETE" && deferredTurn) {
|
|
3466
|
+
await handleDeleteDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|
|
3467
|
+
return true;
|
|
3468
|
+
}
|
|
3046
3469
|
const sessionTurnAbort = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/turns\/([^/]+)\/abort$/);
|
|
3047
3470
|
if (method === "POST" && sessionTurnAbort) {
|
|
3048
3471
|
await handleAbortChatTurn(req, res, deps, decodeURIComponent(sessionTurnAbort[1]), decodeURIComponent(sessionTurnAbort[2]));
|
|
@@ -38,10 +38,12 @@ const OPERATION_TITLES = {
|
|
|
38
38
|
dagStatus: "执行状态",
|
|
39
39
|
dagDoctor: "执行诊断",
|
|
40
40
|
dagReport: "执行结果",
|
|
41
|
-
dagRerunPlan: "
|
|
41
|
+
dagRerunPlan: "节点恢复评估",
|
|
42
|
+
dagRerun: "从失败节点恢复",
|
|
42
43
|
dagInterruptEligibility: "中止资格",
|
|
43
|
-
standaloneTaskRerun: "
|
|
44
|
-
workerTaskRetry: "
|
|
44
|
+
standaloneTaskRerun: "带反馈重新执行",
|
|
45
|
+
workerTaskRetry: "重试 Worker 任务",
|
|
46
|
+
workerPoolDoctor: "任务池诊断",
|
|
45
47
|
promoteRun: "任务推进",
|
|
46
48
|
status: "任务状态",
|
|
47
49
|
};
|
|
@@ -87,19 +89,29 @@ const TOOL_SEMANTICS = {
|
|
|
87
89
|
error: "读取执行结果时遇到问题",
|
|
88
90
|
},
|
|
89
91
|
dagRerunPlan: {
|
|
90
|
-
running: "
|
|
91
|
-
success: "
|
|
92
|
+
running: "正在评估能否从失败节点安全恢复",
|
|
93
|
+
success: "节点恢复条件评估已经完成",
|
|
92
94
|
error: "暂时无法确定安全的恢复方式",
|
|
93
95
|
},
|
|
96
|
+
dagRerun: {
|
|
97
|
+
running: "正在创建节点恢复 run",
|
|
98
|
+
success: "节点恢复 run 已经启动",
|
|
99
|
+
error: "从失败节点恢复时遇到问题",
|
|
100
|
+
},
|
|
94
101
|
dagInterruptEligibility: {
|
|
95
102
|
running: "正在检查能否协作中止此运行",
|
|
96
103
|
success: "中止资格已经确认",
|
|
97
104
|
error: "当前运行不能协作中止",
|
|
98
105
|
},
|
|
99
106
|
standaloneTaskRerun: {
|
|
100
|
-
running: "
|
|
101
|
-
success: "
|
|
102
|
-
error: "
|
|
107
|
+
running: "正在创建新的完整 DAG,并带入上一轮未解决反馈",
|
|
108
|
+
success: "新的完整 DAG 已经按上一轮反馈重新执行",
|
|
109
|
+
error: "带反馈重新执行任务时遇到问题",
|
|
110
|
+
},
|
|
111
|
+
workerPoolDoctor: {
|
|
112
|
+
running: "正在读取任务池状态",
|
|
113
|
+
success: "任务池状态已经读取",
|
|
114
|
+
error: "读取任务池状态时遇到问题",
|
|
103
115
|
},
|
|
104
116
|
promoteRun: {
|
|
105
117
|
running: "正在推进任务状态",
|