@tea-agent/loop-agent 0.44.0-next.3 → 0.44.0-next.5
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 +8 -2
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +2 -2
- package/dist/executors/shell-write-guard.js +26 -3
- package/dist/shared/file-identity.js +23 -0
- package/dist/shared/git-workspace-status.js +480 -0
- package/dist/shared/path-safety.js +2 -2
- package/dist/task/config-types.js +2 -1
- package/dist/task/contract/schema.js +1 -0
- package/dist/worker/console/chat/chat-event-store.js +32 -0
- package/dist/worker/console/chat/context-insights.js +723 -0
- package/dist/worker/console/chat/conversation-nodes.js +383 -0
- package/dist/worker/console/chat/pi-runtime.js +128 -12
- package/dist/worker/console/chat/provider-error.js +0 -12
- package/dist/worker/console/chat/repo-browser.js +3 -4
- package/dist/worker/console/chat/routes.js +209 -4
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/turn-process.js +128 -7
- package/dist/worker/console/prd-intake-bridge.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-COQlLTI7.js → abnfDiagram-N423BO3Z-BHuhGM1e.js} +1 -1
- package/dist/worker/console/static/assets/{arc-zN3QkNaV.js → arc-CKh5E_sr.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Pyoyj5f2.js → architectureDiagram-T3A2C74G-DizhiBEv.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BNppA5WW.js → blockDiagram-VBNYF7ZC-3Neea-Z-.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-lyxL8nTx.js → c4Diagram-5PPSVZJV-BCP_Qrgi.js} +1 -1
- package/dist/worker/console/static/assets/channel-DxgzA1GF.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-0hgjcdK4.js → chunk-2GRJ4B5K-C_wydftK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D63N9d4i.js → chunk-2Q5K7J3B-jRPXZbzx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CUw2AyB5.js → chunk-5RXB4S5H-BLQf2OYO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-Q_kwZT30.js → chunk-5VM5RSS4-0qRq64rI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-C7vMiKcw.js → chunk-6Q2QTUOP-ClMg8xmV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-T9GbV5Hz.js → chunk-GF5L2VYU-Coop2FlL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D8nUSUJI.js → chunk-JWPE2WC7-CDdO-jPJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BCH2ewdp.js → chunk-KBJHAD2P-D8Gkppjl.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BTdDuDBN.js → chunk-RYQCIY6F-DA9HIySf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Cqm2RcKF.js → chunk-XXDRQBXY-DZNDRX1k.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BZcm7fs6.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BZcm7fs6.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-pjwfpVu4.js → cose-bilkent-JH36ORCC-CZ1zGyjq.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BvalptKA.js → cynefin-VYW2F7L2-B8u1_FtM.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BU4qJ6d7.js → cynefinDiagram-MW4NZA55-CPBB6lfo.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-DVTpq8J9.js → dagre-VZM6K2ZE-CG3Eov3E.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B8s1W6Fc.js → diagram-7IWD3JNH-ycI-dOjh.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DPrFk9yH.js → diagram-B4RE2ZJO-Bx_GyQ7Y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CnQIQsHC.js → diagram-LBJQPF4R-m0G6mP4d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-op0DEpCv.js → diagram-Q27KOJAE-DTDJkqcJ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-DXQamm4T.js → diagram-UB23O5K3-BCfFrG-p.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-b1ad2C0r.js → ebnfDiagram-BXEA7PRR-BIkTNPpp.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-B4cD-ZFJ.js → erDiagram-JOGREHBK-DTcMWrd2.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CrgGjlQ1.js → flowDiagram-UKHOOZJN-BqzDaNQ7.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DovmXQNV.js → ganttDiagram-PKOTCBZU-BSl_vGFe.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DpFyu2Jn.js → gitGraphDiagram-DS77QQ5N-DCA5HXM6.js} +1 -1
- package/dist/worker/console/static/assets/index-BAEDjTbD.css +1 -0
- package/dist/worker/console/static/assets/index-wKEJUaP5.js +482 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-D3MoQZIl.js → infoDiagram-6WML65LV-DvqT18ge.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-Ci6C1yjS.js → ishikawaDiagram-WSZJBQD7-B6zGKrQ3.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BWMo2lIP.js → journeyDiagram-NVQOT4AX-BpLQPI9C.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BdthIAg3.js → kanban-definition-27J2QSJJ-CzIz_-Lt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DuFAlSOj.js → linear-CMQ8Jpp-.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Dqz3Slj9.js → mermaid.core-BdAMrTf7.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DNVTrD09.js → mindmap-definition-FAOFIHXS-hSwF6sOq.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CgfZPfBh.js → pegDiagram-VL7TDLO6-DZdv509B.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-ClM9P7yo.js → pieDiagram-7S7Q4E2Y-CmD4-b0K.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DwnEak2J.js → quadrantDiagram-CIZ2JOQS-DtKxNxlS.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-rK4hxt9P.js → railroadDiagram-AXF67PYL-D48iIoiS.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Cjqa6vo3.js → requirementDiagram-LRYGKXZP-C3N0K-2p.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-D3Fa250Z.js → sankeyDiagram-W5VNT64P-CACWR-Xs.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-2PdrNajj.js → sequenceDiagram-SI44F4Z6-g_QgugvZ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BBcuGeZn.js → sizeCapture-X5ZJPWSS-DqLoRPGK.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CSJX9X6s.js → stateDiagram-OKZ733FA-ES59VgMG.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyCIq6jx.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-pOUU1bic.js → swimlanes-SLNWSIFB-fjyLVNWo.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CsKtO34q.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CAeqfQn0.js → timeline-definition-Z64GVDOM-Cd5-biKv.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DYPLOMVO.js → vennDiagram-T6HMQDX7-CZTu6mPf.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Dy2V0Cha.js → wardleyDiagram-T6FBY63Y-DR01jjbe.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-Dj9o7jrn.js → xychartDiagram-ELKLHX3M-iZcuGVGa.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +97 -3
- package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +11 -2
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +2 -1
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +9 -0
- package/dist/worker/observe/node-context-insights.js +479 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +3 -1
- package/dist/workflows/dag/frontend-plan-completeness.js +13 -3
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -6
- package/dist/workflows/dag/frontend-typed-event-store.js +5 -0
- package/dist/workflows/dag/frontend-writer-admission.js +1 -0
- package/dist/workflows/dag/output-protocol.js +1 -0
- package/dist/workflows/dag/rerun-task.js +1 -0
- package/docs/README.md +1 -1
- package/docs/governance/harness-methodology-verification.md +11 -0
- package/package.json +1 -1
- package/dist/worker/console/static/assets/channel-CgLPkTw2.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuSD1Mhb.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuSD1Mhb.js +0 -1
- package/dist/worker/console/static/assets/index-DHBWEwz_.css +0 -1
- package/dist/worker/console/static/assets/index-JRfTUgP7.js +0 -478
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cqskj0zt.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CM0ZIhNP.js +0 -8
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
function finiteStep(data) {
|
|
2
|
+
return typeof data.step === "number" && Number.isSafeInteger(data.step) && data.step >= 0
|
|
3
|
+
? data.step
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
6
|
+
function locationFor(event, step = finiteStep(event.data)) {
|
|
7
|
+
return step === undefined
|
|
8
|
+
? { kind: "turn", turnId: event.turnId }
|
|
9
|
+
: { kind: "step", turnId: event.turnId, step };
|
|
10
|
+
}
|
|
11
|
+
function status(value) {
|
|
12
|
+
return value === "scheduled" ||
|
|
13
|
+
value === "started" ||
|
|
14
|
+
value === "cancelled" ||
|
|
15
|
+
value === "running" ||
|
|
16
|
+
value === "completed" ||
|
|
17
|
+
value === "failed" ||
|
|
18
|
+
value === "interrupted"
|
|
19
|
+
? value
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
function positiveInteger(value) {
|
|
23
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0
|
|
24
|
+
? value
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
27
|
+
function nonNegativeNumber(value) {
|
|
28
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
29
|
+
? value
|
|
30
|
+
: undefined;
|
|
31
|
+
}
|
|
32
|
+
function nonEmptyString(value) {
|
|
33
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
34
|
+
}
|
|
35
|
+
function unknownRetryNode(event, base) {
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
key: `unknown:${event.eventId}`,
|
|
39
|
+
kind: "unknown",
|
|
40
|
+
location: locationFor(event),
|
|
41
|
+
data: {
|
|
42
|
+
eventType: "model_retry",
|
|
43
|
+
preview: "Incomplete model retry lifecycle event",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function retryStateFromEvent(data) {
|
|
48
|
+
if (data.state === "scheduled")
|
|
49
|
+
return "scheduled";
|
|
50
|
+
return data.success === false || data.state === "failed"
|
|
51
|
+
? "cancelled"
|
|
52
|
+
: "started";
|
|
53
|
+
}
|
|
54
|
+
function isRetryAttempt(value) {
|
|
55
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
56
|
+
return false;
|
|
57
|
+
const attempt = value;
|
|
58
|
+
return (positiveInteger(attempt.attempt) !== undefined &&
|
|
59
|
+
positiveInteger(attempt.maxAttempts) !== undefined &&
|
|
60
|
+
nonNegativeNumber(attempt.delayMs) !== undefined &&
|
|
61
|
+
nonEmptyString(attempt.errorMessage) !== undefined &&
|
|
62
|
+
(attempt.state === "scheduled" ||
|
|
63
|
+
attempt.state === "started" ||
|
|
64
|
+
attempt.state === "cancelled") &&
|
|
65
|
+
typeof attempt.seq === "number" &&
|
|
66
|
+
typeof attempt.at === "number");
|
|
67
|
+
}
|
|
68
|
+
/** Read the current complete attempt from a projected retry-chain node. */
|
|
69
|
+
export function modelRetryCurrent(node) {
|
|
70
|
+
if (node.kind !== "retry")
|
|
71
|
+
return undefined;
|
|
72
|
+
return isRetryAttempt(node.data.current) ? node.data.current : undefined;
|
|
73
|
+
}
|
|
74
|
+
function modelRetryAttempts(node) {
|
|
75
|
+
return Array.isArray(node.data.attempts)
|
|
76
|
+
? node.data.attempts.filter(isRetryAttempt)
|
|
77
|
+
: [];
|
|
78
|
+
}
|
|
79
|
+
function retryUpdate(node) {
|
|
80
|
+
const value = node.data.retryUpdate;
|
|
81
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
82
|
+
return undefined;
|
|
83
|
+
const candidate = value;
|
|
84
|
+
if (positiveInteger(candidate.attempt) === undefined ||
|
|
85
|
+
(candidate.state !== "started" && candidate.state !== "cancelled"))
|
|
86
|
+
return undefined;
|
|
87
|
+
return candidate;
|
|
88
|
+
}
|
|
89
|
+
/** Convert one already-redacted durable event into a stable UI node. */
|
|
90
|
+
export function conversationNodeFromEvent(event) {
|
|
91
|
+
const at = Date.parse(event.at);
|
|
92
|
+
const base = {
|
|
93
|
+
anchorSeq: event.seq,
|
|
94
|
+
updatedSeq: event.seq,
|
|
95
|
+
at: Number.isFinite(at) ? at : 0,
|
|
96
|
+
data: event.data,
|
|
97
|
+
};
|
|
98
|
+
const step = finiteStep(event.data);
|
|
99
|
+
switch (event.kind) {
|
|
100
|
+
case "step_start":
|
|
101
|
+
case "step_end": {
|
|
102
|
+
if (step === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
const outcome = status(event.data.outcome);
|
|
105
|
+
return {
|
|
106
|
+
...base,
|
|
107
|
+
key: `step:${event.turnId}:${step}`,
|
|
108
|
+
kind: "step",
|
|
109
|
+
location: { kind: "step", turnId: event.turnId, step },
|
|
110
|
+
status: event.kind === "step_start" ? "running" : (outcome ?? "completed"),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
case "model_retry": {
|
|
114
|
+
const attempt = positiveInteger(event.data.attempt);
|
|
115
|
+
if (attempt === undefined)
|
|
116
|
+
return unknownRetryNode(event, base);
|
|
117
|
+
const retryState = retryStateFromEvent(event.data);
|
|
118
|
+
const key = `retry:${event.turnId}:${step ?? 0}`;
|
|
119
|
+
if (retryState !== "scheduled") {
|
|
120
|
+
return {
|
|
121
|
+
...base,
|
|
122
|
+
key,
|
|
123
|
+
kind: "retry",
|
|
124
|
+
location: locationFor(event),
|
|
125
|
+
status: retryState,
|
|
126
|
+
data: {
|
|
127
|
+
retryUpdate: {
|
|
128
|
+
attempt,
|
|
129
|
+
state: retryState,
|
|
130
|
+
seq: event.seq,
|
|
131
|
+
at: base.at,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const maxAttempts = positiveInteger(event.data.maxAttempts);
|
|
137
|
+
const delayMs = nonNegativeNumber(event.data.delayMs);
|
|
138
|
+
const errorMessage = nonEmptyString(event.data.errorMessage);
|
|
139
|
+
if (maxAttempts === undefined || delayMs === undefined || !errorMessage) {
|
|
140
|
+
return unknownRetryNode(event, base);
|
|
141
|
+
}
|
|
142
|
+
const current = {
|
|
143
|
+
attempt,
|
|
144
|
+
maxAttempts,
|
|
145
|
+
delayMs,
|
|
146
|
+
errorMessage,
|
|
147
|
+
state: "scheduled",
|
|
148
|
+
seq: event.seq,
|
|
149
|
+
at: base.at,
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
...base,
|
|
153
|
+
key,
|
|
154
|
+
kind: "retry",
|
|
155
|
+
location: locationFor(event),
|
|
156
|
+
status: "scheduled",
|
|
157
|
+
data: { attempts: [current], current },
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
case "turn_error":
|
|
161
|
+
return {
|
|
162
|
+
...base,
|
|
163
|
+
key: `turn-error:${event.turnId}`,
|
|
164
|
+
kind: "turn-error",
|
|
165
|
+
location: locationFor(event),
|
|
166
|
+
status: "failed",
|
|
167
|
+
};
|
|
168
|
+
case "turn_max_tokens":
|
|
169
|
+
return {
|
|
170
|
+
...base,
|
|
171
|
+
key: `max-tokens:${event.turnId}:${step ?? 0}`,
|
|
172
|
+
kind: "max-tokens",
|
|
173
|
+
location: locationFor(event),
|
|
174
|
+
status: "completed",
|
|
175
|
+
};
|
|
176
|
+
case "request_context": {
|
|
177
|
+
const requestId = typeof event.data.requestId === "string" && event.data.requestId
|
|
178
|
+
? event.data.requestId
|
|
179
|
+
: event.eventId;
|
|
180
|
+
return {
|
|
181
|
+
...base,
|
|
182
|
+
key: `request:${requestId}`,
|
|
183
|
+
kind: "request",
|
|
184
|
+
location: locationFor(event),
|
|
185
|
+
status: "completed",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
case "command": {
|
|
189
|
+
const commandId = typeof event.data.commandId === "string" && event.data.commandId
|
|
190
|
+
? event.data.commandId
|
|
191
|
+
: event.eventId;
|
|
192
|
+
return {
|
|
193
|
+
...base,
|
|
194
|
+
key: `command:${commandId}`,
|
|
195
|
+
kind: "command",
|
|
196
|
+
location: { kind: "turn", turnId: event.turnId },
|
|
197
|
+
status: status(event.data.outcome) ?? "completed",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
case "unknown":
|
|
201
|
+
return {
|
|
202
|
+
...base,
|
|
203
|
+
key: `unknown:${event.eventId}`,
|
|
204
|
+
kind: "unknown",
|
|
205
|
+
location: locationFor(event),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function conversationNodesFromEvents(events) {
|
|
210
|
+
let nodes = [];
|
|
211
|
+
for (const event of events) {
|
|
212
|
+
const node = conversationNodeFromEvent(event);
|
|
213
|
+
if (node)
|
|
214
|
+
nodes = upsertConversationNode(nodes, node);
|
|
215
|
+
}
|
|
216
|
+
return nodes;
|
|
217
|
+
}
|
|
218
|
+
/** Keyed upsert that preserves the first structural anchor and location. */
|
|
219
|
+
export function upsertConversationNode(nodes, next) {
|
|
220
|
+
if (next.kind === "retry")
|
|
221
|
+
return upsertRetryNode(nodes, next);
|
|
222
|
+
const merged = upsertBase(nodes, next);
|
|
223
|
+
if (next.kind === "step" && next.location.kind === "step") {
|
|
224
|
+
const { step: nextStep, turnId } = next.location;
|
|
225
|
+
if (next.status === "running") {
|
|
226
|
+
return transitionScheduledRetries(merged, (node) => node.location.kind === "step" &&
|
|
227
|
+
node.location.turnId === turnId &&
|
|
228
|
+
node.location.step < nextStep, "started", next.updatedSeq);
|
|
229
|
+
}
|
|
230
|
+
return transitionScheduledRetries(merged, (node) => node.location.kind === "step" &&
|
|
231
|
+
node.location.turnId === turnId &&
|
|
232
|
+
node.location.step === nextStep &&
|
|
233
|
+
node.anchorSeq < next.updatedSeq, "cancelled", next.updatedSeq);
|
|
234
|
+
}
|
|
235
|
+
if (next.kind === "turn-error") {
|
|
236
|
+
return transitionScheduledRetries(merged, (node) => node.location.kind !== "session" &&
|
|
237
|
+
next.location.kind !== "session" &&
|
|
238
|
+
node.location.turnId === next.location.turnId, "cancelled", next.updatedSeq);
|
|
239
|
+
}
|
|
240
|
+
return merged;
|
|
241
|
+
}
|
|
242
|
+
function upsertBase(nodes, next) {
|
|
243
|
+
const index = nodes.findIndex((node) => node.key === next.key);
|
|
244
|
+
if (index < 0) {
|
|
245
|
+
return [...nodes, next].sort((left, right) => left.anchorSeq - right.anchorSeq || left.key.localeCompare(right.key));
|
|
246
|
+
}
|
|
247
|
+
const previous = nodes[index];
|
|
248
|
+
if (next.updatedSeq < previous.updatedSeq)
|
|
249
|
+
return [...nodes];
|
|
250
|
+
const merged = {
|
|
251
|
+
...previous,
|
|
252
|
+
...next,
|
|
253
|
+
anchorSeq: previous.anchorSeq,
|
|
254
|
+
at: previous.at,
|
|
255
|
+
location: previous.location,
|
|
256
|
+
data: { ...previous.data, ...next.data },
|
|
257
|
+
};
|
|
258
|
+
const result = [...nodes];
|
|
259
|
+
result[index] = merged;
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
function retryTurnId(node) {
|
|
263
|
+
return node.location.kind === "session" ? undefined : node.location.turnId;
|
|
264
|
+
}
|
|
265
|
+
function retryCandidateIndex(nodes, next) {
|
|
266
|
+
const exact = nodes.findIndex((node) => node.key === next.key);
|
|
267
|
+
if (exact >= 0)
|
|
268
|
+
return exact;
|
|
269
|
+
const update = retryUpdate(next);
|
|
270
|
+
const nextCurrent = modelRetryCurrent(next);
|
|
271
|
+
const attempt = update?.attempt ?? nextCurrent?.attempt;
|
|
272
|
+
const turnId = retryTurnId(next);
|
|
273
|
+
if (attempt === undefined || !turnId)
|
|
274
|
+
return -1;
|
|
275
|
+
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
276
|
+
const candidate = nodes[index];
|
|
277
|
+
const current = modelRetryCurrent(candidate);
|
|
278
|
+
if (candidate.kind !== "retry" || retryTurnId(candidate) !== turnId || !current)
|
|
279
|
+
continue;
|
|
280
|
+
if (current.attempt === attempt)
|
|
281
|
+
return index;
|
|
282
|
+
if (nextCurrent?.state === "scheduled" && current.attempt + 1 === attempt)
|
|
283
|
+
return index;
|
|
284
|
+
}
|
|
285
|
+
return -1;
|
|
286
|
+
}
|
|
287
|
+
function upsertRetryNode(nodes, next) {
|
|
288
|
+
const index = retryCandidateIndex(nodes, next);
|
|
289
|
+
const nextCurrent = modelRetryCurrent(next);
|
|
290
|
+
const update = retryUpdate(next);
|
|
291
|
+
if (index < 0) {
|
|
292
|
+
if (nextCurrent)
|
|
293
|
+
return upsertBase(nodes, next);
|
|
294
|
+
return upsertBase(nodes, {
|
|
295
|
+
...next,
|
|
296
|
+
key: `unknown:${next.key}:${next.updatedSeq}`,
|
|
297
|
+
kind: "unknown",
|
|
298
|
+
status: undefined,
|
|
299
|
+
data: {
|
|
300
|
+
eventType: "model_retry",
|
|
301
|
+
preview: "Orphan model retry lifecycle update",
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const previous = nodes[index];
|
|
306
|
+
if (next.updatedSeq < previous.updatedSeq)
|
|
307
|
+
return [...nodes];
|
|
308
|
+
let attempts = modelRetryAttempts(previous);
|
|
309
|
+
if (nextCurrent) {
|
|
310
|
+
attempts = attempts.map((attempt) => attempt.state === "scheduled" && attempt.attempt < nextCurrent.attempt
|
|
311
|
+
? { ...attempt, state: "started" }
|
|
312
|
+
: attempt);
|
|
313
|
+
const existing = attempts.findIndex((attempt) => attempt.attempt === nextCurrent.attempt);
|
|
314
|
+
if (existing >= 0)
|
|
315
|
+
attempts[existing] = { ...attempts[existing], ...nextCurrent };
|
|
316
|
+
else
|
|
317
|
+
attempts = [...attempts, nextCurrent];
|
|
318
|
+
}
|
|
319
|
+
else if (update) {
|
|
320
|
+
attempts = attempts.map((attempt) => attempt.attempt === update.attempt
|
|
321
|
+
? { ...attempt, state: update.state }
|
|
322
|
+
: attempt);
|
|
323
|
+
}
|
|
324
|
+
const current = attempts.at(-1);
|
|
325
|
+
if (!current)
|
|
326
|
+
return [...nodes];
|
|
327
|
+
const result = [...nodes];
|
|
328
|
+
result[index] = {
|
|
329
|
+
...previous,
|
|
330
|
+
updatedSeq: next.updatedSeq,
|
|
331
|
+
status: current.state,
|
|
332
|
+
data: { attempts, current },
|
|
333
|
+
};
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
function transitionScheduledRetries(nodes, predicate, state, updatedSeq) {
|
|
337
|
+
return nodes.map((node) => {
|
|
338
|
+
const current = modelRetryCurrent(node);
|
|
339
|
+
if (node.kind !== "retry" || current?.state !== "scheduled" || !predicate(node)) {
|
|
340
|
+
return node;
|
|
341
|
+
}
|
|
342
|
+
const attempts = modelRetryAttempts(node).map((attempt) => attempt.attempt === current.attempt
|
|
343
|
+
? { ...attempt, state }
|
|
344
|
+
: attempt);
|
|
345
|
+
const nextCurrent = attempts.at(-1);
|
|
346
|
+
return {
|
|
347
|
+
...node,
|
|
348
|
+
updatedSeq: Math.max(node.updatedSeq, updatedSeq),
|
|
349
|
+
status: state,
|
|
350
|
+
data: { attempts, current: nextCurrent },
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/** Resolve explicit parent links only. Cycles and excessive chains fail flat. */
|
|
355
|
+
export function toolNestingDepths(tools, maxDepth = 32) {
|
|
356
|
+
const byId = new Map(tools.map((tool) => [tool.id, tool]));
|
|
357
|
+
const depths = new Map();
|
|
358
|
+
const cyclic = new Set();
|
|
359
|
+
const resolve = (id, path) => {
|
|
360
|
+
const cached = depths.get(id);
|
|
361
|
+
if (cached !== undefined)
|
|
362
|
+
return cached;
|
|
363
|
+
const cycleAt = path.indexOf(id);
|
|
364
|
+
if (cycleAt >= 0) {
|
|
365
|
+
for (const key of path.slice(cycleAt))
|
|
366
|
+
cyclic.add(key);
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
if (path.length >= maxDepth)
|
|
370
|
+
return 0;
|
|
371
|
+
const tool = byId.get(id);
|
|
372
|
+
const parent = tool?.parentToolCallId;
|
|
373
|
+
if (!parent || !byId.has(parent) || parent === id)
|
|
374
|
+
return 0;
|
|
375
|
+
const depth = Math.min(maxDepth, resolve(parent, [...path, id]) + 1);
|
|
376
|
+
return cyclic.has(id) ? 0 : depth;
|
|
377
|
+
};
|
|
378
|
+
for (const tool of tools)
|
|
379
|
+
depths.set(tool.id, resolve(tool.id, []));
|
|
380
|
+
for (const id of cyclic)
|
|
381
|
+
depths.set(id, 0);
|
|
382
|
+
return depths;
|
|
383
|
+
}
|
|
@@ -297,6 +297,35 @@ export function createWizardLocalHeadersExtension(options) {
|
|
|
297
297
|
export { THINKING_LEVELS, RuntimeSnapshotUnavailableError, projectRuntimeSnapshot, };
|
|
298
298
|
export { createDefaultPiSdkBindings } from "./pi-runtime/sdk-bindings.js";
|
|
299
299
|
export { extractAssistantContentBlocks, extractAssistantText, isAssistantSdkMessage };
|
|
300
|
+
export function buildRequestContextEvent(input) {
|
|
301
|
+
const systemPrompt = input.session.state?.systemPrompt ?? input.session.systemPrompt ?? "";
|
|
302
|
+
const activeTools = input.session.getActiveToolNames()
|
|
303
|
+
.filter((name) => typeof name === "string" && name.trim())
|
|
304
|
+
.slice(0, 256);
|
|
305
|
+
const provider = input.session.model?.provider;
|
|
306
|
+
const modelId = input.session.model?.modelId ?? input.session.model?.id;
|
|
307
|
+
const sources = ["system", "conversation"];
|
|
308
|
+
if (activeTools.length > 0)
|
|
309
|
+
sources.push("tools");
|
|
310
|
+
if (input.imageCount > 0)
|
|
311
|
+
sources.push("images");
|
|
312
|
+
return {
|
|
313
|
+
type: "request_context",
|
|
314
|
+
sessionId: input.sessionId,
|
|
315
|
+
requestId: `${input.turnId}:request:1`,
|
|
316
|
+
step: 1,
|
|
317
|
+
...(provider && modelId ? { model: { provider, modelId } } : {}),
|
|
318
|
+
...(input.session.thinkingLevel
|
|
319
|
+
? { thinkingLevel: input.session.thinkingLevel }
|
|
320
|
+
: {}),
|
|
321
|
+
systemPromptHash: createHash("sha256").update(systemPrompt).digest("hex"),
|
|
322
|
+
systemPromptChars: systemPrompt.length,
|
|
323
|
+
activeTools,
|
|
324
|
+
inputChars: input.text.length,
|
|
325
|
+
imageCount: input.imageCount,
|
|
326
|
+
sources,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
300
329
|
function readSessionManagerId(manager) {
|
|
301
330
|
if (!manager || typeof manager !== "object")
|
|
302
331
|
return undefined;
|
|
@@ -1977,6 +2006,14 @@ export class ConsolePiRuntime {
|
|
|
1977
2006
|
};
|
|
1978
2007
|
emitContextUsage();
|
|
1979
2008
|
await this.runPrePromptCompaction(sessionId, session, text, options?.images?.length ?? 0, onEvent);
|
|
2009
|
+
onEvent(buildRequestContextEvent({
|
|
2010
|
+
sessionId,
|
|
2011
|
+
turnId: options?.turnId ?? `${sessionId}:active`,
|
|
2012
|
+
session,
|
|
2013
|
+
text,
|
|
2014
|
+
imageCount: options?.images?.length ?? 0,
|
|
2015
|
+
}));
|
|
2016
|
+
let step = 0;
|
|
1980
2017
|
let stepStartedAt;
|
|
1981
2018
|
let firstTokenAt;
|
|
1982
2019
|
let pendingProviderError;
|
|
@@ -1985,12 +2022,24 @@ export class ConsolePiRuntime {
|
|
|
1985
2022
|
const emitPendingProviderError = () => {
|
|
1986
2023
|
if (!pendingProviderError)
|
|
1987
2024
|
return;
|
|
1988
|
-
|
|
2025
|
+
const error = pendingProviderError;
|
|
2026
|
+
onEvent(error);
|
|
2027
|
+
onEvent({
|
|
2028
|
+
type: "turn_error",
|
|
2029
|
+
sessionId,
|
|
2030
|
+
step: Math.max(1, step),
|
|
2031
|
+
...(error.providerError.code === undefined
|
|
2032
|
+
? {}
|
|
2033
|
+
: { code: String(error.providerError.code) }),
|
|
2034
|
+
message: error.message,
|
|
2035
|
+
});
|
|
1989
2036
|
pendingProviderError = undefined;
|
|
1990
2037
|
providerErrorEmitted = true;
|
|
1991
2038
|
};
|
|
1992
2039
|
unsub = session.subscribe((event) => {
|
|
1993
2040
|
const observedAt = Date.now();
|
|
2041
|
+
if (event.type === "turn_start")
|
|
2042
|
+
step += 1;
|
|
1994
2043
|
if (event.type === "message_start" && isUserSdkMessage(event.message)) {
|
|
1995
2044
|
if (!initialUserMessageStarted) {
|
|
1996
2045
|
// The first user message_start belongs to session.prompt(text), not
|
|
@@ -2021,7 +2070,7 @@ export class ConsolePiRuntime {
|
|
|
2021
2070
|
const compactionId = event.type === "compaction_start" || event.type === "compaction_end"
|
|
2022
2071
|
? activeCompaction?.compactionId ?? newCompactionId(sessionId)
|
|
2023
2072
|
: undefined;
|
|
2024
|
-
const mapped = mapSdkEvent(sessionId, event, compactionId);
|
|
2073
|
+
const mapped = mapSdkEvent(sessionId, event, compactionId, Math.max(1, step));
|
|
2025
2074
|
if (event.type === "compaction_end")
|
|
2026
2075
|
activeCompaction = undefined;
|
|
2027
2076
|
if (mapped?.type === "error") {
|
|
@@ -2046,7 +2095,7 @@ export class ConsolePiRuntime {
|
|
|
2046
2095
|
if (mapped?.type === "agent_settled") {
|
|
2047
2096
|
emitPendingProviderError();
|
|
2048
2097
|
}
|
|
2049
|
-
if (mapped?.type === "agent_start") {
|
|
2098
|
+
if (mapped?.type === "step_start" || mapped?.type === "agent_start") {
|
|
2050
2099
|
stepStartedAt = observedAt;
|
|
2051
2100
|
firstTokenAt = undefined;
|
|
2052
2101
|
}
|
|
@@ -2074,6 +2123,13 @@ export class ConsolePiRuntime {
|
|
|
2074
2123
|
: { outputTokens: usage.outputTokens }),
|
|
2075
2124
|
};
|
|
2076
2125
|
onEvent({ ...mapped, ...(timing ? { timing } : {}) });
|
|
2126
|
+
if (mapped.stopReason === "length") {
|
|
2127
|
+
onEvent({
|
|
2128
|
+
type: "turn_max_tokens",
|
|
2129
|
+
sessionId,
|
|
2130
|
+
step: mapped.step ?? Math.max(1, step),
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2077
2133
|
if (usage)
|
|
2078
2134
|
onEvent({ type: "usage", sessionId, usage });
|
|
2079
2135
|
emitContextUsage();
|
|
@@ -2331,7 +2387,7 @@ export class ConsolePiRuntime {
|
|
|
2331
2387
|
this.dbConnections.dispose();
|
|
2332
2388
|
}
|
|
2333
2389
|
}
|
|
2334
|
-
function mapAssistantMessageEvent(type, sessionId, message) {
|
|
2390
|
+
function mapAssistantMessageEvent(type, sessionId, message, step) {
|
|
2335
2391
|
const record = message !== null && typeof message === "object"
|
|
2336
2392
|
? message
|
|
2337
2393
|
: undefined;
|
|
@@ -2355,6 +2411,10 @@ function mapAssistantMessageEvent(type, sessionId, message) {
|
|
|
2355
2411
|
sessionId,
|
|
2356
2412
|
text: extractAssistantText(message),
|
|
2357
2413
|
...(content.length > 0 ? { content } : {}),
|
|
2414
|
+
step,
|
|
2415
|
+
...(type === "message_end" && typeof record?.stopReason === "string"
|
|
2416
|
+
? { stopReason: record.stopReason }
|
|
2417
|
+
: {}),
|
|
2358
2418
|
};
|
|
2359
2419
|
}
|
|
2360
2420
|
function isUserSdkMessage(message) {
|
|
@@ -2362,10 +2422,12 @@ function isUserSdkMessage(message) {
|
|
|
2362
2422
|
typeof message === "object" &&
|
|
2363
2423
|
message.role === "user");
|
|
2364
2424
|
}
|
|
2365
|
-
function mapSdkEvent(sessionId, event, compactionId) {
|
|
2425
|
+
export function mapSdkEvent(sessionId, event, compactionId, step = 1) {
|
|
2366
2426
|
switch (event.type) {
|
|
2367
2427
|
case "agent_start":
|
|
2368
2428
|
return { type: "agent_start", sessionId };
|
|
2429
|
+
case "turn_start":
|
|
2430
|
+
return { type: "step_start", sessionId, step };
|
|
2369
2431
|
case "agent_end":
|
|
2370
2432
|
return {
|
|
2371
2433
|
type: "agent_end",
|
|
@@ -2375,15 +2437,50 @@ function mapSdkEvent(sessionId, event, compactionId) {
|
|
|
2375
2437
|
case "message_update":
|
|
2376
2438
|
if (!isAssistantSdkMessage(event.message))
|
|
2377
2439
|
return undefined;
|
|
2378
|
-
return mapAssistantMessageEvent("message_update", sessionId, event.message);
|
|
2440
|
+
return mapAssistantMessageEvent("message_update", sessionId, event.message, step);
|
|
2379
2441
|
case "message_end":
|
|
2380
2442
|
if (!isAssistantSdkMessage(event.message))
|
|
2381
2443
|
return undefined;
|
|
2382
|
-
return mapAssistantMessageEvent("message_end", sessionId, event.message);
|
|
2383
|
-
case "turn_end":
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2444
|
+
return mapAssistantMessageEvent("message_end", sessionId, event.message, step);
|
|
2445
|
+
case "turn_end": {
|
|
2446
|
+
const record = event.message && typeof event.message === "object"
|
|
2447
|
+
? event.message
|
|
2448
|
+
: undefined;
|
|
2449
|
+
const reason = record?.stopReason;
|
|
2450
|
+
return {
|
|
2451
|
+
type: "step_end",
|
|
2452
|
+
sessionId,
|
|
2453
|
+
step,
|
|
2454
|
+
outcome: reason === "error"
|
|
2455
|
+
? "failed"
|
|
2456
|
+
: reason === "aborted"
|
|
2457
|
+
? "interrupted"
|
|
2458
|
+
: "completed",
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
case "auto_retry_start":
|
|
2462
|
+
return {
|
|
2463
|
+
type: "model_retry",
|
|
2464
|
+
sessionId,
|
|
2465
|
+
step,
|
|
2466
|
+
attempt: event.attempt,
|
|
2467
|
+
maxAttempts: event.maxAttempts,
|
|
2468
|
+
delayMs: event.delayMs,
|
|
2469
|
+
errorMessage: redactRuntimeText(event.errorMessage, 600),
|
|
2470
|
+
state: "scheduled",
|
|
2471
|
+
};
|
|
2472
|
+
case "auto_retry_end":
|
|
2473
|
+
return {
|
|
2474
|
+
type: "model_retry",
|
|
2475
|
+
sessionId,
|
|
2476
|
+
step,
|
|
2477
|
+
attempt: event.attempt,
|
|
2478
|
+
state: event.success ? "completed" : "failed",
|
|
2479
|
+
success: event.success,
|
|
2480
|
+
...(event.finalError
|
|
2481
|
+
? { errorMessage: redactRuntimeText(event.finalError, 600) }
|
|
2482
|
+
: {}),
|
|
2483
|
+
};
|
|
2387
2484
|
case "tool_execution_start":
|
|
2388
2485
|
return {
|
|
2389
2486
|
type: "tool_call",
|
|
@@ -2391,6 +2488,10 @@ function mapSdkEvent(sessionId, event, compactionId) {
|
|
|
2391
2488
|
toolCallId: event.toolCallId,
|
|
2392
2489
|
toolName: event.toolName,
|
|
2393
2490
|
args: event.args,
|
|
2491
|
+
step,
|
|
2492
|
+
...(event.parentToolCallId
|
|
2493
|
+
? { parentToolCallId: event.parentToolCallId }
|
|
2494
|
+
: {}),
|
|
2394
2495
|
};
|
|
2395
2496
|
case "tool_execution_end":
|
|
2396
2497
|
return {
|
|
@@ -2400,6 +2501,10 @@ function mapSdkEvent(sessionId, event, compactionId) {
|
|
|
2400
2501
|
toolName: event.toolName,
|
|
2401
2502
|
result: event.result,
|
|
2402
2503
|
isError: event.isError,
|
|
2504
|
+
step,
|
|
2505
|
+
...(event.parentToolCallId
|
|
2506
|
+
? { parentToolCallId: event.parentToolCallId }
|
|
2507
|
+
: {}),
|
|
2403
2508
|
};
|
|
2404
2509
|
case "agent_settled":
|
|
2405
2510
|
return { type: "agent_settled", sessionId };
|
|
@@ -2446,8 +2551,19 @@ function mapSdkEvent(sessionId, event, compactionId) {
|
|
|
2446
2551
|
: {}),
|
|
2447
2552
|
};
|
|
2448
2553
|
}
|
|
2449
|
-
|
|
2554
|
+
case "message_start":
|
|
2555
|
+
case "queue_update":
|
|
2450
2556
|
return undefined;
|
|
2557
|
+
default:
|
|
2558
|
+
{
|
|
2559
|
+
const unknownEvent = event;
|
|
2560
|
+
return {
|
|
2561
|
+
type: "unknown",
|
|
2562
|
+
sessionId,
|
|
2563
|
+
eventType: String(unknownEvent.type ?? "unknown"),
|
|
2564
|
+
value: unknownEvent,
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2451
2567
|
}
|
|
2452
2568
|
}
|
|
2453
2569
|
function newCompactionId(sessionId) {
|
|
@@ -85,15 +85,3 @@ export function parseProviderError(value) {
|
|
|
85
85
|
category: classify({ message, raw, code }),
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
|
-
export function providerErrorTitle(category) {
|
|
89
|
-
switch (category) {
|
|
90
|
-
case "authentication":
|
|
91
|
-
return "认证失败";
|
|
92
|
-
case "context-overflow":
|
|
93
|
-
return "上下文过大";
|
|
94
|
-
case "rate-limit":
|
|
95
|
-
return "请求过于频繁";
|
|
96
|
-
default:
|
|
97
|
-
return "Provider 请求失败";
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { lstat, open, readdir } from "node:fs/promises";
|
|
4
|
+
import { sameFileIdentity } from "../../../shared/file-identity.js";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { guardReadPath, isSensitivePath, scrubSecrets, } from "./explore-tools.js";
|
|
6
7
|
const TREE_LIMIT = 500;
|
|
@@ -143,8 +144,7 @@ export async function readRepoPreview(repoRoot, rawPath) {
|
|
|
143
144
|
throw new RepoBrowserError(403, guardedCode(rechecked.code), "file path changed while it was being opened");
|
|
144
145
|
const current = await lstat(rechecked.abs);
|
|
145
146
|
if (current.isSymbolicLink() ||
|
|
146
|
-
|
|
147
|
-
current.ino !== info.ino)
|
|
147
|
+
!sameFileIdentity(info, current))
|
|
148
148
|
throw new RepoBrowserError(403, "OUT_OF_REPO", "file identity changed while it was being opened");
|
|
149
149
|
size = info.size;
|
|
150
150
|
const readSize = Math.min(size, TEXT_PREVIEW_LIMIT);
|
|
@@ -204,8 +204,7 @@ export async function readRepoHtmlAsset(repoRoot, rawPath) {
|
|
|
204
204
|
throw new RepoBrowserError(403, guardedCode(rechecked.code), "file path changed while it was being opened");
|
|
205
205
|
const current = await lstat(rechecked.abs);
|
|
206
206
|
if (current.isSymbolicLink() ||
|
|
207
|
-
|
|
208
|
-
current.ino !== info.ino)
|
|
207
|
+
!sameFileIdentity(info, current))
|
|
209
208
|
throw new RepoBrowserError(403, "OUT_OF_REPO", "file identity changed while it was being opened");
|
|
210
209
|
return {
|
|
211
210
|
body: await handle.readFile(),
|