@tea-agent/loop-agent 0.38.0 → 0.38.2
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 +191 -0
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init-upgrade.js +97 -19
- package/dist/executors/dag-pi-executor.js +42 -4
- package/dist/executors/shell-executor.js +269 -15
- package/dist/infrastructure/harness/atomic-write.js +2 -1
- package/dist/shared/openspec-spec.js +76 -0
- package/dist/shared/operator/capabilities.js +149 -27
- package/dist/task/config-types.js +43 -0
- package/dist/task/frontend-project-capability.js +292 -38
- 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 +477 -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 +128 -4
- package/dist/worker/console/operator-user-error.js +19 -0
- package/dist/worker/console/pi-readiness.js +8 -1
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/security.js +20 -1
- package/dist/worker/console/server.js +6 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-C4bmo3iY.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-SAD7McOS.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-DAVLUdK1.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-CPPdv4vj.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-D1GRGZax.js} +1 -1
- package/dist/worker/console/static/assets/channel-R6oQIDkw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-D_Bq2ZqK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-CPLVtamK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-DnaZWcL3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BRTmFgnq.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-BRlfYZoH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-CjNybD4u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-D3e-74fG.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-Br3ir6f2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-1NgVuRbh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-BTYuvuWN.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-BC6Bgosf.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-DV-HypfL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-DhTaddO-.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-BnEcOmiI.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-xphGRVKp.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-CrdD1mRG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-D4YdLsaF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-LCKyGZ5U.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-BeiTe7t0.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-BO2cq8GS.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-CmWww3Nt.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-C2kkUYg5.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-DY_JZ2iQ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-BizhCSw9.js} +1 -1
- package/dist/worker/console/static/assets/index-BXN89VPX.js +325 -0
- package/dist/worker/console/static/assets/index-CuH5CNFu.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-Dlo2eK_d.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-DiMDe5Yh.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-sN553oIN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-D9Py31Ld.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-D7Occzk_.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-DjDRboiR.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-Cgc4qlzp.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-DxZj8pKd.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-CJ50MngR.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-BY-OD8Tr.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-CQFButfK.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-BB_QUK_b.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-CqhXZjyx.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-2l1U4rgJ.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-BX1FbrhJ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DOSnT7hb.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-DVJQA7AN.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-74uxl9gx.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-DsBxlcg_.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-snuW9Mjw.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/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/workflows/dag/backend-test-case-coverage-analysis.js +755 -51
- 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 +249 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
- 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-routing.js +9 -4
- package/dist/workflows/dag/frontend-implementation-contract.js +386 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +479 -28
- package/dist/workflows/dag/init-hybrid.js +920 -110
- package/dist/workflows/dag/lifecycle.js +4 -0
- package/dist/workflows/dag/node-execution.js +77 -8
- package/dist/workflows/dag/prompt.js +45 -0
- package/dist/workflows/dag/report.js +6 -0
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +93 -27
- package/dist/workflows/dag/rerun-run.js +3 -0
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +33 -0
- package/dist/workflows/dag/types.js +148 -4
- package/dist/workflows/dag/validate.js +7 -2
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-design-contract.md +40 -14
- package/docs/templates/frontend-implementation-contract.schema.json +9 -2
- package/docs/templates/frontend-task-constraints.md +40 -13
- package/docs/templates/frontend-task-requirement.md +54 -20
- package/harness.json +3 -1
- package/package.json +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 +15 -11
- 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). */
|
|
@@ -645,19 +646,195 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
645
646
|
const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
|
|
646
647
|
const snapshot = deps.events.snapshot(sessionId);
|
|
647
648
|
const lastEvent = snapshot.at(-1);
|
|
649
|
+
const activeTurn = deps.events.getActiveTurn(sessionId);
|
|
648
650
|
sendJson(res, 200, {
|
|
649
651
|
ok: true,
|
|
650
652
|
eventEpoch: deps.events.epoch(sessionId),
|
|
651
653
|
session: {
|
|
652
654
|
...record,
|
|
653
655
|
composerDraft,
|
|
654
|
-
activeTurnId:
|
|
656
|
+
activeTurnId: activeTurn?.turnId,
|
|
657
|
+
turnPhase: activeTurn
|
|
658
|
+
? activeTurn.abortRequestedAt
|
|
659
|
+
? "stopping"
|
|
660
|
+
: "running"
|
|
661
|
+
: "idle",
|
|
655
662
|
},
|
|
656
663
|
hasBash: true,
|
|
657
664
|
lastEventId: lastEvent?.eventId ?? null,
|
|
658
665
|
lastEventSeq: lastEvent?.seq ?? 0,
|
|
659
666
|
});
|
|
660
667
|
}
|
|
668
|
+
function parseDeferredImages(value) {
|
|
669
|
+
const parsed = parseChatImages(value);
|
|
670
|
+
const raw = Array.isArray(value) ? value : [];
|
|
671
|
+
return parsed.map((image, index) => {
|
|
672
|
+
const candidate = raw[index];
|
|
673
|
+
const name = candidate &&
|
|
674
|
+
typeof candidate === "object" &&
|
|
675
|
+
typeof candidate.name === "string"
|
|
676
|
+
? candidate.name.trim().slice(0, 180)
|
|
677
|
+
: `image-${index + 1}`;
|
|
678
|
+
return { ...image, name: name || `image-${index + 1}` };
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
export async function handlePutDeferredTurn(req, res, deps, sessionId) {
|
|
682
|
+
const gate = gateMutation(req, deps);
|
|
683
|
+
if (!gate.ok) {
|
|
684
|
+
sendJson(res, gate.status, {
|
|
685
|
+
ok: false,
|
|
686
|
+
accepted: false,
|
|
687
|
+
error: { code: gate.code, message: gate.message },
|
|
688
|
+
});
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
let body;
|
|
692
|
+
try {
|
|
693
|
+
body = await readJsonBody(req);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
sendJson(res, 400, {
|
|
697
|
+
ok: false,
|
|
698
|
+
accepted: false,
|
|
699
|
+
error: {
|
|
700
|
+
code: "INVALID_INPUT",
|
|
701
|
+
message: error instanceof Error ? error.message : String(error),
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const clientRequestId = typeof body.clientRequestId === "string" ? body.clientRequestId.trim() : "";
|
|
707
|
+
const blockedByTurnId = typeof body.blockedByTurnId === "string" ? body.blockedByTurnId.trim() : "";
|
|
708
|
+
const text = typeof body.text === "string" ? body.text : "";
|
|
709
|
+
const expectedRevision = typeof body.expectedRevision === "number" &&
|
|
710
|
+
Number.isSafeInteger(body.expectedRevision) &&
|
|
711
|
+
body.expectedRevision >= 0
|
|
712
|
+
? body.expectedRevision
|
|
713
|
+
: undefined;
|
|
714
|
+
let images;
|
|
715
|
+
try {
|
|
716
|
+
images = parseDeferredImages(body.images);
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
sendJson(res, 400, {
|
|
720
|
+
ok: false,
|
|
721
|
+
accepted: false,
|
|
722
|
+
error: {
|
|
723
|
+
code: "INVALID_ATTACHMENT",
|
|
724
|
+
message: error instanceof Error ? error.message : String(error),
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
const active = deps.events.getActiveTurn(sessionId);
|
|
730
|
+
if (!active || !active.abortRequestedAt || active.turnId !== blockedByTurnId) {
|
|
731
|
+
sendJson(res, 409, {
|
|
732
|
+
ok: false,
|
|
733
|
+
accepted: false,
|
|
734
|
+
error: {
|
|
735
|
+
code: "TURN_NOT_STOPPING",
|
|
736
|
+
message: "只有仍在停止中的当前 Turn 可以保存停止后发送消息",
|
|
737
|
+
},
|
|
738
|
+
activeTurn: active ? turnSummary(active) : null,
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const result = await deps.store.putDeferredTurn(sessionId, {
|
|
743
|
+
clientRequestId,
|
|
744
|
+
blockedByTurnId,
|
|
745
|
+
text,
|
|
746
|
+
images,
|
|
747
|
+
...(expectedRevision !== undefined ? { expectedRevision } : {}),
|
|
748
|
+
});
|
|
749
|
+
if (!result.ok) {
|
|
750
|
+
const status = result.code === "NOT_FOUND"
|
|
751
|
+
? 404
|
|
752
|
+
: result.code === "INVALID_INPUT"
|
|
753
|
+
? 400
|
|
754
|
+
: 409;
|
|
755
|
+
sendJson(res, status, {
|
|
756
|
+
ok: false,
|
|
757
|
+
accepted: false,
|
|
758
|
+
error: {
|
|
759
|
+
code: result.code,
|
|
760
|
+
message: result.code === "REVISION_CONFLICT"
|
|
761
|
+
? "待发送消息已在其他页面更新,请使用服务端最新状态"
|
|
762
|
+
: result.code === "NOT_REPLACEABLE"
|
|
763
|
+
? "待发送消息已经开始发送,不能再覆盖"
|
|
764
|
+
: result.code === "REQUEST_ID_REUSE_CONFLICT"
|
|
765
|
+
? "clientRequestId 已绑定到不同消息"
|
|
766
|
+
: "待发送消息无效或会话不存在",
|
|
767
|
+
},
|
|
768
|
+
deferredTurn: result.deferredTurn ?? null,
|
|
769
|
+
summary: projectDeferredTurnSummary(result.deferredTurn),
|
|
770
|
+
});
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
emitDeferredTurnUpdate(deps, sessionId, blockedByTurnId, result.deferredTurn, result.deferredTurn.revision);
|
|
774
|
+
sendJson(res, 200, {
|
|
775
|
+
ok: true,
|
|
776
|
+
accepted: true,
|
|
777
|
+
idempotent: result.idempotent,
|
|
778
|
+
state: result.deferredTurn.state,
|
|
779
|
+
revision: result.deferredTurn.revision,
|
|
780
|
+
deferredTurn: result.deferredTurn,
|
|
781
|
+
summary: projectDeferredTurnSummary(result.deferredTurn),
|
|
782
|
+
});
|
|
783
|
+
void dispatchDeferredTurn(deps, sessionId);
|
|
784
|
+
}
|
|
785
|
+
export async function handleDeleteDeferredTurn(req, res, deps, sessionId) {
|
|
786
|
+
const gate = gateMutation(req, deps);
|
|
787
|
+
if (!gate.ok) {
|
|
788
|
+
sendJson(res, gate.status, {
|
|
789
|
+
ok: false,
|
|
790
|
+
error: { code: gate.code, message: gate.message },
|
|
791
|
+
});
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
let body;
|
|
795
|
+
try {
|
|
796
|
+
body = await readJsonBody(req);
|
|
797
|
+
}
|
|
798
|
+
catch (error) {
|
|
799
|
+
sendJson(res, 400, {
|
|
800
|
+
ok: false,
|
|
801
|
+
error: {
|
|
802
|
+
code: "INVALID_INPUT",
|
|
803
|
+
message: error instanceof Error ? error.message : String(error),
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const clientRequestId = typeof body.clientRequestId === "string" ? body.clientRequestId.trim() : "";
|
|
809
|
+
const expectedRevision = typeof body.expectedRevision === "number" && Number.isSafeInteger(body.expectedRevision)
|
|
810
|
+
? body.expectedRevision
|
|
811
|
+
: -1;
|
|
812
|
+
const result = await deps.store.deleteDeferredTurn(sessionId, {
|
|
813
|
+
clientRequestId,
|
|
814
|
+
expectedRevision,
|
|
815
|
+
});
|
|
816
|
+
if (!result.ok) {
|
|
817
|
+
sendJson(res, result.code === "NOT_FOUND" ? 404 : 409, {
|
|
818
|
+
ok: false,
|
|
819
|
+
error: {
|
|
820
|
+
code: result.code,
|
|
821
|
+
message: result.code === "NOT_CANCELLABLE"
|
|
822
|
+
? "消息已经开始发送,不能再撤回"
|
|
823
|
+
: "待发送消息已变化,请刷新后重试",
|
|
824
|
+
},
|
|
825
|
+
deferredTurn: result.deferredTurn ?? null,
|
|
826
|
+
});
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const clearedRevision = result.deferredTurn.revision + 1;
|
|
830
|
+
emitDeferredTurnUpdate(deps, sessionId, result.deferredTurn.blockedByTurnId, undefined, clearedRevision);
|
|
831
|
+
sendJson(res, 200, {
|
|
832
|
+
ok: true,
|
|
833
|
+
cancelled: true,
|
|
834
|
+
deferredTurn: result.deferredTurn,
|
|
835
|
+
revision: clearedRevision,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
661
838
|
/** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
|
|
662
839
|
* get_state equivalent): exposes the active turn + the latest turn's durable
|
|
663
840
|
* outcome WITHOUT the full message payload, so the UI can poll it on an
|
|
@@ -684,6 +861,14 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
684
861
|
isCompacting: false,
|
|
685
862
|
isBashRunning: false,
|
|
686
863
|
};
|
|
864
|
+
const runtimeBusy = Object.values(runtimeFacts).some((value) => value === true);
|
|
865
|
+
const turnPhase = active
|
|
866
|
+
? active.abortRequestedAt
|
|
867
|
+
? "stopping"
|
|
868
|
+
: "running"
|
|
869
|
+
: runtimeBusy
|
|
870
|
+
? "running"
|
|
871
|
+
: "idle";
|
|
687
872
|
sendJson(res, 200, {
|
|
688
873
|
ok: true,
|
|
689
874
|
state: {
|
|
@@ -695,6 +880,8 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
695
880
|
activeTurn: active ? turnSummary(active) : null,
|
|
696
881
|
latestTurn: latest ? turnSummary(latest) : null,
|
|
697
882
|
runtime: runtimeFacts,
|
|
883
|
+
turnPhase,
|
|
884
|
+
deferredTurn: projectDeferredTurnSummary(record.deferredTurn),
|
|
698
885
|
},
|
|
699
886
|
});
|
|
700
887
|
}
|
|
@@ -703,10 +890,19 @@ export async function handleListChatSessions(_req, res, deps) {
|
|
|
703
890
|
sendJson(res, 200, {
|
|
704
891
|
ok: true,
|
|
705
892
|
schemaVersion: 2,
|
|
706
|
-
sessions: sessions.map((session) =>
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
893
|
+
sessions: sessions.map((session) => {
|
|
894
|
+
const active = deps.events.getActiveTurn(session.sessionId);
|
|
895
|
+
return {
|
|
896
|
+
...session,
|
|
897
|
+
activeTurnId: active?.turnId,
|
|
898
|
+
turnPhase: active?.abortRequestedAt
|
|
899
|
+
? "stopping"
|
|
900
|
+
: active
|
|
901
|
+
? "running"
|
|
902
|
+
: "idle",
|
|
903
|
+
deferredTurn: projectDeferredTurnSummary(session.deferredTurn),
|
|
904
|
+
};
|
|
905
|
+
}),
|
|
710
906
|
hasBash: true,
|
|
711
907
|
});
|
|
712
908
|
}
|
|
@@ -1057,49 +1253,63 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1057
1253
|
* Pi prompt continues independently of this HTTP response and publishes to the
|
|
1058
1254
|
* persisted event ring consumed by GET /events.
|
|
1059
1255
|
*/
|
|
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;
|
|
1256
|
+
export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPayload) {
|
|
1257
|
+
let text;
|
|
1258
|
+
let clientRequestId;
|
|
1089
1259
|
let images;
|
|
1090
|
-
|
|
1091
|
-
|
|
1260
|
+
if (trustedPayload) {
|
|
1261
|
+
text = trustedPayload.text.trim();
|
|
1262
|
+
clientRequestId = trustedPayload.clientRequestId.trim();
|
|
1263
|
+
images = trustedPayload.images.map(({ type, mimeType, data }) => ({
|
|
1264
|
+
type,
|
|
1265
|
+
mimeType,
|
|
1266
|
+
data,
|
|
1267
|
+
}));
|
|
1092
1268
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1269
|
+
else {
|
|
1270
|
+
const gate = gateMutation(req, deps);
|
|
1271
|
+
if (!gate.ok) {
|
|
1272
|
+
sendJson(res, gate.status, {
|
|
1273
|
+
ok: false,
|
|
1274
|
+
accepted: false,
|
|
1275
|
+
error: { code: gate.code, message: gate.message },
|
|
1276
|
+
});
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
let body;
|
|
1280
|
+
try {
|
|
1281
|
+
body = await readJsonBody(req);
|
|
1282
|
+
}
|
|
1283
|
+
catch (error) {
|
|
1284
|
+
sendJson(res, 400, {
|
|
1285
|
+
ok: false,
|
|
1286
|
+
accepted: false,
|
|
1287
|
+
error: {
|
|
1288
|
+
code: "INVALID_INPUT",
|
|
1289
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1290
|
+
},
|
|
1291
|
+
});
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
text = typeof body.text === "string" ? body.text.trim() : "";
|
|
1295
|
+
clientRequestId =
|
|
1296
|
+
typeof body.clientRequestId === "string" && body.clientRequestId.trim()
|
|
1297
|
+
? body.clientRequestId.trim()
|
|
1298
|
+
: undefined;
|
|
1299
|
+
try {
|
|
1300
|
+
images = parseChatImages(body.images);
|
|
1301
|
+
}
|
|
1302
|
+
catch (error) {
|
|
1303
|
+
sendJson(res, 400, {
|
|
1304
|
+
ok: false,
|
|
1305
|
+
accepted: false,
|
|
1306
|
+
error: {
|
|
1307
|
+
code: "INVALID_ATTACHMENT",
|
|
1308
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1309
|
+
},
|
|
1310
|
+
});
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1103
1313
|
}
|
|
1104
1314
|
if (!text) {
|
|
1105
1315
|
sendJson(res, 400, {
|
|
@@ -1235,7 +1445,8 @@ export async function handleCreateChatTurn(req, res, deps, sessionId) {
|
|
|
1235
1445
|
text,
|
|
1236
1446
|
images,
|
|
1237
1447
|
signal: controller.signal,
|
|
1238
|
-
onUserMessagePersisted: (record) => {
|
|
1448
|
+
onUserMessagePersisted: async (record, userMessage) => {
|
|
1449
|
+
await trustedPayload?.onUserMessagePersisted?.(turn.turnId, userMessage);
|
|
1239
1450
|
const users = record.messages.filter((message) => message.role === "user");
|
|
1240
1451
|
if (users.length !== 1)
|
|
1241
1452
|
return;
|
|
@@ -1312,9 +1523,200 @@ export async function handleCreateChatTurn(req, res, deps, sessionId) {
|
|
|
1312
1523
|
error: terminalError,
|
|
1313
1524
|
});
|
|
1314
1525
|
deps.turnExecutions?.clear(sessionId, turn.turnId);
|
|
1526
|
+
void dispatchDeferredTurn(deps, sessionId);
|
|
1315
1527
|
}
|
|
1316
1528
|
})();
|
|
1317
1529
|
}
|
|
1530
|
+
function emitDeferredTurnUpdate(deps, sessionId, turnId, deferredTurn, revision) {
|
|
1531
|
+
deps.events.append(sessionId, turnId, {
|
|
1532
|
+
kind: "queue_update",
|
|
1533
|
+
data: {
|
|
1534
|
+
deferredTurn: projectDeferredTurnSummary(deferredTurn),
|
|
1535
|
+
revision,
|
|
1536
|
+
},
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
async function startTrustedChatTurn(deps, sessionId, payload) {
|
|
1540
|
+
let status = 500;
|
|
1541
|
+
let raw = "";
|
|
1542
|
+
const response = {
|
|
1543
|
+
writeHead(code) {
|
|
1544
|
+
status = code;
|
|
1545
|
+
return this;
|
|
1546
|
+
},
|
|
1547
|
+
end(chunk) {
|
|
1548
|
+
if (chunk !== undefined)
|
|
1549
|
+
raw += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : chunk;
|
|
1550
|
+
return this;
|
|
1551
|
+
},
|
|
1552
|
+
};
|
|
1553
|
+
await handleCreateChatTurn({}, response, deps, sessionId, payload);
|
|
1554
|
+
let body = {};
|
|
1555
|
+
try {
|
|
1556
|
+
body = raw ? JSON.parse(raw) : {};
|
|
1557
|
+
}
|
|
1558
|
+
catch {
|
|
1559
|
+
body = {};
|
|
1560
|
+
}
|
|
1561
|
+
return { status, body };
|
|
1562
|
+
}
|
|
1563
|
+
const deferredDispatchingSessions = new Set();
|
|
1564
|
+
/** Dispatch one durable follow-up through the exact normal Turn creation path. */
|
|
1565
|
+
export async function dispatchDeferredTurn(deps, sessionId) {
|
|
1566
|
+
if (deferredDispatchingSessions.has(sessionId))
|
|
1567
|
+
return;
|
|
1568
|
+
deferredDispatchingSessions.add(sessionId);
|
|
1569
|
+
try {
|
|
1570
|
+
let record = await deps.store.get(sessionId);
|
|
1571
|
+
let deferred = record?.deferredTurn;
|
|
1572
|
+
if (!deferred)
|
|
1573
|
+
return;
|
|
1574
|
+
if (deferred.state === "accepted")
|
|
1575
|
+
return;
|
|
1576
|
+
if (deferred.state === "failed")
|
|
1577
|
+
return;
|
|
1578
|
+
if (deferred.state === "dispatching") {
|
|
1579
|
+
const existing = deps.events.findTurnByClientRequestId(sessionId, deferred.clientRequestId);
|
|
1580
|
+
if (existing) {
|
|
1581
|
+
const accepted = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1582
|
+
expectedRevision: deferred.revision,
|
|
1583
|
+
state: "accepted",
|
|
1584
|
+
acceptedTurnId: existing.turnId,
|
|
1585
|
+
});
|
|
1586
|
+
if (accepted)
|
|
1587
|
+
emitDeferredTurnUpdate(deps, sessionId, existing.turnId, accepted, accepted.revision);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
if (deps.events.getActiveTurn(sessionId))
|
|
1591
|
+
return;
|
|
1592
|
+
const requeued = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1593
|
+
expectedRevision: deferred.revision,
|
|
1594
|
+
state: "queued",
|
|
1595
|
+
});
|
|
1596
|
+
if (!requeued)
|
|
1597
|
+
return;
|
|
1598
|
+
deferred = requeued;
|
|
1599
|
+
}
|
|
1600
|
+
const runtimeFacts = deps.runtime.getSessionBusyFacts?.(sessionId);
|
|
1601
|
+
const runtimeBusy = Boolean(runtimeFacts && Object.values(runtimeFacts).some((value) => value === true));
|
|
1602
|
+
if (deps.events.getActiveTurn(sessionId) || runtimeBusy)
|
|
1603
|
+
return;
|
|
1604
|
+
const claim = await deps.store.claimDeferredTurn(sessionId, deferred.revision);
|
|
1605
|
+
if (!claim.ok)
|
|
1606
|
+
return;
|
|
1607
|
+
deferred = claim.deferredTurn;
|
|
1608
|
+
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, deferred, deferred.revision);
|
|
1609
|
+
const started = await startTrustedChatTurn(deps, sessionId, {
|
|
1610
|
+
clientRequestId: deferred.clientRequestId,
|
|
1611
|
+
text: deferred.text,
|
|
1612
|
+
images: deferred.images,
|
|
1613
|
+
onUserMessagePersisted: async (turnId, userMessage) => {
|
|
1614
|
+
deps.events.append(sessionId, turnId, {
|
|
1615
|
+
kind: "user_message",
|
|
1616
|
+
data: {
|
|
1617
|
+
clientRequestId: deferred.clientRequestId,
|
|
1618
|
+
message: {
|
|
1619
|
+
id: userMessage.id,
|
|
1620
|
+
text: userMessage.text,
|
|
1621
|
+
at: userMessage.at,
|
|
1622
|
+
},
|
|
1623
|
+
},
|
|
1624
|
+
});
|
|
1625
|
+
const cleared = await deps.store.clearDeferredTurnForRequest(sessionId, deferred.clientRequestId);
|
|
1626
|
+
if (cleared)
|
|
1627
|
+
emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, cleared.clearedRevision);
|
|
1628
|
+
},
|
|
1629
|
+
});
|
|
1630
|
+
const accepted = started.body.accepted === true;
|
|
1631
|
+
const turn = started.body.turn && typeof started.body.turn === "object"
|
|
1632
|
+
? started.body.turn
|
|
1633
|
+
: undefined;
|
|
1634
|
+
if (accepted && turn?.turnId) {
|
|
1635
|
+
const next = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1636
|
+
expectedRevision: deferred.revision,
|
|
1637
|
+
state: "accepted",
|
|
1638
|
+
acceptedTurnId: turn.turnId,
|
|
1639
|
+
});
|
|
1640
|
+
if (next)
|
|
1641
|
+
emitDeferredTurnUpdate(deps, sessionId, turn.turnId, next, next.revision);
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const error = started.body.error && typeof started.body.error === "object"
|
|
1645
|
+
? started.body.error
|
|
1646
|
+
: {};
|
|
1647
|
+
if (error.code === "TURN_ACTIVE") {
|
|
1648
|
+
const next = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1649
|
+
expectedRevision: deferred.revision,
|
|
1650
|
+
state: "queued",
|
|
1651
|
+
});
|
|
1652
|
+
if (next)
|
|
1653
|
+
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, next, next.revision);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
const failed = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1657
|
+
expectedRevision: deferred.revision,
|
|
1658
|
+
state: "failed",
|
|
1659
|
+
lastError: {
|
|
1660
|
+
code: error.code ?? `HTTP_${started.status}`,
|
|
1661
|
+
message: error.message ?? "停止后的消息未能自动发送",
|
|
1662
|
+
},
|
|
1663
|
+
});
|
|
1664
|
+
if (failed)
|
|
1665
|
+
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, failed, failed.revision);
|
|
1666
|
+
}
|
|
1667
|
+
catch (error) {
|
|
1668
|
+
// Keep fire-and-forget callers safe and make a dispatch failure visible and
|
|
1669
|
+
// recoverable from the composer instead of leaving the slot indefinitely
|
|
1670
|
+
// in an ambiguous queued/dispatching state.
|
|
1671
|
+
try {
|
|
1672
|
+
const current = (await deps.store.get(sessionId))?.deferredTurn;
|
|
1673
|
+
if (current && (current.state === "queued" || current.state === "dispatching")) {
|
|
1674
|
+
const failed = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1675
|
+
expectedRevision: current.revision,
|
|
1676
|
+
state: "failed",
|
|
1677
|
+
lastError: {
|
|
1678
|
+
code: "DEFERRED_TURN_DISPATCH_FAILED",
|
|
1679
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1680
|
+
},
|
|
1681
|
+
});
|
|
1682
|
+
if (failed)
|
|
1683
|
+
emitDeferredTurnUpdate(deps, sessionId, failed.blockedByTurnId, failed, failed.revision);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
catch {
|
|
1687
|
+
// The durable store itself is unavailable. Leave the original record in
|
|
1688
|
+
// place so a later Console boot can recover it.
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
finally {
|
|
1692
|
+
deferredDispatchingSessions.delete(sessionId);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
export async function recoverDeferredTurns(deps) {
|
|
1696
|
+
const sessions = await deps.store.list({ includeArchived: false });
|
|
1697
|
+
await Promise.all(sessions
|
|
1698
|
+
.filter((session) => session.deferredTurn
|
|
1699
|
+
? session.deferredTurn.state === "queued" ||
|
|
1700
|
+
session.deferredTurn.state === "dispatching"
|
|
1701
|
+
: false)
|
|
1702
|
+
.map(async (session) => {
|
|
1703
|
+
const deferred = session.deferredTurn;
|
|
1704
|
+
const active = deps.events.getActiveTurn(session.sessionId);
|
|
1705
|
+
// A process restart cannot retain AbortController ownership. When the
|
|
1706
|
+
// durable blocker was already stopping and no execution owns it now,
|
|
1707
|
+
// close that orphan before dispatching the server-owned follow-up.
|
|
1708
|
+
if (active?.turnId === deferred.blockedByTurnId &&
|
|
1709
|
+
active.abortRequestedAt &&
|
|
1710
|
+
deps.turnExecutions &&
|
|
1711
|
+
!deps.turnExecutions.get(session.sessionId, active.turnId)) {
|
|
1712
|
+
deps.events.setTurnState(session.sessionId, active.turnId, "failed", {
|
|
1713
|
+
code: "CHAT_TURN_OWNER_LOST",
|
|
1714
|
+
message: "turn ownership was lost during Console restart",
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
await dispatchDeferredTurn(deps, session.sessionId);
|
|
1718
|
+
}));
|
|
1719
|
+
}
|
|
1318
1720
|
/** Real server-side abort: invokes Pi session.abort via the execution registry. */
|
|
1319
1721
|
export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
|
|
1320
1722
|
const gate = gateMutation(req, deps);
|
|
@@ -1642,16 +2044,27 @@ async function handleChatFiles(res, deps, sessionId, query) {
|
|
|
1642
2044
|
if (!(await requireChatSession(res, deps, sessionId)))
|
|
1643
2045
|
return;
|
|
1644
2046
|
const q = query.trim().toLowerCase();
|
|
1645
|
-
const
|
|
2047
|
+
const walked = await walkRepoFiles(deps.appData.repoRoot, {
|
|
1646
2048
|
skipSensitive: true,
|
|
1647
2049
|
maxFiles: 5000,
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
.
|
|
2050
|
+
includeDirs: true,
|
|
2051
|
+
});
|
|
2052
|
+
const entries = walked
|
|
2053
|
+
.filter((entry) => !isSensitivePath(entry.repoRelative) &&
|
|
2054
|
+
(!q || entry.repoRelative.toLowerCase().includes(q)))
|
|
2055
|
+
.sort((a, b) => {
|
|
2056
|
+
const aDir = a.repoRelative.endsWith("/");
|
|
2057
|
+
const bDir = b.repoRelative.endsWith("/");
|
|
2058
|
+
if (aDir !== bDir)
|
|
2059
|
+
return aDir ? -1 : 1;
|
|
2060
|
+
return a.repoRelative.localeCompare(b.repoRelative);
|
|
2061
|
+
})
|
|
1652
2062
|
.slice(0, 50)
|
|
1653
|
-
.map((
|
|
1654
|
-
|
|
2063
|
+
.map((entry) => ({
|
|
2064
|
+
path: entry.repoRelative,
|
|
2065
|
+
isDir: entry.repoRelative.endsWith("/"),
|
|
2066
|
+
}));
|
|
2067
|
+
sendJson(res, 200, { ok: true, files: entries });
|
|
1655
2068
|
}
|
|
1656
2069
|
export async function handleChatCommands(res, deps, sessionId) {
|
|
1657
2070
|
try {
|
|
@@ -3043,6 +3456,15 @@ export async function handleChatRequest(req, res, deps, pathname) {
|
|
|
3043
3456
|
await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
|
|
3044
3457
|
return true;
|
|
3045
3458
|
}
|
|
3459
|
+
const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
|
|
3460
|
+
if (method === "PUT" && deferredTurn) {
|
|
3461
|
+
await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|
|
3462
|
+
return true;
|
|
3463
|
+
}
|
|
3464
|
+
if (method === "DELETE" && deferredTurn) {
|
|
3465
|
+
await handleDeleteDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|
|
3466
|
+
return true;
|
|
3467
|
+
}
|
|
3046
3468
|
const sessionTurnAbort = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/turns\/([^/]+)\/abort$/);
|
|
3047
3469
|
if (method === "POST" && sessionTurnAbort) {
|
|
3048
3470
|
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: "正在推进任务状态",
|