@tinycloud/courier 0.2.0-beta.37 → 0.2.0-beta.39

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.
Files changed (87) hide show
  1. package/README.md +369 -12
  2. package/dist/a2a.d.ts +6 -0
  3. package/dist/a2a.js +10 -0
  4. package/dist/a2a.js.map +1 -1
  5. package/dist/approval.d.ts +6 -5
  6. package/dist/approval.js +7 -16
  7. package/dist/approval.js.map +1 -1
  8. package/dist/args.js +1 -1
  9. package/dist/args.js.map +1 -1
  10. package/dist/broker-config.d.ts +24 -0
  11. package/dist/broker-config.js +99 -0
  12. package/dist/broker-config.js.map +1 -0
  13. package/dist/broker.d.ts +77 -0
  14. package/dist/broker.js +253 -0
  15. package/dist/broker.js.map +1 -0
  16. package/dist/commands.js +5 -2
  17. package/dist/commands.js.map +1 -1
  18. package/dist/consumed.d.ts +10 -0
  19. package/dist/consumed.js +54 -0
  20. package/dist/consumed.js.map +1 -0
  21. package/dist/guard/binaries.d.ts +17 -0
  22. package/dist/guard/binaries.js +89 -0
  23. package/dist/guard/binaries.js.map +1 -0
  24. package/dist/guard/cleanhome.d.ts +27 -0
  25. package/dist/guard/cleanhome.js +111 -0
  26. package/dist/guard/cleanhome.js.map +1 -0
  27. package/dist/guard/command.d.ts +13 -0
  28. package/dist/guard/command.js +158 -0
  29. package/dist/guard/command.js.map +1 -0
  30. package/dist/guard/expansion.d.ts +10 -0
  31. package/dist/guard/expansion.js +74 -0
  32. package/dist/guard/expansion.js.map +1 -0
  33. package/dist/guard/hook.d.ts +38 -0
  34. package/dist/guard/hook.js +124 -0
  35. package/dist/guard/hook.js.map +1 -0
  36. package/dist/guard/install.d.ts +67 -0
  37. package/dist/guard/install.js +193 -0
  38. package/dist/guard/install.js.map +1 -0
  39. package/dist/guard/network.d.ts +7 -0
  40. package/dist/guard/network.js +78 -0
  41. package/dist/guard/network.js.map +1 -0
  42. package/dist/guard/normalize.d.ts +28 -0
  43. package/dist/guard/normalize.js +257 -0
  44. package/dist/guard/normalize.js.map +1 -0
  45. package/dist/guard/paseo.d.ts +30 -0
  46. package/dist/guard/paseo.js +148 -0
  47. package/dist/guard/paseo.js.map +1 -0
  48. package/dist/guard/paths.d.ts +31 -0
  49. package/dist/guard/paths.js +180 -0
  50. package/dist/guard/paths.js.map +1 -0
  51. package/dist/guard/policy.d.ts +65 -0
  52. package/dist/guard/policy.js +1179 -0
  53. package/dist/guard/policy.js.map +1 -0
  54. package/dist/guard/shell.d.ts +55 -0
  55. package/dist/guard/shell.js +727 -0
  56. package/dist/guard/shell.js.map +1 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +2 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/keys.d.ts +4 -1
  61. package/dist/keys.js +7 -2
  62. package/dist/keys.js.map +1 -1
  63. package/dist/main.js +44 -4
  64. package/dist/main.js.map +1 -1
  65. package/dist/node.d.ts +1 -1
  66. package/dist/node.js +3 -3
  67. package/dist/node.js.map +1 -1
  68. package/dist/notify.d.ts +43 -0
  69. package/dist/notify.js +92 -0
  70. package/dist/notify.js.map +1 -0
  71. package/dist/pause.d.ts +32 -0
  72. package/dist/pause.js +111 -0
  73. package/dist/pause.js.map +1 -0
  74. package/dist/profile.d.ts +3 -3
  75. package/dist/profile.js +12 -12
  76. package/dist/profile.js.map +1 -1
  77. package/dist/wake.d.ts +17 -0
  78. package/dist/wake.js +74 -13
  79. package/dist/wake.js.map +1 -1
  80. package/dist/watch-cmd.d.ts +3 -0
  81. package/dist/watch-cmd.js +56 -2
  82. package/dist/watch-cmd.js.map +1 -1
  83. package/dist/watch.d.ts +134 -0
  84. package/dist/watch.js +697 -13
  85. package/dist/watch.js.map +1 -1
  86. package/package.json +1 -1
  87. package/skills/courier/SKILL.md +119 -8
package/dist/watch.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { HookSubscription } from '@tinycloud/node-sdk';
2
2
  import type { StoredPeer } from './profile.js';
3
3
  import { type KV } from './inbox.js';
4
4
  import type { WakeApproval, WakeMessage, Waker, WatchLog } from './wake.js';
5
+ import { type Notice, type Notifier } from './notify.js';
5
6
  export interface PendingWake {
6
7
  peer: string;
7
8
  threadId: string;
@@ -12,6 +13,38 @@ export interface PendingWake {
12
13
  firstSeenAt: number;
13
14
  lastSeenAt: number;
14
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
+ }
15
48
  export interface WatchState {
16
49
  v: 1;
17
50
  /** Keyed `<peer>/<threadId>`. */
@@ -19,11 +52,61 @@ export interface WatchState {
19
52
  /** Recently woken message ids, so a receipt write that failed cannot cause a second wake. */
20
53
  woken: string[];
21
54
  approvalsSeen: string[];
55
+ /** Message wake attempts today (UTC), per `<peer>/<threadId>`, as epoch ms (approval wakes do not count). */
56
+ wakes: Record<string, number[]>;
57
+ /** Threads this watcher has seen paused; a marker that vanishes from this list means the human resumed it. */
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
+ }>;
68
+ /** Posts and notifications that must happen exactly-ish once and survive a restart (retried until they succeed). */
69
+ outbox: OutboxItem[];
70
+ held: Record<string, HeldBacklog>;
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. */
77
+ noticeLog: Record<string, number[]>;
78
+ }
79
+ /** `tries`: notification dispatch attempts (max 3), or failed inform attempts (max 8). */
80
+ export type OutboxItem = {
81
+ id: string;
82
+ kind: 'inform';
83
+ peer: string;
84
+ threadId: string;
85
+ text: string;
86
+ pauseId?: string;
87
+ tries?: number;
88
+ nextAttemptAt?: number;
89
+ } | {
90
+ id: string;
91
+ kind: 'notify';
92
+ notice: Notice;
93
+ tries?: number;
94
+ nextAttemptAt?: number;
95
+ logged?: boolean;
96
+ };
97
+ /** Messages that arrived while a thread was paused; they wake the agent once, together, after `courier resume`. */
98
+ export interface HeldBacklog {
99
+ peer: string;
100
+ threadId: string;
101
+ subject: string;
102
+ messages: WakeMessage[];
22
103
  }
23
104
  export interface WatchOptions {
24
105
  debounceMs: number;
25
106
  pollMs: number;
26
107
  ticketSeconds: number;
108
+ /** Spec §8: at most this many wakes per side per thread per 24 h, then the thread pauses. */
109
+ wakeBudget: number;
27
110
  }
28
111
  export declare const WATCH_DEFAULTS: WatchOptions;
29
112
  interface HookEventLike {
@@ -48,6 +131,14 @@ export interface WatchDeps {
48
131
  kvFor(peer: StoredPeer): Promise<KV>;
49
132
  wake: Waker;
50
133
  log: WatchLog;
134
+ /** Posts the `thread-paused` inform to a peer (own space, `to/<peer>/threads/<id>/msg/`). Default: nothing. */
135
+ postInform?(peer: string, threadId: string, text: string, pauseId?: string): Promise<void>;
136
+ /** Tells this side's human without a model (desktop notification or operator command). Default: log only. */
137
+ notify?: Notifier;
138
+ /** Local bounded notification log; omitted by library callers that want no disk output. */
139
+ notificationHome?: string;
140
+ /** D mode mirrors and receipts, then notifies without invoking a wake adapter. */
141
+ notifyOnly?: boolean;
51
142
  /**
52
143
  * Who this watcher is, for wake claims. `profileId` is stable for this profile on this machine (a restart keeps it,
53
144
  * a second profile or machine differs); `pid` and `isAlive` tell a restarted watcher its predecessor is dead.
@@ -71,6 +162,11 @@ export declare class Watcher {
71
162
  private readonly retryAt;
72
163
  private readonly waking;
73
164
  private approvalsRunning;
165
+ /** Thread keys currently paused: markers under me/state/paused/ plus those this watcher just wrote. */
166
+ private pausedKeys;
167
+ /** Serialises everything that reads or writes pause markers, so a stale listing cannot drop a pause just written. */
168
+ private pausedLock;
169
+ private readonly outboxBusy;
74
170
  private approvalsAgain;
75
171
  /** Unique to this run: a claim with this id is this process's own. */
76
172
  private readonly runId;
@@ -120,6 +216,8 @@ export declare class Watcher {
120
216
  /** Serialises syncs per peer; a request that arrives mid-sync reruns once after it. */
121
217
  sync(peer: StoredPeer, via: Via): Promise<void>;
122
218
  private syncPeer;
219
+ /** Only locally authored meta/messages/tasks, or a local wake/pause witness, establish a known thread. */
220
+ private syncKnownThreads;
123
221
  private mirrorCreate;
124
222
  /**
125
223
  * meta.json is immutable (create-only); tasks are mutable, so copy the latest until they are terminal. Runs after
@@ -137,7 +235,43 @@ export declare class Watcher {
137
235
  private approvalWake;
138
236
  /** Wake every thread whose debounce has elapsed (`force`: all of them now). Threads wake in parallel, one wake per thread at a time. */
139
237
  tick(force?: boolean): Promise<void>;
238
+ /** Spec §8: the budget is per side, per thread, per day. A day is the UTC calendar day, so it is easy to state and to check. */
239
+ private recentWakes;
140
240
  private wakeThread;
241
+ /** Budget reached: pause this side's wakes for the thread, tell the peer (inform), tell this side's human. */
242
+ private pauseThread;
243
+ private exclusive;
244
+ /** Called inside `mutate`: parks a message that arrived while its thread is paused (bounded). */
245
+ private hold;
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. */
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;
266
+ /**
267
+ * Markers under me/state/paused/ are the truth. A marker that disappeared means the human resumed: the budget starts
268
+ * over and what arrived meanwhile wakes the agent once. Serialised, so overlapping calls never apply a stale listing.
269
+ */
270
+ syncPaused(): Promise<void>;
271
+ /** State that only matters for a day or an hour is dropped, so it cannot grow without bound. */
272
+ private pruneState;
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. */
274
+ private runOutbox;
141
275
  pollOnce(via: Via): Promise<void>;
142
276
  /**
143
277
  * Runs until `signal` aborts. With `once`, does one catch-up and wakes everything immediately. Throws