@tea-agent/loop-agent 0.43.0-next.4 → 0.43.0-next.6
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 +82 -9
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +233 -202
- package/dist/executors/pi-executor.js +2 -0
- package/dist/executors/pi-sdk-executor.js +77 -39
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/pi-runtime.js +113 -95
- package/dist/worker/console/chat/routes.js +164 -54
- package/dist/worker/console/chat/scm-routes.js +193 -0
- package/dist/worker/console/chat/scm-service.js +253 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/session-catalog.js +1 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CCj36aaC.js → abnfDiagram-N423BO3Z-BuyrlONK.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Cl3MIVFh.js → arc-BvCl_wpi.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C_n4kMJS.js → architectureDiagram-T3A2C74G-BkBpBaRq.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DlWYJ7cK.js → blockDiagram-VBNYF7ZC-yve4Mcb_.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ud5pS7vS.js → c4Diagram-5PPSVZJV-9yLGboBx.js} +1 -1
- package/dist/worker/console/static/assets/channel-BRahIhjw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CQPwOv_j.js → chunk-2GRJ4B5K-BKP69b8r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CMzjxrWI.js → chunk-2Q5K7J3B-NWFRnXju.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-pcnXnD6t.js → chunk-5RXB4S5H-BiweqLEc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CEB3AvU2.js → chunk-5VM5RSS4-CP9GHmRY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DSKnbXbp.js → chunk-6Q2QTUOP-BiGDkBtj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CS6-PjCZ.js → chunk-GF5L2VYU-DkJ4ZjQS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-f6q8Tnzz.js → chunk-JWPE2WC7-B5jjn7zq.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DkB0QX3q.js → chunk-KBJHAD2P-C6KoK9O0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BdB4jAv6.js → chunk-RYQCIY6F-BvNIfyn-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVm1bnX6.js → chunk-XXDRQBXY-BZ33iX4W.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-FV_P-IEp.js → cose-bilkent-JH36ORCC-CP8eIuZd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BM7RtkA2.js → cynefin-VYW2F7L2-DZECjpOe.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C1BZwTG5.js → cynefinDiagram-MW4NZA55-CvVJoFqf.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C6McNKmY.js → dagre-VZM6K2ZE-D_JTqdAn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-C0aWRUaE.js → diagram-7IWD3JNH-Db7AQ0z0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bfk_SKE8.js → diagram-B4RE2ZJO-AZPVo5CZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-prx1T4qf.js → diagram-LBJQPF4R-BgXqI3E6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DBvty0eU.js → diagram-Q27KOJAE-BYjFn3Ba.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CF0em8e9.js → diagram-UB23O5K3-B4rJgGGO.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CF90b4Ws.js → ebnfDiagram-BXEA7PRR-C6XWZhpw.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-hfunxzof.js → erDiagram-JOGREHBK-1YdQRIQW.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D-QSy4Nl.js → flowDiagram-UKHOOZJN-l3a0AwN0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DaBGNcvT.js → ganttDiagram-PKOTCBZU-CQL96F2Z.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BChcubGL.js → gitGraphDiagram-DS77QQ5N-BxCvafRb.js} +1 -1
- package/dist/worker/console/static/assets/index-4wCGsXuU.js +465 -0
- package/dist/worker/console/static/assets/index-CiF9WBoR.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BkC67fIa.js → infoDiagram-6WML65LV-BUipIeDO.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-ibhLczCD.js → ishikawaDiagram-WSZJBQD7-CRfomVMX.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DCRMitay.js → journeyDiagram-NVQOT4AX-_UFvyviX.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DJ_hKaYI.js → kanban-definition-27J2QSJJ-CKIRoqKv.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIlLsYsA.js → linear-CjttJDCM.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-C-5IQMP_.js → mermaid.core-DPvHX155.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-TCzV5TQT.js → mindmap-definition-FAOFIHXS-Qmj-qwBe.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-bfhd2BQH.js → pegDiagram-VL7TDLO6-Auw6DRgq.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BxFRAm5N.js → pieDiagram-7S7Q4E2Y-CqMrfD4c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C-m12lxv.js → quadrantDiagram-CIZ2JOQS-BAddeggH.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CwmLiGgx.js → railroadDiagram-AXF67PYL-DG1JuOu2.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-oc0Q1jiD.js → requirementDiagram-LRYGKXZP-DLYGMKlC.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-9Ght6Yur.js → sankeyDiagram-W5VNT64P-g68SbLM9.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-ndm3lyz9.js → sequenceDiagram-SI44F4Z6-CmszLKv8.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7sPlKVcw.js → sizeCapture-X5ZJPWSS-D2tEKUCj.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Izl21-oS.js → stateDiagram-OKZ733FA-Syc5E_Jf.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CwWwuPI-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CtO3D65o.js → swimlanes-SLNWSIFB-Ibn0IRCa.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-A7fqemr6.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cyg5cY7z.js → timeline-definition-Z64GVDOM-2ZW9W3iS.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CwtE_TFK.js → vennDiagram-T6HMQDX7-v50HsKUh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Dtt9v8OO.js → wardleyDiagram-T6FBY63Y-DqCXEoDH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B2Y3ryb6.js → xychartDiagram-ELKLHX3M-vIP8Vo9d.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +20 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/workspace-context.js +6 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/package.json +1 -1
- package/skills/frontend-plan/SKILL.md +7 -0
- package/dist/worker/console/static/assets/channel-CEMRpNm-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/index-CJkCY1XR.js +0 -464
- package/dist/worker/console/static/assets/index-DUBCbiqv.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +0 -8
|
@@ -9,6 +9,9 @@ import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DE
|
|
|
9
9
|
import { serializeSessionEvent } from "./pi-event-serializer.js";
|
|
10
10
|
import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
|
|
11
11
|
import { createCompactionController, createContextPressureExtension, createSiftBridgeState, inMemoryCompactionSettings, isDagSiftEnabled, parseCompactionPolicy, parseSiftBridgeConfig, SIFT_RETRIEVE_TOOL_NAME, } from "../shared/pi-context-pressure/index.js";
|
|
12
|
+
/** Maximum number of same-session continuations after provider output truncation. */
|
|
13
|
+
export const PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX = 3;
|
|
14
|
+
export const PI_SDK_OUTPUT_LIMIT_CONTINUE_PROMPT = "继续。上一轮因输出长度上限(stopReason=length)截断;当前会话、工具与已完成工作仍保留。只完成尚未完成的部分,不重做已成功的写入或事实提交。";
|
|
12
15
|
/** Wrap the actual stream seam: tool turns, retries and compaction all await it. */
|
|
13
16
|
export async function installPiProviderRequestBudget(session, reserve, onFailure, capacity) {
|
|
14
17
|
const agent = session.agent;
|
|
@@ -611,6 +614,8 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
611
614
|
const requestEnvelopeObservation = { peakBytes: 0, peakEstimatedTokens: 0, estimatorVersion: "utf8-bytes-div3-v1-estimate" };
|
|
612
615
|
const stdoutPreview = new BoundedTextPreview("stdout");
|
|
613
616
|
const stdoutCollector = createPiJsonlStreamCollector();
|
|
617
|
+
let turnCollector = createPiJsonlStreamCollector();
|
|
618
|
+
let committedTerminalObserved = false;
|
|
614
619
|
const usageSamples = [];
|
|
615
620
|
const observationEvents = [];
|
|
616
621
|
const readObservation = createFrontendReadObservation();
|
|
@@ -622,6 +627,9 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
622
627
|
// length-stopped thinking-only attempt with no persisted write tool calls is
|
|
623
628
|
// still distinguishable from generic empty output.
|
|
624
629
|
let stopReason;
|
|
630
|
+
let providerErrorMessage;
|
|
631
|
+
let promptIndex = 0;
|
|
632
|
+
let outputContinuesSent = 0;
|
|
625
633
|
let thinkingObserved = false;
|
|
626
634
|
let writeToolCallCount = 0;
|
|
627
635
|
const observeWriterThinkingExhaustionEvidence = (event) => {
|
|
@@ -768,6 +776,8 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
768
776
|
resolveContextBudget?.("context-budget");
|
|
769
777
|
};
|
|
770
778
|
unsubscribe = session.subscribe((event) => {
|
|
779
|
+
if (event.type === "tool_execution_end" && event.isError !== true && typeof event.toolName === "string" && options.outputLimitRecovery?.terminalToolNames.includes(event.toolName) && isRecord(event.result) && isRecord(event.result.details) && event.result.details.ok === true)
|
|
780
|
+
committedTerminalObserved = true;
|
|
771
781
|
if (event.type === "agent_settled" && !settled) {
|
|
772
782
|
settled = true;
|
|
773
783
|
resolveSettlement?.("settled");
|
|
@@ -788,6 +798,8 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
788
798
|
}
|
|
789
799
|
}
|
|
790
800
|
const observationMessage = isRecord(event.message) ? event.message : undefined;
|
|
801
|
+
if (typeof observationMessage?.errorMessage === "string")
|
|
802
|
+
providerErrorMessage = observationMessage.errorMessage;
|
|
791
803
|
try {
|
|
792
804
|
readObservation.observe(event);
|
|
793
805
|
}
|
|
@@ -796,7 +808,7 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
796
808
|
observationEvents.push({ responseId: event.responseId, messageId: event.messageId, usage: event.usage, tokenUsage: event.tokenUsage, message: observationMessage ? { role: observationMessage.role, id: observationMessage.id, responseId: observationMessage.responseId, usage: observationMessage.usage } : undefined });
|
|
797
809
|
const usageSample = extractSdkUsageSample(event);
|
|
798
810
|
if (usageSample)
|
|
799
|
-
usageSamples.push(usageSample);
|
|
811
|
+
usageSamples.push({ ...usageSample, responseKey: usageSample.responseKey ?? `turn-${promptIndex}` });
|
|
800
812
|
if (event.type === "turn_end")
|
|
801
813
|
observedTurns += 1;
|
|
802
814
|
checkContextBudget();
|
|
@@ -810,6 +822,7 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
810
822
|
const line = serializeSessionEvent(event);
|
|
811
823
|
stdoutPreview.append(`${line}\n`);
|
|
812
824
|
stdoutCollector.append(`${line}\n`);
|
|
825
|
+
turnCollector.append(`${line}\n`);
|
|
813
826
|
sessionEventAppender?.append(line, event);
|
|
814
827
|
});
|
|
815
828
|
const timeoutPromise = timeoutMs > 0
|
|
@@ -820,6 +833,9 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
820
833
|
})
|
|
821
834
|
: null;
|
|
822
835
|
const runTurn = async (userMessage) => {
|
|
836
|
+
promptIndex += 1;
|
|
837
|
+
stopReason = undefined;
|
|
838
|
+
turnCollector = createPiJsonlStreamCollector();
|
|
823
839
|
const settlementPromise = resetSettlement();
|
|
824
840
|
const stallPromise = resetStallPromise();
|
|
825
841
|
const contextBudgetPromise = options.contextBudget
|
|
@@ -865,44 +881,55 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
865
881
|
: options.userMessage;
|
|
866
882
|
let continuesSent = 0;
|
|
867
883
|
let raced = await runTurn(promptMessage);
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
884
|
+
while (true) {
|
|
885
|
+
if (raced === "context-budget") {
|
|
886
|
+
appendStderr(contextBudgetDetail);
|
|
887
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
888
|
+
await disposeSession();
|
|
889
|
+
}
|
|
890
|
+
else if (raced === "stall") {
|
|
891
|
+
abortConfirmed = await runSessionActionWithGrace("abort", () => session.abort());
|
|
892
|
+
const withinAbsoluteTimeout = timeoutMs <= 0 || Date.now() - startedAt < timeoutMs;
|
|
893
|
+
const canContinue = abortConfirmed &&
|
|
894
|
+
writeToolCallCount === 0 &&
|
|
895
|
+
withinAbsoluteTimeout &&
|
|
896
|
+
continuesSent < PI_SDK_STALL_CONTINUE_MAX;
|
|
897
|
+
if (canContinue) {
|
|
898
|
+
// The aborted turn's stopReason must not leak onto a recovered continue.
|
|
899
|
+
stopReason = undefined;
|
|
900
|
+
continuesSent += 1;
|
|
901
|
+
persistRuntimeNote({
|
|
902
|
+
type: "loop-agent-stall-continue",
|
|
903
|
+
prompt: PI_SDK_STALL_CONTINUE_PROMPT,
|
|
904
|
+
maxContinues: PI_SDK_STALL_CONTINUE_MAX,
|
|
905
|
+
});
|
|
906
|
+
raced = await runTurn(PI_SDK_STALL_CONTINUE_PROMPT);
|
|
907
|
+
if (raced !== "done" && raced !== "settled") {
|
|
908
|
+
await failClosedAfterWait(raced === "stall" ? "stall" : "absolute-timeout", raced === "stall"
|
|
909
|
+
? "pi SDK stall continue did not recover; treating as timeout"
|
|
910
|
+
: "pi SDK stall continue hit the absolute timeout", true);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
await failClosedAfterWait("stall", !abortConfirmed
|
|
915
|
+
? undefined
|
|
916
|
+
: writeToolCallCount > 0
|
|
917
|
+
? "pi SDK stall continue skipped because write tools already ran"
|
|
918
|
+
: "pi SDK stall continue skipped because absolute timeout elapsed", false);
|
|
894
919
|
}
|
|
895
920
|
}
|
|
896
|
-
else {
|
|
897
|
-
await failClosedAfterWait("
|
|
898
|
-
? undefined
|
|
899
|
-
: writeToolCallCount > 0
|
|
900
|
-
? "pi SDK stall continue skipped because write tools already ran"
|
|
901
|
-
: "pi SDK stall continue skipped because absolute timeout elapsed", false);
|
|
921
|
+
else if (raced === "absolute-timeout") {
|
|
922
|
+
await failClosedAfterWait("absolute-timeout");
|
|
902
923
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
924
|
+
if (options.outputLimitRecovery && stopReason === "length" && !committedTerminalObserved && !stderr && outputContinuesSent < PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX && !timedOut) {
|
|
925
|
+
outputContinuesSent += 1;
|
|
926
|
+
const note = serializeSessionEvent({ type: "loop-agent-output-limit-continue", continue: outputContinuesSent, maxContinues: PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX, prompt: PI_SDK_OUTPUT_LIMIT_CONTINUE_PROMPT });
|
|
927
|
+
stdoutPreview.append(`${note}\n`);
|
|
928
|
+
stdoutCollector.append(`${note}\n`);
|
|
929
|
+
raced = await runTurn(PI_SDK_OUTPUT_LIMIT_CONTINUE_PROMPT);
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
break;
|
|
906
933
|
}
|
|
907
934
|
}
|
|
908
935
|
catch (error) {
|
|
@@ -931,20 +958,29 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
931
958
|
}
|
|
932
959
|
}
|
|
933
960
|
}
|
|
961
|
+
if (providerErrorMessage && stopReason === "error" && !stderr)
|
|
962
|
+
appendStderr(providerErrorMessage);
|
|
963
|
+
if (options.outputLimitRecovery && stopReason === "length" && !committedTerminalObserved && outputContinuesSent >= PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX) {
|
|
964
|
+
const note = serializeSessionEvent({ type: "loop-agent-output-limit-exhausted", maxContinues: PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX });
|
|
965
|
+
stdoutPreview.append(`${note}\n`);
|
|
966
|
+
}
|
|
934
967
|
const stdout = stdoutPreview.text();
|
|
935
968
|
const durationMs = Date.now() - startedAt;
|
|
936
969
|
const collected = stdoutCollector.finish();
|
|
937
970
|
const fallbackParsed = collected.parsedEvents > 0
|
|
938
971
|
? undefined
|
|
939
972
|
: extractAssistantTextFromPiJson(stdout);
|
|
973
|
+
const finalTurn = turnCollector.finish();
|
|
940
974
|
const assistantText = collected.parsedEvents > 0
|
|
941
|
-
?
|
|
975
|
+
? finalTurn.assistantText
|
|
942
976
|
: (fallbackParsed?.assistantText ?? "");
|
|
943
977
|
const parsedEvents = collected.parsedEvents > 0
|
|
944
978
|
? collected.parsedEvents
|
|
945
979
|
: (fallbackParsed?.parsedEvents ?? 0);
|
|
946
980
|
const tokensUsed = aggregateSdkTokenUsage(usageSamples);
|
|
947
|
-
const
|
|
981
|
+
const cancelled = stopReason === "aborted";
|
|
982
|
+
const outputLimitExhausted = Boolean(options.outputLimitRecovery && stopReason === "length" && !committedTerminalObserved && !timedOut && !stderr);
|
|
983
|
+
const failureCategory = !terminationConfirmed ? "termination-unconfirmed" : stopReason === "aborted" ? "cancelled" : outputLimitExhausted ? "output-limit" : providerBudgetFailure ? (providerBudgetFailure.includes("REQUEST_ABORTED") ? "interrupted" : providerBudgetFailure.startsWith("FRONTEND_CONTEXT_ESTIMATE_EXCEEDED") ? "context-budget-exhausted" : "budget_breach") : terminationConfirmed
|
|
948
984
|
? classifyPiFailure({
|
|
949
985
|
assistantText,
|
|
950
986
|
exitCode: timedOut ? 1 : stderr ? 1 : 0,
|
|
@@ -959,12 +995,14 @@ async function executeSingleSdkAttemptInternal(options) {
|
|
|
959
995
|
backend: "sdk",
|
|
960
996
|
command: ["pi-sdk", ...piSdkArgs],
|
|
961
997
|
durationMs,
|
|
962
|
-
exitCode: timedOut || stderr ? 1 : 0,
|
|
998
|
+
exitCode: timedOut || stderr || outputLimitExhausted || cancelled ? 1 : 0,
|
|
963
999
|
failureCategory,
|
|
964
1000
|
modelDisplay,
|
|
965
1001
|
ok: terminationConfirmed &&
|
|
966
1002
|
!timedOut &&
|
|
967
1003
|
!stderr &&
|
|
1004
|
+
!outputLimitExhausted &&
|
|
1005
|
+
!cancelled &&
|
|
968
1006
|
assistantText.length > 0 &&
|
|
969
1007
|
!collected.outputTooLarge,
|
|
970
1008
|
parsedEvents,
|
|
@@ -69,7 +69,7 @@ const reviewResponseSchema = z.object({
|
|
|
69
69
|
disposition: z.enum(FRAGMENT_DISPOSITIONS).optional(),
|
|
70
70
|
reason: z.string().min(1).optional(),
|
|
71
71
|
})
|
|
72
|
-
.
|
|
72
|
+
.nullish(),
|
|
73
73
|
});
|
|
74
74
|
/**
|
|
75
75
|
* 模型队列构建镜像 semantic-intake.ts:MED→HIGH→LOW(harness executors.pi)+
|
|
@@ -134,7 +134,7 @@ function buildReviewPrompt(request) {
|
|
|
134
134
|
"- approved: true only when the exclusion/conflict handling is justified.",
|
|
135
135
|
"- fragmentId: MUST equal the requested fragment id exactly (echo the `fragment id:` line verbatim); identity mismatch is rejected.",
|
|
136
136
|
"- reason: recommended; explain the verdict.",
|
|
137
|
-
"- revision:
|
|
137
|
+
"- revision: omit or use null when no revision is proposed; otherwise one bounded revision {disposition, reason}. Never invent requirements or human resolutions.",
|
|
138
138
|
JSON.stringify({
|
|
139
139
|
fragmentId: "REQ-SRC-AB12CD34EF56",
|
|
140
140
|
approved: true,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* source/references/, separate from the derived source/需求.md contract.
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { access, copyFile, mkdir, readdir, readFile, writeFile, } from "node:fs/promises";
|
|
8
|
+
import { access, copyFile, lstat, mkdir, readdir, readFile, writeFile, } from "node:fs/promises";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { getTaskPaths, loadTaskConfig } from "./runtime.js";
|
|
11
11
|
import { writeTaskConfig } from "../infrastructure/harness/task-store.js";
|
|
@@ -87,6 +87,7 @@ export async function materializeTaskReferenceDocs(input) {
|
|
|
87
87
|
const referenceDir = path.join(paths.sourceDir, SOURCE_REFERENCE_DIRECTORY);
|
|
88
88
|
await mkdir(referenceDir, { recursive: true });
|
|
89
89
|
const materialized = [];
|
|
90
|
+
const manifest = await readSourceManifest(paths.sourceDir);
|
|
90
91
|
for (const doc of docs) {
|
|
91
92
|
const absoluteSource = path.isAbsolute(doc.path)
|
|
92
93
|
? doc.path
|
|
@@ -94,7 +95,10 @@ export async function materializeTaskReferenceDocs(input) {
|
|
|
94
95
|
await assertReadableFile(absoluteSource, `referenceDocs entry "${doc.name ?? doc.path}"`);
|
|
95
96
|
const extension = path.extname(absoluteSource) || ".md";
|
|
96
97
|
const baseName = slugifyName(doc.name ?? path.basename(absoluteSource, extension));
|
|
97
|
-
const
|
|
98
|
+
const imported = manifest?.documents.find((entry) => path.basename(entry.materializedPath, path.extname(entry.materializedPath)) === baseName);
|
|
99
|
+
const targetName = imported
|
|
100
|
+
? path.basename(imported.materializedPath)
|
|
101
|
+
: `${baseName}${extension}`;
|
|
98
102
|
const targetPath = path.join(referenceDir, targetName);
|
|
99
103
|
try {
|
|
100
104
|
await access(targetPath);
|
|
@@ -132,14 +136,23 @@ export async function importPrdDocument(input) {
|
|
|
132
136
|
catch {
|
|
133
137
|
// directory may be empty / just created
|
|
134
138
|
}
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
// Search every immutable reference, including suffixed and renamed imports.
|
|
140
|
+
// Roles remain distinct: identical bytes must not erase a constraint/AC role.
|
|
141
|
+
const matching = existingManifest?.documents.find((doc) => doc.role === role && doc.sha256 === hash.sha256 && doc.bytes === hash.bytes);
|
|
142
|
+
if (matching) {
|
|
143
|
+
const candidate = path.resolve(paths.sourceDir, matching.materializedPath);
|
|
144
|
+
if (path.dirname(candidate) !== path.resolve(referenceDir)) {
|
|
145
|
+
throw new Error(`REFERENCE_PATH_UNSAFE: ${matching.materializedPath}`);
|
|
146
|
+
}
|
|
147
|
+
const stats = await lstat(candidate);
|
|
148
|
+
if (!stats.isFile() || stats.isSymbolicLink() ||
|
|
149
|
+
!(await sameHashIfExists(candidate, hash.sha256))) {
|
|
150
|
+
throw new Error(`REFERENCE_HASH_MISMATCH: ${matching.materializedPath}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const targetFileName = matching
|
|
154
|
+
? path.basename(matching.materializedPath)
|
|
155
|
+
: ensureUniqueReferenceName(existingNames, requestedName, extension);
|
|
143
156
|
const targetPath = path.join(referenceDir, targetFileName);
|
|
144
157
|
if (!(await fileExists(targetPath))) {
|
|
145
158
|
await writeFile(targetPath, hash.content);
|
|
@@ -149,7 +162,7 @@ export async function importPrdDocument(input) {
|
|
|
149
162
|
? toPosixRelative(input.repoRoot, absoluteSource)
|
|
150
163
|
: input.filePath.split(path.sep).join("/");
|
|
151
164
|
const materializedRelativePath = `${SOURCE_REFERENCE_DIRECTORY}/${targetFileName}`;
|
|
152
|
-
const document = {
|
|
165
|
+
const document = matching ?? {
|
|
153
166
|
role,
|
|
154
167
|
name: requestedName,
|
|
155
168
|
sourcePath: sourcePathForRecord,
|
|
@@ -176,20 +189,28 @@ export async function importPrdDocument(input) {
|
|
|
176
189
|
: (existingManifest?.updatedAt ?? importedAt),
|
|
177
190
|
documents,
|
|
178
191
|
};
|
|
179
|
-
|
|
192
|
+
if (documentSetChanged)
|
|
193
|
+
await writeSourceManifest(paths.sourceDir, manifest);
|
|
180
194
|
const taskConfig = await loadTaskConfig(input.repoRoot, input.taskId);
|
|
181
195
|
const referenceDocs = [...(taskConfig.referenceDocs ?? [])];
|
|
182
|
-
const
|
|
196
|
+
const listedIndex = referenceDocs.findIndex((doc) => {
|
|
183
197
|
const absolute = path.isAbsolute(doc.path)
|
|
184
198
|
? doc.path
|
|
185
199
|
: path.resolve(input.repoRoot, doc.path);
|
|
186
200
|
return absolute === absoluteSource;
|
|
187
201
|
});
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
202
|
+
// Keep original source paths as provenance, but point materialization at
|
|
203
|
+
// the chosen immutable filename so DAG generation cannot recreate duplicates.
|
|
204
|
+
const reference = {
|
|
205
|
+
name: path.basename(targetFileName, path.extname(targetFileName)),
|
|
206
|
+
path: sourcePathForRecord,
|
|
207
|
+
};
|
|
208
|
+
const configChanged = listedIndex < 0 || referenceDocs[listedIndex]?.name !== reference.name;
|
|
209
|
+
if (configChanged) {
|
|
210
|
+
if (listedIndex < 0)
|
|
211
|
+
referenceDocs.push(reference);
|
|
212
|
+
else
|
|
213
|
+
referenceDocs[listedIndex] = { ...referenceDocs[listedIndex], ...reference };
|
|
193
214
|
await writeTaskConfig(input.repoRoot, input.taskId, {
|
|
194
215
|
...taskConfig,
|
|
195
216
|
referenceDocs,
|
|
@@ -198,11 +219,15 @@ export async function importPrdDocument(input) {
|
|
|
198
219
|
// Managed contracts must bump revision after import-prd mutates manifest/task config.
|
|
199
220
|
// Lazy import avoids circular init with contract/observe.
|
|
200
221
|
const { refreshManagedContractAfterImport } = await import("./contract/import-revision.js");
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
222
|
+
const changed = documentSetChanged || configChanged;
|
|
223
|
+
if (changed) {
|
|
224
|
+
await refreshManagedContractAfterImport({
|
|
225
|
+
repoRoot: input.repoRoot,
|
|
226
|
+
taskId: input.taskId,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
205
229
|
return {
|
|
230
|
+
changed,
|
|
206
231
|
taskId: input.taskId,
|
|
207
232
|
sourcePath: sourcePathForRecord,
|
|
208
233
|
materializedAbsolutePath: targetPath,
|
|
@@ -210,7 +235,7 @@ export async function importPrdDocument(input) {
|
|
|
210
235
|
sha256: hash.sha256,
|
|
211
236
|
bytes: hash.bytes,
|
|
212
237
|
manifestPath: getSourceManifestPath(paths.sourceDir),
|
|
213
|
-
referenceDocs
|
|
238
|
+
referenceDocs,
|
|
214
239
|
};
|
|
215
240
|
}
|
|
216
241
|
async function sameHashIfExists(filePath, sha256) {
|