@astrosheep/square 0.4.0 → 0.4.1

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 (187) hide show
  1. package/dist/artifact.d.ts +1 -3
  2. package/dist/artifact.js +12 -36
  3. package/dist/attention-presentation.d.ts +2 -22
  4. package/dist/attention-presentation.js +9 -12
  5. package/dist/boundary-presentation.d.ts +3 -14
  6. package/dist/boundary-presentation.js +29 -104
  7. package/dist/card-presentation.d.ts +1 -0
  8. package/dist/card-presentation.js +2 -0
  9. package/dist/catch-decisions.js +1 -7
  10. package/dist/claude-delivery.d.ts +13 -8
  11. package/dist/claude-delivery.js +40 -93
  12. package/dist/claude-mcp-context.js +2 -2
  13. package/dist/claude-mod.js +28 -140
  14. package/dist/claude-transport.d.ts +3 -3
  15. package/dist/claude-transport.js +20 -44
  16. package/dist/cli/context.js +7 -9
  17. package/dist/cli/observation-commands.d.ts +2 -0
  18. package/dist/cli/observation-commands.js +12 -2
  19. package/dist/cli/registry.js +3 -1
  20. package/dist/codex-address.d.ts +2 -2
  21. package/dist/codex-boundary-state.d.ts +1 -5
  22. package/dist/codex-boundary-state.js +0 -4
  23. package/dist/codex-hook.d.ts +7 -10
  24. package/dist/codex-hook.js +69 -33
  25. package/dist/codex-queue.d.ts +10 -11
  26. package/dist/codex-queue.js +32 -32
  27. package/dist/codex-watch.d.ts +2 -0
  28. package/dist/codex-watch.js +100 -0
  29. package/dist/decisions.js +2 -2
  30. package/dist/delivery-deferral.d.ts +8 -17
  31. package/dist/delivery-deferral.js +0 -28
  32. package/dist/delivery-frame.d.ts +7 -0
  33. package/dist/delivery-frame.js +11 -0
  34. package/dist/delivery-ledger-sqlite.d.ts +97 -0
  35. package/dist/delivery-ledger-sqlite.js +496 -0
  36. package/dist/delivery-ledger.d.ts +196 -0
  37. package/dist/delivery-ledger.js +268 -0
  38. package/dist/delivery-lifetime.d.ts +6 -0
  39. package/dist/delivery-lifetime.js +45 -0
  40. package/dist/harness-links.js +61 -7
  41. package/dist/harness.js +12 -17
  42. package/dist/help.js +2 -0
  43. package/dist/http-delivery.d.ts +5 -9
  44. package/dist/http-delivery.js +20 -77
  45. package/dist/inbox.d.ts +6 -49
  46. package/dist/inbox.js +24 -371
  47. package/dist/index.d.ts +6 -3
  48. package/dist/index.js +4 -1
  49. package/dist/mcp.js +1 -1
  50. package/dist/model.d.ts +7 -47
  51. package/dist/model.js +1 -7
  52. package/dist/notifications.d.ts +24 -20
  53. package/dist/notifications.js +39 -108
  54. package/dist/operation-control.d.ts +4 -0
  55. package/dist/operation-control.js +20 -0
  56. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  57. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  58. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  59. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  60. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  61. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  62. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  63. package/dist/packages/agent-delivery/src/codex.js +41 -4
  64. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  65. package/dist/packages/agent-delivery/src/index.js +1 -1
  66. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  67. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  68. package/dist/paseo-delivery.d.ts +5 -5
  69. package/dist/paseo-delivery.js +19 -97
  70. package/dist/paseo.d.ts +1 -1
  71. package/dist/paseo.js +1 -1
  72. package/dist/plugins/opencode/index.d.ts +5 -3
  73. package/dist/plugins/opencode/index.js +99 -21
  74. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  75. package/dist/plugins/opencode-shared/delivery.js +109 -0
  76. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  77. package/dist/plugins/opencode-v1/index.js +41 -0
  78. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  79. package/dist/plugins/opencode-v1/runtime.js +152 -0
  80. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  81. package/dist/plugins/opencode-v1/transport.js +50 -0
  82. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  83. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  84. package/dist/ports.d.ts +31 -90
  85. package/dist/session-lifecycle.d.ts +8 -7
  86. package/dist/session-lifecycle.js +40 -42
  87. package/dist/sqlite-square.d.ts +14 -23
  88. package/dist/sqlite-square.js +132 -139
  89. package/dist/square-api.js +4 -7
  90. package/dist/square-core.d.ts +0 -1
  91. package/dist/square-domain.d.ts +5 -17
  92. package/dist/square-domain.js +104 -221
  93. package/dist/square-facade.d.ts +18 -15
  94. package/dist/square-post.d.ts +53 -0
  95. package/dist/square-post.js +4 -0
  96. package/dist/square-posting.d.ts +13 -0
  97. package/dist/square-posting.js +174 -0
  98. package/dist/square-producer-sqlite.d.ts +11 -0
  99. package/dist/square-producer-sqlite.js +210 -0
  100. package/dist/square-sqlite.d.ts +15 -25
  101. package/dist/square-sqlite.js +150 -226
  102. package/dist/views.d.ts +2 -2
  103. package/dist/views.js +0 -1
  104. package/dist/wake-sink.d.ts +4 -11
  105. package/dist/wake-sink.js +8 -32
  106. package/dist/wake-transports.d.ts +4 -7
  107. package/dist/wake-transports.js +8 -39
  108. package/examples/symmetric-domains.mjs +29 -249
  109. package/guides/change-waits.md +13 -53
  110. package/guides/claude-native.md +19 -175
  111. package/guides/codex-delivery.md +55 -80
  112. package/guides/delivery-primitives.md +48 -207
  113. package/guides/operation-cancellation.md +2 -5
  114. package/guides/participant-ownership.md +25 -89
  115. package/guides/pi-native-delivery.md +19 -56
  116. package/guides/pi-tools.md +6 -8
  117. package/guides/square-api.md +113 -615
  118. package/package.json +8 -8
  119. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  120. package/plugins/claude/hooks/hooks.json +1 -1
  121. package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
  122. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  123. package/plugins/codex/hooks/hooks.json +11 -23
  124. package/plugins/opencode/index.ts +91 -23
  125. package/plugins/opencode-shared/delivery.ts +91 -0
  126. package/plugins/opencode-v1/index.ts +36 -0
  127. package/plugins/opencode-v1/runtime.ts +134 -0
  128. package/plugins/opencode-v1/transport.ts +41 -0
  129. package/plugins/paseo/OVERVIEW.md +5 -5
  130. package/plugins/paseo/README.md +7 -6
  131. package/plugins/paseo/client/markdown.tsx +5 -5
  132. package/plugins/paseo/client/square-message.tsx +28 -24
  133. package/plugins/paseo/client/transform.ts +8 -5
  134. package/plugins/paseo/shared/card-presentation.ts +16 -0
  135. package/plugins/paseo/shared/message.ts +6 -11
  136. package/plugins/pi/square-pi.js +156 -417
  137. package/dist/channel-artifact.d.ts +0 -10
  138. package/dist/channel-artifact.js +0 -55
  139. package/dist/channel-delivery.d.ts +0 -11
  140. package/dist/channel-delivery.js +0 -51
  141. package/dist/channel-domain.d.ts +0 -3
  142. package/dist/channel-domain.js +0 -83
  143. package/dist/channel-facade.d.ts +0 -76
  144. package/dist/channel-facade.js +0 -1
  145. package/dist/channel-internal.d.ts +0 -126
  146. package/dist/channel-internal.js +0 -440
  147. package/dist/channel-metadata.d.ts +0 -11
  148. package/dist/channel-metadata.js +0 -1
  149. package/dist/channel-ports.d.ts +0 -241
  150. package/dist/channel-ports.js +0 -1
  151. package/dist/channel-presentation.d.ts +0 -17
  152. package/dist/channel-presentation.js +0 -22
  153. package/dist/channel-sqlite-schema.d.ts +0 -13
  154. package/dist/channel-sqlite-schema.js +0 -81
  155. package/dist/channel-transport.d.ts +0 -62
  156. package/dist/channel-transport.js +0 -1
  157. package/dist/channel.d.ts +0 -84
  158. package/dist/channel.js +0 -60
  159. package/dist/claude-delivery-status.d.ts +0 -13
  160. package/dist/claude-delivery-status.js +0 -46
  161. package/dist/cmd/notify-once.d.ts +0 -2
  162. package/dist/cmd/notify-once.js +0 -29
  163. package/dist/delivery-attempts.d.ts +0 -52
  164. package/dist/delivery-attempts.js +0 -73
  165. package/dist/delivery-effects.d.ts +0 -139
  166. package/dist/delivery-effects.js +0 -163
  167. package/dist/delivery-health.d.ts +0 -51
  168. package/dist/delivery-health.js +0 -173
  169. package/dist/delivery-operations.d.ts +0 -22
  170. package/dist/delivery-operations.js +0 -198
  171. package/dist/delivery-primitives.d.ts +0 -5
  172. package/dist/delivery-primitives.js +0 -3
  173. package/dist/delivery.d.ts +0 -56
  174. package/dist/delivery.js +0 -80
  175. package/dist/plugins/opencode/delivery.d.ts +0 -19
  176. package/dist/plugins/opencode/delivery.js +0 -223
  177. package/dist/presentation-operations.d.ts +0 -5
  178. package/dist/presentation-operations.js +0 -86
  179. package/dist/sqlite-channel.d.ts +0 -15
  180. package/dist/sqlite-channel.js +0 -600
  181. package/dist/square-index.d.ts +0 -16
  182. package/dist/square-index.js +0 -106
  183. package/dist/wake-preview.d.ts +0 -7
  184. package/dist/wake-preview.js +0 -15
  185. package/plugins/opencode/delivery.ts +0 -198
  186. /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
  187. /package/dist/{channel-values.js → square-values.js} +0 -0
package/dist/inbox.js CHANGED
@@ -1,382 +1,35 @@
1
- import { createArtifactPort, observeSquareDeliveryRevision, observeSquareRevision } from './artifact.js';
2
- import { indexedSquareEntries, observeSquareIndex } from './square-index.js';
1
+ import path from 'node:path';
2
+ import { createArtifactPort } from './artifact.js';
3
+ import { discoverSquares, MAX_LIST_LIMIT } from './list.js';
3
4
  import { canonicalPath } from './canonical-path.js';
4
- import { formatMessageId } from './message.js';
5
- import { freshWatchLease, WATCH_STALE_MS } from './runtime.js';
6
- import { nativePendingHead, pendingHeads } from './delivery.js';
7
- import { deliveryDeferralExpiry } from './delivery-deferral.js';
8
- import { projectMessageNotification, renderSquareReminder } from './attention-presentation.js';
9
- export function pendingKey(membership, messageIndex) {
10
- return membership.location + '\0' + membership.seat.participant.toLocaleLowerCase() + '\0' + messageIndex;
11
- }
12
- function eligibleNotifications(facts, membership, now = Date.now()) {
13
- const seat = facts.seat(membership.seat.participant);
14
- if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== membership.seat.generation
15
- || seat.owner !== membership.seat.owner)
16
- return { notifications: [] };
17
- const cancelledThrough = facts.channel.cancelledThrough(seat.participant, seat.generation);
18
- let expiry = Infinity;
19
- const notifications = membership.notifications.filter(notification => {
20
- if (cancelledThrough !== undefined && notification.messageIndex <= cancelledThrough
21
- || facts.deliveryEvidence(seat.participant, seat.generation, formatMessageId(notification.messageIndex))
22
- .some(row => row.outcome !== 'failed' && row.outcome !== 'released'))
23
- return false;
24
- const deferredUntil = deliveryDeferralExpiry(facts.channel, seat, notification.messageIndex, now);
25
- if (deferredUntil === undefined)
26
- return true;
27
- expiry = Math.min(expiry, deferredUntil);
28
- return false;
29
- });
30
- return { notifications, ...(expiry === Infinity ? {} : { deliveryDeferralExpiry: expiry }) };
31
- }
32
- /** Revalidate a supplied batch immediately before the boundary's independent claim/send checks. */
33
- export async function projectBoundaryEligibility(inbox) {
34
- const grouped = new Map();
35
- for (const membership of inbox.filter(candidate => candidate.notifications.length > 0)) {
36
- const entries = grouped.get(membership.location) ?? [];
37
- entries.push(membership);
38
- grouped.set(membership.location, entries);
39
- }
40
- const pending = [];
41
- for (const [location, memberships] of grouped) {
42
- const artifact = createArtifactPort(location);
43
- try {
44
- const { value } = await artifact.read(facts => memberships.flatMap(membership => {
45
- const eligibility = eligibleNotifications(facts, membership);
46
- return eligibility.notifications.length === 0 ? [] : [{ ...membership, ...eligibility }];
47
- }));
48
- pending.push(...value);
49
- }
50
- catch {
51
- // An unreadable or stale candidate cannot authorize boundary presentation.
52
- }
53
- finally {
54
- await artifact.close();
55
- }
56
- }
57
- return pending;
58
- }
59
- /** HOME is a candidate path index; current seats and delivery evidence live in the artifact. */
60
- export async function projectSessionCandidates(input) {
61
- const location = input.location === undefined ? undefined : await canonicalPath(input.location);
62
- const entries = await indexedSquareEntries({ owner: input.owner }, input.env ?? process.env);
63
- const candidates = new Map();
64
- for (const entry of entries) {
65
- if (location !== undefined && location !== entry.location)
66
- continue;
67
- const key = JSON.stringify([entry.location, entry.owner]);
68
- candidates.set(key, { location: entry.location, owner: entry.owner });
69
- }
70
- return [...candidates.values()];
71
- }
72
- function groupCandidates(candidates) {
73
- const grouped = new Map();
74
- for (const { location, owner } of candidates) {
75
- const owners = grouped.get(location) ?? [];
76
- owners.push(owner);
77
- grouped.set(location, owners);
78
- }
79
- return grouped;
80
- }
81
- function locatedSeats(facts, location, seats) {
82
- return seats.flatMap(seat => {
83
- const participant = facts.participant(seat.participant);
84
- const index = participant?.lastJoinIndex;
85
- const joined = index === undefined ? undefined : facts.activity(index);
86
- return joined?.kind === 'join' ? [{ location, seat, joinedAt: joined.at }] : [{ location, seat, joinedAt: 0 }];
87
- });
88
- }
89
- /** Lifecycle reads resolve seats and join times, never pending activity bodies. */
90
- export async function projectSessionSeats(input, signal) {
91
- const seats = [];
92
- for (const [location, owners] of groupCandidates(await projectSessionCandidates(input))) {
93
- signal?.throwIfAborted();
94
- const artifact = createArtifactPort(location, undefined, signal);
95
- try {
96
- seats.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner))), signal)).value);
97
- }
98
- catch (error) {
99
- if (signal?.aborted)
100
- throw signal.reason ?? error;
101
- }
102
- finally {
103
- await artifact.close();
104
- }
105
- }
106
- return seats;
107
- }
108
- /** Public count boundary: one coherent read per artifact, without pending body hydration. */
5
+ /** Filesystem-scoped membership counts. This is a World read, not a delivery scan. */
109
6
  export async function sessionInboxCounts(input, signal) {
7
+ const root = path.resolve(input.cwd ?? process.cwd());
110
8
  const rows = [];
111
- for (const [location, owners] of groupCandidates(await projectSessionCandidates(input))) {
9
+ let after;
10
+ do {
112
11
  signal?.throwIfAborted();
113
- const artifact = createArtifactPort(location, undefined, signal);
114
- try {
115
- rows.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner).filter(seat => seat.attachment === 'attached'))).map(({ seat }) => ({
116
- name: seat.participant, squarePath: location, pending: facts.directedCount(seat.participant, { unseenOnly: true, currentGeneration: true }),
117
- })), signal)).value);
118
- }
119
- catch (error) {
120
- if (signal?.aborted)
121
- throw signal.reason ?? error;
122
- }
123
- finally {
124
- await artifact.close();
125
- }
126
- }
127
- return rows;
128
- }
129
- /** One authoritative read per canonical artifact, even when discovery names several bodies. */
130
- async function projectPending(candidates, cache, signal) {
131
- const pending = [];
132
- for (const [location, owners] of groupCandidates(candidates)) {
133
- signal?.throwIfAborted();
134
- const artifact = createArtifactPort(location, undefined, signal);
135
- try {
136
- const { value: current } = await artifact.read(facts => {
137
- const seats = owners.flatMap(owner => facts.seatsForOwner(owner).filter(seat => seat.attachment === 'attached'));
138
- const coordinate = JSON.stringify([owners, seats.map(seat => [seat.participant, seat.owner, seat.generation])]);
139
- const header = facts.header();
140
- let cached = cache?.get(location);
141
- if (cached?.coordinate !== coordinate || cached.version !== header.revision) {
142
- cached = { coordinate, version: header.revision, value: locatedSeats(facts, location, seats).map(membership => {
143
- const heads = pendingHeads(facts, membership.seat.participant, Date.now(), false);
144
- const messages = new Map(facts.channel.messages(heads.map(head => head.index)).map(item => [item.index, item]));
145
- return { ...membership, notifications: heads.flatMap(head => {
146
- const item = messages.get(head.index);
147
- if (item === undefined)
148
- return [];
149
- return [{ messageIndex: item.index, ...(head.activityIndex === undefined ? {} : { actIndex: head.activityIndex }),
150
- actor: item.from, at: item.at, body: item.body, ...(item.preview === undefined ? {} : { preview: item.preview }), ...(head.route === undefined ? {} : { route: head.route }) }];
151
- }) };
152
- }) };
153
- }
154
- // Cache conversation content, never leases or delivery eligibility: evidence can change
155
- // without a conversation revision and must not force body hydration or self-wake retries.
156
- return { cached, pending: cached.value.map(membership => {
157
- const catchLease = freshWatchLease(facts, membership.seat.participant);
158
- // Cached conversation content, but claim coverage and deferrals are re-read on every projection.
159
- const observed = { ...membership, notifications: membership.notifications.filter(note => !facts.channel.suppressed(membership.seat.participant, membership.seat.generation, note.messageIndex, Date.now())) };
160
- return { ...observed, ...eligibleNotifications(facts, observed),
161
- ...(catchLease === undefined ? {} : { catchLease }) };
162
- }) };
163
- }, signal);
164
- cache?.set(location, current.cached);
165
- pending.push(...current.pending);
166
- }
167
- catch (error) {
168
- cache?.delete(location);
169
- if (signal?.aborted)
170
- throw signal.reason ?? error;
171
- // Unreadable candidates remain watch targets, but never authorize cached content.
172
- }
173
- finally {
174
- await artifact.close();
175
- }
176
- }
177
- return pending;
178
- }
179
- /** One current eligibility + source projection read; senders use text, ACK coverage uses clipped. */
180
- export function nativePendingSource(facts, input) {
181
- const head = nativePendingHead(facts, input);
182
- if (head === undefined)
183
- return undefined;
184
- const item = facts.channel.message(input.index);
185
- if (item === undefined)
186
- return undefined;
187
- const projected = projectMessageNotification({ squarePath: input.location, recipient: input.participant,
188
- actor: item.from, body: item.body, ...(item.preview === undefined ? {} : { preview: item.preview }),
189
- ...(head.activityIndex === undefined || head.route === undefined ? {} : { agora: { actIndex: head.activityIndex, route: head.route } }) });
190
- return { text: projected.text, clipped: projected.clipped };
191
- }
192
- /** The sender-facing fixed reminder over the shared source projection. */
193
- export function nativePendingPreview(facts, input) {
194
- const source = nativePendingSource(facts, input);
195
- return source === undefined ? undefined : { payload: renderSquareReminder([source.text]), clipped: source.clipped };
196
- }
197
- export async function sessionInbox(owner, env = process.env, signal) {
198
- return projectPending(await projectSessionCandidates({ owner, env }), undefined, signal);
199
- }
200
- async function capture(observer, kind, signal) {
201
- const generation = observer.generation;
202
- try {
203
- const value = await observer.read(signal);
204
- return { kind, value, wait: (timeout, abort) => observer.changed(value, timeout, abort) };
205
- }
206
- catch (error) {
207
- if (signal.aborted)
208
- throw signal.reason ?? error;
209
- // Failed revision reads are not changes; only a successful retry wakes projection.
210
- return { kind, wait: async (timeout, abort) => {
211
- const deadline = Date.now() + Math.max(0, timeout);
212
- let since = generation;
213
- while (await observer.hinted(since, Math.max(0, deadline - Date.now()), abort)) {
214
- since = observer.generation;
215
- try {
216
- await observer.read(abort);
217
- return true;
218
- }
219
- catch (error) {
220
- if (abort.aborted)
221
- throw abort.reason ?? error;
222
- }
223
- }
224
- return false;
225
- } };
226
- }
227
- }
228
- /** Session-scoped projection over process-shared storage change detectors. */
229
- export async function observeSessionPending(owner, suppliedEnv = process.env) {
230
- const env = { ...suppliedEnv };
231
- const query = { owner, env };
232
- const index = await observeSquareIndex(env);
233
- const artifacts = new Map();
234
- const cache = new Map();
235
- const lifetime = new AbortController();
236
- async function currentInbox(candidates, signal) {
237
- const inbox = await projectPending(candidates, cache, signal);
238
- const now = Date.now();
239
- return inbox.map((membership) => {
240
- const { catchLease, ...rest } = membership;
241
- return catchLease !== undefined && catchLease.expiresAt > now && now - catchLease.heartbeatAt <= WATCH_STALE_MS
242
- ? membership : rest;
243
- });
244
- }
245
- function eligibleInbox(inbox, excludeKeys) {
246
- const eligible = inbox.filter(membership => membership.notifications.length > 0);
247
- return excludeKeys === undefined ? eligible : eligible.flatMap((membership) => {
248
- const notifications = membership.notifications.filter((notification) => !excludeKeys.has(pendingKey(membership, notification.messageIndex)));
249
- return notifications.length === 0 ? [] : [{ ...membership, notifications }];
250
- });
251
- }
252
- return {
253
- async read(options = {}) {
254
- const signal = AbortSignal.any([lifetime.signal, ...(options.signal === undefined ? [] : [options.signal])]);
255
- signal.throwIfAborted();
256
- const inbox = await currentInbox(await projectSessionCandidates(query), signal);
257
- const pending = eligibleInbox(inbox, options.excludeKeys);
258
- signal.throwIfAborted();
259
- return pending;
260
- },
261
- async wait(timeoutMs, options = {}) {
262
- const deadline = Date.now() + Math.max(0, timeoutMs);
263
- const expired = new AbortController();
264
- const timer = Number.isFinite(timeoutMs) && timeoutMs > 0
265
- ? setTimeout(() => expired.abort(), Math.min(timeoutMs, 2_147_483_647)) : undefined;
266
- const signal = AbortSignal.any([lifetime.signal, expired.signal, ...(options.signal === undefined ? [] : [options.signal])]);
267
- let canReturn = !options.skipImmediate;
268
- let previousEligible;
269
- let armed = false;
270
- let candidates = [];
271
- let candidatePresence;
272
- let discoveredCandidates = false;
12
+ const page = await discoverSquares(root, { limit: MAX_LIST_LIMIT, ...(after === undefined ? {} : { after }) }, { signal });
13
+ for (const item of page.items) {
14
+ signal?.throwIfAborted();
15
+ const location = await canonicalPath(path.resolve(root, item.path));
16
+ const artifact = createArtifactPort(location, undefined, signal);
273
17
  try {
274
- while (!signal.aborted) {
275
- // Subscribe to the candidate index before discovering artifacts; a concurrent join can
276
- // publish its path without touching the artifact revision.
277
- const sources = [await capture(index, 'index', signal)];
278
- const presence = sources[0];
279
- const currentPresence = presence.value;
280
- if (!discoveredCandidates || currentPresence !== undefined && !Object.is(currentPresence, candidatePresence)) {
281
- candidates = await projectSessionCandidates(query);
282
- candidatePresence = currentPresence;
283
- discoveredCandidates = true;
284
- }
285
- const paths = new Set(candidates.map((candidate) => candidate.location));
286
- for (const [file, entry] of artifacts) {
287
- if (!paths.has(file)) {
288
- entry.observer.close();
289
- entry.delivery.close();
290
- artifacts.delete(file);
291
- cache.delete(file);
292
- }
293
- }
294
- for (const file of paths) {
295
- if (artifacts.has(file))
296
- continue;
297
- const observer = await observeSquareRevision(file);
298
- const delivery = await observeSquareDeliveryRevision(file);
299
- if (signal.aborted) {
300
- observer.close();
301
- delivery.close();
302
- return [];
303
- }
304
- artifacts.set(file, { observer, delivery });
305
- }
306
- // Capture both revision baselines before selecting seats and pending facts. A takeover
307
- // during an empty selection must remain visible to the already armed observers.
308
- for (const entry of artifacts.values()) {
309
- sources.push(await capture(entry.observer, 'artifact', signal));
310
- sources.push(await capture(entry.delivery, 'delivery', signal));
311
- }
312
- const inbox = await currentInbox(candidates, signal);
313
- const pending = eligibleInbox(inbox, options.excludeKeys);
314
- const eligibleKeys = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => pendingKey(membership, note.messageIndex))).sort());
315
- if (previousEligible !== undefined && previousEligible !== eligibleKeys)
316
- canReturn = true;
317
- previousEligible = eligibleKeys;
318
- if (signal.aborted)
319
- return [];
320
- if (canReturn && pending.length > 0)
321
- return pending;
322
- const remaining = deadline - Date.now();
323
- if (remaining <= 0)
324
- return [];
325
- // Eligibility may change at lease expiry without any storage write.
326
- const leaseExpiry = Math.min(Infinity, ...inbox.flatMap((membership) => membership.catchLease === undefined ? [] : [Math.min(membership.catchLease.expiresAt, membership.catchLease.heartbeatAt + WATCH_STALE_MS + 1)]));
327
- const nextExpiry = Math.min(leaseExpiry, ...inbox.map(membership => membership.deliveryDeferralExpiry ?? Infinity));
328
- const duration = Math.min(remaining, Math.max(1, nextExpiry - Date.now()));
329
- const cancelled = new AbortController();
330
- const waitSignal = AbortSignal.any([signal, cancelled.signal]);
331
- const waits = sources.map(async (source) => {
332
- try {
333
- return await source.wait(duration, waitSignal) ? source.kind : 'deadline';
334
- }
335
- catch (error) {
336
- if (waitSignal.aborted)
337
- throw error;
338
- return source.kind;
339
- }
340
- });
341
- if (!armed) {
342
- armed = true;
343
- options.onChangeArmed?.(true);
344
- }
345
- try {
346
- const change = await Promise.race(waits);
347
- if (change !== 'delivery')
348
- canReturn = true;
349
- }
350
- finally {
351
- cancelled.abort();
352
- await Promise.allSettled(waits);
353
- }
354
- }
355
- return [];
18
+ rows.push(...(await artifact.read(facts => facts.seatsForOwner(input.owner)
19
+ .filter(seat => facts.participant(seat.participant)?.joined)
20
+ .map(seat => ({ name: seat.participant, squarePath: location,
21
+ pending: facts.directedCount(seat.participant, { unseenOnly: true, currentGeneration: true }) })), signal)).value);
356
22
  }
357
23
  catch (error) {
358
- if (signal.aborted)
359
- return [];
360
- throw error;
24
+ if (signal?.aborted)
25
+ throw signal.reason ?? error;
26
+ // Discovery candidates may disappear or become unreadable before this read.
361
27
  }
362
28
  finally {
363
- if (timer !== undefined)
364
- clearTimeout(timer);
365
- if (!armed)
366
- options.onChangeArmed?.(false);
29
+ await artifact.close();
367
30
  }
368
- },
369
- close() {
370
- if (lifetime.signal.aborted)
371
- return;
372
- lifetime.abort();
373
- index.close();
374
- for (const entry of artifacts.values()) {
375
- entry.observer.close();
376
- entry.delivery.close();
377
- }
378
- artifacts.clear();
379
- cache.clear();
380
- },
381
- };
31
+ }
32
+ after = page.next;
33
+ } while (after !== undefined);
34
+ return rows;
382
35
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare } from './square-api.js';
2
2
  export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
3
- export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
4
- export type { WakeTransportPort, WakeRequest, WakeOutcome } from './ports.js';
5
- export type { WakeTransports } from './notifications.js';
3
+ export { selectOwnSeat, requireOwner, requireJoinName, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
4
+ export { postAndDispatch, dispatchEligibleCards } from './notifications.js';
5
+ export { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
6
+ export type * from './delivery-ledger.js';
7
+ export { isCardPresentation } from './card-presentation.js';
8
+ export type { CardPresentation } from './card-presentation.js';
6
9
  export type * from './square-facade.js';
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare } from './square-api.js';
2
2
  export { SquareError, ACTIVITY_METADATA_MAX_BYTES } from './model.js';
3
- export { selectOwnSeat, requireOwner, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
3
+ export { selectOwnSeat, requireOwner, requireJoinName, localUserOwner, harnessOwner, decodeHarnessOwner } from './square-api.js';
4
+ export { postAndDispatch, dispatchEligibleCards } from './notifications.js';
5
+ export { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
6
+ export { isCardPresentation } from './card-presentation.js';
package/dist/mcp.js CHANGED
@@ -51,7 +51,7 @@ export function createSquareMcpServer(options) {
51
51
  ? wakeAddress : undefined;
52
52
  return {
53
53
  identity: { owner, wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
54
- ? codexWakeAddress(threadId, env) : callWake },
54
+ ? { ...codexWakeAddress(threadId, env), kind: 'codex-queue' } : callWake },
55
55
  open: (location, control) => Square.at({
56
56
  ...resourceOptions, env, cwd: claude?.cwd ?? resourceOptions.cwd,
57
57
  path: location ?? options.path,
package/dist/model.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import type { Act, ActivityId } from './square-core.js';
2
- import type { EndpointGeneration } from './message.js';
3
2
  import type { Owner } from './neutral.js';
4
- import type { MessageMetadata as ActivityMetadata, WakeAddress } from './channel-values.js';
5
- export { WAKE_ROUTE_KINDS, MESSAGE_METADATA_MAX_BYTES as ACTIVITY_METADATA_MAX_BYTES, MESSAGE_METADATA_MAX_DEPTH as ACTIVITY_METADATA_MAX_DEPTH, validateMessageMetadata as validateActivityMetadata, messageMetadataHasContent as activityMetadataHasContent, isWakeRouteKind, sameName, NAME_GRAPHEME_SOURCE, validateName } from './channel-values.js';
6
- export type { WakeRouteKind, JsonValue, MessageMetadata as ActivityMetadata, WakeAddress, WakeRoute } from './channel-values.js';
3
+ import type { WakeAddress } from './square-values.js';
4
+ export { WAKE_ROUTE_KINDS, MESSAGE_METADATA_MAX_BYTES as ACTIVITY_METADATA_MAX_BYTES, MESSAGE_METADATA_MAX_DEPTH as ACTIVITY_METADATA_MAX_DEPTH, validateMessageMetadata as validateActivityMetadata, messageMetadataHasContent as activityMetadataHasContent, isWakeRouteKind, sameName, NAME_GRAPHEME_SOURCE, validateName } from './square-values.js';
5
+ export type { WakeRouteKind, JsonValue, MessageMetadata as ActivityMetadata, WakeAddress, WakeRoute } from './square-values.js';
7
6
  export { SquareErrorBase, SquareError, InternalSquareError, isSquareError, validateOwner, nameKey } from './neutral.js';
8
7
  export type { Owner, SquareErrorCode, InternalSquareErrorCode, SquareErrorFacts } from './neutral.js';
9
8
  export type { Act, ActivityId, Audience, Reach } from './square-core.js';
@@ -11,7 +10,7 @@ export type { Act, ActivityId, Audience, Reach } from './square-core.js';
11
10
  export interface Seat {
12
11
  readonly participant: string;
13
12
  readonly owner: Owner;
14
- readonly generation: EndpointGeneration;
13
+ readonly generation: string;
15
14
  readonly attachment: 'attached' | 'detached';
16
15
  readonly wakeAddress?: WakeAddress;
17
16
  }
@@ -30,26 +29,12 @@ export interface BuildOptions {
30
29
  export type ActDraft = Act & {
31
30
  at: number;
32
31
  };
33
- /** Persisted say: identity and social facts only. Its content lives in the linked Channel message. */
34
- export type SayRelation = Omit<Extract<Act, {
35
- kind: 'say';
36
- }>, 'body' | 'metadata'> & {
32
+ /** A landed activity owns its content and social facts. */
33
+ export type StoredAct = Act & {
37
34
  index: number;
38
35
  at: number;
39
- messageIndex: number;
40
- };
41
- /** A landed upper activity. A say is a relation; it carries no content of its own. */
42
- export type StoredAct = Extract<Act, {
43
- kind: 'join' | 'done' | 'read' | 'listen' | 'ignore' | 'unlisten';
44
- }> & {
45
- index: number;
46
- at: number;
47
- } | SayRelation;
48
- /** Explicit upper projection: a stored say resolved against its authoritative message. */
49
- export type HydratedAct = Exclude<StoredAct, SayRelation> | SayRelation & {
50
- body?: string;
51
- metadata?: ActivityMetadata;
52
36
  };
37
+ export type HydratedAct = StoredAct;
53
38
  export type ObservationState = 'seen';
54
39
  export interface ActivityObservation {
55
40
  state: ObservationState;
@@ -66,31 +51,6 @@ export interface WatchLease {
66
51
  filter?: WatchLeaseFilter;
67
52
  }
68
53
  export type DirectedNotificationRoute = import('./attention.js').AudienceRoute;
69
- export interface InboxNotification {
70
- /** Lower message coordinate. */
71
- messageIndex: number;
72
- /** Upper Agora activity sharing this message, when one exists. */
73
- actIndex?: number;
74
- actor: string;
75
- at: number;
76
- route?: DirectedNotificationRoute;
77
- body: string;
78
- readonly preview?: string;
79
- readonly description?: string;
80
- readonly coverage?: 'full' | 'clipped';
81
- }
82
- /** The one stored-content-to-notification field mapping used by inbox and native previews. */
83
- export declare function notificationFacts(item: HydratedAct, route: DirectedNotificationRoute): InboxNotification;
84
- export interface LocatedSeat {
85
- readonly location: string;
86
- readonly seat: Seat;
87
- readonly joinedAt: number;
88
- }
89
- export interface PendingMembership extends LocatedSeat {
90
- readonly notifications: InboxNotification[];
91
- readonly catchLease?: WatchLease;
92
- readonly deliveryDeferralExpiry?: number;
93
- }
94
54
  export interface WatchOptions {
95
55
  participants?: string[];
96
56
  mention?: string;
package/dist/model.js CHANGED
@@ -1,12 +1,6 @@
1
1
  // Shared model and constants for Square.
2
- export { WAKE_ROUTE_KINDS, MESSAGE_METADATA_MAX_BYTES as ACTIVITY_METADATA_MAX_BYTES, MESSAGE_METADATA_MAX_DEPTH as ACTIVITY_METADATA_MAX_DEPTH, validateMessageMetadata as validateActivityMetadata, messageMetadataHasContent as activityMetadataHasContent, isWakeRouteKind, sameName, NAME_GRAPHEME_SOURCE, validateName } from './channel-values.js';
2
+ export { WAKE_ROUTE_KINDS, MESSAGE_METADATA_MAX_BYTES as ACTIVITY_METADATA_MAX_BYTES, MESSAGE_METADATA_MAX_DEPTH as ACTIVITY_METADATA_MAX_DEPTH, validateMessageMetadata as validateActivityMetadata, messageMetadataHasContent as activityMetadataHasContent, isWakeRouteKind, sameName, NAME_GRAPHEME_SOURCE, validateName } from './square-values.js';
3
3
  export { SquareErrorBase, SquareError, InternalSquareError, isSquareError, validateOwner, nameKey } from './neutral.js';
4
- /** The one stored-content-to-notification field mapping used by inbox and native previews. */
5
- export function notificationFacts(item, route) {
6
- if (item.kind !== 'say' || item.messageIndex === undefined)
7
- throw new Error(`Activity ${item.index} is not conversation content.`);
8
- return { messageIndex: item.messageIndex, actIndex: item.index, actor: item.actor, at: item.at, route, body: item.body ?? '(structured activity)' };
9
- }
10
4
  export function formatHardCap(hardCap) {
11
5
  return hardCap === null ? '-1' : String(hardCap);
12
6
  }
@@ -1,20 +1,24 @@
1
- import { type WakeTransports } from './wake-transports.js';
2
- export { createDefaultWakeTransport, createWakeTransport, type WakeTransports } from './wake-transports.js';
3
- export declare const PRIVILEGED_HOOK_BUDGET_MS = 3000;
4
- export declare function wakeGraceMs(env?: NodeJS.ProcessEnv): number;
5
- interface ProcessNotificationOptions {
6
- transports?: WakeTransports;
7
- env?: NodeJS.ProcessEnv;
8
- now?: () => number;
9
- }
10
- export declare function processActNotificationsOnce(squarePath: string, actIndex: number, opts?: ProcessNotificationOptions): Promise<void>;
11
- /** Privileged hook fallback: sweep indexed squares plus the current cwd's local squares within one boundary budget. */
12
- export declare function sweepPrivilegedPending(cwd: string, env?: NodeJS.ProcessEnv, suppliedTransports?: WakeTransports, deadline?: number, signal?: AbortSignal): Promise<void>;
13
- export interface SweepPendingNotificationsOptions {
14
- env?: NodeJS.ProcessEnv;
15
- now?: number;
16
- limit?: number;
17
- dispatchCandidate?: (actIndex: number) => void | Promise<void>;
18
- }
19
- /** Select old pending attention at a bounded action boundary for an explicit executor. */
20
- export declare function sweepPendingNotifications(squarePath: string, opts?: SweepPendingNotificationsOptions): Promise<number[]>;
1
+ import type { DeliveryLedger, DeliveryTransport, HandoffResult, PostResult } from './delivery-ledger.js';
2
+ import type { Owner } from './owner.js';
3
+ export { createDefaultDeliveryTransports } from './wake-transports.js';
4
+ /** Best-effort dispatch for currently eligible cards, including cards restored by release. */
5
+ export declare function dispatchEligibleCards(ledger: DeliveryLedger, owner: Owner, options?: {
6
+ readonly env?: NodeJS.ProcessEnv;
7
+ readonly signal?: AbortSignal;
8
+ readonly track?: (result: HandoffResult) => void;
9
+ readonly transports?: readonly DeliveryTransport[];
10
+ }): Promise<void>;
11
+ /** Post, then best-effort dispatch without consulting producer storage. */
12
+ export declare function postAndDispatch(ledger: DeliveryLedger, input: {
13
+ readonly owner: Owner;
14
+ readonly key: string;
15
+ readonly text: string;
16
+ readonly details?: Readonly<Record<string, unknown>>;
17
+ }, options?: {
18
+ readonly env?: NodeJS.ProcessEnv;
19
+ readonly signal?: AbortSignal;
20
+ readonly track?: (result: HandoffResult) => void;
21
+ readonly transports?: readonly DeliveryTransport[];
22
+ }): Promise<PostResult>;
23
+ /** Best-effort dispatch after a card has been posted; never inspect producer storage. */
24
+ export declare function dispatchPostedCard(ledger: DeliveryLedger, owner: Owner, key: string, track: (result: HandoffResult) => void, env?: NodeJS.ProcessEnv, signal?: AbortSignal, suppliedTransports?: readonly DeliveryTransport[]): Promise<void>;