@tangle-network/agent-app 0.43.46 → 0.43.47
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/dist/chat-routes/index.d.ts +14 -157
- package/dist/chat-routes/index.js +5 -93
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chunk-BQV42AFK.js +99 -0
- package/dist/chunk-BQV42AFK.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/stale-turn-lock-C8Na1cFZ.d.ts +149 -0
- package/dist/stream/index.d.ts +2 -146
- package/dist/teams/index.js +9 -9
- package/dist/teams/invitations-api.js +7 -7
- package/dist/turn-buffer-C9mEgoop.d.ts +146 -0
- package/dist/turn-stream/index.d.ts +551 -0
- package/dist/turn-stream/index.js +837 -0
- package/dist/turn-stream/index.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
import {
|
|
2
|
+
reconcileStaleTurnLock
|
|
3
|
+
} from "../chunk-BQV42AFK.js";
|
|
4
|
+
|
|
5
|
+
// src/turn-stream/core.ts
|
|
6
|
+
function isTerminalRunEvent(type) {
|
|
7
|
+
return type === "session.run.completed" || type === "session.run.failed";
|
|
8
|
+
}
|
|
9
|
+
function threadChannelKey(workspaceId, threadId) {
|
|
10
|
+
return `${workspaceId}:${threadId}`;
|
|
11
|
+
}
|
|
12
|
+
function workspaceChannelKey(workspaceId) {
|
|
13
|
+
return workspaceId;
|
|
14
|
+
}
|
|
15
|
+
function turnLockChannelKey(workspaceId, threadId, scope) {
|
|
16
|
+
return scope === "workspace" ? workspaceChannelKey(workspaceId) : threadChannelKey(workspaceId, threadId);
|
|
17
|
+
}
|
|
18
|
+
function turnStorageChannelKey(turnId) {
|
|
19
|
+
return `turn:${turnId}`;
|
|
20
|
+
}
|
|
21
|
+
function scopeIndexChannelKey(scopeId) {
|
|
22
|
+
return `scope:${scopeId}`;
|
|
23
|
+
}
|
|
24
|
+
var MAX_SEGMENT_EVENTS = 2e3;
|
|
25
|
+
var MAX_RECENT_CREATED = 50;
|
|
26
|
+
var ACTIVITY_TTL_MS = 15 * 60 * 1e3;
|
|
27
|
+
function createSegmentStore() {
|
|
28
|
+
return { segments: /* @__PURE__ */ new Map(), activeExecutionId: null };
|
|
29
|
+
}
|
|
30
|
+
function appendSegmentEvent(store, executionId, incoming, maxEvents = MAX_SEGMENT_EVENTS) {
|
|
31
|
+
let segment = store.segments.get(executionId);
|
|
32
|
+
if (incoming.type === "session.run.started" || !segment) {
|
|
33
|
+
if (!segment) {
|
|
34
|
+
segment = { events: [], maxSeq: 0, terminal: false };
|
|
35
|
+
store.segments.set(executionId, segment);
|
|
36
|
+
}
|
|
37
|
+
store.activeExecutionId = executionId;
|
|
38
|
+
for (const id of store.segments.keys()) {
|
|
39
|
+
if (id !== executionId) store.segments.delete(id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const seq = ++segment.maxSeq;
|
|
43
|
+
const stamped = { ...incoming, seq };
|
|
44
|
+
segment.events.push(stamped);
|
|
45
|
+
if (segment.events.length > maxEvents) {
|
|
46
|
+
segment.events = segment.events.slice(-maxEvents);
|
|
47
|
+
}
|
|
48
|
+
if (isTerminalRunEvent(incoming.type)) {
|
|
49
|
+
segment.terminal = true;
|
|
50
|
+
}
|
|
51
|
+
return stamped;
|
|
52
|
+
}
|
|
53
|
+
function replayActiveSegment(store, afterSeq) {
|
|
54
|
+
const segment = store.activeExecutionId ? store.segments.get(store.activeExecutionId) : void 0;
|
|
55
|
+
if (!segment || segment.terminal) return [];
|
|
56
|
+
return segment.events.filter((event) => (event.seq ?? 0) > afterSeq);
|
|
57
|
+
}
|
|
58
|
+
function pruneStaleThreads(active, now, ttlMs) {
|
|
59
|
+
const removed = [];
|
|
60
|
+
for (const [threadId, startedAt] of active) {
|
|
61
|
+
if (now - startedAt > ttlMs) {
|
|
62
|
+
active.delete(threadId);
|
|
63
|
+
removed.push(threadId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return removed;
|
|
67
|
+
}
|
|
68
|
+
var TURN_LOCK_TTL_MS = 30 * 60 * 1e3;
|
|
69
|
+
function activeTurnLock(stored, now) {
|
|
70
|
+
if (!stored) return null;
|
|
71
|
+
const lock = stored.scope ? stored : { ...stored, scope: "thread" };
|
|
72
|
+
return lock.expiresAt > now ? lock : null;
|
|
73
|
+
}
|
|
74
|
+
function createTurnLock(input, now, ttlMs = TURN_LOCK_TTL_MS) {
|
|
75
|
+
return {
|
|
76
|
+
workspaceId: input.workspaceId,
|
|
77
|
+
threadId: input.threadId,
|
|
78
|
+
scope: input.scope,
|
|
79
|
+
executionId: input.executionId,
|
|
80
|
+
lockId: input.lockId,
|
|
81
|
+
startedAt: now,
|
|
82
|
+
expiresAt: now + ttlMs,
|
|
83
|
+
...input.turnId ? { turnId: input.turnId } : {}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function turnLockMatchesRelease(active, input) {
|
|
87
|
+
if (active.executionId !== input.executionId) return false;
|
|
88
|
+
if (input.lockId && active.lockId !== input.lockId) return false;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
function interruptedReleaseApplies(active, input) {
|
|
92
|
+
if (active.threadId !== input.threadId) return false;
|
|
93
|
+
if (active.startedAt > input.interruptedAt) return false;
|
|
94
|
+
if (active.turnId) {
|
|
95
|
+
if (active.turnId !== input.turnId) return false;
|
|
96
|
+
} else if (input.turnId) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
var TURN_STREAM_PATHS = {
|
|
102
|
+
broadcast: "/broadcast",
|
|
103
|
+
lockAcquire: "/chat-turn-lock/acquire",
|
|
104
|
+
lockRelease: "/chat-turn-lock/release",
|
|
105
|
+
lockReleaseInterrupted: "/chat-turn-lock/release-interrupted",
|
|
106
|
+
turnEventsAppend: "/turn-events/append",
|
|
107
|
+
turnEventsRead: "/turn-events/read",
|
|
108
|
+
turnStatusSet: "/turn-status/set",
|
|
109
|
+
turnStatusGet: "/turn-status/get",
|
|
110
|
+
scopeStatusSet: "/turn-scope/set",
|
|
111
|
+
scopeRunningList: "/turn-scope/running"
|
|
112
|
+
};
|
|
113
|
+
var TURN_STREAM_STORAGE_KEYS = {
|
|
114
|
+
lock: "chatTurnLock",
|
|
115
|
+
activeThreads: "activeThreads",
|
|
116
|
+
turnStatus: "turnStatus",
|
|
117
|
+
turnScope: "turnScopeIndex",
|
|
118
|
+
turnEventPrefix: "turnEvent:"
|
|
119
|
+
};
|
|
120
|
+
function turnEventStorageKey(seq) {
|
|
121
|
+
return `${TURN_STREAM_STORAGE_KEYS.turnEventPrefix}${String(seq).padStart(10, "0")}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/turn-stream/do.ts
|
|
125
|
+
function jsonResponse(body, status = 200) {
|
|
126
|
+
return new Response(JSON.stringify(body), {
|
|
127
|
+
status,
|
|
128
|
+
headers: { "Content-Type": "application/json" }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function stringValue(value) {
|
|
132
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
133
|
+
}
|
|
134
|
+
async function jsonBody(request) {
|
|
135
|
+
const body = await request.json().catch(() => null);
|
|
136
|
+
return body && typeof body === "object" && !Array.isArray(body) ? body : null;
|
|
137
|
+
}
|
|
138
|
+
var MAX_SCOPE_TURNS = 100;
|
|
139
|
+
var TurnStreamDO = class {
|
|
140
|
+
state;
|
|
141
|
+
env;
|
|
142
|
+
options;
|
|
143
|
+
// Thread channel: per-turn segments; only the active one is replayed.
|
|
144
|
+
segments = createSegmentStore();
|
|
145
|
+
// Workspace channel: recent thread.created markers (in-memory, best-effort)
|
|
146
|
+
// + durable responding set (threadId → startedAt) that survives eviction.
|
|
147
|
+
recentCreated = [];
|
|
148
|
+
activeThreads = null;
|
|
149
|
+
constructor(state, env, options = {}) {
|
|
150
|
+
this.state = state;
|
|
151
|
+
this.env = env;
|
|
152
|
+
this.options = options;
|
|
153
|
+
}
|
|
154
|
+
async fetch(request) {
|
|
155
|
+
const url = new URL(request.url);
|
|
156
|
+
if (request.headers.get("Upgrade") === "websocket") {
|
|
157
|
+
return this.handleWebSocketUpgrade(request, url);
|
|
158
|
+
}
|
|
159
|
+
if (request.method === "POST") {
|
|
160
|
+
switch (url.pathname) {
|
|
161
|
+
case TURN_STREAM_PATHS.broadcast:
|
|
162
|
+
return this.handleBroadcast(request);
|
|
163
|
+
case TURN_STREAM_PATHS.lockAcquire:
|
|
164
|
+
return this.handleLockAcquire(request);
|
|
165
|
+
case TURN_STREAM_PATHS.lockRelease:
|
|
166
|
+
return this.handleLockRelease(request);
|
|
167
|
+
case TURN_STREAM_PATHS.lockReleaseInterrupted:
|
|
168
|
+
return this.handleLockReleaseInterrupted(request);
|
|
169
|
+
case TURN_STREAM_PATHS.turnEventsAppend:
|
|
170
|
+
return this.handleTurnEventsAppend(request);
|
|
171
|
+
case TURN_STREAM_PATHS.turnEventsRead:
|
|
172
|
+
return this.handleTurnEventsRead(request);
|
|
173
|
+
case TURN_STREAM_PATHS.turnStatusSet:
|
|
174
|
+
return this.handleTurnStatusSet(request);
|
|
175
|
+
case TURN_STREAM_PATHS.turnStatusGet:
|
|
176
|
+
return this.handleTurnStatusGet();
|
|
177
|
+
case TURN_STREAM_PATHS.scopeStatusSet:
|
|
178
|
+
return this.handleScopeStatusSet(request);
|
|
179
|
+
case TURN_STREAM_PATHS.scopeRunningList:
|
|
180
|
+
return this.handleScopeRunningList();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const product = await this.handleProductRequest(request, url);
|
|
184
|
+
if (product) return product;
|
|
185
|
+
return request.method === "POST" ? jsonResponse({ error: `Unknown turn-stream endpoint ${url.pathname}` }, 404) : new Response("Method not allowed", { status: 405 });
|
|
186
|
+
}
|
|
187
|
+
// ── product extension seam ────────────────────────────────────────────────
|
|
188
|
+
/** Called for any request no base endpoint claimed (before the 404), so a
|
|
189
|
+
* subclass adds product endpoints without touching base routing. Return
|
|
190
|
+
* `null` to decline. */
|
|
191
|
+
async handleProductRequest(_request, _url) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
/** Consulted before any lock release (cooperative, interrupted, or the
|
|
195
|
+
* terminal-event auto-release). Return `true` while a product-owned
|
|
196
|
+
* post-turn task for `executionId` must keep the scope serialized (e.g.
|
|
197
|
+
* file persistence still reading the box) — the release is then parked as
|
|
198
|
+
* `releasePending` on the lock and completed via
|
|
199
|
+
* {@link completeDeferredLockRelease}. Base: never defer. */
|
|
200
|
+
async shouldDeferLockRelease(_executionId) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
/** Complete a release parked by {@link shouldDeferLockRelease}. A subclass
|
|
204
|
+
* calls this when its deferred condition settles. */
|
|
205
|
+
async completeDeferredLockRelease(executionId) {
|
|
206
|
+
const active = await this.loadActiveLock();
|
|
207
|
+
if (!active?.releasePending || active.executionId !== executionId) return false;
|
|
208
|
+
await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
/** Extra product state replayed to a socket during its `sync`, after the
|
|
212
|
+
* base replay for its scope (e.g. an in-flight persistence status card).
|
|
213
|
+
* Base: none. */
|
|
214
|
+
async productSyncEvents(_scope, _meta) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
// ── WebSocket channel ─────────────────────────────────────────────────────
|
|
218
|
+
handleWebSocketUpgrade(_request, url) {
|
|
219
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
220
|
+
if (!sessionId) return new Response("Missing sessionId", { status: 400 });
|
|
221
|
+
const scope = url.searchParams.get("scope") === "workspace" ? "workspace" : "thread";
|
|
222
|
+
const PairCtor = globalThis.WebSocketPair;
|
|
223
|
+
if (!PairCtor) {
|
|
224
|
+
return new Response("WebSocket upgrades require the Cloudflare runtime", { status: 501 });
|
|
225
|
+
}
|
|
226
|
+
const pair = new PairCtor();
|
|
227
|
+
const client = pair[0];
|
|
228
|
+
const server = pair[1];
|
|
229
|
+
this.state.acceptWebSocket(server);
|
|
230
|
+
server.serializeAttachment({ sessionId, scope, synced: false });
|
|
231
|
+
return new Response(null, { status: 101, webSocket: client });
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* First (and only) client message after open: `{ type: 'sync', afterSeq }`.
|
|
235
|
+
* Replays the current state for the socket's scope, then marks it `synced`
|
|
236
|
+
* so live broadcasts start flowing. Because the DO is single-threaded, the
|
|
237
|
+
* replay snapshot and the synced flip are atomic w.r.t. broadcasts — every
|
|
238
|
+
* event reaches the socket exactly once, in order, via replay XOR live
|
|
239
|
+
* fan-out.
|
|
240
|
+
*/
|
|
241
|
+
async webSocketMessage(ws, message) {
|
|
242
|
+
const meta = ws.deserializeAttachment();
|
|
243
|
+
if (!meta || meta.synced) return;
|
|
244
|
+
let afterSeq = 0;
|
|
245
|
+
try {
|
|
246
|
+
const parsed = JSON.parse(typeof message === "string" ? message : "");
|
|
247
|
+
if (parsed.type !== "sync") return;
|
|
248
|
+
afterSeq = typeof parsed.afterSeq === "number" ? parsed.afterSeq : 0;
|
|
249
|
+
} catch {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (meta.scope === "workspace") {
|
|
253
|
+
const active = await this.loadActiveThreads();
|
|
254
|
+
const removed = pruneStaleThreads(active, Date.now(), this.options.activityTtlMs ?? ACTIVITY_TTL_MS);
|
|
255
|
+
if (removed.length > 0) await this.persistActiveThreads(active);
|
|
256
|
+
for (const [threadId, startedAt] of active) {
|
|
257
|
+
this.trySend(ws, {
|
|
258
|
+
type: "thread.activity",
|
|
259
|
+
data: { threadId, phase: "start", sessionId: meta.sessionId },
|
|
260
|
+
timestamp: startedAt
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
for (const event of this.recentCreated) this.trySend(ws, event);
|
|
264
|
+
} else {
|
|
265
|
+
for (const event of replayActiveSegment(this.segments, afterSeq)) {
|
|
266
|
+
this.trySend(ws, event);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const event of await this.productSyncEvents(meta.scope, { sessionId: meta.sessionId })) {
|
|
270
|
+
this.trySend(ws, event);
|
|
271
|
+
}
|
|
272
|
+
ws.serializeAttachment({ ...meta, synced: true });
|
|
273
|
+
}
|
|
274
|
+
webSocketClose(ws, code, reason) {
|
|
275
|
+
try {
|
|
276
|
+
ws.close(code, reason);
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
webSocketError() {
|
|
281
|
+
}
|
|
282
|
+
trySend(ws, event) {
|
|
283
|
+
try {
|
|
284
|
+
ws.send(JSON.stringify(event));
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// ── broadcast (live fanout + segments + activity) ─────────────────────────
|
|
289
|
+
async handleBroadcast(request) {
|
|
290
|
+
const incoming = await request.json();
|
|
291
|
+
const data = incoming.data ?? {};
|
|
292
|
+
const sessionId = typeof data.sessionId === "string" ? data.sessionId : void 0;
|
|
293
|
+
let outgoing = incoming;
|
|
294
|
+
if (incoming.type === "thread.activity") {
|
|
295
|
+
const threadId = typeof data.threadId === "string" ? data.threadId : void 0;
|
|
296
|
+
if (threadId) {
|
|
297
|
+
const active = await this.loadActiveThreads();
|
|
298
|
+
if (data.phase === "end") active.delete(threadId);
|
|
299
|
+
else active.set(threadId, Date.now());
|
|
300
|
+
await this.persistActiveThreads(active);
|
|
301
|
+
}
|
|
302
|
+
} else if (incoming.type === "thread.created") {
|
|
303
|
+
this.recentCreated.push(incoming);
|
|
304
|
+
if (this.recentCreated.length > MAX_RECENT_CREATED) {
|
|
305
|
+
this.recentCreated = this.recentCreated.slice(-MAX_RECENT_CREATED);
|
|
306
|
+
}
|
|
307
|
+
} else if (typeof data.executionId === "string") {
|
|
308
|
+
outgoing = appendSegmentEvent(
|
|
309
|
+
this.segments,
|
|
310
|
+
data.executionId,
|
|
311
|
+
incoming,
|
|
312
|
+
this.options.maxSegmentEvents ?? MAX_SEGMENT_EVENTS
|
|
313
|
+
);
|
|
314
|
+
if (isTerminalRunEvent(incoming.type)) {
|
|
315
|
+
if (await this.shouldDeferLockRelease(data.executionId)) {
|
|
316
|
+
await this.deferLockRelease({ executionId: data.executionId });
|
|
317
|
+
} else {
|
|
318
|
+
await this.releaseActiveLock({ executionId: data.executionId });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const message = JSON.stringify(outgoing);
|
|
323
|
+
for (const ws of this.state.getWebSockets()) {
|
|
324
|
+
const meta = ws.deserializeAttachment();
|
|
325
|
+
if (!meta?.synced) continue;
|
|
326
|
+
if (!sessionId || meta.sessionId === sessionId) {
|
|
327
|
+
try {
|
|
328
|
+
ws.send(message);
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return new Response("OK", { status: 200 });
|
|
334
|
+
}
|
|
335
|
+
async loadActiveThreads() {
|
|
336
|
+
if (this.activeThreads === null) {
|
|
337
|
+
const stored = await this.state.storage.get(TURN_STREAM_STORAGE_KEYS.activeThreads);
|
|
338
|
+
this.activeThreads = new Map(Object.entries(stored ?? {}));
|
|
339
|
+
}
|
|
340
|
+
return this.activeThreads;
|
|
341
|
+
}
|
|
342
|
+
async persistActiveThreads(map) {
|
|
343
|
+
await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.activeThreads, Object.fromEntries(map));
|
|
344
|
+
}
|
|
345
|
+
// ── chat-turn lock ────────────────────────────────────────────────────────
|
|
346
|
+
async loadActiveLock(now = Date.now()) {
|
|
347
|
+
const stored = await this.state.storage.get(TURN_STREAM_STORAGE_KEYS.lock);
|
|
348
|
+
const lock = activeTurnLock(stored, now);
|
|
349
|
+
if (stored && !lock) await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock);
|
|
350
|
+
return lock;
|
|
351
|
+
}
|
|
352
|
+
async releaseActiveLock(input) {
|
|
353
|
+
const active = await this.loadActiveLock();
|
|
354
|
+
if (!active || !turnLockMatchesRelease(active, input)) return false;
|
|
355
|
+
await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock);
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
/** Park a release on the lock itself; {@link completeDeferredLockRelease}
|
|
359
|
+
* finishes it once the product's deferred condition settles. */
|
|
360
|
+
async deferLockRelease(input) {
|
|
361
|
+
const active = await this.loadActiveLock();
|
|
362
|
+
if (!active || !turnLockMatchesRelease(active, input)) return false;
|
|
363
|
+
if (active.releasePending) return true;
|
|
364
|
+
await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, { ...active, releasePending: true });
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
async handleLockAcquire(request) {
|
|
368
|
+
const body = await jsonBody(request);
|
|
369
|
+
const workspaceId = stringValue(body?.workspaceId);
|
|
370
|
+
const threadId = stringValue(body?.threadId);
|
|
371
|
+
const executionId = stringValue(body?.executionId);
|
|
372
|
+
const lockId = stringValue(body?.lockId);
|
|
373
|
+
const scope = body?.scope === "workspace" ? "workspace" : body?.scope === "thread" ? "thread" : null;
|
|
374
|
+
const turnId = stringValue(body?.turnId) ?? void 0;
|
|
375
|
+
if (!workspaceId || !threadId || !executionId || !lockId || !scope) {
|
|
376
|
+
return jsonResponse({ error: "Missing workspaceId, threadId, executionId, lockId, or scope" }, 400);
|
|
377
|
+
}
|
|
378
|
+
const active = await this.loadActiveLock();
|
|
379
|
+
if (active) {
|
|
380
|
+
return jsonResponse({ acquired: false, active }, 409);
|
|
381
|
+
}
|
|
382
|
+
const lock = createTurnLock(
|
|
383
|
+
{ workspaceId, threadId, scope, executionId, lockId, ...turnId ? { turnId } : {} },
|
|
384
|
+
Date.now(),
|
|
385
|
+
this.options.lockTtlMs ?? TURN_LOCK_TTL_MS
|
|
386
|
+
);
|
|
387
|
+
await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.lock, lock);
|
|
388
|
+
return jsonResponse({ acquired: true, lock });
|
|
389
|
+
}
|
|
390
|
+
async handleLockRelease(request) {
|
|
391
|
+
const body = await jsonBody(request);
|
|
392
|
+
const executionId = stringValue(body?.executionId);
|
|
393
|
+
const lockId = stringValue(body?.lockId);
|
|
394
|
+
if (!executionId || !lockId) {
|
|
395
|
+
return jsonResponse({ error: "Missing executionId or lockId" }, 400);
|
|
396
|
+
}
|
|
397
|
+
if (await this.shouldDeferLockRelease(executionId)) {
|
|
398
|
+
const deferred = await this.deferLockRelease({ executionId, lockId });
|
|
399
|
+
return jsonResponse({ released: false, deferred });
|
|
400
|
+
}
|
|
401
|
+
const released = await this.releaseActiveLock({ executionId, lockId });
|
|
402
|
+
return jsonResponse({ released });
|
|
403
|
+
}
|
|
404
|
+
async handleLockReleaseInterrupted(request) {
|
|
405
|
+
const body = await jsonBody(request);
|
|
406
|
+
const threadId = stringValue(body?.threadId);
|
|
407
|
+
const interruptedAt = typeof body?.interruptedAt === "number" && Number.isFinite(body.interruptedAt) ? body.interruptedAt : void 0;
|
|
408
|
+
if (!threadId || interruptedAt === void 0) {
|
|
409
|
+
return jsonResponse({ error: "Missing threadId or interruptedAt" }, 400);
|
|
410
|
+
}
|
|
411
|
+
const turnId = stringValue(body?.turnId) ?? void 0;
|
|
412
|
+
const active = await this.loadActiveLock();
|
|
413
|
+
if (!active) return jsonResponse({ released: false });
|
|
414
|
+
if (await this.shouldDeferLockRelease(active.executionId)) {
|
|
415
|
+
return jsonResponse({ released: false });
|
|
416
|
+
}
|
|
417
|
+
if (!interruptedReleaseApplies(active, { threadId, interruptedAt, ...turnId ? { turnId } : {} })) {
|
|
418
|
+
return jsonResponse({ released: false });
|
|
419
|
+
}
|
|
420
|
+
await this.state.storage.delete(TURN_STREAM_STORAGE_KEYS.lock);
|
|
421
|
+
return jsonResponse({ released: true });
|
|
422
|
+
}
|
|
423
|
+
// ── durable turn-event storage (TurnEventStore backing) ───────────────────
|
|
424
|
+
async handleTurnEventsAppend(request) {
|
|
425
|
+
const body = await jsonBody(request);
|
|
426
|
+
const events = Array.isArray(body?.events) ? body.events : null;
|
|
427
|
+
if (!events) return jsonResponse({ error: "Missing events" }, 400);
|
|
428
|
+
for (const row of events) {
|
|
429
|
+
const seq = row.seq;
|
|
430
|
+
const event = row.event;
|
|
431
|
+
if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 1 || typeof event !== "string") {
|
|
432
|
+
return jsonResponse({ error: "Invalid turn-event row" }, 400);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
for (const row of events) {
|
|
436
|
+
await this.state.storage.put(turnEventStorageKey(row.seq), row.event);
|
|
437
|
+
}
|
|
438
|
+
return jsonResponse({ appended: events.length });
|
|
439
|
+
}
|
|
440
|
+
async handleTurnEventsRead(request) {
|
|
441
|
+
const body = await jsonBody(request);
|
|
442
|
+
const fromSeq = typeof body?.fromSeq === "number" && Number.isFinite(body.fromSeq) ? Math.trunc(body.fromSeq) : 0;
|
|
443
|
+
const rows = await this.state.storage.list({
|
|
444
|
+
prefix: TURN_STREAM_STORAGE_KEYS.turnEventPrefix,
|
|
445
|
+
start: turnEventStorageKey(fromSeq + 1)
|
|
446
|
+
});
|
|
447
|
+
const events = [];
|
|
448
|
+
for (const [key, event] of rows) {
|
|
449
|
+
const seq = Number(key.slice(TURN_STREAM_STORAGE_KEYS.turnEventPrefix.length));
|
|
450
|
+
if (Number.isFinite(seq) && seq > fromSeq) events.push({ seq, event });
|
|
451
|
+
}
|
|
452
|
+
return jsonResponse({ events });
|
|
453
|
+
}
|
|
454
|
+
async handleTurnStatusSet(request) {
|
|
455
|
+
const body = await jsonBody(request);
|
|
456
|
+
const status = body?.status;
|
|
457
|
+
if (status !== "running" && status !== "complete" && status !== "error") {
|
|
458
|
+
return jsonResponse({ error: "Invalid status" }, 400);
|
|
459
|
+
}
|
|
460
|
+
await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnStatus, status);
|
|
461
|
+
return jsonResponse({ ok: true });
|
|
462
|
+
}
|
|
463
|
+
async handleTurnStatusGet() {
|
|
464
|
+
const status = await this.state.storage.get(TURN_STREAM_STORAGE_KEYS.turnStatus);
|
|
465
|
+
return jsonResponse({ status: status ?? null });
|
|
466
|
+
}
|
|
467
|
+
// ── scope index (listRunning reconnect discovery) ─────────────────────────
|
|
468
|
+
async handleScopeStatusSet(request) {
|
|
469
|
+
const body = await jsonBody(request);
|
|
470
|
+
const turnId = stringValue(body?.turnId);
|
|
471
|
+
const status = body?.status;
|
|
472
|
+
if (!turnId || status !== "running" && status !== "complete" && status !== "error") {
|
|
473
|
+
return jsonResponse({ error: "Invalid scope-status request" }, 400);
|
|
474
|
+
}
|
|
475
|
+
const index = await this.state.storage.get(TURN_STREAM_STORAGE_KEYS.turnScope) ?? {};
|
|
476
|
+
index[turnId] = { status, updatedAt: Date.now() };
|
|
477
|
+
const entries = Object.entries(index);
|
|
478
|
+
if (entries.length > MAX_SCOPE_TURNS) {
|
|
479
|
+
const removable = entries.filter(([, entry]) => entry.status !== "running").sort((a, b) => a[1].updatedAt - b[1].updatedAt);
|
|
480
|
+
for (const [id] of removable.slice(0, entries.length - MAX_SCOPE_TURNS)) {
|
|
481
|
+
delete index[id];
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.turnScope, index);
|
|
485
|
+
return jsonResponse({ ok: true });
|
|
486
|
+
}
|
|
487
|
+
async handleScopeRunningList() {
|
|
488
|
+
const index = await this.state.storage.get(TURN_STREAM_STORAGE_KEYS.turnScope) ?? {};
|
|
489
|
+
const running = Object.entries(index).filter(([, entry]) => entry.status === "running").sort((a, b) => b[1].updatedAt - a[1].updatedAt).map(([turnId]) => turnId);
|
|
490
|
+
return jsonResponse({ running });
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/turn-stream/adapters.ts
|
|
495
|
+
var INTERNAL_ORIGIN = "https://turn-stream.internal";
|
|
496
|
+
async function postJson(namespace, channelKey, path, body) {
|
|
497
|
+
const stub = namespace.get(namespace.idFromName(channelKey));
|
|
498
|
+
const response = await stub.fetch(`${INTERNAL_ORIGIN}${path}`, {
|
|
499
|
+
method: "POST",
|
|
500
|
+
headers: { "Content-Type": "application/json" },
|
|
501
|
+
body: JSON.stringify(body)
|
|
502
|
+
});
|
|
503
|
+
return { status: response.status, body: await response.json() };
|
|
504
|
+
}
|
|
505
|
+
async function postJsonOk(namespace, channelKey, path, body) {
|
|
506
|
+
const result = await postJson(namespace, channelKey, path, body);
|
|
507
|
+
if (result.status !== 200) {
|
|
508
|
+
throw new Error(`turn-stream ${path} failed with status ${result.status}`);
|
|
509
|
+
}
|
|
510
|
+
return result.body;
|
|
511
|
+
}
|
|
512
|
+
function createDurableObjectTurnEventStore(namespace) {
|
|
513
|
+
return {
|
|
514
|
+
async append(turnId, events) {
|
|
515
|
+
if (!events.length) return;
|
|
516
|
+
await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnEventsAppend, { events });
|
|
517
|
+
},
|
|
518
|
+
async read(turnId, fromSeq) {
|
|
519
|
+
const body = await postJsonOk(
|
|
520
|
+
namespace,
|
|
521
|
+
turnStorageChannelKey(turnId),
|
|
522
|
+
TURN_STREAM_PATHS.turnEventsRead,
|
|
523
|
+
{ fromSeq }
|
|
524
|
+
);
|
|
525
|
+
return body.events;
|
|
526
|
+
},
|
|
527
|
+
async setStatus(turnId, status, scopeId) {
|
|
528
|
+
await postJsonOk(namespace, turnStorageChannelKey(turnId), TURN_STREAM_PATHS.turnStatusSet, { status });
|
|
529
|
+
if (scopeId) {
|
|
530
|
+
await postJsonOk(namespace, scopeIndexChannelKey(scopeId), TURN_STREAM_PATHS.scopeStatusSet, {
|
|
531
|
+
turnId,
|
|
532
|
+
status
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
async getStatus(turnId) {
|
|
537
|
+
const body = await postJsonOk(
|
|
538
|
+
namespace,
|
|
539
|
+
turnStorageChannelKey(turnId),
|
|
540
|
+
TURN_STREAM_PATHS.turnStatusGet,
|
|
541
|
+
{}
|
|
542
|
+
);
|
|
543
|
+
return body.status;
|
|
544
|
+
},
|
|
545
|
+
async listRunning(scopeId) {
|
|
546
|
+
const body = await postJsonOk(
|
|
547
|
+
namespace,
|
|
548
|
+
scopeIndexChannelKey(scopeId),
|
|
549
|
+
TURN_STREAM_PATHS.scopeRunningList,
|
|
550
|
+
{}
|
|
551
|
+
);
|
|
552
|
+
return body.running;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
async function acquireDurableTurnLock(namespace, input) {
|
|
557
|
+
const lockId = input.lockId ?? crypto.randomUUID();
|
|
558
|
+
const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope);
|
|
559
|
+
const result = await postJson(namespace, key, TURN_STREAM_PATHS.lockAcquire, {
|
|
560
|
+
...input,
|
|
561
|
+
lockId
|
|
562
|
+
});
|
|
563
|
+
if (result.status !== 200 && result.status !== 409) {
|
|
564
|
+
throw new Error(`turn-stream lock acquire failed with status ${result.status}`);
|
|
565
|
+
}
|
|
566
|
+
return result.body;
|
|
567
|
+
}
|
|
568
|
+
async function releaseDurableTurnLock(namespace, input) {
|
|
569
|
+
const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope);
|
|
570
|
+
return postJsonOk(namespace, key, TURN_STREAM_PATHS.lockRelease, input);
|
|
571
|
+
}
|
|
572
|
+
async function releaseInterruptedDurableTurnLock(namespace, input) {
|
|
573
|
+
const scopes = input.scope ? [input.scope] : ["workspace", "thread"];
|
|
574
|
+
for (const scope of scopes) {
|
|
575
|
+
const key = turnLockChannelKey(input.workspaceId, input.threadId, scope);
|
|
576
|
+
const result = await postJsonOk(namespace, key, TURN_STREAM_PATHS.lockReleaseInterrupted, {
|
|
577
|
+
threadId: input.threadId,
|
|
578
|
+
interruptedAt: input.interruptedAt,
|
|
579
|
+
...input.turnId ? { turnId: input.turnId } : {}
|
|
580
|
+
});
|
|
581
|
+
if (result.released) return true;
|
|
582
|
+
}
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
async function reconcileStaleDurableTurnLock(options) {
|
|
586
|
+
const { namespace, workspaceId, threadId, active, ...policy } = options;
|
|
587
|
+
return reconcileStaleTurnLock({
|
|
588
|
+
...policy,
|
|
589
|
+
lockStartedAt: active.startedAt,
|
|
590
|
+
release: (fence) => releaseInterruptedDurableTurnLock(namespace, {
|
|
591
|
+
workspaceId,
|
|
592
|
+
threadId: active.threadId,
|
|
593
|
+
scope: active.scope,
|
|
594
|
+
interruptedAt: fence.observedAt,
|
|
595
|
+
...active.turnId ? { turnId: active.turnId } : {}
|
|
596
|
+
})
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
function defaultRefusalResponse(active, diagnostics) {
|
|
600
|
+
const workspaceConflict = active.scope === "workspace";
|
|
601
|
+
return Response.json(
|
|
602
|
+
{
|
|
603
|
+
error: {
|
|
604
|
+
code: workspaceConflict ? "workspace_turn_in_flight" : "chat_turn_in_flight",
|
|
605
|
+
message: workspaceConflict ? "A chat turn is already running for this workspace. Wait for it to finish before starting another." : "A chat turn is already running for this thread. Reconnect to the active response or wait for it to finish.",
|
|
606
|
+
threadId: active.threadId,
|
|
607
|
+
executionId: active.executionId,
|
|
608
|
+
...active.turnId ? { turnId: active.turnId } : {},
|
|
609
|
+
lockStartedAt: active.startedAt,
|
|
610
|
+
lockAgeMs: Date.now() - active.startedAt,
|
|
611
|
+
...diagnostics ? { diagnostics } : {}
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
{ status: 409 }
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
function createDurableTurnLock(options) {
|
|
618
|
+
return {
|
|
619
|
+
async acquire(args) {
|
|
620
|
+
const workspaceId = args.identity.tenantId;
|
|
621
|
+
const threadId = args.identity.sessionId;
|
|
622
|
+
const scope = options.scopeOf(args);
|
|
623
|
+
const executionId = options.lockExecutionIdOf?.(args) ?? args.executionId;
|
|
624
|
+
const turnId = options.clientTurnIdOf ? options.clientTurnIdOf(args) : args.body.turnId;
|
|
625
|
+
const input = {
|
|
626
|
+
workspaceId,
|
|
627
|
+
threadId,
|
|
628
|
+
scope,
|
|
629
|
+
executionId,
|
|
630
|
+
...turnId ? { turnId } : {}
|
|
631
|
+
};
|
|
632
|
+
let acquired = await acquireDurableTurnLock(options.namespace, input);
|
|
633
|
+
let diagnostics;
|
|
634
|
+
if (!acquired.acquired && options.reconcile) {
|
|
635
|
+
const reconciled = await options.reconcile(args, acquired.active);
|
|
636
|
+
diagnostics = reconciled.diagnostics;
|
|
637
|
+
if (reconciled.released) {
|
|
638
|
+
acquired = await acquireDurableTurnLock(options.namespace, input);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (!acquired.acquired) {
|
|
642
|
+
return {
|
|
643
|
+
acquired: false,
|
|
644
|
+
response: (options.onRefused ?? defaultRefusalResponse)(acquired.active, diagnostics)
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const handle = {
|
|
648
|
+
workspaceId,
|
|
649
|
+
threadId,
|
|
650
|
+
scope,
|
|
651
|
+
executionId,
|
|
652
|
+
lockId: acquired.lock.lockId
|
|
653
|
+
};
|
|
654
|
+
return { acquired: true, handle };
|
|
655
|
+
},
|
|
656
|
+
async release(handle) {
|
|
657
|
+
if (!handle) return;
|
|
658
|
+
await releaseDurableTurnLock(options.namespace, handle);
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
async function broadcastTurnStreamEvent(namespace, input) {
|
|
663
|
+
try {
|
|
664
|
+
await postJson(namespace, threadChannelKey(input.workspaceId, input.threadId), TURN_STREAM_PATHS.broadcast, {
|
|
665
|
+
type: input.event.type,
|
|
666
|
+
timestamp: Date.now(),
|
|
667
|
+
data: {
|
|
668
|
+
...input.event.data ?? {},
|
|
669
|
+
workspaceId: input.workspaceId,
|
|
670
|
+
threadId: input.threadId,
|
|
671
|
+
sessionId: input.threadId,
|
|
672
|
+
executionId: input.executionId
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
async function broadcastWorkspaceActivity(namespace, workspaceId, threadId, phase) {
|
|
679
|
+
try {
|
|
680
|
+
await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {
|
|
681
|
+
type: "thread.activity",
|
|
682
|
+
timestamp: Date.now(),
|
|
683
|
+
data: { threadId, phase, workspaceId, sessionId: workspaceId }
|
|
684
|
+
});
|
|
685
|
+
} catch {
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
async function broadcastThreadCreated(namespace, workspaceId, thread) {
|
|
689
|
+
try {
|
|
690
|
+
await postJson(namespace, workspaceChannelKey(workspaceId), TURN_STREAM_PATHS.broadcast, {
|
|
691
|
+
type: "thread.created",
|
|
692
|
+
timestamp: Date.now(),
|
|
693
|
+
data: { threadId: thread.threadId, title: thread.title, workspaceId, sessionId: workspaceId }
|
|
694
|
+
});
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function createTurnStreamUpgradeHandler(options) {
|
|
699
|
+
const path = options.path ?? "/api/session-stream";
|
|
700
|
+
return async (request) => {
|
|
701
|
+
const url = new URL(request.url);
|
|
702
|
+
if (url.pathname !== path || request.headers.get("Upgrade") !== "websocket") return null;
|
|
703
|
+
const workspaceId = url.searchParams.get("workspaceId");
|
|
704
|
+
const threadId = url.searchParams.get("threadId");
|
|
705
|
+
if (!workspaceId) return new Response("Missing workspaceId", { status: 400 });
|
|
706
|
+
const auth = await options.authorize(request, { workspaceId, threadId });
|
|
707
|
+
if (!auth.ok) return auth.response;
|
|
708
|
+
const key = threadId ? threadChannelKey(workspaceId, threadId) : workspaceChannelKey(workspaceId);
|
|
709
|
+
const sessionId = threadId ?? workspaceId;
|
|
710
|
+
const stub = options.namespace.get(options.namespace.idFromName(key));
|
|
711
|
+
const forwardUrl = new URL(request.url);
|
|
712
|
+
forwardUrl.searchParams.set("sessionId", sessionId);
|
|
713
|
+
forwardUrl.searchParams.set("scope", threadId ? "thread" : "workspace");
|
|
714
|
+
return stub.fetch(new Request(forwardUrl, request));
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/turn-stream/memory.ts
|
|
719
|
+
function createMemoryStorage() {
|
|
720
|
+
const map = /* @__PURE__ */ new Map();
|
|
721
|
+
return {
|
|
722
|
+
async get(key) {
|
|
723
|
+
return map.get(key);
|
|
724
|
+
},
|
|
725
|
+
async put(key, value) {
|
|
726
|
+
map.set(key, value);
|
|
727
|
+
},
|
|
728
|
+
async delete(key) {
|
|
729
|
+
return map.delete(key);
|
|
730
|
+
},
|
|
731
|
+
async list({ prefix, start }) {
|
|
732
|
+
const keys = [...map.keys()].filter((key) => key.startsWith(prefix) && (start === void 0 || key >= start)).sort();
|
|
733
|
+
return new Map(keys.map((key) => [key, map.get(key)]));
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function createMemoryTurnStreamHarness(createInstance = (state) => new TurnStreamDO(state), _options = {}) {
|
|
738
|
+
const instances = /* @__PURE__ */ new Map();
|
|
739
|
+
function ensure(name) {
|
|
740
|
+
let entry = instances.get(name);
|
|
741
|
+
if (!entry) {
|
|
742
|
+
const sockets = [];
|
|
743
|
+
const state = {
|
|
744
|
+
storage: createMemoryStorage(),
|
|
745
|
+
acceptWebSocket: (ws) => sockets.push(ws),
|
|
746
|
+
getWebSockets: () => sockets.filter((ws) => !ws.closed)
|
|
747
|
+
};
|
|
748
|
+
entry = { instance: createInstance(state), sockets };
|
|
749
|
+
instances.set(name, entry);
|
|
750
|
+
}
|
|
751
|
+
return entry;
|
|
752
|
+
}
|
|
753
|
+
const namespace = {
|
|
754
|
+
idFromName: (name) => name,
|
|
755
|
+
get(id) {
|
|
756
|
+
const entry = ensure(String(id));
|
|
757
|
+
return {
|
|
758
|
+
fetch: (input, init) => entry.instance.fetch(typeof input === "string" ? new Request(input, init) : input)
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
return {
|
|
763
|
+
namespace,
|
|
764
|
+
channel(name) {
|
|
765
|
+
const entry = ensure(name);
|
|
766
|
+
return {
|
|
767
|
+
instance: entry.instance,
|
|
768
|
+
async connect({ sessionId, scope, afterSeq = 0 }) {
|
|
769
|
+
let attachment = null;
|
|
770
|
+
const frames = [];
|
|
771
|
+
let closed = false;
|
|
772
|
+
const socket = {
|
|
773
|
+
get frames() {
|
|
774
|
+
return frames;
|
|
775
|
+
},
|
|
776
|
+
get closed() {
|
|
777
|
+
return closed;
|
|
778
|
+
},
|
|
779
|
+
send(data) {
|
|
780
|
+
if (closed) throw new Error("socket closed");
|
|
781
|
+
frames.push(data);
|
|
782
|
+
},
|
|
783
|
+
close() {
|
|
784
|
+
closed = true;
|
|
785
|
+
},
|
|
786
|
+
serializeAttachment(value) {
|
|
787
|
+
attachment = value;
|
|
788
|
+
},
|
|
789
|
+
deserializeAttachment() {
|
|
790
|
+
return attachment;
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
socket.serializeAttachment({ sessionId, scope, synced: false });
|
|
794
|
+
entry.sockets.push(socket);
|
|
795
|
+
await entry.instance.webSocketMessage(socket, JSON.stringify({ type: "sync", afterSeq }));
|
|
796
|
+
return socket;
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
export {
|
|
803
|
+
ACTIVITY_TTL_MS,
|
|
804
|
+
MAX_RECENT_CREATED,
|
|
805
|
+
MAX_SEGMENT_EVENTS,
|
|
806
|
+
TURN_LOCK_TTL_MS,
|
|
807
|
+
TURN_STREAM_PATHS,
|
|
808
|
+
TURN_STREAM_STORAGE_KEYS,
|
|
809
|
+
TurnStreamDO,
|
|
810
|
+
acquireDurableTurnLock,
|
|
811
|
+
activeTurnLock,
|
|
812
|
+
appendSegmentEvent,
|
|
813
|
+
broadcastThreadCreated,
|
|
814
|
+
broadcastTurnStreamEvent,
|
|
815
|
+
broadcastWorkspaceActivity,
|
|
816
|
+
createDurableObjectTurnEventStore,
|
|
817
|
+
createDurableTurnLock,
|
|
818
|
+
createMemoryTurnStreamHarness,
|
|
819
|
+
createSegmentStore,
|
|
820
|
+
createTurnLock,
|
|
821
|
+
createTurnStreamUpgradeHandler,
|
|
822
|
+
interruptedReleaseApplies,
|
|
823
|
+
isTerminalRunEvent,
|
|
824
|
+
pruneStaleThreads,
|
|
825
|
+
reconcileStaleDurableTurnLock,
|
|
826
|
+
releaseDurableTurnLock,
|
|
827
|
+
releaseInterruptedDurableTurnLock,
|
|
828
|
+
replayActiveSegment,
|
|
829
|
+
scopeIndexChannelKey,
|
|
830
|
+
threadChannelKey,
|
|
831
|
+
turnEventStorageKey,
|
|
832
|
+
turnLockChannelKey,
|
|
833
|
+
turnLockMatchesRelease,
|
|
834
|
+
turnStorageChannelKey,
|
|
835
|
+
workspaceChannelKey
|
|
836
|
+
};
|
|
837
|
+
//# sourceMappingURL=index.js.map
|