@tea-agent/loop-agent 0.44.0-next.5 → 0.44.0-next.7
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 +299 -445
- package/dist/build-stamp.json +3 -3
- package/dist/executors/pi-sdk-executor.js +7 -7
- package/dist/infrastructure/console/operation-store.js +46 -14
- package/dist/shared/git-workspace-status.js +2 -0
- package/dist/shared/wizard-local-telemetry.js +2 -1
- package/dist/worker/console/chat/assistant-content.js +5 -34
- package/dist/worker/console/chat/chat-event-store.js +27 -10
- package/dist/worker/console/chat/db-connections.js +72 -8
- package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +52 -24
- package/dist/worker/console/chat/pi-runtime.js +251 -18
- package/dist/worker/console/chat/repo-browser.js +49 -0
- package/dist/worker/console/chat/routes.js +215 -66
- package/dist/worker/console/chat/session-store.js +147 -19
- package/dist/worker/console/chat/turn-process.js +69 -23
- package/dist/worker/console/chat/user-turn-outline.js +19 -0
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BHuhGM1e.js → abnfDiagram-N423BO3Z-DTuku_-7.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CKh5E_sr.js → arc-CQbCsSqb.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DizhiBEv.js → architectureDiagram-T3A2C74G-C-xUntTR.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-3Neea-Z-.js → blockDiagram-VBNYF7ZC-DhNlwGAl.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCP_Qrgi.js → c4Diagram-5PPSVZJV-B7j3nx8o.js} +1 -1
- package/dist/worker/console/static/assets/channel-5_os7t_x.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-C_wydftK.js → chunk-2GRJ4B5K-BCrcxEh2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-jRPXZbzx.js → chunk-2Q5K7J3B-CsaePDb3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BLQf2OYO.js → chunk-5RXB4S5H--SRQc1rN.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-0qRq64rI.js → chunk-5VM5RSS4-C5BFY2Po.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-ClMg8xmV.js → chunk-6Q2QTUOP-Caso6uN7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Coop2FlL.js → chunk-GF5L2VYU-AY2ZSAx2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CDdO-jPJ.js → chunk-JWPE2WC7-DiMqZQ_8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D8Gkppjl.js → chunk-KBJHAD2P-gpvvriOp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DA9HIySf.js → chunk-RYQCIY6F-CUdyoJhI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DZNDRX1k.js → chunk-XXDRQBXY-Bp1xW3T4.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-loz0n18W.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-loz0n18W.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CZ1zGyjq.js → cose-bilkent-JH36ORCC-D-JIcOIT.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B8u1_FtM.js → cynefin-VYW2F7L2-C-NKFflx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CPBB6lfo.js → cynefinDiagram-MW4NZA55-CSOcvOAA.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CG3Eov3E.js → dagre-VZM6K2ZE-BQSBltMy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ycI-dOjh.js → diagram-7IWD3JNH-B0LVC45G.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bx_GyQ7Y.js → diagram-B4RE2ZJO-Cypg3nqy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-m0G6mP4d.js → diagram-LBJQPF4R-BBDU88XJ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DTDJkqcJ.js → diagram-Q27KOJAE-BZusLqSb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BCfFrG-p.js → diagram-UB23O5K3-Cfolotcr.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BIkTNPpp.js → ebnfDiagram-BXEA7PRR-CjfyUy0m.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DTcMWrd2.js → erDiagram-JOGREHBK-DJdz7txE.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BqzDaNQ7.js → flowDiagram-UKHOOZJN-CQqgPO6y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BSl_vGFe.js → ganttDiagram-PKOTCBZU-BOpGfS2m.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCA5HXM6.js → gitGraphDiagram-DS77QQ5N-C6UCu0qx.js} +1 -1
- package/dist/worker/console/static/assets/index-CUT440A8.css +1 -0
- package/dist/worker/console/static/assets/index-DWKejNVJ.js +486 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DvqT18ge.js → infoDiagram-6WML65LV-BWeaVEUB.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B6zGKrQ3.js → ishikawaDiagram-WSZJBQD7-C8gmlBbk.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BpLQPI9C.js → journeyDiagram-NVQOT4AX-DF3bm8Bp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CzIz_-Lt.js → kanban-definition-27J2QSJJ-D2fG2Vxi.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CMQ8Jpp-.js → linear-BGKgMc2T.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BdAMrTf7.js → mermaid.core-DF6mMPJ9.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-hSwF6sOq.js → mindmap-definition-FAOFIHXS-AMhePheC.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DZdv509B.js → pegDiagram-VL7TDLO6-DN1Cl0W0.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CmD4-b0K.js → pieDiagram-7S7Q4E2Y-Cx9ngUeN.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DtKxNxlS.js → quadrantDiagram-CIZ2JOQS-BcYPTQJb.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-D48iIoiS.js → railroadDiagram-AXF67PYL-BqHUZWnH.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C3N0K-2p.js → requirementDiagram-LRYGKXZP-CmqWZ0-B.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CACWR-Xs.js → sankeyDiagram-W5VNT64P--4YetH1y.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-g_QgugvZ.js → sequenceDiagram-SI44F4Z6-DGD4VMfD.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DqLoRPGK.js → sizeCapture-X5ZJPWSS-QVw9q7rh.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-ES59VgMG.js → stateDiagram-OKZ733FA-CuemV9pe.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BdlGdIJw.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-fjyLVNWo.js → swimlanes-SLNWSIFB-C7oKzZKs.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DjjbHbde.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cd5-biKv.js → timeline-definition-Z64GVDOM-C2Of60nT.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CZTu6mPf.js → vennDiagram-T6HMQDX7-DHtpQHyL.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DR01jjbe.js → wardleyDiagram-T6FBY63Y-COi9OhSy.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-iZcuGVGa.js → xychartDiagram-ELKLHX3M-DPHQfRrt.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +58 -3
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +19 -6
- package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +2 -0
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
- package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +202 -41
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +78 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +55 -18
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
- package/dist/worker/scheduler/scheduled-goal-evidence.js +6 -1
- package/docs/templates/backend-test-dag.json +1 -0
- package/package.json +4 -4
- package/dist/worker/console/static/assets/channel-DxgzA1GF.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BZcm7fs6.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BZcm7fs6.js +0 -1
- package/dist/worker/console/static/assets/index-BAEDjTbD.css +0 -1
- package/dist/worker/console/static/assets/index-wKEJUaP5.js +0 -482
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DyCIq6jx.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CsKtO34q.js +0 -8
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { isSubagentTool } from "../../chat/tool-preview.js";
|
|
2
2
|
/**
|
|
3
3
|
* Count the collapsed-turn disclosure buckets exactly like DSH
|
|
4
|
-
* `TurnProcessNodeView`: tool calls,
|
|
5
|
-
* order. Built-in subagent delegations (`Agent` tool) count as
|
|
6
|
-
* as ordinary tool calls (2026-09-08 alignment).
|
|
4
|
+
* `TurnProcessNodeView`: tool calls, durable assistant replies and subagents,
|
|
5
|
+
* in that order. Built-in subagent delegations (`Agent` tool) count as
|
|
6
|
+
* subagents, not as ordinary tool calls (2026-09-08 alignment).
|
|
7
7
|
*
|
|
8
8
|
* @param items - ordered process nodes of one turn.
|
|
9
9
|
* @returns the three bucket counts.
|
|
@@ -15,17 +15,30 @@ function countEntries(entries) {
|
|
|
15
15
|
let toolCalls = 0;
|
|
16
16
|
let messages = 0;
|
|
17
17
|
let subagents = 0;
|
|
18
|
+
const assistantMessageIds = new Set();
|
|
18
19
|
for (const entry of entries) {
|
|
19
|
-
if (entry.source
|
|
20
|
-
|
|
20
|
+
if (entry.source !== "process")
|
|
21
|
+
continue;
|
|
22
|
+
const item = entry.item;
|
|
23
|
+
if (item.kind === "tool") {
|
|
21
24
|
if (isSubagentTool(item.tool.toolName))
|
|
22
25
|
subagents += 1;
|
|
23
26
|
else
|
|
24
27
|
toolCalls += 1;
|
|
25
28
|
continue;
|
|
26
29
|
}
|
|
27
|
-
|
|
30
|
+
if (item.kind === "activity" || item.kind === "dag-progress") {
|
|
31
|
+
toolCalls += item.toolCallCount ?? 0;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (item.kind !== "text" || item.text.trim() === "")
|
|
35
|
+
continue;
|
|
36
|
+
if (item.assistantMessageId)
|
|
37
|
+
assistantMessageIds.add(item.assistantMessageId);
|
|
38
|
+
else
|
|
39
|
+
messages += 1;
|
|
28
40
|
}
|
|
41
|
+
messages += assistantMessageIds.size;
|
|
29
42
|
return { toolCalls, messages, subagents };
|
|
30
43
|
}
|
|
31
44
|
/** Count only the ordered process window; prelude/post-answer nodes are kept
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const TRANSCRIPT_MODE_STORAGE_KEY = "loop-console.operator-chat.transcript-mode.v1";
|
|
2
|
+
export function parseTranscriptMode(value) {
|
|
3
|
+
return value === "compact" || value === "full" ? value : null;
|
|
4
|
+
}
|
|
5
|
+
export function loadTranscriptMode(storage) {
|
|
6
|
+
try {
|
|
7
|
+
return parseTranscriptMode(storage?.getItem(TRANSCRIPT_MODE_STORAGE_KEY)) ?? "compact";
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return "compact";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function saveTranscriptMode(mode, storage) {
|
|
14
|
+
try {
|
|
15
|
+
storage?.setItem(TRANSCRIPT_MODE_STORAGE_KEY, mode);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Preference storage is optional.
|
|
19
|
+
}
|
|
20
|
+
return mode;
|
|
21
|
+
}
|
|
@@ -7,6 +7,8 @@ function sameReferences(previous, next) {
|
|
|
7
7
|
export function sameTurnGroupInputs(previous, next) {
|
|
8
8
|
return (previous.key === next.key &&
|
|
9
9
|
previous.user === next.user &&
|
|
10
|
+
previous.turnState === next.turnState &&
|
|
11
|
+
previous.turnClosed === next.turnClosed &&
|
|
10
12
|
previous.live === next.live &&
|
|
11
13
|
sameReferences(previous.assistants, next.assistants) &&
|
|
12
14
|
sameReferences(previous.tools, next.tools) &&
|
|
@@ -2,9 +2,17 @@ export function turnProcessDisclosureKey(groupKey, answerId) {
|
|
|
2
2
|
return `${groupKey}:generation-${answerId ?? "none"}`;
|
|
3
3
|
}
|
|
4
4
|
export function isTurnProcessCompact(input) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
input.processItemCount > 0
|
|
9
|
-
|
|
5
|
+
const eligible = !input.historyHasMore &&
|
|
6
|
+
input.turnClosed &&
|
|
7
|
+
input.hasFinalAnswer &&
|
|
8
|
+
input.processItemCount > 0;
|
|
9
|
+
if (!eligible)
|
|
10
|
+
return false;
|
|
11
|
+
if (input.manualOpen)
|
|
12
|
+
return false;
|
|
13
|
+
if (input.manualClosed)
|
|
14
|
+
return true;
|
|
15
|
+
if (input.mode === "full")
|
|
16
|
+
return false;
|
|
17
|
+
return true;
|
|
10
18
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export const TURN_STATE_RECONCILE_DELAYS_MS = [0, 75, 200, 500];
|
|
2
|
+
export const TURN_STATE_QUEUE_RETRY_DELAYS_MS = [0, 75, 200, 500, 2_000];
|
|
3
|
+
const MAX_TURN_STATE_QUERY_BATCH = 8;
|
|
4
|
+
/** Normalize server turn facts. Queued is externally still an open/running Turn. */
|
|
5
|
+
export function normalizeDurableTurnState(state) {
|
|
6
|
+
if (state === "queued" || state === "running")
|
|
7
|
+
return "running";
|
|
8
|
+
if (state === "settled" || state === "aborted" || state === "failed") {
|
|
9
|
+
return state;
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Merge authoritative summaries without deleting older paginated Turn facts. */
|
|
14
|
+
export function mergeTurnStateSummaries(current, summaries) {
|
|
15
|
+
if (!summaries?.length)
|
|
16
|
+
return current;
|
|
17
|
+
let next;
|
|
18
|
+
for (const summary of summaries) {
|
|
19
|
+
if (typeof summary.turnId !== "string" || !summary.turnId)
|
|
20
|
+
continue;
|
|
21
|
+
const state = normalizeDurableTurnState(summary.state);
|
|
22
|
+
const previous = next?.[summary.turnId] ?? current[summary.turnId];
|
|
23
|
+
const merged = mergeDurableTurnState(previous, state);
|
|
24
|
+
if (!merged || previous === merged)
|
|
25
|
+
continue;
|
|
26
|
+
next ??= { ...current };
|
|
27
|
+
next[summary.turnId] = merged;
|
|
28
|
+
}
|
|
29
|
+
return next ?? current;
|
|
30
|
+
}
|
|
31
|
+
export function setProjectedTurnState(current, turnId, state) {
|
|
32
|
+
if (!turnId)
|
|
33
|
+
return current;
|
|
34
|
+
const normalized = normalizeDurableTurnState(state);
|
|
35
|
+
const merged = mergeDurableTurnState(current[turnId], normalized);
|
|
36
|
+
if (!merged || current[turnId] === merged) {
|
|
37
|
+
return current;
|
|
38
|
+
}
|
|
39
|
+
return { ...current, [turnId]: merged };
|
|
40
|
+
}
|
|
41
|
+
function mergeDurableTurnState(current, incoming) {
|
|
42
|
+
if (!incoming)
|
|
43
|
+
return current;
|
|
44
|
+
if (!current)
|
|
45
|
+
return incoming;
|
|
46
|
+
if (current === "failed" || current === "aborted")
|
|
47
|
+
return current;
|
|
48
|
+
if (current === "settled" && incoming === "running")
|
|
49
|
+
return current;
|
|
50
|
+
return incoming;
|
|
51
|
+
}
|
|
52
|
+
export function turnStateReconcileTrigger(replay, eventName) {
|
|
53
|
+
if (eventName !== "agent_settled")
|
|
54
|
+
return undefined;
|
|
55
|
+
return replay ? "after-replay" : "immediate";
|
|
56
|
+
}
|
|
57
|
+
export async function reconcileTurnStateWithBackoff(input) {
|
|
58
|
+
const delays = input.delaysMs ?? TURN_STATE_RECONCILE_DELAYS_MS;
|
|
59
|
+
const wait = input.wait ??
|
|
60
|
+
((delayMs) => new Promise((resolve) => globalThis.setTimeout(resolve, delayMs)));
|
|
61
|
+
for (const delayMs of delays) {
|
|
62
|
+
if (delayMs > 0)
|
|
63
|
+
await wait(delayMs);
|
|
64
|
+
if (input.isCancelled?.())
|
|
65
|
+
return "cancelled";
|
|
66
|
+
let snapshot = null;
|
|
67
|
+
try {
|
|
68
|
+
snapshot = await input.read();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// A transport failure consumes one bounded attempt; later attempts or an
|
|
72
|
+
// SSE reconnect retain ownership of the pending reconciliation.
|
|
73
|
+
}
|
|
74
|
+
if (input.isCancelled?.())
|
|
75
|
+
return "cancelled";
|
|
76
|
+
if (!snapshot?.turnStates)
|
|
77
|
+
continue;
|
|
78
|
+
input.apply(snapshot.turnStates);
|
|
79
|
+
const state = snapshot.turnStates
|
|
80
|
+
.filter((summary) => summary.turnId === input.turnId)
|
|
81
|
+
.map((summary) => normalizeDurableTurnState(summary.state))
|
|
82
|
+
.find((candidate) => candidate !== undefined);
|
|
83
|
+
if (state && state !== "running")
|
|
84
|
+
return "terminal";
|
|
85
|
+
}
|
|
86
|
+
return "pending";
|
|
87
|
+
}
|
|
88
|
+
/** Serialize targeted Turn-state reads, batching replay work and retaining
|
|
89
|
+
* unresolved Turns until durable terminal truth arrives or the owner ends. */
|
|
90
|
+
export function createTurnStateReconcileQueue(input) {
|
|
91
|
+
const pending = new Set();
|
|
92
|
+
const wait = input.wait ??
|
|
93
|
+
((delayMs) => new Promise((resolve) => globalThis.setTimeout(resolve, delayMs)));
|
|
94
|
+
const retryDelays = input.retryDelaysMs?.length
|
|
95
|
+
? input.retryDelaysMs
|
|
96
|
+
: TURN_STATE_QUEUE_RETRY_DELAYS_MS;
|
|
97
|
+
let worker = null;
|
|
98
|
+
const run = async () => {
|
|
99
|
+
let retryIndex = 0;
|
|
100
|
+
while (pending.size > 0 && !input.isCancelled?.()) {
|
|
101
|
+
const delayMs = retryDelays[Math.min(retryIndex, retryDelays.length - 1)] ?? 0;
|
|
102
|
+
if (delayMs > 0) {
|
|
103
|
+
try {
|
|
104
|
+
await wait(delayMs);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
if (input.isCancelled?.())
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (input.isCancelled?.())
|
|
112
|
+
return;
|
|
113
|
+
const batch = [...pending].slice(0, MAX_TURN_STATE_QUERY_BATCH);
|
|
114
|
+
let snapshot = null;
|
|
115
|
+
try {
|
|
116
|
+
snapshot = await input.read(batch);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// The queue owns retry cadence; a transient read failure keeps the batch pending.
|
|
120
|
+
}
|
|
121
|
+
if (input.isCancelled?.())
|
|
122
|
+
return;
|
|
123
|
+
const summaries = snapshot?.turnStates;
|
|
124
|
+
if (summaries?.length)
|
|
125
|
+
input.apply(summaries);
|
|
126
|
+
let completed = 0;
|
|
127
|
+
for (const turnId of batch) {
|
|
128
|
+
const state = summaries
|
|
129
|
+
?.filter((summary) => summary.turnId === turnId)
|
|
130
|
+
.map((summary) => normalizeDurableTurnState(summary.state))
|
|
131
|
+
.find((candidate) => candidate !== undefined);
|
|
132
|
+
if (state && state !== "running") {
|
|
133
|
+
pending.delete(turnId);
|
|
134
|
+
completed += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Rotate unresolved ids so batches larger than the server query cap cannot starve.
|
|
138
|
+
pending.delete(turnId);
|
|
139
|
+
pending.add(turnId);
|
|
140
|
+
}
|
|
141
|
+
retryIndex = completed > 0 ? 0 : retryIndex + 1;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const start = () => {
|
|
145
|
+
if (worker)
|
|
146
|
+
return worker;
|
|
147
|
+
if (pending.size === 0 || input.isCancelled?.())
|
|
148
|
+
return Promise.resolve();
|
|
149
|
+
worker = run().finally(() => {
|
|
150
|
+
worker = null;
|
|
151
|
+
if (pending.size > 0 && !input.isCancelled?.())
|
|
152
|
+
void start();
|
|
153
|
+
});
|
|
154
|
+
return worker;
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
enqueue: (turnId, options) => {
|
|
158
|
+
if (!turnId || input.isCancelled?.())
|
|
159
|
+
return;
|
|
160
|
+
pending.add(turnId);
|
|
161
|
+
if (!options?.defer)
|
|
162
|
+
void start();
|
|
163
|
+
},
|
|
164
|
+
start,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -19,11 +19,65 @@ export const OPERATOR_CHAT_REAL_PATHS = {
|
|
|
19
19
|
events: "/api/operator/v1/chat/sessions/:sessionId/events",
|
|
20
20
|
abort: "/api/operator/v1/chat/sessions/:sessionId/turns/:turnId/abort",
|
|
21
21
|
};
|
|
22
|
+
const MAX_STATE_TURN_IDS = 8;
|
|
23
|
+
const MAX_STATE_TURN_ID_CHARS = 240;
|
|
24
|
+
function boundedStateTurnIds(turnIds) {
|
|
25
|
+
const result = [];
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
for (const raw of turnIds ?? []) {
|
|
28
|
+
const turnId = raw.trim();
|
|
29
|
+
if (!turnId ||
|
|
30
|
+
turnId.length > MAX_STATE_TURN_ID_CHARS ||
|
|
31
|
+
seen.has(turnId)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
seen.add(turnId);
|
|
35
|
+
result.push(turnId);
|
|
36
|
+
if (result.length >= MAX_STATE_TURN_IDS)
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
22
41
|
export function operatorChatTurnsUrl(origin, sessionId) {
|
|
23
42
|
return `${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/turns`;
|
|
24
43
|
}
|
|
25
|
-
export function operatorChatStateUrl(origin, sessionId) {
|
|
26
|
-
|
|
44
|
+
export function operatorChatStateUrl(origin, sessionId, turnIds) {
|
|
45
|
+
const base = `${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/state`;
|
|
46
|
+
const bounded = boundedStateTurnIds(turnIds);
|
|
47
|
+
if (bounded.length === 0)
|
|
48
|
+
return base;
|
|
49
|
+
const query = new URLSearchParams();
|
|
50
|
+
for (const turnId of bounded)
|
|
51
|
+
query.append("turnId", turnId);
|
|
52
|
+
return `${base}?${query.toString()}`;
|
|
53
|
+
}
|
|
54
|
+
/** Compose session ownership with a short request timeout and explicit cleanup. */
|
|
55
|
+
export function createBoundedOwnerSignal(input) {
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const abortFromOwner = () => controller.abort(input.ownerSignal?.reason);
|
|
58
|
+
if (input.ownerSignal?.aborted)
|
|
59
|
+
abortFromOwner();
|
|
60
|
+
else
|
|
61
|
+
input.ownerSignal?.addEventListener("abort", abortFromOwner, { once: true });
|
|
62
|
+
const schedule = input.schedule ??
|
|
63
|
+
((callback, delayMs) => globalThis.setTimeout(callback, delayMs));
|
|
64
|
+
const cancel = input.cancel ??
|
|
65
|
+
((handle) => globalThis.clearTimeout(handle));
|
|
66
|
+
const timer = controller.signal.aborted
|
|
67
|
+
? undefined
|
|
68
|
+
: schedule(() => controller.abort(new Error("Operator Chat state request timed out")), input.timeoutMs);
|
|
69
|
+
let disposed = false;
|
|
70
|
+
return {
|
|
71
|
+
signal: controller.signal,
|
|
72
|
+
dispose: () => {
|
|
73
|
+
if (disposed)
|
|
74
|
+
return;
|
|
75
|
+
disposed = true;
|
|
76
|
+
if (timer !== undefined)
|
|
77
|
+
cancel(timer);
|
|
78
|
+
input.ownerSignal?.removeEventListener("abort", abortFromOwner);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
27
81
|
}
|
|
28
82
|
export function operatorChatEventsUrl(origin, sessionId) {
|
|
29
83
|
return `${origin}/api/operator/v1/chat/sessions/${encodeURIComponent(sessionId)}/events`;
|