@astrosheep/square 0.3.92 → 0.3.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +2 -2
  4. package/dist/boundary-presentation.d.ts +2 -2
  5. package/dist/catch-decisions.d.ts +4 -6
  6. package/dist/catch-decisions.js +4 -4
  7. package/dist/claude-delivery-status.js +10 -4
  8. package/dist/claude-delivery.js +19 -11
  9. package/dist/claude-mod.js +24 -53
  10. package/dist/cli/context.d.ts +9 -4
  11. package/dist/cli/context.js +30 -25
  12. package/dist/cli/express-presentation.d.ts +3 -1
  13. package/dist/cli/express-presentation.js +26 -14
  14. package/dist/cli/json.d.ts +3 -0
  15. package/dist/cli/json.js +14 -0
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.d.ts +1 -3
  18. package/dist/cli/observation-commands.js +43 -56
  19. package/dist/cli/program.js +2 -2
  20. package/dist/cli/registry.js +6 -3
  21. package/dist/cli/square-commands.d.ts +1 -1
  22. package/dist/cli/square-commands.js +46 -54
  23. package/dist/codex-hook.d.ts +3 -2
  24. package/dist/codex-hook.js +13 -4
  25. package/dist/codex-queue.js +4 -4
  26. package/dist/decisions.d.ts +5 -11
  27. package/dist/decisions.js +9 -20
  28. package/dist/delivery-deferral.js +2 -2
  29. package/dist/delivery-health.d.ts +0 -1
  30. package/dist/delivery-health.js +1 -3
  31. package/dist/delivery-operations.d.ts +2 -2
  32. package/dist/delivery-operations.js +38 -38
  33. package/dist/delivery.d.ts +1 -1
  34. package/dist/delivery.js +1 -1
  35. package/dist/harness-links.d.ts +1 -10
  36. package/dist/harness-links.js +144 -109
  37. package/dist/harness.js +4 -13
  38. package/dist/help.js +12 -10
  39. package/dist/inbox.d.ts +9 -22
  40. package/dist/inbox.js +23 -27
  41. package/dist/index.d.ts +1 -3
  42. package/dist/index.js +1 -1
  43. package/dist/mcp.d.ts +3 -0
  44. package/dist/mcp.js +23 -24
  45. package/dist/model.d.ts +8 -15
  46. package/dist/model.js +4 -7
  47. package/dist/notifications.d.ts +1 -1
  48. package/dist/notifications.js +6 -5
  49. package/dist/operation-presentation.d.ts +5 -4
  50. package/dist/operation-presentation.js +50 -45
  51. package/dist/owner.d.ts +11 -0
  52. package/dist/owner.js +32 -0
  53. package/dist/paseo-delivery.d.ts +4 -1
  54. package/dist/paseo-delivery.js +7 -12
  55. package/dist/paseo-state.d.ts +2 -2
  56. package/dist/paseo-state.js +6 -4
  57. package/dist/plugins/opencode/delivery.js +27 -15
  58. package/dist/ports.d.ts +13 -33
  59. package/dist/presentation-operations.js +4 -4
  60. package/dist/presentation.d.ts +5 -8
  61. package/dist/presentation.js +33 -43
  62. package/dist/runtime.d.ts +1 -7
  63. package/dist/runtime.js +0 -14
  64. package/dist/seats.d.ts +2 -3
  65. package/dist/seats.js +7 -15
  66. package/dist/session-lifecycle.d.ts +6 -2
  67. package/dist/session-lifecycle.js +39 -11
  68. package/dist/sqlite-square.d.ts +36 -22
  69. package/dist/sqlite-square.js +138 -161
  70. package/dist/square-api.d.ts +15 -10
  71. package/dist/square-api.js +26 -22
  72. package/dist/square-core.d.ts +0 -5
  73. package/dist/square-core.js +0 -9
  74. package/dist/square-domain.d.ts +4 -2
  75. package/dist/square-domain.js +56 -21
  76. package/dist/square-facade.d.ts +45 -87
  77. package/dist/square-index.d.ts +5 -13
  78. package/dist/square-index.js +13 -38
  79. package/dist/square-location.d.ts +1 -14
  80. package/dist/square-location.js +4 -69
  81. package/dist/square-migration-host-ledger.d.ts +2 -2
  82. package/dist/square-migration-host-ledger.js +6 -5
  83. package/dist/square-migration.d.ts +2 -1
  84. package/dist/square-migration.js +52 -30
  85. package/dist/square-sqlite.d.ts +6 -2
  86. package/dist/square-sqlite.js +54 -28
  87. package/dist/square-tools.d.ts +7 -10
  88. package/dist/square-tools.js +22 -54
  89. package/dist/stream.js +4 -5
  90. package/dist/views.d.ts +21 -10
  91. package/dist/views.js +38 -38
  92. package/dist/wakes.d.ts +1 -2
  93. package/dist/wakes.js +1 -1
  94. package/guides/application-facade-delivery-evidence.md +1 -1
  95. package/guides/claude-native.md +18 -9
  96. package/guides/codex-delivery.md +17 -6
  97. package/guides/mcp-connection-lifetime.md +1 -1
  98. package/guides/mcp.md +3 -3
  99. package/guides/migration.md +142 -151
  100. package/guides/operation-cancellation.md +1 -1
  101. package/guides/participant-ownership.md +78 -47
  102. package/guides/pi-tools.md +33 -4
  103. package/guides/primitive-history-consolidation.md +1 -1
  104. package/guides/row-storage-design.md +8 -8
  105. package/guides/square-api.md +244 -88
  106. package/package.json +2 -1
  107. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  108. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  109. package/plugins/opencode/delivery.ts +27 -17
  110. package/plugins/paseo/client/palette.ts +18 -18
  111. package/plugins/paseo/client/square-message.tsx +17 -23
  112. package/plugins/pi/square-pi.js +17 -17
  113. package/dist/square-backend.d.ts +0 -13
  114. package/dist/square-backend.js +0 -1
  115. package/dist/square-stdio.d.ts +0 -22
  116. package/dist/square-stdio.js +0 -235
  117. package/dist/stdio-server.d.ts +0 -3
  118. package/dist/stdio-server.js +0 -212
  119. package/guides/backends.md +0 -193
@@ -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({
@@ -1,11 +1,3 @@
1
- export interface HarnessLink {
2
- source: string;
3
- target: string;
4
- kind?: 'skill' | 'extension';
5
- }
6
- export declare function installHarnessLinks(links: HarnessLink[], force?: boolean): string[];
7
- export declare function uninstallHarnessLinks(links: HarnessLink[]): string[];
8
- export declare function doctorHarnessLinks(links: HarnessLink[]): string[];
9
1
  export type OpenCodeCommandRunner = (homeDir: string, args: string[]) => {
10
2
  status: number;
11
3
  stdout: string;
@@ -13,6 +5,5 @@ export type OpenCodeCommandRunner = (homeDir: string, args: string[]) => {
13
5
  };
14
6
  export declare function installOpenCodePlugin(homeDir: string, force?: boolean, run?: OpenCodeCommandRunner): string[];
15
7
  export declare function uninstallOpenCodePlugin(homeDir: string): string[];
16
- /** Verify that OpenCode accepts its resolved runtime configuration after links are installed. */
8
+ /** Config sources prove configuration, not that a running session loaded the plugin. */
17
9
  export declare function verifyOpenCodeRuntime(homeDir: string, run?: OpenCodeCommandRunner): string;
18
- export declare function skillLinks(homeDir?: string, parents?: Array<'.claude' | '.agents'>): HarnessLink[];