@tea-agent/loop-agent 0.43.0 → 0.44.0-next.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 +226 -146
- package/dist/build-stamp.json +3 -3
- package/dist/executors/pi-sdk-executor.js +29 -1
- package/dist/shared/wizard-local-telemetry.js +50 -0
- package/dist/worker/console/chat/chat-event-store.js +3 -0
- package/dist/worker/console/chat/db-connections.js +758 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
- package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +627 -0
- package/dist/worker/console/chat/pi-runtime.js +66 -6
- package/dist/worker/console/chat/routes.js +154 -3
- package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
- package/dist/worker/console/chat/timeline-message-order.js +22 -0
- package/dist/worker/console/chat/tools.js +34 -0
- package/dist/worker/console/chat/turn-process.js +43 -39
- package/dist/worker/console/dag-confirmation.js +3 -0
- package/dist/worker/console/dag-execution-receipt.js +3 -0
- package/dist/worker/console/operator-actions.js +32 -2
- package/dist/worker/console/server.js +5 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-B8EdIjdl.js} +1 -1
- package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-CUaduV17.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-bYAcxRaf.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-FfhFUEMJ.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-0Rqwn2_U.js} +1 -1
- package/dist/worker/console/static/assets/channel-ICokO4I1.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-D4FbDmsM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-C2nu32ml.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-G2tHSClK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-181nyzuH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-D091VbiQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-BjrMCKTH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-T3zFHTpb.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-CIB7wrYq.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-HwnAsgDw.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CQSdQAqo.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0c0PBcA.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0c0PBcA.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-CyBVLpjP.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-D2Pf3U7m.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-CzXuQ1a2.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-9cMmcOsQ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-DTOr8GLs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-B8saqFHU.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-Bvygp--w.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-CJyLwBhZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CkR1FDRN.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-BsbNtwvi.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-EVZo9AKy.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-C1x91tWN.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-CiNBUsrJ.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-CSd50p3d.js} +1 -1
- package/dist/worker/console/static/assets/index-DHBWEwz_.css +1 -0
- package/dist/worker/console/static/assets/index-RFWQLUE-.js +478 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-1JDiYCuA.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-BZLE6zpm.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-Cg0nDmbe.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-DcwEdKjq.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-CXvVge5x.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-5R4WTYSN.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-By9pSeeq.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DzbVZTqW.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-B9nbfnAq.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-D268jfWH.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-oNOXPRNn.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-DXdr8K-4.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Dtm4uOJ0.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-CeeFiof4.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CK1gARll.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-3jMoAcZw.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cr_kaxrX.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-Div6kwoD.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bl4fQmZh.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-B7P_6Up9.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-eOPaGUOX.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-tAcDnb31.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-Dg_r_785.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +33 -1
- package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +15 -7
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +47 -5
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +24 -2
- package/dist/worker/console/workspace-context.js +1 -0
- package/dist/worker/scheduler/scheduled-goal-evidence.js +60 -2
- package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
- package/package.json +3 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { runtimeSelectionFromRecord } from "../../chat/runtime-selection.js";
|
|
3
3
|
import { hydrateSessionThread } from "../../chat/turn-process.js";
|
|
4
4
|
import { forgetActiveChatSession, readActiveChatSessionId, rememberActiveChatSession, } from "../../chat/workspace-landing.js";
|
|
@@ -179,10 +179,11 @@ function mergeCompactionMessages(messages, compactions) {
|
|
|
179
179
|
* compact, plus browser-refresh recovery from the durable session store. */
|
|
180
180
|
export function useChatSessions(params) {
|
|
181
181
|
const { origin, refs, setSession, thread, composer, browser, setError, setNotice, onCompactionChange, setProcessOpen, setAutoScroll, } = params;
|
|
182
|
-
const { applyActivatedSession, resetForNewSession, setMessages } = thread;
|
|
182
|
+
const { applyActivatedSession, resetForNewSession, setMessages, refreshSessionTelemetry, } = thread;
|
|
183
183
|
const { seedSession, releaseSession } = composer;
|
|
184
184
|
const { setPreviews, setActivePreviewPath } = browser;
|
|
185
185
|
const { workspaceId, blocked } = params;
|
|
186
|
+
const visibleWorkspaceIds = useMemo(() => new Set(params.visibleWorkspaceIds), [params.visibleWorkspaceIds]);
|
|
186
187
|
const workspaceQuery = workspaceId
|
|
187
188
|
? `?workspaceId=${encodeURIComponent(workspaceId)}`
|
|
188
189
|
: "";
|
|
@@ -276,10 +277,13 @@ export function useChatSessions(params) {
|
|
|
276
277
|
return;
|
|
277
278
|
}
|
|
278
279
|
const sessions = body.sessions ?? [];
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
280
|
+
const belongsToVisibleWorkspace = (item) => !item.workspaceId || visibleWorkspaceIds.has(item.workspaceId);
|
|
281
|
+
const durable = sessions.filter((item) => belongsToVisibleWorkspace(item) &&
|
|
282
|
+
!pendingDeletedIdsRef.current.has(item.sessionId));
|
|
283
|
+
const optimistic = [...pendingCreatedRowsRef.current.values()].filter((item) => belongsToVisibleWorkspace(item) &&
|
|
284
|
+
!durable.some((session) => session.sessionId === item.sessionId));
|
|
285
|
+
// Preserve summaries for visible workspaces. ChatWorkspaceSidebar applies
|
|
286
|
+
// the recent-item limit independently inside each workspace group.
|
|
283
287
|
const next = stabilizeSessionList(sessionListRef.current, [...optimistic, ...durable]);
|
|
284
288
|
sessionListRef.current = next;
|
|
285
289
|
setSessionList(next);
|
|
@@ -292,7 +296,7 @@ export function useChatSessions(params) {
|
|
|
292
296
|
return;
|
|
293
297
|
setError(`会话列表加载失败:${error instanceof Error ? error.message : String(error)}`);
|
|
294
298
|
}
|
|
295
|
-
}, [origin, blocked, setError]);
|
|
299
|
+
}, [origin, blocked, setError, visibleWorkspaceIds]);
|
|
296
300
|
const deleteSession = useCallback(async (sessionId) => {
|
|
297
301
|
if (pendingDeletedIdsRef.current.has(sessionId))
|
|
298
302
|
return false;
|
|
@@ -500,6 +504,7 @@ export function useChatSessions(params) {
|
|
|
500
504
|
sessionStats: body.sessionStats,
|
|
501
505
|
usage: body.usage,
|
|
502
506
|
turnTelemetry: body.turnTelemetry,
|
|
507
|
+
sddSessionIdentity: body.sddDataAlignment ?? null,
|
|
503
508
|
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
504
509
|
turnPhase: body.session.turnPhase ??
|
|
505
510
|
(body.session.activeTurnId ? "running" : "idle"),
|
|
@@ -634,6 +639,7 @@ export function useChatSessions(params) {
|
|
|
634
639
|
sessionStats: body.sessionStats,
|
|
635
640
|
usage: body.usage,
|
|
636
641
|
turnTelemetry: body.turnTelemetry,
|
|
642
|
+
sddSessionIdentity: body.sddDataAlignment ?? null,
|
|
637
643
|
streaming: visuallyStopped ? false : Boolean(body.session.activeTurnId),
|
|
638
644
|
turnPhase: body.session.turnPhase ??
|
|
639
645
|
(body.session.activeTurnId ? "running" : "idle"),
|
|
@@ -760,6 +766,7 @@ export function useChatSessions(params) {
|
|
|
760
766
|
setSelectedSession(created);
|
|
761
767
|
rememberActiveChatSession(window.localStorage, created.workspaceId ?? options?.workspaceId ?? workspaceId, created.sessionId);
|
|
762
768
|
setSession(created);
|
|
769
|
+
void refreshSessionTelemetry();
|
|
763
770
|
return created;
|
|
764
771
|
}
|
|
765
772
|
catch (e) {
|
|
@@ -783,6 +790,7 @@ export function useChatSessions(params) {
|
|
|
783
790
|
activateSession,
|
|
784
791
|
blocked,
|
|
785
792
|
workspaceId,
|
|
793
|
+
refreshSessionTelemetry,
|
|
786
794
|
]);
|
|
787
795
|
// Phase-1: fork / in-session branch / mainline switch UI deferred; API routes remain.
|
|
788
796
|
/** Single compact helper for toolbar + `/compact <instructions>` (AC-1). */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect } from "react";
|
|
2
2
|
import { formatOperatorUserError } from "../../operator-user-error.js";
|
|
3
|
+
import { inspectChatSseContinuity, readChatStreamReadyCursor, selectChatSseRepairBlocks, selectLaterChatEventId, } from "./chat-stream-continuity.js";
|
|
3
4
|
import { confirmationToken, requestId, sleep } from "./format.js";
|
|
4
5
|
import { classifyCreateTurnNetworkFailure, classifyCreateTurnResponse, createPendingSubmission, decideSubmissionRoute, isRuntimeBusyUnion, isSubmissionCurrent, isTerminalTurnState, keepAmbiguousAfterRetry, reconcileDeferredSubmissionFromState, reconcilePendingSubmissionFromState, resolveAmbiguousFromState, } from "./turn-submission.js";
|
|
5
6
|
import { applyAbortDecision, applyPendingReconcileDecision, classifyAbortResponse, markStopRequested, operatorChatAbortUrl, operatorChatEventsUrl, operatorChatStateUrl, operatorChatTurnsUrl, releaseOwnershipOnTerminalEvidence, releaseSubmitOwnership as releaseOwnershipBag, requestStopOnOwnership, settleCreateTurnOutcome, turnPhaseFromOwnership, visualTurnPhaseFromOwnership, } from "./turn-stream-controller.js";
|
|
@@ -104,6 +105,7 @@ export function useChatStream(params) {
|
|
|
104
105
|
* means replay cannot render duplicate messages while live text remains
|
|
105
106
|
* smooth (M4.1/M4.3). */
|
|
106
107
|
const openEventsStream = useCallback(async (sessionId, generation, signal) => {
|
|
108
|
+
const gapReplayBuffer = [];
|
|
107
109
|
while (!signal.aborted) {
|
|
108
110
|
const state = refs.eventsStreamStateRef.current;
|
|
109
111
|
if (!state ||
|
|
@@ -129,21 +131,38 @@ export function useChatStream(params) {
|
|
|
129
131
|
const decoder = new TextDecoder();
|
|
130
132
|
let buffer = "";
|
|
131
133
|
let replay = true;
|
|
134
|
+
let replayRepairNeeded = false;
|
|
135
|
+
let liveContinuityCursor = null;
|
|
132
136
|
const fallbackAssistantId = requestId("a");
|
|
133
|
-
for (;;) {
|
|
137
|
+
readLoop: for (;;) {
|
|
134
138
|
const { done, value } = await reader.read();
|
|
135
139
|
if (done)
|
|
136
140
|
break;
|
|
137
141
|
buffer += decoder.decode(value, { stream: true });
|
|
138
142
|
const blocks = buffer.split("\n\n");
|
|
139
143
|
buffer = blocks.pop() ?? "";
|
|
140
|
-
for (
|
|
144
|
+
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex += 1) {
|
|
145
|
+
const block = blocks[blockIndex];
|
|
146
|
+
if (block === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
if (!replay) {
|
|
149
|
+
const continuity = inspectChatSseContinuity(block, liveContinuityCursor);
|
|
150
|
+
if (continuity.action === "reconnect") {
|
|
151
|
+
// Buffer this block and the rest of the decoded chunk before closing
|
|
152
|
+
// the reader. Retained replay repairs structure; the buffer preserves
|
|
153
|
+
// cumulative text frames that persistence may have coalesced away.
|
|
154
|
+
gapReplayBuffer.push(...blocks.slice(blockIndex));
|
|
155
|
+
replayRepairNeeded = true;
|
|
156
|
+
break readLoop;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
141
159
|
if (sseEventName(block) === "stream_ready") {
|
|
142
160
|
const current = refs.eventsStreamStateRef.current;
|
|
143
161
|
if (current?.sessionId === sessionId &&
|
|
144
162
|
current.generation === generation)
|
|
145
163
|
current.ready = true;
|
|
146
164
|
replay = false;
|
|
165
|
+
liveContinuityCursor = readChatStreamReadyCursor(block);
|
|
147
166
|
// Fall through: the applier owns the buffered-replay flush and
|
|
148
167
|
// returns immediately for this event. `continue` here silently
|
|
149
168
|
// dropped that flush, so a reconnect that landed after the
|
|
@@ -155,15 +174,38 @@ export function useChatStream(params) {
|
|
|
155
174
|
targetSessionId: sessionId,
|
|
156
175
|
generation,
|
|
157
176
|
});
|
|
158
|
-
if (eventId)
|
|
159
|
-
refs.lastEventIdRef.current = eventId;
|
|
177
|
+
if (eventId) {
|
|
178
|
+
refs.lastEventIdRef.current = selectLaterChatEventId(refs.lastEventIdRef.current, eventId);
|
|
179
|
+
if (!replay)
|
|
180
|
+
liveContinuityCursor = selectLaterChatEventId(liveContinuityCursor, eventId);
|
|
181
|
+
}
|
|
182
|
+
if (sseEventName(block) === "stream_ready" &&
|
|
183
|
+
gapReplayBuffer.length > 0) {
|
|
184
|
+
const repairedBlocks = selectChatSseRepairBlocks(gapReplayBuffer.splice(0), liveContinuityCursor);
|
|
185
|
+
for (const repairedBlock of repairedBlocks) {
|
|
186
|
+
const repairedEventId = applySseBlock(repairedBlock, refs.assistantIdRef.current ?? fallbackAssistantId, {
|
|
187
|
+
replay: false,
|
|
188
|
+
targetSessionId: sessionId,
|
|
189
|
+
generation,
|
|
190
|
+
});
|
|
191
|
+
if (!repairedEventId)
|
|
192
|
+
continue;
|
|
193
|
+
refs.lastEventIdRef.current = selectLaterChatEventId(refs.lastEventIdRef.current, repairedEventId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
160
196
|
}
|
|
161
197
|
}
|
|
198
|
+
if (replayRepairNeeded) {
|
|
199
|
+
await reader.cancel().catch(() => undefined);
|
|
200
|
+
}
|
|
162
201
|
const current = refs.eventsStreamStateRef.current;
|
|
163
202
|
if (current?.sessionId === sessionId &&
|
|
164
203
|
current.generation === generation)
|
|
165
204
|
current.ready = false;
|
|
166
|
-
|
|
205
|
+
// A detected hole is expected to be immediately recoverable from the
|
|
206
|
+
// durable ring. Other EOFs retain the normal reconnect backoff.
|
|
207
|
+
if (!replayRepairNeeded)
|
|
208
|
+
await sleep(1_000, signal);
|
|
167
209
|
}
|
|
168
210
|
catch {
|
|
169
211
|
if (signal.aborted)
|
|
@@ -140,6 +140,8 @@ export function useChatThread(params) {
|
|
|
140
140
|
const [usage, setUsage] = useState(EMPTY_USAGE);
|
|
141
141
|
const [sessionStats, setSessionStats] = useState(EMPTY_SESSION_STATS);
|
|
142
142
|
const [turnTelemetry, setTurnTelemetry] = useState([]);
|
|
143
|
+
const [operatorActions, setOperatorActions] = useState([]);
|
|
144
|
+
const [sddSessionIdentity, setSddSessionIdentity] = useState(null);
|
|
143
145
|
const [streaming, setStreaming] = useState(false);
|
|
144
146
|
const [turnPhase, setTurnPhase] = useState("idle");
|
|
145
147
|
const [deferredTurn, setDeferredTurn] = useState(null);
|
|
@@ -167,6 +169,7 @@ export function useChatThread(params) {
|
|
|
167
169
|
if (body.usage)
|
|
168
170
|
setUsage(body.usage);
|
|
169
171
|
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
172
|
+
setSddSessionIdentity(body.sddDataAlignment ?? null);
|
|
170
173
|
}
|
|
171
174
|
catch {
|
|
172
175
|
// Telemetry is supplementary; the normal event stream remains authoritative.
|
|
@@ -188,6 +191,8 @@ export function useChatThread(params) {
|
|
|
188
191
|
setPendingQuestion,
|
|
189
192
|
setTodos,
|
|
190
193
|
setGoal,
|
|
194
|
+
setOperatorActions,
|
|
195
|
+
setSddSessionIdentity,
|
|
191
196
|
setStreamingAssistantId,
|
|
192
197
|
setUnread,
|
|
193
198
|
setOperationCards,
|
|
@@ -421,6 +426,8 @@ export function useChatThread(params) {
|
|
|
421
426
|
setUsage(EMPTY_USAGE);
|
|
422
427
|
setSessionStats(EMPTY_SESSION_STATS);
|
|
423
428
|
setTurnTelemetry([]);
|
|
429
|
+
setOperatorActions([]);
|
|
430
|
+
setSddSessionIdentity(null);
|
|
424
431
|
setStreaming(false);
|
|
425
432
|
setTurnPhase("idle");
|
|
426
433
|
setDeferredTurn(null);
|
|
@@ -477,6 +484,8 @@ export function useChatThread(params) {
|
|
|
477
484
|
setUsage(next.usage ?? EMPTY_USAGE);
|
|
478
485
|
setSessionStats(next.sessionStats ?? EMPTY_SESSION_STATS);
|
|
479
486
|
setTurnTelemetry(next.turnTelemetry ?? []);
|
|
487
|
+
setOperatorActions([]);
|
|
488
|
+
setSddSessionIdentity(next.sddSessionIdentity ?? null);
|
|
480
489
|
refs.usageResponseKeysRef.current = new Set();
|
|
481
490
|
setStreaming(visuallyStopped ? false : next.streaming);
|
|
482
491
|
setTurnPhase(visuallyStopped
|
|
@@ -500,13 +509,15 @@ export function useChatThread(params) {
|
|
|
500
509
|
* operation/human-gate/artifact/interview cards, which the reconnected tail
|
|
501
510
|
* stream will not replay (they live in the ring, already consumed). */
|
|
502
511
|
const recoverSession = useCallback(async (targetSessionId) => {
|
|
512
|
+
const targetGeneration = refs.sessionGenerationRef.current;
|
|
503
513
|
try {
|
|
504
514
|
const res = await fetch(`${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(targetSessionId)}`, { credentials: "include" });
|
|
505
515
|
if (!res.ok)
|
|
506
516
|
return false;
|
|
507
517
|
const body = (await res.json());
|
|
508
518
|
if (!body.session ||
|
|
509
|
-
refs.sessionRef.current?.sessionId !== targetSessionId
|
|
519
|
+
refs.sessionRef.current?.sessionId !== targetSessionId ||
|
|
520
|
+
refs.sessionGenerationRef.current !== targetGeneration)
|
|
510
521
|
return false;
|
|
511
522
|
const activeTurnId = body.session.activeTurnId ?? null;
|
|
512
523
|
const visuallyStopped = body.session.turnPhase === "stopping";
|
|
@@ -526,6 +537,7 @@ export function useChatThread(params) {
|
|
|
526
537
|
if (body.usage)
|
|
527
538
|
setUsage(body.usage);
|
|
528
539
|
setTurnTelemetry(body.turnTelemetry ?? []);
|
|
540
|
+
setSddSessionIdentity(body.sddDataAlignment ?? null);
|
|
529
541
|
// Resume the events stream from the snapshot tail so the next
|
|
530
542
|
// reconnect never immediately re-expires (AC-003). The refs below
|
|
531
543
|
// are reset so any follow-up tail replay re-applies cleanly.
|
|
@@ -534,7 +546,13 @@ export function useChatThread(params) {
|
|
|
534
546
|
refs.turnAssistantIdsRef.current = new Map();
|
|
535
547
|
refs.sealedAssistantIdsRef.current = new Set();
|
|
536
548
|
const hydrated = hydrateSessionThread(body.session.messages ?? []);
|
|
537
|
-
|
|
549
|
+
// Recovery is addressed to targetSessionId, so an optimistic
|
|
550
|
+
// submission from another session must never be projected into
|
|
551
|
+
// this snapshot (the activation path enforces the same owner gate).
|
|
552
|
+
const pending = refs.pendingSubmissionRef.current?.sessionId === targetSessionId &&
|
|
553
|
+
refs.pendingSubmissionRef.current.sessionGeneration === targetGeneration
|
|
554
|
+
? refs.pendingSubmissionRef.current
|
|
555
|
+
: undefined;
|
|
538
556
|
const recovered = mergePendingSubmissionOnHydration(hydrated.messages, pending
|
|
539
557
|
? {
|
|
540
558
|
clientRequestId: pending.clientRequestId,
|
|
@@ -599,6 +617,9 @@ export function useChatThread(params) {
|
|
|
599
617
|
usage,
|
|
600
618
|
sessionStats,
|
|
601
619
|
turnTelemetry,
|
|
620
|
+
operatorActions,
|
|
621
|
+
sddSessionIdentity,
|
|
622
|
+
setSddSessionIdentity,
|
|
602
623
|
streaming,
|
|
603
624
|
setStreaming,
|
|
604
625
|
turnPhase,
|
|
@@ -621,6 +642,7 @@ export function useChatThread(params) {
|
|
|
621
642
|
applySseBlock,
|
|
622
643
|
loadTaskContext,
|
|
623
644
|
applyOperationLinkMutation,
|
|
645
|
+
refreshSessionTelemetry,
|
|
624
646
|
resetForNewSession,
|
|
625
647
|
applyActivatedSession,
|
|
626
648
|
recoverSession,
|
|
@@ -265,6 +265,7 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
265
265
|
const aimaxAuth = new AimaxAuthReader();
|
|
266
266
|
await aimaxAuth.getToken();
|
|
267
267
|
const sddDataAlignment = new SddDataAlignmentService(chatStore, aimaxAuth, deps.fetchImpl);
|
|
268
|
+
actionContext.sddDataAlignment = sddDataAlignment;
|
|
268
269
|
const chatEventEpoch = randomBytes(12).toString("hex");
|
|
269
270
|
const chatEvents = createChatEventStore({
|
|
270
271
|
appData,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { execFile } from "node:child_process";
|
|
2
|
+
import { execFile, spawn } from "node:child_process";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { lstat, readFile, readlink, realpath } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
@@ -7,6 +7,59 @@ import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
|
7
7
|
import { scheduledGoalDraftSchema } from "./scheduled-goal-types.js";
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
9
|
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
10
|
+
/** Working-copy paths that are plain files, so Git can read them back. */
|
|
11
|
+
async function plainFileNames(root, names) {
|
|
12
|
+
const plain = [];
|
|
13
|
+
for (const name of names) {
|
|
14
|
+
const info = await lstat(path.resolve(root, name)).catch(() => undefined);
|
|
15
|
+
if (info?.isFile())
|
|
16
|
+
plain.push(name);
|
|
17
|
+
}
|
|
18
|
+
return plain;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Repository-form blob IDs for working-copy files, computed by Git itself so
|
|
22
|
+
* that `core.autocrlf` and `eol` attributes normalize exactly as a commit
|
|
23
|
+
* would. Hashing never consults the index, so staged-only and
|
|
24
|
+
* `assume-unchanged` paths keep their raw-byte meaning. A failure degrades to
|
|
25
|
+
* the caller's raw-byte blob, which can only over-report change, never hide it;
|
|
26
|
+
* `--stdin-paths` cannot carry names containing a newline.
|
|
27
|
+
*/
|
|
28
|
+
async function gitNormalizedBlobIds(root, names) {
|
|
29
|
+
const ids = new Map();
|
|
30
|
+
const candidates = names.filter((name) => !name.includes("\n"));
|
|
31
|
+
if (candidates.length === 0)
|
|
32
|
+
return ids;
|
|
33
|
+
let stdout;
|
|
34
|
+
try {
|
|
35
|
+
stdout = await new Promise((resolve, reject) => {
|
|
36
|
+
const child = spawn("git", ["-C", root, "hash-object", "--stdin-paths"], { stdio: ["pipe", "pipe", "pipe"] });
|
|
37
|
+
let output = "";
|
|
38
|
+
let failure = "";
|
|
39
|
+
child.stdout.setEncoding("utf8");
|
|
40
|
+
child.stdout.on("data", (chunk) => { output += chunk; });
|
|
41
|
+
child.stderr.setEncoding("utf8");
|
|
42
|
+
child.stderr.on("data", (chunk) => { failure += chunk; });
|
|
43
|
+
child.on("error", reject);
|
|
44
|
+
child.on("close", (code) => {
|
|
45
|
+
if (code === 0)
|
|
46
|
+
resolve(output);
|
|
47
|
+
else
|
|
48
|
+
reject(new Error(`git hash-object failed: ${failure.trim()}`));
|
|
49
|
+
});
|
|
50
|
+
child.stdin.end(`${candidates.join("\n")}\n`);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return ids;
|
|
55
|
+
}
|
|
56
|
+
const hashes = stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
57
|
+
if (hashes.length !== candidates.length)
|
|
58
|
+
return ids;
|
|
59
|
+
for (let index = 0; index < candidates.length; index += 1)
|
|
60
|
+
ids.set(candidates[index], hashes[index]);
|
|
61
|
+
return ids;
|
|
62
|
+
}
|
|
10
63
|
/** Run only frozen AC commands. A timeout is uncertain execution, never a passing or repairable failure. */
|
|
11
64
|
export async function evaluateScheduledGoalCriteria(frozen, options = {}) {
|
|
12
65
|
frozen = scheduledGoalDraftSchema.pick({ worktreeRef: true, frozenBase: true, allowedPaths: true, forbiddenPaths: true, acceptance: true, time: true }).strip().parse(frozen);
|
|
@@ -115,6 +168,11 @@ export async function captureScheduledGoalTree(worktree, base, allowedPaths, for
|
|
|
115
168
|
const blobId = (bytes) => createHash(objectFormat).update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
|
|
116
169
|
const names = [...new Set([...entries(tracked), ...entries(untracked), ...baseEntries.keys()])].sort();
|
|
117
170
|
const deliveryNames = new Set(names);
|
|
171
|
+
// Compare content identity through Git's own normalization: a working copy
|
|
172
|
+
// materialized with `core.autocrlf` (or an `eol` attribute) is stored as LF
|
|
173
|
+
// while the checkout is CRLF, and raw bytes would report that as drift on
|
|
174
|
+
// every text file.
|
|
175
|
+
const normalizedIds = await gitNormalizedBlobIds(root, await plainFileNames(root, names));
|
|
118
176
|
const files = [];
|
|
119
177
|
const changedPaths = [];
|
|
120
178
|
for (const name of names) {
|
|
@@ -144,7 +202,7 @@ export async function captureScheduledGoalTree(worktree, base, allowedPaths, for
|
|
|
144
202
|
else if (stat.isFile()) {
|
|
145
203
|
const bytes = await readFile(target);
|
|
146
204
|
files.push({ name, kind: "file", hash: sha256(bytes), executable: Boolean(stat.mode & 0o111) });
|
|
147
|
-
actualId = blobId(bytes);
|
|
205
|
+
actualId = normalizedIds.get(name) ?? blobId(bytes);
|
|
148
206
|
actualMode = stat.mode & 0o111 ? "100755" : "100644";
|
|
149
207
|
}
|
|
150
208
|
else
|
|
@@ -16,6 +16,16 @@ async function syncDirectory(directoryPath) {
|
|
|
16
16
|
try {
|
|
17
17
|
await directory.sync();
|
|
18
18
|
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
// The file body is already durable from the file-level fsync above.
|
|
21
|
+
// Windows refuses to flush a directory handle (EPERM) and some
|
|
22
|
+
// filesystems reject the call outright, so a directory-only failure must
|
|
23
|
+
// not abort a transaction whose data is already on disk.
|
|
24
|
+
const code = error.code;
|
|
25
|
+
if (code !== "EPERM" && code !== "EACCES" && code !== "EINVAL"
|
|
26
|
+
&& code !== "ENOTSUP" && code !== "EOPNOTSUPP" && code !== "ENOSYS")
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
19
29
|
finally {
|
|
20
30
|
await directory.close();
|
|
21
31
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tea-agent/loop-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0-next.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"loop-agent": "bin/loop-agent.js",
|
|
@@ -84,7 +84,9 @@
|
|
|
84
84
|
"commander": "^12.1.0",
|
|
85
85
|
"katex": "^0.16.47",
|
|
86
86
|
"mermaid": "^11.16.1",
|
|
87
|
+
"mysql2": "^3.24.4",
|
|
87
88
|
"node-pty": "^1.2.0-beta.15",
|
|
89
|
+
"pg": "^8.23.0",
|
|
88
90
|
"playwright-core": "^1.63.0",
|
|
89
91
|
"rehype-highlight": "^7.0.2",
|
|
90
92
|
"rehype-katex": "^7.0.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ag as o,ah as n}from"./mermaid.core-7pKqYtpZ.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-CxcKZ9mV.js";import{_ as i}from"./mermaid.core-7pKqYtpZ.js";import"./chunk-5VM5RSS4-XKxoJNJ4.js";import"./chunk-XXDRQBXY-DC_Ds11b.js";import"./chunk-KBJHAD2P-DebTtdFp.js";import"./chunk-2GRJ4B5K-YBHmhik1.js";import"./index-Cdkvw_H6.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-CxcKZ9mV.js";import{_ as i}from"./mermaid.core-7pKqYtpZ.js";import"./chunk-5VM5RSS4-XKxoJNJ4.js";import"./chunk-XXDRQBXY-DC_Ds11b.js";import"./chunk-KBJHAD2P-DebTtdFp.js";import"./chunk-2GRJ4B5K-YBHmhik1.js";import"./index-Cdkvw_H6.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|