@astrosheep/square 0.3.85 → 0.3.87

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 (148) hide show
  1. package/dist/artifact.d.ts +6 -1
  2. package/dist/artifact.js +79 -10
  3. package/dist/boundary-presentation.d.ts +5 -4
  4. package/dist/boundary-presentation.js +21 -25
  5. package/dist/catch-decisions.js +2 -1
  6. package/dist/claude-delivery-status.d.ts +8 -2
  7. package/dist/claude-delivery-status.js +26 -10
  8. package/dist/claude-delivery.d.ts +8 -4
  9. package/dist/claude-delivery.js +114 -104
  10. package/dist/claude-mcp-context.js +2 -2
  11. package/dist/claude-mod.js +119 -105
  12. package/dist/cli/context.d.ts +3 -1
  13. package/dist/cli/context.js +25 -1
  14. package/dist/cli/express-presentation.d.ts +3 -1
  15. package/dist/cli/express-presentation.js +15 -12
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.js +2 -1
  18. package/dist/cli/registry.js +0 -2
  19. package/dist/cli/square-commands.d.ts +4 -2
  20. package/dist/cli/square-commands.js +21 -6
  21. package/dist/codex-hook.d.ts +3 -3
  22. package/dist/codex-hook.js +4 -4
  23. package/dist/codex-queue.d.ts +5 -6
  24. package/dist/codex-queue.js +9 -5
  25. package/dist/decisions.d.ts +3 -2
  26. package/dist/decisions.js +18 -6
  27. package/dist/delivery-health.d.ts +32 -2
  28. package/dist/delivery-health.js +108 -45
  29. package/dist/delivery-operations.d.ts +1 -2
  30. package/dist/delivery-operations.js +155 -196
  31. package/dist/delivery.d.ts +14 -23
  32. package/dist/delivery.js +48 -18
  33. package/dist/file-lock.d.ts +8 -1
  34. package/dist/file-lock.js +2 -0
  35. package/dist/help.js +12 -14
  36. package/dist/hints.js +2 -3
  37. package/dist/inbox.d.ts +35 -9
  38. package/dist/inbox.js +228 -144
  39. package/dist/index.d.ts +4 -4
  40. package/dist/index.js +1 -3
  41. package/dist/list.js +9 -4
  42. package/dist/mcp.js +12 -1
  43. package/dist/model.d.ts +39 -5
  44. package/dist/model.js +84 -0
  45. package/dist/notifications.d.ts +8 -6
  46. package/dist/notifications.js +56 -143
  47. package/dist/operation-presentation.js +12 -14
  48. package/dist/participant-identity.d.ts +0 -3
  49. package/dist/participant-identity.js +0 -31
  50. package/dist/paseo-delivery.d.ts +5 -6
  51. package/dist/paseo-delivery.js +13 -8
  52. package/dist/paseo.d.ts +1 -1
  53. package/dist/paseo.js +1 -1
  54. package/dist/plugins/opencode/delivery.d.ts +8 -3
  55. package/dist/plugins/opencode/delivery.js +187 -132
  56. package/dist/plugins/opencode/index.js +12 -38
  57. package/dist/ports.d.ts +112 -40
  58. package/dist/presentation-operations.d.ts +1 -1
  59. package/dist/presentation-operations.js +51 -35
  60. package/dist/presentation.d.ts +0 -1
  61. package/dist/presentation.js +18 -13
  62. package/dist/seats.d.ts +7 -0
  63. package/dist/seats.js +27 -0
  64. package/dist/session-lifecycle.d.ts +2 -2
  65. package/dist/session-lifecycle.js +10 -12
  66. package/dist/sqlite-square.d.ts +53 -0
  67. package/dist/sqlite-square.js +602 -0
  68. package/dist/square-api.d.ts +12 -41
  69. package/dist/square-api.js +26 -474
  70. package/dist/square-backend.d.ts +13 -0
  71. package/dist/square-core.d.ts +3 -2
  72. package/dist/square-domain.d.ts +12 -5
  73. package/dist/square-domain.js +163 -7
  74. package/dist/square-facade.d.ts +16 -10
  75. package/dist/square-index.d.ts +24 -0
  76. package/dist/square-index.js +131 -0
  77. package/dist/square-legacy-framing.d.ts +9 -0
  78. package/dist/square-legacy-framing.js +56 -0
  79. package/dist/square-location.d.ts +15 -0
  80. package/dist/square-location.js +76 -0
  81. package/dist/square-migration-host-ledger.d.ts +26 -0
  82. package/dist/square-migration-host-ledger.js +168 -0
  83. package/dist/square-migration.d.ts +5 -5
  84. package/dist/square-migration.js +373 -71
  85. package/dist/square-sqlite.d.ts +9 -9
  86. package/dist/square-sqlite.js +329 -79
  87. package/dist/square-stdio.d.ts +22 -0
  88. package/dist/square-stdio.js +222 -0
  89. package/dist/square-tools.js +30 -3
  90. package/dist/stdio-server.d.ts +3 -0
  91. package/dist/stdio-server.js +192 -0
  92. package/dist/stream.d.ts +3 -2
  93. package/dist/stream.js +9 -10
  94. package/dist/views.d.ts +9 -14
  95. package/dist/views.js +50 -70
  96. package/dist/wakes.d.ts +6 -5
  97. package/dist/wakes.js +20 -17
  98. package/guides/application-facade-delivery-evidence.md +1 -1
  99. package/guides/backends.md +180 -0
  100. package/guides/claude-native.md +9 -9
  101. package/guides/mcp.md +1 -1
  102. package/guides/migration.md +107 -67
  103. package/guides/row-storage-design.md +1 -1
  104. package/guides/square-api.md +69 -30
  105. package/package.json +1 -1
  106. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  107. package/plugins/claude/hooks/register.js +1 -1
  108. package/plugins/claude/skills/square/SKILL.md +7 -7
  109. package/plugins/claude/types.d.ts +1 -1
  110. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  111. package/plugins/opencode/delivery.ts +170 -118
  112. package/plugins/opencode/index.ts +12 -30
  113. package/plugins/pi/square-pi.js +58 -20
  114. package/skills/square/SKILL.md +7 -7
  115. package/dist/cli/ledger-command.d.ts +0 -12
  116. package/dist/cli/ledger-command.js +0 -72
  117. package/dist/host-ledger-file-adapter.d.ts +0 -47
  118. package/dist/host-ledger-file-adapter.js +0 -384
  119. package/dist/host-ledger-migration.d.ts +0 -27
  120. package/dist/host-ledger-migration.js +0 -264
  121. package/dist/host-ledger-root.d.ts +0 -2
  122. package/dist/host-ledger-root.js +0 -9
  123. package/dist/host-ledger-sqlite.d.ts +0 -65
  124. package/dist/host-ledger-sqlite.js +0 -368
  125. package/dist/host-ledger.d.ts +0 -195
  126. package/dist/ledger-maintenance.d.ts +0 -30
  127. package/dist/ledger-maintenance.js +0 -110
  128. package/dist/open-square.d.ts +0 -36
  129. package/dist/open-square.js +0 -81
  130. package/dist/participant-host.d.ts +0 -22
  131. package/dist/participant-host.js +0 -92
  132. package/dist/registry.d.ts +0 -55
  133. package/dist/registry.js +0 -123
  134. package/dist/routes.d.ts +0 -39
  135. package/dist/routes.js +0 -99
  136. package/dist/square-actions.d.ts +0 -56
  137. package/dist/square-actions.js +0 -353
  138. package/dist/square-projections.d.ts +0 -31
  139. package/dist/square-projections.js +0 -64
  140. package/dist/wake-attempts.d.ts +0 -10
  141. package/dist/wake-attempts.js +0 -39
  142. package/dist/wake-eligibility.d.ts +0 -53
  143. package/dist/wake-eligibility.js +0 -123
  144. package/dist/wake-evidence.d.ts +0 -33
  145. package/dist/wake-evidence.js +0 -32
  146. package/guides/host-ledger.md +0 -58
  147. package/guides/indexed-host-ledger-performance.md +0 -39
  148. /package/dist/{host-ledger.js → square-backend.js} +0 -0
package/dist/ports.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import type { Act, HardCap, InboxNotification, StoredAct, WakeRoute, WakeRouteKind, WatchLease } from './model.js';
2
- export type { ClaimResult, EvidenceClaim, EvidenceRelease, EvidenceLookup, EvidenceRecord, NativeTokenLookup, NativeTokenResult, HostLedgerPort, PresenceKey, PresenceLookup, PresenceRecord, PresenceClaimResult, PresenceChannel, PresenceResult, } from './host-ledger.js';
3
- import type { HostLedgerPort } from './host-ledger.js';
1
+ import type { Act, ActivityId, Body, HardCap, Seat, StoredAct, WakeAddress, WakeRoute, WatchLease } from './model.js';
4
2
  export type ActivityIndex = number;
5
3
  export interface SquareHeader {
6
4
  readonly revision: number;
5
+ readonly deliveryRevision: number;
7
6
  readonly nextActIndex: number;
8
7
  readonly lastActIndex: number;
9
8
  readonly hardCap: HardCap;
@@ -88,19 +87,92 @@ export interface ReceiptAssignment {
88
87
  export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
89
88
  readonly [key: string]: JsonValue;
90
89
  };
91
- export interface RouteFact {
92
- readonly location: string;
90
+ export type SeatRef = Omit<Seat, 'wakeAddress'>;
91
+ export type ArtifactWriteAuthority = {
92
+ readonly kind: 'participant';
93
+ readonly seat: SeatRef;
94
+ } | {
95
+ readonly kind: 'join';
93
96
  readonly participant: string;
94
- readonly sessionId: string;
95
- readonly channel: string;
96
- readonly kind: WakeRouteKind;
97
- readonly address: Readonly<Record<string, string>>;
98
- readonly updatedAt: number;
99
- readonly epoch?: JsonValue;
100
- readonly extra: Readonly<Record<string, JsonValue>>;
97
+ readonly body: Body;
98
+ readonly wakeAddress?: WakeAddress;
99
+ } | {
100
+ readonly kind: 'takeover';
101
+ readonly participant: string;
102
+ readonly body: Body;
103
+ readonly expectedGeneration: ActivityId;
104
+ readonly wakeAddress?: WakeAddress;
105
+ };
106
+ export interface DeliveryEvidence {
107
+ readonly participant: string;
108
+ readonly generation: ActivityId;
109
+ readonly activity: ActivityId;
110
+ readonly kind: 'wake' | 'presentation';
111
+ readonly outcome: 'dispatching' | 'accepted' | 'failed' | 'unknown' | 'released' | 'presented' | 'clipped';
112
+ readonly attemptN: number;
113
+ readonly at: number;
114
+ readonly expiresAt?: number;
115
+ readonly claimToken?: string;
116
+ readonly route?: WakeAddress;
117
+ readonly signature?: string;
118
+ readonly message?: string;
119
+ readonly diagnostic?: import('./model.js').JsonValue;
120
+ readonly correlation?: {
121
+ readonly harness: 'claude' | 'opencode' | 'pi';
122
+ readonly token?: string;
123
+ readonly messageId?: string;
124
+ readonly payloadHash?: string;
125
+ };
126
+ readonly nativeObservations?: readonly {
127
+ readonly eventId: string;
128
+ readonly messageId: string;
129
+ readonly state: 'held' | 'released' | 'dropped';
130
+ readonly reason?: string;
131
+ readonly observedAt: number;
132
+ }[];
133
+ }
134
+ export interface DeliveryClaimInput {
135
+ readonly seat: SeatRef;
136
+ readonly activity: ActivityId;
137
+ readonly kind: 'wake' | 'presentation';
138
+ readonly route?: WakeAddress;
139
+ readonly at: number;
140
+ readonly leaseMs: number;
141
+ readonly claimToken: string;
142
+ }
143
+ export type DeliveryClaimResult = {
144
+ readonly status: 'acquired';
145
+ readonly claimToken: string;
146
+ readonly attemptN: number;
147
+ } | {
148
+ readonly status: 'busy' | 'terminal' | 'stale';
149
+ readonly evidence?: DeliveryEvidence;
150
+ };
151
+ export interface DeliveryPreparation {
152
+ readonly seat: SeatRef;
153
+ readonly claimToken: string;
154
+ readonly correlation: NonNullable<DeliveryEvidence['correlation']>;
155
+ }
156
+ export interface DeliveryCompletion {
157
+ readonly seat: SeatRef;
158
+ readonly activity: ActivityId;
159
+ readonly claimToken: string;
160
+ readonly outcome: Exclude<DeliveryEvidence['outcome'], 'dispatching'>;
161
+ readonly at: number;
162
+ readonly signature?: string;
163
+ readonly message?: string;
164
+ readonly diagnostic?: import('./model.js').JsonValue;
165
+ readonly correlation?: DeliveryEvidence['correlation'];
101
166
  }
102
167
  /** Domain facts exist only during a synchronous artifact callback. Values are detached. */
103
168
  export interface SquareRead {
169
+ seat(requestedName: string): Seat | undefined;
170
+ seatsForBody(body: Body): readonly Seat[];
171
+ /** Latest completed coordinates only; never participant write authority. */
172
+ completedSeatsForBody(body: Body): readonly Seat[];
173
+ deliveryEvidence(participant: string, generation: ActivityId, activity?: ActivityId): readonly DeliveryEvidence[];
174
+ deliveryByToken(claimToken: string): DeliveryEvidence | undefined;
175
+ cancelledThrough(participant: string, generation: ActivityId): number | undefined;
104
176
  header(): SquareHeader;
105
177
  participant(requestedName: string): ParticipantFact | undefined;
106
178
  participantCounts(): ParticipantCounts;
@@ -139,16 +211,24 @@ export interface SquareRead {
139
211
  receiptFacts(requestedName: string, afterIndex: number): ReceiptFacts;
140
212
  receiptAssignments(): readonly ReceiptAssignment[];
141
213
  lease(requestedName: string): WatchLease | undefined;
142
- routes(): readonly RouteFact[];
143
214
  }
144
215
  export interface SquareWrite extends SquareRead {
145
216
  land(activity: Act & {
146
217
  readonly at: number;
147
218
  }): StoredAct;
219
+ updateWakeAddress(seat: SeatRef, wakeAddress?: WakeAddress): boolean;
220
+ cancelThrough(seat: SeatRef, through: ActivityIndex): boolean;
221
+ claimDelivery(input: DeliveryClaimInput): DeliveryClaimResult;
222
+ prepareDelivery(input: DeliveryPreparation): boolean;
223
+ completeDelivery(input: DeliveryCompletion): boolean;
224
+ observeNativeDelivery(input: {
225
+ readonly seat: SeatRef;
226
+ readonly claimToken: string;
227
+ readonly observation: NonNullable<DeliveryEvidence['nativeObservations']>[number];
228
+ }): boolean;
148
229
  markSeen(requestedName: string, indexes: readonly ActivityIndex[], at: number): number;
149
- putLease(requestedName: string, lease: WatchLease): boolean;
150
- removeLease(requestedName: string, expectedLeaseId: string): boolean;
151
- replaceRoutes(routes: readonly RouteFact[]): boolean;
230
+ putLease(requestedName: string, lease: WatchLease, generation?: ActivityId): boolean;
231
+ removeLease(requestedName: string, expectedLeaseId: string, generation?: ActivityId): boolean;
152
232
  }
153
233
  export interface SquareReadResult<R> {
154
234
  readonly value: R;
@@ -160,8 +240,15 @@ export interface SquareCommitResult<R> extends SquareReadResult<R> {
160
240
  /** Atomic artifact access; no framing, SQL or lazy transaction capability escapes. */
161
241
  export interface SquareArtifactPort {
162
242
  read<R>(select: (facts: SquareRead) => R, signal?: AbortSignal): Promise<SquareReadResult<R>>;
163
- transact<R>(change: (facts: SquareWrite) => R, signal?: AbortSignal): Promise<SquareCommitResult<R>>;
243
+ /** Participant/domain writes must carry an explicit authority; no body-less mutation path exists. */
244
+ transactAs<R>(authority: ArtifactWriteAuthority, change: (facts: SquareWrite) => R, signal?: AbortSignal): Promise<SquareCommitResult<R>>;
164
245
  revision(signal?: AbortSignal): Promise<number>;
246
+ deliveryRevision(signal?: AbortSignal): Promise<number>;
247
+ claimDelivery(input: DeliveryClaimInput, signal?: AbortSignal): Promise<DeliveryClaimResult>;
248
+ prepareDelivery(input: DeliveryPreparation, signal?: AbortSignal): Promise<boolean>;
249
+ completeDelivery(input: DeliveryCompletion, signal?: AbortSignal): Promise<boolean>;
250
+ /** Persist suppression through a fixed index, or atomically capture the current artifact head. */
251
+ cancelDeliveries(seat: SeatRef, through?: ActivityIndex, signal?: AbortSignal): Promise<boolean>;
165
252
  changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
166
253
  close(): Promise<void>;
167
254
  }
@@ -175,46 +262,31 @@ export interface SquareResourceOwner {
175
262
  artifact(location: string): SquareArtifactPort;
176
263
  close(): Promise<void>;
177
264
  }
178
- /** Capability-neutral wake transport. Unused by this Contract's activity operations. */
265
+ /** Sole wake executor protocol; implementations own only transport readiness and sending. */
179
266
  export interface WakeTransportPort {
180
267
  /** Capability check that performs no external wake and writes no evidence. */
181
268
  probe?(route: WakeRoute): Promise<boolean | {
182
269
  readonly outcome: 'not-capable';
183
270
  readonly diagnostic?: string;
184
271
  }>;
185
- attempt(request: WakeRequest, timeoutMs: number, beforeSend?: () => Promise<boolean>): Promise<WakeOutcome>;
272
+ attempt(request: WakeRequest, timeoutMs: number, beforeSend: () => Promise<boolean>): Promise<WakeOutcome>;
186
273
  }
187
274
  export interface WakeRequest {
188
275
  readonly location: string;
189
276
  readonly participant: string;
190
277
  readonly activity: string;
191
278
  readonly actor: string;
192
- /** Wake attempt token, also the opaque native correlation. */
279
+ readonly seat: SeatRef;
193
280
  readonly claimToken?: string;
194
281
  readonly attemptN?: number;
195
282
  readonly route: WakeRoute;
283
+ /** Artifact context for native adapters that must persist correlation before I/O. */
284
+ readonly artifact?: SquareArtifactPort;
196
285
  }
197
286
  /** Executor-neutral presentation capability; intentionally separate from wake transport. */
198
287
  export interface PresentationSinkPort {
199
288
  present(activity: StoredAct): void | Promise<void>;
200
289
  }
201
- /** Stable projection of a host binding; storage rows never cross this boundary. */
202
- export interface SessionBindingProjection {
203
- readonly epoch?: number;
204
- readonly cancelledThrough?: number;
205
- readonly location: string;
206
- readonly participant: string;
207
- readonly sessionId: string;
208
- readonly channel: import('./host-ledger.js').PresenceChannel;
209
- readonly updatedAt: number;
210
- }
211
- /** Pending boundary presentation projected for one bound session. */
212
- export interface PresentationProjection {
213
- readonly binding: SessionBindingProjection;
214
- readonly joined: boolean;
215
- readonly notifications: readonly InboxNotification[];
216
- readonly catchLease?: WatchLease;
217
- }
218
290
  /** Read-only presentation evidence projection for diagnostics and migration callers. */
219
291
  export interface PresentationEvidenceProjection {
220
292
  readonly location: string;
@@ -256,7 +328,6 @@ export interface DeliveryResult {
256
328
  }
257
329
  export interface DeliverPendingInput {
258
330
  readonly artifact: SquareArtifactPort;
259
- readonly hostLedger: HostLedgerPort;
260
331
  readonly transport: WakeTransportPort;
261
332
  readonly location: string;
262
333
  readonly timeoutMs?: number;
@@ -273,8 +344,9 @@ export interface PresentPendingInput {
273
344
  readonly participant: string;
274
345
  readonly activity: string | number;
275
346
  readonly sink: PresentationSinkPort;
276
- readonly hostLedger?: HostLedgerPort;
277
- readonly session?: string;
347
+ /** Captured artifact seat; used to fence both presentation and consumption. */
348
+ readonly seat?: SeatRef;
349
+ readonly generation?: ActivityId;
278
350
  readonly now?: number;
279
351
  readonly timeoutMs?: number;
280
352
  /** One hook deadline propagated into artifact reads/transitions so busy retries stay bounded. */
@@ -1,3 +1,3 @@
1
1
  import type { PresentationResult, PresentPendingInput } from './ports.js';
2
- /** Presentation evidence is not a receipt; only the fenced receipt transaction marks seen. */
2
+ /** Presentation is external; its claim and receipt/evidence commit are generation-fenced in the artifact. */
3
3
  export declare function presentPending(input: PresentPendingInput): Promise<PresentationResult>;
@@ -1,56 +1,72 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { formatActivityId, parseActivityId } from './square-core.js';
3
+ import { isSquareError, sameBody } from './model.js';
2
4
  import { pendingHead, sameJoin } from './delivery.js';
3
- /** Presentation evidence is not a receipt; only the fenced receipt transaction marks seen. */
5
+ import { asSeatRef } from './seats.js';
6
+ function currentFor(input, captured, participant, index) {
7
+ return (facts) => {
8
+ const seat = facts.seat(captured.participant);
9
+ return seat !== undefined && sameBody(seat.body, captured.body) && seat.generation === captured.generation
10
+ && sameJoin(facts, participant) && input.current?.(facts) !== false
11
+ && pendingHead(facts, captured.participant, index) !== undefined;
12
+ };
13
+ }
14
+ /** Presentation is external; its claim and receipt/evidence commit are generation-fenced in the artifact. */
4
15
  export async function presentPending(input) {
5
16
  const index = typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
6
17
  if (index === undefined)
7
18
  return { presented: false };
8
- const before = await input.artifact.read((facts) => {
9
- if (input.current?.(facts) === false || !pendingHead(facts, input.participant, index))
19
+ const initial = await input.artifact.read((facts) => {
20
+ const participant = facts.participant(input.participant);
21
+ const seat = facts.seat(input.participant);
22
+ if (!participant?.joined || seat === undefined || input.generation !== undefined && input.generation !== seat.generation
23
+ || input.seat !== undefined && (input.seat.generation !== seat.generation || !sameBody(input.seat.body, seat.body))
24
+ || input.current?.(facts) === false || !pendingHead(facts, participant.name, index))
10
25
  return undefined;
11
- return { item: facts.activity(index), participant: facts.participant(input.participant) };
26
+ const item = facts.activity(index);
27
+ return item?.kind === 'say' ? { participant, seat, item } : undefined;
12
28
  }, input.signal).catch(() => undefined);
13
- if (before?.value === undefined)
29
+ if (initial?.value === undefined)
30
+ return { presented: false };
31
+ const { participant, seat, item } = initial.value;
32
+ const ref = asSeatRef(seat);
33
+ const current = currentFor(input, seat, participant, index);
34
+ const claimToken = randomUUID();
35
+ const claimed = await input.artifact.claimDelivery({ seat: ref, activity: formatActivityId(index), kind: 'presentation',
36
+ at: input.now ?? Date.now(), leaseMs: input.timeoutMs ?? 5000, claimToken }, input.signal).catch(() => ({ status: 'stale' }));
37
+ if (claimed.status !== 'acquired')
38
+ return { presented: false };
39
+ const release = async (outcome, message) => input.artifact.completeDelivery({
40
+ seat: ref, activity: formatActivityId(index), claimToken, outcome, at: Date.now(),
41
+ ...(message === undefined ? {} : { message }),
42
+ }, input.signal).catch(() => false);
43
+ const fresh = await input.artifact.read(current, input.signal).catch(() => undefined);
44
+ if (!fresh?.value) {
45
+ await release('released', 'The notification changed before presentation.');
14
46
  return { presented: false };
15
- const { item, participant } = before.value;
16
- const current = (facts) => sameJoin(facts, participant) && input.current?.(facts) !== false
17
- && pendingHead(facts, participant.name, index) !== undefined;
18
- const coordinate = { location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation' };
19
- let claimToken;
20
- const release = async () => {
21
- if (claimToken !== undefined)
22
- await input.hostLedger.releaseEvidence({ ...coordinate, claimToken });
23
- };
24
- if (input.hostLedger !== undefined && input.session !== undefined) {
25
- const claim = await input.hostLedger.claimEvidence({ ...coordinate, leaseMs: input.timeoutMs ?? 5000 });
26
- if (claim.status !== 'acquired')
27
- return { presented: false };
28
- claimToken = claim.claimToken;
29
- const fresh = await input.artifact.read(current, input.signal).catch(() => undefined);
30
- if (!fresh?.value) {
31
- await release().catch(() => undefined);
32
- return { presented: false };
33
- }
34
47
  }
35
48
  try {
36
49
  await input.sink.present(item);
37
50
  }
38
51
  catch (error) {
39
- if (claimToken !== undefined)
40
- await input.hostLedger.appendEvidence({ ...coordinate, outcome: 'failed', message: error instanceof Error ? error.message : String(error), claimToken });
52
+ await release('failed', error instanceof Error ? error.message : String(error));
41
53
  throw error;
42
54
  }
43
- const { value: committed } = await input.artifact.transact((facts) => {
55
+ const outcome = input.markSeen === false ? 'clipped' : 'presented';
56
+ const { value: committed } = await input.artifact.transactAs({ kind: 'participant', seat: ref }, (facts) => {
44
57
  if (!current(facts))
45
58
  return false;
46
- return input.markSeen === false || facts.markSeen(participant.name, [index], input.now ?? Date.now()) > 0;
47
- }, input.signal);
48
- if (!committed) {
49
- await release();
59
+ if (!facts.completeDelivery({ seat: ref, activity: formatActivityId(index), claimToken, outcome, at: input.now ?? Date.now() }))
60
+ return false;
61
+ if (input.markSeen === false)
62
+ return true;
63
+ return facts.markSeen(participant.name, [index], input.now ?? Date.now()) > 0;
64
+ }, input.signal).catch(error => {
65
+ if (isSquareError(error) && error.code === 'already_joined')
66
+ return { value: false };
67
+ throw error;
68
+ });
69
+ if (!committed)
50
70
  return { presented: false };
51
- }
52
- if (claimToken !== undefined)
53
- await input.hostLedger.appendEvidence({ ...coordinate, outcome: input.markSeen === false ? 'clipped' : 'presented',
54
- ...(input.markSeen === false ? { message: 'presentation clipped' } : {}), claimToken });
55
71
  return { presented: true, activity: item };
56
72
  }
@@ -22,7 +22,6 @@ interface ActivityLimitOptions extends ParticipantOutputOptions {
22
22
  }
23
23
  interface ActivityBlockedOptions extends ParticipantOutputOptions {
24
24
  retryCommand: string;
25
- forceCommand: string;
26
25
  activitySummaries: readonly UnreadActivitySummary[];
27
26
  draftPath?: string;
28
27
  }
@@ -9,6 +9,8 @@ import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
9
9
  import { grepSnippet } from './search.js';
10
10
  import { style } from './tty-style.js';
11
11
  export function displayPath(squarePath, cwd = process.cwd()) {
12
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(squarePath))
13
+ return squarePath;
12
14
  if (!path.isAbsolute(squarePath))
13
15
  return squarePath.split(path.sep).join('/');
14
16
  const comparableCwd = fs.realpathSync.native(cwd);
@@ -49,7 +51,7 @@ export function commandPrefix(squarePath) {
49
51
  return `square --location ${quoteShell(squarePath)}`;
50
52
  }
51
53
  export function participantCommandPrefix(squarePath, name) {
52
- return `square --location ${quoteShell(path.resolve(squarePath))} --as ${quoteShell(name)}`;
54
+ return `square --location ${quoteShell(/^[A-Za-z][A-Za-z0-9+.-]*:/.test(squarePath) ? squarePath : path.resolve(squarePath))} --as ${quoteShell(name)}`;
53
55
  }
54
56
  /** Blocked actions end with a full copy-pasteable recovery command. */
55
57
  export function joinRecoveryCommand(squarePath, name) {
@@ -166,13 +168,16 @@ function renderEventCliFacts(event, opts = {}) {
166
168
  return { text: `· ${renderRoomChangeText(event)}`, bodyClipped: false };
167
169
  case 'say': {
168
170
  const body = renderedBody(event.body, maxBody);
171
+ const visibleBody = body.text === '' && event.metadata !== undefined
172
+ ? { text: '(structured activity)', clipped: false }
173
+ : body;
169
174
  const mention = opts.mention;
170
175
  const mentionSuffix = mention !== undefined && (event.reach === 'bell' || event.mentions?.some((name) => sameName(name, mention)))
171
176
  ? ` · calls your name across the square — ${participantIdentity(mention)}`
172
177
  : '';
173
178
  const replySuffix = event.reply === undefined ? '' : ` · replies to ${replyId(event.reply)}`;
174
- const meta = ` #${opts.actNumber ?? 1} · ${eventId(event)} · ${formatRelativeTime(event.at, now)}${mentionSuffix}${replySuffix}`;
175
- return { text: `● ${participantIdentity(event.actor)}${style('dim', meta)}${bodySuffix(body.text)}`, bodyClipped: body.clipped };
179
+ const meta = ` · ${eventId(event)} · ${formatRelativeTime(event.at, now)}${mentionSuffix}${replySuffix}`;
180
+ return { text: `● ${participantIdentity(event.actor)}${style('dim', meta)}${bodySuffix(visibleBody.text)}`, bodyClipped: visibleBody.clipped };
176
181
  }
177
182
  case 'done': {
178
183
  const body = renderedBody(event.body, maxBody);
@@ -190,7 +195,7 @@ export function renderEventCli(event, opts = {}) {
190
195
  }
191
196
  function renderPresenceOnlySay(event, opts = {}) {
192
197
  const targets = event.reach === 'bell' ? [] : event.mentions ?? [];
193
- const meta = style('dim', ` #${opts.actNumber ?? 1} · ${eventId(event)} · ${formatRelativeTime(event.at, opts.now)}`);
198
+ const meta = style('dim', ` · ${eventId(event)} · ${formatRelativeTime(event.at, opts.now)}`);
194
199
  if (targets.length === 0) {
195
200
  return `● ${participantIdentity(event.actor)}${meta}`;
196
201
  }
@@ -236,8 +241,10 @@ function renderUnreadSummary(opts) {
236
241
  if (preview.perception === 'presence') {
237
242
  return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · ${rendered.replace(/\n/g, ' ')}`);
238
243
  }
239
- const compact = preview.act.body.replace(/\s+/g, ' ').trim();
240
- const bodyPreview = compact === '' ? { text: '(empty)', remaining: 0 } : truncateChars(compact, UNREAD_PREVIEW_CHARS);
244
+ const compact = (preview.act.body ?? '').replace(/\s+/g, ' ').trim();
245
+ const bodyPreview = compact === ''
246
+ ? { text: preview.act.metadata === undefined ? '(empty)' : '(structured activity)', remaining: 0 }
247
+ : truncateChars(compact, UNREAD_PREVIEW_CHARS);
241
248
  const bodyText = bodyPreview.remaining === 0 ? bodyPreview.text : `${bodyPreview.text}${style('dim', `… (+${bodyPreview.remaining} chars)`)}`;
242
249
  return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${bodyText}"`);
243
250
  }),
@@ -251,14 +258,12 @@ export function renderPendingFeed(publicItems, sayNumbers, perceptions, viewer)
251
258
  export function renderActivityBlocked(opts) {
252
259
  const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} catch`;
253
260
  return withPathOutput(opts.squarePath, [
254
- `${style('blocked', '✕')} your activity doesn't land — the square moved behind your back`,
261
+ `${style('blocked', '✕')} your activity hasn't landed — someone spoke while you were away`,
255
262
  ...renderUnreadSummary({ activitySummaries: opts.activitySummaries, viewer: opts.name }),
256
263
  ...draftSavedLines(opts.draftPath),
257
264
  `${readNowCommand}`,
258
265
  ' · take it in, then retry:',
259
266
  withDraftInput(opts.retryCommand, opts.draftPath),
260
- ' · optional: express over unread activity with --force:',
261
- withDraftInput(opts.forceCommand, opts.draftPath),
262
267
  ].join('\n'), { participantCount: opts.participantCount, held: opts.held });
263
268
  }
264
269
  /** An indeterminate or already-committed send only points to inspection, never resend. */
@@ -279,14 +284,14 @@ function holdReasonLines(reason) {
279
284
  return text === '' ? [] : [style('dim', ` · ${text}`)];
280
285
  }
281
286
  function heldMainLine(holder, styled) {
282
- const glyph = styled ? style('blocked', '✕') : '✕';
287
+ const glyph = styled ? style('blocked', '✕') : '·';
283
288
  const who = holder === undefined ? 'a hand is raised' : `${participantIdentity(holder)} has a hand raised`;
284
289
  return `${glyph} ${who} — voices drop, yours too`;
285
290
  }
286
291
  export function renderExpressWaiting(opts) {
287
292
  if (opts.reason === 'throttled') {
288
293
  return [
289
- '✕ the square is packed — shoulder to shoulder',
294
+ '· the square is packed — your activity is waiting',
290
295
  ` · a lull opens in ${formatDuration(opts.delayMs)}`,
291
296
  ' · --no-wait saves a draft and returns now',
292
297
  ].join('\n');
@@ -370,7 +375,7 @@ export function renderActivityLimit(opts) {
370
375
  const spoken = opts.count !== undefined && opts.hardCap !== undefined ? ` — ${opts.count}/${opts.hardCap} spoken` : '';
371
376
  const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} done`;
372
377
  return withPathOutput(opts.squarePath, [
373
- `${style('blocked', '✕')} nothing left in you${spoken}`,
378
+ `${style('blocked', '✕')} you’ve reached your speaking cap${spoken}`,
374
379
  ...draftSavedLines(opts.draftPath),
375
380
  ' · your draft stays unsent; done only steps out',
376
381
  doneCommand,
@@ -425,7 +430,7 @@ export function renderWatchStatus(opts) {
425
430
  const spoken = opts.hardCap === undefined || opts.hardCap === null
426
431
  ? ''
427
432
  : ` — ${opts.ownActivityCount ?? 0}/${opts.hardCap} spoken`;
428
- return [`${style('blocked', '✕')} nothing left in you${spoken}`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
433
+ return [`${style('blocked', '✕')} you’ve reached your speaking cap${spoken}`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
429
434
  }
430
435
  }
431
436
  }
@@ -0,0 +1,7 @@
1
+ import { type Body, type Seat } from './model.js';
2
+ import type { ArtifactWriteAuthority, SeatRef } from './ports.js';
3
+ export declare function requireBody(body: Body | undefined): Body;
4
+ export declare function requireJoinName(name: string | undefined): string;
5
+ export declare function asSeatRef(seat: Seat): SeatRef;
6
+ export declare function participantAuthority(seat: Seat): ArtifactWriteAuthority;
7
+ export declare function requireSeat(seat: Seat | undefined, body: Body | undefined, participant: string): Seat;
package/dist/seats.js ADDED
@@ -0,0 +1,27 @@
1
+ import { SquareError, sameBody, sameName, validateBody, validateName } from './model.js';
2
+ export function requireBody(body) {
3
+ if (body === undefined)
4
+ throw new SquareError('invalid_args', 'Participant writes require an explicit body: channel and session.');
5
+ validateBody(body);
6
+ return body;
7
+ }
8
+ export function requireJoinName(name) {
9
+ if (name === undefined)
10
+ throw new SquareError('invalid_args', 'Square.join requires an explicit participant name.');
11
+ validateName(name);
12
+ return name;
13
+ }
14
+ export function asSeatRef(seat) {
15
+ const { wakeAddress: _wakeAddress, ...ref } = seat;
16
+ return ref;
17
+ }
18
+ export function participantAuthority(seat) {
19
+ return { kind: 'participant', seat: asSeatRef(seat) };
20
+ }
21
+ export function requireSeat(seat, body, participant) {
22
+ if (seat === undefined || !sameName(seat.participant, participant))
23
+ throw new SquareError('already_joined', `${participant} needs a current participant handle; join first`);
24
+ if (!sameBody(seat.body, requireBody(body)))
25
+ throw new SquareError('already_joined', `${participant} is not held by this body`);
26
+ return seat;
27
+ }
@@ -1,3 +1,3 @@
1
- import { type AutomaticProvider } from './participant-identity.js';
2
- /** Retire only memberships owned at this session's end, regardless of square filename or cwd. */
1
+ import type { AutomaticProvider } from './participant-identity.js';
2
+ /** Retire only seats held by this exact body, discovered through the rebuildable index. */
3
3
  export declare function endSessionMemberships(provider: AutomaticProvider, sessionId: string, env?: NodeJS.ProcessEnv): Promise<void>;
@@ -1,18 +1,16 @@
1
1
  import { Square } from './square-api.js';
2
- import { operationEnv } from './participant-identity.js';
3
- import { hostLedgerForEnv } from './registry.js';
4
- import { projectSessionBindings } from './square-projections.js';
5
- /** Retire only memberships owned at this session's end, regardless of square filename or cwd. */
2
+ import { projectSessionSeats } from './inbox.js';
3
+ function bodyFor(provider, sessionId) {
4
+ return { channel: provider === 'claude' ? 'claude-code' : provider, session: sessionId };
5
+ }
6
+ /** Retire only seats held by this exact body, discovered through the rebuildable index. */
6
7
  export async function endSessionMemberships(provider, sessionId, env = process.env) {
7
- const scoped = operationEnv(provider, sessionId, env);
8
- const hostLedger = hostLedgerForEnv(scoped);
9
- const bindings = await projectSessionBindings({ hostLedger, sessionId });
10
- for (const binding of bindings) {
11
- const square = await Square.at({ path: binding.location, hostLedger, env: scoped });
8
+ const body = bodyFor(provider, sessionId);
9
+ const seats = await projectSessionSeats({ body, env });
10
+ for (const { location, seat } of seats) {
11
+ const square = await Square.at({ path: location, env, body, participant: seat.participant });
12
12
  try {
13
- // The captured epoch fences a same-session rejoin as well as a foreign takeover.
14
- // endOwnedSession also settles routes and presence under the ownership claim lock.
15
- await square.endOwnedSession(binding.participant, sessionId, binding.epoch ?? 0);
13
+ await square.endOwnedSession(seat.participant, sessionId, seat.generation, body);
16
14
  }
17
15
  finally {
18
16
  await square.close();
@@ -0,0 +1,53 @@
1
+ import { type Body } from './model.js';
2
+ import type { ActivityResult, HistoryQuery, HistoryResult, JoinOptions, JoinResult, ObservationOptions, OperationControl, Participant, SquareAtInput, SquareBuildInput, SquareLocation, StatusOptions, StatusResult, ParticipantsResult, RecoveryResult, DiscoveryOptions, InboxOptions, InboxResult, StreamOptions } from './square-facade.js';
3
+ export type * from './square-facade.js';
4
+ export { createSquareResourceOwner } from './artifact.js';
5
+ export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
6
+ export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
7
+ export { HELD_WAIT_BUDGET_MS } from './runtime.js';
8
+ export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
9
+ export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
10
+ export declare function squareClock(env: NodeJS.ProcessEnv, clock?: () => number): () => number;
11
+ /** One concrete local resource, including its entry-resolved dependencies and lifetime. */
12
+ export declare class SqliteSquare {
13
+ readonly location: string;
14
+ private readonly artifact;
15
+ private readonly clock;
16
+ private readonly body?;
17
+ private readonly wakeAddress?;
18
+ private readonly wakeTransport?;
19
+ private readonly indexPath?;
20
+ private readonly automaticDelivery;
21
+ private readonly ownsArtifact;
22
+ private readonly defaultParticipant?;
23
+ private constructor();
24
+ static at(input: SquareAtInput, control?: OperationControl, automaticDelivery?: boolean): Promise<SqliteSquare>;
25
+ static build(input: SquareBuildInput, control?: OperationControl, automaticDelivery?: boolean): Promise<SqliteSquare>;
26
+ static inMemory(input: Omit<SquareBuildInput, 'path' | 'resources'>): SqliteSquare;
27
+ /** Discovery is best effort and never grants write authority. Finish it before returning. */
28
+ private publishIdentity;
29
+ private observation;
30
+ private contextObservation;
31
+ private handle;
32
+ private static readonly ParticipantHandle;
33
+ participant(requested?: string): Promise<Participant>;
34
+ join(requested?: string, options?: JoinOptions, control?: OperationControl): Promise<JoinResult>;
35
+ implicitJoin(name: string, control?: OperationControl): Promise<{
36
+ readonly state: 'joined' | 'active' | 'done';
37
+ readonly participant?: Participant;
38
+ }>;
39
+ snapshot(control?: OperationControl): Promise<import("./square-facade.js").SquareSnapshot>;
40
+ history(query?: HistoryQuery, control?: OperationControl): Promise<HistoryResult>;
41
+ status(options?: StatusOptions, control?: OperationControl): Promise<StatusResult>;
42
+ participants(options?: ObservationOptions, control?: OperationControl): Promise<ParticipantsResult>;
43
+ recovery(requested?: string, control?: OperationControl): Promise<RecoveryResult>;
44
+ stream(options: StreamOptions, control?: OperationControl): AsyncIterable<import("./square-facade.js").StreamRecord>;
45
+ recognize(): Promise<readonly Participant[]>;
46
+ close(): Promise<void>;
47
+ cancelDeliveries(name: string, expectedGeneration: import('./square-core.js').ActivityId): Promise<boolean>;
48
+ endOwnedSession(name: string, sessionId: string, expectedGeneration?: import('./square-core.js').ActivityId, body?: Body): Promise<ActivityResult | null>;
49
+ }
50
+ export declare function diagnoseSquare(input: SquareLocation, control?: OperationControl): Promise<import("./artifact.js").DiagnoseResult>;
51
+ export declare function migrateSquare(input: SquareLocation, control?: OperationControl): Promise<import("./square-migration.js").MigrationOutcome>;
52
+ export declare function discover(input: SquareAtInput, options?: DiscoveryOptions, control?: OperationControl): Promise<import("./square-facade.js").DiscoveryResult>;
53
+ export declare function inbox(input: SquareAtInput, identity: string | Body, options?: InboxOptions, control?: OperationControl): Promise<InboxResult>;