@ours.network/fleet 1.2.0-nightly.68 → 1.2.0-nightly.69

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.
@@ -21,6 +21,15 @@
21
21
  * are not retained, so session recovery cannot replay them) and is retried
22
22
  * under the same event id. An event found `dispatched` may or may not have been
23
23
  * seen, so it is held as `uncertain` and never automatically re-admitted.
24
+ *
25
+ * Urgency is an explicit opt-in per reminder (`interrupt`). Absent means false,
26
+ * so stores written before it existed keep their exact idle-only meaning, and
27
+ * an older runtime that ignores the field stays idle-only. An urgent event may
28
+ * request at most ONE interruption of the agent's current turn per revision:
29
+ * the request is durably recorded before the session is asked to cancel, so a
30
+ * crash, a forced adapter restart or a second urgent reminder can never turn
31
+ * it into a cancellation loop. Every later admission of that event is
32
+ * idle-only, and only the release guard ever records `dispatched`.
24
33
  */
25
34
  export declare const SELF_REMINDERS_FILE = ".self-reminders.json";
26
35
  export declare const MAX_OPEN_REMINDERS = 100;
@@ -70,6 +79,40 @@ export interface ReminderRecord {
70
79
  createdAt: string;
71
80
  updatedAt: string;
72
81
  closedReason?: string;
82
+ /** Present (true) only for an urgent reminder; absent is the idle-only default. */
83
+ interrupt?: true;
84
+ }
85
+ /**
86
+ * What became of an urgent event's single interruption request. `requested` is
87
+ * the durable mark written before the session is asked to cancel; it is never
88
+ * evidence that anything was cancelled or delivered.
89
+ *
90
+ * - `cancelled`: the target turn's own terminal result says it was cancelled
91
+ * by this request (correlated, not inferred).
92
+ * - `ended`: the session reported the cancellation settled, but the target's
93
+ * terminal could not be correlated, or it ended some other way.
94
+ * - `forced`: the adapter ignored the cancel and was restarted.
95
+ * - `failed`: the control call failed; it may or may not have reached the adapter.
96
+ * - `unknown`: the supervisor restarted before the outcome was recorded.
97
+ */
98
+ export type ReminderInterruptOutcome = 'requested' | 'cancelled' | 'ended' | 'forced' | 'failed' | 'unknown';
99
+ /**
100
+ * The exact turn an urgent event asked to stop: typed provenance and the
101
+ * session's own identifiers for it (prompt id within that session generation),
102
+ * never a body.
103
+ */
104
+ export interface ReminderInterruptTarget {
105
+ source: string;
106
+ promptId: string;
107
+ generation: string;
108
+ sessionId?: string;
109
+ }
110
+ export interface ReminderEventInterrupt {
111
+ requestedAt: string;
112
+ target: ReminderInterruptTarget;
113
+ outcome: ReminderInterruptOutcome;
114
+ settledAt?: string;
115
+ reason?: string;
73
116
  }
74
117
  export interface ReminderEvent {
75
118
  id: string;
@@ -84,6 +127,10 @@ export interface ReminderEvent {
84
127
  dispatchedAt?: string;
85
128
  closedAt?: string;
86
129
  reason?: string;
130
+ /** The one interruption an urgent event may have requested. */
131
+ interrupt?: ReminderEventInterrupt;
132
+ /** Why an urgent event last found the busy turn not interruptible (diagnostic). */
133
+ interruptDeferred?: string;
87
134
  }
88
135
  export declare class ReminderError extends Error {
89
136
  readonly code: string;
@@ -99,6 +146,7 @@ export interface ScheduleInput {
99
146
  time_zone?: unknown;
100
147
  fold?: unknown;
101
148
  late_window_seconds?: unknown;
149
+ interrupt?: unknown;
102
150
  }
103
151
  export interface ReminderView {
104
152
  id: string;
@@ -113,6 +161,8 @@ export interface ReminderView {
113
161
  fold?: 0 | 1;
114
162
  due_at: string;
115
163
  late_window_seconds: number;
164
+ /** True only when this reminder may interrupt a busy turn when due. */
165
+ interrupt: boolean;
116
166
  expires_at: string;
117
167
  created_at: string;
118
168
  updated_at: string;
@@ -122,6 +172,7 @@ export interface ReminderView {
122
172
  id: string;
123
173
  state: ReminderEventState;
124
174
  revision: number;
175
+ interrupt_state?: ReminderInterruptOutcome;
125
176
  };
126
177
  }
127
178
  export declare class ReminderStore {
@@ -168,6 +219,20 @@ export declare class ReminderStore {
168
219
  };
169
220
  inspect(eventId: unknown): {
170
221
  event: {
222
+ interrupt?: {
223
+ reason?: string | undefined;
224
+ settled_at?: string | undefined;
225
+ state: ReminderInterruptOutcome;
226
+ requested_at: string;
227
+ target_source: string;
228
+ target: {
229
+ session_id?: string | undefined;
230
+ source: string;
231
+ prompt_id: string;
232
+ generation: string;
233
+ };
234
+ } | undefined;
235
+ interrupt_deferred?: string | undefined;
171
236
  reason?: string | undefined;
172
237
  closed_at?: string | undefined;
173
238
  dispatched_at?: string | undefined;
@@ -184,6 +249,20 @@ export declare class ReminderStore {
184
249
  };
185
250
  ack(eventId: unknown, expectedRevision: unknown): {
186
251
  event: {
252
+ interrupt?: {
253
+ reason?: string | undefined;
254
+ settled_at?: string | undefined;
255
+ state: ReminderInterruptOutcome;
256
+ requested_at: string;
257
+ target_source: string;
258
+ target: {
259
+ session_id?: string | undefined;
260
+ source: string;
261
+ prompt_id: string;
262
+ generation: string;
263
+ };
264
+ } | undefined;
265
+ interrupt_deferred?: string | undefined;
187
266
  reason?: string | undefined;
188
267
  closed_at?: string | undefined;
189
268
  dispatched_at?: string | undefined;
@@ -221,6 +300,27 @@ export declare class ReminderStore {
221
300
  dispatch: boolean;
222
301
  reason?: string;
223
302
  };
303
+ /**
304
+ * Whether this undispatched event may still ask to interrupt a busy turn: its
305
+ * reminder is urgent and current, and no interruption was ever requested
306
+ * for it (in this process or before a restart).
307
+ */
308
+ mayInterrupt(eventId: string): boolean;
309
+ /**
310
+ * Durable mark made BEFORE the session is asked to cancel. False: do not
311
+ * interrupt. Once written it is never cleared, which is what bounds every
312
+ * urgent event to one interruption.
313
+ */
314
+ markInterruptRequested(eventId: string, target: ReminderInterruptTarget): boolean;
315
+ /**
316
+ * Record why an urgent event did not interrupt this time. Written only when
317
+ * the reason changes, so a long busy stretch costs one write, not one per poll.
318
+ */
319
+ recordInterruptDeferred(eventId: string, reason: string): void;
320
+ /** How the requested interruption ended. Diagnostic; never moves the event. */
321
+ recordInterruptOutcome(eventId: string, outcome: Exclude<ReminderInterruptOutcome, 'requested'>, reason?: string): void;
322
+ /** Still the current, undispatched claim this process made? Re-checked after every await. */
323
+ stillAdmitting(eventId: string): boolean;
224
324
  /**
225
325
  * An admission that ended without the release guard dispatching it (queue
226
326
  * refused, session died, release refused while stopping) goes back to
@@ -26,6 +26,15 @@ import { ReminderScheduleError, resolveSchedule } from './schedule.js';
26
26
  * are not retained, so session recovery cannot replay them) and is retried
27
27
  * under the same event id. An event found `dispatched` may or may not have been
28
28
  * seen, so it is held as `uncertain` and never automatically re-admitted.
29
+ *
30
+ * Urgency is an explicit opt-in per reminder (`interrupt`). Absent means false,
31
+ * so stores written before it existed keep their exact idle-only meaning, and
32
+ * an older runtime that ignores the field stays idle-only. An urgent event may
33
+ * request at most ONE interruption of the agent's current turn per revision:
34
+ * the request is durably recorded before the session is asked to cancel, so a
35
+ * crash, a forced adapter restart or a second urgent reminder can never turn
36
+ * it into a cancellation loop. Every later admission of that event is
37
+ * idle-only, and only the release guard ever records `dispatched`.
29
38
  */
30
39
  export const SELF_REMINDERS_FILE = '.self-reminders.json';
31
40
  export const MAX_OPEN_REMINDERS = 100;
@@ -37,6 +46,7 @@ const MAX_HORIZON_MS = 5 * 366 * 24 * 3600 * 1000;
37
46
  const MAX_TITLE_CHARS = 200;
38
47
  const MAX_NOTE_CHARS = 4000;
39
48
  const MAX_REQUEST_KEY_CHARS = 200;
49
+ const INTERRUPT_OUTCOMES = new Set(['requested', 'cancelled', 'ended', 'forced', 'failed', 'unknown']);
40
50
  export class ReminderError extends Error {
41
51
  code;
42
52
  constructor(code, message) {
@@ -81,6 +91,13 @@ function revisionOf(raw) {
81
91
  throw new ReminderError('invalid_request', 'expected_revision must be the current positive revision');
82
92
  return raw;
83
93
  }
94
+ function urgency(raw) {
95
+ if (raw === undefined || raw === null)
96
+ return false;
97
+ if (typeof raw !== 'boolean')
98
+ throw new ReminderError('invalid_request', 'interrupt must be true or false');
99
+ return raw;
100
+ }
84
101
  export class ReminderStore {
85
102
  owner;
86
103
  now;
@@ -137,6 +154,10 @@ export class ReminderStore {
137
154
  recover() {
138
155
  const at = iso(this.now());
139
156
  for (const event of Object.values(this.state.events)) {
157
+ // An interruption requested by the previous process has an unknown end.
158
+ // Keep the mark (so it is never requested again) and say so.
159
+ if (event.interrupt?.outcome === 'requested')
160
+ event.interrupt = { ...event.interrupt, outcome: 'unknown', settledAt: at, reason: 'restarted_before_outcome' };
140
161
  if (event.state === 'admitting') {
141
162
  // The release guard never ran, so the model never saw it.
142
163
  event.state = 'pending';
@@ -186,6 +207,8 @@ export class ReminderStore {
186
207
  const retime = !previous || input.local_time !== undefined || input.time_zone !== undefined || input.fold !== undefined;
187
208
  const late = input.late_window_seconds === undefined && previous
188
209
  ? previous.lateWindowSeconds : lateWindow(input.late_window_seconds);
210
+ const interrupt = input.interrupt === undefined && previous
211
+ ? previous.interrupt === true : urgency(input.interrupt);
189
212
  let when;
190
213
  if (retime) {
191
214
  try {
@@ -205,7 +228,7 @@ export class ReminderStore {
205
228
  else {
206
229
  when = { localTime: previous.localTime, timeZone: previous.timeZone, fold: previous.fold, dueAt: Date.parse(previous.dueAt) };
207
230
  }
208
- return { title, note, ...when, late };
231
+ return { title, note, ...when, late, interrupt };
209
232
  }
210
233
  openCount() {
211
234
  return Object.values(this.state.reminders).filter(item => item.state === 'active' || item.state === 'delivered').length;
@@ -216,6 +239,9 @@ export class ReminderStore {
216
239
  title: input.title ?? null, note: input.note ?? null, local_time: input.local_time ?? null,
217
240
  time_zone: input.time_zone ?? null, fold: input.fold ?? null,
218
241
  late_window_seconds: input.late_window_seconds ?? null,
242
+ // Only an explicit urgent request changes the digest, so a retry of a
243
+ // call made before `interrupt` existed (or with it false) still matches.
244
+ ...(input.interrupt === true ? { interrupt: true } : {}),
219
245
  });
220
246
  const existing = Object.values(this.state.reminders).find(item => item.requestKey === key);
221
247
  if (existing) {
@@ -234,6 +260,7 @@ export class ReminderStore {
234
260
  ...(values.fold === undefined ? {} : { fold: values.fold }),
235
261
  dueAt: iso(values.dueAt), lateWindowSeconds: values.late,
236
262
  expiresAt: iso(values.dueAt + values.late * 1000), createdAt: at, updatedAt: at,
263
+ ...(values.interrupt ? { interrupt: true } : {}),
237
264
  };
238
265
  state.reminders[record.id] = record;
239
266
  this.prune(state);
@@ -264,6 +291,10 @@ export class ReminderStore {
264
291
  delete target.fold;
265
292
  else
266
293
  target.fold = values.fold;
294
+ if (values.interrupt)
295
+ target.interrupt = true;
296
+ else
297
+ delete target.interrupt;
267
298
  delete target.closedReason;
268
299
  return this.view(target);
269
300
  });
@@ -523,6 +554,80 @@ export class ReminderStore {
523
554
  }
524
555
  return { dispatch: true };
525
556
  }
557
+ /**
558
+ * Whether this undispatched event may still ask to interrupt a busy turn: its
559
+ * reminder is urgent and current, and no interruption was ever requested
560
+ * for it (in this process or before a restart).
561
+ */
562
+ mayInterrupt(eventId) {
563
+ const event = this.state.events[eventId];
564
+ const record = event && this.state.reminders[event.reminderId];
565
+ return Boolean(event && record && record.interrupt === true && !event.interrupt
566
+ && (event.state === 'pending' || event.state === 'admitting')
567
+ && record.state === 'active' && record.revision === event.revision);
568
+ }
569
+ /**
570
+ * Durable mark made BEFORE the session is asked to cancel. False: do not
571
+ * interrupt. Once written it is never cleared, which is what bounds every
572
+ * urgent event to one interruption.
573
+ */
574
+ markInterruptRequested(eventId, target) {
575
+ if (!this.mayInterrupt(eventId) || this.state.events[eventId].state !== 'admitting')
576
+ return false;
577
+ if (!target.source || !target.promptId || !target.generation)
578
+ return false;
579
+ const event = this.state.events[eventId];
580
+ try {
581
+ this.commit(() => {
582
+ event.interrupt = {
583
+ requestedAt: iso(this.now()), outcome: 'requested',
584
+ target: {
585
+ source: target.source, promptId: target.promptId, generation: target.generation,
586
+ ...(target.sessionId ? { sessionId: target.sessionId } : {}),
587
+ },
588
+ };
589
+ delete event.interruptDeferred;
590
+ });
591
+ }
592
+ catch {
593
+ return false;
594
+ }
595
+ return true;
596
+ }
597
+ /**
598
+ * Record why an urgent event did not interrupt this time. Written only when
599
+ * the reason changes, so a long busy stretch costs one write, not one per poll.
600
+ */
601
+ recordInterruptDeferred(eventId, reason) {
602
+ const event = this.state.events[eventId];
603
+ if (!event || event.interrupt || event.interruptDeferred === reason)
604
+ return;
605
+ try {
606
+ this.commit(() => { event.interruptDeferred = reason; });
607
+ }
608
+ catch { /* diagnostic only */ }
609
+ }
610
+ /** How the requested interruption ended. Diagnostic; never moves the event. */
611
+ recordInterruptOutcome(eventId, outcome, reason) {
612
+ const event = this.state.events[eventId];
613
+ if (!event?.interrupt || event.interrupt.outcome !== 'requested')
614
+ return;
615
+ try {
616
+ this.commit(() => {
617
+ event.interrupt = {
618
+ ...event.interrupt, outcome, settledAt: iso(this.now()), ...(reason ? { reason } : {}),
619
+ };
620
+ });
621
+ }
622
+ catch { /* stays `requested`: still never re-requested, still not delivered */ }
623
+ }
624
+ /** Still the current, undispatched claim this process made? Re-checked after every await. */
625
+ stillAdmitting(eventId) {
626
+ const event = this.state.events[eventId];
627
+ const record = event && this.state.reminders[event.reminderId];
628
+ return Boolean(event && record && event.state === 'admitting' && record.state === 'active'
629
+ && record.revision === event.revision);
630
+ }
526
631
  /**
527
632
  * An admission that ended without the release guard dispatching it (queue
528
633
  * refused, session died, release refused while stopping) goes back to
@@ -576,10 +681,14 @@ export class ReminderStore {
576
681
  title: record.title, note: record.note,
577
682
  local_time: record.localTime, time_zone: record.timeZone,
578
683
  ...(record.fold === undefined ? {} : { fold: record.fold }),
579
- due_at: record.dueAt, late_window_seconds: record.lateWindowSeconds, expires_at: record.expiresAt,
684
+ due_at: record.dueAt, late_window_seconds: record.lateWindowSeconds, interrupt: record.interrupt === true,
685
+ expires_at: record.expiresAt,
580
686
  created_at: record.createdAt, updated_at: record.updatedAt,
581
687
  ...(record.closedReason ? { closed_reason: record.closedReason } : {}),
582
- ...(event ? { event: { id: event.id, state: event.state, revision: event.revision } } : {}),
688
+ ...(event ? { event: {
689
+ id: event.id, state: event.state, revision: event.revision,
690
+ ...(event.interrupt ? { interrupt_state: event.interrupt.outcome } : {}),
691
+ } } : {}),
583
692
  };
584
693
  }
585
694
  eventView(event) {
@@ -589,6 +698,18 @@ export class ReminderStore {
589
698
  ...(event.dispatchedAt ? { dispatched_at: event.dispatchedAt } : {}),
590
699
  ...(event.closedAt ? { closed_at: event.closedAt } : {}),
591
700
  ...(event.reason ? { reason: event.reason } : {}),
701
+ ...(event.interruptDeferred ? { interrupt_deferred: event.interruptDeferred } : {}),
702
+ ...(event.interrupt ? { interrupt: {
703
+ state: event.interrupt.outcome, requested_at: event.interrupt.requestedAt,
704
+ target_source: event.interrupt.target.source,
705
+ target: {
706
+ source: event.interrupt.target.source, prompt_id: event.interrupt.target.promptId,
707
+ generation: event.interrupt.target.generation,
708
+ ...(event.interrupt.target.sessionId ? { session_id: event.interrupt.target.sessionId } : {}),
709
+ },
710
+ ...(event.interrupt.settledAt ? { settled_at: event.interrupt.settledAt } : {}),
711
+ ...(event.interrupt.reason ? { reason: event.interrupt.reason } : {}),
712
+ } } : {}),
592
713
  };
593
714
  }
594
715
  }
@@ -618,10 +739,16 @@ function validFile(value) {
618
739
  return Object.entries(file.reminders).every(([id, item]) => item && item.id === id
619
740
  && Number.isSafeInteger(item.revision) && item.revision >= 1 && reminderStates.has(item.state)
620
741
  && typeof item.requestKey === 'string' && typeof item.title === 'string' && typeof item.note === 'string'
621
- && Number.isFinite(Date.parse(item.dueAt)) && Number.isFinite(Date.parse(item.expiresAt)))
742
+ && Number.isFinite(Date.parse(item.dueAt)) && Number.isFinite(Date.parse(item.expiresAt))
743
+ && (item.interrupt === undefined || item.interrupt === true))
622
744
  && Object.entries(file.events).every(([id, item]) => item && item.id === id
623
745
  && typeof item.reminderId === 'string' && Number.isSafeInteger(item.revision) && eventStates.has(item.state)
624
- && Number.isSafeInteger(item.attempts));
746
+ && Number.isSafeInteger(item.attempts)
747
+ && (item.interrupt === undefined || (typeof item.interrupt === 'object' && item.interrupt !== null
748
+ && typeof item.interrupt.requestedAt === 'string' && typeof item.interrupt.target?.source === 'string'
749
+ && typeof item.interrupt.target.promptId === 'string' && typeof item.interrupt.target.generation === 'string'
750
+ && (item.interrupt.target.sessionId === undefined || typeof item.interrupt.target.sessionId === 'string')
751
+ && INTERRUPT_OUTCOMES.has(item.interrupt.outcome))));
625
752
  }
626
753
  /** Remove a retired agent's reminder store and any quarantined copies. */
627
754
  export function removeReminderState(stateDir) {
@@ -9,6 +9,10 @@ const schedule = {
9
9
  time_zone: z.string().describe('IANA zone the time is meant in, e.g. "Europe/London", "America/New_York" or "UTC". Use the zone of the person or task, not a guess.'),
10
10
  fold: z.union([z.literal(0), z.literal(1)]).optional().describe('Only for a wall time that occurs twice when clocks go back: 0 = first occurrence, 1 = second.'),
11
11
  late_window_seconds: z.number().int().optional().describe('How late the wake may still be delivered if this agent or the host was offline or busy at the due time (60..604800, default 3600). After it the reminder expires without a wake.'),
12
+ interrupt: z.boolean().optional().describe('Default false: the wake waits until you are idle. true only for a return that is genuinely time-bound: '
13
+ + 'when due and you are busy with interruptible work, Fleet asks your current turn to stop (session and context are kept) and wakes you with this reminder instead of waiting. '
14
+ + 'At most one interruption per reminder revision. It never pre-empts owner requests, the owner console, startup, a pending permission, queued work or another reminder; '
15
+ + 'then it waits like an ordinary reminder. Not a hard real-time alarm: no wake while the host or agent is down, and model start-up still takes time.'),
12
16
  };
13
17
  function result(value) {
14
18
  return {
@@ -90,6 +94,7 @@ admit) {
90
94
  time_zone: schedule.time_zone.optional(),
91
95
  fold: schedule.fold,
92
96
  late_window_seconds: schedule.late_window_seconds,
97
+ interrupt: schedule.interrupt,
93
98
  },
94
99
  }, async ({ id, expected_revision, ...changes }) => run(({ store }) => store.update(id, expected_revision, changes), true));
95
100
  server.registerTool('fleet_reminder_cancel', {
@@ -97,7 +102,8 @@ admit) {
97
102
  inputSchema: { id: z.string(), expected_revision: z.number().int() },
98
103
  }, async ({ id, expected_revision }) => run(({ store }) => store.cancel(id, expected_revision), true));
99
104
  server.registerTool('fleet_reminder_inspect', {
100
- description: 'Read the reminder behind a [fleet-reminder] wake, including its note, and whether the wake is still current and eligible.',
105
+ description: 'Read the reminder behind a [fleet-reminder] wake, including its note, whether the wake is still current and eligible, '
106
+ + 'and for an urgent reminder what its interruption request did (interrupt.state).',
101
107
  inputSchema: { event_id: z.string() },
102
108
  }, async ({ event_id }) => run(({ store }) => store.inspect(event_id), false));
103
109
  server.registerTool('fleet_reminder_ack', {
@@ -12,6 +12,42 @@ export type ReminderAttempt =
12
12
  error: string;
13
13
  admitted: false;
14
14
  };
15
+ /**
16
+ * The single interruption an urgent reminder may request, as the arbiter saw
17
+ * it. `target` names the exact checked turn; `outcome` is what the session
18
+ * and the target's own terminal result actually reported.
19
+ */
20
+ export interface UrgentInterruptReport {
21
+ target: UrgentInterruptTarget;
22
+ outcome: 'cancelled' | 'ended' | 'forced' | 'failed';
23
+ reason?: string;
24
+ }
25
+ export interface UrgentInterruptTarget {
26
+ source: NonNullable<SessionSnapshot['activePromptSource']>;
27
+ sessionId?: string;
28
+ generation: string;
29
+ promptId: string;
30
+ }
31
+ /** The urgent path's durable seams; every one is synchronous and owned by the reminder store. */
32
+ export interface UrgentReminderHooks {
33
+ /** The durable `admitting` claim (as for `tryReminder`). False: nothing is queued. */
34
+ beforeQueue(): boolean;
35
+ /** Release fence; the only way the prompt reaches the model. */
36
+ releaseGuard(): boolean;
37
+ /** Whether this event may still request its one interruption. */
38
+ mayInterrupt(): boolean;
39
+ /** Durable mark written BEFORE the session is asked to cancel. False: do not cancel. */
40
+ markRequested(target: UrgentInterruptTarget): boolean;
41
+ /** How the requested interruption ended. */
42
+ recordOutcome(report: UrgentInterruptReport): void;
43
+ /** The busy turn was not interruptible this time, and why. */
44
+ deferred(reason: UrgentDeferral): void;
45
+ /** Still this caller's current, undispatched claim? Re-checked after every await. */
46
+ stillAdmitting(): boolean;
47
+ /** The wake text, built once the interruption (if any) is known. */
48
+ text(report?: UrgentInterruptReport): string;
49
+ }
50
+ export type UrgentDeferral = 'protected_turn' | 'untracked_turn' | 'permission_pending' | 'queued_work' | 'session_starting' | 'interrupt_unsupported' | 'uncorrelatable' | 'interrupt_already_used';
15
51
  export type ScheduledAttempt = {
16
52
  state: 'started';
17
53
  queued: QueuedPrompt;
@@ -34,6 +70,10 @@ export declare class RoleTurnArbiter implements AgentSession {
34
70
  readonly capabilities: import("./types.js").AgentSessionCapabilities | undefined;
35
71
  private tail;
36
72
  private unsettled;
73
+ /** Admissions waiting for the boundary; an urgent interruption yields to them. */
74
+ private waiting;
75
+ /** Terminal results of prompts this arbiter admitted and that have not settled yet. */
76
+ private readonly live;
37
77
  private stopping;
38
78
  private retiring;
39
79
  /** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */
@@ -91,6 +131,26 @@ export declare class RoleTurnArbiter implements AgentSession {
91
131
  tryReminder(text: string, origin: Extract<PromptOrigin, {
92
132
  kind: 'self-reminder';
93
133
  }>, beforeQueue: () => boolean, releaseGuard: () => boolean): Promise<ReminderAttempt>;
134
+ /**
135
+ * Admit an URGENT self-reminder wake. Idle: exactly `tryReminder`. Busy: the
136
+ * reminder may ask the exact checked turn to stop — once per event — and is
137
+ * then admitted at the next boundary like any other reminder.
138
+ *
139
+ * The check and the cancel request happen in one synchronous step inside the
140
+ * admission boundary, so the turn asked to stop is the turn that was checked.
141
+ * Only a running, correlatable, interruptible turn with nothing queued behind
142
+ * it and no admission waiting is ever asked to stop; anything else is a
143
+ * reported deferral (`busy`). After the cancellation the boundary is given up
144
+ * before the wake is queued, so an owner prompt (or any producer) that
145
+ * arrived meanwhile is admitted first and the wake simply finds the role
146
+ * busy. A forced, failed or uncertain cancellation never queues the wake.
147
+ */
148
+ tryUrgentReminder(origin: Extract<PromptOrigin, {
149
+ kind: 'self-reminder';
150
+ }>, hooks: UrgentReminderHooks): Promise<ReminderAttempt>;
151
+ /** The exact interruptible turn, or why there is none. Synchronous by design. */
152
+ private urgentTarget;
153
+ private queueReminder;
94
154
  /** Retire only at the same admission boundary used by all prompt producers. */
95
155
  retireIfIdle(expired: (snapshot: SessionSnapshot, queueDepth: number) => boolean): Promise<boolean>;
96
156
  sharedTurnId(promptId: string): string | undefined;