@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
@@ -5,9 +5,9 @@ import { squareLocation } from './square-location.js';
5
5
  import crypto from 'node:crypto';
6
6
  import { setTimeout as sleep } from 'node:timers/promises';
7
7
  import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.js';
8
- import { Channel, ChannelEndpoint, checkControl, failure, squareClock } from './channel-internal.js';
9
- import { channelArtifact } from './artifact.js';
10
- import { deliverPending } from './delivery-operations.js';
8
+ import { checkControl, failure, squareClock } from './operation-control.js';
9
+ import { validateMetadataMatch } from './delivery-deferral.js';
10
+ import { flushSquarePosts, reconcileSquareDelivery, readSquareSessions, ensureInitialDeliverySession } from './square-posting.js';
11
11
  import { attentionChange, coreDone, coreListening, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
12
12
  import { WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
13
13
  import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
@@ -16,17 +16,15 @@ import { expressHint } from './hints.js';
16
16
  import { asSeatRef, participantAuthority, requireJoinName, requireOwner } from './seats.js';
17
17
  import { recoveryObservation, entryPresentation, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, toPerceivedActivity, publicActivities, selectHistory, NON_READ, HISTORY_MAX_LIMIT, HISTORY_DEFAULT_LIMIT } from './views.js';
18
18
  import { sessionInboxCounts } from './inbox.js';
19
- import { squareIndexPath } from './square-index.js';
20
19
  import { participantIdentity } from './participant-identity.js';
21
20
  import { joinRecoveryCommand } from './presentation.js';
22
21
  import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
23
- import { squareWakeProjection } from './wake-preview.js';
24
22
  import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuild } from './artifact.js';
25
23
  import { activityQueryFilter } from './activity-query.js';
26
24
  import { discoverSquares } from './list.js';
27
25
  import { streamSquare } from './stream.js';
28
26
  export { createSquareResourceOwner } from './artifact.js';
29
- export { squareClock } from './channel-internal.js';
27
+ export { squareClock } from './operation-control.js';
30
28
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
31
29
  export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
32
30
  export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
@@ -46,21 +44,20 @@ export class SqliteSquare {
46
44
  location;
47
45
  artifact;
48
46
  clock;
49
- wakeTransport;
47
+ env;
48
+ closesArtifact;
50
49
  deliveryLocation;
51
- channel;
52
- constructor(location, artifact, clock, wakeTransport, indexPath, closesArtifact = true, deliveryLocation = location) {
50
+ constructor(location, artifact, clock, env, closesArtifact = true, deliveryLocation = location) {
53
51
  this.location = location;
54
52
  this.artifact = artifact;
55
53
  this.clock = clock;
56
- this.wakeTransport = wakeTransport;
54
+ this.env = env;
55
+ this.closesArtifact = closesArtifact;
57
56
  this.deliveryLocation = deliveryLocation;
58
- this.channel = Channel.fromPort({ location, artifact: channelArtifact(artifact), clock,
59
- ...(wakeTransport === undefined ? {} : { deliverEffect: message => deliverPending({ artifact: channelArtifact(artifact),
60
- preparePresentation: squareWakeProjection(artifact), transport: wakeTransport, location: deliveryLocation,
61
- ...(message === undefined ? {} : { message }), now: clock() }) }),
62
- ...(indexPath === undefined ? {} : { indexPath }), closesArtifact });
63
57
  }
58
+ reconcile() { return reconcileSquareDelivery(this.artifact, this.deliveryLocation, this.env, this.clock); }
59
+ // Posting is retryable after commit; a delivery failure cannot make a landed write unsent.
60
+ async flush() { await flushSquarePosts(this.artifact, this.deliveryLocation, this.env, this.clock).catch(() => undefined); }
64
61
  static async at(input, control) {
65
62
  const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
66
63
  const clock = squareClock(env, input.clock);
@@ -69,7 +66,7 @@ export class SqliteSquare {
69
66
  const artifact = borrowed ?? createArtifactPort(location, undefined, control?.signal);
70
67
  try {
71
68
  await artifact.read(facts => facts.header(), control?.signal);
72
- return new SqliteSquare(location, artifact, clock, input.wakeTransport || undefined, squareIndexPath(env), borrowed === undefined, await canonicalPath(location));
69
+ return new SqliteSquare(location, artifact, clock, env, borrowed === undefined, await canonicalPath(location));
73
70
  }
74
71
  catch (error) {
75
72
  if (borrowed === undefined)
@@ -89,18 +86,15 @@ export class SqliteSquare {
89
86
  }
90
87
  static inMemory(input) {
91
88
  const env = { ...(input.env ?? process.env) };
92
- return new SqliteSquare(`memory://${crypto.randomUUID()}`, createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), input.wakeTransport || undefined);
89
+ return new SqliteSquare(`memory://${crypto.randomUUID()}`, createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), env);
93
90
  }
94
- /** Agora join composes membership and endpoint custody in one transaction. */
91
+ /** Join establishes membership and local producer custody in one transaction. */
95
92
  async joinSeat(name, options, control) {
96
93
  checkControl(control);
97
- const last = options?.last ?? HISTORY_DEFAULT_LIMIT;
98
- if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
99
- throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
100
- const owner = requireOwner(options?.owner), wakeAddress = options.wakeAddress, now = this.clock();
94
+ const owner = requireOwner(options?.owner), now = this.clock();
101
95
  const initial = !options.takeover ? undefined : (await this.artifact.read(facts => facts.participant(name)?.joined ? facts.seat(name) : undefined, control?.signal)).value;
102
- const authority = initial === undefined ? { kind: 'join', participant: name, owner, wakeAddress }
103
- : { kind: 'takeover', participant: initial.participant, owner, expectedGeneration: initial.generation, wakeAddress };
96
+ const authority = initial === undefined ? { kind: 'join', participant: name, owner }
97
+ : { kind: 'takeover', participant: initial.participant, owner, expectedGeneration: initial.generation };
104
98
  const committed = (await this.artifact.transactAs(authority, facts => {
105
99
  const participant = facts.participant(name)?.name ?? name;
106
100
  const current = facts.participant(participant), currentSeat = facts.seat(participant);
@@ -114,10 +108,8 @@ export class SqliteSquare {
114
108
  else if (current?.joined) {
115
109
  if (currentSeat === undefined || currentSeat.owner !== owner)
116
110
  throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another owner; take it over explicitly`);
117
- if (currentSeat.attachment === 'detached')
118
- facts.reattachSeat(asSeatRef(currentSeat), wakeAddress);
119
- else if (wakeAddress !== undefined && (wakeAddress.kind !== 'paseo' || currentSeat.wakeAddress === undefined || currentSeat.wakeAddress.kind === 'paseo'))
120
- facts.updateWakeAddress(asSeatRef(currentSeat), wakeAddress);
111
+ if (currentSeat.attachment !== 'attached')
112
+ throw new SquareError('already_joined', `${participantIdentity(participant)} is away; resume the host session first`);
121
113
  kind = 'reconnected';
122
114
  }
123
115
  if (kind !== 'reconnected') {
@@ -128,12 +120,11 @@ export class SqliteSquare {
128
120
  }
129
121
  const seat = facts.seat(participant);
130
122
  if (seat === undefined)
131
- throw new SquareError('invalid_args', 'The join activity did not establish its artifact endpoint.');
123
+ throw new SquareError('invalid_args', 'The join activity did not establish participant custody.');
132
124
  return { seat, activity, kind,
133
125
  ...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
134
- deliveryAvailable: seat.wakeAddress !== undefined } }) };
126
+ deliveryAvailable: false } }) };
135
127
  }, control?.signal)).value;
136
- await this.channel.remember(owner);
137
128
  return committed;
138
129
  }
139
130
  async requiredSeat(name, { owner }) {
@@ -143,10 +134,10 @@ export class SqliteSquare {
143
134
  const participant = facts.participant(name), seat = facts.seat(name);
144
135
  if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
145
136
  const reason = participant === undefined ? 'has never stepped into this square'
146
- : !participant.joined ? 'needs a current attached endpoint; join first'
137
+ : !participant.joined ? 'needs to join first'
147
138
  : seat !== undefined && seat.owner !== owner ? 'is not held by this owner; join before writing'
148
- : seat?.attachment === 'detached' ? 'is away — join again before writing'
149
- : 'needs a current attached endpoint; join first';
139
+ : seat?.attachment === 'detached' ? 'is away; resume the host session first'
140
+ : 'needs a current participant seat; join first';
150
141
  throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
151
142
  reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
152
143
  });
@@ -154,53 +145,6 @@ export class SqliteSquare {
154
145
  return seat;
155
146
  })).value;
156
147
  }
157
- async reattachSeat(name, { owner, wakeAddress }, control) {
158
- checkControl(control);
159
- validateName(name);
160
- requireOwner(owner);
161
- const captured = (await this.artifact.read(facts => facts.seat(name), control?.signal)).value;
162
- if (captured === undefined || captured.owner !== owner)
163
- return null;
164
- const seat = (await this.artifact.transactAs({ kind: 'join', participant: name, owner, wakeAddress }, facts => {
165
- const current = facts.seat(name);
166
- if (!facts.participant(name)?.joined || current === undefined || current.generation !== captured.generation || current.owner !== owner)
167
- return null;
168
- if (current.attachment === 'detached')
169
- facts.reattachSeat(asSeatRef(current), wakeAddress);
170
- else if (wakeAddress !== undefined && (wakeAddress.kind !== 'paseo' || current.wakeAddress === undefined || current.wakeAddress.kind === 'paseo'))
171
- facts.updateWakeAddress(asSeatRef(current), wakeAddress);
172
- return facts.seat(name);
173
- }, control?.signal)).value;
174
- if (seat !== null)
175
- await this.channel.remember(owner);
176
- return seat;
177
- }
178
- async endHeldSeat(name, identity) {
179
- const captured = (await this.artifact.read(facts => {
180
- const seat = facts.seat(name);
181
- return seat?.generation === identity.generation && seat.owner === identity.owner ? seat : undefined;
182
- })).value;
183
- if (captured === undefined)
184
- return null;
185
- return (await this.artifact.transactAs({ kind: 'host-cleanup', seat: captured }, facts => {
186
- const current = facts.seat(name);
187
- if (current === undefined || current.generation !== captured.generation || current.owner !== captured.owner)
188
- return null;
189
- return { activity: toPublicActivity(facts.land(coreDone(facts, current.participant, '', this.clock()))) };
190
- })).value;
191
- }
192
- async cancelHeld(name, identity) {
193
- const captured = (await this.artifact.read(facts => {
194
- const seat = facts.seat(name);
195
- return seat?.generation === identity.generation && seat.owner === identity.owner ? seat : undefined;
196
- })).value;
197
- if (captured === undefined)
198
- return false;
199
- return (await this.artifact.transactAs({ kind: 'participant', seat: captured }, facts => {
200
- const boundary = facts.channel.header().lastMessageIndex;
201
- return boundary < 0 ? false : facts.channel.cancelThrough(captured, boundary);
202
- })).value;
203
- }
204
148
  observation(standingCount, participant, now = this.clock()) {
205
149
  return { location: this.location, standingCount, now,
206
150
  ...(participant === undefined ? {} : { participant }) };
@@ -208,6 +152,28 @@ export class SqliteSquare {
208
152
  async contextObservation(participant, control) {
209
153
  return (await this.artifact.read(facts => this.observation(inSquareCount(facts), participant), control?.signal)).value;
210
154
  }
155
+ /** Host liveness is a read-only, non-atomic overlay; it never changes membership. */
156
+ async withHostPresence(select, control) {
157
+ const seats = (await this.artifact.read(facts => facts.roster().filter(person => person.joined)
158
+ .flatMap(person => { const seat = facts.seat(person.name); return seat === undefined ? [] : [seat]; }), control?.signal)).value;
159
+ const sessions = await readSquareSessions(seats, this.env);
160
+ const captured = new Map(seats.map(seat => [nameKey(seat.participant), seat]));
161
+ return (await this.artifact.read(facts => {
162
+ const seat = (name) => {
163
+ const current = facts.seat(name);
164
+ if (current === undefined)
165
+ return undefined;
166
+ const before = captured.get(nameKey(current.participant));
167
+ const attached = before?.owner === current.owner && before.generation === current.generation
168
+ && sessions.get(current.owner)?.state === 'attached';
169
+ return { ...current, attachment: attached ? 'attached' : 'detached' };
170
+ };
171
+ return select(Object.assign({}, facts, { seat }), name => {
172
+ const current = seat(name);
173
+ return current?.attachment === 'attached' && (sessions.get(current.owner)?.routes.length ?? 0) > 0;
174
+ });
175
+ }, control?.signal)).value;
176
+ }
211
177
  handle(seat) { return new SqliteSquare.ParticipantHandle(seat.participant, this, seat); }
212
178
  // The nested handle uses its resource's private dependencies without copying
213
179
  // them into a second context.
@@ -215,14 +181,12 @@ export class SqliteSquare {
215
181
  name;
216
182
  owner;
217
183
  seat;
218
- endpoint;
219
184
  constructor(name, owner, seat) {
220
185
  this.name = name;
221
186
  this.owner = owner;
222
187
  this.seat = seat;
223
- this.endpoint = new ChannelEndpoint(owner.channel, seat);
224
188
  }
225
- get generation() { return this.endpoint.generation; }
189
+ get generation() { return this.seat.generation; }
226
190
  async express(bodyOrInput, optionsOrControl = {}, operationControl) {
227
191
  const square = this.owner;
228
192
  const input = typeof bodyOrInput === 'object' && bodyOrInput !== null ? bodyOrInput : undefined;
@@ -233,12 +197,14 @@ export class SqliteSquare {
233
197
  let landedId;
234
198
  try {
235
199
  checkControl(control);
200
+ await square.reconcile();
236
201
  name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
237
202
  const seat = this.seat;
238
203
  const context = !options.observe ? undefined : (await square.artifact.read(facts => expressionContext(facts, name), control?.signal)).value;
239
204
  while (true) {
240
205
  checkControl(control);
241
206
  try {
207
+ await square.reconcile();
242
208
  effect = 'unknown';
243
209
  const now = square.clock();
244
210
  const reply = rejectUnsentActivity(() => {
@@ -267,21 +233,17 @@ export class SqliteSquare {
267
233
  return proposed;
268
234
  });
269
235
  const stored = facts.land(decision.act);
270
- // One explicit projection: a landed say resolves its own Channel content in this transaction.
271
- const activity = stored.kind === 'say' ? facts.activities([stored.index])[0] : stored;
272
- if (activity === undefined)
273
- throw new SquareError('io', 'The landed activity is missing.');
274
- return { stored, activity, standingCount: inSquareCount(facts),
236
+ return { stored, standingCount: inSquareCount(facts),
275
237
  ownActivityCount: facts.participant(name).sayCount, hardCap: facts.header().hardCap,
276
238
  ...(decision.unmatchedMentions === undefined ? {} : { unmatchedMentions: decision.unmatchedMentions }),
277
239
  ...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) };
278
240
  }, control?.signal)).value;
279
241
  landedId = formatActivityId(committed.stored.index);
280
242
  effect = 'landed';
281
- await square.channel.deliver(committed.stored.kind === 'say' ? committed.stored.messageIndex : undefined);
282
- const { stored, activity, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
243
+ await square.flush();
244
+ const { stored, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
283
245
  const hint = expressHint(ownActivityCount);
284
- return { activity: toPublicActivity(activity), ...metadata,
246
+ return { activity: toPublicActivity(stored), ...metadata,
285
247
  ...(hint === undefined ? {} : { hint }), ...(context === undefined ? {} : { observation: {
286
248
  ...square.observation(standingCount, name, now), ownActivityCount, hardCap, context, participant: name,
287
249
  } }) };
@@ -304,10 +266,11 @@ export class SqliteSquare {
304
266
  }
305
267
  async listener(verb, target, options, control) {
306
268
  const square = this.owner;
307
- const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
308
269
  checkControl(control);
270
+ await square.reconcile();
271
+ const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
309
272
  const seat = this.seat, now = square.clock();
310
- return (await square.artifact.transactAs(participantAuthority(seat), facts => {
273
+ const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
311
274
  const act = attentionChange(facts, verb, name, target, now);
312
275
  const activity = act === undefined ? null : toPublicActivity(facts.land(act));
313
276
  return { activity, ...(!options.observe ? {} : { observation: {
@@ -315,6 +278,8 @@ export class SqliteSquare {
315
278
  target: activity?.target ?? target,
316
279
  } }) };
317
280
  }, control?.signal)).value;
281
+ await square.flush();
282
+ return result;
318
283
  }
319
284
  listen(target, options = {}, control) { return this.listener('listen', target, options, control); }
320
285
  ignore(target, options = {}, control) { return this.listener('ignore', target, options, control); }
@@ -328,6 +293,7 @@ export class SqliteSquare {
328
293
  }
329
294
  /** Receipt selection rechecks the lease inside the writer, not at its last heartbeat. */
330
295
  async catchPage(name, seat, options, control, leaseId) {
296
+ await this.owner.reconcile();
331
297
  return (await this.owner.artifact.transactAs(participantAuthority(seat), facts => {
332
298
  if (leaseId !== undefined && facts.lease(name)?.leaseId !== leaseId)
333
299
  return null;
@@ -346,27 +312,35 @@ export class SqliteSquare {
346
312
  }, square.clock()))).value;
347
313
  if (reason === 'drained')
348
314
  reason = view.terminalStatus ?? reason;
315
+ await square.flush();
349
316
  if (leaseId !== undefined) {
350
317
  await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
351
318
  leaseId = undefined;
352
319
  }
353
- let presence;
320
+ let presence, deliveryAvailable = false;
354
321
  if (options.observe) {
355
322
  try {
356
- presence = (await square.artifact.read(facts => catchPresence(facts, square.clock()), control?.signal)).value;
323
+ const observed = await square.withHostPresence((facts, available) => {
324
+ const data = catchPresence(facts, square.clock());
325
+ return { presence: { ...data, participants: data.participants.map(person => ({ ...person, deliveryAvailable: available(person.name) })) },
326
+ deliveryAvailable: available(view.participant) };
327
+ }, control);
328
+ presence = observed.presence;
329
+ deliveryAvailable = observed.deliveryAvailable;
357
330
  }
358
331
  catch { /* Optional presence cannot erase the completed selection or receipts. */ }
359
332
  }
360
333
  return { activities: view.activities, remaining: view.remaining, reason,
361
334
  ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
362
335
  ...square.observation(view.standingCount, view.participant, view.now), participant: view.participant,
363
- deliveryAvailable: view.deliveryAvailable,
336
+ deliveryAvailable,
364
337
  ownActivityCount: view.ownActivityCount, hardCap: view.hardCap,
365
338
  ...(presence === undefined ? { enrichmentUnavailable: true } : { presence }),
366
339
  } }) };
367
340
  };
368
341
  try {
369
342
  checkControl(control);
343
+ await square.reconcile();
370
344
  validateName(name);
371
345
  if (!Number.isFinite(idle) || idle < 0)
372
346
  throw new SquareError('invalid_args', 'Catch idle duration must be a non-negative number');
@@ -441,7 +415,7 @@ export class SqliteSquare {
441
415
  const limit = options.limit === undefined ? 100 : options.limit;
442
416
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
443
417
  throw new SquareError('invalid_args', 'Pending limit must be a positive integer no greater than 100.');
444
- // Pending follows the shared archive cursor: act/N with before/after, never a Channel msg/N cursor.
418
+ // Pending follows the shared archive cursor: act/N with before/after.
445
419
  const shared = activityQueryFilter(options);
446
420
  const filter = { kinds: ['say'], unseenOnly: true,
447
421
  ...(shared.indexes === undefined ? {} : { indexes: shared.indexes }),
@@ -457,45 +431,52 @@ export class SqliteSquare {
457
431
  remaining: facts.directedCount(this.name, filter) - heads.items.length };
458
432
  }, control?.signal)).value;
459
433
  }
460
- ack(ids, control) {
434
+ ack(input, control) {
461
435
  const square = this.owner;
436
+ checkControl(control);
462
437
  return square.artifact.transactAs(participantAuthority(this.seat), facts => {
438
+ if (!Array.isArray(input)) {
439
+ const exact = input;
440
+ if (exact?.future !== 'exact')
441
+ throw new SquareError('invalid_args', 'Ack requires an exact future predicate.');
442
+ validateName(exact.from);
443
+ return { consumed: facts.ackMatching(asSeatRef(this.seat), { from: exact.from, match: validateMetadataMatch(exact.match), future: 'exact' }, square.clock()) };
444
+ }
463
445
  const indexes = [];
464
- for (const id of ids) {
446
+ for (const id of input) {
465
447
  const activity = facts.activity(parseRequiredActivityId(id));
466
- if (activity?.kind !== 'say' || activity.messageIndex === undefined)
448
+ if (activity?.kind !== 'say')
467
449
  throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
468
450
  const head = facts.directed(this.name, { indexes: [activity.index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items[0];
469
451
  if (head === undefined)
470
452
  throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
471
- indexes.push(activity.messageIndex);
453
+ indexes.push(activity.index);
472
454
  }
473
- return { consumed: facts.channel.markSeen(this.name, indexes, square.clock()) };
455
+ return { consumed: facts.markSeen(this.name, indexes, square.clock()) };
474
456
  }, control?.signal).then(result => result.value);
475
457
  }
476
- deferDelivery(input, control) {
477
- return this.endpoint.deferDelivery(input, control);
478
- }
479
- renewDeliveryDeferral(id, durationMs, control) {
480
- return this.endpoint.renewDeliveryDeferral(id, durationMs, control);
481
- }
482
- releaseDeliveryDeferral(id, control) {
483
- return this.endpoint.releaseDeliveryDeferral(id, control);
484
- }
485
458
  async done(body, options = {}, control) {
486
459
  validateDoneBody(body);
460
+ checkControl(control);
487
461
  const square = this.owner;
462
+ await square.reconcile();
488
463
  const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
489
464
  const seat = this.seat, now = square.clock();
490
- return (await square.artifact.transactAs(participantAuthority(seat), facts => {
465
+ const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
491
466
  const stored = facts.land(coreDone(facts, name, body ?? '', now));
492
467
  return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name, now),
493
468
  }) };
494
469
  }, control?.signal)).value;
470
+ await square.flush();
471
+ return result;
495
472
  }
496
473
  };
497
- send(input, control) {
498
- return this.channel.send(input, control);
474
+ async send(input, control) {
475
+ checkControl(control);
476
+ await this.reconcile();
477
+ const result = (await this.artifact.transactAs({ kind: 'send', actor: input.from }, facts => facts.insertNotice(input, this.clock()), control?.signal)).value;
478
+ await this.flush();
479
+ return result;
499
480
  }
500
481
  async participant(name, options) {
501
482
  return this.handle(await this.requiredSeat(name, options));
@@ -505,28 +486,36 @@ export class SqliteSquare {
505
486
  try {
506
487
  checkControl(control);
507
488
  name = requireJoinName(requested);
489
+ const last = options.last ?? HISTORY_DEFAULT_LIMIT;
490
+ if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
491
+ throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
492
+ if (!await ensureInitialDeliverySession(requireOwner(options.owner), this.env, options.wakeAddress))
493
+ throw new SquareError('already_joined', `${participantIdentity(name)} is away; resume the host session first`, { reason: 'needs_seat' });
494
+ await this.reconcile();
508
495
  const committed = await this.joinSeat(name, options, control);
496
+ await this.flush();
509
497
  const participant = this.handle(committed.seat);
510
498
  const result = { participant, activity: committed.activity, kind: committed.kind,
511
499
  ...(committed.observation === undefined ? {} : { observation: committed.observation }) };
512
500
  if (!options.observe)
513
501
  return result;
514
- let entry;
502
+ let entry, deliveryAvailable = false;
515
503
  try {
516
- entry = (await this.artifact.read(facts => entryPresentation(facts, participant.name, options.last ?? HISTORY_DEFAULT_LIMIT), control?.signal)).value;
504
+ const observed = await this.withHostPresence((facts, available) => ({
505
+ entry: entryPresentation(facts, participant.name, options.last ?? HISTORY_DEFAULT_LIMIT),
506
+ deliveryAvailable: available(participant.name),
507
+ }), control);
508
+ entry = observed.entry;
509
+ deliveryAvailable = observed.deliveryAvailable;
517
510
  }
518
511
  catch { /* Keep the committed join even when optional history is unavailable. */ }
519
- return { ...result, observation: { ...committed.observation,
512
+ return { ...result, observation: { ...committed.observation, deliveryAvailable,
520
513
  ...(entry === undefined ? { enrichmentUnavailable: true } : { entry }) } };
521
514
  }
522
515
  catch (error) {
523
516
  throw failure(error, { operation: 'join', subject: 'caller', participant: name });
524
517
  }
525
518
  }
526
- async reattach(name, { owner, wakeAddress }, control) {
527
- const seat = await this.reattachSeat(name, { owner, wakeAddress }, control);
528
- return seat === null ? null : this.handle(seat);
529
- }
530
519
  async history(query = {}, control) {
531
520
  if (query.limit !== undefined && query.limit !== null && (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > HISTORY_MAX_LIMIT))
532
521
  throw new SquareError('invalid_args', `History limit must be a positive integer no greater than ${HISTORY_MAX_LIMIT}`);
@@ -558,12 +547,22 @@ export class SqliteSquare {
558
547
  }, control?.signal)).value;
559
548
  }
560
549
  async status(options = {}, control) {
561
- return (await this.artifact.read(facts => queryStatus(facts, { location: this.location, now: this.clock() }, options.viewer, options.observe === true), control?.signal)).value;
550
+ return this.withHostPresence((facts, available) => {
551
+ const result = queryStatus(facts, { location: this.location, now: this.clock() }, options.viewer, options.observe === true);
552
+ return { ...result, participants: result.participants.map(person => ({ ...person, deliveryAvailable: available(person.name) })) };
553
+ }, control);
562
554
  }
555
+ /** Local mode reads only World-known facts; it does not verify host attachment. */
563
556
  async participants(options = {}, control) {
564
- return (await this.artifact.read(facts => queryParticipants(facts, {
565
- location: this.location, now: this.clock(),
566
- }, options.observe === true), control?.signal)).value;
557
+ if (options.local)
558
+ return (await this.artifact.read(facts => {
559
+ const result = queryParticipants(facts, { location: this.location, now: this.clock() }, options.observe === true);
560
+ return { ...result, participants: result.participants.map(person => ({ ...person, deliveryAvailable: false })) };
561
+ }, control?.signal)).value;
562
+ return this.withHostPresence((facts, available) => {
563
+ const result = queryParticipants(facts, { location: this.location, now: this.clock() }, options.observe === true);
564
+ return { ...result, participants: result.participants.map(person => ({ ...person, deliveryAvailable: available(person.name) })) };
565
+ }, control);
567
566
  }
568
567
  async recovery(requested, control) {
569
568
  const facts = requested === undefined ? undefined : (await this.artifact.read(facts => recoveryObservation(facts, requested, this.clock()), control?.signal).catch(() => undefined))?.value;
@@ -575,18 +574,12 @@ export class SqliteSquare {
575
574
  return streamSquare(this.location, options, control);
576
575
  }
577
576
  async seatsOf(owner) {
578
- return this.channel.endpointsOf(owner);
579
- }
580
- close() { return this.channel.close(); }
581
- cancelDeliveries(name, identity) {
582
- return this.cancelHeld(name, identity);
583
- }
584
- detach(name, identity) {
585
- return this.channel.detach(name, identity);
586
- }
587
- endHeld(name, identity) {
588
- return this.endHeldSeat(name, identity);
577
+ requireOwner(owner);
578
+ return (await this.artifact.read(facts => facts.seatsForOwner(owner)
579
+ .filter(seat => facts.participant(seat.participant)?.joined && seat.attachment === 'attached')
580
+ .map(seat => ({ name: seat.participant, generation: seat.generation })))).value;
589
581
  }
582
+ close() { return this.closesArtifact ? this.artifact.close() : Promise.resolve(); }
590
583
  }
591
584
  export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(squareLocation(input)); }
592
585
  export function discover(input, options = {}, control) {
@@ -598,10 +591,10 @@ export function discover(input, options = {}, control) {
598
591
  export async function inbox(input, owner, options = {}, control) {
599
592
  checkControl(control);
600
593
  requireOwner(owner);
601
- const env = { ...(input.env ?? process.env) }, limit = options.limit ?? 20;
594
+ const limit = options.limit ?? 20;
602
595
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
603
596
  throw new SquareError('invalid_args', 'Inbox limit must be between 1 and 100');
604
- const rows = await sessionInboxCounts({ owner, env }, control?.signal);
597
+ const rows = await sessionInboxCounts({ owner, cwd: input.cwd }, control?.signal);
605
598
  checkControl(control);
606
599
  const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
607
600
  rows.sort((a, b) => compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name));
@@ -1,12 +1,8 @@
1
1
  import { SqliteSquare } from './sqlite-square.js';
2
2
  import { SquareError } from './model.js';
3
3
  import { requireOwner } from './seats.js';
4
- import { createDefaultWakeTransport } from './notifications.js';
5
- export const Square = { at: async (input, control) => SqliteSquare.at({ ...input, wakeTransport: await resolveWakeTransport(input) }, control),
6
- build: async (input, control) => SqliteSquare.build({ ...input, wakeTransport: await resolveWakeTransport(input) }, control), inMemory: SqliteSquare.inMemory };
7
- const resolveWakeTransport = (input) => input.wakeTransport !== undefined
8
- ? Promise.resolve(input.wakeTransport)
9
- : createDefaultWakeTransport(input.env ?? process.env).catch(() => undefined);
4
+ export const Square = { at: (input, control) => SqliteSquare.at(input, control),
5
+ build: (input, control) => SqliteSquare.build(input, control), inMemory: SqliteSquare.inMemory };
10
6
  export { requireOwner, requireJoinName } from './seats.js';
11
7
  export { squareLocation } from './square-location.js';
12
8
  export async function selectOwnSeat(square, owner, name, options) {
@@ -15,7 +11,8 @@ export async function selectOwnSeat(square, owner, name, options) {
15
11
  const caller = requireOwner(owner);
16
12
  if (name !== undefined)
17
13
  return square.participant(name, { owner: caller });
18
- const seats = (await square.seatsOf(caller)).filter(seat => seat.attachment === 'attached');
14
+ // Selection is local producer custody, not a ledger attachment check.
15
+ const seats = await square.seatsOf(caller);
19
16
  if (seats.length !== 1) {
20
17
  if (options?.optional)
21
18
  return undefined;
@@ -29,7 +29,6 @@ export type Act = {
29
29
  mentions?: readonly Participant[];
30
30
  reach?: Reach;
31
31
  reply?: number;
32
- messageIndex?: number;
33
32
  } | {
34
33
  kind: 'read';
35
34
  actor: Participant;
@@ -1,12 +1,6 @@
1
- import { type ActDraft, type Seat, type StoredAct } from './model.js';
2
- import { type EndpointGeneration } from './message.js';
1
+ import { type ActDraft, type Seat } from './model.js';
3
2
  import type { ArtifactWriteAuthority, AttentionFact, LandedRecipient, ParticipantFact, SeatRef, SquareRead, SquareWrite } from './ports.js';
4
- import type { ChannelWrite, EndpointRef } from './channel-ports.js';
5
- import type { WatchLease } from './model.js';
6
- /** Pending messages the live catch currently owns; the lower pipeline only needs this coverage. */
7
- export declare function catchCoverage(read: SquareRead, name: string, lease: WatchLease): number[];
8
- /** Artifact-private landing effect; sender capabilities never expose another recipient's claim. */
9
- export declare function extendLandingCatchCoverage(read: SquareRead, putClaim: ChannelWrite['putReceiveClaim'], activity: StoredAct, audience: readonly LandedRecipient[]): void;
3
+ import type { ProducerWrite } from './square-post.js';
10
4
  export interface LandingEffect {
11
5
  readonly activity: ActDraft & {
12
6
  readonly index: number;
@@ -23,29 +17,23 @@ export interface LandingEffect {
23
17
  export interface LandingFacts {
24
18
  readonly actor?: ParticipantFact;
25
19
  readonly standing: readonly ParticipantFact[];
26
- /** Endpoint custody at landing time, so an address fences the generation it was written for. */
27
- readonly generations: ReadonlyMap<string, EndpointGeneration>;
20
+ readonly seats: ReadonlyMap<string, Seat>;
28
21
  readonly incoming: readonly AttentionFact[];
29
22
  readonly outgoing: readonly AttentionFact[];
30
23
  readonly replyAuthor?: string;
31
24
  }
32
- /** The single landing transition, shared by both adapters and explicit rebuild. */
33
25
  export declare function deriveLanding(activity: ActDraft & {
34
26
  readonly index: number;
35
27
  }, facts: LandingFacts): LandingEffect;
36
28
  export declare function landingFacts(read: SquareRead, activity: ActDraft): LandingFacts;
37
- export interface LandingWriter extends Pick<SquareWrite, 'putLease' | 'removeLease' | 'updateWakeAddress' | 'reattachSeat' | 'detachSeat'> {
38
- /** Artifact-private endpoint persistence used only by this gate's lifecycle transition. */
29
+ export interface LandingWriter extends Pick<SquareWrite, 'putLease' | 'removeLease' | 'markSeen' | 'setSeatAttachment'>, ProducerWrite {
39
30
  storeSeat(seat: Seat): boolean;
40
- removeSeat(seat: EndpointRef): boolean;
31
+ removeSeat(seat: SeatRef): boolean;
41
32
  applyLanding(effect: LandingEffect): void;
42
- readonly channel: ChannelWrite;
43
33
  }
44
34
  export interface SquareWriteGate {
45
35
  readonly facts: SquareWrite;
46
36
  complete(): void;
47
37
  }
48
- /** Read probes and writes share the same captured-coordinate and attachment fence. */
49
38
  export declare function assertCurrentSeat(read: SquareRead, expected: SeatRef, allowDetached?: boolean): void;
50
- /** The artifact transaction supplies one captured authority; every mutation checks it here. */
51
39
  export declare function wrapSquareWrite(read: SquareRead, writes: LandingWriter, authority: ArtifactWriteAuthority): SquareWriteGate;