@astrosheep/square 0.3.106 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +8 -12
  5. package/dist/artifact.js +66 -343
  6. package/dist/attention-presentation.d.ts +33 -7
  7. package/dist/attention-presentation.js +33 -69
  8. package/dist/boundary-presentation.d.ts +0 -2
  9. package/dist/boundary-presentation.js +17 -22
  10. package/dist/catch-decisions.d.ts +2 -2
  11. package/dist/catch-decisions.js +14 -4
  12. package/dist/catch-selection.d.ts +8 -0
  13. package/dist/catch-selection.js +5 -0
  14. package/dist/channel-artifact.d.ts +10 -0
  15. package/dist/channel-artifact.js +55 -0
  16. package/dist/channel-delivery.d.ts +11 -0
  17. package/dist/channel-delivery.js +51 -0
  18. package/dist/channel-domain.d.ts +3 -0
  19. package/dist/channel-domain.js +83 -0
  20. package/dist/channel-facade.d.ts +76 -0
  21. package/dist/channel-facade.js +1 -0
  22. package/dist/channel-internal.d.ts +126 -0
  23. package/dist/channel-internal.js +440 -0
  24. package/dist/channel-metadata.d.ts +11 -0
  25. package/dist/channel-metadata.js +1 -0
  26. package/dist/channel-ports.d.ts +241 -0
  27. package/dist/channel-ports.js +1 -0
  28. package/dist/channel-presentation.d.ts +17 -0
  29. package/dist/channel-presentation.js +22 -0
  30. package/dist/channel-sqlite-schema.d.ts +13 -0
  31. package/dist/channel-sqlite-schema.js +81 -0
  32. package/dist/channel-transport.d.ts +62 -0
  33. package/dist/channel-transport.js +1 -0
  34. package/dist/channel-values.d.ts +27 -0
  35. package/dist/channel-values.js +90 -0
  36. package/dist/channel.d.ts +31 -21
  37. package/dist/channel.js +42 -74
  38. package/dist/claude-delivery-status.js +1 -1
  39. package/dist/claude-delivery.d.ts +2 -5
  40. package/dist/claude-delivery.js +56 -103
  41. package/dist/claude-mod.js +4 -4
  42. package/dist/claude-transport.d.ts +5 -0
  43. package/dist/claude-transport.js +58 -0
  44. package/dist/cli/context.d.ts +2 -2
  45. package/dist/cli/context.js +0 -1
  46. package/dist/cli/maintenance-commands.d.ts +0 -4
  47. package/dist/cli/maintenance-commands.js +2 -29
  48. package/dist/cli/program.js +1 -1
  49. package/dist/cli/registry.js +1 -2
  50. package/dist/codex-address.d.ts +1 -1
  51. package/dist/codex-queue.d.ts +2 -2
  52. package/dist/codex-queue.js +1 -3
  53. package/dist/decisions.js +3 -3
  54. package/dist/delivery-deferral.d.ts +9 -18
  55. package/dist/delivery-deferral.js +4 -4
  56. package/dist/delivery-health.d.ts +4 -4
  57. package/dist/delivery-health.js +11 -11
  58. package/dist/delivery-operations.d.ts +15 -2
  59. package/dist/delivery-operations.js +60 -30
  60. package/dist/delivery.d.ts +44 -33
  61. package/dist/delivery.js +49 -75
  62. package/dist/help.js +1 -6
  63. package/dist/http-delivery.d.ts +2 -2
  64. package/dist/http-delivery.js +2 -4
  65. package/dist/inbox.d.ts +10 -18
  66. package/dist/inbox.js +44 -53
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/mcp.d.ts +2 -1
  70. package/dist/mcp.js +40 -70
  71. package/dist/message.d.ts +7 -0
  72. package/dist/message.js +23 -0
  73. package/dist/model.d.ts +41 -77
  74. package/dist/model.js +7 -139
  75. package/dist/neutral.d.ts +72 -0
  76. package/dist/neutral.js +27 -0
  77. package/dist/notifications.d.ts +2 -8
  78. package/dist/notifications.js +15 -50
  79. package/dist/operation-presentation.d.ts +1 -2
  80. package/dist/operation-presentation.js +1 -7
  81. package/dist/owner.d.ts +2 -2
  82. package/dist/owner.js +1 -1
  83. package/dist/paseo-delivery.d.ts +1 -1
  84. package/dist/paseo-delivery.js +2 -4
  85. package/dist/plugins/opencode/delivery.js +27 -42
  86. package/dist/ports.d.ts +46 -168
  87. package/dist/presentation-operations.d.ts +1 -14
  88. package/dist/presentation-operations.js +16 -16
  89. package/dist/presentation.d.ts +5 -5
  90. package/dist/presentation.js +1 -0
  91. package/dist/runtime.js +2 -2
  92. package/dist/search.js +1 -1
  93. package/dist/sqlite-channel.d.ts +15 -0
  94. package/dist/sqlite-channel.js +600 -0
  95. package/dist/sqlite-framing.d.ts +8 -0
  96. package/dist/sqlite-framing.js +31 -0
  97. package/dist/sqlite-square.d.ts +12 -13
  98. package/dist/sqlite-square.js +192 -248
  99. package/dist/sqlite-values.d.ts +21 -0
  100. package/dist/sqlite-values.js +78 -0
  101. package/dist/square-api.d.ts +1 -1
  102. package/dist/square-api.js +1 -1
  103. package/dist/square-core.d.ts +1 -1
  104. package/dist/square-domain.d.ts +21 -7
  105. package/dist/square-domain.js +93 -94
  106. package/dist/square-facade.d.ts +12 -66
  107. package/dist/square-index.d.ts +1 -1
  108. package/dist/square-index.js +1 -1
  109. package/dist/square-location.d.ts +6 -1
  110. package/dist/square-location.js +1 -1
  111. package/dist/square-receive.d.ts +2 -3
  112. package/dist/square-sqlite.d.ts +45 -44
  113. package/dist/square-sqlite.js +308 -764
  114. package/dist/square-tools.d.ts +8 -0
  115. package/dist/square-tools.js +39 -0
  116. package/dist/views.d.ts +6 -6
  117. package/dist/views.js +15 -9
  118. package/dist/wake-preview.d.ts +7 -0
  119. package/dist/wake-preview.js +15 -0
  120. package/dist/wake-sink.d.ts +2 -2
  121. package/dist/wake-sink.js +1 -1
  122. package/dist/wake-transports.d.ts +7 -0
  123. package/dist/wake-transports.js +42 -0
  124. package/guides/claude-native.md +13 -10
  125. package/guides/codex-delivery.md +3 -1
  126. package/guides/participant-ownership.md +1 -1
  127. package/guides/row-runtime-delivery-evidence.md +2 -0
  128. package/guides/row-storage-design.md +19 -44
  129. package/guides/square-api.md +137 -60
  130. package/package.json +1 -1
  131. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  132. package/plugins/claude/types.d.ts +1 -1
  133. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  134. package/plugins/opencode/delivery.ts +28 -41
  135. package/plugins/pi/square-pi.js +28 -51
  136. package/dist/square-legacy-framing.d.ts +0 -11
  137. package/dist/square-legacy-framing.js +0 -64
  138. package/dist/square-migration-host-ledger.d.ts +0 -26
  139. package/dist/square-migration-host-ledger.js +0 -169
  140. package/dist/square-migration.d.ts +0 -30
  141. package/dist/square-migration.js +0 -599
  142. package/guides/migration.md +0 -156
@@ -1,54 +1,36 @@
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 { Channel, ChannelEndpoint, checkControl, failure, squareClock } from './channel-internal.js';
9
+ import { channelArtifact } from './artifact.js';
10
+ import { deliverPending } from './delivery-operations.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';
19
+ import { squareIndexPath } from './square-index.js';
21
20
  import { participantIdentity } from './participant-identity.js';
22
21
  import { joinRecoveryCommand } from './presentation.js';
23
22
  import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
23
+ import { squareWakeProjection } from './wake-preview.js';
24
24
  import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuild } from './artifact.js';
25
- import { migrateSquareFile } from './square-migration.js';
25
+ import { activityQueryFilter } from './activity-query.js';
26
26
  import { discoverSquares } from './list.js';
27
27
  import { streamSquare } from './stream.js';
28
28
  export { createSquareResourceOwner } from './artifact.js';
29
+ export { squareClock } from './channel-internal.js';
29
30
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
30
31
  export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
31
32
  export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
32
33
  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
34
  function rejectUnsentActivity(operation) {
53
35
  try {
54
36
  return operation();
@@ -59,27 +41,25 @@ function rejectUnsentActivity(operation) {
59
41
  throw error;
60
42
  }
61
43
  }
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
44
  /** One concrete local resource, including its entry-resolved dependencies and lifetime. */
67
45
  export class SqliteSquare {
68
46
  location;
69
47
  artifact;
70
48
  clock;
71
49
  wakeTransport;
72
- indexPath;
73
- closesArtifact;
74
50
  deliveryLocation;
51
+ channel;
75
52
  constructor(location, artifact, clock, wakeTransport, indexPath, closesArtifact = true, deliveryLocation = location) {
76
53
  this.location = location;
77
54
  this.artifact = artifact;
78
55
  this.clock = clock;
79
56
  this.wakeTransport = wakeTransport;
80
- this.indexPath = indexPath;
81
- this.closesArtifact = closesArtifact;
82
57
  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 });
83
63
  }
84
64
  static async at(input, control) {
85
65
  const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
@@ -111,10 +91,115 @@ export class SqliteSquare {
111
91
  const env = { ...(input.env ?? process.env) };
112
92
  return new SqliteSquare(`memory://${crypto.randomUUID()}`, createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), input.wakeTransport || undefined);
113
93
  }
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);
94
+ /** Agora join composes membership and endpoint custody in one transaction. */
95
+ async joinSeat(name, options, control) {
96
+ 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();
101
+ 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 };
104
+ const committed = (await this.artifact.transactAs(authority, facts => {
105
+ const participant = facts.participant(name)?.name ?? name;
106
+ const current = facts.participant(participant), currentSeat = facts.seat(participant);
107
+ let kind = 'joined', activity = null;
108
+ if (initial !== undefined) {
109
+ if (!current?.joined)
110
+ throw new SquareError('already_done', `${participantIdentity(participant)} is no longer standing here`);
111
+ facts.land(coreDone(facts, participant, '', now));
112
+ kind = 'taken-over';
113
+ }
114
+ else if (current?.joined) {
115
+ if (currentSeat === undefined || currentSeat.owner !== owner)
116
+ 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);
121
+ kind = 'reconnected';
122
+ }
123
+ if (kind !== 'reconnected') {
124
+ const joinAct = decideJoin(facts, participant, now);
125
+ if (joinAct === undefined)
126
+ throw new SquareError('already_joined', `${participantIdentity(participant)} could not be joined`);
127
+ activity = toPublicActivity(facts.land(joinAct));
128
+ }
129
+ const seat = facts.seat(participant);
130
+ if (seat === undefined)
131
+ throw new SquareError('invalid_args', 'The join activity did not establish its artifact endpoint.');
132
+ return { seat, activity, kind,
133
+ ...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), seat.participant, now),
134
+ deliveryAvailable: seat.wakeAddress !== undefined } }) };
135
+ }, control?.signal)).value;
136
+ await this.channel.remember(owner);
137
+ return committed;
138
+ }
139
+ async requiredSeat(name, { owner }) {
140
+ validateName(name);
141
+ requireOwner(owner);
142
+ return (await this.artifact.read(facts => {
143
+ const participant = facts.participant(name), seat = facts.seat(name);
144
+ if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
145
+ const reason = participant === undefined ? 'has never stepped into this square'
146
+ : !participant.joined ? 'needs a current attached endpoint; join first'
147
+ : 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';
150
+ throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
151
+ reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
152
+ });
153
+ }
154
+ return seat;
155
+ })).value;
156
+ }
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;
118
203
  }
119
204
  observation(standingCount, participant, now = this.clock()) {
120
205
  return { location: this.location, standingCount, now,
@@ -130,12 +215,14 @@ export class SqliteSquare {
130
215
  name;
131
216
  owner;
132
217
  seat;
218
+ endpoint;
133
219
  constructor(name, owner, seat) {
134
220
  this.name = name;
135
221
  this.owner = owner;
136
222
  this.seat = seat;
223
+ this.endpoint = new ChannelEndpoint(owner.channel, seat);
137
224
  }
138
- get generation() { return this.seat.generation; }
225
+ get generation() { return this.endpoint.generation; }
139
226
  async express(bodyOrInput, optionsOrControl = {}, operationControl) {
140
227
  const square = this.owner;
141
228
  const input = typeof bodyOrInput === 'object' && bodyOrInput !== null ? bodyOrInput : undefined;
@@ -179,18 +266,22 @@ export class SqliteSquare {
179
266
  throw new SquareError('bell_quota', `${participantIdentity(name)} cannot ring the bell yet`, { retryAfterMs: Math.max(1, proposed.nextAt - now) });
180
267
  return proposed;
181
268
  });
182
- return { stored: facts.land(decision.act), standingCount: inSquareCount(facts),
269
+ 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),
183
275
  ownActivityCount: facts.participant(name).sayCount, hardCap: facts.header().hardCap,
184
276
  ...(decision.unmatchedMentions === undefined ? {} : { unmatchedMentions: decision.unmatchedMentions }),
185
277
  ...(decision.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: decision.replyAuthorNotHere }) };
186
278
  }, control?.signal)).value;
187
279
  landedId = formatActivityId(committed.stored.index);
188
280
  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);
191
- const { stored, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
281
+ await square.channel.deliver(committed.stored.kind === 'say' ? committed.stored.messageIndex : undefined);
282
+ const { stored, activity, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
192
283
  const hint = expressHint(ownActivityCount);
193
- return { activity: toPublicActivity(stored), ...metadata,
284
+ return { activity: toPublicActivity(activity), ...metadata,
194
285
  ...(hint === undefined ? {} : { hint }), ...(context === undefined ? {} : { observation: {
195
286
  ...square.observation(standingCount, name, now), ownActivityCount, hardCap, context, participant: name,
196
287
  } }) };
@@ -350,58 +441,46 @@ export class SqliteSquare {
350
441
  const limit = options.limit === undefined ? 100 : options.limit;
351
442
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
352
443
  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 => {
444
+ // Pending follows the shared archive cursor: act/N with before/after, never a Channel msg/N cursor.
445
+ const shared = activityQueryFilter(options);
446
+ const filter = { kinds: ['say'], unseenOnly: true,
447
+ ...(shared.indexes === undefined ? {} : { indexes: shared.indexes }),
448
+ ...(shared.actors === undefined ? {} : { senders: shared.actors }),
449
+ ...(shared.metadataMatch === undefined ? {} : { metadataMatch: shared.metadataMatch }),
450
+ ...(shared.search === undefined ? {} : { search: shared.search }),
451
+ ...(shared.afterIndex === undefined ? {} : { afterIndex: shared.afterIndex }),
452
+ ...(shared.beforeIndex === undefined ? {} : { beforeIndex: shared.beforeIndex }) };
453
+ return (await this.owner.artifact.read(facts => {
355
454
  assertCurrentSeat(facts, asSeatRef(this.seat));
356
- const filter = { ...query, senders: actors, unseenOnly: true };
357
455
  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 };
456
+ return { activities: facts.activities(heads.items.map(head => head.index)).map(activity => toPerceivedActivity(facts, activity, this.name)),
457
+ remaining: facts.directedCount(this.name, filter) - heads.items.length };
361
458
  }, control?.signal)).value;
362
459
  }
363
- async ack(ids, control) {
364
- 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
- });
460
+ ack(ids, control) {
461
+ const square = this.owner;
462
+ return square.artifact.transactAs(participantAuthority(this.seat), facts => {
463
+ const indexes = [];
464
+ for (const id of ids) {
465
+ const activity = facts.activity(parseRequiredActivityId(id));
466
+ if (activity?.kind !== 'say' || activity.messageIndex === undefined)
467
+ throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
468
+ const head = facts.directed(this.name, { indexes: [activity.index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items[0];
469
+ if (head === undefined)
470
+ throw new SquareError('invalid_args', `${id} is not addressed to you.`, { reason: 'not_addressed', activityId: id });
471
+ indexes.push(activity.messageIndex);
374
472
  }
375
- return { consumed: facts.markSeen(this.name, indexes, square.clock()) };
376
- }, control?.signal)).value;
473
+ return { consumed: facts.channel.markSeen(this.name, indexes, square.clock()) };
474
+ }, control?.signal).then(result => result.value);
377
475
  }
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;
476
+ deferDelivery(input, control) {
477
+ return this.endpoint.deferDelivery(input, control);
388
478
  }
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;
479
+ renewDeliveryDeferral(id, durationMs, control) {
480
+ return this.endpoint.renewDeliveryDeferral(id, durationMs, control);
399
481
  }
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;
482
+ releaseDeliveryDeferral(id, control) {
483
+ return this.endpoint.releaseDeliveryDeferral(id, control);
405
484
  }
406
485
  async done(body, options = {}, control) {
407
486
  validateDoneBody(body);
@@ -415,152 +494,37 @@ export class SqliteSquare {
415
494
  }, control?.signal)).value;
416
495
  }
417
496
  };
418
- /** Direct messages use explicit recipients, not participant expression admission or body attention. */
419
- async send(input, control) {
420
- 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 }) };
497
+ send(input, control) {
498
+ return this.channel.send(input, control);
467
499
  }
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;
500
+ async participant(name, options) {
501
+ return this.handle(await this.requiredSeat(name, options));
485
502
  }
486
503
  async join(requested, options, control) {
487
504
  let name = requested;
488
505
  try {
489
506
  checkControl(control);
490
507
  name = requireJoinName(requested);
491
- const last = options?.last ?? HISTORY_DEFAULT_LIMIT;
492
- if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
493
- 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);
508
+ const committed = await this.joinSeat(name, options, control);
509
+ const participant = this.handle(committed.seat);
510
+ const result = { participant, activity: committed.activity, kind: committed.kind,
511
+ ...(committed.observation === undefined ? {} : { observation: committed.observation }) };
531
512
  if (!options.observe)
532
- return committed;
513
+ return result;
533
514
  let entry;
534
515
  try {
535
- entry = (await this.artifact.read(facts => entryPresentation(facts, committed.participant.name, last), control?.signal)).value;
516
+ entry = (await this.artifact.read(facts => entryPresentation(facts, participant.name, options.last ?? HISTORY_DEFAULT_LIMIT), control?.signal)).value;
536
517
  }
537
518
  catch { /* Keep the committed join even when optional history is unavailable. */ }
538
- return { ...committed, observation: { ...committed.observation, ...(entry === undefined ? { enrichmentUnavailable: true } : { entry }) } };
519
+ return { ...result, observation: { ...committed.observation,
520
+ ...(entry === undefined ? { enrichmentUnavailable: true } : { entry }) } };
539
521
  }
540
522
  catch (error) {
541
523
  throw failure(error, { operation: 'join', subject: 'caller', participant: name });
542
524
  }
543
525
  }
544
- /** Restore only a standing seat; a concurrent departure or takeover cannot be revived. */
545
526
  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);
527
+ const seat = await this.reattachSeat(name, { owner, wakeAddress }, control);
564
528
  return seat === null ? null : this.handle(seat);
565
529
  }
566
530
  async history(query = {}, control) {
@@ -575,7 +539,7 @@ export class SqliteSquare {
575
539
  throw new SquareError('invalid_args', 'History context must be a non-negative safe integer');
576
540
  const limit = query.limit === undefined ? (query.at?.length ? null : HISTORY_DEFAULT_LIMIT) : query.limit;
577
541
  const contextual = !!query.at?.length || query.contextBefore !== undefined || query.contextAfter !== undefined;
578
- const kinds = contextual || query.since !== undefined ? undefined : NON_READ;
542
+ const kinds = contextual || query.since !== undefined ? [...NON_READ, 'read'] : NON_READ;
579
543
  return (await this.artifact.read(facts => {
580
544
  const selected = selectHistory(facts, query, limit, kinds, query.observe === true);
581
545
  if (contextual && limit === null && selected.totalMatches > HISTORY_MAX_LIMIT)
@@ -611,40 +575,20 @@ export class SqliteSquare {
611
575
  return streamSquare(this.location, options, control);
612
576
  }
613
577
  async seatsOf(owner) {
614
- requireOwner(owner);
615
- return (await this.artifact.read(facts => facts.seatsForOwner(owner).map(({ participant: name, generation, attachment }) => ({ name, generation, attachment })))).value;
578
+ return this.channel.endpointsOf(owner);
616
579
  }
617
- 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));
580
+ close() { return this.channel.close(); }
581
+ cancelDeliveries(name, identity) {
582
+ return this.cancelHeld(name, identity);
626
583
  }
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;
584
+ detach(name, identity) {
585
+ return this.channel.detach(name, identity);
633
586
  }
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;
587
+ endHeld(name, identity) {
588
+ return this.endHeldSeat(name, identity);
644
589
  }
645
590
  }
646
591
  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
592
  export function discover(input, options = {}, control) {
649
593
  if (input.path !== undefined || (input.env ?? process.env).SQUARE_LOCATION)
650
594
  squareLocation(input);
@@ -0,0 +1,21 @@
1
+ import { SquareError, type FactPage, type IndexPage } from './neutral.js';
2
+ export type Row = Record<string, string | number | null>;
3
+ export declare const encode: {
4
+ (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string;
5
+ (value: any, replacer?: (number | string)[] | null, space?: string | number): string;
6
+ };
7
+ export declare const key: (name: string) => string;
8
+ export declare function invalidArtifact(detail: string): SquareError;
9
+ export declare function requireFact(ok: unknown, detail?: string): asserts ok;
10
+ export declare function integer(value: unknown, minimum?: number): number;
11
+ export declare function finite(value: unknown): number;
12
+ export declare function string(value: unknown, nonempty?: boolean): string;
13
+ export declare function json(value: unknown): unknown;
14
+ export declare function object(value: unknown): Record<string, unknown>;
15
+ export declare function shape(value: unknown, required: readonly string[], optional?: readonly string[]): Record<string, unknown>;
16
+ export declare function strings(value: unknown): string[];
17
+ export declare function decoded(value: unknown, nonempty?: boolean): string;
18
+ export declare function immutable<T>(value: T): T;
19
+ export declare function stableJson(value: unknown): string;
20
+ export declare function pageRows<T>(rows: Row[], page: IndexPage, decode: (row: Row) => T): FactPage<T>;
21
+ export declare function pageLimit(page: IndexPage): number;