@astrosheep/square 0.3.106 → 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 (206) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +6 -12
  5. package/dist/artifact.js +43 -344
  6. package/dist/attention-presentation.d.ts +13 -7
  7. package/dist/attention-presentation.js +29 -68
  8. package/dist/boundary-presentation.d.ts +3 -16
  9. package/dist/boundary-presentation.js +29 -109
  10. package/dist/card-presentation.d.ts +1 -0
  11. package/dist/card-presentation.js +2 -0
  12. package/dist/catch-decisions.d.ts +2 -2
  13. package/dist/catch-decisions.js +7 -3
  14. package/dist/catch-selection.d.ts +8 -0
  15. package/dist/catch-selection.js +5 -0
  16. package/dist/claude-delivery.d.ts +14 -12
  17. package/dist/claude-delivery.js +42 -142
  18. package/dist/claude-mcp-context.js +2 -2
  19. package/dist/claude-mod.js +28 -140
  20. package/dist/claude-transport.d.ts +5 -0
  21. package/dist/claude-transport.js +34 -0
  22. package/dist/cli/context.d.ts +2 -2
  23. package/dist/cli/context.js +7 -10
  24. package/dist/cli/maintenance-commands.d.ts +0 -4
  25. package/dist/cli/maintenance-commands.js +2 -29
  26. package/dist/cli/observation-commands.d.ts +2 -0
  27. package/dist/cli/observation-commands.js +12 -2
  28. package/dist/cli/program.js +1 -1
  29. package/dist/cli/registry.js +4 -3
  30. package/dist/codex-address.d.ts +2 -2
  31. package/dist/codex-boundary-state.d.ts +1 -5
  32. package/dist/codex-boundary-state.js +0 -4
  33. package/dist/codex-hook.d.ts +7 -10
  34. package/dist/codex-hook.js +69 -33
  35. package/dist/codex-queue.d.ts +10 -11
  36. package/dist/codex-queue.js +32 -34
  37. package/dist/codex-watch.d.ts +2 -0
  38. package/dist/codex-watch.js +100 -0
  39. package/dist/decisions.js +3 -3
  40. package/dist/delivery-deferral.d.ts +1 -19
  41. package/dist/delivery-deferral.js +1 -29
  42. package/dist/delivery-frame.d.ts +7 -0
  43. package/dist/delivery-frame.js +11 -0
  44. package/dist/delivery-ledger-sqlite.d.ts +97 -0
  45. package/dist/delivery-ledger-sqlite.js +496 -0
  46. package/dist/delivery-ledger.d.ts +196 -0
  47. package/dist/delivery-ledger.js +268 -0
  48. package/dist/delivery-lifetime.d.ts +6 -0
  49. package/dist/delivery-lifetime.js +45 -0
  50. package/dist/harness-links.js +61 -7
  51. package/dist/harness.js +12 -17
  52. package/dist/help.js +3 -6
  53. package/dist/http-delivery.d.ts +5 -9
  54. package/dist/http-delivery.js +20 -79
  55. package/dist/inbox.d.ts +6 -57
  56. package/dist/inbox.js +24 -380
  57. package/dist/index.d.ts +7 -4
  58. package/dist/index.js +5 -2
  59. package/dist/mcp.d.ts +2 -1
  60. package/dist/mcp.js +40 -70
  61. package/dist/message.d.ts +7 -0
  62. package/dist/message.js +23 -0
  63. package/dist/model.d.ts +15 -91
  64. package/dist/model.js +2 -140
  65. package/dist/neutral.d.ts +72 -0
  66. package/dist/neutral.js +27 -0
  67. package/dist/notifications.d.ts +24 -26
  68. package/dist/notifications.js +39 -143
  69. package/dist/operation-control.d.ts +4 -0
  70. package/dist/operation-control.js +20 -0
  71. package/dist/operation-presentation.d.ts +1 -2
  72. package/dist/operation-presentation.js +1 -7
  73. package/dist/owner.d.ts +2 -2
  74. package/dist/owner.js +1 -1
  75. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  76. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  77. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  78. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  79. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  80. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  81. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  82. package/dist/packages/agent-delivery/src/codex.js +41 -4
  83. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  84. package/dist/packages/agent-delivery/src/index.js +1 -1
  85. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  86. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  87. package/dist/paseo-delivery.d.ts +5 -5
  88. package/dist/paseo-delivery.js +19 -99
  89. package/dist/paseo.d.ts +1 -1
  90. package/dist/paseo.js +1 -1
  91. package/dist/plugins/opencode/index.d.ts +5 -3
  92. package/dist/plugins/opencode/index.js +99 -21
  93. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  94. package/dist/plugins/opencode-shared/delivery.js +109 -0
  95. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  96. package/dist/plugins/opencode-v1/index.js +41 -0
  97. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  98. package/dist/plugins/opencode-v1/runtime.js +152 -0
  99. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  100. package/dist/plugins/opencode-v1/transport.js +50 -0
  101. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  102. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  103. package/dist/ports.d.ts +33 -214
  104. package/dist/presentation.d.ts +5 -5
  105. package/dist/presentation.js +1 -0
  106. package/dist/runtime.js +2 -2
  107. package/dist/search.js +1 -1
  108. package/dist/session-lifecycle.d.ts +8 -7
  109. package/dist/session-lifecycle.js +40 -42
  110. package/dist/sqlite-framing.d.ts +8 -0
  111. package/dist/sqlite-framing.js +31 -0
  112. package/dist/sqlite-square.d.ts +16 -26
  113. package/dist/sqlite-square.js +199 -262
  114. package/dist/sqlite-values.d.ts +21 -0
  115. package/dist/sqlite-values.js +78 -0
  116. package/dist/square-api.d.ts +1 -1
  117. package/dist/square-api.js +5 -8
  118. package/dist/square-core.d.ts +0 -1
  119. package/dist/square-domain.d.ts +12 -10
  120. package/dist/square-domain.js +105 -223
  121. package/dist/square-facade.d.ts +25 -76
  122. package/dist/square-location.d.ts +6 -1
  123. package/dist/square-location.js +1 -1
  124. package/dist/square-post.d.ts +53 -0
  125. package/dist/square-post.js +4 -0
  126. package/dist/square-posting.d.ts +13 -0
  127. package/dist/square-posting.js +174 -0
  128. package/dist/square-producer-sqlite.d.ts +11 -0
  129. package/dist/square-producer-sqlite.js +210 -0
  130. package/dist/square-receive.d.ts +2 -3
  131. package/dist/square-sqlite.d.ts +33 -42
  132. package/dist/square-sqlite.js +236 -768
  133. package/dist/square-tools.d.ts +8 -0
  134. package/dist/square-tools.js +39 -0
  135. package/dist/square-values.d.ts +27 -0
  136. package/dist/square-values.js +90 -0
  137. package/dist/views.d.ts +6 -6
  138. package/dist/views.js +14 -9
  139. package/dist/wake-sink.d.ts +4 -11
  140. package/dist/wake-sink.js +8 -32
  141. package/dist/wake-transports.d.ts +4 -0
  142. package/dist/wake-transports.js +11 -0
  143. package/examples/symmetric-domains.mjs +29 -249
  144. package/guides/change-waits.md +13 -53
  145. package/guides/claude-native.md +19 -172
  146. package/guides/codex-delivery.md +55 -78
  147. package/guides/delivery-primitives.md +48 -207
  148. package/guides/operation-cancellation.md +2 -5
  149. package/guides/participant-ownership.md +25 -89
  150. package/guides/pi-native-delivery.md +19 -56
  151. package/guides/pi-tools.md +6 -8
  152. package/guides/row-runtime-delivery-evidence.md +2 -0
  153. package/guides/row-storage-design.md +19 -44
  154. package/guides/square-api.md +113 -538
  155. package/package.json +8 -8
  156. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  157. package/plugins/claude/hooks/hooks.json +1 -1
  158. package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
  159. package/plugins/claude/types.d.ts +1 -1
  160. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  161. package/plugins/codex/hooks/hooks.json +11 -23
  162. package/plugins/opencode/index.ts +91 -23
  163. package/plugins/opencode-shared/delivery.ts +91 -0
  164. package/plugins/opencode-v1/index.ts +36 -0
  165. package/plugins/opencode-v1/runtime.ts +134 -0
  166. package/plugins/opencode-v1/transport.ts +41 -0
  167. package/plugins/paseo/OVERVIEW.md +5 -5
  168. package/plugins/paseo/README.md +7 -6
  169. package/plugins/paseo/client/markdown.tsx +5 -5
  170. package/plugins/paseo/client/square-message.tsx +28 -24
  171. package/plugins/paseo/client/transform.ts +8 -5
  172. package/plugins/paseo/shared/card-presentation.ts +16 -0
  173. package/plugins/paseo/shared/message.ts +6 -11
  174. package/plugins/pi/square-pi.js +167 -451
  175. package/dist/channel.d.ts +0 -74
  176. package/dist/channel.js +0 -92
  177. package/dist/claude-delivery-status.d.ts +0 -13
  178. package/dist/claude-delivery-status.js +0 -46
  179. package/dist/cmd/notify-once.d.ts +0 -2
  180. package/dist/cmd/notify-once.js +0 -29
  181. package/dist/delivery-attempts.d.ts +0 -52
  182. package/dist/delivery-attempts.js +0 -73
  183. package/dist/delivery-effects.d.ts +0 -139
  184. package/dist/delivery-effects.js +0 -163
  185. package/dist/delivery-health.d.ts +0 -51
  186. package/dist/delivery-health.js +0 -173
  187. package/dist/delivery-operations.d.ts +0 -9
  188. package/dist/delivery-operations.js +0 -168
  189. package/dist/delivery-primitives.d.ts +0 -5
  190. package/dist/delivery-primitives.js +0 -3
  191. package/dist/delivery.d.ts +0 -45
  192. package/dist/delivery.js +0 -106
  193. package/dist/plugins/opencode/delivery.d.ts +0 -19
  194. package/dist/plugins/opencode/delivery.js +0 -238
  195. package/dist/presentation-operations.d.ts +0 -18
  196. package/dist/presentation-operations.js +0 -86
  197. package/dist/square-index.d.ts +0 -16
  198. package/dist/square-index.js +0 -106
  199. package/dist/square-legacy-framing.d.ts +0 -11
  200. package/dist/square-legacy-framing.js +0 -64
  201. package/dist/square-migration-host-ledger.d.ts +0 -26
  202. package/dist/square-migration-host-ledger.js +0 -169
  203. package/dist/square-migration.d.ts +0 -30
  204. package/dist/square-migration.js +0 -599
  205. package/guides/migration.md +0 -156
  206. package/plugins/opencode/delivery.ts +0 -211
package/dist/inbox.js CHANGED
@@ -1,391 +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 { formatActivityId, parseActivityId } from './square-core.js';
5
- import { freshWatchLease, WATCH_STALE_MS } from './runtime.js';
6
- import { pendingHead, pendingHeads, leaseOwnsNotification } from './delivery.js';
7
- import { deliveryDeferralExpiry } from './delivery-deferral.js';
8
- import { attentionBodyIsClipped, renderAttentionPreview, renderSquareReminder } from './attention-presentation.js';
9
- export function pendingKey(membership, actIndex) {
10
- return membership.location + '\0' + membership.seat.participant.toLocaleLowerCase() + '\0' + actIndex;
11
- }
12
- export function pendingAtBoundary(inbox) {
13
- return inbox.map((membership) => {
14
- const lease = membership.catchLease;
15
- if (lease === undefined)
16
- return membership;
17
- return { ...membership, notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, { ...notification, recipient: membership.seat.participant })) };
18
- }).filter((membership) => membership.notifications.length > 0);
19
- }
20
- function eligibleNotifications(facts, membership, now = Date.now()) {
21
- const seat = facts.seat(membership.seat.participant);
22
- if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== membership.seat.generation
23
- || seat.owner !== membership.seat.owner)
24
- return { notifications: [] };
25
- const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
26
- let expiry = Infinity;
27
- const notifications = membership.notifications.filter(notification => {
28
- if (cancelledThrough !== undefined && notification.actIndex <= cancelledThrough
29
- || facts.deliveryEvidence(seat.participant, seat.generation, formatActivityId(notification.actIndex))
30
- .some(row => row.outcome !== 'failed' && row.outcome !== 'released'))
31
- return false;
32
- const deferredUntil = deliveryDeferralExpiry(facts, seat, notification.actIndex, now);
33
- if (deferredUntil === undefined)
34
- return true;
35
- expiry = Math.min(expiry, deferredUntil);
36
- return false;
37
- });
38
- return { notifications, ...(expiry === Infinity ? {} : { deliveryDeferralExpiry: expiry }) };
39
- }
40
- /** Revalidate a supplied batch immediately before the boundary's independent claim/send checks. */
41
- export async function projectBoundaryEligibility(inbox) {
42
- const grouped = new Map();
43
- for (const membership of pendingAtBoundary(inbox)) {
44
- const entries = grouped.get(membership.location) ?? [];
45
- entries.push(membership);
46
- grouped.set(membership.location, entries);
47
- }
48
- const pending = [];
49
- for (const [location, memberships] of grouped) {
50
- const artifact = createArtifactPort(location);
51
- try {
52
- const { value } = await artifact.read(facts => memberships.flatMap(membership => {
53
- const eligibility = eligibleNotifications(facts, membership);
54
- return eligibility.notifications.length === 0 ? [] : [{ ...membership, ...eligibility }];
55
- }));
56
- pending.push(...value);
57
- }
58
- catch {
59
- // An unreadable or stale candidate cannot authorize boundary presentation.
60
- }
61
- finally {
62
- await artifact.close();
63
- }
64
- }
65
- return pending;
66
- }
67
- /** HOME is a candidate path index; current seats and delivery evidence live in the artifact. */
68
- export async function projectSessionCandidates(input) {
69
- const location = input.location === undefined ? undefined : await canonicalPath(input.location);
70
- const entries = await indexedSquareEntries({ owner: input.owner }, input.env ?? process.env);
71
- const candidates = new Map();
72
- for (const entry of entries) {
73
- if (location !== undefined && location !== entry.location)
74
- continue;
75
- const key = JSON.stringify([entry.location, entry.owner]);
76
- candidates.set(key, { location: entry.location, owner: entry.owner });
77
- }
78
- return [...candidates.values()];
79
- }
80
- function groupCandidates(candidates) {
81
- const grouped = new Map();
82
- for (const { location, owner } of candidates) {
83
- const owners = grouped.get(location) ?? [];
84
- owners.push(owner);
85
- grouped.set(location, owners);
86
- }
87
- return grouped;
88
- }
89
- function locatedSeats(facts, location, seats) {
90
- return seats.flatMap(seat => {
91
- const index = parseActivityId(seat.generation);
92
- const joined = index === undefined ? undefined : facts.activity(index);
93
- return joined?.kind !== 'join' ? [] : [{ location, seat, joinedAt: joined.at }];
94
- });
95
- }
96
- /** Lifecycle reads resolve seats and join times, never pending activity bodies. */
97
- export async function projectSessionSeats(input, signal) {
98
- const seats = [];
99
- for (const [location, owners] of groupCandidates(await projectSessionCandidates(input))) {
100
- signal?.throwIfAborted();
101
- const artifact = createArtifactPort(location, undefined, signal);
102
- try {
103
- seats.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner))), signal)).value);
104
- }
105
- catch (error) {
106
- if (signal?.aborted)
107
- throw signal.reason ?? error;
108
- }
109
- finally {
110
- await artifact.close();
111
- }
112
- }
113
- return seats;
114
- }
115
- /** 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. */
116
6
  export async function sessionInboxCounts(input, signal) {
7
+ const root = path.resolve(input.cwd ?? process.cwd());
117
8
  const rows = [];
118
- for (const [location, owners] of groupCandidates(await projectSessionCandidates(input))) {
119
- signal?.throwIfAborted();
120
- const artifact = createArtifactPort(location, undefined, signal);
121
- try {
122
- rows.push(...(await artifact.read(facts => locatedSeats(facts, location, owners.flatMap(owner => facts.seatsForOwner(owner).filter(seat => seat.attachment === 'attached'))).map(({ seat }) => ({
123
- name: seat.participant, squarePath: location, pending: facts.directedCount(seat.participant, { unseenOnly: true }),
124
- })), signal)).value);
125
- }
126
- catch (error) {
127
- if (signal?.aborted)
128
- throw signal.reason ?? error;
129
- }
130
- finally {
131
- await artifact.close();
132
- }
133
- }
134
- return rows;
135
- }
136
- /** One authoritative read per canonical artifact, even when discovery names several bodies. */
137
- async function projectPending(candidates, cache, signal) {
138
- const pending = [];
139
- for (const [location, owners] of groupCandidates(candidates)) {
9
+ let after;
10
+ do {
140
11
  signal?.throwIfAborted();
141
- const artifact = createArtifactPort(location, undefined, signal);
142
- try {
143
- const { value: current } = await artifact.read(facts => {
144
- const seats = owners.flatMap(owner => facts.seatsForOwner(owner).filter(seat => seat.attachment === 'attached'));
145
- const coordinate = JSON.stringify([owners, seats.map(seat => [seat.participant, seat.owner, seat.generation])]);
146
- const header = facts.header();
147
- let cached = cache?.get(location);
148
- if (cached?.coordinate !== coordinate || cached.version !== header.revision) {
149
- cached = { coordinate, version: header.revision, value: locatedSeats(facts, location, seats).map(membership => {
150
- const heads = pendingHeads(facts, membership.seat.participant);
151
- const routes = new Map(heads.map(head => [head.index, head.route]));
152
- return { ...membership, notifications: facts.activities(heads.map(head => head.index)).flatMap(item => item.kind === 'say'
153
- ? [{ actIndex: item.index, actor: item.actor, at: item.at, body: item.body ?? '(structured activity)',
154
- route: routes.get(item.index), ...(item.delivery === undefined ? {} : { delivery: item.delivery }) }]
155
- : []) };
156
- }) };
157
- }
158
- // Cache conversation content, never leases or delivery eligibility: evidence can change
159
- // without a conversation revision and must not force body hydration or self-wake retries.
160
- return { cached, pending: cached.value.map(membership => {
161
- const catchLease = freshWatchLease(facts, membership.seat.participant);
162
- return { ...membership, ...eligibleNotifications(facts, membership),
163
- ...(catchLease === undefined ? {} : { catchLease }) };
164
- }) };
165
- }, signal);
166
- cache?.set(location, current.cached);
167
- pending.push(...current.pending);
168
- }
169
- catch (error) {
170
- cache?.delete(location);
171
- if (signal?.aborted)
172
- throw signal.reason ?? error;
173
- // Unreadable candidates remain watch targets, but never authorize cached content.
174
- }
175
- finally {
176
- await artifact.close();
177
- }
178
- }
179
- return pending;
180
- }
181
- function sameWakeAddress(address, kind, requested) {
182
- if (address?.kind !== kind || Object.keys(address.address).length !== Object.keys(requested).length)
183
- return false;
184
- return Object.entries(requested).every(([key, value]) => address.address[key] === value);
185
- }
186
- /** Current-generation native attention, owned by its exact wake address and catch state. */
187
- export function nativePendingPreview(facts, input) {
188
- const seat = facts.seat(input.participant);
189
- if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== input.generation
190
- || !sameWakeAddress(seat.wakeAddress, input.routeKind, input.address))
191
- return undefined;
192
- const cancelledThrough = facts.cancelledThrough(seat.participant, seat.generation);
193
- if (input.actIndex <= Math.max(input.cancelledThrough ?? -1, cancelledThrough ?? -1))
194
- return undefined;
195
- const head = pendingHead(facts, seat.participant, input.actIndex);
196
- const lease = freshWatchLease(facts, input.participant, input.now);
197
- if (!head || (lease !== undefined && leaseOwnsNotification(lease, head)))
198
- return undefined;
199
- const item = facts.activity(input.actIndex);
200
- if (item?.kind !== 'say' || deliveryDeferralExpiry(facts, seat, input.actIndex, input.now) !== undefined)
201
- return undefined;
202
- return { payload: renderSquareReminder([renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
203
- actor: item.actor, route: head.route, body: item.body ?? '(structured activity)', delivery: item.delivery })], [item.delivery]),
204
- clipped: attentionBodyIsClipped(item.body ?? '(structured activity)', item.delivery) };
205
- }
206
- export async function sessionInbox(owner, env = process.env, signal) {
207
- return projectPending(await projectSessionCandidates({ owner, env }), undefined, signal);
208
- }
209
- async function capture(observer, kind, signal) {
210
- const generation = observer.generation;
211
- try {
212
- const value = await observer.read(signal);
213
- return { kind, value, wait: (timeout, abort) => observer.changed(value, timeout, abort) };
214
- }
215
- catch (error) {
216
- if (signal.aborted)
217
- throw signal.reason ?? error;
218
- // Failed revision reads are not changes; only a successful retry wakes projection.
219
- return { kind, wait: async (timeout, abort) => {
220
- const deadline = Date.now() + Math.max(0, timeout);
221
- let since = generation;
222
- while (await observer.hinted(since, Math.max(0, deadline - Date.now()), abort)) {
223
- since = observer.generation;
224
- try {
225
- await observer.read(abort);
226
- return true;
227
- }
228
- catch (error) {
229
- if (abort.aborted)
230
- throw abort.reason ?? error;
231
- }
232
- }
233
- return false;
234
- } };
235
- }
236
- }
237
- /** Session-scoped projection over process-shared storage change detectors. */
238
- export async function observeSessionPending(owner, suppliedEnv = process.env) {
239
- const env = { ...suppliedEnv };
240
- const query = { owner, env };
241
- const index = await observeSquareIndex(env);
242
- const artifacts = new Map();
243
- const cache = new Map();
244
- const lifetime = new AbortController();
245
- async function currentInbox(candidates, signal) {
246
- const inbox = await projectPending(candidates, cache, signal);
247
- const now = Date.now();
248
- return inbox.map((membership) => {
249
- const { catchLease, ...rest } = membership;
250
- return catchLease !== undefined && catchLease.expiresAt > now && now - catchLease.heartbeatAt <= WATCH_STALE_MS
251
- ? membership : rest;
252
- });
253
- }
254
- function eligibleInbox(inbox, excludeKeys) {
255
- const eligible = pendingAtBoundary(inbox);
256
- return excludeKeys === undefined ? eligible : eligible.flatMap((membership) => {
257
- const notifications = membership.notifications.filter((notification) => !excludeKeys.has(pendingKey(membership, notification.actIndex)));
258
- return notifications.length === 0 ? [] : [{ ...membership, notifications }];
259
- });
260
- }
261
- return {
262
- async read(options = {}) {
263
- const signal = AbortSignal.any([lifetime.signal, ...(options.signal === undefined ? [] : [options.signal])]);
264
- signal.throwIfAborted();
265
- const inbox = await currentInbox(await projectSessionCandidates(query), signal);
266
- const pending = eligibleInbox(inbox, options.excludeKeys);
267
- signal.throwIfAborted();
268
- return pending;
269
- },
270
- async wait(timeoutMs, options = {}) {
271
- const deadline = Date.now() + Math.max(0, timeoutMs);
272
- const expired = new AbortController();
273
- const timer = Number.isFinite(timeoutMs) && timeoutMs > 0
274
- ? setTimeout(() => expired.abort(), Math.min(timeoutMs, 2_147_483_647)) : undefined;
275
- const signal = AbortSignal.any([lifetime.signal, expired.signal, ...(options.signal === undefined ? [] : [options.signal])]);
276
- let canReturn = !options.skipImmediate;
277
- let previousEligible;
278
- let armed = false;
279
- let candidates = [];
280
- let candidatePresence;
281
- 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);
282
17
  try {
283
- while (!signal.aborted) {
284
- // Subscribe to the candidate index before discovering artifacts; a concurrent join can
285
- // publish its path without touching the artifact revision.
286
- const sources = [await capture(index, 'index', signal)];
287
- const presence = sources[0];
288
- const currentPresence = presence.value;
289
- if (!discoveredCandidates || currentPresence !== undefined && !Object.is(currentPresence, candidatePresence)) {
290
- candidates = await projectSessionCandidates(query);
291
- candidatePresence = currentPresence;
292
- discoveredCandidates = true;
293
- }
294
- const paths = new Set(candidates.map((candidate) => candidate.location));
295
- for (const [file, entry] of artifacts) {
296
- if (!paths.has(file)) {
297
- entry.observer.close();
298
- entry.delivery.close();
299
- artifacts.delete(file);
300
- cache.delete(file);
301
- }
302
- }
303
- for (const file of paths) {
304
- if (artifacts.has(file))
305
- continue;
306
- const observer = await observeSquareRevision(file);
307
- const delivery = await observeSquareDeliveryRevision(file);
308
- if (signal.aborted) {
309
- observer.close();
310
- delivery.close();
311
- return [];
312
- }
313
- artifacts.set(file, { observer, delivery });
314
- }
315
- // Capture both revision baselines before selecting seats and pending facts. A takeover
316
- // during an empty selection must remain visible to the already armed observers.
317
- for (const entry of artifacts.values()) {
318
- sources.push(await capture(entry.observer, 'artifact', signal));
319
- sources.push(await capture(entry.delivery, 'delivery', signal));
320
- }
321
- const inbox = await currentInbox(candidates, signal);
322
- const pending = eligibleInbox(inbox, options.excludeKeys);
323
- const eligibleKeys = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => pendingKey(membership, note.actIndex))).sort());
324
- if (previousEligible !== undefined && previousEligible !== eligibleKeys)
325
- canReturn = true;
326
- previousEligible = eligibleKeys;
327
- if (signal.aborted)
328
- return [];
329
- if (canReturn && pending.length > 0)
330
- return pending;
331
- const remaining = deadline - Date.now();
332
- if (remaining <= 0)
333
- return [];
334
- // Eligibility may change at lease expiry without any storage write.
335
- const leaseExpiry = Math.min(Infinity, ...inbox.flatMap((membership) => membership.catchLease === undefined ? [] : [Math.min(membership.catchLease.expiresAt, membership.catchLease.heartbeatAt + WATCH_STALE_MS + 1)]));
336
- const nextExpiry = Math.min(leaseExpiry, ...inbox.map(membership => membership.deliveryDeferralExpiry ?? Infinity));
337
- const duration = Math.min(remaining, Math.max(1, nextExpiry - Date.now()));
338
- const cancelled = new AbortController();
339
- const waitSignal = AbortSignal.any([signal, cancelled.signal]);
340
- const waits = sources.map(async (source) => {
341
- try {
342
- return await source.wait(duration, waitSignal) ? source.kind : 'deadline';
343
- }
344
- catch (error) {
345
- if (waitSignal.aborted)
346
- throw error;
347
- return source.kind;
348
- }
349
- });
350
- if (!armed) {
351
- armed = true;
352
- options.onChangeArmed?.(true);
353
- }
354
- try {
355
- const change = await Promise.race(waits);
356
- if (change !== 'delivery')
357
- canReturn = true;
358
- }
359
- finally {
360
- cancelled.abort();
361
- await Promise.allSettled(waits);
362
- }
363
- }
364
- 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);
365
22
  }
366
23
  catch (error) {
367
- if (signal.aborted)
368
- return [];
369
- throw error;
24
+ if (signal?.aborted)
25
+ throw signal.reason ?? error;
26
+ // Discovery candidates may disappear or become unreadable before this read.
370
27
  }
371
28
  finally {
372
- if (timer !== undefined)
373
- clearTimeout(timer);
374
- if (!armed)
375
- options.onChangeArmed?.(false);
376
- }
377
- },
378
- close() {
379
- if (lifetime.signal.aborted)
380
- return;
381
- lifetime.abort();
382
- index.close();
383
- for (const entry of artifacts.values()) {
384
- entry.observer.close();
385
- entry.delivery.close();
29
+ await artifact.close();
386
30
  }
387
- artifacts.clear();
388
- cache.clear();
389
- },
390
- };
31
+ }
32
+ after = page.next;
33
+ } while (after !== undefined);
34
+ return rows;
391
35
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
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
- export { Square, squareLocation, squareClock, createSquareResourceOwner, discover, inbox, diagnoseSquare, migrateSquare } from './square-api.js';
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.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { type Owner } from './square-api.js';
3
3
  import type { SquareAtInput } from './square-facade.js';
4
+ import type { WakeAddress } from './model.js';
4
5
  /** The server owns its artifact connection lifetime, so callers cannot lend one. */
5
6
  export interface SquareMcpOptions extends Omit<SquareAtInput, 'resources'> {
6
7
  readonly owner?: Owner;
7
- readonly wakeAddress?: import('./model.js').WakeAddress;
8
+ readonly wakeAddress?: WakeAddress;
8
9
  }
9
10
  export type SquareMcpServer = McpServer;
10
11
  export declare function createSquareMcpServer(options: SquareMcpOptions): SquareMcpServer;
package/dist/mcp.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, SquareExpressDrafts, squareToolsEnabled, squareToolFailure, squareToolFailureText } from './square-tools.js';
3
+ import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, createSquareToolRunner, squareToolsEnabled } from './square-tools.js';
4
4
  import { operationEnv } from './participant-identity.js';
5
5
  import { codexWakeAddress } from './codex-address.js';
6
6
  import { readClaudeMcpCall } from './claude-mcp-context.js';
@@ -12,7 +12,7 @@ export function createSquareMcpServer(options) {
12
12
  const baseEnv = { ...(resourceOptions.env ?? process.env) };
13
13
  const toolsEnabled = squareToolsEnabled(baseEnv);
14
14
  const resources = createSquareResourceOwner();
15
- const drafts = new SquareExpressDrafts();
15
+ const invoke = createSquareToolRunner();
16
16
  // Calls to the configured square borrow one server-owned connection.
17
17
  const inFlight = new Set();
18
18
  const server = new McpServer({ name: 'square', version: SQUARE_IDENTITY.packageVersion }, {
@@ -21,77 +21,47 @@ export function createSquareMcpServer(options) {
21
21
  if (toolsEnabled)
22
22
  for (const tool of SQUARE_TOOLS) {
23
23
  server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, (args, extra) => {
24
- const call = (async () => {
25
- let square;
26
- let toolArgs = args;
27
- let owner;
28
- let draftToken;
29
- let expressAttempted = false;
30
- try {
31
- const threadId = extra._meta?.threadId;
32
- const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
33
- // Claude's plugin endpoint requires the mod's call-local coordinate, never startup identity.
34
- const claude = baseEnv.SQUARE_CLAUDE_MCP_CONTEXT === '1'
35
- ? await readClaudeMcpCall(extra._meta?.['claudecode/toolUseId'], baseEnv)
36
- : undefined;
37
- owner = claude !== undefined
38
- ? harnessOwner({ channel: 'claude-code', session: claude.sessionId, ...(claude.agentId === undefined ? {} : { agent: claude.agentId }) })
39
- : typeof threadId === 'string' && threadId.trim()
40
- ? harnessOwner({ channel: 'codex', session: threadId })
41
- : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
42
- ? harnessOwner({ channel: 'opencode', session: opencodeSessionId })
43
- : configuredOwner;
44
- if (tool.name === 'express') {
45
- const resolved = drafts.resolve(toolArgs, owner);
46
- toolArgs = resolved.args;
47
- draftToken = resolved.token;
48
- }
49
- const { location } = toolArgs;
50
- const env = claude !== undefined
51
- ? { ...operationEnv('claude', claude.sessionId, baseEnv),
52
- // Preserve Claude's session + child flag semantics; an agent loop ID is not a session ID.
53
- CLAUDE_CODE_CHILD_SESSION: claude.agentId === undefined ? '' : '1' }
54
- : typeof threadId === 'string' && threadId.trim()
55
- ? operationEnv('codex', threadId, baseEnv)
56
- : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
57
- ? operationEnv('opencode', opencodeSessionId, baseEnv)
58
- : baseEnv;
59
- square = await Square.at({
24
+ const call = invoke(tool, args, { signal: extra.signal }, async () => {
25
+ const threadId = extra._meta?.threadId;
26
+ const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
27
+ // Claude's plugin endpoint requires the mod's call-local coordinate, never startup identity.
28
+ const claude = baseEnv.SQUARE_CLAUDE_MCP_CONTEXT === '1'
29
+ ? await readClaudeMcpCall(extra._meta?.['claudecode/toolUseId'], baseEnv)
30
+ : undefined;
31
+ const owner = claude !== undefined
32
+ ? harnessOwner({ channel: 'claude-code', session: claude.sessionId, ...(claude.agentId === undefined ? {} : { agent: claude.agentId }) })
33
+ : typeof threadId === 'string' && threadId.trim()
34
+ ? harnessOwner({ channel: 'codex', session: threadId })
35
+ : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
36
+ ? harnessOwner({ channel: 'opencode', session: opencodeSessionId })
37
+ : configuredOwner;
38
+ const env = claude !== undefined
39
+ ? { ...operationEnv('claude', claude.sessionId, baseEnv),
40
+ // An agent loop ID is not a session ID.
41
+ CLAUDE_CODE_CHILD_SESSION: claude.agentId === undefined ? '' : '1' }
42
+ : typeof threadId === 'string' && threadId.trim()
43
+ ? operationEnv('codex', threadId, baseEnv)
44
+ : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
45
+ ? operationEnv('opencode', opencodeSessionId, baseEnv)
46
+ : baseEnv;
47
+ const callWake = wakeAddress !== undefined && (owner === configuredOwner
48
+ || wakeAddress.kind === 'paseo' && wakeAddress.address.agentId === baseEnv.PASEO_AGENT_ID
49
+ || wakeAddress.kind === 'claude-native' && wakeAddress.address.sessionId === claude?.sessionId
50
+ || wakeAddress.kind === 'opencode-server' && wakeAddress.address.sessionId === opencodeSessionId)
51
+ ? wakeAddress : undefined;
52
+ return {
53
+ identity: { owner, wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
54
+ ? { ...codexWakeAddress(threadId, env), kind: 'codex-queue' } : callWake },
55
+ open: (location, control) => Square.at({
60
56
  ...resourceOptions, env, cwd: claude?.cwd ?? resourceOptions.cwd,
61
57
  path: location ?? options.path,
62
- // The server owner is bound to its configured square; overrides close after each call.
58
+ // Overrides and Claude calls own their connections.
63
59
  resources: location === undefined && claude === undefined ? resources : undefined,
64
- }, { signal: extra.signal });
65
- const callWake = wakeAddress !== undefined && (owner === configuredOwner
66
- || wakeAddress.kind === 'paseo' && wakeAddress.address.agentId === baseEnv.PASEO_AGENT_ID
67
- || wakeAddress.kind === 'claude-native' && wakeAddress.address.sessionId === claude?.sessionId
68
- || wakeAddress.kind === 'opencode-server' && wakeAddress.address.sessionId === opencodeSessionId)
69
- ? wakeAddress : undefined;
70
- let text;
71
- try {
72
- expressAttempted = tool.name === 'express';
73
- text = await tool.run(square, toolArgs, { signal: extra.signal }, {
74
- owner, wakeAddress: claude === undefined && typeof threadId === 'string' && threadId.trim()
75
- ? codexWakeAddress(threadId, env) : callWake,
76
- });
77
- drafts.consume(draftToken);
78
- }
79
- finally {
80
- await square.close();
81
- }
82
- return { content: [{ type: 'text', text }] };
83
- }
84
- catch (error) {
85
- const failure = squareToolFailure(error, extra.signal);
86
- const participant = toolArgs.as;
87
- const draft = tool.name === 'express' ? drafts.retain(toolArgs, owner, draftToken, failure, expressAttempted) : undefined;
88
- return { isError: true, content: [{ type: 'text', text: squareToolFailureText(failure, {
89
- location: square?.location ?? options.path,
90
- participant: typeof participant === 'string' ? participant : undefined,
91
- draft,
92
- }) }] };
93
- }
94
- })();
60
+ }, control),
61
+ };
62
+ }, options.path).then(({ text, isError }) => ({
63
+ ...(isError ? { isError } : {}), content: [{ type: 'text', text }],
64
+ }));
95
65
  inFlight.add(call);
96
66
  return call.finally(() => { inFlight.delete(call); });
97
67
  });
@@ -0,0 +1,7 @@
1
+ export type MessageId = `msg/${number}`;
2
+ /** Opaque endpoint custody token, allocated when an endpoint is established. */
3
+ export type EndpointGeneration = string;
4
+ export declare function formatMessageId(index: number): MessageId;
5
+ export declare function parseMessageId(value: string): number | undefined;
6
+ export declare function newEndpointGeneration(): EndpointGeneration;
7
+ export declare function validateEndpointGeneration(value: unknown): EndpointGeneration;
@@ -0,0 +1,23 @@
1
+ // Message coordinates belong to Channel alone; Agora activity ids are a separate coordinate.
2
+ import crypto from 'node:crypto';
3
+ export function formatMessageId(index) {
4
+ if (!Number.isSafeInteger(index) || index < 0)
5
+ throw new Error(`Invalid message index: ${index}`);
6
+ return `msg/${index}`;
7
+ }
8
+ export function parseMessageId(value) {
9
+ if (value === 'msg/0')
10
+ return 0;
11
+ if (typeof value !== 'string' || !/^msg\/[1-9]\d*$/.test(value))
12
+ return undefined;
13
+ const index = Number(value.slice(4));
14
+ return Number.isSafeInteger(index) ? index : undefined;
15
+ }
16
+ export function newEndpointGeneration() {
17
+ return `endpoint/${crypto.randomUUID()}`;
18
+ }
19
+ export function validateEndpointGeneration(value) {
20
+ if (typeof value !== 'string' || value.length === 0 || value.length > 512 || /[\u0000-\u001f\u007f-\u009f]/u.test(value))
21
+ throw new TypeError('Endpoint generation must be a non-empty opaque token without control characters.');
22
+ return value;
23
+ }