@tinycloud/courier 0.2.0-beta.38 → 0.2.0-beta.40

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/watch.d.ts CHANGED
@@ -13,6 +13,38 @@ export interface PendingWake {
13
13
  firstSeenAt: number;
14
14
  lastSeenAt: number;
15
15
  }
16
+ interface NoticeRetry {
17
+ /** Six digits while dispatching, otherwise six dashes; independent of the display count. */
18
+ dispatchArrivals: string;
19
+ tries: number;
20
+ nextAttemptAt: number;
21
+ logged: boolean;
22
+ }
23
+ type NeedsKind = 'message' | 'approval';
24
+ interface NeedsSlot extends NoticeRetry {
25
+ threadId: string;
26
+ eventId: string;
27
+ count: string;
28
+ firstSeenAt: string;
29
+ arrivalFirstSeenAt: string;
30
+ }
31
+ interface PauseEpisode extends NoticeRetry {
32
+ kind: '' | 'paused' | 'peer-paused';
33
+ eventId: string;
34
+ /** Fixed-width saturating count of additional transitions represented by this slot. */
35
+ repeats: string;
36
+ }
37
+ /** Allocated only from our own thread/wake/pause records, never from incoming notice ids. */
38
+ interface ThreadNoticeState {
39
+ ourPaused: boolean;
40
+ peerPaused: boolean;
41
+ lastPeerNoticeId: string;
42
+ lastNoticeSentAt: string;
43
+ ourMarkerId: string;
44
+ episodes: [PauseEpisode, PauseEpisode];
45
+ informPending: boolean;
46
+ informText: string;
47
+ }
16
48
  export interface WatchState {
17
49
  v: 1;
18
50
  /** Keyed `<peer>/<threadId>`. */
@@ -24,21 +56,34 @@ export interface WatchState {
24
56
  wakes: Record<string, number[]>;
25
57
  /** Threads this watcher has seen paused; a marker that vanishes from this list means the human resumed it. */
26
58
  paused: string[];
59
+ /** Failed wake count and completion-based backoff, retained across watcher restarts. */
60
+ wakeRetries: Record<string, {
61
+ failures: number;
62
+ nextAttemptAt: number;
63
+ }>;
64
+ approvalRetries: Record<string, {
65
+ failures: number;
66
+ nextAttemptAt: number;
67
+ }>;
27
68
  /** Posts and notifications that must happen exactly-ish once and survive a restart (retried until they succeed). */
28
69
  outbox: OutboxItem[];
29
70
  held: Record<string, HeldBacklog>;
30
- /** UTC day of the last notification per kind/peer/thread: one of each kind per day. */
31
- peerPausedDay: Record<string, number>;
32
- /** Times of recent notifications per peer, for the shared per-hour cap. */
71
+ /** One fixed record per locally known thread. Peer ids are values, never keys. */
72
+ threadNotices: Record<string, ThreadNoticeState>;
73
+ /** Two coalescing needs-you slots per locally paired peer, allocated only from local configuration. */
74
+ peerNotices: Record<string, Record<NeedsKind, NeedsSlot>>;
75
+ noticeGlobalLog: number[];
76
+ /** Every dispatch attempt, success or failure, per peer for the shared rolling-hour cap. */
33
77
  noticeLog: Record<string, number[]>;
34
78
  }
35
- /** `tries` counts failed attempts; an item that keeps failing is dropped (see `MAX_OUTBOX_TRIES`). */
79
+ /** `tries`: notification dispatch attempts (max 3), or failed inform attempts (max 8). */
36
80
  export type OutboxItem = {
37
81
  id: string;
38
82
  kind: 'inform';
39
83
  peer: string;
40
84
  threadId: string;
41
85
  text: string;
86
+ pauseId?: string;
42
87
  tries?: number;
43
88
  nextAttemptAt?: number;
44
89
  } | {
@@ -87,12 +132,12 @@ export interface WatchDeps {
87
132
  wake: Waker;
88
133
  log: WatchLog;
89
134
  /** Posts the `thread-paused` inform to a peer (own space, `to/<peer>/threads/<id>/msg/`). Default: nothing. */
90
- postInform?(peer: string, threadId: string, text: string): Promise<void>;
135
+ postInform?(peer: string, threadId: string, text: string, pauseId?: string): Promise<void>;
91
136
  /** Tells this side's human without a model (desktop notification or operator command). Default: log only. */
92
137
  notify?: Notifier;
93
138
  /** Local bounded notification log; omitted by library callers that want no disk output. */
94
139
  notificationHome?: string;
95
- /** D mode: mirror/receipt and notify the human, never invoke the wake adapter or spend its budget. */
140
+ /** D mode mirrors and receipts, then notifies without invoking a wake adapter. */
96
141
  notifyOnly?: boolean;
97
142
  /**
98
143
  * Who this watcher is, for wake claims. `profileId` is stable for this profile on this machine (a restart keeps it,
@@ -121,7 +166,6 @@ export declare class Watcher {
121
166
  private pausedKeys;
122
167
  /** Serialises everything that reads or writes pause markers, so a stale listing cannot drop a pause just written. */
123
168
  private pausedLock;
124
- private readonly wakeFailures;
125
169
  private readonly outboxBusy;
126
170
  private approvalsAgain;
127
171
  /** Unique to this run: a claim with this id is this process's own. */
@@ -172,6 +216,8 @@ export declare class Watcher {
172
216
  /** Serialises syncs per peer; a request that arrives mid-sync reruns once after it. */
173
217
  sync(peer: StoredPeer, via: Via): Promise<void>;
174
218
  private syncPeer;
219
+ /** Only locally authored meta/messages/tasks, or a local wake/pause witness, establish a known thread. */
220
+ private syncKnownThreads;
175
221
  private mirrorCreate;
176
222
  /**
177
223
  * meta.json is immutable (create-only); tasks are mutable, so copy the latest until they are terminal. Runs after
@@ -197,8 +243,26 @@ export declare class Watcher {
197
243
  private exclusive;
198
244
  /** Called inside `mutate`: parks a message that arrived while its thread is paused (bounded). */
199
245
  private hold;
200
- /** Called inside `mutate`: queues an inform or notification; the outbox is bounded, the oldest item goes first. */
246
+ /** Two episode values per known thread. Additional transitions coalesce, without allocating another slot. */
247
+ private queueEpisode;
248
+ /** Only the paired handles in our local configuration allocate needs-you state. */
249
+ private syncNoticePeers;
250
+ /** Bounded values only: a later delivery updates the same paired-peer slot. Called inside mutate. */
251
+ private queueNeeds;
252
+ private noticeSlot;
253
+ private rememberNotice;
254
+ /** Earliest rolling-hour retry under the global allowance and each paired peer's share. */
255
+ private noticeRateDeadline;
256
+ /** -1 appends; a nonnegative index yields that place; undefined leaves the pending slot alone. */
257
+ private admissionIndex;
258
+ /** Queue places can yield; the two fixed episode records retain all retry metadata. */
201
259
  private enqueue;
260
+ private outboxCandidates;
261
+ /** Admit local work first, allowing eligible peers to pass deferred entries in a full queue. */
262
+ private fillOutbox;
263
+ private fairOutbox;
264
+ /** Completion matches both the fixed slot and episode ULID; another queued episode is independent. */
265
+ private finishOutbox;
202
266
  /**
203
267
  * Markers under me/state/paused/ are the truth. A marker that disappeared means the human resumed: the budget starts
204
268
  * over and what arrived meanwhile wakes the agent once. Serialised, so overlapping calls never apply a stale listing.
@@ -206,7 +270,7 @@ export declare class Watcher {
206
270
  syncPaused(): Promise<void>;
207
271
  /** State that only matters for a day or an hour is dropped, so it cannot grow without bound. */
208
272
  private pruneState;
209
- /** Informs and notifications queued in state; failures back off (1, 2, 4 ... 15 minutes) and give up after MAX_OUTBOX_TRIES. */
273
+ /** Informs and notifications queued in state; failures back off (1, 2, 4 ... 15 minutes) and give up after 3 notification attempts or 8 inform failures. */
210
274
  private runOutbox;
211
275
  pollOnce(via: Via): Promise<void>;
212
276
  /**