@astrosheep/square 0.4.0 → 0.4.2

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 (196) 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-boundary-state.d.ts +20 -0
  11. package/dist/claude-boundary-state.js +84 -0
  12. package/dist/claude-hook.d.ts +2 -0
  13. package/dist/claude-hook.js +231 -0
  14. package/dist/claude-mcp-context.d.ts +1 -1
  15. package/dist/claude-mcp-context.js +12 -3
  16. package/dist/cli/context.js +7 -9
  17. package/dist/cli/observation-commands.d.ts +4 -1
  18. package/dist/cli/observation-commands.js +37 -4
  19. package/dist/cli/registry.js +5 -2
  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-claude.js +3 -4
  41. package/dist/harness-links.js +63 -7
  42. package/dist/harness.js +12 -17
  43. package/dist/help.js +4 -1
  44. package/dist/http-delivery.d.ts +5 -9
  45. package/dist/http-delivery.js +20 -77
  46. package/dist/inbox.d.ts +6 -49
  47. package/dist/inbox.js +24 -371
  48. package/dist/index.d.ts +6 -3
  49. package/dist/index.js +4 -1
  50. package/dist/mcp.js +2 -3
  51. package/dist/model.d.ts +7 -47
  52. package/dist/model.js +1 -7
  53. package/dist/notifications.d.ts +24 -20
  54. package/dist/notifications.js +39 -108
  55. package/dist/operation-control.d.ts +4 -0
  56. package/dist/operation-control.js +20 -0
  57. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  58. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  59. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  60. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  61. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  62. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  63. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  64. package/dist/packages/agent-delivery/src/codex.js +41 -4
  65. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  66. package/dist/packages/agent-delivery/src/index.js +1 -1
  67. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  68. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  69. package/dist/paseo-delivery.d.ts +5 -5
  70. package/dist/paseo-delivery.js +19 -97
  71. package/dist/paseo.d.ts +1 -1
  72. package/dist/paseo.js +1 -1
  73. package/dist/plugins/opencode/index.d.ts +5 -3
  74. package/dist/plugins/opencode/index.js +99 -21
  75. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  76. package/dist/plugins/opencode-shared/delivery.js +109 -0
  77. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  78. package/dist/plugins/opencode-v1/index.js +41 -0
  79. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  80. package/dist/plugins/opencode-v1/runtime.js +152 -0
  81. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  82. package/dist/plugins/opencode-v1/transport.js +50 -0
  83. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  84. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  85. package/dist/ports.d.ts +31 -90
  86. package/dist/session-lifecycle.d.ts +8 -7
  87. package/dist/session-lifecycle.js +40 -42
  88. package/dist/sqlite-square.d.ts +14 -23
  89. package/dist/sqlite-square.js +132 -139
  90. package/dist/square-api.js +4 -7
  91. package/dist/square-core.d.ts +0 -1
  92. package/dist/square-domain.d.ts +5 -17
  93. package/dist/square-domain.js +104 -221
  94. package/dist/square-facade.d.ts +18 -15
  95. package/dist/square-post.d.ts +53 -0
  96. package/dist/square-post.js +4 -0
  97. package/dist/square-posting.d.ts +13 -0
  98. package/dist/square-posting.js +174 -0
  99. package/dist/square-producer-sqlite.d.ts +11 -0
  100. package/dist/square-producer-sqlite.js +210 -0
  101. package/dist/square-sqlite.d.ts +15 -25
  102. package/dist/square-sqlite.js +150 -226
  103. package/dist/views.d.ts +2 -2
  104. package/dist/views.js +0 -1
  105. package/dist/wake-sink.d.ts +4 -11
  106. package/dist/wake-sink.js +8 -32
  107. package/dist/wake-transports.d.ts +4 -7
  108. package/dist/wake-transports.js +7 -39
  109. package/examples/symmetric-domains.mjs +29 -249
  110. package/guides/change-waits.md +13 -53
  111. package/guides/claude-native.md +7 -171
  112. package/guides/codex-delivery.md +55 -80
  113. package/guides/delivery-primitives.md +48 -207
  114. package/guides/mcp.md +1 -1
  115. package/guides/operation-cancellation.md +2 -5
  116. package/guides/participant-ownership.md +25 -89
  117. package/guides/pi-native-delivery.md +19 -56
  118. package/guides/pi-tools.md +6 -8
  119. package/guides/square-api.md +113 -615
  120. package/package.json +8 -8
  121. package/plugins/claude/.claude-plugin/plugin.json +2 -3
  122. package/plugins/claude/hooks/hooks.json +11 -1
  123. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  124. package/plugins/codex/hooks/hooks.json +11 -23
  125. package/plugins/opencode/index.ts +91 -23
  126. package/plugins/opencode-shared/delivery.ts +91 -0
  127. package/plugins/opencode-v1/index.ts +36 -0
  128. package/plugins/opencode-v1/runtime.ts +134 -0
  129. package/plugins/opencode-v1/transport.ts +41 -0
  130. package/plugins/paseo/OVERVIEW.md +5 -5
  131. package/plugins/paseo/README.md +7 -6
  132. package/plugins/paseo/client/markdown.tsx +5 -5
  133. package/plugins/paseo/client/square-message.tsx +28 -24
  134. package/plugins/paseo/client/transform.ts +8 -5
  135. package/plugins/paseo/shared/card-presentation.ts +16 -0
  136. package/plugins/paseo/shared/message.ts +6 -11
  137. package/plugins/pi/square-pi.js +156 -417
  138. package/dist/channel-artifact.d.ts +0 -10
  139. package/dist/channel-artifact.js +0 -55
  140. package/dist/channel-delivery.d.ts +0 -11
  141. package/dist/channel-delivery.js +0 -51
  142. package/dist/channel-domain.d.ts +0 -3
  143. package/dist/channel-domain.js +0 -83
  144. package/dist/channel-facade.d.ts +0 -76
  145. package/dist/channel-facade.js +0 -1
  146. package/dist/channel-internal.d.ts +0 -126
  147. package/dist/channel-internal.js +0 -440
  148. package/dist/channel-metadata.d.ts +0 -11
  149. package/dist/channel-metadata.js +0 -1
  150. package/dist/channel-ports.d.ts +0 -241
  151. package/dist/channel-ports.js +0 -1
  152. package/dist/channel-presentation.d.ts +0 -17
  153. package/dist/channel-presentation.js +0 -22
  154. package/dist/channel-sqlite-schema.d.ts +0 -13
  155. package/dist/channel-sqlite-schema.js +0 -81
  156. package/dist/channel-transport.d.ts +0 -62
  157. package/dist/channel-transport.js +0 -1
  158. package/dist/channel.d.ts +0 -84
  159. package/dist/channel.js +0 -60
  160. package/dist/claude-delivery-status.d.ts +0 -13
  161. package/dist/claude-delivery-status.js +0 -46
  162. package/dist/claude-delivery.d.ts +0 -12
  163. package/dist/claude-delivery.js +0 -108
  164. package/dist/claude-mod.d.ts +0 -8
  165. package/dist/claude-mod.js +0 -172
  166. package/dist/claude-transport.d.ts +0 -5
  167. package/dist/claude-transport.js +0 -58
  168. package/dist/cmd/notify-once.d.ts +0 -2
  169. package/dist/cmd/notify-once.js +0 -29
  170. package/dist/delivery-attempts.d.ts +0 -52
  171. package/dist/delivery-attempts.js +0 -73
  172. package/dist/delivery-effects.d.ts +0 -139
  173. package/dist/delivery-effects.js +0 -163
  174. package/dist/delivery-health.d.ts +0 -51
  175. package/dist/delivery-health.js +0 -173
  176. package/dist/delivery-operations.d.ts +0 -22
  177. package/dist/delivery-operations.js +0 -198
  178. package/dist/delivery-primitives.d.ts +0 -5
  179. package/dist/delivery-primitives.js +0 -3
  180. package/dist/delivery.d.ts +0 -56
  181. package/dist/delivery.js +0 -80
  182. package/dist/plugins/opencode/delivery.d.ts +0 -19
  183. package/dist/plugins/opencode/delivery.js +0 -223
  184. package/dist/presentation-operations.d.ts +0 -5
  185. package/dist/presentation-operations.js +0 -86
  186. package/dist/sqlite-channel.d.ts +0 -15
  187. package/dist/sqlite-channel.js +0 -600
  188. package/dist/square-index.d.ts +0 -16
  189. package/dist/square-index.js +0 -106
  190. package/dist/wake-preview.d.ts +0 -7
  191. package/dist/wake-preview.js +0 -15
  192. package/plugins/claude/hooks/register.js +0 -132
  193. package/plugins/claude/types.d.ts +0 -7
  194. package/plugins/opencode/delivery.ts +0 -198
  195. /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
  196. /package/dist/{channel-values.js → square-values.js} +0 -0
@@ -1,198 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { formatMessageId, parseMessageId } from './message.js';
3
- import { pendingAddresses, pendingHeadAt, pendingMessageHeads, presentationBlocksWake } from './channel-delivery.js';
4
- import { deliveryDeferralExpiry } from './delivery-deferral.js';
5
- import { dispatchEffect } from './delivery-effects.js';
6
- import { projectChannelNotification, renderSquareReminder } from './channel-presentation.js';
7
- /** Standalone Channel projection; final send currentness is still checked by the dispatcher. */
8
- export async function channelWakePreview(artifact, request, signal) {
9
- const index = parseMessageId(request.message);
10
- if (index === undefined)
11
- return undefined;
12
- return (await artifact.read(facts => {
13
- const seat = facts.endpoint(request.participant);
14
- const now = Date.now();
15
- if (seat === undefined || seat.attachment !== 'attached' || seat.owner !== request.seat.owner
16
- || seat.generation !== request.seat.generation || pendingHeadAt(facts, request.participant, index, now) === undefined
17
- || deliveryDeferralExpiry(facts, request.seat, index, now) !== undefined)
18
- return undefined;
19
- const item = facts.message(index);
20
- if (item === undefined)
21
- return undefined;
22
- const projected = projectChannelNotification({ squarePath: request.location, recipient: request.participant,
23
- actor: item.from, body: item.body, ...(item.preview === undefined ? {} : { preview: item.preview }) });
24
- return { payload: renderSquareReminder([projected.text]), coverage: projected.clipped ? 'clipped' : 'full' };
25
- }, signal)).value;
26
- }
27
- function routeFor(location, seat, now) {
28
- if (seat.attachment !== 'attached' || seat.wakeAddress === undefined)
29
- return undefined;
30
- return {
31
- location, participant: seat.participant,
32
- kind: seat.wakeAddress.kind, address: { ...seat.wakeAddress.address }, updatedAt: now,
33
- };
34
- }
35
- async function currentness(artifact, request, now) {
36
- const index = parseMessageId(request.message);
37
- const { value } = await artifact.read((facts) => {
38
- const currentSeat = facts.endpoint(request.participant);
39
- const seatCurrent = currentSeat !== undefined && currentSeat.attachment === 'attached' && currentSeat.generation === request.seat.generation
40
- && currentSeat.owner === request.seat.owner
41
- && currentSeat.wakeAddress?.kind === request.route.kind
42
- && Object.keys(currentSeat.wakeAddress.address).length === Object.keys(request.route.address).length
43
- && Object.entries(currentSeat.wakeAddress.address).every(([key, val]) => request.route.address[key] === val);
44
- const head = pendingHeadAt(facts, request.participant, index, now);
45
- const activityPending = seatCurrent && head !== undefined;
46
- const cancelledThrough = facts.cancelledThrough(request.participant, request.seat.generation);
47
- const cancelled = cancelledThrough !== undefined && index <= cancelledThrough;
48
- const evidence = facts.deliveryEvidence(request.participant, request.seat.generation, formatMessageId(index));
49
- const presented = presentationBlocksWake(evidence, now);
50
- const deferred = deliveryDeferralExpiry(facts, request.seat, index, now) !== undefined;
51
- return { current: activityPending && !cancelled && !presented && !deferred, activityPending, seatCurrent, cancelled, presented };
52
- });
53
- return value;
54
- }
55
- async function candidates(artifact, requested, now = Date.now()) {
56
- const { value } = await artifact.read((facts) => {
57
- const heads = requested === undefined
58
- ? facts.endpoints().filter(endpoint => endpoint.attachment === 'attached').flatMap(endpoint => pendingMessageHeads(facts, endpoint.participant, now))
59
- : pendingAddresses(facts, requested, now);
60
- return heads.flatMap(head => {
61
- const seat = facts.endpoint(head.recipient);
62
- return seat === undefined || seat.attachment !== 'attached' || deliveryDeferralExpiry(facts, seat, head.index, now) !== undefined
63
- ? [] : [{ seat, head }];
64
- });
65
- });
66
- return value;
67
- }
68
- function safeJson(value) {
69
- if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number')
70
- return value;
71
- if (Array.isArray(value))
72
- return value.map(safeJson);
73
- if (typeof value === 'object')
74
- return Object.fromEntries(Object.entries(value)
75
- .filter(([, item]) => item !== undefined).map(([key, item]) => [key, safeJson(item)]));
76
- return String(value);
77
- }
78
- export async function deliverPending(input) {
79
- // The resource owner supplies its identity; delivery does not interpret it as a file.
80
- const location = input.location;
81
- const named = input.message;
82
- const requested = named === undefined ? undefined : typeof named === 'number' ? named : parseMessageId(named);
83
- if (named !== undefined && requested === undefined)
84
- return;
85
- const pending = await candidates(input.artifact, requested, input.now ?? Date.now());
86
- // Preserve order within a receiving owner; unrelated receivers need not wait for each other.
87
- const receivers = new Map();
88
- for (const candidate of pending) {
89
- const key = candidate.seat.owner;
90
- const group = receivers.get(key) ?? [];
91
- group.push(candidate);
92
- receivers.set(key, group);
93
- }
94
- const dispatch = async ({ seat, head }) => {
95
- const route = routeFor(location, seat, input.now ?? Date.now());
96
- if (route === undefined)
97
- return;
98
- const request = {
99
- location, participant: seat.participant, message: formatMessageId(head.index), actor: head.from,
100
- seat: { participant: seat.participant, owner: seat.owner, generation: seat.generation }, route,
101
- };
102
- if (input.transport.probe !== undefined) {
103
- try {
104
- const probe = await input.transport.probe(route);
105
- if (probe === false || typeof probe === 'object' && probe.outcome === 'not-capable')
106
- return;
107
- }
108
- catch {
109
- return;
110
- }
111
- }
112
- const claimToken = randomUUID();
113
- const leaseMs = input.timeoutMs ?? 5000;
114
- let snapshot;
115
- await dispatchEffect({
116
- async claim() {
117
- const claim = await input.artifact.claimDelivery({ seat: request.seat, message: request.message, kind: 'wake',
118
- route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
119
- return claim.status === 'acquired' ? { token: claimToken, ordinal: claim.attemptN } : undefined;
120
- },
121
- async current() {
122
- snapshot = undefined;
123
- snapshot = await currentness(input.artifact, request, input.now ?? Date.now());
124
- return snapshot.current;
125
- },
126
- async settle(claim, completion) {
127
- let fields = {};
128
- if (completion.reason === 'not-current')
129
- fields = {
130
- signature: 'pre_send_revalidation_failed',
131
- message: completion.phase === 'final' ? 'The notification changed before the final send check.'
132
- : snapshot === undefined ? 'Square delivery state could not be verified.' : 'The notification is no longer current.',
133
- diagnostic: snapshot ?? (completion.phase === 'final' ? { observationAvailable: false } : undefined),
134
- };
135
- else if (completion.reason === 'timeout')
136
- fields = { diagnostic: 'transport timeout' };
137
- else if (completion.reason === 'threw')
138
- fields = { diagnostic: completion.error instanceof Error ? completion.error.message : String(completion.error) };
139
- else if (completion.reason === 'not-capable')
140
- fields = {
141
- signature: 'transport_not_capable', diagnostic: completion.data?.diagnostic,
142
- };
143
- else if (completion.reason === 'returned')
144
- fields = completion.data;
145
- return input.artifact.completeDelivery({ seat: request.seat, message: request.message, claimToken: claim.token,
146
- outcome: completion.outcome, at: Date.now(),
147
- ...(fields.signature === undefined ? {} : { signature: fields.signature }),
148
- ...(fields.message === undefined ? {} : { messageText: fields.message }),
149
- ...(fields.diagnostic === undefined ? {} : { diagnostic: safeJson(fields.diagnostic) }),
150
- }).catch(() => false);
151
- },
152
- }, async ({ claim, deadline, beforeSend }) => {
153
- const { location, participant, message, actor, seat, route } = request;
154
- const prepared = input.preparePresentation === undefined
155
- ? await channelWakePreview(input.artifact, request)
156
- : await input.preparePresentation({ location, participant, message, actor, seat, route });
157
- if (prepared === undefined)
158
- return { outcome: 'not-capable', data: { diagnostic: 'The pending presentation is no longer current.' } };
159
- const remaining = deadline - Date.now();
160
- if (remaining <= 0)
161
- return { outcome: 'not-capable', data: { diagnostic: 'The presentation budget elapsed before transport.' } };
162
- const presentation = prepared;
163
- const preparationSeat = Object.freeze({ ...request.seat });
164
- const channel = { prepare: (details, signal) => input.artifact.prepareDelivery({
165
- seat: preparationSeat, claimToken: claim.token, correlation: details.correlation,
166
- coverage: details.coverage, description: details.description,
167
- }, signal) };
168
- const outcome = await input.transport.attempt({ ...request, seat: { ...request.seat }, channel, claimToken: claim.token, attemptN: claim.ordinal,
169
- ...(presentation === undefined ? {} : { presentation }) }, remaining, beforeSend);
170
- return { outcome: outcome.outcome, data: outcome };
171
- }, { timeoutMs: leaseMs });
172
- };
173
- const groups = receivers.values();
174
- const settled = await Promise.allSettled(Array.from({ length: Math.min(4, receivers.size) }, async () => {
175
- for (const group of groups)
176
- for (const candidate of group)
177
- await dispatch(candidate);
178
- }));
179
- // Even an unexpected failure must drain siblings before the caller closes its artifact.
180
- for (const result of settled)
181
- if (result.status === 'rejected')
182
- throw result.reason;
183
- }
184
- export async function sweepPending(input) {
185
- if (input.limit <= 0)
186
- return [];
187
- const pending = await candidates(input.artifact, undefined, input.now);
188
- const selected = [];
189
- for (const { head } of pending) {
190
- if (input.now - head.at <= input.graceMs)
191
- continue;
192
- if (!selected.includes(head.index))
193
- selected.push(head.index);
194
- if (selected.length >= input.limit)
195
- break;
196
- }
197
- return selected;
198
- }
@@ -1,5 +0,0 @@
1
- /** Storage-independent attempt transitions and bound effect lifecycles. */
2
- export { claimAttempt, prepareAttempt, settleAttempt, presentationBlocksDispatch, automaticDispatchEligible } from './delivery-attempts.js';
3
- export type { DeliveryPurpose, AttemptOutcome, Attempt, AttemptRows, AttemptClaim } from './delivery-attempts.js';
4
- export { dispatchEffect, preparePresentation, presentPrepared, presentPreparedBatch } from './delivery-effects.js';
5
- export type { EffectClaim, EffectPort, Persistence, Settlement, DispatchOutcome, DispatchCompletion, DispatchContext, DispatchReport, PresentationCompletion, PresentationWork, PreparedPresentation, PresentationControl, PresentationReport } from './delivery-effects.js';
@@ -1,3 +0,0 @@
1
- /** Storage-independent attempt transitions and bound effect lifecycles. */
2
- export { claimAttempt, prepareAttempt, settleAttempt, presentationBlocksDispatch, automaticDispatchEligible } from './delivery-attempts.js';
3
- export { dispatchEffect, preparePresentation, presentPrepared, presentPreparedBatch } from './delivery-effects.js';
@@ -1,56 +0,0 @@
1
- import type { DirectedNotificationRoute, Owner, WakeAddress } from './model.js';
2
- import type { SquareArtifactPort, SquareRead, EndpointGeneration } from './ports.js';
3
- import { formatMessageId } from './message.js';
4
- export type { DirectedNotificationRoute } from './model.js';
5
- /** One lower pending message plus its upper reason when an Agora say created it. */
6
- export interface PendingMessage {
7
- readonly index: number;
8
- readonly at: number;
9
- readonly actor: string;
10
- readonly recipient: string;
11
- readonly generation?: EndpointGeneration;
12
- /** Agora activity that shares this message, when one exists. */
13
- readonly activityIndex?: number;
14
- readonly route?: DirectedNotificationRoute;
15
- }
16
- export { pendingHeadAt, pendingMessageHeads, pendingAddresses, presentationBlocksWake, automaticWakeEligible } from './channel-delivery.js';
17
- /** Notification pending is not cursor suffix or historical catch membership. */
18
- export declare function pendingHead(facts: SquareRead, name: string, index: number, now?: number): PendingMessage | undefined;
19
- /** Current native eligibility shared by wake selection, preview and ACK. */
20
- export interface NativePendingInput {
21
- readonly location: string;
22
- readonly participant: string;
23
- readonly generation: EndpointGeneration;
24
- readonly owner?: Owner;
25
- readonly index: number;
26
- readonly route: WakeAddress;
27
- readonly now: number;
28
- readonly cancelledThrough?: number;
29
- }
30
- export declare function nativePendingHead(facts: SquareRead, input: NativePendingInput): PendingMessage | undefined;
31
- /** Still-pending directed messages for one recipient, oldest first. Bodies belong to presentation. */
32
- export declare function pendingHeads(facts: SquareRead, name: string, now?: number, suppress?: boolean): PendingMessage[];
33
- export interface PendingScan {
34
- readonly roster: readonly {
35
- readonly name: string;
36
- readonly generation?: string;
37
- }[];
38
- readonly throughIndex: number;
39
- }
40
- export declare function capturePendingScan(facts: SquareRead): PendingScan;
41
- export declare function sameJoin(facts: SquareRead, captured: {
42
- readonly name: string;
43
- readonly generation?: string;
44
- }): boolean;
45
- /** Finite bodyless pass: unrelated revisions never rewind the last examined message. */
46
- export declare function pendingPages(artifact: SquareArtifactPort, scan: PendingScan, afterIndex?: number): AsyncGenerator<PendingMessage[], void, unknown>;
47
- /** A pass retains only its captured joins; routes and individual receipts are fresh. */
48
- export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly PendingMessage[]): {
49
- pending: PendingMessage[];
50
- };
51
- /** Global discovery alone sweeps every endpoint; exact dispatch uses pendingAddresses. */
52
- export declare function collectPending(artifact: SquareArtifactPort): Promise<{
53
- pending: PendingMessage[];
54
- scan: PendingScan;
55
- }>;
56
- export { formatMessageId };
package/dist/delivery.js DELETED
@@ -1,80 +0,0 @@
1
- import { formatMessageId } from './message.js';
2
- import { deliveryDeferralExpiry } from './delivery-deferral.js';
3
- export { pendingHeadAt, pendingMessageHeads, pendingAddresses, presentationBlocksWake, automaticWakeEligible } from './channel-delivery.js';
4
- import { pendingHeadAt, pendingMessageHeads } from './channel-delivery.js';
5
- /** Upper enrichment: the reason this recipient was reached, when an Agora say owns the message. */
6
- function enriched(facts, head) {
7
- const relation = facts.activityByMessage(head.index);
8
- return { index: head.index, at: head.at, actor: head.from, recipient: head.recipient,
9
- ...(head.generation === undefined ? {} : { generation: head.generation }),
10
- ...(relation === undefined ? {} : { activityIndex: relation.index, route: facts.directedAt(head.recipient, relation.index)?.route }) };
11
- }
12
- /** Notification pending is not cursor suffix or historical catch membership. */
13
- export function pendingHead(facts, name, index, now = Date.now()) {
14
- const head = pendingHeadAt(facts.channel, name, index, now);
15
- return head === undefined ? undefined : enriched(facts, head);
16
- }
17
- export function nativePendingHead(facts, input) {
18
- const seat = facts.channel.endpoint(input.participant);
19
- if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== input.generation
20
- || input.owner !== undefined && seat.owner !== input.owner
21
- || !sameWakeAddress(seat.wakeAddress, input.route))
22
- return undefined;
23
- const cancelledThrough = facts.channel.cancelledThrough(seat.participant, seat.generation);
24
- if (input.index <= Math.max(input.cancelledThrough ?? -1, cancelledThrough ?? -1))
25
- return undefined;
26
- if (deliveryDeferralExpiry(facts.channel, seat, input.index, input.now) !== undefined)
27
- return undefined;
28
- return pendingHead(facts, seat.participant, input.index, input.now);
29
- }
30
- /** Still-pending directed messages for one recipient, oldest first. Bodies belong to presentation. */
31
- export function pendingHeads(facts, name, now = Date.now(), suppress = true) {
32
- return pendingMessageHeads(facts.channel, name, now, suppress).map(head => enriched(facts, head));
33
- }
34
- export function capturePendingScan(facts) {
35
- return { roster: facts.channel.endpoints().filter(endpoint => endpoint.attachment === 'attached').map(endpoint => ({ name: endpoint.participant, generation: endpoint.generation })),
36
- throughIndex: facts.channel.header().lastMessageIndex };
37
- }
38
- export function sameJoin(facts, captured) {
39
- const current = facts.channel.endpoint(captured.name);
40
- return current?.attachment === 'attached' && current.generation === captured.generation;
41
- }
42
- /** Finite bodyless pass: unrelated revisions never rewind the last examined message. */
43
- export async function* pendingPages(artifact, scan, afterIndex = -1) {
44
- const captured = new Map(scan.roster.map((participant) => [participant.name, participant]));
45
- let cursor = afterIndex;
46
- while (cursor < scan.throughIndex) {
47
- const { value: page } = await artifact.read((facts) => {
48
- const items = facts.channel.endpoints().filter(endpoint => endpoint.attachment === 'attached' && captured.has(endpoint.participant)).flatMap(endpoint => facts.channel.directed(endpoint.participant, { afterIndex: cursor, throughIndex: scan.throughIndex, unseenOnly: true, currentGeneration: true }, { edge: 'oldest', limit: 100 }).items
49
- .filter(() => sameJoin(facts, captured.get(endpoint.participant)))
50
- .map(head => enriched(facts, head)));
51
- return { items, next: items.at(-1)?.index, done: items.length === 0 };
52
- });
53
- yield page.items;
54
- if (page.done || page.next === undefined)
55
- break;
56
- cursor = page.next;
57
- }
58
- }
59
- /** A pass retains only its captured joins; routes and individual receipts are fresh. */
60
- export function pendingFacts(facts, scan, heads) {
61
- const joined = new Set(scan.roster.filter((participant) => sameJoin(facts, participant)).map((participant) => participant.name));
62
- return { pending: heads.filter((head) => joined.has(head.recipient)
63
- && pendingHeadAt(facts.channel, head.recipient, head.index) !== undefined) };
64
- }
65
- /** Global discovery alone sweeps every endpoint; exact dispatch uses pendingAddresses. */
66
- export async function collectPending(artifact) {
67
- const { value: scan } = await artifact.read(capturePendingScan);
68
- const pending = [];
69
- for await (const page of pendingPages(artifact, scan))
70
- pending.push(...page);
71
- const { value } = await artifact.read((facts) => ({ scan, ...pendingFacts(facts, scan, pending) }));
72
- return value;
73
- }
74
- function sameWakeAddress(left, right) {
75
- if (left === undefined || right === undefined || left.kind !== right.kind)
76
- return false;
77
- const keys = Object.keys(left.address);
78
- return keys.length === Object.keys(right.address).length && keys.every(key => left.address[key] === right.address[key]);
79
- }
80
- export { formatMessageId };
@@ -1,19 +0,0 @@
1
- import { type OpenCodeNativeSession } from '../../packages/agent-delivery/src/opencode-native.js';
2
- export interface OpenCodeContextMessage {
3
- readonly id?: string;
4
- readonly role: string;
5
- readonly content: readonly {
6
- readonly type: string;
7
- readonly text?: string;
8
- }[];
9
- }
10
- export declare function indexedOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<readonly string[]>;
11
- export declare function discoverOpenCodeSeats(sessionId: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<import("../../model.js").LocatedSeat[]>;
12
- /** The exact prepared native message can authorize artifact-backed presentation once. */
13
- export declare function observeOpenCodeContext(sessionId: string, messages: readonly OpenCodeContextMessage[], env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
14
- /** The native receiver owns no claims outside the artifact; HOME is used only for discovery. */
15
- export declare function receiveOpenCodePending(target: OpenCodeNativeSession, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
16
- /** Publish the native address into current artifact seats; the legacy route projection is not read. */
17
- export declare function publishOpenCodeAddress(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<boolean>;
18
- export declare function cancelOpenCodeDeliveries(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
19
- export declare function endOpenCodeSession(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
@@ -1,223 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { squareWakeProjection } from '../../wake-preview.js';
3
- import { channelArtifact, createArtifactPort } from '../../artifact.js';
4
- import { deliverPending } from '../../delivery-operations.js';
5
- import { nativePendingSource, observeSessionPending, projectSessionSeats } from '../../inbox.js';
6
- import { automaticWakeEligible, nativePendingHead, presentationBlocksWake } from '../../delivery.js';
7
- import { harnessOwner, decodeHarnessOwner } from '../../owner.js';
8
- import { formatMessageId, parseMessageId } from '../../message.js';
9
- import { indexedSquareEntries } from '../../square-index.js';
10
- import { asSeatRef } from '../../seats.js';
11
- import { restoreSessionSeats } from '../../session-lifecycle.js';
12
- import { presentPending } from '../../presentation-operations.js';
13
- import { createNativeInputId, sendNativeText } from '../../packages/agent-delivery/src/opencode-native.js';
14
- const routeKind = 'opencode-server';
15
- const ownerFor = (sessionId) => harnessOwner({ channel: 'opencode', session: sessionId });
16
- const payloadHash = (payload) => createHash('sha256').update(payload).digest('hex');
17
- export async function indexedOpenCodeSessions(env) {
18
- return [...new Set((await indexedSquareEntries({}, env)).flatMap((entry) => {
19
- const coordinate = decodeHarnessOwner(entry.owner);
20
- return coordinate?.channel === 'opencode' ? [coordinate.session] : [];
21
- }))];
22
- }
23
- export async function discoverOpenCodeSeats(sessionId, env, signal) {
24
- return projectSessionSeats({ owner: ownerFor(sessionId), env }, signal);
25
- }
26
- function sameSeat(left, right) {
27
- return left !== undefined && left.attachment === 'attached' && left.generation === right.generation && left.owner === right.owner;
28
- }
29
- function createOpenCodeWakeTransport(target, lifetime) {
30
- return {
31
- probe: async (route) => !lifetime.aborted && route.kind === routeKind
32
- && route.address.sessionId === target.sessionId,
33
- async attempt(request, timeoutMs, beforeSend) {
34
- const presentation = request.presentation;
35
- if (request.claimToken === undefined || parseMessageId(request.message) === undefined || presentation === undefined
36
- || request.route.kind !== routeKind || request.route.address.sessionId !== target.sessionId) {
37
- return { outcome: 'not-capable', diagnostic: 'OpenCode attention has no matching artifact claim or session.' };
38
- }
39
- const signal = AbortSignal.any([lifetime, AbortSignal.timeout(timeoutMs)]);
40
- const started = Date.now();
41
- if (signal.aborted || !await beforeSend())
42
- return { outcome: 'not-capable', diagnostic: 'OpenCode attention changed before send.' };
43
- const inputId = createNativeInputId();
44
- const correlation = { harness: 'opencode', token: inputId, payloadHash: payloadHash(presentation.payload) };
45
- if (!await request.channel.prepare({ correlation,
46
- coverage: presentation.coverage, ...(presentation.description === undefined ? {} : { description: presentation.description }) }, signal))
47
- return { outcome: 'not-capable', diagnostic: 'OpenCode delivery claim is no longer current.' };
48
- if (signal.aborted || !await beforeSend())
49
- return { outcome: 'not-capable', diagnostic: 'OpenCode attention was consumed before send.' };
50
- const result = await sendNativeText(target, presentation.payload, {
51
- inputId, delivery: 'steer', timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)), signal,
52
- });
53
- if (result.state === 'accepted')
54
- return { outcome: 'accepted' };
55
- if (result.state === 'unavailable')
56
- return { outcome: 'failed', signature: result.code };
57
- return { outcome: 'unknown', signature: result.code };
58
- },
59
- };
60
- }
61
- function textMessage(message) {
62
- if (message.role !== 'user' || message.id === undefined || !message.content.every((part) => part.type === 'text' && typeof part.text === 'string'))
63
- return undefined;
64
- return message.content.map((part) => part.text).join('');
65
- }
66
- /** The exact prepared native message can authorize artifact-backed presentation once. */
67
- export async function observeOpenCodeContext(sessionId, messages, env, signal) {
68
- const observed = new Map(messages.flatMap((message) => {
69
- const text = textMessage(message);
70
- return text === undefined ? [] : [[message.id, payloadHash(text)]];
71
- }));
72
- if (observed.size === 0 || signal.aborted)
73
- return;
74
- for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
75
- if (signal.aborted)
76
- return;
77
- const artifact = createArtifactPort(location, undefined, signal);
78
- try {
79
- const { value: matches } = await artifact.read((facts) => {
80
- if (!sameSeat(facts.seat(seat.participant), seat))
81
- return [];
82
- return facts.deliveryEvidence(seat.participant, seat.generation).flatMap((evidence) => {
83
- const token = evidence.correlation?.harness === 'opencode' ? evidence.correlation.token : undefined;
84
- const hash = token === undefined ? undefined : observed.get(token);
85
- const index = parseMessageId(evidence.message), now = Date.now();
86
- if (token === undefined || hash === undefined || evidence.claimToken === undefined || index === undefined
87
- || evidence.route === undefined || !['dispatching', 'unknown', 'accepted'].includes(evidence.outcome)
88
- || evidence.correlation?.payloadHash !== hash
89
- || evidence.route.kind !== routeKind
90
- || presentationBlocksWake(facts.deliveryEvidence(seat.participant, seat.generation, evidence.message), now))
91
- return [];
92
- const source = nativePendingSource(facts, { location, participant: seat.participant, generation: seat.generation,
93
- owner: seat.owner, index, route: evidence.route, now });
94
- if (source === undefined)
95
- return [];
96
- return [{ evidence, index, hash, clipped: evidence.coverage === 'clipped' }];
97
- });
98
- }, signal);
99
- for (const { evidence, index, hash, clipped } of matches) {
100
- if (signal.aborted || evidence.claimToken === undefined)
101
- return;
102
- await presentPending({ artifact, location, participant: seat.participant,
103
- message: formatMessageId(index), seat: asSeatRef(seat), observationOf: evidence.claimToken, sink: { present() { } }, markSeen: !clipped, signal,
104
- current: (facts) => {
105
- const correlation = facts.deliveryByToken(evidence.claimToken);
106
- return nativePendingHead(facts, { location, participant: seat.participant, generation: seat.generation,
107
- owner: seat.owner, index, route: evidence.route, now: Date.now() }) !== undefined
108
- && ['dispatching', 'unknown', 'accepted'].includes(correlation?.outcome ?? '')
109
- && correlation?.correlation?.harness === 'opencode'
110
- && correlation.correlation.token !== undefined
111
- && correlation.correlation.payloadHash === hash;
112
- },
113
- });
114
- }
115
- }
116
- finally {
117
- await artifact.close();
118
- }
119
- }
120
- }
121
- /** The native receiver owns no claims outside the artifact; HOME is used only for discovery. */
122
- export async function receiveOpenCodePending(target, env, signal) {
123
- const observer = await observeSessionPending(ownerFor(target.sessionId), env);
124
- const transport = createOpenCodeWakeTransport(target, signal);
125
- let waiting;
126
- try {
127
- while (!signal.aborted) {
128
- const armed = new Promise((resolve) => {
129
- waiting = observer.wait(Infinity, { signal, skipImmediate: true, onChangeArmed: resolve });
130
- void waiting.catch(() => resolve(false));
131
- });
132
- if (!await armed) {
133
- await waiting;
134
- break;
135
- }
136
- for (const membership of await observer.read({ signal })) {
137
- const { location, seat } = membership;
138
- if (signal.aborted)
139
- return;
140
- const artifact = createArtifactPort(location, undefined, signal);
141
- try {
142
- const { value: notifications } = await artifact.read((facts) => {
143
- if (!sameSeat(facts.seat(seat.participant), seat))
144
- return [];
145
- return membership.notifications.filter((note) => automaticWakeEligible(facts.deliveryEvidence(seat.participant, seat.generation, formatMessageId(note.messageIndex)), Date.now()));
146
- }, signal);
147
- for (const note of notifications) {
148
- if (signal.aborted)
149
- return;
150
- await deliverPending({ artifact: channelArtifact(artifact), preparePresentation: squareWakeProjection(artifact), transport, location, message: formatMessageId(note.messageIndex), now: Date.now() });
151
- }
152
- }
153
- catch (error) {
154
- if (signal.aborted)
155
- throw signal.reason ?? error;
156
- // A candidate that became unreadable cannot authorize sending; the shared observer
157
- // keeps its subscription and will reconsider the artifact after repair.
158
- }
159
- finally {
160
- await artifact.close();
161
- }
162
- }
163
- await waiting;
164
- waiting = undefined;
165
- }
166
- }
167
- catch (error) {
168
- if (!signal.aborted)
169
- throw error;
170
- }
171
- finally {
172
- observer.close();
173
- await waiting?.catch(() => { });
174
- }
175
- }
176
- /** Publish the native address into current artifact seats; the legacy route projection is not read. */
177
- export async function publishOpenCodeAddress(sessionId, env, signal) {
178
- await restoreSessionSeats(ownerFor(sessionId), env, { kind: routeKind, address: { sessionId } }, signal);
179
- const seats = (await discoverOpenCodeSeats(sessionId, env, signal)).filter(({ seat }) => seat.attachment === 'attached');
180
- for (const { location, seat } of seats) {
181
- if (signal.aborted)
182
- return false;
183
- const artifact = createArtifactPort(location, undefined, signal);
184
- try {
185
- await artifact.transactAs({ kind: 'participant', seat: asSeatRef(seat) }, (facts) => facts.updateWakeAddress(asSeatRef(seat), { kind: routeKind, address: { sessionId } }));
186
- }
187
- finally {
188
- await artifact.close();
189
- }
190
- }
191
- return seats.length > 0;
192
- }
193
- export async function cancelOpenCodeDeliveries(sessionId, env, signal) {
194
- for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
195
- if (signal.aborted)
196
- return;
197
- if (seat.attachment !== 'attached')
198
- continue;
199
- const artifact = createArtifactPort(location, undefined, signal);
200
- try {
201
- const { value: through } = await artifact.read((facts) => facts.channel.header().lastMessageIndex, signal);
202
- if (through >= 0)
203
- await artifact.cancelDeliveries(asSeatRef(seat), through, signal);
204
- }
205
- finally {
206
- await artifact.close();
207
- }
208
- }
209
- }
210
- export async function endOpenCodeSession(sessionId, env, signal) {
211
- for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
212
- if (signal.aborted)
213
- return;
214
- const { Square } = await import('../../square-api.js');
215
- const square = await Square.at({ path: location, env });
216
- try {
217
- await square.endHeld(seat.participant, { owner: seat.owner, generation: seat.generation });
218
- }
219
- finally {
220
- await square.close();
221
- }
222
- }
223
- }
@@ -1,5 +0,0 @@
1
- import type { PresentationResult, PresentPendingInput } from './ports.js';
2
- /** Prepare custody before any external effect. A batch prepares every included item first. */
3
- export declare function preparePendingPresentation(input: Omit<PresentPendingInput, 'sink'>): Promise<import("./delivery-effects.js").PreparedPresentation<import("./channel-ports.js").MessageFact> | undefined>;
4
- /** A sink error cannot prove that content was never injected. */
5
- export declare function presentPending(input: PresentPendingInput): Promise<PresentationResult>;