@tangle-network/agent-app 0.60.210 → 0.60.212
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.js +1 -1
- package/dist/{chunk-B673CO6M.js → chunk-IJBFC72R.js} +1 -1
- package/dist/chunk-IJBFC72R.js.map +1 -0
- package/dist/{chunk-4VBM5KRG.js → chunk-K6D5EGN2.js} +9 -2
- package/dist/chunk-K6D5EGN2.js.map +1 -0
- package/dist/copy-quality/index.d.ts +2 -1
- package/dist/copy-quality/index.js +1 -1
- package/dist/profile/index.js +1 -1
- package/dist/public-consultation/index.js +1 -1
- package/dist/turn-stream/adapters.d.ts +3 -2
- package/dist/turn-stream/core.d.ts +14 -6
- package/dist/turn-stream/do.d.ts +6 -1
- package/dist/turn-stream/index.js +58 -26
- package/dist/turn-stream/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4VBM5KRG.js.map +0 -1
- package/dist/chunk-B673CO6M.js.map +0 -1
|
@@ -29,15 +29,22 @@ export declare function mintTurnStreamToken(channelName: string, secret: string,
|
|
|
29
29
|
export declare function verifyTurnStreamToken(channelName: string, token: string, secret: string, now?: () => number): Promise<boolean>;
|
|
30
30
|
/** Define the scope level for acquiring a turn lock within thread or workspace contexts */
|
|
31
31
|
export type TurnLockScope = 'thread' | 'workspace';
|
|
32
|
-
/** Channel for a thread
|
|
32
|
+
/** Channel for a thread's viewer signals. */
|
|
33
33
|
export declare function threadChannelKey(workspaceId: string, threadId: string): string;
|
|
34
34
|
/** Channel for workspace signals and the workspace-scoped lock. */
|
|
35
35
|
export declare function workspaceChannelKey(workspaceId: string): string;
|
|
36
|
-
/** The channel a lock lives on: workspace
|
|
37
|
-
* in the workspace (one shared
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
36
|
+
/** The channel a lock lives on: the workspace channel, for both scopes.
|
|
37
|
+
* Workspace-scope locks serialize every thread in the workspace (one shared
|
|
38
|
+
* sandbox) and thread-scope locks serialize one thread (router lane); they
|
|
39
|
+
* stay independent because each has its own storage key
|
|
40
|
+
* ({@link turnLockStorageKey}). A thread lock on its own channel created a
|
|
41
|
+
* new Durable Object on every thread's first turn, 0.8-1.9 s inside Creative's
|
|
42
|
+
* admission on staging (2026-10-20) against an object that already exists. */
|
|
43
|
+
export declare function turnLockChannelKey(workspaceId: string, _threadId: string, _scope: TurnLockScope): string;
|
|
44
|
+
/** The storage key a lock lives under inside its channel's instance: the
|
|
45
|
+
* workspace-scope lock under `TURN_STREAM_STORAGE_KEYS.lock`, each thread's
|
|
46
|
+
* lock under `TURN_STREAM_STORAGE_KEYS.threadLockPrefix` + its thread id. */
|
|
47
|
+
export declare function turnLockStorageKey(scope: TurnLockScope, threadId: string): string;
|
|
41
48
|
/** Channel for the optional Durable Object turn-event backend. */
|
|
42
49
|
export declare function turnStorageChannelKey(turnId: string): string;
|
|
43
50
|
/** Running-turn index for the optional Durable Object turn-event backend. */
|
|
@@ -147,6 +154,7 @@ export declare const TURN_STREAM_PATHS: {
|
|
|
147
154
|
* a product extending the DO must not collide with these. */
|
|
148
155
|
export declare const TURN_STREAM_STORAGE_KEYS: {
|
|
149
156
|
readonly lock: 'chatTurnLock';
|
|
157
|
+
readonly threadLockPrefix: 'chatTurnLock:thread:';
|
|
150
158
|
readonly activeThreads: 'activeThreads';
|
|
151
159
|
readonly turnStatus: 'turnStatus';
|
|
152
160
|
readonly turnScope: 'turnScopeIndex';
|
package/dist/turn-stream/do.d.ts
CHANGED
|
@@ -120,11 +120,16 @@ export declare class TurnStreamDO {
|
|
|
120
120
|
private handleBroadcast;
|
|
121
121
|
private loadActiveThreads;
|
|
122
122
|
private persistActiveThreads;
|
|
123
|
-
|
|
123
|
+
/** The live lock under `key` (default: the workspace-scope lock); an
|
|
124
|
+
* expired one is deleted and reads as none. */
|
|
125
|
+
protected loadActiveLock(now?: number, key?: string): Promise<DurableTurnLock | null>;
|
|
124
126
|
private releaseActiveLock;
|
|
125
127
|
/** Park a release on the lock itself; {@link completeDeferredLockRelease}
|
|
126
128
|
* finishes it once the product's deferred condition settles. */
|
|
127
129
|
private deferLockRelease;
|
|
130
|
+
/** Delete expired thread locks, so a thread whose release never arrived
|
|
131
|
+
* leaves no row behind once its lock's TTL passes. */
|
|
132
|
+
private pruneExpiredThreadLocks;
|
|
128
133
|
private handleLockAcquire;
|
|
129
134
|
private handleLockRelease;
|
|
130
135
|
private handleLockReleaseInterrupted;
|
|
@@ -37,8 +37,11 @@ function threadChannelKey(workspaceId, threadId) {
|
|
|
37
37
|
function workspaceChannelKey(workspaceId) {
|
|
38
38
|
return workspaceId;
|
|
39
39
|
}
|
|
40
|
-
function turnLockChannelKey(workspaceId,
|
|
41
|
-
return
|
|
40
|
+
function turnLockChannelKey(workspaceId, _threadId, _scope) {
|
|
41
|
+
return workspaceChannelKey(workspaceId);
|
|
42
|
+
}
|
|
43
|
+
function turnLockStorageKey(scope, threadId) {
|
|
44
|
+
return scope === "workspace" ? TURN_STREAM_STORAGE_KEYS.lock : `${TURN_STREAM_STORAGE_KEYS.threadLockPrefix}${threadId}`;
|
|
42
45
|
}
|
|
43
46
|
function turnStorageChannelKey(turnId) {
|
|
44
47
|
return `turn:${turnId}`;
|
|
@@ -107,6 +110,7 @@ var TURN_STREAM_PATHS = {
|
|
|
107
110
|
};
|
|
108
111
|
var TURN_STREAM_STORAGE_KEYS = {
|
|
109
112
|
lock: "chatTurnLock",
|
|
113
|
+
threadLockPrefix: "chatTurnLock:thread:",
|
|
110
114
|
activeThreads: "activeThreads",
|
|
111
115
|
turnStatus: "turnStatus",
|
|
112
116
|
turnScope: "turnScopeIndex",
|
|
@@ -130,6 +134,10 @@ async function jsonBody(request) {
|
|
|
130
134
|
const body = await request.json().catch(() => null);
|
|
131
135
|
return body && typeof body === "object" && !Array.isArray(body) ? body : null;
|
|
132
136
|
}
|
|
137
|
+
function lockStorageKeyOf(body) {
|
|
138
|
+
const threadId = stringValue(body?.threadId);
|
|
139
|
+
return body?.scope === "thread" && threadId ? turnLockStorageKey("thread", threadId) : TURN_STREAM_STORAGE_KEYS.lock;
|
|
140
|
+
}
|
|
133
141
|
var MAX_SCOPE_TURNS = 100;
|
|
134
142
|
var TurnStreamDO = class {
|
|
135
143
|
state;
|
|
@@ -195,7 +203,7 @@ var TurnStreamDO = class {
|
|
|
195
203
|
case TURN_STREAM_PATHS.lockAcquire:
|
|
196
204
|
return this.handleLockAcquire(request);
|
|
197
205
|
case TURN_STREAM_PATHS.lockPeek:
|
|
198
|
-
return jsonResponse({ active: await this.loadActiveLock() });
|
|
206
|
+
return jsonResponse({ active: await this.loadActiveLock(Date.now(), lockStorageKeyOf(await jsonBody(request))) });
|
|
199
207
|
case TURN_STREAM_PATHS.lockRelease:
|
|
200
208
|
return this.handleLockRelease(request);
|
|
201
209
|
case TURN_STREAM_PATHS.lockReleaseInterrupted:
|
|
@@ -236,10 +244,14 @@ var TurnStreamDO = class {
|
|
|
236
244
|
/** Complete a release parked by {@link shouldDeferLockRelease}. A subclass
|
|
237
245
|
* calls this when its deferred condition settles. */
|
|
238
246
|
async completeDeferredLockRelease(executionId) {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
const threadLocks = await this.state.storage.list({ prefix: TURN_STREAM_STORAGE_KEYS.threadLockPrefix });
|
|
248
|
+
for (const key of [TURN_STREAM_STORAGE_KEYS.lock, ...threadLocks.keys()]) {
|
|
249
|
+
const active = await this.loadActiveLock(Date.now(), key);
|
|
250
|
+
if (!active?.releasePending || active.executionId !== executionId) continue;
|
|
251
|
+
await this.state.storage.delete(key);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
243
255
|
}
|
|
244
256
|
/** Extra product state replayed to a socket during its `sync`, after the
|
|
245
257
|
* base replay for its scope (e.g. an in-flight persistence status card).
|
|
@@ -355,27 +367,37 @@ var TurnStreamDO = class {
|
|
|
355
367
|
await this.state.storage.put(TURN_STREAM_STORAGE_KEYS.activeThreads, Object.fromEntries(map));
|
|
356
368
|
}
|
|
357
369
|
// ── chat-turn lock ────────────────────────────────────────────────────────
|
|
358
|
-
|
|
359
|
-
|
|
370
|
+
/** The live lock under `key` (default: the workspace-scope lock); an
|
|
371
|
+
* expired one is deleted and reads as none. */
|
|
372
|
+
async loadActiveLock(now = Date.now(), key = TURN_STREAM_STORAGE_KEYS.lock) {
|
|
373
|
+
const stored = await this.state.storage.get(key);
|
|
360
374
|
const lock = activeTurnLock(stored, now);
|
|
361
|
-
if (stored && !lock) await this.state.storage.delete(
|
|
375
|
+
if (stored && !lock) await this.state.storage.delete(key);
|
|
362
376
|
return lock;
|
|
363
377
|
}
|
|
364
|
-
async releaseActiveLock(input) {
|
|
365
|
-
const active = await this.loadActiveLock();
|
|
378
|
+
async releaseActiveLock(key, input) {
|
|
379
|
+
const active = await this.loadActiveLock(Date.now(), key);
|
|
366
380
|
if (!active || !turnLockMatchesRelease(active, input)) return false;
|
|
367
|
-
await this.state.storage.delete(
|
|
381
|
+
await this.state.storage.delete(key);
|
|
368
382
|
return true;
|
|
369
383
|
}
|
|
370
384
|
/** Park a release on the lock itself; {@link completeDeferredLockRelease}
|
|
371
385
|
* finishes it once the product's deferred condition settles. */
|
|
372
|
-
async deferLockRelease(input) {
|
|
373
|
-
const active = await this.loadActiveLock();
|
|
386
|
+
async deferLockRelease(key, input) {
|
|
387
|
+
const active = await this.loadActiveLock(Date.now(), key);
|
|
374
388
|
if (!active || !turnLockMatchesRelease(active, input)) return false;
|
|
375
389
|
if (active.releasePending) return true;
|
|
376
|
-
await this.state.storage.put(
|
|
390
|
+
await this.state.storage.put(key, { ...active, releasePending: true });
|
|
377
391
|
return true;
|
|
378
392
|
}
|
|
393
|
+
/** Delete expired thread locks, so a thread whose release never arrived
|
|
394
|
+
* leaves no row behind once its lock's TTL passes. */
|
|
395
|
+
async pruneExpiredThreadLocks(now) {
|
|
396
|
+
const threadLocks = await this.state.storage.list({ prefix: TURN_STREAM_STORAGE_KEYS.threadLockPrefix });
|
|
397
|
+
for (const [key, stored] of threadLocks) {
|
|
398
|
+
if (!activeTurnLock(stored, now)) await this.state.storage.delete(key);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
379
401
|
async handleLockAcquire(request) {
|
|
380
402
|
const body = await jsonBody(request);
|
|
381
403
|
const workspaceId = stringValue(body?.workspaceId);
|
|
@@ -387,16 +409,19 @@ var TurnStreamDO = class {
|
|
|
387
409
|
if (!workspaceId || !threadId || !executionId || !lockId || !scope) {
|
|
388
410
|
return jsonResponse({ error: "Missing workspaceId, threadId, executionId, lockId, or scope" }, 400);
|
|
389
411
|
}
|
|
390
|
-
const
|
|
412
|
+
const key = turnLockStorageKey(scope, threadId);
|
|
413
|
+
const now = Date.now();
|
|
414
|
+
const active = await this.loadActiveLock(now, key);
|
|
391
415
|
if (active) {
|
|
392
416
|
return jsonResponse({ acquired: false, active }, 409);
|
|
393
417
|
}
|
|
418
|
+
if (scope === "thread") await this.pruneExpiredThreadLocks(now);
|
|
394
419
|
const lock = createTurnLock(
|
|
395
420
|
{ workspaceId, threadId, scope, executionId, lockId, ...turnId ? { turnId } : {} },
|
|
396
|
-
|
|
421
|
+
now,
|
|
397
422
|
this.options.lockTtlMs ?? TURN_LOCK_TTL_MS
|
|
398
423
|
);
|
|
399
|
-
await this.state.storage.put(
|
|
424
|
+
await this.state.storage.put(key, lock);
|
|
400
425
|
return jsonResponse({ acquired: true, lock });
|
|
401
426
|
}
|
|
402
427
|
async handleLockRelease(request) {
|
|
@@ -406,11 +431,12 @@ var TurnStreamDO = class {
|
|
|
406
431
|
if (!executionId || !lockId) {
|
|
407
432
|
return jsonResponse({ error: "Missing executionId or lockId" }, 400);
|
|
408
433
|
}
|
|
434
|
+
const key = lockStorageKeyOf(body);
|
|
409
435
|
if (await this.shouldDeferLockRelease(executionId)) {
|
|
410
|
-
const deferred = await this.deferLockRelease({ executionId, lockId });
|
|
436
|
+
const deferred = await this.deferLockRelease(key, { executionId, lockId });
|
|
411
437
|
return jsonResponse({ released: false, deferred });
|
|
412
438
|
}
|
|
413
|
-
const released = await this.releaseActiveLock({ executionId, lockId });
|
|
439
|
+
const released = await this.releaseActiveLock(key, { executionId, lockId });
|
|
414
440
|
return jsonResponse({ released });
|
|
415
441
|
}
|
|
416
442
|
async handleLockReleaseInterrupted(request) {
|
|
@@ -421,7 +447,8 @@ var TurnStreamDO = class {
|
|
|
421
447
|
return jsonResponse({ error: "Missing threadId or interruptedAt" }, 400);
|
|
422
448
|
}
|
|
423
449
|
const turnId = stringValue(body?.turnId) ?? void 0;
|
|
424
|
-
const
|
|
450
|
+
const key = lockStorageKeyOf(body);
|
|
451
|
+
const active = await this.loadActiveLock(Date.now(), key);
|
|
425
452
|
if (!active) return jsonResponse({ released: false });
|
|
426
453
|
if (await this.shouldDeferLockRelease(active.executionId)) {
|
|
427
454
|
return jsonResponse({ released: false });
|
|
@@ -429,7 +456,7 @@ var TurnStreamDO = class {
|
|
|
429
456
|
if (!interruptedReleaseApplies(active, { threadId, interruptedAt, ...turnId ? { turnId } : {} })) {
|
|
430
457
|
return jsonResponse({ released: false });
|
|
431
458
|
}
|
|
432
|
-
await this.state.storage.delete(
|
|
459
|
+
await this.state.storage.delete(key);
|
|
433
460
|
return jsonResponse({ released: true });
|
|
434
461
|
}
|
|
435
462
|
// ── durable turn-event storage (TurnEventStore backing) ───────────────────
|
|
@@ -594,7 +621,10 @@ async function acquireDurableTurnLock(namespace, input, auth) {
|
|
|
594
621
|
}
|
|
595
622
|
async function peekDurableTurnLock(namespace, input, auth) {
|
|
596
623
|
const key = turnLockChannelKey(input.workspaceId, input.threadId, input.scope);
|
|
597
|
-
const result = await postJson(namespace, key, TURN_STREAM_PATHS.lockPeek, {
|
|
624
|
+
const result = await postJson(namespace, key, TURN_STREAM_PATHS.lockPeek, {
|
|
625
|
+
scope: input.scope,
|
|
626
|
+
threadId: input.threadId
|
|
627
|
+
}, auth);
|
|
598
628
|
if (result.status !== 200) throw new Error(`turn-stream lock peek failed with status ${result.status}`);
|
|
599
629
|
return result.body.active ?? null;
|
|
600
630
|
}
|
|
@@ -607,6 +637,7 @@ async function releaseInterruptedDurableTurnLock(namespace, input, auth) {
|
|
|
607
637
|
for (const scope of scopes) {
|
|
608
638
|
const key = turnLockChannelKey(input.workspaceId, input.threadId, scope);
|
|
609
639
|
const result = await postJsonOk(namespace, key, TURN_STREAM_PATHS.lockReleaseInterrupted, {
|
|
640
|
+
scope,
|
|
610
641
|
threadId: input.threadId,
|
|
611
642
|
interruptedAt: input.interruptedAt,
|
|
612
643
|
...input.turnId ? { turnId: input.turnId } : {}
|
|
@@ -649,8 +680,8 @@ function defaultRefusalResponse(active, diagnostics) {
|
|
|
649
680
|
}
|
|
650
681
|
function createDurableTurnLock(options) {
|
|
651
682
|
return {
|
|
652
|
-
//
|
|
653
|
-
//
|
|
683
|
+
// Every lock lives on the workspace channel, so this wakes that object
|
|
684
|
+
// (evicted after idle) before the turn's acquire reaches it.
|
|
654
685
|
async warm(target) {
|
|
655
686
|
await peekDurableTurnLock(options.namespace, { workspaceId: target.tenantId, threadId: target.threadId, scope: "thread" }, options.auth);
|
|
656
687
|
},
|
|
@@ -863,6 +894,7 @@ export {
|
|
|
863
894
|
turnEventStorageKey,
|
|
864
895
|
turnLockChannelKey,
|
|
865
896
|
turnLockMatchesRelease,
|
|
897
|
+
turnLockStorageKey,
|
|
866
898
|
turnStorageChannelKey,
|
|
867
899
|
verifyTurnStreamToken,
|
|
868
900
|
workspaceChannelKey
|