@astrosheep/square 0.3.106 → 0.4.0

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 (142) 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 +8 -12
  5. package/dist/artifact.js +66 -343
  6. package/dist/attention-presentation.d.ts +33 -7
  7. package/dist/attention-presentation.js +33 -69
  8. package/dist/boundary-presentation.d.ts +0 -2
  9. package/dist/boundary-presentation.js +17 -22
  10. package/dist/catch-decisions.d.ts +2 -2
  11. package/dist/catch-decisions.js +14 -4
  12. package/dist/catch-selection.d.ts +8 -0
  13. package/dist/catch-selection.js +5 -0
  14. package/dist/channel-artifact.d.ts +10 -0
  15. package/dist/channel-artifact.js +55 -0
  16. package/dist/channel-delivery.d.ts +11 -0
  17. package/dist/channel-delivery.js +51 -0
  18. package/dist/channel-domain.d.ts +3 -0
  19. package/dist/channel-domain.js +83 -0
  20. package/dist/channel-facade.d.ts +76 -0
  21. package/dist/channel-facade.js +1 -0
  22. package/dist/channel-internal.d.ts +126 -0
  23. package/dist/channel-internal.js +440 -0
  24. package/dist/channel-metadata.d.ts +11 -0
  25. package/dist/channel-metadata.js +1 -0
  26. package/dist/channel-ports.d.ts +241 -0
  27. package/dist/channel-ports.js +1 -0
  28. package/dist/channel-presentation.d.ts +17 -0
  29. package/dist/channel-presentation.js +22 -0
  30. package/dist/channel-sqlite-schema.d.ts +13 -0
  31. package/dist/channel-sqlite-schema.js +81 -0
  32. package/dist/channel-transport.d.ts +62 -0
  33. package/dist/channel-transport.js +1 -0
  34. package/dist/channel-values.d.ts +27 -0
  35. package/dist/channel-values.js +90 -0
  36. package/dist/channel.d.ts +31 -21
  37. package/dist/channel.js +42 -74
  38. package/dist/claude-delivery-status.js +1 -1
  39. package/dist/claude-delivery.d.ts +2 -5
  40. package/dist/claude-delivery.js +56 -103
  41. package/dist/claude-mod.js +4 -4
  42. package/dist/claude-transport.d.ts +5 -0
  43. package/dist/claude-transport.js +58 -0
  44. package/dist/cli/context.d.ts +2 -2
  45. package/dist/cli/context.js +0 -1
  46. package/dist/cli/maintenance-commands.d.ts +0 -4
  47. package/dist/cli/maintenance-commands.js +2 -29
  48. package/dist/cli/program.js +1 -1
  49. package/dist/cli/registry.js +1 -2
  50. package/dist/codex-address.d.ts +1 -1
  51. package/dist/codex-queue.d.ts +2 -2
  52. package/dist/codex-queue.js +1 -3
  53. package/dist/decisions.js +3 -3
  54. package/dist/delivery-deferral.d.ts +9 -18
  55. package/dist/delivery-deferral.js +4 -4
  56. package/dist/delivery-health.d.ts +4 -4
  57. package/dist/delivery-health.js +11 -11
  58. package/dist/delivery-operations.d.ts +15 -2
  59. package/dist/delivery-operations.js +60 -30
  60. package/dist/delivery.d.ts +44 -33
  61. package/dist/delivery.js +49 -75
  62. package/dist/help.js +1 -6
  63. package/dist/http-delivery.d.ts +2 -2
  64. package/dist/http-delivery.js +2 -4
  65. package/dist/inbox.d.ts +10 -18
  66. package/dist/inbox.js +44 -53
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/mcp.d.ts +2 -1
  70. package/dist/mcp.js +40 -70
  71. package/dist/message.d.ts +7 -0
  72. package/dist/message.js +23 -0
  73. package/dist/model.d.ts +41 -77
  74. package/dist/model.js +7 -139
  75. package/dist/neutral.d.ts +72 -0
  76. package/dist/neutral.js +27 -0
  77. package/dist/notifications.d.ts +2 -8
  78. package/dist/notifications.js +15 -50
  79. package/dist/operation-presentation.d.ts +1 -2
  80. package/dist/operation-presentation.js +1 -7
  81. package/dist/owner.d.ts +2 -2
  82. package/dist/owner.js +1 -1
  83. package/dist/paseo-delivery.d.ts +1 -1
  84. package/dist/paseo-delivery.js +2 -4
  85. package/dist/plugins/opencode/delivery.js +27 -42
  86. package/dist/ports.d.ts +46 -168
  87. package/dist/presentation-operations.d.ts +1 -14
  88. package/dist/presentation-operations.js +16 -16
  89. package/dist/presentation.d.ts +5 -5
  90. package/dist/presentation.js +1 -0
  91. package/dist/runtime.js +2 -2
  92. package/dist/search.js +1 -1
  93. package/dist/sqlite-channel.d.ts +15 -0
  94. package/dist/sqlite-channel.js +600 -0
  95. package/dist/sqlite-framing.d.ts +8 -0
  96. package/dist/sqlite-framing.js +31 -0
  97. package/dist/sqlite-square.d.ts +12 -13
  98. package/dist/sqlite-square.js +192 -248
  99. package/dist/sqlite-values.d.ts +21 -0
  100. package/dist/sqlite-values.js +78 -0
  101. package/dist/square-api.d.ts +1 -1
  102. package/dist/square-api.js +1 -1
  103. package/dist/square-core.d.ts +1 -1
  104. package/dist/square-domain.d.ts +21 -7
  105. package/dist/square-domain.js +93 -94
  106. package/dist/square-facade.d.ts +12 -66
  107. package/dist/square-index.d.ts +1 -1
  108. package/dist/square-index.js +1 -1
  109. package/dist/square-location.d.ts +6 -1
  110. package/dist/square-location.js +1 -1
  111. package/dist/square-receive.d.ts +2 -3
  112. package/dist/square-sqlite.d.ts +45 -44
  113. package/dist/square-sqlite.js +308 -764
  114. package/dist/square-tools.d.ts +8 -0
  115. package/dist/square-tools.js +39 -0
  116. package/dist/views.d.ts +6 -6
  117. package/dist/views.js +15 -9
  118. package/dist/wake-preview.d.ts +7 -0
  119. package/dist/wake-preview.js +15 -0
  120. package/dist/wake-sink.d.ts +2 -2
  121. package/dist/wake-sink.js +1 -1
  122. package/dist/wake-transports.d.ts +7 -0
  123. package/dist/wake-transports.js +42 -0
  124. package/guides/claude-native.md +13 -10
  125. package/guides/codex-delivery.md +3 -1
  126. package/guides/participant-ownership.md +1 -1
  127. package/guides/row-runtime-delivery-evidence.md +2 -0
  128. package/guides/row-storage-design.md +19 -44
  129. package/guides/square-api.md +137 -60
  130. package/package.json +1 -1
  131. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  132. package/plugins/claude/types.d.ts +1 -1
  133. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  134. package/plugins/opencode/delivery.ts +28 -41
  135. package/plugins/pi/square-pi.js +28 -51
  136. package/dist/square-legacy-framing.d.ts +0 -11
  137. package/dist/square-legacy-framing.js +0 -64
  138. package/dist/square-migration-host-ledger.d.ts +0 -26
  139. package/dist/square-migration-host-ledger.js +0 -169
  140. package/dist/square-migration.d.ts +0 -30
  141. package/dist/square-migration.js +0 -599
  142. package/guides/migration.md +0 -156
@@ -9,7 +9,7 @@ async function handleSquareError(error, context) {
9
9
  throw error;
10
10
  const detail = error.operation;
11
11
  const squarePath = context?.squarePath;
12
- const recovery = error.facts?.reason === 'migration_required' || context === undefined || squarePath === undefined ? undefined
12
+ const recovery = context === undefined || squarePath === undefined ? undefined
13
13
  : await withSquare({ ...context, name: detail?.participant ?? context.name }, (square) => square.recovery(detail?.participant ?? context.name)).catch(() => undefined);
14
14
  const name = detail?.participant ?? recovery?.participant ?? context?.name;
15
15
  const lines = renderFailureLines(error, { squarePath, name, owner: context?.owner ?? context?.env.SQUARE_OWNER });
@@ -1,5 +1,5 @@
1
1
  import { buildCommand, doneCommand, expressCommand, ignoreCommand, joinCommand, listenCommand, listeningCommand, unlistenCommand } from './square-commands.js';
2
- import { doctorCommand, migrateCommand } from './maintenance-commands.js';
2
+ import { doctorCommand } from './maintenance-commands.js';
3
3
  import { harnessCommand, installCommand, uninstallCommand } from './harness-command.js';
4
4
  import { helpCommand, versionCommand } from './meta-commands.js';
5
5
  import { mcpServerCommand } from './mcp-server-command.js';
@@ -25,7 +25,6 @@ export const commandRegistry = [
25
25
  { names: ['uninstall'], spec: uninstallCommand },
26
26
  { names: ['harness'], spec: harnessCommand },
27
27
  { names: ['doctor'], spec: doctorCommand },
28
- { names: ['migrate'], spec: migrateCommand },
29
28
  { names: ['history'], spec: historyCommand },
30
29
  { names: ['status'], spec: statusCommand },
31
30
  { names: ['participants'], spec: participantsCommand },
@@ -1,3 +1,3 @@
1
- import type { WakeAddress } from './model.js';
1
+ import type { WakeAddress } from './channel-values.js';
2
2
  /** Capture the receiver's CLI configuration when its seat is established, not at send time. */
3
3
  export declare function codexWakeAddress(threadId: string, env: NodeJS.ProcessEnv): WakeAddress;
@@ -1,8 +1,8 @@
1
1
  import { type CodexCliAgent, type CodexCliConnectOptions } from './packages/agent-delivery/src/index.js';
2
2
  export { sendCodexQueue, CodexQueueSendError } from './packages/agent-delivery/src/codex-queue.js';
3
3
  export type { CodexQueueRequest, CodexQueueFailureKind } from './packages/agent-delivery/src/codex-queue.js';
4
- import type { WakeRequest, WakeOutcome, WakeTransportPort } from './ports.js';
5
- import type { WakeRoute } from './model.js';
4
+ import type { WakeRequest, WakeOutcome, WakeTransportPort } from './channel-transport.js';
5
+ import type { WakeRoute } from './channel-values.js';
6
6
  export interface CodexQueueTransportOptions {
7
7
  env?: NodeJS.ProcessEnv;
8
8
  connect?: (options: CodexCliConnectOptions) => Promise<CodexCliAgent>;
@@ -1,7 +1,6 @@
1
1
  import { connect, DeliveryError } from './packages/agent-delivery/src/index.js';
2
2
  export { sendCodexQueue, CodexQueueSendError } from './packages/agent-delivery/src/codex-queue.js';
3
3
  import { codexBoundaryStore } from './codex-boundary-state.js';
4
- import { wakePreview } from './delivery.js';
5
4
  export class CodexQueueTransport {
6
5
  opts;
7
6
  constructor(opts = {}) {
@@ -15,8 +14,7 @@ export class CodexQueueTransport {
15
14
  return { outcome: 'failed', signature: 'invalid_address', message: 'Codex route has no thread id.' };
16
15
  }
17
16
  const env = this.opts.env ?? process.env;
18
- const payload = request.artifact === undefined ? undefined
19
- : (await request.artifact.read((facts) => wakePreview(facts, request))).value;
17
+ const payload = request.presentation?.payload;
20
18
  if (payload === undefined)
21
19
  return { outcome: 'not-capable', diagnostic: 'No pending Square activity to preview.' };
22
20
  if (!(await beforeSend()))
package/dist/decisions.js CHANGED
@@ -51,7 +51,7 @@ export function unreadActivitySummaries(facts, name, now) {
51
51
  let afterIndex = readCursor(facts, name);
52
52
  const summaries = new Map();
53
53
  while (true) {
54
- const page = facts.directed(name, { afterIndex }, { edge: 'oldest', limit: FACT_PAGE_SIZE });
54
+ const page = facts.directed(name, { afterIndex, kinds: ['say'] }, { edge: 'oldest', limit: FACT_PAGE_SIZE });
55
55
  for (const item of page.items) {
56
56
  const summary = summaries.get(item.actor) ?? { count: 0, latestAt: -Infinity, lastIndex: item.index };
57
57
  summary.count++;
@@ -194,8 +194,8 @@ export function coreParticipants(facts, now) {
194
194
  activityCount: current.sayCount, lastActiveAt,
195
195
  presence: current.done ? 'done' : !current.joined ? 'never-joined' : facts.seat(current.name)?.attachment === 'detached' ? 'away'
196
196
  : freshWatchLease(facts, current.name, now) !== undefined ? 'watching' : 'active',
197
- unreadActivityCount: current.joined ? facts.directedCount(current.name, { afterIndex: readCursor(facts, current.name) }) : 0,
198
- pendingMentionCount: current.joined ? facts.directedCount(current.name, { afterIndex: current.lastJoinIndex ?? -1, unseenOnly: true }) : 0,
197
+ unreadActivityCount: current.joined ? facts.directedCount(current.name, { kinds: ['say'], afterIndex: readCursor(facts, current.name) }) : 0,
198
+ pendingMentionCount: current.joined ? facts.directedCount(current.name, { kinds: ['say'], afterIndex: current.lastJoinIndex ?? -1, unseenOnly: true }) : 0,
199
199
  };
200
200
  }).sort(compareParticipantActivity);
201
201
  }
@@ -1,19 +1,10 @@
1
- import { type ActivityMetadata, type ActivityId } from './model.js';
2
- import type { SeatRef, SquareRead } from './ports.js';
3
- export type MetadataScalar = null | boolean | number | string;
4
- export interface MetadataMatch {
5
- readonly fields: readonly Readonly<{
6
- path: readonly string[];
7
- equals: MetadataScalar;
8
- }>[];
9
- }
10
- export interface DeliveryDeferral {
11
- readonly id: string;
12
- readonly expiresAt: string;
13
- }
1
+ import type { MessageMetadata } from './channel-values.js';
2
+ import type { ChannelRead, EndpointRef } from './channel-ports.js';
3
+ import type { MetadataMatch } from './channel-metadata.js';
4
+ export type { MetadataMatch, MetadataScalar, DeliveryDeferral } from './channel-metadata.js';
14
5
  export interface DeliveryDeferralFact {
15
6
  readonly participant: string;
16
- readonly generation: ActivityId;
7
+ readonly generation: string;
17
8
  readonly id: string;
18
9
  readonly match: MetadataMatch;
19
10
  readonly expiresAt: number;
@@ -22,7 +13,7 @@ export declare const MAX_DELIVERY_DEFERRAL_MS: number;
22
13
  export declare function validateMetadataMatch(value: unknown): MetadataMatch;
23
14
  export declare function validateDeferralDuration(value: unknown): number;
24
15
  export declare function validateDeferralId(value: unknown): string;
25
- export declare function matchesMetadata(metadata: ActivityMetadata | undefined, match: MetadataMatch): boolean;
26
- export declare function matchingDeferrals(deferrals: readonly DeliveryDeferralFact[], metadata: ActivityMetadata | undefined, now: number): readonly DeliveryDeferralFact[];
27
- export declare function deliveryDeferralExpiry(facts: SquareRead, captured: SeatRef, index: number, now: number): number | undefined;
28
- export declare function earliestMatchingDeferralExpiry(deferrals: readonly DeliveryDeferralFact[], metadata: ActivityMetadata | undefined, now: number): number | undefined;
16
+ export declare function matchesMetadata(metadata: MessageMetadata | undefined, match: MetadataMatch): boolean;
17
+ export declare function matchingDeferrals(deferrals: readonly DeliveryDeferralFact[], metadata: MessageMetadata | undefined, now: number): readonly DeliveryDeferralFact[];
18
+ export declare function deliveryDeferralExpiry(facts: ChannelRead, captured: EndpointRef, index: number, now: number): number | undefined;
19
+ export declare function earliestMatchingDeferralExpiry(deferrals: readonly DeliveryDeferralFact[], metadata: MessageMetadata | undefined, now: number): number | undefined;
@@ -1,4 +1,4 @@
1
- import { SquareError } from './model.js';
1
+ import { SquareError } from './neutral.js';
2
2
  export const MAX_DELIVERY_DEFERRAL_MS = 24 * 60 * 60 * 1000;
3
3
  export function validateMetadataMatch(value) {
4
4
  const fail = () => { throw new SquareError('invalid_args', 'Invalid metadata match.'); };
@@ -68,14 +68,14 @@ export function matchingDeferrals(deferrals, metadata, now) {
68
68
  return deferrals.filter((lease) => lease.expiresAt > now && matchesMetadata(metadata, lease.match));
69
69
  }
70
70
  export function deliveryDeferralExpiry(facts, captured, index, now) {
71
- const seat = facts.seat(captured.participant);
71
+ const seat = facts.endpoint(captured.participant);
72
72
  if (seat === undefined || seat.generation !== captured.generation || seat.owner !== captured.owner)
73
73
  return undefined;
74
74
  const leases = facts.deliveryDeferrals(seat.participant, seat.generation);
75
75
  if (!leases.some(lease => lease.expiresAt > now))
76
76
  return undefined;
77
- const activity = facts.activity(index);
78
- return activity?.kind === 'say' ? earliestMatchingDeferralExpiry(leases, activity.metadata, now) : undefined;
77
+ const message = facts.message(index);
78
+ return message === undefined ? undefined : earliestMatchingDeferralExpiry(leases, message.metadata, now);
79
79
  }
80
80
  export function earliestMatchingDeferralExpiry(deferrals, metadata, now) {
81
81
  const matches = matchingDeferrals(deferrals, metadata, now);
@@ -6,7 +6,7 @@ export interface WakeAttempt {
6
6
  readonly attention: {
7
7
  readonly squarePath: string;
8
8
  readonly recipient: string;
9
- readonly actIndex: number;
9
+ readonly messageIndex: number;
10
10
  };
11
11
  readonly routeKind: import('./model.js').WakeRouteKind;
12
12
  readonly outcome: 'accepted' | 'unknown' | 'failed';
@@ -20,7 +20,7 @@ export interface WakeReleaseDiagnostic {
20
20
  readonly attention: {
21
21
  readonly squarePath: string;
22
22
  readonly recipient: string;
23
- readonly actIndex: number;
23
+ readonly messageIndex: number;
24
24
  };
25
25
  readonly routeKind?: import('./model.js').WakeRouteKind;
26
26
  readonly attemptN?: number;
@@ -33,11 +33,11 @@ export type DeliveryHealthKind = 'awaiting' | 'wake-accepted' | 'wake-unknown' |
33
33
  export interface DeliveryHealthItem {
34
34
  squarePath: string;
35
35
  recipient: string;
36
- actIndex: number;
36
+ messageIndex: number;
37
37
  actor: string;
38
38
  at: number;
39
39
  ageMs: number;
40
- route: DirectedNotificationRoute;
40
+ route?: DirectedNotificationRoute;
41
41
  kind: DeliveryHealthKind;
42
42
  attempt?: WakeAttempt;
43
43
  }
@@ -1,5 +1,5 @@
1
1
  import { collectPending } from './delivery.js';
2
- import { formatActivityId } from './square-core.js';
2
+ import { formatMessageId, parseMessageId } from './message.js';
3
3
  import { participantIdentity, truncateChars } from './presentation.js';
4
4
  import { formatDuration } from './time.js';
5
5
  import { InternalSquareError, SquareError } from './model.js';
@@ -22,7 +22,7 @@ export async function classifyDeliveryHealth(squarePath, opts) {
22
22
  const { pending } = await collectPending(artifact);
23
23
  const current = (await artifact.read((facts) => pending.map((note) => {
24
24
  const seat = facts.seat(note.recipient);
25
- const evidence = seat === undefined ? [] : facts.deliveryEvidence(seat.participant, seat.generation, formatActivityId(note.index));
25
+ const evidence = seat === undefined ? [] : facts.deliveryEvidence(seat.participant, seat.generation, formatMessageId(note.index));
26
26
  return { note, seat, evidence };
27
27
  }))).value;
28
28
  const items = [];
@@ -32,7 +32,7 @@ export async function classifyDeliveryHealth(squarePath, opts) {
32
32
  || row.outcome === 'dispatching'));
33
33
  const terminal = evidence.findLast((row) => row.kind === 'wake' && (row.outcome === 'accepted' || row.outcome === 'unknown'));
34
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, actIndex: note.index });
35
+ const attempt = attemptRow === undefined || seat === undefined ? undefined : deliveryAttempt(squarePath, attemptRow, { squarePath, recipient: seat.participant, messageIndex: note.index });
36
36
  const kind = presentation
37
37
  ? 'presented-not-delivered'
38
38
  : terminal?.outcome === 'accepted'
@@ -45,7 +45,7 @@ export async function classifyDeliveryHealth(squarePath, opts) {
45
45
  items.push({
46
46
  squarePath,
47
47
  recipient: note.recipient,
48
- actIndex: note.index,
48
+ messageIndex: note.index,
49
49
  actor: note.actor,
50
50
  at: note.at,
51
51
  ageMs,
@@ -83,7 +83,7 @@ export function renderDeliveryHealth(items, releases = [], now = Date.now(), nat
83
83
  const details = group.slice(0, DETAIL_LIMIT - displayed);
84
84
  out.push(...details.map((item) => {
85
85
  const evidence = item.attempt?.signature === undefined ? '' : ` · ${displayField(item.attempt.signature)}`;
86
- return ` · ${formatActivityId(item.actIndex)} → ${displayField(participantIdentity(item.recipient))} from ${displayField(participantIdentity(item.actor))} · ${formatDuration(item.ageMs)}${evidence}`;
86
+ return ` · ${formatMessageId(item.messageIndex)} → ${displayField(participantIdentity(item.recipient))} from ${displayField(participantIdentity(item.actor))} · ${formatDuration(item.ageMs)}${evidence}`;
87
87
  }));
88
88
  displayed += details.length;
89
89
  }
@@ -96,7 +96,7 @@ export function renderDeliveryHealth(items, releases = [], now = Date.now(), nat
96
96
  const observations = [...attempt.nativeObservations ?? []].sort((a, b) => a.observedAt - b.observedAt);
97
97
  const last = observations.at(-1);
98
98
  const states = observations.map((observation) => `${observation.state}${observation.reason ? ` (${observation.reason})` : ''}`).join(' → ');
99
- return ` · ${formatActivityId(attempt.attention.actIndex)} → ${displayField(participantIdentity(attempt.attention.recipient))} · ${attempt.outcome} · native ${displayField(states)} · ${formatDuration(Math.max(0, now - last.observedAt))} · message ${displayField(last.messageId)}`;
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
100
  }));
101
101
  if (nativeAttempts.length > DETAIL_LIMIT)
102
102
  out.push(`${DETAIL_LIMIT} of ${nativeAttempts.length} native custody details shown`);
@@ -115,9 +115,9 @@ function formatRelease(release, now) {
115
115
  const age = formatDuration(Math.max(0, now - release.at));
116
116
  const message = release.message === undefined ? '' : ` — ${displayField(release.message)}`;
117
117
  const diagnostic = release.diagnostic === undefined ? '' : ` · ${displayField(JSON.stringify(release.diagnostic))}`;
118
- return ` · ${formatActivityId(release.attention.actIndex)} → ${displayField(participantIdentity(release.attention.recipient))} · ${route} · ${reason} · ${age}${message}${diagnostic}`;
118
+ return ` · ${formatMessageId(release.attention.messageIndex)} → ${displayField(participantIdentity(release.attention.recipient))} · ${route} · ${reason} · ${age}${message}${diagnostic}`;
119
119
  }
120
- function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: row.participant, actIndex: Number(row.activity.slice(4)) }) {
120
+ function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: row.participant, messageIndex: parseMessageId(row.message) ?? 0 }) {
121
121
  if (row.route?.kind === undefined || row.outcome === 'released' || row.outcome === 'dispatching')
122
122
  return undefined;
123
123
  return {
@@ -127,7 +127,7 @@ function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: r
127
127
  outcome: row.outcome,
128
128
  attemptN: row.attemptN,
129
129
  ...(row.signature === undefined ? {} : { signature: row.signature }),
130
- ...(row.message === undefined ? {} : { message: row.message }),
130
+ ...(row.messageText === undefined ? {} : { message: row.messageText }),
131
131
  ...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
132
132
  ...(row.nativeObservations === undefined ? {} : { nativeObservations: row.nativeObservations }),
133
133
  };
@@ -135,11 +135,11 @@ function deliveryAttempt(squarePath, row, attention = { squarePath, recipient: r
135
135
  function deliveryRelease(squarePath, row) {
136
136
  return {
137
137
  at: row.at,
138
- attention: { squarePath, recipient: row.participant, actIndex: Number(row.activity.slice(4)) },
138
+ attention: { squarePath, recipient: row.participant, messageIndex: parseMessageId(row.message) ?? 0 },
139
139
  routeKind: row.route?.kind,
140
140
  attemptN: row.attemptN,
141
141
  ...(row.signature === undefined ? {} : { signature: row.signature }),
142
- ...(row.message === undefined ? {} : { message: row.message }),
142
+ ...(row.messageText === undefined ? {} : { message: row.messageText }),
143
143
  ...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
144
144
  };
145
145
  }
@@ -1,9 +1,22 @@
1
- import type { SquareArtifactPort, DeliverPendingInput } from './ports.js';
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>;
2
14
  export declare function deliverPending(input: DeliverPendingInput): Promise<void>;
3
15
  export declare function sweepPending(input: {
4
- readonly artifact: SquareArtifactPort;
16
+ readonly artifact: ChannelArtifact;
5
17
  readonly location: string;
6
18
  readonly now: number;
7
19
  readonly graceMs: number;
8
20
  readonly limit: number;
9
21
  }): Promise<number[]>;
22
+ export {};
@@ -1,11 +1,29 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { formatActivityId, parseActivityId } from './square-core.js';
3
- import { pendingHead, pendingHeads, pendingRecipients, presentationBlocksWake } from './delivery.js';
4
- import { asSeatRef } from './seats.js';
5
- import { freshWatchLease } from './runtime.js';
2
+ import { formatMessageId, parseMessageId } from './message.js';
3
+ import { pendingAddresses, pendingHeadAt, pendingMessageHeads, presentationBlocksWake } from './channel-delivery.js';
6
4
  import { deliveryDeferralExpiry } from './delivery-deferral.js';
7
- import { leaseOwnsNotification } from './delivery.js';
8
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
+ }
9
27
  function routeFor(location, seat, now) {
10
28
  if (seat.attachment !== 'attached' || seat.wakeAddress === undefined)
11
29
  return undefined;
@@ -15,37 +33,34 @@ function routeFor(location, seat, now) {
15
33
  };
16
34
  }
17
35
  async function currentness(artifact, request, now) {
18
- const index = parseActivityId(request.activity);
36
+ const index = parseMessageId(request.message);
19
37
  const { value } = await artifact.read((facts) => {
20
- const currentSeat = facts.seat(request.participant);
38
+ const currentSeat = facts.endpoint(request.participant);
21
39
  const seatCurrent = currentSeat !== undefined && currentSeat.attachment === 'attached' && currentSeat.generation === request.seat.generation
22
40
  && currentSeat.owner === request.seat.owner
23
41
  && currentSeat.wakeAddress?.kind === request.route.kind
24
42
  && Object.keys(currentSeat.wakeAddress.address).length === Object.keys(request.route.address).length
25
43
  && Object.entries(currentSeat.wakeAddress.address).every(([key, val]) => request.route.address[key] === val);
26
- const head = pendingHead(facts, request.participant, index);
44
+ const head = pendingHeadAt(facts, request.participant, index, now);
27
45
  const activityPending = seatCurrent && head !== undefined;
28
46
  const cancelledThrough = facts.cancelledThrough(request.participant, request.seat.generation);
29
47
  const cancelled = cancelledThrough !== undefined && index <= cancelledThrough;
30
- const evidence = facts.deliveryEvidence(request.participant, request.seat.generation, formatActivityId(index));
48
+ const evidence = facts.deliveryEvidence(request.participant, request.seat.generation, formatMessageId(index));
31
49
  const presented = presentationBlocksWake(evidence, now);
32
- const lease = freshWatchLease(facts, request.participant, now);
33
- const caught = head !== undefined && lease !== undefined && leaseOwnsNotification(lease, {
34
- actor: request.actor, route: head.route, recipient: request.participant,
35
- });
36
50
  const deferred = deliveryDeferralExpiry(facts, request.seat, index, now) !== undefined;
37
- return { current: activityPending && !cancelled && !presented && !caught && !deferred, activityPending, seatCurrent, cancelled, presented };
51
+ return { current: activityPending && !cancelled && !presented && !deferred, activityPending, seatCurrent, cancelled, presented };
38
52
  });
39
53
  return value;
40
54
  }
41
55
  async function candidates(artifact, requested, now = Date.now()) {
42
56
  const { value } = await artifact.read((facts) => {
43
57
  const heads = requested === undefined
44
- ? facts.roster().flatMap((participant) => participant.joined ? pendingHeads(facts, participant.name) : [])
45
- : pendingRecipients(facts, requested);
46
- return heads.flatMap((head) => {
47
- const seat = facts.seat(head.recipient);
48
- return seat === undefined || seat.attachment !== 'attached' || deliveryDeferralExpiry(facts, seat, head.index, now) !== undefined ? [] : [{ seat, head }];
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 }];
49
64
  });
50
65
  });
51
66
  return value;
@@ -63,9 +78,9 @@ function safeJson(value) {
63
78
  export async function deliverPending(input) {
64
79
  // The resource owner supplies its identity; delivery does not interpret it as a file.
65
80
  const location = input.location;
66
- const requested = input.activity === undefined ? undefined
67
- : typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
68
- if (input.activity !== undefined && requested === undefined)
81
+ const named = input.message;
82
+ const requested = named === undefined ? undefined : typeof named === 'number' ? named : parseMessageId(named);
83
+ if (named !== undefined && requested === undefined)
69
84
  return;
70
85
  const pending = await candidates(input.artifact, requested, input.now ?? Date.now());
71
86
  // Preserve order within a receiving owner; unrelated receivers need not wait for each other.
@@ -81,9 +96,8 @@ export async function deliverPending(input) {
81
96
  if (route === undefined)
82
97
  return;
83
98
  const request = {
84
- location, participant: seat.participant, activity: formatActivityId(head.index), actor: head.actor,
85
- seat: asSeatRef(seat), route,
86
- artifact: input.artifact,
99
+ location, participant: seat.participant, message: formatMessageId(head.index), actor: head.from,
100
+ seat: { participant: seat.participant, owner: seat.owner, generation: seat.generation }, route,
87
101
  };
88
102
  if (input.transport.probe !== undefined) {
89
103
  try {
@@ -100,7 +114,7 @@ export async function deliverPending(input) {
100
114
  let snapshot;
101
115
  await dispatchEffect({
102
116
  async claim() {
103
- const claim = await input.artifact.claimDelivery({ seat: request.seat, activity: request.activity, kind: 'wake',
117
+ const claim = await input.artifact.claimDelivery({ seat: request.seat, message: request.message, kind: 'wake',
104
118
  route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
105
119
  return claim.status === 'acquired' ? { token: claimToken, ordinal: claim.attemptN } : undefined;
106
120
  },
@@ -128,15 +142,31 @@ export async function deliverPending(input) {
128
142
  };
129
143
  else if (completion.reason === 'returned')
130
144
  fields = completion.data;
131
- return input.artifact.completeDelivery({ seat: request.seat, activity: request.activity, claimToken: claim.token,
145
+ return input.artifact.completeDelivery({ seat: request.seat, message: request.message, claimToken: claim.token,
132
146
  outcome: completion.outcome, at: Date.now(),
133
147
  ...(fields.signature === undefined ? {} : { signature: fields.signature }),
134
- ...(fields.message === undefined ? {} : { message: fields.message }),
148
+ ...(fields.message === undefined ? {} : { messageText: fields.message }),
135
149
  ...(fields.diagnostic === undefined ? {} : { diagnostic: safeJson(fields.diagnostic) }),
136
150
  }).catch(() => false);
137
151
  },
138
- }, async ({ claim, timeoutMs, beforeSend }) => {
139
- const outcome = await input.transport.attempt({ ...request, claimToken: claim.token, attemptN: claim.ordinal }, timeoutMs, beforeSend);
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);
140
170
  return { outcome: outcome.outcome, data: outcome };
141
171
  }, { timeoutMs: leaseMs });
142
172
  };
@@ -1,45 +1,56 @@
1
- import type { DirectedNotificationRoute, WatchLease } from './model.js';
2
- import type { DeliveryEvidence, DirectedHead, ParticipantFact, SquareArtifactPort, SquareRead, WakeRequest } from './ports.js';
1
+ import type { DirectedNotificationRoute, Owner, WakeAddress } from './model.js';
2
+ import type { SquareArtifactPort, SquareRead, EndpointGeneration } from './ports.js';
3
+ import { formatMessageId } from './message.js';
3
4
  export type { DirectedNotificationRoute } from './model.js';
4
- /** Exact stored activity projection for transports that send an awareness reminder. */
5
- export declare function wakePreview(facts: SquareRead, request: WakeRequest): string | undefined;
6
- export interface RoutedNotification {
7
- actor: string;
8
- route: DirectedNotificationRoute;
9
- recipient: string;
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;
10
15
  }
11
- /** Presentation custody blocks a wake, independently of native wake acceptance. */
12
- export declare function presentationBlocksWake(evidence: readonly DeliveryEvidence[], now: number): boolean;
13
- /** Preselection only: an expired wake dispatch is uncertain, never safe automatic replay. */
14
- export declare function automaticWakeEligible(evidence: readonly DeliveryEvidence[], now: number): boolean;
16
+ export { pendingHeadAt, pendingMessageHeads, pendingAddresses, presentationBlocksWake, automaticWakeEligible } from './channel-delivery.js';
15
17
  /** Notification pending is not cursor suffix or historical catch membership. */
16
- export declare function pendingHead(facts: SquareRead, name: string, index: number): DirectedHead | undefined;
17
- /** One activity's landed recipients that still owe a wake; the rest of the roster is untouched. */
18
- export declare function pendingRecipients(facts: SquareRead, index: number): DirectedHead[];
19
- /** Still-pending directed heads for one recipient, oldest first. Bodies belong to presentation. */
20
- export declare function pendingHeads(facts: SquareRead, name: string): DirectedHead[];
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[];
21
33
  export interface PendingScan {
22
- readonly roster: readonly ParticipantFact[];
34
+ readonly roster: readonly {
35
+ readonly name: string;
36
+ readonly generation?: string;
37
+ }[];
23
38
  readonly throughIndex: number;
24
39
  }
25
40
  export declare function capturePendingScan(facts: SquareRead): PendingScan;
26
- export declare function sameJoin(facts: SquareRead, captured: ParticipantFact): boolean;
27
- /** Finite bodyless pass: unrelated revisions never rewind the last examined group. */
28
- export declare function pendingPages(artifact: SquareArtifactPort, scan: PendingScan, afterIndex?: number): AsyncGenerator<{
29
- index: number;
30
- at: number;
31
- actor: string;
32
- recipient: string;
33
- route: import("./attention.js").AudienceRoute;
34
- }[], void, unknown>;
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>;
35
47
  /** A pass retains only its captured joins; routes and individual receipts are fresh. */
36
- export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly DirectedHead[]): {
37
- pending: DirectedHead[];
48
+ export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly PendingMessage[]): {
49
+ pending: PendingMessage[];
38
50
  };
39
- /** Global discovery alone sweeps pendingAudience; exact dispatch uses pendingRecipients. */
51
+ /** Global discovery alone sweeps every endpoint; exact dispatch uses pendingAddresses. */
40
52
  export declare function collectPending(artifact: SquareArtifactPort): Promise<{
41
- pending: DirectedHead[];
53
+ pending: PendingMessage[];
42
54
  scan: PendingScan;
43
55
  }>;
44
- /** A live catch owns every matching notification, independent of its page size. */
45
- export declare function leaseOwnsNotification(lease: WatchLease, notification: RoutedNotification): boolean;
56
+ export { formatMessageId };