@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
@@ -1,173 +0,0 @@
1
- import { collectPending } from './delivery.js';
2
- import { formatMessageId, parseMessageId } from './message.js';
3
- import { participantIdentity, truncateChars } from './presentation.js';
4
- import { formatDuration } from './time.js';
5
- import { InternalSquareError, SquareError } from './model.js';
6
- import { createArtifactPort } from './artifact.js';
7
- const DISPLAY_ORDER = [
8
- 'awaiting',
9
- 'wake-accepted',
10
- 'wake-unknown',
11
- 'presented-not-delivered',
12
- 'unreachable',
13
- ];
14
- const ACTIONABLE = new Set(['wake-unknown', 'unreachable']);
15
- const DETAIL_LIMIT = 20;
16
- const DISPLAY_FIELD_LIMIT = 160;
17
- /** Classify current pending attention from the artifact's seats and delivery evidence. */
18
- export async function classifyDeliveryHealth(squarePath, opts) {
19
- const now = opts.now ?? Date.now();
20
- const artifact = createArtifactPort(squarePath);
21
- try {
22
- const { pending } = await collectPending(artifact);
23
- const current = (await artifact.read((facts) => pending.map((note) => {
24
- const seat = facts.seat(note.recipient);
25
- const evidence = seat === undefined ? [] : facts.deliveryEvidence(seat.participant, seat.generation, formatMessageId(note.index));
26
- return { note, seat, evidence };
27
- }))).value;
28
- const items = [];
29
- for (const { note, seat, evidence } of current) {
30
- const ageMs = Math.max(0, now - note.at);
31
- const presentation = evidence.some((row) => row.kind === 'presentation' && (row.outcome === 'presented' || row.outcome === 'clipped' || row.outcome === 'unknown'
32
- || row.outcome === 'dispatching'));
33
- const terminal = evidence.findLast((row) => row.kind === 'wake' && (row.outcome === 'accepted' || row.outcome === 'unknown'));
34
- const attemptRow = evidence.findLast((row) => row.kind === 'wake' && (row.outcome === 'accepted' || row.outcome === 'unknown' || row.outcome === 'failed'));
35
- const attempt = attemptRow === undefined || seat === undefined ? undefined : deliveryAttempt(squarePath, attemptRow, { squarePath, recipient: seat.participant, messageIndex: note.index });
36
- const kind = presentation
37
- ? 'presented-not-delivered'
38
- : terminal?.outcome === 'accepted'
39
- ? 'wake-accepted'
40
- : terminal?.outcome === 'unknown'
41
- ? 'wake-unknown'
42
- : ageMs > opts.graceMs && seat?.wakeAddress === undefined
43
- ? 'unreachable'
44
- : 'awaiting';
45
- items.push({
46
- squarePath,
47
- recipient: note.recipient,
48
- messageIndex: note.index,
49
- actor: note.actor,
50
- at: note.at,
51
- ageMs,
52
- route: note.route,
53
- kind,
54
- ...(attempt === undefined ? {} : { attempt }),
55
- });
56
- }
57
- // The captured pending set is the ownership boundary: a candidate that was
58
- // not pending at the scan cannot become a health item during this read.
59
- return items;
60
- }
61
- catch (error) {
62
- if (error instanceof InternalSquareError && error.code === 'not_found')
63
- throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
64
- throw error;
65
- }
66
- finally {
67
- await artifact.close();
68
- }
69
- }
70
- function displayField(value) {
71
- const truncated = truncateChars(value, DISPLAY_FIELD_LIMIT - 1);
72
- return truncated.remaining === 0 ? truncated.text : `${truncated.text}…`;
73
- }
74
- export function renderDeliveryHealth(items, releases = [], now = Date.now(), nativeAttempts = []) {
75
- const out = items.length === 0 ? ['✓ no pending delivery attention'] : [`· delivery attention · ${items.length} pending`];
76
- if (items.length > 0) {
77
- let displayed = 0;
78
- for (const kind of DISPLAY_ORDER) {
79
- const group = items.filter((item) => item.kind === kind);
80
- if (group.length === 0)
81
- continue;
82
- out.push(`${ACTIONABLE.has(kind) ? '✕' : '○'} ${kind}: ${group.length}`);
83
- const details = group.slice(0, DETAIL_LIMIT - displayed);
84
- out.push(...details.map((item) => {
85
- const evidence = item.attempt?.signature === undefined ? '' : ` · ${displayField(item.attempt.signature)}`;
86
- return ` · ${formatMessageId(item.messageIndex)} → ${displayField(participantIdentity(item.recipient))} from ${displayField(participantIdentity(item.actor))} · ${formatDuration(item.ageMs)}${evidence}`;
87
- }));
88
- displayed += details.length;
89
- }
90
- if (items.length > DETAIL_LIMIT)
91
- out.push(`${DETAIL_LIMIT} of ${items.length} pending details shown`);
92
- }
93
- if (nativeAttempts.length > 0) {
94
- out.push(`· Claude native custody · ${nativeAttempts.length}`);
95
- out.push(...nativeAttempts.slice(0, DETAIL_LIMIT).map((attempt) => {
96
- const observations = [...attempt.nativeObservations ?? []].sort((a, b) => a.observedAt - b.observedAt);
97
- const last = observations.at(-1);
98
- const states = observations.map((observation) => `${observation.state}${observation.reason ? ` (${observation.reason})` : ''}`).join(' → ');
99
- return ` · ${formatMessageId(attempt.attention.messageIndex)} → ${displayField(participantIdentity(attempt.attention.recipient))} · ${attempt.outcome} · native ${displayField(states)} · ${formatDuration(Math.max(0, now - last.observedAt))} · message ${displayField(last.messageId)}`;
100
- }));
101
- if (nativeAttempts.length > DETAIL_LIMIT)
102
- out.push(`${DETAIL_LIMIT} of ${nativeAttempts.length} native custody details shown`);
103
- }
104
- if (releases.length > 0) {
105
- out.push(`· recent wake releases · ${releases.length}`);
106
- out.push(...releases.slice(0, DETAIL_LIMIT).map((release) => formatRelease(release, now)));
107
- if (releases.length > DETAIL_LIMIT)
108
- out.push(`${DETAIL_LIMIT} of ${releases.length} wake releases shown`);
109
- }
110
- return out;
111
- }
112
- function formatRelease(release, now) {
113
- const route = displayField(release.routeKind ?? 'unknown route');
114
- const reason = displayField(release.signature ?? 'reason not recorded');
115
- const age = formatDuration(Math.max(0, now - release.at));
116
- const message = release.message === undefined ? '' : ` — ${displayField(release.message)}`;
117
- const diagnostic = release.diagnostic === undefined ? '' : ` · ${displayField(JSON.stringify(release.diagnostic))}`;
118
- return ` · ${formatMessageId(release.attention.messageIndex)} → ${displayField(participantIdentity(release.attention.recipient))} · ${route} · ${reason} · ${age}${message}${diagnostic}`;
119
- }
120
- function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: row.participant, messageIndex: parseMessageId(row.message) ?? 0 }) {
121
- if (row.route?.kind === undefined || row.outcome === 'released' || row.outcome === 'dispatching')
122
- return undefined;
123
- return {
124
- at: row.at,
125
- attention,
126
- routeKind: row.route.kind,
127
- outcome: row.outcome,
128
- attemptN: row.attemptN,
129
- ...(row.signature === undefined ? {} : { signature: row.signature }),
130
- ...(row.messageText === undefined ? {} : { message: row.messageText }),
131
- ...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
132
- ...(row.nativeObservations === undefined ? {} : { nativeObservations: row.nativeObservations }),
133
- };
134
- }
135
- function deliveryRelease(squarePath, row) {
136
- return {
137
- at: row.at,
138
- attention: { squarePath, recipient: row.participant, messageIndex: parseMessageId(row.message) ?? 0 },
139
- routeKind: row.route?.kind,
140
- attemptN: row.attemptN,
141
- ...(row.signature === undefined ? {} : { signature: row.signature }),
142
- ...(row.messageText === undefined ? {} : { message: row.messageText }),
143
- ...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
144
- };
145
- }
146
- export async function doctorDeliveryHealth(squarePath, graceMs, now = Date.now(), env = process.env) {
147
- const items = await classifyDeliveryHealth(squarePath, { graceMs, now, env });
148
- const artifact = createArtifactPort(squarePath);
149
- try {
150
- const { value: rows } = await artifact.read((facts) => facts.roster().flatMap((participant) => {
151
- const seat = facts.seat(participant.name);
152
- return seat === undefined ? [] : facts.deliveryEvidence(seat.participant, seat.generation);
153
- }));
154
- const releases = rows.filter((row) => row.kind === 'wake' && row.outcome === 'released')
155
- .map((row) => deliveryRelease(squarePath, row))
156
- .sort((left, right) => right.at - left.at);
157
- const nativeAttempts = rows.flatMap((row) => {
158
- if (row.kind !== 'wake' || row.nativeObservations?.length === 0)
159
- return [];
160
- const attempt = deliveryAttempt(squarePath, row);
161
- return attempt?.nativeObservations?.length ? [attempt] : [];
162
- }).sort((a, b) => Math.max(...b.nativeObservations.map((row) => row.observedAt)) - Math.max(...a.nativeObservations.map((row) => row.observedAt)));
163
- return renderDeliveryHealth(items, releases, now, nativeAttempts);
164
- }
165
- catch (error) {
166
- if (error instanceof InternalSquareError && error.code === 'not_found')
167
- throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
168
- throw error;
169
- }
170
- finally {
171
- await artifact.close();
172
- }
173
- }
@@ -1,22 +0,0 @@
1
- import type { ChannelArtifact } from './channel-ports.js';
2
- import type { WakeProjection, WakeProjectionContext, WakeRequest, WakeTransportPort } from './channel-transport.js';
3
- interface DeliverPendingInput {
4
- readonly artifact: ChannelArtifact;
5
- readonly preparePresentation?: WakeProjection;
6
- readonly transport: WakeTransportPort;
7
- readonly location: string;
8
- readonly timeoutMs?: number;
9
- readonly now?: number;
10
- readonly message?: string | number;
11
- }
12
- /** Standalone Channel projection; final send currentness is still checked by the dispatcher. */
13
- export declare function channelWakePreview(artifact: ChannelArtifact, request: WakeProjectionContext, signal?: AbortSignal): Promise<NonNullable<WakeRequest['presentation']> | undefined>;
14
- export declare function deliverPending(input: DeliverPendingInput): Promise<void>;
15
- export declare function sweepPending(input: {
16
- readonly artifact: ChannelArtifact;
17
- readonly location: string;
18
- readonly now: number;
19
- readonly graceMs: number;
20
- readonly limit: number;
21
- }): Promise<number[]>;
22
- export {};
@@ -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>;