@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
|
@@ -2,7 +2,9 @@ import { A as ChatTurnRequestPayload, B as ChatTurnPartInput, e as ChatMessagePa
|
|
|
2
2
|
export { c as ChatMentionKind, E as ChatTurnInputError, G as ChatTurnTextPartInput, F as FileMention, H as FileMentionsToPartsOptions, I as INLINE_PARTS_MAX_BYTES, M as MENTION_MAX_COUNT, J as SandboxMentionPathCheck, K as assertPromptPartsWithinCap, L as buildMentionPromptBlock, N as chatTurnRequestInit, O as fileMentionsToParts, P as mediaTypeForMentionPath, Q as mentionKindForPath, R as parseChatTurnParts, T as parseFileMentions, U as promptPartsByteSize, V as validateSandboxMentionPath } from '../parts-1_3y2JmR.js';
|
|
3
3
|
import { ChatTurnIdentity, ChatTurnProducer } from '@tangle-network/agent-runtime';
|
|
4
4
|
import { InteractionAnswerRoute, InteractionAnswerRouteOptions } from '../interactions/index.js';
|
|
5
|
-
import { PersistedChatMessageForTurn
|
|
5
|
+
import { PersistedChatMessageForTurn } from '../stream/index.js';
|
|
6
|
+
import { d as TurnEventStore } from '../turn-buffer-C9mEgoop.js';
|
|
7
|
+
export { D as DEFAULT_STALE_TURN_LOCK_GRACE_MS, a as DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS, R as ReconcileStaleTurnLockOptions, b as ReconcileStaleTurnLockResult, S as StaleTurnLockSandboxProbeResult, c as StaleTurnLockSessionProbeResult, r as reconcileStaleTurnLock } from '../stale-turn-lock-C8Na1cFZ.js';
|
|
6
8
|
export { C as CreateSandboxFileIndexRouteOptions, F as FileIndexAuthorization, a as FileIndexCache, b as FileIndexReadyResponse, c as FileIndexResponse, d as FileIndexWarmingResponse, S as SandboxFileTreeSource, e as SandboxTreeFile, f as SandboxTreeResult, g as createSandboxFileIndexRoute } from '../file-index-Bn6sitKb.js';
|
|
7
9
|
import '@tangle-network/agent-interface';
|
|
8
10
|
import '../contract-KfqJh_au.js';
|
|
@@ -40,10 +42,11 @@ import '../plans/index.js';
|
|
|
40
42
|
* `handleChatTurn` stays the engine — the seams only wrap its input, its
|
|
41
43
|
* producer stream, and its settle.
|
|
42
44
|
*
|
|
43
|
-
* Seam stability: `lifecycle` and `
|
|
44
|
-
* `turnLock
|
|
45
|
-
*
|
|
46
|
-
* a
|
|
45
|
+
* Seam stability: `lifecycle`, `heartbeat`, and `turnLock` are generic and
|
|
46
|
+
* stable (`turnLock` graduated with `/turn-stream`'s shared DO adapter, #221).
|
|
47
|
+
* `contextGate`, `beforeTurn`, and `onRawEvent` are `@experimental` — proven
|
|
48
|
+
* by a single consumer (gtm's chat vertical, #200) and may change once a
|
|
49
|
+
* second consumer exercises them. They stay FLAT top-level options (not
|
|
47
50
|
* grouped under a `hooks` object): that grouping would break the shipped
|
|
48
51
|
* consumer's call for no mechanism gain, and this package's exports are
|
|
49
52
|
* additive-only.
|
|
@@ -222,7 +225,8 @@ interface CreateChatTurnRoutesOptions<TContext = void> {
|
|
|
222
225
|
authorize(args: ChatTurnAuthorizeArgs): Promise<ChatTurnAuthorization<TContext>>;
|
|
223
226
|
/** Thread/message persistence (`/chat-store`'s store or a product adapter). */
|
|
224
227
|
store: ChatTurnMessageStore;
|
|
225
|
-
/** Turn-event buffer (`createD1TurnEventStore(env.DB)`
|
|
228
|
+
/** Turn-event buffer (`createD1TurnEventStore(env.DB)` or `/turn-stream`'s
|
|
229
|
+
* `createDurableObjectTurnEventStore(env.TURN_STREAM_DO)` in production,
|
|
226
230
|
* `createMemoryTurnEventStore()` in tests). Wired by default — every turn
|
|
227
231
|
* is buffered and replayable. */
|
|
228
232
|
turnStore: TurnEventStore;
|
|
@@ -231,8 +235,9 @@ interface CreateChatTurnRoutesOptions<TContext = void> {
|
|
|
231
235
|
* product's own producer. May be async (box resolution). */
|
|
232
236
|
produce(args: ChatTurnProduceArgs<TContext>): ChatTurnRouteProducer | Promise<ChatTurnRouteProducer>;
|
|
233
237
|
/** Single-flight lock acquired before any side effect and released once when
|
|
234
|
-
* the turn settles (including short-circuit/throw).
|
|
235
|
-
*
|
|
238
|
+
* the turn settles (including short-circuit/throw). `/turn-stream`'s
|
|
239
|
+
* `createDurableTurnLock` is the shared DO-backed implementation. Omit →
|
|
240
|
+
* no lock. */
|
|
236
241
|
turnLock?: ChatTurnLock<TContext>;
|
|
237
242
|
/** Pre-turn readiness gate that can short-circuit with a product `Response`
|
|
238
243
|
* before the producer runs (the user row is already persisted). Runs after
|
|
@@ -318,154 +323,6 @@ interface ChatTurnRoutes {
|
|
|
318
323
|
}
|
|
319
324
|
declare function createChatTurnRoutes<TContext = void>(options: CreateChatTurnRoutesOptions<TContext>): ChatTurnRoutes;
|
|
320
325
|
|
|
321
|
-
/**
|
|
322
|
-
* Recovery policy for a `ChatTurnLock` whose holder died.
|
|
323
|
-
*
|
|
324
|
-
* `createChatTurnRoutes` takes the lock as a seam (`acquire`/`release`) and a
|
|
325
|
-
* lock is a single-flight guard: while it is held, a second turn on the same
|
|
326
|
-
* scope is refused. Products give it a TTL measured in tens of minutes, so a
|
|
327
|
-
* turn that dies without releasing wedges chat for that whole window. Every
|
|
328
|
-
* app on the seam inherits that wedge, which is why the way OUT of it is
|
|
329
|
-
* policy this package owns rather than something each app rediscovers.
|
|
330
|
-
*
|
|
331
|
-
* The policy takes PROBES, not clients: it imports no sandbox SDK, opens no
|
|
332
|
-
* connection, and knows nothing about how a product finds its box or talks to
|
|
333
|
-
* a sidecar. That is what makes the rules testable and what keeps the concrete
|
|
334
|
-
* probes — which box key, which session id, which sidecar endpoint — in the
|
|
335
|
-
* product.
|
|
336
|
-
*
|
|
337
|
-
* The rules, in precedence order:
|
|
338
|
-
*
|
|
339
|
-
* 1. The session probe answered and the execution is TERMINAL ⇒ release, once
|
|
340
|
-
* the lock is past a short grace period. The authority on "is this turn
|
|
341
|
-
* still running" is whatever is actually running it; a terminal verdict is
|
|
342
|
-
* proof the lock outlived its turn — but only if the verdict is about THIS
|
|
343
|
-
* turn, which is what the grace buys (see
|
|
344
|
-
* {@link DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS}).
|
|
345
|
-
* 2. The session probe answered and the execution is LIVE ⇒ hold, always.
|
|
346
|
-
* Nothing below may override this. The lock is doing exactly its job.
|
|
347
|
-
* 3. The probes could not reach that authority at all — the sandbox could not
|
|
348
|
-
* be listed, is gone, is not running, or its session probe failed ⇒ fall
|
|
349
|
-
* back on the physical argument: an execution runs INSIDE the box, so a box
|
|
350
|
-
* that is not there is running nothing, and the lock is releasable. Without
|
|
351
|
-
* this fallback the recovery would depend on the very subsystem whose
|
|
352
|
-
* failure produced the stale lock.
|
|
353
|
-
*
|
|
354
|
-
* Rule 3 is gated on a grace period because it is an inference, not an
|
|
355
|
-
* observation — see {@link DEFAULT_STALE_TURN_LOCK_GRACE_MS}.
|
|
356
|
-
*/
|
|
357
|
-
/** Where the box is, as far as the caller can see. `state` on `not-running`
|
|
358
|
-
* is the platform's own status string, carried through for the log. */
|
|
359
|
-
type StaleTurnLockSandboxProbeResult = {
|
|
360
|
-
status: 'running';
|
|
361
|
-
} | {
|
|
362
|
-
status: 'absent';
|
|
363
|
-
} | {
|
|
364
|
-
status: 'not-running';
|
|
365
|
-
state?: string;
|
|
366
|
-
};
|
|
367
|
-
/** What the thing running the turn says about it. `terminal: false` means an
|
|
368
|
-
* execution is LIVE — the strongest signal in the policy. `diagnostics` rides
|
|
369
|
-
* through to the result and the logs unread. */
|
|
370
|
-
type StaleTurnLockSessionProbeResult = {
|
|
371
|
-
reachable: true;
|
|
372
|
-
terminal: boolean;
|
|
373
|
-
diagnostics?: Record<string, unknown>;
|
|
374
|
-
} | {
|
|
375
|
-
reachable: false;
|
|
376
|
-
reason?: string;
|
|
377
|
-
};
|
|
378
|
-
/**
|
|
379
|
-
* Minimum age a lock must reach before the "sandbox unreachable ⇒ nothing can
|
|
380
|
-
* be running" fallback may force-release it.
|
|
381
|
-
*
|
|
382
|
-
* The lock is acquired BEFORE the box is ensured, so during a cold workspace's
|
|
383
|
-
* first turn there is a real window in which the lock is held and no box exists
|
|
384
|
-
* yet — indistinguishable, from a peek, from a box that vanished. The grace
|
|
385
|
-
* period has to outlast that window (create + bootstrap + whatever the product
|
|
386
|
-
* hydrates) or a concurrent request steals the lock from a turn that is merely
|
|
387
|
-
* still provisioning. Five minutes clears observed cold starts with room to
|
|
388
|
-
* spare while cutting the worst case from a TTL-length wedge down to five
|
|
389
|
-
* minutes. Raising it makes recovery slower; lowering it risks stealing a lock
|
|
390
|
-
* mid-provision.
|
|
391
|
-
*/
|
|
392
|
-
declare const DEFAULT_STALE_TURN_LOCK_GRACE_MS: number;
|
|
393
|
-
/**
|
|
394
|
-
* Minimum age a lock must reach before a TERMINAL session verdict may release
|
|
395
|
-
* it.
|
|
396
|
-
*
|
|
397
|
-
* The session probe is keyed on the THREAD, not on the execution the lock
|
|
398
|
-
* holds: a sidecar that has nothing running reports `terminal` with
|
|
399
|
-
* `activeExecutionId: null`, so there is no id to match the lock against. The
|
|
400
|
-
* lock, meanwhile, is acquired BEFORE the box is ensured and before the
|
|
401
|
-
* execution registers with the sidecar. Between those two moments a second
|
|
402
|
-
* request that reconciles the lock asks the sidecar about a turn it has not
|
|
403
|
-
* heard of yet and gets back the PREVIOUS turn's terminal state — proof about
|
|
404
|
-
* the wrong execution. Releasing on that verdict hands the second request a
|
|
405
|
-
* lock the first one is still using, which is two concurrent turns on a scope
|
|
406
|
-
* whose single-flight guard just voted for itself.
|
|
407
|
-
*
|
|
408
|
-
* One minute covers the acquire → box-ensure → sidecar-registration window on
|
|
409
|
-
* a warm box (the cold-box case is Rule 3's, and has its own, much longer
|
|
410
|
-
* grace). Deliberately NOT
|
|
411
|
-
* {@link DEFAULT_STALE_TURN_LOCK_GRACE_MS}: this branch has a positive
|
|
412
|
-
* observation behind it, so it should recover fast, and stretching it to five
|
|
413
|
-
* minutes would leave a genuinely dead turn wedged for the whole window that
|
|
414
|
-
* the session probe exists to shortcut. Raising it delays recovery from a
|
|
415
|
-
* crashed turn; lowering it narrows the registration window it protects.
|
|
416
|
-
*/
|
|
417
|
-
declare const DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS: number;
|
|
418
|
-
interface ReconcileStaleTurnLockOptions {
|
|
419
|
-
/** When the held lock was acquired (epoch ms). The grace period is measured
|
|
420
|
-
* from here, so it must be the LOCK's start, not the turn's. */
|
|
421
|
-
lockStartedAt: number;
|
|
422
|
-
/** Is the box there and running? Never provisions — a peek, not an ensure.
|
|
423
|
-
* A throw is treated as unreachable, same as `absent`. */
|
|
424
|
-
probeSandbox(): Promise<StaleTurnLockSandboxProbeResult>;
|
|
425
|
-
/** Ask the running box whether the execution is still live. Only called when
|
|
426
|
-
* `probeSandbox` reported `running`. A throw is treated as unreachable. */
|
|
427
|
-
probeSession(): Promise<StaleTurnLockSessionProbeResult>;
|
|
428
|
-
/** Release the lock, fenced by the instant the releasing evidence was
|
|
429
|
-
* observed. `fence.observedAt` is snapshotted BEFORE the probe that
|
|
430
|
-
* justified the release, so a store that can compare it against the held
|
|
431
|
-
* lock's start refuses to delete a SUCCESSOR lock acquired while the probe
|
|
432
|
-
* was in flight. A store that cannot make that comparison may ignore the
|
|
433
|
-
* fence, but must not substitute its own `Date.now()` — that timestamp is
|
|
434
|
-
* by construction newer than any successor and makes the check vacuous.
|
|
435
|
-
*
|
|
436
|
-
* Returns whether the release actually landed — `false` when the lock was
|
|
437
|
-
* already gone (someone else got there first), which is reported, never
|
|
438
|
-
* treated as a release. */
|
|
439
|
-
release(fence: {
|
|
440
|
-
observedAt: number;
|
|
441
|
-
}): boolean | Promise<boolean>;
|
|
442
|
-
/** Override {@link DEFAULT_STALE_TURN_LOCK_GRACE_MS} (Rule 3's fallback). */
|
|
443
|
-
graceMs?: number;
|
|
444
|
-
/** Override {@link DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS} (Rule 1's release). */
|
|
445
|
-
terminalGraceMs?: number;
|
|
446
|
-
/** Identity fields merged into every log line (workspace, thread, execution
|
|
447
|
-
* id — whatever makes the entry findable in the product's logs). */
|
|
448
|
-
context?: Record<string, unknown>;
|
|
449
|
-
/** Defaults to `console.warn`. Both the withheld and the force-released
|
|
450
|
-
* branches log; a force-release is never silent. */
|
|
451
|
-
log?(message: string, meta: Record<string, unknown>): void;
|
|
452
|
-
/** Injectable clock, for tests. */
|
|
453
|
-
now?(): number;
|
|
454
|
-
}
|
|
455
|
-
interface ReconcileStaleTurnLockResult {
|
|
456
|
-
released: boolean;
|
|
457
|
-
/** Why the policy decided what it did — the probe's own diagnostics on the
|
|
458
|
-
* reachable path, the unreachable reason and lock age on the fallback. */
|
|
459
|
-
diagnostics: Record<string, unknown>;
|
|
460
|
-
}
|
|
461
|
-
/**
|
|
462
|
-
* Decide whether a held lock is stale and, if so, release it.
|
|
463
|
-
*
|
|
464
|
-
* Never provisions and never mutates anything but the lock: a reconciliation
|
|
465
|
-
* attempt on a cold workspace leaves it cold.
|
|
466
|
-
*/
|
|
467
|
-
declare function reconcileStaleTurnLock(options: ReconcileStaleTurnLockOptions): Promise<ReconcileStaleTurnLockResult>;
|
|
468
|
-
|
|
469
326
|
/**
|
|
470
327
|
* Sandbox lane: bridge a raw sandbox event stream (`streamSandboxPrompt`) into
|
|
471
328
|
* the `ChatTurnProducer` shape agent-runtime's `handleChatTurn` consumes AND
|
|
@@ -588,4 +445,4 @@ declare function sanitizeUploadFilename(name: string): string;
|
|
|
588
445
|
declare function bytesToBase64(bytes: Uint8Array): string;
|
|
589
446
|
declare function createUploadRoute(options: CreateUploadRouteOptions): (request: Request) => Promise<Response>;
|
|
590
447
|
|
|
591
|
-
export { type ChatRouteDurableProjection, type ChatRouteDurableProjectionLogger, type ChatTurnAuthorization, type ChatTurnAuthorizeArgs, ChatTurnFilePartInput, type ChatTurnGateResult, type ChatTurnHeartbeat, type ChatTurnInputPatch, type ChatTurnLifecycle, type ChatTurnLifecycleComplete, type ChatTurnLifecycleError, type ChatTurnLifecycleStart, type ChatTurnLock, type ChatTurnLockResult, type ChatTurnMessageStore, ChatTurnPartInput, type ChatTurnProduceArgs, ChatTurnRequestPayload, type ChatTurnRouteProducer, type ChatTurnRoutes, type ChatTurnUsage, type CreateChatTurnRoutesOptions, type CreateUploadRouteOptions,
|
|
448
|
+
export { type ChatRouteDurableProjection, type ChatRouteDurableProjectionLogger, type ChatTurnAuthorization, type ChatTurnAuthorizeArgs, ChatTurnFilePartInput, type ChatTurnGateResult, type ChatTurnHeartbeat, type ChatTurnInputPatch, type ChatTurnLifecycle, type ChatTurnLifecycleComplete, type ChatTurnLifecycleError, type ChatTurnLifecycleStart, type ChatTurnLock, type ChatTurnLockResult, type ChatTurnMessageStore, ChatTurnPartInput, type ChatTurnProduceArgs, ChatTurnRequestPayload, type ChatTurnRouteProducer, type ChatTurnRoutes, type ChatTurnUsage, type CreateChatTurnRoutesOptions, type CreateUploadRouteOptions, type SandboxChatProducerOptions, type SandboxUploadSink, UPLOAD_INLINE_MAX_BYTES, UPLOAD_MAX_FILE_BYTES, type UploadAuthorization, type UploadedChatFile, bytesToBase64, createChatTurnRoutes, createSandboxChatProducer, createUploadRoute, sanitizeUploadFilename, withDurableChatProjection };
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSandboxFileIndexRoute
|
|
3
3
|
} from "../chunk-LCNY3DCM.js";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_STALE_TURN_LOCK_GRACE_MS,
|
|
6
|
+
DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS,
|
|
7
|
+
reconcileStaleTurnLock
|
|
8
|
+
} from "../chunk-BQV42AFK.js";
|
|
4
9
|
import {
|
|
5
10
|
asRecord,
|
|
6
11
|
asString,
|
|
@@ -499,99 +504,6 @@ function concatStreams(streams) {
|
|
|
499
504
|
});
|
|
500
505
|
}
|
|
501
506
|
|
|
502
|
-
// src/chat-routes/stale-turn-lock.ts
|
|
503
|
-
var DEFAULT_STALE_TURN_LOCK_GRACE_MS = 5 * 60 * 1e3;
|
|
504
|
-
var DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS = 60 * 1e3;
|
|
505
|
-
function messageOf(err) {
|
|
506
|
-
return err instanceof Error ? err.message : String(err);
|
|
507
|
-
}
|
|
508
|
-
async function reconcileStaleTurnLock(options) {
|
|
509
|
-
let sandbox;
|
|
510
|
-
try {
|
|
511
|
-
sandbox = await options.probeSandbox();
|
|
512
|
-
} catch (err) {
|
|
513
|
-
return forceReleaseUnreachable(options, {
|
|
514
|
-
unreachableReason: "SANDBOX_PROBE_FAILED",
|
|
515
|
-
unreachableDetail: messageOf(err)
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
if (sandbox.status !== "running") {
|
|
519
|
-
return forceReleaseUnreachable(options, {
|
|
520
|
-
unreachableReason: sandbox.status === "absent" ? "SANDBOX_ABSENT" : "SANDBOX_NOT_RUNNING",
|
|
521
|
-
...sandbox.status === "not-running" && sandbox.state !== void 0 ? { sandboxState: sandbox.state } : {}
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
const observedAt = (options.now ?? Date.now)();
|
|
525
|
-
let session;
|
|
526
|
-
try {
|
|
527
|
-
session = await options.probeSession();
|
|
528
|
-
} catch (err) {
|
|
529
|
-
return forceReleaseUnreachable(options, {
|
|
530
|
-
unreachableReason: "SESSION_PROBE_FAILED",
|
|
531
|
-
unreachableDetail: messageOf(err)
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
if (!session.reachable) {
|
|
535
|
-
return forceReleaseUnreachable(options, {
|
|
536
|
-
unreachableReason: "SESSION_UNREACHABLE",
|
|
537
|
-
...session.reason !== void 0 ? { sessionProbeError: session.reason } : {}
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
const diagnostics = {
|
|
541
|
-
sandboxReachable: true,
|
|
542
|
-
sessionTerminal: session.terminal,
|
|
543
|
-
...session.diagnostics ?? {}
|
|
544
|
-
};
|
|
545
|
-
if (!session.terminal) return { released: false, diagnostics };
|
|
546
|
-
const terminalGraceMs = options.terminalGraceMs ?? DEFAULT_TERMINAL_TURN_LOCK_GRACE_MS;
|
|
547
|
-
const lockAgeMs = observedAt - options.lockStartedAt;
|
|
548
|
-
if (lockAgeMs < terminalGraceMs) {
|
|
549
|
-
const log = options.log ?? ((message, meta) => console.warn(message, meta));
|
|
550
|
-
const withheld = {
|
|
551
|
-
...diagnostics,
|
|
552
|
-
lockAgeMs,
|
|
553
|
-
terminalReleaseGraceMs: terminalGraceMs,
|
|
554
|
-
terminalReleaseWithheld: "LOCK_WITHIN_TERMINAL_GRACE_PERIOD"
|
|
555
|
-
};
|
|
556
|
-
log("[chat-routes] stale turn lock held: terminal verdict but lock is younger than the registration window", {
|
|
557
|
-
...options.context ?? {},
|
|
558
|
-
...withheld
|
|
559
|
-
});
|
|
560
|
-
return { released: false, diagnostics: withheld };
|
|
561
|
-
}
|
|
562
|
-
const released = await options.release({ observedAt });
|
|
563
|
-
return {
|
|
564
|
-
released,
|
|
565
|
-
diagnostics: { ...diagnostics, lockAgeMs, terminalReleaseGraceMs: terminalGraceMs, released, observedAt }
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
async function forceReleaseUnreachable(options, reason) {
|
|
569
|
-
const log = options.log ?? ((message, meta) => console.warn(message, meta));
|
|
570
|
-
const graceMs = options.graceMs ?? DEFAULT_STALE_TURN_LOCK_GRACE_MS;
|
|
571
|
-
const at = (options.now ?? Date.now)();
|
|
572
|
-
const lockAgeMs = at - options.lockStartedAt;
|
|
573
|
-
const diagnostics = {
|
|
574
|
-
...reason,
|
|
575
|
-
sandboxReachable: false,
|
|
576
|
-
lockAgeMs,
|
|
577
|
-
forceReleaseGraceMs: graceMs
|
|
578
|
-
};
|
|
579
|
-
if (lockAgeMs < graceMs) {
|
|
580
|
-
log("[chat-routes] stale turn lock held: sandbox unreachable but lock is inside the grace period", {
|
|
581
|
-
...options.context ?? {},
|
|
582
|
-
...diagnostics
|
|
583
|
-
});
|
|
584
|
-
return { released: false, diagnostics: { ...diagnostics, forceReleaseWithheld: "LOCK_WITHIN_GRACE_PERIOD" } };
|
|
585
|
-
}
|
|
586
|
-
const released = await options.release({ observedAt: at });
|
|
587
|
-
log("[chat-routes] force-released stale turn lock: sandbox unreachable, no turn can be executing", {
|
|
588
|
-
...options.context ?? {},
|
|
589
|
-
...diagnostics,
|
|
590
|
-
released
|
|
591
|
-
});
|
|
592
|
-
return { released, diagnostics: { ...diagnostics, forceReleased: released } };
|
|
593
|
-
}
|
|
594
|
-
|
|
595
507
|
// src/chat-routes/sandbox-producer.ts
|
|
596
508
|
function textDelta(tracker, key, part, rawDelta) {
|
|
597
509
|
const explicit = typeof rawDelta === "string" ? rawDelta : void 0;
|