@astrosheep/square 0.3.106 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +6 -12
  5. package/dist/artifact.js +43 -344
  6. package/dist/attention-presentation.d.ts +13 -7
  7. package/dist/attention-presentation.js +29 -68
  8. package/dist/boundary-presentation.d.ts +3 -16
  9. package/dist/boundary-presentation.js +29 -109
  10. package/dist/card-presentation.d.ts +1 -0
  11. package/dist/card-presentation.js +2 -0
  12. package/dist/catch-decisions.d.ts +2 -2
  13. package/dist/catch-decisions.js +7 -3
  14. package/dist/catch-selection.d.ts +8 -0
  15. package/dist/catch-selection.js +5 -0
  16. package/dist/claude-delivery.d.ts +14 -12
  17. package/dist/claude-delivery.js +42 -142
  18. package/dist/claude-mcp-context.js +2 -2
  19. package/dist/claude-mod.js +28 -140
  20. package/dist/claude-transport.d.ts +5 -0
  21. package/dist/claude-transport.js +34 -0
  22. package/dist/cli/context.d.ts +2 -2
  23. package/dist/cli/context.js +7 -10
  24. package/dist/cli/maintenance-commands.d.ts +0 -4
  25. package/dist/cli/maintenance-commands.js +2 -29
  26. package/dist/cli/observation-commands.d.ts +2 -0
  27. package/dist/cli/observation-commands.js +12 -2
  28. package/dist/cli/program.js +1 -1
  29. package/dist/cli/registry.js +4 -3
  30. package/dist/codex-address.d.ts +2 -2
  31. package/dist/codex-boundary-state.d.ts +1 -5
  32. package/dist/codex-boundary-state.js +0 -4
  33. package/dist/codex-hook.d.ts +7 -10
  34. package/dist/codex-hook.js +69 -33
  35. package/dist/codex-queue.d.ts +10 -11
  36. package/dist/codex-queue.js +32 -34
  37. package/dist/codex-watch.d.ts +2 -0
  38. package/dist/codex-watch.js +100 -0
  39. package/dist/decisions.js +3 -3
  40. package/dist/delivery-deferral.d.ts +1 -19
  41. package/dist/delivery-deferral.js +1 -29
  42. package/dist/delivery-frame.d.ts +7 -0
  43. package/dist/delivery-frame.js +11 -0
  44. package/dist/delivery-ledger-sqlite.d.ts +97 -0
  45. package/dist/delivery-ledger-sqlite.js +496 -0
  46. package/dist/delivery-ledger.d.ts +196 -0
  47. package/dist/delivery-ledger.js +268 -0
  48. package/dist/delivery-lifetime.d.ts +6 -0
  49. package/dist/delivery-lifetime.js +45 -0
  50. package/dist/harness-links.js +61 -7
  51. package/dist/harness.js +12 -17
  52. package/dist/help.js +3 -6
  53. package/dist/http-delivery.d.ts +5 -9
  54. package/dist/http-delivery.js +20 -79
  55. package/dist/inbox.d.ts +6 -57
  56. package/dist/inbox.js +24 -380
  57. package/dist/index.d.ts +7 -4
  58. package/dist/index.js +5 -2
  59. package/dist/mcp.d.ts +2 -1
  60. package/dist/mcp.js +40 -70
  61. package/dist/message.d.ts +7 -0
  62. package/dist/message.js +23 -0
  63. package/dist/model.d.ts +15 -91
  64. package/dist/model.js +2 -140
  65. package/dist/neutral.d.ts +72 -0
  66. package/dist/neutral.js +27 -0
  67. package/dist/notifications.d.ts +24 -26
  68. package/dist/notifications.js +39 -143
  69. package/dist/operation-control.d.ts +4 -0
  70. package/dist/operation-control.js +20 -0
  71. package/dist/operation-presentation.d.ts +1 -2
  72. package/dist/operation-presentation.js +1 -7
  73. package/dist/owner.d.ts +2 -2
  74. package/dist/owner.js +1 -1
  75. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  76. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  77. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  78. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  79. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  80. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  81. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  82. package/dist/packages/agent-delivery/src/codex.js +41 -4
  83. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  84. package/dist/packages/agent-delivery/src/index.js +1 -1
  85. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  86. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  87. package/dist/paseo-delivery.d.ts +5 -5
  88. package/dist/paseo-delivery.js +19 -99
  89. package/dist/paseo.d.ts +1 -1
  90. package/dist/paseo.js +1 -1
  91. package/dist/plugins/opencode/index.d.ts +5 -3
  92. package/dist/plugins/opencode/index.js +99 -21
  93. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  94. package/dist/plugins/opencode-shared/delivery.js +109 -0
  95. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  96. package/dist/plugins/opencode-v1/index.js +41 -0
  97. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  98. package/dist/plugins/opencode-v1/runtime.js +152 -0
  99. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  100. package/dist/plugins/opencode-v1/transport.js +50 -0
  101. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  102. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  103. package/dist/ports.d.ts +33 -214
  104. package/dist/presentation.d.ts +5 -5
  105. package/dist/presentation.js +1 -0
  106. package/dist/runtime.js +2 -2
  107. package/dist/search.js +1 -1
  108. package/dist/session-lifecycle.d.ts +8 -7
  109. package/dist/session-lifecycle.js +40 -42
  110. package/dist/sqlite-framing.d.ts +8 -0
  111. package/dist/sqlite-framing.js +31 -0
  112. package/dist/sqlite-square.d.ts +16 -26
  113. package/dist/sqlite-square.js +199 -262
  114. package/dist/sqlite-values.d.ts +21 -0
  115. package/dist/sqlite-values.js +78 -0
  116. package/dist/square-api.d.ts +1 -1
  117. package/dist/square-api.js +5 -8
  118. package/dist/square-core.d.ts +0 -1
  119. package/dist/square-domain.d.ts +12 -10
  120. package/dist/square-domain.js +105 -223
  121. package/dist/square-facade.d.ts +25 -76
  122. package/dist/square-location.d.ts +6 -1
  123. package/dist/square-location.js +1 -1
  124. package/dist/square-post.d.ts +53 -0
  125. package/dist/square-post.js +4 -0
  126. package/dist/square-posting.d.ts +13 -0
  127. package/dist/square-posting.js +174 -0
  128. package/dist/square-producer-sqlite.d.ts +11 -0
  129. package/dist/square-producer-sqlite.js +210 -0
  130. package/dist/square-receive.d.ts +2 -3
  131. package/dist/square-sqlite.d.ts +33 -42
  132. package/dist/square-sqlite.js +236 -768
  133. package/dist/square-tools.d.ts +8 -0
  134. package/dist/square-tools.js +39 -0
  135. package/dist/square-values.d.ts +27 -0
  136. package/dist/square-values.js +90 -0
  137. package/dist/views.d.ts +6 -6
  138. package/dist/views.js +14 -9
  139. package/dist/wake-sink.d.ts +4 -11
  140. package/dist/wake-sink.js +8 -32
  141. package/dist/wake-transports.d.ts +4 -0
  142. package/dist/wake-transports.js +11 -0
  143. package/examples/symmetric-domains.mjs +29 -249
  144. package/guides/change-waits.md +13 -53
  145. package/guides/claude-native.md +19 -172
  146. package/guides/codex-delivery.md +55 -78
  147. package/guides/delivery-primitives.md +48 -207
  148. package/guides/operation-cancellation.md +2 -5
  149. package/guides/participant-ownership.md +25 -89
  150. package/guides/pi-native-delivery.md +19 -56
  151. package/guides/pi-tools.md +6 -8
  152. package/guides/row-runtime-delivery-evidence.md +2 -0
  153. package/guides/row-storage-design.md +19 -44
  154. package/guides/square-api.md +113 -538
  155. package/package.json +8 -8
  156. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  157. package/plugins/claude/hooks/hooks.json +1 -1
  158. package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
  159. package/plugins/claude/types.d.ts +1 -1
  160. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  161. package/plugins/codex/hooks/hooks.json +11 -23
  162. package/plugins/opencode/index.ts +91 -23
  163. package/plugins/opencode-shared/delivery.ts +91 -0
  164. package/plugins/opencode-v1/index.ts +36 -0
  165. package/plugins/opencode-v1/runtime.ts +134 -0
  166. package/plugins/opencode-v1/transport.ts +41 -0
  167. package/plugins/paseo/OVERVIEW.md +5 -5
  168. package/plugins/paseo/README.md +7 -6
  169. package/plugins/paseo/client/markdown.tsx +5 -5
  170. package/plugins/paseo/client/square-message.tsx +28 -24
  171. package/plugins/paseo/client/transform.ts +8 -5
  172. package/plugins/paseo/shared/card-presentation.ts +16 -0
  173. package/plugins/paseo/shared/message.ts +6 -11
  174. package/plugins/pi/square-pi.js +167 -451
  175. package/dist/channel.d.ts +0 -74
  176. package/dist/channel.js +0 -92
  177. package/dist/claude-delivery-status.d.ts +0 -13
  178. package/dist/claude-delivery-status.js +0 -46
  179. package/dist/cmd/notify-once.d.ts +0 -2
  180. package/dist/cmd/notify-once.js +0 -29
  181. package/dist/delivery-attempts.d.ts +0 -52
  182. package/dist/delivery-attempts.js +0 -73
  183. package/dist/delivery-effects.d.ts +0 -139
  184. package/dist/delivery-effects.js +0 -163
  185. package/dist/delivery-health.d.ts +0 -51
  186. package/dist/delivery-health.js +0 -173
  187. package/dist/delivery-operations.d.ts +0 -9
  188. package/dist/delivery-operations.js +0 -168
  189. package/dist/delivery-primitives.d.ts +0 -5
  190. package/dist/delivery-primitives.js +0 -3
  191. package/dist/delivery.d.ts +0 -45
  192. package/dist/delivery.js +0 -106
  193. package/dist/plugins/opencode/delivery.d.ts +0 -19
  194. package/dist/plugins/opencode/delivery.js +0 -238
  195. package/dist/presentation-operations.d.ts +0 -18
  196. package/dist/presentation-operations.js +0 -86
  197. package/dist/square-index.d.ts +0 -16
  198. package/dist/square-index.js +0 -106
  199. package/dist/square-legacy-framing.d.ts +0 -11
  200. package/dist/square-legacy-framing.js +0 -64
  201. package/dist/square-migration-host-ledger.d.ts +0 -26
  202. package/dist/square-migration-host-ledger.js +0 -169
  203. package/dist/square-migration.d.ts +0 -30
  204. package/dist/square-migration.js +0 -599
  205. package/guides/migration.md +0 -156
  206. package/plugins/opencode/delivery.ts +0 -211
@@ -1,54 +1,34 @@
1
- import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
1
+ import { formatActivityId } from './square-core.js';
2
2
  import { assertCurrentSeat } from './square-domain.js';
3
3
  import { canonicalPath } from './canonical-path.js';
4
- import { activityQueryFilter } from './activity-query.js';
5
- import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
6
4
  import { squareLocation } from './square-location.js';
7
5
  import crypto from 'node:crypto';
8
6
  import { setTimeout as sleep } from 'node:timers/promises';
9
- import { activityMetadataHasContent, InternalSquareError, SquareError, isSquareError, nameKey, sameName, validateActivityMetadata, validateName } from './model.js';
7
+ import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.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';
10
11
  import { attentionChange, coreDone, coreListening, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
11
12
  import { WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
12
13
  import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
13
14
  import { expressionContext } from './activity-feed.js';
14
15
  import { expressHint } from './hints.js';
15
- import { ATTENTION_BODY_MAX } from './attention-presentation.js';
16
- import { asSeatRef, participantAuthority, requireOwner, requireJoinName } from './seats.js';
17
- import { recoveryObservation, entryPresentation, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, toPerceivedActivity, publicActivities, selectHistory, NON_READ, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
16
+ import { asSeatRef, participantAuthority, requireJoinName, requireOwner } from './seats.js';
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 { rememberSquarePath, squareIndexPath } from './square-index.js';
20
- import { deliverPending } from './delivery-operations.js';
21
19
  import { participantIdentity } from './participant-identity.js';
22
20
  import { joinRecoveryCommand } from './presentation.js';
23
21
  import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
24
22
  import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuild } from './artifact.js';
25
- import { migrateSquareFile } from './square-migration.js';
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';
27
+ export { squareClock } from './operation-control.js';
29
28
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
30
29
  export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
31
30
  export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
32
31
  export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
33
- function checkControl(control) {
34
- if (control?.signal?.aborted)
35
- throw control.signal.reason ?? new Error('Square operation was cancelled');
36
- }
37
- export function squareClock(env, clock) {
38
- if (clock !== undefined)
39
- return clock;
40
- if (env.SQUARE_NOW_MS === undefined)
41
- return Date.now;
42
- const value = Number.parseInt(env.SQUARE_NOW_MS, 10);
43
- if (!Number.isFinite(value))
44
- throw new SquareError('invalid_args', 'Invalid SQUARE_NOW_MS: expected integer milliseconds.');
45
- return () => value;
46
- }
47
- function failure(error, detail) {
48
- const normalized = error instanceof Error ? error : new Error(String(error), { cause: error });
49
- Object.assign(normalized, { operation: detail });
50
- return normalized;
51
- }
52
32
  function rejectUnsentActivity(operation) {
53
33
  try {
54
34
  return operation();
@@ -59,28 +39,25 @@ function rejectUnsentActivity(operation) {
59
39
  throw error;
60
40
  }
61
41
  }
62
- /** A generic managed address must not displace the receiver's native registration on reconnect. */
63
- function canRefreshWakeAddress(current, proposed) {
64
- return proposed.kind !== 'paseo' || current === undefined || current.kind === 'paseo';
65
- }
66
42
  /** One concrete local resource, including its entry-resolved dependencies and lifetime. */
67
43
  export class SqliteSquare {
68
44
  location;
69
45
  artifact;
70
46
  clock;
71
- wakeTransport;
72
- indexPath;
47
+ env;
73
48
  closesArtifact;
74
49
  deliveryLocation;
75
- constructor(location, artifact, clock, wakeTransport, indexPath, closesArtifact = true, deliveryLocation = location) {
50
+ constructor(location, artifact, clock, env, closesArtifact = true, deliveryLocation = location) {
76
51
  this.location = location;
77
52
  this.artifact = artifact;
78
53
  this.clock = clock;
79
- this.wakeTransport = wakeTransport;
80
- this.indexPath = indexPath;
54
+ this.env = env;
81
55
  this.closesArtifact = closesArtifact;
82
56
  this.deliveryLocation = deliveryLocation;
83
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); }
84
61
  static async at(input, control) {
85
62
  const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
86
63
  const clock = squareClock(env, input.clock);
@@ -89,7 +66,7 @@ export class SqliteSquare {
89
66
  const artifact = borrowed ?? createArtifactPort(location, undefined, control?.signal);
90
67
  try {
91
68
  await artifact.read(facts => facts.header(), control?.signal);
92
- 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));
93
70
  }
94
71
  catch (error) {
95
72
  if (borrowed === undefined)
@@ -109,12 +86,64 @@ export class SqliteSquare {
109
86
  }
110
87
  static inMemory(input) {
111
88
  const env = { ...(input.env ?? process.env) };
112
- 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);
113
90
  }
114
- /** Discovery is best effort and never grants write authority. */
115
- async publishSeat(owner) {
116
- if (this.indexPath !== undefined)
117
- await rememberSquarePath(this.location, owner, this.indexPath).catch(() => undefined);
91
+ /** Join establishes membership and local producer custody in one transaction. */
92
+ async joinSeat(name, options, control) {
93
+ checkControl(control);
94
+ const owner = requireOwner(options?.owner), now = this.clock();
95
+ const initial = !options.takeover ? undefined : (await this.artifact.read(facts => facts.participant(name)?.joined ? facts.seat(name) : undefined, control?.signal)).value;
96
+ const authority = initial === undefined ? { kind: 'join', participant: name, owner }
97
+ : { kind: 'takeover', participant: initial.participant, owner, expectedGeneration: initial.generation };
98
+ const committed = (await this.artifact.transactAs(authority, facts => {
99
+ const participant = facts.participant(name)?.name ?? name;
100
+ const current = facts.participant(participant), currentSeat = facts.seat(participant);
101
+ let kind = 'joined', activity = null;
102
+ if (initial !== undefined) {
103
+ if (!current?.joined)
104
+ throw new SquareError('already_done', `${participantIdentity(participant)} is no longer standing here`);
105
+ facts.land(coreDone(facts, participant, '', now));
106
+ kind = 'taken-over';
107
+ }
108
+ else if (current?.joined) {
109
+ if (currentSeat === undefined || currentSeat.owner !== owner)
110
+ throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another owner; take it over explicitly`);
111
+ if (currentSeat.attachment !== 'attached')
112
+ throw new SquareError('already_joined', `${participantIdentity(participant)} is away; resume the host session first`);
113
+ kind = 'reconnected';
114
+ }
115
+ if (kind !== 'reconnected') {
116
+ const joinAct = decideJoin(facts, participant, now);
117
+ if (joinAct === undefined)
118
+ throw new SquareError('already_joined', `${participantIdentity(participant)} could not be joined`);
119
+ activity = toPublicActivity(facts.land(joinAct));
120
+ }
121
+ const seat = facts.seat(participant);
122
+ if (seat === undefined)
123
+ throw new SquareError('invalid_args', 'The join activity did not establish participant custody.');
124
+ return { seat, activity, kind,
125
+ ...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
126
+ deliveryAvailable: false } }) };
127
+ }, control?.signal)).value;
128
+ return committed;
129
+ }
130
+ async requiredSeat(name, { owner }) {
131
+ validateName(name);
132
+ requireOwner(owner);
133
+ return (await this.artifact.read(facts => {
134
+ const participant = facts.participant(name), seat = facts.seat(name);
135
+ if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
136
+ const reason = participant === undefined ? 'has never stepped into this square'
137
+ : !participant.joined ? 'needs to join first'
138
+ : seat !== undefined && seat.owner !== owner ? 'is not held by this owner; join before writing'
139
+ : seat?.attachment === 'detached' ? 'is away; resume the host session first'
140
+ : 'needs a current participant seat; join first';
141
+ throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
142
+ reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
143
+ });
144
+ }
145
+ return seat;
146
+ })).value;
118
147
  }
119
148
  observation(standingCount, participant, now = this.clock()) {
120
149
  return { location: this.location, standingCount, now,
@@ -123,6 +152,28 @@ export class SqliteSquare {
123
152
  async contextObservation(participant, control) {
124
153
  return (await this.artifact.read(facts => this.observation(inSquareCount(facts), participant), control?.signal)).value;
125
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
+ }
126
177
  handle(seat) { return new SqliteSquare.ParticipantHandle(seat.participant, this, seat); }
127
178
  // The nested handle uses its resource's private dependencies without copying
128
179
  // them into a second context.
@@ -146,12 +197,14 @@ export class SqliteSquare {
146
197
  let landedId;
147
198
  try {
148
199
  checkControl(control);
200
+ await square.reconcile();
149
201
  name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
150
202
  const seat = this.seat;
151
203
  const context = !options.observe ? undefined : (await square.artifact.read(facts => expressionContext(facts, name), control?.signal)).value;
152
204
  while (true) {
153
205
  checkControl(control);
154
206
  try {
207
+ await square.reconcile();
155
208
  effect = 'unknown';
156
209
  const now = square.clock();
157
210
  const reply = rejectUnsentActivity(() => {
@@ -179,15 +232,15 @@ export class SqliteSquare {
179
232
  throw new SquareError('bell_quota', `${participantIdentity(name)} cannot ring the bell yet`, { retryAfterMs: Math.max(1, proposed.nextAt - now) });
180
233
  return proposed;
181
234
  });
182
- return { stored: facts.land(decision.act), standingCount: inSquareCount(facts),
235
+ const stored = facts.land(decision.act);
236
+ return { stored, standingCount: inSquareCount(facts),
183
237
  ownActivityCount: facts.participant(name).sayCount, hardCap: facts.header().hardCap,
184
238
  ...(decision.unmatchedMentions === undefined ? {} : { unmatchedMentions: decision.unmatchedMentions }),
185
239
  ...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) };
186
240
  }, control?.signal)).value;
187
241
  landedId = formatActivityId(committed.stored.index);
188
242
  effect = 'landed';
189
- if (square.wakeTransport !== undefined)
190
- await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.deliveryLocation, activity: committed.stored.index, now }).catch(() => undefined);
243
+ await square.flush();
191
244
  const { stored, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
192
245
  const hint = expressHint(ownActivityCount);
193
246
  return { activity: toPublicActivity(stored), ...metadata,
@@ -213,10 +266,11 @@ export class SqliteSquare {
213
266
  }
214
267
  async listener(verb, target, options, control) {
215
268
  const square = this.owner;
216
- const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
217
269
  checkControl(control);
270
+ await square.reconcile();
271
+ const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
218
272
  const seat = this.seat, now = square.clock();
219
- return (await square.artifact.transactAs(participantAuthority(seat), facts => {
273
+ const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
220
274
  const act = attentionChange(facts, verb, name, target, now);
221
275
  const activity = act === undefined ? null : toPublicActivity(facts.land(act));
222
276
  return { activity, ...(!options.observe ? {} : { observation: {
@@ -224,6 +278,8 @@ export class SqliteSquare {
224
278
  target: activity?.target ?? target,
225
279
  } }) };
226
280
  }, control?.signal)).value;
281
+ await square.flush();
282
+ return result;
227
283
  }
228
284
  listen(target, options = {}, control) { return this.listener('listen', target, options, control); }
229
285
  ignore(target, options = {}, control) { return this.listener('ignore', target, options, control); }
@@ -237,6 +293,7 @@ export class SqliteSquare {
237
293
  }
238
294
  /** Receipt selection rechecks the lease inside the writer, not at its last heartbeat. */
239
295
  async catchPage(name, seat, options, control, leaseId) {
296
+ await this.owner.reconcile();
240
297
  return (await this.owner.artifact.transactAs(participantAuthority(seat), facts => {
241
298
  if (leaseId !== undefined && facts.lease(name)?.leaseId !== leaseId)
242
299
  return null;
@@ -255,27 +312,35 @@ export class SqliteSquare {
255
312
  }, square.clock()))).value;
256
313
  if (reason === 'drained')
257
314
  reason = view.terminalStatus ?? reason;
315
+ await square.flush();
258
316
  if (leaseId !== undefined) {
259
317
  await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
260
318
  leaseId = undefined;
261
319
  }
262
- let presence;
320
+ let presence, deliveryAvailable = false;
263
321
  if (options.observe) {
264
322
  try {
265
- 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;
266
330
  }
267
331
  catch { /* Optional presence cannot erase the completed selection or receipts. */ }
268
332
  }
269
333
  return { activities: view.activities, remaining: view.remaining, reason,
270
334
  ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
271
335
  ...square.observation(view.standingCount, view.participant, view.now), participant: view.participant,
272
- deliveryAvailable: view.deliveryAvailable,
336
+ deliveryAvailable,
273
337
  ownActivityCount: view.ownActivityCount, hardCap: view.hardCap,
274
338
  ...(presence === undefined ? { enrichmentUnavailable: true } : { presence }),
275
339
  } }) };
276
340
  };
277
341
  try {
278
342
  checkControl(control);
343
+ await square.reconcile();
279
344
  validateName(name);
280
345
  if (!Number.isFinite(idle) || idle < 0)
281
346
  throw new SquareError('invalid_args', 'Catch idle duration must be a non-negative number');
@@ -350,219 +415,107 @@ export class SqliteSquare {
350
415
  const limit = options.limit === undefined ? 100 : options.limit;
351
416
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
352
417
  throw new SquareError('invalid_args', 'Pending limit must be a positive integer no greater than 100.');
353
- const { actors, ...query } = activityQueryFilter(options), square = this.owner;
354
- return (await square.artifact.read(facts => {
418
+ // Pending follows the shared archive cursor: act/N with before/after.
419
+ const shared = activityQueryFilter(options);
420
+ const filter = { kinds: ['say'], unseenOnly: true,
421
+ ...(shared.indexes === undefined ? {} : { indexes: shared.indexes }),
422
+ ...(shared.actors === undefined ? {} : { senders: shared.actors }),
423
+ ...(shared.metadataMatch === undefined ? {} : { metadataMatch: shared.metadataMatch }),
424
+ ...(shared.search === undefined ? {} : { search: shared.search }),
425
+ ...(shared.afterIndex === undefined ? {} : { afterIndex: shared.afterIndex }),
426
+ ...(shared.beforeIndex === undefined ? {} : { beforeIndex: shared.beforeIndex }) };
427
+ return (await this.owner.artifact.read(facts => {
355
428
  assertCurrentSeat(facts, asSeatRef(this.seat));
356
- const filter = { ...query, senders: actors, unseenOnly: true };
357
429
  const heads = facts.directed(this.name, filter, { edge: 'oldest', limit });
358
- const activities = facts.activities(heads.items.map(head => head.index))
359
- .map(activity => toPerceivedActivity(facts, activity, this.name));
360
- return { activities, remaining: facts.directedCount(this.name, filter) - activities.length };
430
+ return { activities: facts.activities(heads.items.map(head => head.index)).map(activity => toPerceivedActivity(facts, activity, this.name)),
431
+ remaining: facts.directedCount(this.name, filter) - heads.items.length };
361
432
  }, control?.signal)).value;
362
433
  }
363
- async ack(ids, control) {
434
+ ack(input, control) {
435
+ const square = this.owner;
364
436
  checkControl(control);
365
- if (!Array.isArray(ids))
366
- throw new SquareError('invalid_args', 'Ack requires an array of activity IDs.');
367
- const indexes = [...new Set(Array.from(ids, parseRequiredActivityId))], square = this.owner;
368
- return (await square.artifact.transactAs(participantAuthority(this.seat), facts => {
369
- for (const index of indexes) {
370
- if (facts.directedAt(this.name, index) === undefined)
371
- throw new SquareError('invalid_args', `${formatActivityId(index)} is not addressed to you.`, {
372
- reason: 'not_addressed', activityId: formatActivityId(index),
373
- });
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
+ }
445
+ const indexes = [];
446
+ for (const id of input) {
447
+ const activity = facts.activity(parseRequiredActivityId(id));
448
+ if (activity?.kind !== 'say')
449
+ throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
450
+ const head = facts.directed(this.name, { indexes: [activity.index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items[0];
451
+ if (head === undefined)
452
+ throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
453
+ indexes.push(activity.index);
374
454
  }
375
455
  return { consumed: facts.markSeen(this.name, indexes, square.clock()) };
376
- }, control?.signal)).value;
377
- }
378
- async deferDelivery(input, control) {
379
- checkControl(control);
380
- const match = validateMetadataMatch(input?.match), duration = validateDeferralDuration(input?.durationMs);
381
- const square = this.owner, seat = this.seat;
382
- return (await square.artifact.transactAs(participantAuthority(seat), facts => {
383
- const now = square.clock(), expiresAt = now + duration, id = crypto.randomUUID();
384
- const result = { id, expiresAt: new Date(expiresAt).toISOString() };
385
- facts.putDeliveryDeferral(asSeatRef(seat), id, match, expiresAt, now);
386
- return result;
387
- }, control?.signal)).value;
388
- }
389
- async renewDeliveryDeferral(id, durationMs, control) {
390
- checkControl(control);
391
- validateDeferralId(id);
392
- const duration = validateDeferralDuration(durationMs), square = this.owner;
393
- const seat = this.seat;
394
- return (await square.artifact.transactAs(participantAuthority(seat), facts => {
395
- const now = square.clock(), expiresAt = now + duration;
396
- const result = { id, expiresAt: new Date(expiresAt).toISOString() };
397
- return facts.renewDeliveryDeferral(asSeatRef(seat), id, expiresAt, now) ? result : null;
398
- }, control?.signal)).value;
399
- }
400
- async releaseDeliveryDeferral(id, control) {
401
- checkControl(control);
402
- validateDeferralId(id);
403
- const square = this.owner, seat = this.seat;
404
- return (await square.artifact.transactAs(participantAuthority(seat), facts => facts.releaseDeliveryDeferral(asSeatRef(seat), id, square.clock()), control?.signal)).value;
456
+ }, control?.signal).then(result => result.value);
405
457
  }
406
458
  async done(body, options = {}, control) {
407
459
  validateDoneBody(body);
460
+ checkControl(control);
408
461
  const square = this.owner;
462
+ await square.reconcile();
409
463
  const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
410
464
  const seat = this.seat, now = square.clock();
411
- return (await square.artifact.transactAs(participantAuthority(seat), facts => {
465
+ const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
412
466
  const stored = facts.land(coreDone(facts, name, body ?? '', now));
413
467
  return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name, now),
414
468
  }) };
415
469
  }, control?.signal)).value;
470
+ await square.flush();
471
+ return result;
416
472
  }
417
473
  };
418
- /** Direct messages use explicit recipients, not participant expression admission or body attention. */
419
474
  async send(input, control) {
420
475
  checkControl(control);
421
- const { from, to, body, truncate = ATTENTION_BODY_MAX } = input;
422
- if (typeof from !== 'string')
423
- throw new SquareError('invalid_args', 'Channel sender must be a name.');
424
- validateName(from);
425
- const targets = typeof to === 'string' ? [to] : to;
426
- if (!Array.isArray(targets) || targets.length < 1 || targets.length > MAX_IDENTITY_SET_SIZE)
427
- throw new SquareError('invalid_args', `Channel needs between 1 and ${MAX_IDENTITY_SET_SIZE} recipients.`);
428
- const unique = [];
429
- for (const target of targets) {
430
- if (typeof target !== 'string')
431
- throw new SquareError('invalid_args', 'Channel recipients must be names.');
432
- validateName(target);
433
- if (!unique.some(name => sameName(name, target)))
434
- unique.push(target);
435
- }
436
- if (typeof body !== 'string' || body.trim() === '')
437
- throw new SquareError('invalid_args', 'Channel body must be non-empty.');
438
- if (truncate !== false && (!Number.isSafeInteger(truncate) || truncate < 1))
439
- throw new SquareError('invalid_args', 'Channel truncate must be false or a positive integer.');
440
- const fullBody = body.replace(/\r\n/g, '\n');
441
- const preview = truncate !== false && fullBody.length > truncate
442
- ? `${fullBody.slice(0, truncate).trimEnd()}… (truncated ${fullBody.length - truncate} chars)`
443
- : undefined;
444
- const delivery = preview === undefined ? { mode: 'direct' } : { mode: 'direct', preview };
445
- const metadata = validateActivityMetadata(input.metadata);
446
- const { stored, resolvedTo } = (await this.artifact.transactAs({ kind: 'send', actor: from }, facts => {
447
- const standing = facts.roster().filter(p => p.joined), resolvedTo = [], missing = [];
448
- for (const target of unique) {
449
- const recipient = standing.find(p => sameName(p.name, target));
450
- if (recipient === undefined)
451
- missing.push(target);
452
- else
453
- resolvedTo.push(recipient.name);
454
- }
455
- if (missing.length > 0)
456
- throw new SquareError('invalid_args', `Channel recipients are not standing: ${missing.join(', ')}`, {
457
- reason: 'not_standing', targets: missing, activityUnsent: true,
458
- });
459
- return { stored: facts.land({ kind: 'say', actor: from, at: this.clock(), body: fullBody,
460
- ...(activityMetadataHasContent(metadata) ? { metadata } : {}), delivery, mentions: resolvedTo }), resolvedTo };
461
- }, control?.signal)).value;
462
- if (this.wakeTransport !== undefined)
463
- await deliverPending({ artifact: this.artifact, transport: this.wakeTransport, location: this.deliveryLocation,
464
- activity: stored.index, now: stored.at }).catch(() => undefined);
465
- return { id: formatActivityId(stored.index), at: stored.at, from: stored.actor, to: resolvedTo, body: preview ?? fullBody,
466
- ...(stored.kind !== 'say' || stored.metadata === undefined ? {} : { metadata: stored.metadata }) };
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;
467
480
  }
468
- async participant(name, { owner }) {
469
- validateName(name);
470
- requireOwner(owner);
471
- return (await this.artifact.read(facts => {
472
- const participant = facts.participant(name), seat = facts.seat(name);
473
- if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
474
- const reason = participant === undefined ? 'has never stepped into this square'
475
- : !participant.joined ? 'needs a current attached seat; join first'
476
- : seat !== undefined && seat.owner !== owner ? 'is not held by this owner; join before writing'
477
- : seat?.attachment === 'detached' ? 'is away — join again before writing'
478
- : 'needs a current attached seat; join first';
479
- throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
480
- reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
481
- });
482
- }
483
- return this.handle(seat);
484
- })).value;
481
+ async participant(name, options) {
482
+ return this.handle(await this.requiredSeat(name, options));
485
483
  }
486
484
  async join(requested, options, control) {
487
485
  let name = requested;
488
486
  try {
489
487
  checkControl(control);
490
488
  name = requireJoinName(requested);
491
- const last = options?.last ?? HISTORY_DEFAULT_LIMIT;
489
+ const last = options.last ?? HISTORY_DEFAULT_LIMIT;
492
490
  if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
493
491
  throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
494
- const owner = requireOwner(options?.owner), wakeAddress = options.wakeAddress, now = this.clock();
495
- const initial = !options.takeover ? undefined : (await this.artifact.read(facts => facts.participant(name)?.joined ? facts.seat(name) : undefined, control?.signal)).value;
496
- const authority = initial === undefined ? { kind: 'join', participant: name, owner, wakeAddress }
497
- : { kind: 'takeover', participant: initial.participant, owner, expectedGeneration: initial.generation, wakeAddress };
498
- const committed = (await this.artifact.transactAs(authority, facts => {
499
- const participant = facts.participant(name)?.name ?? name;
500
- const current = facts.participant(participant), currentSeat = facts.seat(participant);
501
- let kind = 'joined', activity = null;
502
- if (initial !== undefined) {
503
- if (!current?.joined)
504
- throw new SquareError('already_done', `${participantIdentity(participant)} is no longer standing here`);
505
- facts.land(coreDone(facts, participant, '', now));
506
- kind = 'taken-over';
507
- }
508
- else if (current?.joined) {
509
- if (currentSeat === undefined || currentSeat.owner !== owner)
510
- throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another owner; take it over explicitly`);
511
- if (currentSeat.attachment === 'detached')
512
- facts.reattachSeat(asSeatRef(currentSeat), wakeAddress);
513
- else if (wakeAddress !== undefined && canRefreshWakeAddress(currentSeat.wakeAddress, wakeAddress))
514
- facts.updateWakeAddress(asSeatRef(currentSeat), wakeAddress);
515
- kind = 'reconnected';
516
- }
517
- if (kind !== 'reconnected') {
518
- const joinAct = decideJoin(facts, participant, now);
519
- if (joinAct === undefined)
520
- throw new SquareError('already_joined', `${participantIdentity(participant)} could not be joined`);
521
- activity = toPublicActivity(facts.land(joinAct));
522
- }
523
- const seat = facts.seat(participant);
524
- if (seat === undefined)
525
- throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
526
- return { participant: this.handle(seat), activity, kind,
527
- ...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
528
- deliveryAvailable: seat.wakeAddress !== undefined } }) };
529
- }, control?.signal)).value;
530
- await this.publishSeat(owner);
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();
495
+ const committed = await this.joinSeat(name, options, control);
496
+ await this.flush();
497
+ const participant = this.handle(committed.seat);
498
+ const result = { participant, activity: committed.activity, kind: committed.kind,
499
+ ...(committed.observation === undefined ? {} : { observation: committed.observation }) };
531
500
  if (!options.observe)
532
- return committed;
533
- let entry;
501
+ return result;
502
+ let entry, deliveryAvailable = false;
534
503
  try {
535
- entry = (await this.artifact.read(facts => entryPresentation(facts, committed.participant.name, last), 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;
536
510
  }
537
511
  catch { /* Keep the committed join even when optional history is unavailable. */ }
538
- return { ...committed, observation: { ...committed.observation, ...(entry === undefined ? { enrichmentUnavailable: true } : { entry }) } };
512
+ return { ...result, observation: { ...committed.observation, deliveryAvailable,
513
+ ...(entry === undefined ? { enrichmentUnavailable: true } : { entry }) } };
539
514
  }
540
515
  catch (error) {
541
516
  throw failure(error, { operation: 'join', subject: 'caller', participant: name });
542
517
  }
543
518
  }
544
- /** Restore only a standing seat; a concurrent departure or takeover cannot be revived. */
545
- async reattach(name, { owner, wakeAddress }, control) {
546
- checkControl(control);
547
- validateName(name);
548
- requireOwner(owner);
549
- const captured = (await this.artifact.read(facts => facts.seat(name), control?.signal)).value;
550
- if (captured === undefined || captured.owner !== owner)
551
- return null;
552
- const seat = (await this.artifact.transactAs({ kind: 'join', participant: name, owner, wakeAddress }, facts => {
553
- const current = facts.seat(name);
554
- if (!facts.participant(name)?.joined || current === undefined || current.generation !== captured.generation || current.owner !== owner)
555
- return null;
556
- if (current.attachment === 'detached')
557
- facts.reattachSeat(asSeatRef(current), wakeAddress);
558
- else if (wakeAddress !== undefined && canRefreshWakeAddress(current.wakeAddress, wakeAddress))
559
- facts.updateWakeAddress(asSeatRef(current), wakeAddress);
560
- return facts.seat(name);
561
- }, control?.signal)).value;
562
- if (seat !== null)
563
- await this.publishSeat(owner);
564
- return seat === null ? null : this.handle(seat);
565
- }
566
519
  async history(query = {}, control) {
567
520
  if (query.limit !== undefined && query.limit !== null && (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > HISTORY_MAX_LIMIT))
568
521
  throw new SquareError('invalid_args', `History limit must be a positive integer no greater than ${HISTORY_MAX_LIMIT}`);
@@ -575,7 +528,7 @@ export class SqliteSquare {
575
528
  throw new SquareError('invalid_args', 'History context must be a non-negative safe integer');
576
529
  const limit = query.limit === undefined ? (query.at?.length ? null : HISTORY_DEFAULT_LIMIT) : query.limit;
577
530
  const contextual = !!query.at?.length || query.contextBefore !== undefined || query.contextAfter !== undefined;
578
- const kinds = contextual || query.since !== undefined ? undefined : NON_READ;
531
+ const kinds = contextual || query.since !== undefined ? [...NON_READ, 'read'] : NON_READ;
579
532
  return (await this.artifact.read(facts => {
580
533
  const selected = selectHistory(facts, query, limit, kinds, query.observe === true);
581
534
  if (contextual && limit === null && selected.totalMatches > HISTORY_MAX_LIMIT)
@@ -594,12 +547,22 @@ export class SqliteSquare {
594
547
  }, control?.signal)).value;
595
548
  }
596
549
  async status(options = {}, control) {
597
- 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);
598
554
  }
555
+ /** Local mode reads only World-known facts; it does not verify host attachment. */
599
556
  async participants(options = {}, control) {
600
- return (await this.artifact.read(facts => queryParticipants(facts, {
601
- location: this.location, now: this.clock(),
602
- }, 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);
603
566
  }
604
567
  async recovery(requested, control) {
605
568
  const facts = requested === undefined ? undefined : (await this.artifact.read(facts => recoveryObservation(facts, requested, this.clock()), control?.signal).catch(() => undefined))?.value;
@@ -612,39 +575,13 @@ export class SqliteSquare {
612
575
  }
613
576
  async seatsOf(owner) {
614
577
  requireOwner(owner);
615
- return (await this.artifact.read(facts => facts.seatsForOwner(owner).map(({ participant: name, generation, attachment }) => ({ name, generation, attachment })))).value;
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;
616
581
  }
617
582
  close() { return this.closesArtifact ? this.artifact.close() : Promise.resolve(); }
618
- async heldSeat(name, { owner, generation }) {
619
- requireOwner(owner);
620
- const seat = (await this.artifact.read(facts => facts.seat(name))).value;
621
- return seat?.generation === generation && seat.owner === owner ? seat : undefined;
622
- }
623
- async cancelDeliveries(name, identity) {
624
- const seat = await this.heldSeat(name, identity);
625
- return seat === undefined ? false : this.artifact.cancelDeliveries(asSeatRef(seat));
626
- }
627
- async detach(name, identity) {
628
- const captured = await this.heldSeat(name, identity);
629
- if (captured === undefined)
630
- return null;
631
- const seat = asSeatRef(captured);
632
- return (await this.artifact.transactAs({ kind: 'host-cleanup', seat }, facts => facts.detachSeat(seat) ? true : null)).value;
633
- }
634
- async endHeld(name, identity) {
635
- const captured = await this.heldSeat(name, identity);
636
- if (captured === undefined)
637
- return null;
638
- return (await this.artifact.transactAs({ kind: 'host-cleanup', seat: asSeatRef(captured) }, facts => {
639
- const current = facts.seat(name);
640
- if (current === undefined || current.generation !== captured.generation || current.owner !== captured.owner)
641
- return null;
642
- return { activity: toPublicActivity(facts.land(coreDone(facts, current.participant, '', this.clock()))) };
643
- })).value;
644
- }
645
583
  }
646
584
  export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(squareLocation(input)); }
647
- export function migrateSquare(input, control) { checkControl(control); const env = input.env ?? process.env; return migrateSquareFile(squareLocation(input), control?.signal, env); }
648
585
  export function discover(input, options = {}, control) {
649
586
  if (input.path !== undefined || (input.env ?? process.env).SQUARE_LOCATION)
650
587
  squareLocation(input);
@@ -654,10 +591,10 @@ export function discover(input, options = {}, control) {
654
591
  export async function inbox(input, owner, options = {}, control) {
655
592
  checkControl(control);
656
593
  requireOwner(owner);
657
- const env = { ...(input.env ?? process.env) }, limit = options.limit ?? 20;
594
+ const limit = options.limit ?? 20;
658
595
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
659
596
  throw new SquareError('invalid_args', 'Inbox limit must be between 1 and 100');
660
- const rows = await sessionInboxCounts({ owner, env }, control?.signal);
597
+ const rows = await sessionInboxCounts({ owner, cwd: input.cwd }, control?.signal);
661
598
  checkControl(control);
662
599
  const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
663
600
  rows.sort((a, b) => compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name));