@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
@@ -12,7 +12,7 @@ export class CodexQueueTransport {
12
12
  const { address } = request.route;
13
13
  const threadId = address.threadId?.trim();
14
14
  if (!threadId) {
15
- return { outcome: 'failed', unavailable: true, signature: 'invalid_address', message: 'Codex route has no thread id.', routeStale: true };
15
+ return { outcome: 'failed', signature: 'invalid_address', message: 'Codex route has no thread id.' };
16
16
  }
17
17
  const env = this.opts.env ?? process.env;
18
18
  const payload = request.artifact === undefined ? undefined
@@ -28,15 +28,15 @@ export class CodexQueueTransport {
28
28
  });
29
29
  const remaining = deadline - Date.now();
30
30
  if (remaining <= 0) {
31
- return { outcome: 'failed', unavailable: true, signature: 'dispatch_budget_exhausted', message: 'The wake dispatch budget elapsed before queueing.', retainRoute: true };
31
+ return { outcome: 'failed', signature: 'dispatch_budget_exhausted', message: 'The wake dispatch budget elapsed before queueing.' };
32
32
  }
33
33
  await agent.queue(payload, { timeoutMs: remaining });
34
34
  return { outcome: 'accepted' };
35
35
  }
36
36
  catch (error) {
37
37
  if (error instanceof DeliveryError && error.code === 'session_inactive') {
38
- return { outcome: 'failed', unavailable: true, signature: 'boundary_not_stopped',
39
- message: 'The Codex thread has no current Stop boundary for queueing.', retainRoute: true };
38
+ return { outcome: 'failed', signature: 'boundary_not_stopped',
39
+ message: 'The Codex thread has no current Stop boundary for queueing.' };
40
40
  }
41
41
  const message = error instanceof Error ? error.message : String(error);
42
42
  if (!(error instanceof DeliveryError) || error.maybeDelivered)
@@ -5,21 +5,14 @@ type TimedAct<K extends Act['kind'] = Act['kind']> = Extract<Act, {
5
5
  }> & {
6
6
  at: number;
7
7
  };
8
- import type { ExpressResult, ParticipantSummary as ParticipantStatus, UnreadActivitySummary } from './square-facade.js';
9
- export type { ParticipantSummary as ParticipantStatus, ParticipantPresence as CorePresenceState, UnreadActivitySummary, UnreadActivityPreview } from './square-facade.js';
8
+ import type { ExpressResult, ParticipantSummary, UnreadActivitySummary } from './square-facade.js';
10
9
  export declare function resolveKnownName(facts: SquareRead, name: string): string;
11
10
  export interface DecideJoinResult {
12
11
  joinedName: string;
13
12
  addParticipant: boolean;
14
13
  joinAct?: TimedAct<'join'>;
15
14
  }
16
- export interface DecideImplicitJoinResult {
17
- joinedName: string;
18
- state: 'joined' | 'active' | 'done';
19
- joinAct?: TimedAct<'join'>;
20
- }
21
15
  export declare function decideJoin(facts: SquareRead, name: string, now: number): DecideJoinResult;
22
- export declare function decideImplicitJoin(facts: SquareRead, name: string, now: number): DecideImplicitJoinResult;
23
16
  export type ActDecision = ({
24
17
  type: 'sent';
25
18
  act: TimedAct;
@@ -40,7 +33,7 @@ export type ActDecision = ({
40
33
  type: 'bell_quota';
41
34
  nextAt: number;
42
35
  };
43
- /** Count the directed suffix, but hydrate only its last three previews per spelling. */
36
+ /** Count the directed suffix and hydrate only its last activity by index per spelling. */
44
37
  export declare function unreadActivitySummaries(facts: SquareRead, name: string, now: number): UnreadActivitySummary[];
45
38
  export declare function decideAct(facts: SquareRead, input: {
46
39
  name: string;
@@ -61,5 +54,6 @@ export declare function coreUnlisten(facts: SquareRead, actor: string, target: s
61
54
  export declare function coreListening(facts: SquareRead, actor: string): readonly string[];
62
55
  export declare function coreHold(facts: SquareRead, actor: string, body: string, now: number): TimedAct<'hold'>;
63
56
  export declare function coreResume(facts: SquareRead, actor: string, now: number): TimedAct<'resume'>;
64
- export declare function compareParticipantActivity(a: Pick<ParticipantStatus, 'name' | 'lastActiveAt'>, b: Pick<ParticipantStatus, 'name' | 'lastActiveAt'>): number;
65
- export declare function coreParticipants(facts: SquareRead, now: number): ParticipantStatus[];
57
+ export declare function compareParticipantActivity(a: Pick<ParticipantSummary, 'name' | 'lastActiveAt'>, b: Pick<ParticipantSummary, 'name' | 'lastActiveAt'>): number;
58
+ export declare function coreParticipants(facts: SquareRead, now: number): ParticipantSummary[];
59
+ export {};
package/dist/decisions.js CHANGED
@@ -1,4 +1,4 @@
1
- import { SquareError, isSquareError, sameName, nameKey, validateName, validateActivityMetadata, activityMetadataHasContent, } from './model.js';
1
+ import { SquareError, isSquareError, sameName, validateName, validateActivityMetadata, activityMetadataHasContent, } from './model.js';
2
2
  import { participantIdentity } from './participant-identity.js';
3
3
  import { UNREAD_BLOCK_GRACE_MS, freshWatchLease, readCursor, FACT_PAGE_SIZE, THROTTLE_WINDOW_MS } from './runtime.js';
4
4
  import { formatActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
@@ -17,12 +17,6 @@ export function decideJoin(facts, name, now) {
17
17
  return current?.joined ? { joinedName, addParticipant: false }
18
18
  : { joinedName, addParticipant: current === undefined, joinAct: { kind: 'join', actor: joinedName, at: now } };
19
19
  }
20
- export function decideImplicitJoin(facts, name, now) {
21
- validateName(name);
22
- const current = facts.participant(name);
23
- return current === undefined ? { joinedName: name, state: 'joined', joinAct: { kind: 'join', actor: name, at: now } }
24
- : { joinedName: current.name, state: current.joined ? 'active' : 'done' };
25
- }
26
20
  function validateActivityName(name) {
27
21
  try {
28
22
  validateName(name);
@@ -54,34 +48,28 @@ function requireStanding(current) {
54
48
  if (!current.joined)
55
49
  throw new SquareError('not_joined', `${participantIdentity(current.name)} has not joined this square`);
56
50
  }
57
- /** Count the directed suffix, but hydrate only its last three previews per spelling. */
51
+ /** Count the directed suffix and hydrate only its last activity by index per spelling. */
58
52
  export function unreadActivitySummaries(facts, name, now) {
59
53
  let afterIndex = readCursor(facts, name);
60
- const numbers = new Map();
61
54
  const summaries = new Map();
62
55
  while (true) {
63
56
  const page = facts.directed(name, { afterIndex }, { edge: 'oldest', limit: FACT_PAGE_SIZE });
64
57
  for (const item of page.items) {
65
- const key = nameKey(item.actor);
66
- const number = (numbers.get(key) ?? 0) + 1;
67
- numbers.set(key, number);
68
- const summary = summaries.get(item.actor) ?? { count: 0, latestAt: -Infinity, previews: [] };
58
+ const summary = summaries.get(item.actor) ?? { count: 0, latestAt: -Infinity, lastIndex: item.index };
69
59
  summary.count++;
70
60
  summary.latestAt = Math.max(summary.latestAt, item.at);
71
- summary.previews.push({ index: item.index, number });
72
- if (summary.previews.length > 3)
73
- summary.previews.shift();
61
+ summary.lastIndex = item.index;
74
62
  summaries.set(item.actor, summary);
75
63
  }
76
64
  if (!page.hasMore)
77
65
  break;
78
66
  afterIndex = page.items.at(-1).index;
79
67
  }
80
- const selected = facts.activities([...summaries.values()].flatMap((item) => item.previews.map((preview) => preview.index)));
68
+ const selected = facts.activities([...summaries.values()].map((item) => item.lastIndex));
81
69
  const byIndex = new Map(selected.map((item) => [item.index, item]));
82
70
  return [...summaries].map(([name, summary]) => ({
83
71
  name, count: summary.count, latestActivityAgeMs: Math.max(0, now - summary.latestAt),
84
- previews: summary.previews.map((preview) => ({ number: preview.number, act: byIndex.get(preview.index), perception: 'full' })),
72
+ preview: byIndex.get(summary.lastIndex),
85
73
  })).sort((a, b) => a.latestActivityAgeMs - b.latestActivityAgeMs || a.name.localeCompare(b.name));
86
74
  }
87
75
  export function decideAct(facts, input) {
@@ -219,10 +207,11 @@ export function coreParticipants(facts, now) {
219
207
  lastActiveAt = Math.max(lastActiveAt ?? -Infinity, seenAt);
220
208
  }
221
209
  return {
222
- name: current.name, state: current.done ? 'done' : current.joined ? 'active' : 'not joined',
210
+ name: current.name, deliveryAvailable: facts.seat(current.name)?.wakeAddress !== undefined,
223
211
  listening: facts.attention(current.name).filter((item) => item.kind === 'listen').map((item) => item.target),
224
212
  activityCount: current.sayCount, lastActiveAt,
225
- presence: current.done ? 'done' : !current.joined ? 'never-joined' : freshWatchLease(facts, current.name, now) !== undefined ? 'watching' : 'active',
213
+ presence: current.done ? 'done' : !current.joined ? 'never-joined' : facts.seat(current.name)?.attachment === 'detached' ? 'away'
214
+ : freshWatchLease(facts, current.name, now) !== undefined ? 'watching' : 'active',
226
215
  unreadActivityCount: current.joined ? facts.directedCount(current.name, { afterIndex: readCursor(facts, current.name) }) : 0,
227
216
  pendingMentionCount: current.joined ? facts.directedCount(current.name, { afterIndex: current.lastJoinIndex ?? -1, unseenOnly: true }) : 0,
228
217
  };
@@ -1,4 +1,4 @@
1
- import { SquareError, sameBody } from './model.js';
1
+ import { SquareError } from './model.js';
2
2
  export const MAX_DELIVERY_DEFERRAL_MS = 24 * 60 * 60 * 1000;
3
3
  export function validateMetadataMatch(value) {
4
4
  const fail = () => { throw new SquareError('invalid_args', 'Invalid metadata match.'); };
@@ -69,7 +69,7 @@ export function matchingDeferrals(deferrals, metadata, now) {
69
69
  }
70
70
  export function deliveryDeferralExpiry(facts, captured, index, now) {
71
71
  const seat = facts.seat(captured.participant);
72
- if (seat === undefined || seat.generation !== captured.generation || !sameBody(seat.body, captured.body))
72
+ if (seat === undefined || seat.generation !== captured.generation || seat.owner !== captured.owner)
73
73
  return undefined;
74
74
  const leases = facts.deliveryDeferrals(seat.participant, seat.generation);
75
75
  if (!leases.some(lease => lease.expiresAt > now))
@@ -12,7 +12,6 @@ export interface WakeAttempt {
12
12
  readonly outcome: 'accepted' | 'unknown' | 'failed';
13
13
  readonly signature?: string;
14
14
  readonly attemptN: number;
15
- readonly session?: string;
16
15
  readonly message?: string;
17
16
  readonly diagnostic?: unknown;
18
17
  }
@@ -38,7 +38,6 @@ export async function classifyDeliveryHealth(squarePath, opts) {
38
38
  routeKind: attemptRow.route.kind,
39
39
  outcome: attemptRow.outcome,
40
40
  attemptN: attemptRow.attemptN,
41
- session: seat.body.session,
42
41
  ...(attemptRow.signature === undefined ? {} : { signature: attemptRow.signature }),
43
42
  ...(attemptRow.message === undefined ? {} : { message: attemptRow.message }),
44
43
  ...(attemptRow.diagnostic === undefined ? {} : { diagnostic: attemptRow.diagnostic }),
@@ -128,7 +127,7 @@ function formatRelease(release, now) {
128
127
  const diagnostic = release.diagnostic === undefined ? '' : ` · ${displayField(JSON.stringify(release.diagnostic))}`;
129
128
  return ` · ${formatActivityId(release.attention.actIndex)} → ${displayField(participantIdentity(release.attention.recipient))} · ${route} · ${reason} · ${age}${message}${diagnostic}`;
130
129
  }
131
- function deliveryAttempt(squarePath, row, session) {
130
+ function deliveryAttempt(squarePath, row) {
132
131
  if (row.route?.kind === undefined || row.outcome === 'released' || row.outcome === 'dispatching')
133
132
  return undefined;
134
133
  return {
@@ -137,7 +136,6 @@ function deliveryAttempt(squarePath, row, session) {
137
136
  routeKind: row.route.kind,
138
137
  outcome: row.outcome,
139
138
  attemptN: row.attemptN,
140
- ...(session === undefined ? {} : { session }),
141
139
  ...(row.signature === undefined ? {} : { signature: row.signature }),
142
140
  ...(row.message === undefined ? {} : { message: row.message }),
143
141
  ...(row.diagnostic === undefined ? {} : { diagnostic: row.diagnostic }),
@@ -1,5 +1,5 @@
1
- import type { SquareArtifactPort, DeliverPendingInput, DeliveryResult } from './ports.js';
2
- export declare function deliverPending(input: DeliverPendingInput): Promise<DeliveryResult>;
1
+ import type { SquareArtifactPort, DeliverPendingInput } from './ports.js';
2
+ export declare function deliverPending(input: DeliverPendingInput): Promise<void>;
3
3
  export declare function sweepPending(input: {
4
4
  readonly artifact: SquareArtifactPort;
5
5
  readonly location: string;
@@ -1,17 +1,15 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { formatActivityId, parseActivityId } from './square-core.js';
3
- import { sameBody } from './model.js';
4
3
  import { pendingHead, pendingHeads, pendingRecipients, presentationBlocksWake } from './delivery.js';
5
4
  import { asSeatRef } from './seats.js';
6
- import { canonicalPath } from './canonical-path.js';
7
5
  import { freshWatchLease } from './runtime.js';
8
6
  import { deliveryDeferralExpiry } from './delivery-deferral.js';
9
7
  import { leaseOwnsNotification } from './delivery.js';
10
8
  function routeFor(location, seat, now) {
11
- if (seat.wakeAddress === undefined)
9
+ if (seat.attachment !== 'attached' || seat.wakeAddress === undefined)
12
10
  return undefined;
13
11
  return {
14
- location, participant: seat.participant, sessionId: seat.body.session, channel: seat.body.channel,
12
+ location, participant: seat.participant,
15
13
  kind: seat.wakeAddress.kind, address: { ...seat.wakeAddress.address }, updatedAt: now,
16
14
  };
17
15
  }
@@ -19,8 +17,8 @@ async function currentness(artifact, request, now) {
19
17
  const index = parseActivityId(request.activity);
20
18
  const { value } = await artifact.read((facts) => {
21
19
  const currentSeat = facts.seat(request.participant);
22
- const seatCurrent = currentSeat !== undefined && currentSeat.generation === request.seat.generation
23
- && sameBody(currentSeat.body, request.seat.body)
20
+ const seatCurrent = currentSeat !== undefined && currentSeat.attachment === 'attached' && currentSeat.generation === request.seat.generation
21
+ && currentSeat.owner === request.seat.owner
24
22
  && currentSeat.wakeAddress?.kind === request.route.kind
25
23
  && Object.keys(currentSeat.wakeAddress.address).length === Object.keys(request.route.address).length
26
24
  && Object.entries(currentSeat.wakeAddress.address).every(([key, val]) => request.route.address[key] === val);
@@ -59,7 +57,7 @@ async function candidates(artifact, requested, now = Date.now()) {
59
57
  : pendingRecipients(facts, requested);
60
58
  return heads.flatMap((head) => {
61
59
  const seat = facts.seat(head.recipient);
62
- return seat === undefined || deliveryDeferralExpiry(facts, seat, head.index, now) !== undefined ? [] : [{ seat, head }];
60
+ return seat === undefined || seat.attachment !== 'attached' || deliveryDeferralExpiry(facts, seat, head.index, now) !== undefined ? [] : [{ seat, head }];
63
61
  });
64
62
  });
65
63
  return value;
@@ -81,19 +79,25 @@ function safeJson(value) {
81
79
  return String(value);
82
80
  }
83
81
  export async function deliverPending(input) {
84
- const location = await canonicalPath(input.location);
82
+ // The resource owner supplies its identity; delivery does not interpret it as a file.
83
+ const location = input.location;
85
84
  const requested = input.activity === undefined ? undefined
86
85
  : typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
87
86
  if (input.activity !== undefined && requested === undefined)
88
- return { attempted: 0, accepted: 0, failed: 0, unknown: 0, notCapable: 0 };
87
+ return;
89
88
  const pending = await candidates(input.artifact, requested, input.now ?? Date.now());
90
- let attempted = 0, accepted = 0, failed = 0, unknown = 0, notCapable = 0;
91
- for (const { seat, head } of pending) {
89
+ // Preserve order within a receiving owner; unrelated receivers need not wait for each other.
90
+ const receivers = new Map();
91
+ for (const candidate of pending) {
92
+ const key = candidate.seat.owner;
93
+ const group = receivers.get(key) ?? [];
94
+ group.push(candidate);
95
+ receivers.set(key, group);
96
+ }
97
+ const dispatch = async ({ seat, head }) => {
92
98
  const route = routeFor(location, seat, input.now ?? Date.now());
93
- if (route === undefined) {
94
- notCapable += 1;
95
- continue;
96
- }
99
+ if (route === undefined)
100
+ return;
97
101
  const request = {
98
102
  location, participant: seat.participant, activity: formatActivityId(head.index), actor: head.actor,
99
103
  seat: asSeatRef(seat), route,
@@ -102,14 +106,11 @@ export async function deliverPending(input) {
102
106
  if (input.transport.probe !== undefined) {
103
107
  try {
104
108
  const probe = await input.transport.probe(route);
105
- if (probe === false || typeof probe === 'object' && probe.outcome === 'not-capable') {
106
- notCapable += 1;
107
- continue;
108
- }
109
+ if (probe === false || typeof probe === 'object' && probe.outcome === 'not-capable')
110
+ return;
109
111
  }
110
112
  catch {
111
- notCapable += 1;
112
- continue;
113
+ return;
113
114
  }
114
115
  }
115
116
  const claimToken = randomUUID();
@@ -117,7 +118,7 @@ export async function deliverPending(input) {
117
118
  const claim = await input.artifact.claimDelivery({ seat: request.seat, activity: request.activity, kind: 'wake',
118
119
  route: seat.wakeAddress, at: input.now ?? Date.now(), leaseMs, claimToken }).catch(() => ({ status: 'stale' }));
119
120
  if (claim.status !== 'acquired')
120
- continue;
121
+ return;
121
122
  const withClaim = { ...request, claimToken, attemptN: claim.attemptN };
122
123
  let final;
123
124
  let suppressed = false;
@@ -138,7 +139,7 @@ export async function deliverPending(input) {
138
139
  catch { /* unverifiable */ }
139
140
  if (!before?.current) {
140
141
  await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'pre_send_revalidation_failed', before === undefined ? 'Square delivery state could not be verified.' : 'The notification is no longer current.', before);
141
- continue;
142
+ return;
142
143
  }
143
144
  let outcome;
144
145
  try {
@@ -149,32 +150,31 @@ export async function deliverPending(input) {
149
150
  }
150
151
  if (suppressed) {
151
152
  await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'pre_send_revalidation_failed', 'The notification changed before the final send check.', final ?? { observationAvailable: false });
152
- continue;
153
+ return;
153
154
  }
154
155
  if (outcome.outcome === 'not-capable') {
155
156
  await releaseClaim(input.artifact, request.seat, request.activity, claimToken, 'transport_not_capable', undefined, outcome.diagnostic);
156
- notCapable += 1;
157
- continue;
157
+ return;
158
158
  }
159
- attempted += 1;
160
159
  const mapped = outcome.outcome === 'accepted' ? 'accepted'
161
160
  : outcome.outcome === 'unknown' ? 'unknown' : 'failed';
162
- const completed = await input.artifact.completeDelivery({ seat: request.seat, activity: request.activity, claimToken,
161
+ await input.artifact.completeDelivery({ seat: request.seat, activity: request.activity, claimToken,
163
162
  outcome: mapped, at: Date.now(),
164
163
  ...('signature' in outcome && outcome.signature !== undefined ? { signature: outcome.signature } : {}),
165
164
  ...('message' in outcome && outcome.message !== undefined ? { message: outcome.message } : {}),
166
165
  ...('diagnostic' in outcome && outcome.diagnostic !== undefined ? { diagnostic: safeJson(outcome.diagnostic) } : {}),
167
166
  }).catch(() => false);
168
- if (!completed)
169
- continue;
170
- if (mapped === 'accepted')
171
- accepted += 1;
172
- else if (mapped === 'unknown')
173
- unknown += 1;
174
- else
175
- failed += 1;
176
- }
177
- return { attempted, accepted, failed, unknown, notCapable };
167
+ };
168
+ const groups = receivers.values();
169
+ const settled = await Promise.allSettled(Array.from({ length: Math.min(4, receivers.size) }, async () => {
170
+ for (const group of groups)
171
+ for (const candidate of group)
172
+ await dispatch(candidate);
173
+ }));
174
+ // Even an unexpected failure must drain siblings before the caller closes its artifact.
175
+ for (const result of settled)
176
+ if (result.status === 'rejected')
177
+ throw result.reason;
178
178
  }
179
179
  export async function sweepPending(input) {
180
180
  if (input.limit <= 0)
@@ -40,7 +40,7 @@ export declare function pendingPages(artifact: SquareArtifactPort, scan: Pending
40
40
  at: number;
41
41
  actor: string;
42
42
  recipient: string;
43
- route: "bell" | "reply" | "mention" | "attention";
43
+ route: "bell" | "mention" | "reply" | "attention";
44
44
  }[], void, unknown>;
45
45
  /** A pass retains only its captured joins; routes and individual receipts are fresh. */
46
46
  export declare function pendingFacts(facts: SquareRead, scan: PendingScan, heads: readonly DirectedHead[]): {
package/dist/delivery.js CHANGED
@@ -8,7 +8,7 @@ export function wakePreview(facts, request) {
8
8
  const head = index === undefined ? undefined : pendingHead(facts, request.participant, index);
9
9
  const item = index === undefined ? undefined : facts.activity(index);
10
10
  const seat = facts.seat(request.participant);
11
- if (head === undefined || item?.kind !== 'say' || seat === undefined
11
+ if (head === undefined || item?.kind !== 'say' || seat === undefined || seat.attachment !== 'attached'
12
12
  || deliveryDeferralExpiry(facts, request.seat, item.index, Date.now()) !== undefined)
13
13
  return undefined;
14
14
  return renderSquareReminder([renderAttentionPreview({
package/dist/help.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { JSON_COMMANDS } from './cli/json.js';
1
2
  const COMMANDS = [
2
3
  { names: ['help'], usage: 'help [command]', summary: 'Show the command index or help for one command.' },
3
4
  { names: ['version'], usage: 'version', summary: 'Print the installed version.', hiddenFromIndex: true },
@@ -52,9 +53,9 @@ const COMMANDS = [
52
53
  details: ['Options:', ' --ndjson Emit one JSON event per line.', ' --for <name> With --ndjson, emit notifications for one participant.', ' --last <N> Emit the last N eligible activities first (default 10, maximum 100).', ' --after <id> Resume after a canonical activity id (act/12).'],
53
54
  },
54
55
  {
55
- names: ['inbox'], usage: 'inbox --for-session <session-id> [--limit <N>] [--json]', hiddenFromIndex: true,
56
+ names: ['inbox'], usage: 'inbox --for-owner <owner> [--limit <N>]', hiddenFromIndex: true,
56
57
  summary: 'Inspect a bounded machine-local notification snapshot for a native session.',
57
- details: ['Options:', ' --for-session <id> Required harness session id.', ' --limit <N> Snapshot memberships (default 20, maximum 100).', ' --json Emit the bounded snapshot as structured JSON.'],
58
+ details: ['Options:', ' --for-owner <id> Required opaque owner.', ' --limit <N> Snapshot memberships (default 20, maximum 100).'],
58
59
  },
59
60
  { names: ['codex-hook'], usage: '{command}', summary: 'Present pending attention at one native agent boundary.', hiddenFromIndex: true },
60
61
  { names: ['claude-mod'], usage: '{command}', summary: 'Receive a structured operation from the loaded Claude mod.', hiddenFromIndex: true },
@@ -67,7 +68,7 @@ const COMMANDS = [
67
68
  {
68
69
  names: ['participants'], usage: 'participants [--limit <count>]', usesSquare: true, group: 'host',
69
70
  summary: 'Show the participant roster and current states.',
70
- details: ['Options:', ' --limit <count> Show the first count names in roster order (default 20, maximum 100).'],
71
+ details: ['Options:', ' --limit <count> Show the first count names in human text (default 20, maximum 100).'],
71
72
  },
72
73
  { names: ['hold'], usage: '--as <name> hold [reason | -]', usesSquare: true, group: 'participant', summary: 'Raise a hand and pause participant activity.' },
73
74
  { names: ['resume'], usage: '--as <name> resume', usesSquare: true, group: 'participant', summary: 'Lower the raised hand and resume activity.' },
@@ -93,7 +94,7 @@ const COMMANDS = [
93
94
  {
94
95
  names: ['migrate'], usage: 'migrate', usesSquare: true, group: 'maintenance',
95
96
  summary: 'Convert a supported older square to the current format in place.',
96
- details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2, 3 and 4 are accepted; version 1, unrelated or unsupported artifacts are refused without a write. Names whose own owner and route evidence agrees may share a body; names with missing or conflicting ownership stay standing without write or delivery authority. Migration reports recovered seats, unowned standing names, shared-body seats and skipped evidence. There is no --check, --backup or --force.'],
97
+ details: ['For the selected .square artifact only. Stop sessions using this artifact before conversion. Back up the stopped artifact, migrate it, then run square --location <path> doctor before starting updated clients.', 'Versions 2–6 migrate explicitly to format 7; format 6 attachment is preserved and Body coordinates become opaque owners. Versions 4/5 first gain attached seats; 4 also gains empty deferrals. Version 1, unrelated or unsupported artifacts refuse without a write. Names whose legacy owner and routes agree may share an owner; ambiguous names stay standing without custody. Rejoin with the same owner after migration to republish discovery. There is no --check, --backup or --force.'],
97
98
  },
98
99
  ];
99
100
  function definitionFor(command) {
@@ -117,10 +118,11 @@ export function renderGlobalHelp() {
117
118
  '',
118
119
  ]);
119
120
  return [
120
- 'Usage: square [--location <square>] [--as <name>] <command> [args...]',
121
- 'Environment: SQUARE_LOCATION sets the CLI location (local artifact path or stdio://name/key); square-accessing commands require it or --location. Use --as to select a participant.',
122
- 'Backends: SQUARE_BACKENDS_CONFIG selects {backends:{name:{command,args?,cwd?,env?}}}; default ~/.config/square/backends.json. See guides/backends.md.',
123
- 'CLI flags override environment values. Participants join explicitly; no square is joined automatically.',
121
+ 'Usage: square [--location <square>] [--as <name>] [--owner <id>] <command> [args...]',
122
+ 'Environment: SQUARE_LOCATION sets the CLI local artifact path; square-accessing commands require it or --location. Use --as to select a participant.',
123
+ 'Identity: --owner overrides SQUARE_OWNER, then a complete harness identity, then user:<OS user>@<host>. Incomplete or ambiguous harness identities refuse. Owners without wake use catch --idle.',
124
+ 'Output: --json on join, express, catch, history, listen, ignore, unlisten, listening, hold, resume, done, status, participants and inbox prints the SDK result without observation; a participant handle becomes {name}.',
125
+ 'History emits one JSON document; stream --ndjson emits one SDK record per line.',
124
126
  '',
125
127
  ...commandLines,
126
128
  "Run 'square <command> --help' for command options.",
@@ -132,7 +134,7 @@ export function renderSubcommandHelp(command) {
132
134
  if (definition === undefined)
133
135
  return undefined;
134
136
  const aliases = definition.names.filter((name) => name !== command);
135
- const usage = definition.usage.replace('{command}', command);
137
+ const usage = definition.usage.replace('{command}', command) + (JSON_COMMANDS.has(command) ? ' [--json]' : '');
136
138
  return [
137
139
  `Usage: square ${definition.usesSquare ? '[--location <square>] ' : ''}${usage}`,
138
140
  ...(aliases.length > 0 ? [`Aliases: ${aliases.join(', ')}`] : []),
@@ -151,7 +153,7 @@ export function helpRequest(rawArgs) {
151
153
  const args = [];
152
154
  for (let index = 0; index < rawArgs.length; index++) {
153
155
  const arg = rawArgs[index];
154
- if (arg === '--location' || arg === '--as') {
156
+ if (arg === '--location' || arg === '--as' || arg === '--owner') {
155
157
  const value = rawArgs[index + 1];
156
158
  if (value === undefined || value.startsWith('--'))
157
159
  return undefined;
package/dist/inbox.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Body, type LocatedSeat, type PendingMembership } from './model.js';
1
+ import { type Owner, type LocatedSeat, type PendingMembership } from './model.js';
2
2
  import type { SquareRead } from './ports.js';
3
3
  export interface PendingWaitOptions {
4
4
  signal?: AbortSignal;
@@ -14,15 +14,12 @@ export declare function pendingAtBoundary(inbox: readonly PendingMembership[]):
14
14
  export declare function projectBoundaryEligibility(inbox: readonly PendingMembership[]): Promise<PendingMembership[]>;
15
15
  export interface SessionBindingCandidate {
16
16
  readonly location: string;
17
- readonly body: Body;
17
+ readonly owner: Owner;
18
18
  }
19
19
  /** HOME is a candidate path index; current seats and delivery evidence live in the artifact. */
20
20
  export declare function projectSessionCandidates(input: SessionSeatQuery): Promise<readonly SessionBindingCandidate[]>;
21
21
  export interface SessionSeatQuery {
22
- readonly body?: Body;
23
- readonly sessionId?: string;
24
- readonly channel?: string;
25
- readonly agent?: string;
22
+ readonly owner?: Owner;
26
23
  readonly location?: string;
27
24
  readonly env?: NodeJS.ProcessEnv;
28
25
  }
@@ -36,31 +33,21 @@ export interface NativePendingPreview {
36
33
  }
37
34
  export type NativePendingPreviewInput = {
38
35
  readonly location: string;
39
- readonly sessionId: string;
36
+ readonly generation: import('./model.js').ActivityId;
40
37
  readonly participant: string;
41
38
  readonly actIndex: number;
42
39
  readonly cancelledThrough?: number;
43
40
  readonly now: number;
44
41
  } & ({
45
42
  readonly routeKind: 'claude-native';
46
- readonly address: {
47
- readonly endpoint: string;
48
- };
43
+ readonly address: Readonly<Record<string, string>>;
49
44
  } | {
50
45
  readonly routeKind: 'opencode-server';
51
- readonly address: {
52
- readonly sessionId: string;
53
- };
46
+ readonly address: Readonly<Record<string, string>>;
54
47
  });
55
- /** First matching current seat, exact body/generation/address and catch ownership. */
48
+ /** Current-generation native attention, owned by its exact wake address and catch state. */
56
49
  export declare function nativePendingPreview(facts: SquareRead, input: NativePendingPreviewInput): NativePendingPreview | undefined;
57
- export interface SessionIdentityQuery {
58
- readonly channel: string;
59
- readonly session: string;
60
- readonly allAgents?: true;
61
- }
62
- export type SessionIdentity = Body | SessionIdentityQuery;
63
- export declare function sessionInbox(identity: SessionIdentity, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<PendingMembership[]>;
50
+ export declare function sessionInbox(owner: Owner, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<PendingMembership[]>;
64
51
  export interface SessionPendingObserver {
65
52
  /** Read committed revisions before reusing content; filesystem hints are only for waiting. */
66
53
  read(options?: Pick<PendingWaitOptions, 'signal' | 'excludeKeys'>): Promise<PendingMembership[]>;
@@ -68,4 +55,4 @@ export interface SessionPendingObserver {
68
55
  close(): void;
69
56
  }
70
57
  /** Session-scoped projection over process-shared storage change detectors. */
71
- export declare function observeSessionPending(identity: SessionIdentity, suppliedEnv?: NodeJS.ProcessEnv): Promise<SessionPendingObserver>;
58
+ export declare function observeSessionPending(owner: Owner, suppliedEnv?: NodeJS.ProcessEnv): Promise<SessionPendingObserver>;