@astrosheep/square 0.3.92 → 0.3.94

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 (119) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +2 -2
  4. package/dist/boundary-presentation.d.ts +2 -2
  5. package/dist/catch-decisions.d.ts +4 -6
  6. package/dist/catch-decisions.js +4 -4
  7. package/dist/claude-delivery-status.js +10 -4
  8. package/dist/claude-delivery.js +19 -11
  9. package/dist/claude-mod.js +24 -53
  10. package/dist/cli/context.d.ts +9 -4
  11. package/dist/cli/context.js +30 -25
  12. package/dist/cli/express-presentation.d.ts +3 -1
  13. package/dist/cli/express-presentation.js +26 -14
  14. package/dist/cli/json.d.ts +3 -0
  15. package/dist/cli/json.js +14 -0
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.d.ts +1 -3
  18. package/dist/cli/observation-commands.js +43 -56
  19. package/dist/cli/program.js +2 -2
  20. package/dist/cli/registry.js +6 -3
  21. package/dist/cli/square-commands.d.ts +1 -1
  22. package/dist/cli/square-commands.js +46 -54
  23. package/dist/codex-hook.d.ts +3 -2
  24. package/dist/codex-hook.js +13 -4
  25. package/dist/codex-queue.js +4 -4
  26. package/dist/decisions.d.ts +5 -11
  27. package/dist/decisions.js +9 -20
  28. package/dist/delivery-deferral.js +2 -2
  29. package/dist/delivery-health.d.ts +0 -1
  30. package/dist/delivery-health.js +1 -3
  31. package/dist/delivery-operations.d.ts +2 -2
  32. package/dist/delivery-operations.js +38 -38
  33. package/dist/delivery.d.ts +1 -1
  34. package/dist/delivery.js +1 -1
  35. package/dist/harness-links.d.ts +1 -10
  36. package/dist/harness-links.js +144 -109
  37. package/dist/harness.js +4 -13
  38. package/dist/help.js +12 -10
  39. package/dist/inbox.d.ts +9 -22
  40. package/dist/inbox.js +23 -27
  41. package/dist/index.d.ts +1 -3
  42. package/dist/index.js +1 -1
  43. package/dist/mcp.d.ts +3 -0
  44. package/dist/mcp.js +23 -24
  45. package/dist/model.d.ts +8 -15
  46. package/dist/model.js +4 -7
  47. package/dist/notifications.d.ts +1 -1
  48. package/dist/notifications.js +6 -5
  49. package/dist/operation-presentation.d.ts +5 -4
  50. package/dist/operation-presentation.js +50 -45
  51. package/dist/owner.d.ts +11 -0
  52. package/dist/owner.js +32 -0
  53. package/dist/paseo-delivery.d.ts +4 -1
  54. package/dist/paseo-delivery.js +7 -12
  55. package/dist/paseo-state.d.ts +2 -2
  56. package/dist/paseo-state.js +6 -4
  57. package/dist/plugins/opencode/delivery.js +27 -15
  58. package/dist/ports.d.ts +13 -33
  59. package/dist/presentation-operations.js +4 -4
  60. package/dist/presentation.d.ts +5 -8
  61. package/dist/presentation.js +33 -43
  62. package/dist/runtime.d.ts +1 -7
  63. package/dist/runtime.js +0 -14
  64. package/dist/seats.d.ts +2 -3
  65. package/dist/seats.js +7 -15
  66. package/dist/session-lifecycle.d.ts +6 -2
  67. package/dist/session-lifecycle.js +39 -11
  68. package/dist/sqlite-square.d.ts +36 -22
  69. package/dist/sqlite-square.js +138 -161
  70. package/dist/square-api.d.ts +15 -10
  71. package/dist/square-api.js +26 -22
  72. package/dist/square-core.d.ts +0 -5
  73. package/dist/square-core.js +0 -9
  74. package/dist/square-domain.d.ts +4 -2
  75. package/dist/square-domain.js +56 -21
  76. package/dist/square-facade.d.ts +45 -87
  77. package/dist/square-index.d.ts +5 -13
  78. package/dist/square-index.js +13 -38
  79. package/dist/square-location.d.ts +1 -14
  80. package/dist/square-location.js +4 -69
  81. package/dist/square-migration-host-ledger.d.ts +2 -2
  82. package/dist/square-migration-host-ledger.js +6 -5
  83. package/dist/square-migration.d.ts +2 -1
  84. package/dist/square-migration.js +52 -30
  85. package/dist/square-sqlite.d.ts +6 -2
  86. package/dist/square-sqlite.js +54 -28
  87. package/dist/square-tools.d.ts +7 -10
  88. package/dist/square-tools.js +22 -54
  89. package/dist/stream.js +4 -5
  90. package/dist/views.d.ts +21 -10
  91. package/dist/views.js +38 -38
  92. package/dist/wakes.d.ts +1 -2
  93. package/dist/wakes.js +1 -1
  94. package/guides/application-facade-delivery-evidence.md +1 -1
  95. package/guides/claude-native.md +18 -9
  96. package/guides/codex-delivery.md +17 -6
  97. package/guides/mcp-connection-lifetime.md +1 -1
  98. package/guides/mcp.md +3 -3
  99. package/guides/migration.md +142 -151
  100. package/guides/operation-cancellation.md +1 -1
  101. package/guides/participant-ownership.md +78 -47
  102. package/guides/pi-tools.md +33 -4
  103. package/guides/primitive-history-consolidation.md +1 -1
  104. package/guides/row-storage-design.md +8 -8
  105. package/guides/square-api.md +244 -88
  106. package/package.json +2 -1
  107. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  108. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  109. package/plugins/opencode/delivery.ts +27 -17
  110. package/plugins/paseo/client/palette.ts +18 -18
  111. package/plugins/paseo/client/square-message.tsx +17 -23
  112. package/plugins/pi/square-pi.js +17 -17
  113. package/dist/square-backend.d.ts +0 -13
  114. package/dist/square-backend.js +0 -1
  115. package/dist/square-stdio.d.ts +0 -22
  116. package/dist/square-stdio.js +0 -235
  117. package/dist/stdio-server.d.ts +0 -3
  118. package/dist/stdio-server.js +0 -212
  119. package/guides/backends.md +0 -193
@@ -1,21 +1,23 @@
1
1
  import { formatActivityId } from './square-core.js';
2
+ import { assertCurrentSeat } from './square-domain.js';
3
+ import { canonicalPath } from './canonical-path.js';
2
4
  import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
3
- import { squareLocation, localSquareLocation } from './square-location.js';
5
+ import { squareLocation } from './square-location.js';
4
6
  import crypto from 'node:crypto';
5
7
  import { setTimeout as sleep } from 'node:timers/promises';
6
- import { InternalSquareError, SquareError, isSquareError, nameKey, sameBody, validateName } from './model.js';
7
- import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, coreUnlisten, decideAct, decideImplicitJoin, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
8
+ import { InternalSquareError, SquareError, isSquareError, nameKey, validateName } from './model.js';
9
+ import { coreDone, coreHold, coreIgnore, coreListen, coreListening, coreResume, coreUnlisten, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
8
10
  import { HELD_WAIT_BUDGET_MS, WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
9
11
  import { catchLimit, decideCatch, observeCatchSelection, selectCatchPage } from './catch-decisions.js';
10
- import { directedSuffix } from './activity-feed.js';
12
+ import { expressionContext } from './activity-feed.js';
11
13
  import { expressHint } from './hints.js';
12
- import { asSeatRef, participantAuthority, requireBody, requireJoinName, requireSeat } from './seats.js';
14
+ import { asSeatRef, participantAuthority, requireOwner, requireJoinName } from './seats.js';
13
15
  import { recoveryObservation, entryPresentation, snapshot, queryStatus, queryParticipants, catchPresence, parseRequiredActivityId, toPublicActivity, publicActivities, selectHistory, NON_READ, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
14
16
  import { sessionInboxCounts } from './inbox.js';
15
17
  import { rememberSquarePath, squareIndexPath } from './square-index.js';
16
- import { createDefaultWakeTransport } from './notifications.js';
17
18
  import { deliverPending } from './delivery-operations.js';
18
19
  import { participantIdentity } from './participant-identity.js';
20
+ import { joinRecoveryCommand } from './presentation.js';
19
21
  import { acquireWatchLease, ownsWatchLease, pulseWatchLease, releaseWatchLease } from './wakes.js';
20
22
  import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuild } from './artifact.js';
21
23
  import { migrateSquareFile } from './square-migration.js';
@@ -65,38 +67,28 @@ export class SqliteSquare {
65
67
  location;
66
68
  artifact;
67
69
  clock;
68
- body;
69
- wakeAddress;
70
70
  wakeTransport;
71
71
  indexPath;
72
- automaticDelivery;
73
- ownsArtifact;
74
- defaultParticipant;
75
- constructor(location, artifact, clock, body, wakeAddress, wakeTransport, indexPath, automaticDelivery = true, ownsArtifact = true, defaultParticipant) {
72
+ closesArtifact;
73
+ deliveryLocation;
74
+ constructor(location, artifact, clock, wakeTransport, indexPath, closesArtifact = true, deliveryLocation = location) {
76
75
  this.location = location;
77
76
  this.artifact = artifact;
78
77
  this.clock = clock;
79
- this.body = body;
80
- this.wakeAddress = wakeAddress;
81
78
  this.wakeTransport = wakeTransport;
82
79
  this.indexPath = indexPath;
83
- this.automaticDelivery = automaticDelivery;
84
- this.ownsArtifact = ownsArtifact;
85
- this.defaultParticipant = defaultParticipant;
80
+ this.closesArtifact = closesArtifact;
81
+ this.deliveryLocation = deliveryLocation;
86
82
  }
87
- static async at(input, control, automaticDelivery = true) {
83
+ static async at(input, control) {
88
84
  const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
89
85
  const clock = squareClock(env, input.clock);
90
- const wakeTransport = input.wakeTransport ?? (automaticDelivery
91
- ? await createDefaultWakeTransport(env).catch(() => undefined) : undefined);
92
86
  // A request can abort its own borrowed reads, never the owner's connection lifetime.
93
87
  const borrowed = input.resources?.artifact(location);
94
88
  const artifact = borrowed ?? createArtifactPort(location, undefined, control?.signal);
95
89
  try {
96
90
  await artifact.read(facts => facts.header(), control?.signal);
97
- const resource = new SqliteSquare(location, artifact, clock, input.body, input.wakeAddress, wakeTransport, squareIndexPath(env), automaticDelivery, borrowed === undefined, input.participant);
98
- await resource.publishIdentity();
99
- return resource;
91
+ return new SqliteSquare(location, artifact, clock, input.wakeTransport || undefined, squareIndexPath(env), borrowed === undefined, await canonicalPath(location));
100
92
  }
101
93
  catch (error) {
102
94
  if (borrowed === undefined)
@@ -106,33 +98,33 @@ export class SqliteSquare {
106
98
  throw error;
107
99
  }
108
100
  }
109
- static async build(input, control, automaticDelivery = true) {
101
+ static async build(input, control) {
110
102
  checkControl(control);
111
103
  const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
112
104
  await createSquare(location, { hardCap: input.hardCap, throttlePerMinute: input.throttlePerMinute,
113
105
  template: input.template, force: input.force ?? false }, input.markdown, control?.signal);
114
106
  // Publication already committed; cancellation cannot erase the completed build.
115
- return SqliteSquare.at({ ...input, path: location, env }, undefined, automaticDelivery);
107
+ return SqliteSquare.at({ ...input, path: location, env });
116
108
  }
117
109
  static inMemory(input) {
118
110
  const env = { ...(input.env ?? process.env) };
119
- return new SqliteSquare('memory', createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), input.body, input.wakeAddress, input.wakeTransport, undefined, true, true, input.participant);
111
+ return new SqliteSquare(`memory://${crypto.randomUUID()}`, createArtifactPort(':memory:', prepareSquareBuild(input, input.markdown)), squareClock(env, input.clock), input.wakeTransport || undefined);
120
112
  }
121
- /** Discovery is best effort and never grants write authority. Finish it before returning. */
122
- async publishIdentity() {
123
- if (this.location !== 'memory' && this.body !== undefined && this.indexPath !== undefined)
124
- await rememberSquarePath(this.location, this.body, this.indexPath).catch(() => undefined);
113
+ /** Discovery is best effort and never grants write authority. */
114
+ async publishSeat(owner) {
115
+ if (this.indexPath !== undefined)
116
+ await rememberSquarePath(this.location, owner, this.indexPath).catch(() => undefined);
125
117
  }
126
118
  observation(standingCount, held, participant, now = this.clock()) {
127
119
  return { location: this.location, standingCount, held, now,
128
- ...(participant === undefined ? {} : { participant }), automaticDeliveryAvailable: this.automaticDelivery && this.body !== undefined };
120
+ ...(participant === undefined ? {} : { participant }) };
129
121
  }
130
122
  async contextObservation(participant, control) {
131
123
  return (await this.artifact.read(facts => this.observation(inSquareCount(facts), facts.hold().active, participant), control?.signal)).value;
132
124
  }
133
- handle(name, seat) { return new SqliteSquare.ParticipantHandle(name, this, seat); }
134
- // The nested handle can use its resource's private dependencies without widening
135
- // the concrete-derived backend contract or copying them into a second context.
125
+ handle(seat) { return new SqliteSquare.ParticipantHandle(seat.participant, this, seat); }
126
+ // The nested handle uses its resource's private dependencies without copying
127
+ // them into a second context.
136
128
  static ParticipantHandle = class {
137
129
  name;
138
130
  owner;
@@ -150,16 +142,12 @@ export class SqliteSquare {
150
142
  const control = input === undefined ? operationControl : optionsOrControl;
151
143
  let name = this.name, effect = 'unsent';
152
144
  let landedId;
153
- let waited = false, heldWaitedMs = 0;
145
+ let heldWaitedMs = 0;
154
146
  try {
155
147
  checkControl(control);
156
148
  name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
157
- const seat = requireSeat(this.seat, square.body, name);
158
- const feed = !options.observe ? undefined : (await square.artifact.read(facts => {
159
- const pendingPublic = directedSuffix(facts, name);
160
- return { pendingPublic, sayNumbers: facts.sayOrdinals(pendingPublic.map(item => item.index)),
161
- perceptions: Object.fromEntries(pendingPublic.map(item => [item.index, 'full'])) };
162
- }, control?.signal)).value;
149
+ const seat = this.seat;
150
+ const context = !options.observe ? undefined : (await square.artifact.read(facts => expressionContext(facts, name), control?.signal)).value;
163
151
  while (true) {
164
152
  checkControl(control);
165
153
  try {
@@ -200,16 +188,13 @@ export class SqliteSquare {
200
188
  }, control?.signal)).value;
201
189
  landedId = formatActivityId(committed.stored.index);
202
190
  effect = 'landed';
203
- await square.publishIdentity();
204
- const delivery = square.wakeTransport !== undefined && square.location !== 'memory'
205
- ? await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.location, activity: committed.stored.index, now })
206
- .catch(() => ({ attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 }))
207
- : { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 1 };
191
+ if (square.wakeTransport !== undefined)
192
+ await deliverPending({ artifact: square.artifact, transport: square.wakeTransport, location: square.deliveryLocation, activity: committed.stored.index, now }).catch(() => undefined);
208
193
  const { stored, standingCount, held, ownActivityCount, hardCap, ...metadata } = committed;
209
194
  const hint = expressHint(ownActivityCount);
210
- return { activity: toPublicActivity(stored), delivery, waited, ...metadata,
211
- ...(hint === undefined ? {} : { hint }), ...(feed === undefined ? {} : { observation: {
212
- ...square.observation(standingCount, held, name, now), ownActivityCount, hardCap, ...feed, participant: name,
195
+ return { activity: toPublicActivity(stored), ...metadata,
196
+ ...(hint === undefined ? {} : { hint }), ...(context === undefined ? {} : { observation: {
197
+ ...square.observation(standingCount, held, name, now), ownActivityCount, hardCap, context, participant: name,
213
198
  } }) };
214
199
  }
215
200
  catch (error) {
@@ -219,7 +204,6 @@ export class SqliteSquare {
219
204
  throw error;
220
205
  if (error.code === 'held' && heldWaitedMs >= HELD_WAIT_BUDGET_MS)
221
206
  throw error;
222
- waited = true;
223
207
  control?.onProgress?.({ kind: 'waiting', reason: error.code,
224
208
  ...(error.code === 'throttled' ? { delayMs: error.facts?.retryAfterMs ?? 1 } : {}),
225
209
  ...(error.facts?.holder === undefined ? {} : { holder: error.facts.holder }),
@@ -246,13 +230,13 @@ export class SqliteSquare {
246
230
  const square = this.owner;
247
231
  const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
248
232
  checkControl(control);
249
- const seat = requireSeat(this.seat, square.body, name), now = square.clock();
233
+ const seat = this.seat, now = square.clock();
250
234
  return (await square.artifact.transactAs(participantAuthority(seat), facts => {
251
235
  const act = verb === 'listen' ? coreListen(facts, name, target, now) : verb === 'ignore' ? coreIgnore(facts, name, target, now) : coreUnlisten(facts, name, target, now);
252
236
  const activity = act === undefined ? null : toPublicActivity(facts.land(act));
253
237
  return { activity, ...(!options.observe ? {} : { observation: {
254
238
  ...square.observation(inSquareCount(facts), facts.hold().active, name, now), participant: name,
255
- requestedTarget: target, target: activity?.target ?? target,
239
+ target: activity?.target ?? target,
256
240
  } }) };
257
241
  }, control?.signal)).value;
258
242
  }
@@ -278,15 +262,14 @@ export class SqliteSquare {
278
262
  const square = this.owner;
279
263
  let name = this.name, subject = 'caller';
280
264
  let leaseId, seat;
281
- let waitStarted = false, replaced = false;
282
265
  let selection = options;
283
266
  const idle = options.idle ?? 0;
284
- const complete = async (caught, stop) => {
267
+ const complete = async (caught, reason = 'drained') => {
285
268
  const view = caught ?? (await square.artifact.read(facts => observeCatchSelection(facts, name, [], {
286
269
  unseenOnly: true, senders: selection.from, ...(selection.mention === true ? { attentionOnly: true } : {}),
287
270
  }, square.clock()))).value;
288
- if (stop === 'empty-now' || stop === undefined)
289
- stop = view.terminalStatus ?? stop;
271
+ if (reason === 'drained')
272
+ reason = view.terminalStatus ?? reason;
290
273
  if (leaseId !== undefined) {
291
274
  await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
292
275
  leaseId = undefined;
@@ -298,13 +281,12 @@ export class SqliteSquare {
298
281
  }
299
282
  catch { /* Optional presence cannot erase the completed selection or receipts. */ }
300
283
  }
301
- const status = stop === 'already-active' || stop === 'replaced' || stop === 'capped' || stop === 'quorum' ? stop : undefined;
302
- return { activities: view.activities.map(activity => ({ ...toPublicActivity(activity), perception: 'full' })),
303
- consumedThrough: view.consumedThrough, remaining: view.remaining, idleExpired: stop === 'stale',
304
- ...(status === undefined ? {} : { status }), ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
284
+ return { activities: view.activities, remaining: view.remaining, reason,
285
+ ...(view.hint === undefined ? {} : { hint: view.hint }), ...(!options.observe ? {} : { observation: {
305
286
  ...square.observation(view.standingCount, view.held, view.participant, view.now), participant: view.participant,
306
- sayNumbers: view.sayNumbers, ownActivityCount: view.ownActivityCount, hardCap: view.hardCap, stop,
307
- ...(presence === undefined ? { enrichmentUnavailable: true } : { presence }), waitStarted, replaced,
287
+ deliveryAvailable: view.deliveryAvailable,
288
+ ownActivityCount: view.ownActivityCount, hardCap: view.hardCap,
289
+ ...(presence === undefined ? { enrichmentUnavailable: true } : { presence }),
308
290
  } }) };
309
291
  };
310
292
  try {
@@ -319,7 +301,8 @@ export class SqliteSquare {
319
301
  catchLimit(options.limit);
320
302
  const probe = (await square.artifact.read(facts => {
321
303
  name = resolveKnownName(facts, this.name);
322
- seat = requireSeat(this.seat, square.body, name);
304
+ seat = this.seat;
305
+ assertCurrentSeat(facts, asSeatRef(seat));
323
306
  subject = 'filter';
324
307
  const from = options.from?.map(target => resolveKnownName(facts, target));
325
308
  selection = { ...options, ...(from === undefined ? {} : { from }) };
@@ -331,7 +314,7 @@ export class SqliteSquare {
331
314
  }, control?.signal)).value;
332
315
  if (idle === 0) {
333
316
  const data = probe ?? (await this.catchPage(name, seat, selection, control));
334
- return await complete(data, data.activities.length === 0 ? 'empty-now' : undefined);
317
+ return await complete(data);
335
318
  }
336
319
  const watch = {
337
320
  ...(selection.from === undefined ? {} : { participants: [...selection.from] }), ...(options.mention ? { mention: name } : {}),
@@ -341,9 +324,7 @@ export class SqliteSquare {
341
324
  if (start.type === 'active')
342
325
  return await complete(undefined, 'already-active');
343
326
  leaseId = start.leaseId;
344
- waitStarted = true;
345
- replaced = start.replaced;
346
- control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced });
327
+ control?.onProgress?.({ kind: 'catch-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced: start.replaced });
347
328
  let quietSince = square.clock(), wasHeld = false, nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
348
329
  while (true) {
349
330
  checkControl(control);
@@ -368,7 +349,7 @@ export class SqliteSquare {
368
349
  wasHeld = pulse.type === 'held';
369
350
  if (!wasHeld && square.clock() - quietSince >= idle) {
370
351
  const owns = await ownsWatchLease(square.artifact, seat, square.clock(), leaseId, control?.signal);
371
- return await complete(undefined, owns ? 'stale' : 'replaced');
352
+ return await complete(undefined, owns ? 'idle' : 'replaced');
372
353
  }
373
354
  const deadline = Math.min(nextHeartbeat, wasHeld ? Infinity : quietSince + idle);
374
355
  await square.artifact.changed(baseline, Math.max(0, deadline - square.clock()), control?.signal);
@@ -386,7 +367,7 @@ export class SqliteSquare {
386
367
  async deferDelivery(input, control) {
387
368
  checkControl(control);
388
369
  const match = validateMetadataMatch(input?.match), duration = validateDeferralDuration(input?.durationMs);
389
- const square = this.owner, seat = requireSeat(this.seat, square.body, this.name);
370
+ const square = this.owner, seat = this.seat;
390
371
  return (await square.artifact.transactAs(participantAuthority(seat), facts => {
391
372
  const now = square.clock(), expiresAt = now + duration, id = crypto.randomUUID();
392
373
  const result = { id, expiresAt: new Date(expiresAt).toISOString() };
@@ -398,7 +379,7 @@ export class SqliteSquare {
398
379
  checkControl(control);
399
380
  validateDeferralId(id);
400
381
  const duration = validateDeferralDuration(durationMs), square = this.owner;
401
- const seat = requireSeat(this.seat, square.body, this.name);
382
+ const seat = this.seat;
402
383
  return (await square.artifact.transactAs(participantAuthority(seat), facts => {
403
384
  const now = square.clock(), expiresAt = now + duration;
404
385
  const result = { id, expiresAt: new Date(expiresAt).toISOString() };
@@ -408,55 +389,55 @@ export class SqliteSquare {
408
389
  async releaseDeliveryDeferral(id, control) {
409
390
  checkControl(control);
410
391
  validateDeferralId(id);
411
- const square = this.owner, seat = requireSeat(this.seat, square.body, this.name);
392
+ const square = this.owner, seat = this.seat;
412
393
  return (await square.artifact.transactAs(participantAuthority(seat), facts => facts.releaseDeliveryDeferral(asSeatRef(seat), id, square.clock()), control?.signal)).value;
413
394
  }
414
- history(query, control) { return this.owner.history(query, control); }
415
395
  async lifecycle(verb, body, options, control) {
416
396
  if (verb === 'done')
417
397
  validateDoneBody(body);
418
398
  const square = this.owner;
419
399
  const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
420
- const seat = requireSeat(this.seat, square.body, name), now = square.clock();
400
+ const seat = this.seat, now = square.clock();
421
401
  return (await square.artifact.transactAs(participantAuthority(seat), facts => {
422
402
  const stored = facts.land(verb === 'done' ? coreDone(facts, name, body ?? '', now) : verb === 'hold' ? coreHold(facts, name, body ?? '', now) : coreResume(facts, name, now));
423
- return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: {
424
- ...square.observation(inSquareCount(facts), facts.hold().active, name, now), activity: stored,
425
- } }) };
403
+ return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), facts.hold().active, name, now),
404
+ }) };
426
405
  }, control?.signal)).value;
427
406
  }
428
407
  hold(reason, options = {}, control) { return this.lifecycle('hold', reason, options, control); }
429
408
  resume(options = {}, control) { return this.lifecycle('resume', undefined, options, control); }
430
409
  done(body, options = {}, control) { return this.lifecycle('done', body, options, control); }
431
410
  };
432
- async participant(requested) {
433
- const selected = requested ?? this.defaultParticipant;
434
- if (selected !== undefined) {
435
- validateName(selected);
436
- const seat = this.body === undefined ? undefined : (await this.artifact.read(facts => facts.seat(selected))).value;
437
- return this.handle(selected, seat);
438
- }
439
- if (this.body !== undefined) {
440
- const seats = (await this.artifact.read(facts => facts.seatsForBody(this.body))).value;
441
- if (seats.length === 1)
442
- return this.handle(seats[0].participant, seats[0]);
443
- if (seats.length > 1)
444
- throw new SquareError('invalid_args', 'Square needs an explicit participant name: this body holds multiple names');
445
- }
446
- throw new SquareError('invalid_args', 'Square needs one unambiguous participant name');
411
+ async participant(name, { owner }) {
412
+ validateName(name);
413
+ requireOwner(owner);
414
+ return (await this.artifact.read(facts => {
415
+ const participant = facts.participant(name), seat = facts.seat(name);
416
+ if (!participant?.joined || seat === undefined || seat.owner !== owner || seat.attachment !== 'attached') {
417
+ const reason = participant === undefined ? 'has never stepped into this square'
418
+ : !participant.joined ? 'needs a current attached seat; join first'
419
+ : seat !== undefined && seat.owner !== owner ? 'is not held by this owner; join before writing'
420
+ : seat?.attachment === 'detached' ? 'is away — join again before writing'
421
+ : 'needs a current attached seat; join first';
422
+ throw new SquareError('already_joined', `${participantIdentity(participant?.name ?? name)} ${reason}`, {
423
+ reason: 'needs_seat', join: joinRecoveryCommand(this.location, participant?.name ?? name),
424
+ });
425
+ }
426
+ return this.handle(seat);
427
+ })).value;
447
428
  }
448
- async join(requested, options = {}, control) {
429
+ async join(requested, options, control) {
449
430
  let name = requested;
450
431
  try {
451
432
  checkControl(control);
452
433
  name = requireJoinName(requested);
453
- const last = options.last ?? HISTORY_DEFAULT_LIMIT;
434
+ const last = options?.last ?? HISTORY_DEFAULT_LIMIT;
454
435
  if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
455
436
  throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
456
- const body = requireBody(this.body), now = this.clock();
437
+ const owner = requireOwner(options?.owner), wakeAddress = options.wakeAddress, now = this.clock();
457
438
  const initial = !options.takeover ? undefined : (await this.artifact.read(facts => facts.participant(name)?.joined ? facts.seat(name) : undefined, control?.signal)).value;
458
- const authority = initial === undefined ? { kind: 'join', participant: name, body, wakeAddress: this.wakeAddress }
459
- : { kind: 'takeover', participant: initial.participant, body, expectedGeneration: initial.generation, wakeAddress: this.wakeAddress };
439
+ const authority = initial === undefined ? { kind: 'join', participant: name, owner, wakeAddress }
440
+ : { kind: 'takeover', participant: initial.participant, owner, expectedGeneration: initial.generation, wakeAddress };
460
441
  const committed = (await this.artifact.transactAs(authority, facts => {
461
442
  const participant = facts.participant(name)?.name ?? name;
462
443
  const current = facts.participant(participant), currentSeat = facts.seat(participant);
@@ -468,10 +449,12 @@ export class SqliteSquare {
468
449
  kind = 'taken-over';
469
450
  }
470
451
  else if (current?.joined) {
471
- if (currentSeat === undefined || !sameBody(currentSeat.body, body))
472
- throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another body; take it over explicitly`);
473
- if (this.wakeAddress !== undefined && canRefreshWakeAddress(currentSeat.wakeAddress, this.wakeAddress))
474
- facts.updateWakeAddress(asSeatRef(currentSeat), this.wakeAddress);
452
+ if (currentSeat === undefined || currentSeat.owner !== owner)
453
+ throw new SquareError('already_joined', `${participantIdentity(participant)} is held by another owner; take it over explicitly`);
454
+ if (currentSeat.attachment === 'detached')
455
+ facts.reattachSeat(asSeatRef(currentSeat), wakeAddress);
456
+ else if (wakeAddress !== undefined && canRefreshWakeAddress(currentSeat.wakeAddress, wakeAddress))
457
+ facts.updateWakeAddress(asSeatRef(currentSeat), wakeAddress);
475
458
  kind = 'reconnected';
476
459
  }
477
460
  if (kind !== 'reconnected') {
@@ -483,11 +466,11 @@ export class SqliteSquare {
483
466
  const seat = facts.seat(participant);
484
467
  if (seat === undefined)
485
468
  throw new SquareError('invalid_args', 'The join activity did not establish its artifact seat.');
486
- return { participant: this.handle(seat.participant, seat), activity, kind,
487
- participantCount: facts.roster().filter(item => item.joined || item.done).length,
488
- ...(!options.observe ? {} : { observation: this.observation(inSquareCount(facts), facts.hold().active, seat.participant, now) }) };
469
+ return { participant: this.handle(seat), activity, kind,
470
+ ...(!options.observe ? {} : { observation: { ...this.observation(inSquareCount(facts), facts.hold().active, seat.participant, now),
471
+ deliveryAvailable: seat.wakeAddress !== undefined } }) };
489
472
  }, control?.signal)).value;
490
- await this.publishIdentity();
473
+ await this.publishSeat(owner);
491
474
  if (!options.observe)
492
475
  return committed;
493
476
  let entry;
@@ -501,30 +484,27 @@ export class SqliteSquare {
501
484
  throw failure(error, { operation: 'join', subject: 'caller', participant: name });
502
485
  }
503
486
  }
504
- async implicitJoin(name, control) {
487
+ /** Restore only a standing seat; a concurrent departure or takeover cannot be revived. */
488
+ async reattach(name, { owner, wakeAddress }, control) {
505
489
  checkControl(control);
506
490
  validateName(name);
507
- const body = requireBody(this.body), now = this.clock();
508
- const result = (await this.artifact.transactAs({ kind: 'join', participant: name, body, wakeAddress: this.wakeAddress }, facts => {
509
- const decision = decideImplicitJoin(facts, name, now);
510
- let seat = facts.seat(decision.joinedName);
511
- if (decision.joinAct === undefined) {
512
- if (decision.state === 'active' && (seat === undefined || !sameBody(seat.body, body)))
513
- throw new SquareError('already_joined', `${participantIdentity(decision.joinedName)} is held by another body`);
514
- if (decision.state === 'active' && this.wakeAddress !== undefined && canRefreshWakeAddress(seat.wakeAddress, this.wakeAddress))
515
- facts.updateWakeAddress(asSeatRef(seat), this.wakeAddress);
516
- }
517
- else {
518
- facts.land(decision.joinAct);
519
- seat = facts.seat(decision.joinedName);
520
- if (seat === undefined)
521
- throw new SquareError('invalid_args', 'The implicit join did not establish its artifact seat.');
522
- }
523
- return { state: decision.state, ...(decision.state === 'done' ? {} : { participant: this.handle(decision.joinedName, seat) }) };
491
+ requireOwner(owner);
492
+ const captured = (await this.artifact.read(facts => facts.seat(name), control?.signal)).value;
493
+ if (captured === undefined || captured.owner !== owner)
494
+ return null;
495
+ const seat = (await this.artifact.transactAs({ kind: 'join', participant: name, owner, wakeAddress }, facts => {
496
+ const current = facts.seat(name);
497
+ if (!facts.participant(name)?.joined || current === undefined || current.generation !== captured.generation || current.owner !== owner)
498
+ return null;
499
+ if (current.attachment === 'detached')
500
+ facts.reattachSeat(asSeatRef(current), wakeAddress);
501
+ else if (wakeAddress !== undefined && canRefreshWakeAddress(current.wakeAddress, wakeAddress))
502
+ facts.updateWakeAddress(asSeatRef(current), wakeAddress);
503
+ return facts.seat(name);
524
504
  }, control?.signal)).value;
525
- if (result.participant !== undefined)
526
- await this.publishIdentity();
527
- return result;
505
+ if (seat !== null)
506
+ await this.publishSeat(owner);
507
+ return seat === null ? null : this.handle(seat);
528
508
  }
529
509
  async snapshot(control) { return (await this.artifact.read(facts => snapshot(facts, this.clock()), control?.signal)).value; }
530
510
  async history(query = {}, control) {
@@ -560,73 +540,70 @@ export class SqliteSquare {
560
540
  }, control?.signal)).value;
561
541
  }
562
542
  async status(options = {}, control) {
563
- return (await this.artifact.read(facts => {
564
- const seats = this.body === undefined ? [] : facts.seatsForBody(this.body);
565
- const viewer = options.viewer ?? this.defaultParticipant ?? (seats.length === 1 ? seats[0].participant : '');
566
- return queryStatus(facts, {
567
- location: this.location, now: this.clock(), automaticDeliveryAvailable: this.automaticDelivery && this.body !== undefined,
568
- }, viewer, options.observe === true);
569
- }, control?.signal)).value;
543
+ return (await this.artifact.read(facts => queryStatus(facts, { location: this.location, now: this.clock() }, options.viewer, options.observe === true), control?.signal)).value;
570
544
  }
571
545
  async participants(options = {}, control) {
572
546
  return (await this.artifact.read(facts => queryParticipants(facts, {
573
- location: this.location, now: this.clock(), automaticDeliveryAvailable: this.automaticDelivery && this.body !== undefined,
547
+ location: this.location, now: this.clock(),
574
548
  }, options.observe === true), control?.signal)).value;
575
549
  }
576
550
  async recovery(requested, control) {
577
- const name = (await this.participant(requested).catch(() => undefined))?.name;
578
- const facts = name === undefined ? undefined : (await this.artifact.read(facts => recoveryObservation(facts, name, this.clock()), control?.signal).catch(() => undefined))?.value;
579
- return facts ?? await this.contextObservation(name, control);
551
+ const facts = requested === undefined ? undefined : (await this.artifact.read(facts => recoveryObservation(facts, requested, this.clock()), control?.signal).catch(() => undefined))?.value;
552
+ return facts ?? await this.contextObservation(requested, control);
580
553
  }
581
554
  stream(options, control) {
582
555
  // A path stream follows replacements independently of this connection's inode
583
556
  // and lifetime. Only explicit artifact readers borrow an existing port.
584
557
  return streamSquare(this.location, options, control);
585
558
  }
586
- async recognize() {
587
- if (this.body === undefined)
588
- return [];
589
- const candidates = (await this.artifact.read(facts => facts.seatsForBody(this.body))).value;
590
- return candidates.map(seat => this.handle(seat.participant, seat));
559
+ async seatsOf(owner) {
560
+ requireOwner(owner);
561
+ return (await this.artifact.read(facts => facts.seatsForOwner(owner).map(({ participant: name, generation, attachment }) => ({ name, generation, attachment })))).value;
591
562
  }
592
- close() { return this.ownsArtifact ? this.artifact.close() : Promise.resolve(); }
593
- async cancelDeliveries(name, expectedGeneration) {
594
- const body = this.body;
595
- if (body === undefined)
596
- return false;
563
+ close() { return this.closesArtifact ? this.artifact.close() : Promise.resolve(); }
564
+ async heldSeat(name, { owner, generation }) {
565
+ requireOwner(owner);
597
566
  const seat = (await this.artifact.read(facts => facts.seat(name))).value;
598
- if (seat === undefined || seat.generation !== expectedGeneration || !sameBody(seat.body, body))
599
- return false;
600
- return this.artifact.cancelDeliveries(asSeatRef(seat));
567
+ return seat?.generation === generation && seat.owner === owner ? seat : undefined;
568
+ }
569
+ async cancelDeliveries(name, identity) {
570
+ const seat = await this.heldSeat(name, identity);
571
+ return seat === undefined ? false : this.artifact.cancelDeliveries(asSeatRef(seat));
572
+ }
573
+ async detach(name, identity) {
574
+ const captured = await this.heldSeat(name, identity);
575
+ if (captured === undefined)
576
+ return null;
577
+ const seat = asSeatRef(captured);
578
+ return (await this.artifact.transactAs({ kind: 'host-cleanup', seat }, facts => facts.detachSeat(seat) ? true : null)).value;
601
579
  }
602
- async endOwnedSession(name, sessionId, expectedGeneration, body) {
603
- const captured = (await this.artifact.read(facts => facts.seat(name))).value;
604
- if (captured === undefined || captured.body.session !== sessionId || (body !== undefined && !sameBody(captured.body, body))
605
- || (expectedGeneration !== undefined && captured.generation !== expectedGeneration))
580
+ async endHeld(name, identity) {
581
+ const captured = await this.heldSeat(name, identity);
582
+ if (captured === undefined)
606
583
  return null;
607
- return (await this.artifact.transactAs(participantAuthority(captured), facts => {
584
+ return (await this.artifact.transactAs({ kind: 'host-cleanup', seat: asSeatRef(captured) }, facts => {
608
585
  const current = facts.seat(name);
609
- if (current === undefined || current.generation !== captured.generation || !sameBody(current.body, captured.body))
586
+ if (current === undefined || current.generation !== captured.generation || current.owner !== captured.owner)
610
587
  return null;
611
588
  return { activity: toPublicActivity(facts.land(coreDone(facts, current.participant, '', this.clock()))) };
612
589
  })).value;
613
590
  }
614
591
  }
615
- export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(localSquareLocation(input)); }
616
- export function migrateSquare(input, control) { checkControl(control); const env = input.env ?? process.env; return migrateSquareFile(localSquareLocation(input), control?.signal, env); }
592
+ export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(squareLocation(input)); }
593
+ export function migrateSquare(input, control) { checkControl(control); const env = input.env ?? process.env; return migrateSquareFile(squareLocation(input), control?.signal, env); }
617
594
  export function discover(input, options = {}, control) {
618
595
  if (input.path !== undefined || (input.env ?? process.env).SQUARE_LOCATION)
619
- localSquareLocation(input);
596
+ squareLocation(input);
620
597
  const env = { ...(input.env ?? process.env) };
621
598
  return discoverSquares(input.cwd ?? process.cwd(), options, control, { clock: squareClock(env, input.clock) });
622
599
  }
623
- export async function inbox(input, identity, options = {}, control) {
600
+ export async function inbox(input, owner, options = {}, control) {
624
601
  checkControl(control);
602
+ requireOwner(owner);
625
603
  const env = { ...(input.env ?? process.env) }, limit = options.limit ?? 20;
626
604
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
627
605
  throw new SquareError('invalid_args', 'Inbox limit must be between 1 and 100');
628
- const query = typeof identity === 'string' ? { sessionId: identity, env } : { body: identity, env };
629
- const rows = await sessionInboxCounts(query, control?.signal);
606
+ const rows = await sessionInboxCounts({ owner, env }, control?.signal);
630
607
  checkControl(control);
631
608
  const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
632
609
  rows.sort((a, b) => compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name));
@@ -1,14 +1,19 @@
1
- import type { SquareBackend } from './square-backend.js';
2
- import type { SquareAtInput, SquareBuildInput, OperationControl } from './square-facade.js';
1
+ import { SqliteSquare } from './sqlite-square.js';
2
+ import { type Owner } from './model.js';
3
+ import type { OperationControl, Participant, SquareAtInput, SquareBuildInput } from './square-facade.js';
4
+ export type Square = SqliteSquare;
5
+ export declare const Square: {
6
+ at: (input: SquareAtInput, control?: OperationControl) => Promise<SqliteSquare>;
7
+ build: (input: SquareBuildInput, control?: OperationControl) => Promise<SqliteSquare>;
8
+ inMemory: typeof SqliteSquare.inMemory;
9
+ };
10
+ export { requireOwner, requireJoinName } from './seats.js';
3
11
  export type * from './square-facade.js';
4
12
  export { squareLocation } from './square-location.js';
13
+ export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' | 'seatsOf'>, owner: Owner | undefined, name?: string): Promise<Participant>;
14
+ export declare function selectOwnSeat(square: Pick<SqliteSquare, 'participant' | 'seatsOf'>, owner: Owner | undefined, name: string | undefined, options: {
15
+ readonly optional: true;
16
+ }): Promise<Participant | undefined>;
5
17
  export { ACTIVITY_METADATA_MAX_BYTES } from './model.js';
18
+ export { localUserOwner, harnessOwner, decodeHarnessOwner } from './owner.js';
6
19
  export { squareClock, createSquareResourceOwner, formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE, CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT, HELD_WAIT_BUDGET_MS, HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT, DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT, diagnoseSquare, migrateSquare, discover, inbox } from './sqlite-square.js';
7
- /** The same structural resource regardless of persistence or transport. */
8
- export interface Square extends SquareBackend {
9
- }
10
- export declare const Square: {
11
- at(input: SquareAtInput, control?: OperationControl): Promise<Square>;
12
- build(input: SquareBuildInput, control?: OperationControl): Promise<Square>;
13
- inMemory(input: Omit<SquareBuildInput, "path" | "resources">): Square;
14
- };