@astrosheep/square 0.3.93 → 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 (116) 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/help.js +12 -10
  36. package/dist/inbox.d.ts +9 -22
  37. package/dist/inbox.js +23 -27
  38. package/dist/index.d.ts +1 -3
  39. package/dist/index.js +1 -1
  40. package/dist/mcp.d.ts +3 -0
  41. package/dist/mcp.js +23 -24
  42. package/dist/model.d.ts +8 -15
  43. package/dist/model.js +4 -7
  44. package/dist/notifications.d.ts +1 -1
  45. package/dist/notifications.js +6 -5
  46. package/dist/operation-presentation.d.ts +5 -4
  47. package/dist/operation-presentation.js +50 -45
  48. package/dist/owner.d.ts +11 -0
  49. package/dist/owner.js +32 -0
  50. package/dist/paseo-delivery.d.ts +4 -1
  51. package/dist/paseo-delivery.js +7 -12
  52. package/dist/paseo-state.d.ts +2 -2
  53. package/dist/paseo-state.js +6 -4
  54. package/dist/plugins/opencode/delivery.js +27 -15
  55. package/dist/ports.d.ts +13 -33
  56. package/dist/presentation-operations.js +4 -4
  57. package/dist/presentation.d.ts +5 -8
  58. package/dist/presentation.js +33 -43
  59. package/dist/runtime.d.ts +1 -7
  60. package/dist/runtime.js +0 -14
  61. package/dist/seats.d.ts +2 -3
  62. package/dist/seats.js +7 -15
  63. package/dist/session-lifecycle.d.ts +6 -2
  64. package/dist/session-lifecycle.js +39 -11
  65. package/dist/sqlite-square.d.ts +36 -22
  66. package/dist/sqlite-square.js +138 -161
  67. package/dist/square-api.d.ts +15 -10
  68. package/dist/square-api.js +26 -22
  69. package/dist/square-core.d.ts +0 -5
  70. package/dist/square-core.js +0 -9
  71. package/dist/square-domain.d.ts +4 -2
  72. package/dist/square-domain.js +56 -21
  73. package/dist/square-facade.d.ts +45 -87
  74. package/dist/square-index.d.ts +5 -13
  75. package/dist/square-index.js +13 -38
  76. package/dist/square-location.d.ts +1 -14
  77. package/dist/square-location.js +4 -70
  78. package/dist/square-migration-host-ledger.d.ts +2 -2
  79. package/dist/square-migration-host-ledger.js +6 -5
  80. package/dist/square-migration.d.ts +2 -1
  81. package/dist/square-migration.js +52 -30
  82. package/dist/square-sqlite.d.ts +6 -2
  83. package/dist/square-sqlite.js +54 -28
  84. package/dist/square-tools.d.ts +7 -10
  85. package/dist/square-tools.js +22 -54
  86. package/dist/stream.js +4 -5
  87. package/dist/views.d.ts +21 -10
  88. package/dist/views.js +38 -38
  89. package/dist/wakes.d.ts +1 -2
  90. package/dist/wakes.js +1 -1
  91. package/guides/application-facade-delivery-evidence.md +1 -1
  92. package/guides/claude-native.md +18 -9
  93. package/guides/codex-delivery.md +17 -6
  94. package/guides/mcp-connection-lifetime.md +1 -1
  95. package/guides/mcp.md +3 -3
  96. package/guides/migration.md +142 -151
  97. package/guides/operation-cancellation.md +1 -1
  98. package/guides/participant-ownership.md +78 -47
  99. package/guides/pi-tools.md +33 -4
  100. package/guides/primitive-history-consolidation.md +1 -1
  101. package/guides/row-storage-design.md +8 -8
  102. package/guides/square-api.md +244 -88
  103. package/package.json +1 -1
  104. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  105. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  106. package/plugins/opencode/delivery.ts +27 -17
  107. package/plugins/paseo/client/palette.ts +18 -18
  108. package/plugins/paseo/client/square-message.tsx +17 -23
  109. package/plugins/pi/square-pi.js +17 -17
  110. package/dist/square-backend.d.ts +0 -13
  111. package/dist/square-backend.js +0 -1
  112. package/dist/square-stdio.d.ts +0 -22
  113. package/dist/square-stdio.js +0 -235
  114. package/dist/stdio-server.d.ts +0 -3
  115. package/dist/stdio-server.js +0 -212
  116. package/guides/backends.md +0 -193
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { formatActivityId, parseActivityId } from './square-core.js';
3
- import { isSquareError, sameBody } from './model.js';
3
+ import { isSquareError } from './model.js';
4
4
  import { pendingHead, sameJoin } from './delivery.js';
5
5
  import { deliveryDeferralExpiry } from './delivery-deferral.js';
6
6
  import { asSeatRef } from './seats.js';
@@ -12,8 +12,8 @@ export async function preparePendingPresentation(input) {
12
12
  const initial = await input.artifact.read((facts) => {
13
13
  const participant = facts.participant(input.participant);
14
14
  const seat = facts.seat(input.participant);
15
- if (!participant?.joined || seat === undefined || input.generation !== undefined && input.generation !== seat.generation
16
- || input.seat !== undefined && (input.seat.generation !== seat.generation || !sameBody(input.seat.body, seat.body))
15
+ if (!participant?.joined || seat === undefined || seat.attachment !== 'attached' || input.generation !== undefined && input.generation !== seat.generation
16
+ || input.seat !== undefined && (input.seat.generation !== seat.generation || input.seat.owner !== seat.owner)
17
17
  || input.current?.(facts) === false || !pendingHead(facts, participant.name, index)
18
18
  || deliveryDeferralExpiry(facts, seat, index, input.now ?? Date.now()) !== undefined)
19
19
  return undefined;
@@ -26,7 +26,7 @@ export async function preparePendingPresentation(input) {
26
26
  const ref = asSeatRef(seat);
27
27
  const owns = (facts) => {
28
28
  const current = facts.seat(seat.participant);
29
- return current !== undefined && sameBody(current.body, seat.body) && current.generation === seat.generation
29
+ return current !== undefined && current.attachment === 'attached' && current.owner === seat.owner && current.generation === seat.generation
30
30
  && sameJoin(facts, participant);
31
31
  };
32
32
  const claimToken = randomUUID();
@@ -2,7 +2,7 @@ import { type StoredAct, type RoomChangeAct } from './model.js';
2
2
  export { participantIdentity } from './participant-identity.js';
3
3
  import { type Perception } from './square-core.js';
4
4
  import type { Activity, UnreadActivitySummary, ParticipantSummary } from './square-facade.js';
5
- export type WatchStatus = 'stale' | 'empty-now' | 'quorum' | 'capped';
5
+ export type WatchStatus = 'idle' | 'drained' | 'quorum' | 'capped';
6
6
  interface HeaderOptions {
7
7
  participantCount?: number;
8
8
  held?: boolean;
@@ -10,6 +10,7 @@ interface HeaderOptions {
10
10
  plain?: boolean;
11
11
  }
12
12
  interface ParticipantOutputOptions {
13
+ owner?: string;
13
14
  squarePath: string;
14
15
  name: string;
15
16
  participantCount?: number;
@@ -60,11 +61,11 @@ export declare function formatRefusal(squarePath: string, bodyLines: string[], o
60
61
  export declare function refusalText(error: unknown): string;
61
62
  export declare function quoteShell(value: string): string;
62
63
  export declare function commandPrefix(squarePath: string): string;
63
- export declare function participantCommandPrefix(squarePath: string, name: string): string;
64
+ export declare function participantCommandPrefix(squarePath: string, name: string, owner?: string): string;
64
65
  /** Blocked actions end with a full copy-pasteable recovery command. */
65
- export declare function joinRecoveryCommand(squarePath: string, name: string): string;
66
+ export declare function joinRecoveryCommand(squarePath: string, name: string, owner?: string): string;
66
67
  /** Taking a name is an explicit recovery choice, never an automatic retry. */
67
- export declare function takeoverRecoveryLines(squarePath: string, name: string): string[];
68
+ export declare function takeoverRecoveryLines(squarePath: string, name: string, owner?: string): string[];
68
69
  export declare function participantsRecoveryCommand(squarePath: string): string;
69
70
  export declare function renderPresenceLines(recent: readonly ParticipantSummary[], now: number, limit?: number): string[];
70
71
  export declare function truncateChars(body: string, maxChars: number): {
@@ -81,14 +82,12 @@ interface RenderedEvent {
81
82
  export declare function renderEventCli(event: StoredAct | Activity, opts?: {
82
83
  now?: number;
83
84
  preview?: number;
84
- actNumber?: number;
85
85
  mention?: string;
86
86
  }): string;
87
87
  export declare function renderAmbientEventFacts(event: StoredAct | Activity, viewer: string, opts?: {
88
88
  now?: number;
89
89
  preview?: number;
90
90
  presencePreview?: number;
91
- actNumber?: number;
92
91
  mention?: string;
93
92
  perception?: Perception;
94
93
  }): RenderedEvent;
@@ -96,11 +95,9 @@ export declare function renderAmbientEvent(event: StoredAct | Activity, viewer:
96
95
  now?: number;
97
96
  preview?: number;
98
97
  presencePreview?: number;
99
- actNumber?: number;
100
98
  mention?: string;
101
99
  perception?: Perception;
102
100
  }): string;
103
- export declare function renderPendingFeed(publicItems: readonly StoredAct[], sayNumbers: Readonly<Record<number, number>>, perceptions: Readonly<Record<number, Perception>>, viewer: string): string;
104
101
  export declare function renderActivityBlocked(opts: ActivityBlockedOptions): string;
105
102
  /** An indeterminate or already-committed send only points to inspection, never resend. */
106
103
  export declare function renderActivityUncertain(opts: ParticipantOutputOptions & {
@@ -50,18 +50,18 @@ export function quoteShell(value) {
50
50
  export function commandPrefix(squarePath) {
51
51
  return `square --location ${quoteShell(squarePath)}`;
52
52
  }
53
- export function participantCommandPrefix(squarePath, name) {
54
- return `square --location ${quoteShell(/^[A-Za-z][A-Za-z0-9+.-]*:/.test(squarePath) ? squarePath : path.resolve(squarePath))} --as ${quoteShell(name)}`;
53
+ export function participantCommandPrefix(squarePath, name, owner) {
54
+ return `square --location ${quoteShell(/^[A-Za-z][A-Za-z0-9+.-]*:/.test(squarePath) ? squarePath : path.resolve(squarePath))} --as ${quoteShell(name)}${owner === undefined ? '' : ` --owner ${quoteShell(owner)}`}`;
55
55
  }
56
56
  /** Blocked actions end with a full copy-pasteable recovery command. */
57
- export function joinRecoveryCommand(squarePath, name) {
58
- return `${participantCommandPrefix(squarePath, name)} join`;
57
+ export function joinRecoveryCommand(squarePath, name, owner) {
58
+ return `${participantCommandPrefix(squarePath, name, owner)} join`;
59
59
  }
60
60
  /** Taking a name is an explicit recovery choice, never an automatic retry. */
61
- export function takeoverRecoveryLines(squarePath, name) {
61
+ export function takeoverRecoveryLines(squarePath, name, owner) {
62
62
  return [
63
63
  'If this is your name to reclaim, --kick banishes the one standing here so you can step in:',
64
- `${participantCommandPrefix(squarePath, name)} join --kick`,
64
+ `${participantCommandPrefix(squarePath, name, owner)} join --kick`,
65
65
  ];
66
66
  }
67
67
  export function participantsRecoveryCommand(squarePath) {
@@ -79,13 +79,14 @@ export function renderPresenceLines(recent, now, limit = 5) {
79
79
  if (shown.length === 0)
80
80
  return [style('dim', ' ○ nobody nearby')];
81
81
  const lines = shown.map((p) => {
82
- const glyph = p.state === 'done' ? '○' : p.presence === 'watching' ? '◎' : p.lastActiveAt !== undefined ? '●' : '○';
83
- const text = p.state === 'done'
82
+ const glyph = p.presence === 'done' || p.presence === 'away' ? '○' : p.presence === 'watching' ? '◎' : p.lastActiveAt !== undefined ? '●' : '○';
83
+ const text = p.presence === 'done'
84
84
  ? (p.lastActiveAt === undefined ? 'stepped out of the square' : `stepped out of the square · ${formatRelativeTime(p.lastActiveAt, now)}`)
85
- : p.presence === 'watching'
86
- ? (p.lastActiveAt === undefined ? 'is nearby · catching' : `is nearby · catching · ${formatRelativeTime(p.lastActiveAt, now)}`)
87
- : p.lastActiveAt === undefined ? 'is nearby · quiet'
88
- : p.activityCount > 0 ? `is nearby · ${formatRelativeTime(p.lastActiveAt, now)}` : `is nearby · quiet · ${formatRelativeTime(p.lastActiveAt, now)}`;
85
+ : p.presence === 'away' ? 'is away'
86
+ : p.presence === 'watching'
87
+ ? (p.lastActiveAt === undefined ? 'is nearby · catching' : `is nearby · catching · ${formatRelativeTime(p.lastActiveAt, now)}`)
88
+ : p.lastActiveAt === undefined ? 'is nearby · quiet'
89
+ : p.activityCount > 0 ? `is nearby · ${formatRelativeTime(p.lastActiveAt, now)}` : `is nearby · quiet · ${formatRelativeTime(p.lastActiveAt, now)}`;
89
90
  return ` ${glyph === '○' ? style('dim', glyph) : glyph} ${participantIdentity(p.name)}${style('dim', ` ${text}`)}`;
90
91
  });
91
92
  const remaining = recent.length - shown.length;
@@ -229,37 +230,26 @@ function draftSavedLines(draftPath) {
229
230
  function withDraftInput(command, draftPath) {
230
231
  return draftPath === undefined ? command : `${command} < ${quoteShell(draftPath)}`;
231
232
  }
232
- function renderUnreadSummary(opts) {
233
- const visibleSummaries = opts.activitySummaries.slice(0, MAX_IDENTITY_SET_SIZE);
234
- const remainingSummaries = opts.activitySummaries.length - visibleSummaries.length;
233
+ function renderUnreadSummary(activitySummaries) {
234
+ const visibleSummaries = activitySummaries.slice(0, MAX_IDENTITY_SET_SIZE);
235
+ const remainingSummaries = activitySummaries.length - visibleSummaries.length;
235
236
  return [
236
- ...visibleSummaries.flatMap((item) => [
237
- ...item.previews.slice(-1).map((preview) => {
238
- const rendered = renderAmbientEvent(preview.act, opts.viewer, { actNumber: preview.number, perception: preview.perception });
239
- if (rendered === '')
240
- return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago`);
241
- if (preview.perception === 'presence') {
242
- return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · ${rendered.replace(/\n/g, ' ')}`);
243
- }
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);
248
- const bodyText = bodyPreview.remaining === 0 ? bodyPreview.text : `${bodyPreview.text}${style('dim', `… (+${bodyPreview.remaining} chars)`)}`;
249
- return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${bodyText}"`);
250
- }),
251
- ]),
237
+ ...visibleSummaries.map((item) => {
238
+ const compact = (item.preview.body ?? '').replace(/\s+/g, ' ').trim();
239
+ const bodyPreview = compact === ''
240
+ ? { text: item.preview.metadata === undefined ? '(empty)' : '(structured activity)', remaining: 0 }
241
+ : truncateChars(compact, UNREAD_PREVIEW_CHARS);
242
+ const bodyText = bodyPreview.remaining === 0 ? bodyPreview.text : `${bodyPreview.text}${style('dim', `… (+${bodyPreview.remaining} chars)`)}`;
243
+ return style('dim', ` · ${participantIdentity(item.name)} spoke — ${formatAge(item.latestActivityAgeMs)} ago · "${bodyText}"`);
244
+ }),
252
245
  ...(remainingSummaries === 0 ? [] : [style('dim', ` · ${remainingSummaries} more participants have unread activity`)]),
253
246
  ];
254
247
  }
255
- export function renderPendingFeed(publicItems, sayNumbers, perceptions, viewer) {
256
- return publicItems.map((act) => renderAmbientEvent(act, viewer, { actNumber: sayNumbers[act.index], perception: perceptions[act.index] })).filter(Boolean).join('\n\n');
257
- }
258
248
  export function renderActivityBlocked(opts) {
259
- const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} catch`;
249
+ const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)} catch`;
260
250
  return withPathOutput(opts.squarePath, [
261
251
  `${style('blocked', '✕')} your activity hasn't landed — someone spoke while you were away`,
262
- ...renderUnreadSummary({ activitySummaries: opts.activitySummaries, viewer: opts.name }),
252
+ ...renderUnreadSummary(opts.activitySummaries),
263
253
  ...draftSavedLines(opts.draftPath),
264
254
  `${readNowCommand}`,
265
255
  ' · take it in, then retry:',
@@ -373,7 +363,7 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
373
363
  export function renderActivityLimit(opts) {
374
364
  // The cap wording lives inside the count: the speaker is simply spent.
375
365
  const spoken = opts.count !== undefined && opts.hardCap !== undefined ? ` — ${opts.count}/${opts.hardCap} spoken` : '';
376
- const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} done`;
366
+ const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)} done`;
377
367
  return withPathOutput(opts.squarePath, [
378
368
  `${style('blocked', '✕')} you’ve reached your speaking cap${spoken}`,
379
369
  ...draftSavedLines(opts.draftPath),
@@ -386,7 +376,7 @@ export function renderWatchAlreadyActive(opts) {
386
376
  `${style('blocked', '✕')} you are already catching`,
387
377
  ` · an active catch is already running for ${participantIdentity(opts.name)}`,
388
378
  ' · --replace lets a new catch take over',
389
- `${participantCommandPrefix(opts.squarePath, opts.name)} catch --idle 30m --replace`,
379
+ `${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)} catch --idle 30m --replace`,
390
380
  ].join('\n');
391
381
  }
392
382
  export function renderWatchForceTakeover(_opts) {
@@ -406,9 +396,9 @@ export function renderWatchStatus(opts) {
406
396
  ? ['', 'around the square', ...renderPresenceLines(others, opts.presence.now)]
407
397
  : [];
408
398
  switch (opts.status) {
409
- case 'stale':
410
- case 'empty-now': {
411
- const prefix = participantCommandPrefix(opts.squarePath, opts.name);
399
+ case 'idle':
400
+ case 'drained': {
401
+ const prefix = participantCommandPrefix(opts.squarePath, opts.name, opts.owner);
412
402
  // Quiet graduates with the idle length; an empty catch has no idle budget to report.
413
403
  const idleMs = opts.idleMs;
414
404
  const quiet = idleMs !== undefined && idleMs >= 60 * 60 * 1000
@@ -425,12 +415,12 @@ export function renderWatchStatus(opts) {
425
415
  ].join('\n');
426
416
  }
427
417
  case 'quorum':
428
- return [`${style('release', '✓')} everyone else has left — the square is yours alone`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
418
+ return [`${style('release', '✓')} everyone else has left — the square is yours alone`, `${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)} done`].join('\n');
429
419
  case 'capped': {
430
420
  const spoken = opts.hardCap === undefined || opts.hardCap === null
431
421
  ? ''
432
422
  : ` — ${opts.ownActivityCount ?? 0}/${opts.hardCap} spoken`;
433
- return [`${style('blocked', '✕')} you’ve reached your speaking cap${spoken}`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
423
+ return [`${style('blocked', '✕')} you’ve reached your speaking cap${spoken}`, `${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)} done`].join('\n');
434
424
  }
435
425
  }
436
426
  }
package/dist/runtime.d.ts CHANGED
@@ -1,18 +1,12 @@
1
- import { type ActivityId } from './square-core.js';
2
- import { type StoredAct, type WatchLease } from './model.js';
1
+ import { type WatchLease } from './model.js';
3
2
  import type { ParticipantCounts, ParticipantFact, SquareRead } from './ports.js';
4
3
  export declare const WATCH_HEARTBEAT_MS: number;
5
- export declare const SLEEP_MS: number;
6
- export declare const STALE_MS: number;
7
4
  export declare const WATCH_STALE_MS: number;
8
5
  export declare const THROTTLE_WINDOW_MS: number;
9
6
  export declare const UNREAD_BLOCK_GRACE_MS: number;
10
7
  export declare const HELD_WAIT_BUDGET_MS: number;
11
8
  export declare const FACT_PAGE_SIZE = 100;
12
- export declare function nowMs(): number;
13
- export declare function rosterNames(facts: SquareRead): string[];
14
9
  export declare function inSquareCount(facts: SquareRead): number;
15
- export declare function actId(actOrIndex: StoredAct | number): ActivityId;
16
10
  export declare function terminalStatusFor(current: ParticipantFact | undefined, hardCap: number | null, counts: ParticipantCounts): 'capped' | 'quorum' | undefined;
17
11
  export declare function watchTerminalStatus(facts: SquareRead, name: string): 'capped' | 'quorum' | undefined;
18
12
  /** A continuous prefix stops at the first unseen historical audience edge after join. */
package/dist/runtime.js CHANGED
@@ -1,4 +1,3 @@
1
- import { formatActivityId } from './square-core.js';
2
1
  import { SquareError } from './model.js';
3
2
  function parseIntegerEnv(name, fallback) {
4
3
  const raw = process.env[name];
@@ -10,25 +9,12 @@ function parseIntegerEnv(name, fallback) {
10
9
  return value;
11
10
  }
12
11
  export const WATCH_HEARTBEAT_MS = parseIntegerEnv('SQUARE_WATCH_HEARTBEAT_MS', 60000);
13
- export const SLEEP_MS = parseIntegerEnv('SQUARE_WATCH_POLL_MS', 10000);
14
- export const STALE_MS = parseIntegerEnv('SQUARE_WATCH_QUIET_MS', 300000);
15
12
  export const WATCH_STALE_MS = parseIntegerEnv('SQUARE_WATCH_STALE_MS', 180000);
16
13
  export const THROTTLE_WINDOW_MS = parseIntegerEnv('SQUARE_THROTTLE_WINDOW_MS', 60000);
17
14
  export const UNREAD_BLOCK_GRACE_MS = parseIntegerEnv('SQUARE_UNREAD_BLOCK_GRACE_MS', 90000);
18
15
  export const HELD_WAIT_BUDGET_MS = parseIntegerEnv('SQUARE_HELD_WAIT_BUDGET_MS', 10 * 60 * 1000);
19
16
  export const FACT_PAGE_SIZE = 100;
20
- export function nowMs() {
21
- const override = process.env.SQUARE_NOW_MS;
22
- if (override === undefined)
23
- return Date.now();
24
- const value = parseInt(override, 10);
25
- if (!Number.isFinite(value))
26
- throw new SquareError('invalid_args', 'Invalid SQUARE_NOW_MS: expected integer milliseconds.');
27
- return value;
28
- }
29
- export function rosterNames(facts) { return facts.roster().map((participant) => participant.name); }
30
17
  export function inSquareCount(facts) { return facts.participantCounts().joined; }
31
- export function actId(actOrIndex) { return formatActivityId(typeof actOrIndex === 'number' ? actOrIndex : actOrIndex.index); }
32
18
  export function terminalStatusFor(current, hardCap, counts) {
33
19
  if (hardCap !== null && (current?.sayCount ?? 0) >= hardCap)
34
20
  return 'capped';
package/dist/seats.d.ts CHANGED
@@ -1,7 +1,6 @@
1
- import { type Body, type Seat } from './model.js';
1
+ import { type Owner, type Seat } from './model.js';
2
2
  import type { ArtifactWriteAuthority, SeatRef } from './ports.js';
3
- export declare function requireBody(body: Body | undefined): Body;
3
+ export declare function requireOwner(owner: Owner | undefined): Owner;
4
4
  export declare function requireJoinName(name: string | undefined): string;
5
5
  export declare function asSeatRef(seat: Seat): SeatRef;
6
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 CHANGED
@@ -1,9 +1,9 @@
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;
1
+ import { SquareError, validateOwner, validateName } from './model.js';
2
+ export function requireOwner(owner) {
3
+ if (owner === undefined)
4
+ throw new SquareError('invalid_args', 'Participant writes require an explicit owner.');
5
+ validateOwner(owner);
6
+ return owner;
7
7
  }
8
8
  export function requireJoinName(name) {
9
9
  if (name === undefined)
@@ -12,16 +12,8 @@ export function requireJoinName(name) {
12
12
  return name;
13
13
  }
14
14
  export function asSeatRef(seat) {
15
- const { wakeAddress: _wakeAddress, ...ref } = seat;
16
- return ref;
15
+ return { participant: seat.participant, owner: seat.owner, generation: seat.generation };
17
16
  }
18
17
  export function participantAuthority(seat) {
19
18
  return { kind: 'participant', seat: asSeatRef(seat) };
20
19
  }
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,7 @@
1
+ import { type Owner } from './owner.js';
1
2
  import type { AutomaticProvider } from './participant-identity.js';
2
- /** Retire only seats held by this exact body, discovered through the rebuildable index. */
3
- export declare function endSessionMemberships(provider: AutomaticProvider, sessionId: string, env?: NodeJS.ProcessEnv): Promise<void>;
3
+ import type { WakeAddress } from './model.js';
4
+ /** Host shutdown suspends execution, not membership. Discovery never grants custody. */
5
+ export declare function detachSessionSeats(provider: AutomaticProvider, sessionId: string, env?: NodeJS.ProcessEnv): Promise<void>;
6
+ /** Restore only detached, standing seats of this exact owner before starting a receiver. */
7
+ export declare function restoreSessionSeats(owner: Owner, env?: NodeJS.ProcessEnv, wakeAddress?: WakeAddress, signal?: AbortSignal): Promise<void>;
@@ -1,19 +1,47 @@
1
1
  import { Square } from './square-api.js';
2
- import { projectSessionSeats } from './inbox.js';
3
- function bodyFor(provider, sessionId) {
4
- return { channel: provider === 'claude' ? 'claude-code' : provider, session: sessionId };
2
+ import { harnessOwner } from './owner.js';
3
+ import { projectSessionCandidates, projectSessionSeats } from './inbox.js';
4
+ function ownerFor(provider, sessionId) {
5
+ return harnessOwner({ channel: provider === 'claude' ? 'claude-code' : provider, session: sessionId });
5
6
  }
6
- /** Retire only seats held by this exact body, discovered through the rebuildable index. */
7
- export async function endSessionMemberships(provider, sessionId, env = process.env) {
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 });
7
+ /** Host shutdown suspends execution, not membership. Discovery never grants custody. */
8
+ export async function detachSessionSeats(provider, sessionId, env = process.env) {
9
+ const owner = ownerFor(provider, sessionId);
10
+ for (const { location, seat } of await projectSessionSeats({ owner, env })) {
11
+ let square;
12
12
  try {
13
- await square.endOwnedSession(seat.participant, sessionId, seat.generation, body);
13
+ square = await Square.at({ path: location, env });
14
+ await square.detach(seat.participant, { owner, generation: seat.generation });
14
15
  }
16
+ catch { /* One unavailable or replaced seat must not block cleanup of the others. */ }
15
17
  finally {
16
- await square.close();
18
+ await square?.close();
19
+ }
20
+ }
21
+ }
22
+ /** Restore only detached, standing seats of this exact owner before starting a receiver. */
23
+ export async function restoreSessionSeats(owner, env = process.env, wakeAddress, signal) {
24
+ const locations = new Set((await projectSessionCandidates({ owner, env })).map(candidate => candidate.location));
25
+ for (const location of locations) {
26
+ if (signal?.aborted)
27
+ return;
28
+ let square;
29
+ try {
30
+ square = await Square.at({ path: location, env }, { signal });
31
+ for (const seat of await square.seatsOf(owner)) {
32
+ if (signal?.aborted)
33
+ return;
34
+ if (seat.attachment !== 'detached')
35
+ continue;
36
+ try {
37
+ await square.reattach(seat.name, { owner, wakeAddress }, { signal });
38
+ }
39
+ catch { /* A replacement or one failed seat does not block its peers. */ }
40
+ }
41
+ }
42
+ catch { /* An indexed location is only a candidate, not a promise of availability. */ }
43
+ finally {
44
+ await square?.close();
17
45
  }
18
46
  }
19
47
  }
@@ -1,5 +1,5 @@
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';
1
+ import { type Owner } from './model.js';
2
+ import type { HistoryQuery, HistoryResult, JoinOptions, JoinResult, LifecycleResult, ObservationOptions, OperationControl, Participant, SquareAtInput, SquareBuildInput, SquareLocation, StatusOptions, StatusResult, ParticipantsResult, RecoveryResult, DiscoveryOptions, InboxOptions, InboxResult, StreamOptions, StreamRecord, Activity, PerceivedActivity } from './square-facade.js';
3
3
  export type * from './square-facade.js';
4
4
  export { createSquareResourceOwner } from './artifact.js';
5
5
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
@@ -13,41 +13,55 @@ export declare class SqliteSquare {
13
13
  readonly location: string;
14
14
  private readonly artifact;
15
15
  private readonly clock;
16
- private readonly body?;
17
- private readonly wakeAddress?;
18
16
  private readonly wakeTransport?;
19
17
  private readonly indexPath?;
20
- private readonly automaticDelivery;
21
- private readonly ownsArtifact;
22
- private readonly defaultParticipant?;
18
+ private readonly closesArtifact;
19
+ private readonly deliveryLocation;
23
20
  private constructor();
24
- static at(input: SquareAtInput, control?: OperationControl, automaticDelivery?: boolean): Promise<SqliteSquare>;
25
- static build(input: SquareBuildInput, control?: OperationControl, automaticDelivery?: boolean): Promise<SqliteSquare>;
21
+ static at(input: SquareAtInput, control?: OperationControl): Promise<SqliteSquare>;
22
+ static build(input: SquareBuildInput, control?: OperationControl): Promise<SqliteSquare>;
26
23
  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;
24
+ /** Discovery is best effort and never grants write authority. */
25
+ private publishSeat;
29
26
  private observation;
30
27
  private contextObservation;
31
28
  private handle;
32
29
  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
- }>;
30
+ participant(name: string, { owner }: {
31
+ readonly owner: Owner;
32
+ }): Promise<Participant>;
33
+ join(requested: string, options: JoinOptions, control?: OperationControl): Promise<JoinResult>;
34
+ /** Restore only a standing seat; a concurrent departure or takeover cannot be revived. */
35
+ reattach(name: string, { owner, wakeAddress }: Pick<JoinOptions, 'owner' | 'wakeAddress'>, control?: OperationControl): Promise<Participant | null>;
39
36
  snapshot(control?: OperationControl): Promise<import("./square-facade.js").SquareSnapshot>;
40
37
  history(query?: HistoryQuery, control?: OperationControl): Promise<HistoryResult>;
41
38
  status(options?: StatusOptions, control?: OperationControl): Promise<StatusResult>;
42
39
  participants(options?: ObservationOptions, control?: OperationControl): Promise<ParticipantsResult>;
43
40
  recovery(requested?: string, control?: OperationControl): Promise<RecoveryResult>;
44
- stream(options: StreamOptions, control?: OperationControl): AsyncIterable<import("./square-facade.js").StreamRecord>;
45
- recognize(): Promise<readonly Participant[]>;
41
+ stream(options: StreamOptions & {
42
+ readonly recipient: string;
43
+ }, control?: OperationControl): AsyncIterable<PerceivedActivity>;
44
+ stream(options: StreamOptions & {
45
+ readonly recipient?: undefined;
46
+ }, control?: OperationControl): AsyncIterable<Activity>;
47
+ stream(options: StreamOptions, control?: OperationControl): AsyncIterable<StreamRecord>;
48
+ seatsOf(owner: Owner): Promise<readonly import('./square-facade.js').HeldSeat[]>;
46
49
  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>;
50
+ private heldSeat;
51
+ cancelDeliveries(name: string, identity: {
52
+ readonly owner: Owner;
53
+ readonly generation: import('./square-core.js').ActivityId;
54
+ }): Promise<boolean>;
55
+ detach(name: string, identity: {
56
+ readonly owner: Owner;
57
+ readonly generation: import('./square-core.js').ActivityId;
58
+ }): Promise<true | null>;
59
+ endHeld(name: string, identity: {
60
+ readonly owner: Owner;
61
+ readonly generation: import('./square-core.js').ActivityId;
62
+ }): Promise<LifecycleResult | null>;
49
63
  }
50
64
  export declare function diagnoseSquare(input: SquareLocation, control?: OperationControl): Promise<import("./artifact.js").DiagnoseResult>;
51
65
  export declare function migrateSquare(input: SquareLocation, control?: OperationControl): Promise<import("./square-migration.js").MigrationOutcome>;
52
66
  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>;
67
+ export declare function inbox(input: SquareAtInput, owner: Owner, options?: InboxOptions, control?: OperationControl): Promise<InboxResult>;