orizu 0.5.32 → 0.5.33

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.
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-loop.d.ts","sourceRoot":"","sources":["../src/hosted-loop.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hosted-loop.d.ts","sourceRoot":"","sources":["../src/hosted-loop.ts"],"names":[],"mappings":"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"}
@@ -691,7 +691,10 @@ export async function runHostedLoop(opts) {
691
691
  await sleep(delayMs);
692
692
  try {
693
693
  await sink.finish(error.intendedStatus, error.finishOptions);
694
- return { status: error.intendedStatus, agentSessionId, installOk, error: null };
694
+ // A timer flush may have met the server's 404/410 during the sleep,
695
+ // making finish() a no-op: honour the server, as the drain does.
696
+ const status = sink.terminatedServerSide ? 'cancelled' : error.intendedStatus;
697
+ return { status, agentSessionId, installOk, error: null };
695
698
  }
696
699
  catch (retryError) {
697
700
  // The run went terminal server-side while we were retrying: accept
@@ -705,8 +708,10 @@ export async function runHostedLoop(opts) {
705
708
  }
706
709
  if (sink.sealed) {
707
710
  // A concurrent/preceding delivery landed after all — the record is
708
- // terminal server-side with our intended status semantics preserved.
709
- return { status: error.intendedStatus, agentSessionId, installOk, error: null };
711
+ // terminal server-side with our intended status semantics preserved —
712
+ // unless a timer flush met the server's 404/410 instead.
713
+ const status = sink.terminatedServerSide ? 'cancelled' : error.intendedStatus;
714
+ return { status, agentSessionId, installOk, error: null };
710
715
  }
711
716
  return {
712
717
  status: error.intendedStatus,
@@ -66,12 +66,15 @@ export interface RunEventSink {
66
66
  * Terminal kinds are rejected (they must go through `finish`). */
67
67
  append(event: HarnessEvent): Promise<void>;
68
68
  /** Force-flush any coalesced token snapshots (also runs on every structural
69
- * event and on `finish`). Exposed so callers can flush on a timer if desired. */
69
+ * event, on `finish`, and every `tokenFlushMs` while new tokens arrive). */
70
70
  flushTokens(): Promise<void>;
71
71
  /** Terminal transition via PATCH; records the transcript tail first, then CAS. */
72
72
  finish(status: TerminalStatus, opts?: FinishOptions): Promise<void>;
73
73
  /** True once a terminal transition has landed and the sink is sealed. */
74
74
  readonly sealed: boolean;
75
+ /** True when an append met 404/410, i.e. the server ended the run, not this
76
+ * writer's PATCH. A timer flush can learn this with no caller awaiting it. */
77
+ readonly terminatedServerSide: boolean;
75
78
  /** The next sequence the sink will attempt to allocate (test/resume aid). */
76
79
  readonly nextSequence: number;
77
80
  /** Events the sink has confirmed appended, in order (redacted payloads). */
@@ -163,7 +166,13 @@ export interface CreateRunEventSinkOptions {
163
166
  sleepImpl?: (ms: number) => Promise<void>;
164
167
  /** Local diagnostic sink for disagreement-rule logs (never throws). */
165
168
  onDiagnostic?: (message: string) => void;
169
+ /** How often buffered token snapshots are flushed while a reply is being
170
+ * written (ORI-2340). Default TOKEN_FLUSH_MS. */
171
+ tokenFlushMs?: number;
166
172
  }
173
+ /** A text-only reply reaches the browser as growing snapshots at this cadence
174
+ * instead of whole at the end of the turn (ORI-2340). */
175
+ export declare const TOKEN_FLUSH_MS = 250;
167
176
  export declare function createRunEventSink(options: CreateRunEventSinkOptions): RunEventSink;
168
177
  export interface ResumeRunEventSinkOptions extends Omit<CreateRunEventSinkOptions, 'startSequence'> {
169
178
  /** Page size for the cursor tail while discovering the latest sequence. */
@@ -1 +1 @@
1
- {"version":3,"file":"hosted-run-event-sink.d.ts","sourceRoot":"","sources":["../src/hosted-run-event-sink.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hosted-run-event-sink.d.ts","sourceRoot":"","sources":["../src/hosted-run-event-sink.ts"],"names":[],"mappings":"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"}
@@ -156,6 +156,9 @@ const TERMINAL_STATUSES = new Set(['succeeded', 'failed', 'cancelled']);
156
156
  function isTerminalStatus(status) {
157
157
  return status !== null && TERMINAL_STATUSES.has(status);
158
158
  }
159
+ /** A text-only reply reaches the browser as growing snapshots at this cadence
160
+ * instead of whole at the end of the turn (ORI-2340). */
161
+ export const TOKEN_FLUSH_MS = 250; // held equal to SESSION_TIMERS.agentTokenFlushMs by test
159
162
  const DEFAULT_MAX_CRITICAL_ATTEMPTS = 4;
160
163
  // Non-critical appends get a SMALL bounded retry so a single transient blip
161
164
  // (network reset / 5xx) does not drop the event — but on exhaustion they are
@@ -209,6 +212,53 @@ export function createRunEventSink(options) {
209
212
  const tokenBuffer = new Map();
210
213
  let nextSequence = options.startSequence ?? 1;
211
214
  let sealed = false;
215
+ let terminatedServerSide = false;
216
+ // One promise chain for every buffer-touching operation: append, flushTokens,
217
+ // finish and the timer flush below. This is how the timer honours the
218
+ // reentrancy contract on flushTokens — it queues behind an in-flight append
219
+ // or flush instead of running beside it. A rejected step reaches its own
220
+ // caller and never breaks the chain.
221
+ let queue = Promise.resolve();
222
+ function serialize(fn) {
223
+ const run = queue.then(fn);
224
+ queue = run.catch(() => { });
225
+ return run;
226
+ }
227
+ // ORI-2340 timer flush: started by the first token, unref'd so it never keeps
228
+ // the process alive, stopped for good once the sink seals or the bearer is
229
+ // refused. `tokensDirty` keeps a quiet tick (or a dropped snapshot with no
230
+ // newer text) from posting again.
231
+ let flushTimer = null;
232
+ let flushTimerStopped = false;
233
+ let tokensDirty = false;
234
+ let timerFlushQueued = false;
235
+ function stopFlushTimer() {
236
+ flushTimerStopped = true;
237
+ if (flushTimer)
238
+ clearInterval(flushTimer);
239
+ flushTimer = null;
240
+ }
241
+ function onFlushTick() {
242
+ if (sealed)
243
+ return stopFlushTimer();
244
+ if (!tokensDirty || timerFlushQueued)
245
+ return;
246
+ timerFlushQueued = true;
247
+ serialize(async () => {
248
+ timerFlushQueued = false;
249
+ // Queued behind finish(), the sink may have sealed since the tick fired.
250
+ if (sealed)
251
+ return;
252
+ tokensDirty = false;
253
+ await flushTokens();
254
+ }).catch(error => {
255
+ // A refused bearer would be refused again every tick; the loop learns of
256
+ // it at its next structural event or finish.
257
+ if (error instanceof RunAuthError)
258
+ stopFlushTimer();
259
+ diag(`timer token flush failed: ${error instanceof Error ? error.message : String(error)}`);
260
+ });
261
+ }
212
262
  function assertWritable() {
213
263
  if (sealed) {
214
264
  throw new RunTerminalError('run is terminal server-side — refusing to append (Orizu records win)');
@@ -280,6 +330,7 @@ export function createRunEventSink(options) {
280
330
  // Run gone/terminal server-side: seal and stop (Orizu records win).
281
331
  if (response.status === 404 || response.status === 410) {
282
332
  sealed = true;
333
+ terminatedServerSide = true;
283
334
  diag(`append rejected (${response.status}) for ${eventType} — sealing sink (run terminal)`);
284
335
  throw new RunTerminalError(`append rejected (${response.status}) — run is terminal server-side`);
285
336
  }
@@ -321,10 +372,10 @@ export function createRunEventSink(options) {
321
372
  // REENTRANCY CONTRACT: flushTokens MUST run to completion before the next
322
373
  // append()/flushTokens() begins — it is NOT safe to run concurrently with a
323
374
  // token append. Coalescing correctness (and the `===` re-check below) relies on
324
- // the caller awaiting each append/flush strictly sequentially, so no newer
325
- // delta can mutate `tokenBuffer` mid-flush. If a timer-based flush is ever added
326
- // (see the interface note above), it MUST serialize against append — e.g. share
327
- // a mutex/queue — or the drop-preserving guarantee breaks.
375
+ // every append/flush running strictly sequentially, so no newer delta can
376
+ // mutate `tokenBuffer` mid-flush. The public methods and the ORI-2340 timer
377
+ // flush all go through `serialize` to keep that true; any new caller of this
378
+ // inner function MUST too, or the drop-preserving guarantee breaks.
328
379
  async function flushTokens() {
329
380
  if (tokenBuffer.size === 0)
330
381
  return;
@@ -354,6 +405,11 @@ export function createRunEventSink(options) {
354
405
  const messageId = event.messageId ?? '_';
355
406
  const text = typeof event.payload.text === 'string' ? event.payload.text : '';
356
407
  tokenBuffer.set(messageId, text);
408
+ tokensDirty = true;
409
+ if (!flushTimer && !flushTimerStopped) {
410
+ flushTimer = setInterval(onFlushTick, options.tokenFlushMs ?? TOKEN_FLUSH_MS);
411
+ flushTimer.unref?.();
412
+ }
357
413
  return;
358
414
  }
359
415
  const eventType = eventTypeForKind(event.kind);
@@ -470,12 +526,15 @@ export function createRunEventSink(options) {
470
526
  }
471
527
  }
472
528
  return {
473
- append,
474
- flushTokens,
475
- finish,
529
+ append: event => serialize(() => append(event)),
530
+ flushTokens: () => serialize(flushTokens),
531
+ finish: (status, opts) => serialize(() => finish(status, opts)),
476
532
  get sealed() {
477
533
  return sealed;
478
534
  },
535
+ get terminatedServerSide() {
536
+ return terminatedServerSide;
537
+ },
479
538
  get nextSequence() {
480
539
  return nextSequence;
481
540
  },
@@ -583,7 +642,9 @@ export async function drainHarnessToSink(events, sink, opts = {}) {
583
642
  : finishOpts;
584
643
  try {
585
644
  await sink.finish(status, mergedFinishOpts);
586
- return status;
645
+ // A timer flush queued ahead of finish() can meet the server's 404/410
646
+ // after the check above; finish() then no-ops, so honour the server.
647
+ return sink.terminatedServerSide ? 'cancelled' : status;
587
648
  }
588
649
  catch (error) {
589
650
  if (error instanceof RunTerminalError)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.5.32",
3
+ "version": "0.5.33",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -860,7 +860,10 @@ export async function runHostedLoop(opts: RunHostedLoopOptions): Promise<HostedL
860
860
  await sleep(delayMs)
861
861
  try {
862
862
  await sink.finish(error.intendedStatus, error.finishOptions)
863
- return { status: error.intendedStatus, agentSessionId, installOk, error: null }
863
+ // A timer flush may have met the server's 404/410 during the sleep,
864
+ // making finish() a no-op: honour the server, as the drain does.
865
+ const status = sink.terminatedServerSide ? 'cancelled' : error.intendedStatus
866
+ return { status, agentSessionId, installOk, error: null }
864
867
  } catch (retryError) {
865
868
  // The run went terminal server-side while we were retrying: accept
866
869
  // the server record (Orizu records win) instead of retrying further.
@@ -873,8 +876,10 @@ export async function runHostedLoop(opts: RunHostedLoopOptions): Promise<HostedL
873
876
  }
874
877
  if (sink.sealed) {
875
878
  // A concurrent/preceding delivery landed after all — the record is
876
- // terminal server-side with our intended status semantics preserved.
877
- return { status: error.intendedStatus, agentSessionId, installOk, error: null }
879
+ // terminal server-side with our intended status semantics preserved —
880
+ // unless a timer flush met the server's 404/410 instead.
881
+ const status = sink.terminatedServerSide ? 'cancelled' : error.intendedStatus
882
+ return { status, agentSessionId, installOk, error: null }
878
883
  }
879
884
  return {
880
885
  status: error.intendedStatus,
@@ -128,12 +128,15 @@ export interface RunEventSink {
128
128
  * Terminal kinds are rejected (they must go through `finish`). */
129
129
  append(event: HarnessEvent): Promise<void>
130
130
  /** Force-flush any coalesced token snapshots (also runs on every structural
131
- * event and on `finish`). Exposed so callers can flush on a timer if desired. */
131
+ * event, on `finish`, and every `tokenFlushMs` while new tokens arrive). */
132
132
  flushTokens(): Promise<void>
133
133
  /** Terminal transition via PATCH; records the transcript tail first, then CAS. */
134
134
  finish(status: TerminalStatus, opts?: FinishOptions): Promise<void>
135
135
  /** True once a terminal transition has landed and the sink is sealed. */
136
136
  readonly sealed: boolean
137
+ /** True when an append met 404/410, i.e. the server ended the run, not this
138
+ * writer's PATCH. A timer flush can learn this with no caller awaiting it. */
139
+ readonly terminatedServerSide: boolean
137
140
  /** The next sequence the sink will attempt to allocate (test/resume aid). */
138
141
  readonly nextSequence: number
139
142
  /** Events the sink has confirmed appended, in order (redacted payloads). */
@@ -251,8 +254,15 @@ export interface CreateRunEventSinkOptions {
251
254
  sleepImpl?: (ms: number) => Promise<void>
252
255
  /** Local diagnostic sink for disagreement-rule logs (never throws). */
253
256
  onDiagnostic?: (message: string) => void
257
+ /** How often buffered token snapshots are flushed while a reply is being
258
+ * written (ORI-2340). Default TOKEN_FLUSH_MS. */
259
+ tokenFlushMs?: number
254
260
  }
255
261
 
262
+ /** A text-only reply reaches the browser as growing snapshots at this cadence
263
+ * instead of whole at the end of the turn (ORI-2340). */
264
+ export const TOKEN_FLUSH_MS = 250 // held equal to SESSION_TIMERS.agentTokenFlushMs by test
265
+
256
266
  const DEFAULT_MAX_CRITICAL_ATTEMPTS = 4
257
267
  // Non-critical appends get a SMALL bounded retry so a single transient blip
258
268
  // (network reset / 5xx) does not drop the event — but on exhaustion they are
@@ -312,6 +322,50 @@ export function createRunEventSink(options: CreateRunEventSinkOptions): RunEvent
312
322
 
313
323
  let nextSequence = options.startSequence ?? 1
314
324
  let sealed = false
325
+ let terminatedServerSide = false
326
+
327
+ // One promise chain for every buffer-touching operation: append, flushTokens,
328
+ // finish and the timer flush below. This is how the timer honours the
329
+ // reentrancy contract on flushTokens — it queues behind an in-flight append
330
+ // or flush instead of running beside it. A rejected step reaches its own
331
+ // caller and never breaks the chain.
332
+ let queue: Promise<unknown> = Promise.resolve()
333
+ function serialize<T>(fn: () => Promise<T>): Promise<T> {
334
+ const run = queue.then(fn)
335
+ queue = run.catch(() => {})
336
+ return run
337
+ }
338
+
339
+ // ORI-2340 timer flush: started by the first token, unref'd so it never keeps
340
+ // the process alive, stopped for good once the sink seals or the bearer is
341
+ // refused. `tokensDirty` keeps a quiet tick (or a dropped snapshot with no
342
+ // newer text) from posting again.
343
+ let flushTimer: ReturnType<typeof setInterval> | null = null
344
+ let flushTimerStopped = false
345
+ let tokensDirty = false
346
+ let timerFlushQueued = false
347
+ function stopFlushTimer(): void {
348
+ flushTimerStopped = true
349
+ if (flushTimer) clearInterval(flushTimer)
350
+ flushTimer = null
351
+ }
352
+ function onFlushTick(): void {
353
+ if (sealed) return stopFlushTimer()
354
+ if (!tokensDirty || timerFlushQueued) return
355
+ timerFlushQueued = true
356
+ serialize(async () => {
357
+ timerFlushQueued = false
358
+ // Queued behind finish(), the sink may have sealed since the tick fired.
359
+ if (sealed) return
360
+ tokensDirty = false
361
+ await flushTokens()
362
+ }).catch(error => {
363
+ // A refused bearer would be refused again every tick; the loop learns of
364
+ // it at its next structural event or finish.
365
+ if (error instanceof RunAuthError) stopFlushTimer()
366
+ diag(`timer token flush failed: ${error instanceof Error ? error.message : String(error)}`)
367
+ })
368
+ }
315
369
 
316
370
  function assertWritable(): void {
317
371
  if (sealed) {
@@ -389,6 +443,7 @@ export function createRunEventSink(options: CreateRunEventSinkOptions): RunEvent
389
443
  // Run gone/terminal server-side: seal and stop (Orizu records win).
390
444
  if (response.status === 404 || response.status === 410) {
391
445
  sealed = true
446
+ terminatedServerSide = true
392
447
  diag(`append rejected (${response.status}) for ${eventType} — sealing sink (run terminal)`)
393
448
  throw new RunTerminalError(`append rejected (${response.status}) — run is terminal server-side`)
394
449
  }
@@ -434,10 +489,10 @@ export function createRunEventSink(options: CreateRunEventSinkOptions): RunEvent
434
489
  // REENTRANCY CONTRACT: flushTokens MUST run to completion before the next
435
490
  // append()/flushTokens() begins — it is NOT safe to run concurrently with a
436
491
  // token append. Coalescing correctness (and the `===` re-check below) relies on
437
- // the caller awaiting each append/flush strictly sequentially, so no newer
438
- // delta can mutate `tokenBuffer` mid-flush. If a timer-based flush is ever added
439
- // (see the interface note above), it MUST serialize against append — e.g. share
440
- // a mutex/queue — or the drop-preserving guarantee breaks.
492
+ // every append/flush running strictly sequentially, so no newer delta can
493
+ // mutate `tokenBuffer` mid-flush. The public methods and the ORI-2340 timer
494
+ // flush all go through `serialize` to keep that true; any new caller of this
495
+ // inner function MUST too, or the drop-preserving guarantee breaks.
441
496
  async function flushTokens(): Promise<void> {
442
497
  if (tokenBuffer.size === 0) return
443
498
  // Snapshot the pending entries but DO NOT clear the buffer up front: only drop
@@ -467,6 +522,11 @@ export function createRunEventSink(options: CreateRunEventSinkOptions): RunEvent
467
522
  const messageId = event.messageId ?? '_'
468
523
  const text = typeof event.payload.text === 'string' ? event.payload.text : ''
469
524
  tokenBuffer.set(messageId, text)
525
+ tokensDirty = true
526
+ if (!flushTimer && !flushTimerStopped) {
527
+ flushTimer = setInterval(onFlushTick, options.tokenFlushMs ?? TOKEN_FLUSH_MS)
528
+ flushTimer.unref?.()
529
+ }
470
530
  return
471
531
  }
472
532
  const eventType = eventTypeForKind(event.kind)
@@ -586,12 +646,15 @@ export function createRunEventSink(options: CreateRunEventSinkOptions): RunEvent
586
646
  }
587
647
 
588
648
  return {
589
- append,
590
- flushTokens,
591
- finish,
649
+ append: event => serialize(() => append(event)),
650
+ flushTokens: () => serialize(flushTokens),
651
+ finish: (status, opts) => serialize(() => finish(status, opts)),
592
652
  get sealed() {
593
653
  return sealed
594
654
  },
655
+ get terminatedServerSide() {
656
+ return terminatedServerSide
657
+ },
595
658
  get nextSequence() {
596
659
  return nextSequence
597
660
  },
@@ -737,7 +800,9 @@ export async function drainHarnessToSink(
737
800
  : finishOpts
738
801
  try {
739
802
  await sink.finish(status, mergedFinishOpts)
740
- return status
803
+ // A timer flush queued ahead of finish() can meet the server's 404/410
804
+ // after the check above; finish() then no-ops, so honour the server.
805
+ return sink.terminatedServerSide ? 'cancelled' : status
741
806
  } catch (error) {
742
807
  if (error instanceof RunTerminalError) return 'cancelled'
743
808
  throw error