@astrosheep/square 0.3.63 → 0.3.65

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 (171) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +3 -2
  2. package/claude-plugin/hooks/hooks.json +1 -22
  3. package/claude-plugin/hooks/register.js +119 -0
  4. package/claude-plugin/types.d.ts +7 -0
  5. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  6. package/dist/activity-feed.d.ts +1 -10
  7. package/dist/activity-feed.js +1 -27
  8. package/dist/activity.js +57 -25
  9. package/dist/artifact.d.ts +1 -1
  10. package/dist/artifact.js +24 -25
  11. package/dist/attention-presentation.js +1 -0
  12. package/dist/automatic-session.d.ts +3 -1
  13. package/dist/automatic-session.js +21 -34
  14. package/dist/boundary-presentation.js +4 -13
  15. package/dist/canonical-path.d.ts +11 -0
  16. package/dist/canonical-path.js +47 -0
  17. package/dist/catch-decisions.js +14 -10
  18. package/dist/catch-selection.d.ts +6 -0
  19. package/dist/catch-selection.js +14 -0
  20. package/dist/claude-delivery.d.ts +11 -0
  21. package/dist/claude-delivery.js +145 -0
  22. package/dist/claude-mod.d.ts +8 -0
  23. package/dist/claude-mod.js +164 -0
  24. package/dist/cli/context.d.ts +0 -1
  25. package/dist/cli/context.js +9 -21
  26. package/dist/cli/harness-command.d.ts +0 -1
  27. package/dist/cli/harness-command.js +0 -6
  28. package/dist/cli/observation-commands.d.ts +1 -1
  29. package/dist/cli/observation-commands.js +31 -58
  30. package/dist/cli/program.js +20 -18
  31. package/dist/cli/registry.js +2 -2
  32. package/dist/cli/square-commands.js +10 -6
  33. package/dist/codex-boundary-state.d.ts +2 -2
  34. package/dist/codex-boundary-state.js +4 -4
  35. package/dist/codex-hook.js +12 -15
  36. package/dist/codex-queue.d.ts +2 -2
  37. package/dist/codex-queue.js +7 -5
  38. package/dist/decisions.d.ts +2 -6
  39. package/dist/decisions.js +16 -17
  40. package/dist/delivery-health.d.ts +1 -2
  41. package/dist/delivery-health.js +4 -5
  42. package/dist/delivery-operations.d.ts +2 -5
  43. package/dist/delivery-operations.js +77 -214
  44. package/dist/delivery.d.ts +25 -47
  45. package/dist/delivery.js +14 -46
  46. package/dist/diagnostic-redaction.js +7 -9
  47. package/dist/file-changes.d.ts +0 -1
  48. package/dist/file-changes.js +2 -19
  49. package/dist/harness-claude.js +8 -6
  50. package/dist/harness-codex.d.ts +0 -2
  51. package/dist/harness-codex.js +0 -2
  52. package/dist/harness-links.js +13 -17
  53. package/dist/harness.js +3 -7
  54. package/dist/help.js +4 -3
  55. package/dist/host-ledger-file-adapter.d.ts +17 -8
  56. package/dist/host-ledger-file-adapter.js +164 -80
  57. package/dist/host-ledger.d.ts +55 -50
  58. package/dist/inbox.d.ts +0 -3
  59. package/dist/inbox.js +88 -50
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/list.js +1 -4
  63. package/dist/mcp.js +24 -41
  64. package/dist/mention-parse.js +3 -9
  65. package/dist/model.d.ts +11 -2
  66. package/dist/model.js +1 -1
  67. package/dist/notifications.d.ts +3 -16
  68. package/dist/notifications.js +41 -122
  69. package/dist/opencode-delivery.d.ts +14 -0
  70. package/dist/opencode-delivery.js +171 -0
  71. package/dist/opencode.d.ts +39 -18
  72. package/dist/opencode.js +145 -40
  73. package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
  74. package/dist/packages/agent-delivery/src/claude-auth.js +75 -0
  75. package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
  76. package/dist/packages/agent-delivery/src/claude-native.js +68 -0
  77. package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
  78. package/dist/packages/agent-delivery/src/claude.js +66 -0
  79. package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
  80. package/dist/packages/agent-delivery/src/deadline.js +41 -0
  81. package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
  82. package/dist/packages/agent-delivery/src/errors.js +41 -0
  83. package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
  84. package/dist/packages/agent-delivery/src/guards.js +6 -0
  85. package/dist/packages/agent-delivery/src/index.d.ts +83 -0
  86. package/dist/packages/agent-delivery/src/index.js +110 -0
  87. package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
  88. package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
  89. package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
  90. package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
  91. package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
  92. package/dist/packages/agent-delivery/src/opencode.js +127 -0
  93. package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
  94. package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
  95. package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
  96. package/dist/packages/agent-delivery/src/paseo.js +106 -0
  97. package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
  98. package/dist/packages/agent-delivery/src/pi-client.js +106 -0
  99. package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
  100. package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
  101. package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
  102. package/dist/packages/agent-delivery/src/pi-send.js +4 -0
  103. package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
  104. package/dist/packages/agent-delivery/src/pi.js +209 -0
  105. package/dist/packages/agent-delivery/src/types.d.ts +120 -0
  106. package/dist/participant-host.js +12 -13
  107. package/dist/participant-identity.d.ts +21 -0
  108. package/dist/participant-identity.js +32 -11
  109. package/dist/paseo-delivery.d.ts +2 -2
  110. package/dist/paseo-delivery.js +35 -27
  111. package/dist/paseo-timeline.js +25 -21
  112. package/dist/paseo.d.ts +2 -2
  113. package/dist/paseo.js +1 -2
  114. package/dist/ports.d.ts +7 -20
  115. package/dist/presentation-operations.js +16 -4
  116. package/dist/presentation.d.ts +19 -10
  117. package/dist/presentation.js +99 -177
  118. package/dist/registry.d.ts +15 -66
  119. package/dist/registry.js +45 -123
  120. package/dist/routes.d.ts +1 -28
  121. package/dist/routes.js +22 -104
  122. package/dist/runtime.d.ts +3 -9
  123. package/dist/runtime.js +14 -67
  124. package/dist/search.d.ts +0 -2
  125. package/dist/search.js +3 -10
  126. package/dist/square-actions.js +48 -72
  127. package/dist/square-application.js +32 -30
  128. package/dist/square-core.d.ts +3 -0
  129. package/dist/square-core.js +22 -5
  130. package/dist/square-facade.d.ts +2 -0
  131. package/dist/square-file-adapter.d.ts +2 -1
  132. package/dist/square-file-adapter.js +6 -9
  133. package/dist/square-projections.d.ts +40 -45
  134. package/dist/square-projections.js +60 -74
  135. package/dist/square-state.js +1 -4
  136. package/dist/square-storage.d.ts +5 -8
  137. package/dist/square-storage.js +13 -36
  138. package/dist/square-wiring.d.ts +2 -7
  139. package/dist/square-wiring.js +6 -24
  140. package/dist/stream.js +4 -6
  141. package/dist/time.js +2 -7
  142. package/dist/views.d.ts +6 -18
  143. package/dist/views.js +14 -21
  144. package/dist/wake-attempts.d.ts +2 -56
  145. package/dist/wake-attempts.js +15 -110
  146. package/dist/wake-eligibility.d.ts +43 -0
  147. package/dist/wake-eligibility.js +106 -0
  148. package/dist/wake-evidence.d.ts +32 -9
  149. package/dist/wake-evidence.js +29 -24
  150. package/dist/wake-sink.d.ts +13 -9
  151. package/dist/wake-sink.js +35 -54
  152. package/dist/watch.js +13 -21
  153. package/extensions/square-pi.js +27 -13
  154. package/guides/change-waits.md +1 -1
  155. package/guides/claude-native.md +101 -0
  156. package/guides/host-ledger.md +3 -1
  157. package/guides/participant.md +14 -7
  158. package/guides/pi-native-delivery.md +49 -0
  159. package/package.json +4 -3
  160. package/dist/claude-hook.d.ts +0 -10
  161. package/dist/claude-hook.js +0 -60
  162. package/dist/paseo-connection.d.ts +0 -15
  163. package/dist/paseo-connection.js +0 -141
  164. package/dist/presence.d.ts +0 -2
  165. package/dist/presence.js +0 -24
  166. package/dist/presented.d.ts +0 -2
  167. package/dist/presented.js +0 -10
  168. package/dist/state-cell.d.ts +0 -14
  169. package/dist/wake-port.d.ts +0 -23
  170. package/dist/wake-port.js +0 -27
  171. /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
package/dist/inbox.js CHANGED
@@ -1,39 +1,42 @@
1
- import { hostLedgerRoot } from './host-ledger-root.js';
1
+ import { nameKey } from './model.js';
2
2
  import { openSquareArtifact, observeSquareChanges } from './square-file-adapter.js';
3
- import { createHostLedgerPort } from './host-ledger-file-adapter.js';
4
- import { canonicalFilePath } from './file-changes.js';
5
- import { pendingAtBoundary, presentationSuppressesWake, projectPresentation, projectPresentationEvidence, projectSessionBindings } from './square-projections.js';
6
- import { formatActivityId } from './square-core.js';
3
+ import { hostLedgerForEnv } from './registry.js';
4
+ import { canonicalPath } from './canonical-path.js';
5
+ import { projectBoundaryEligibility, projectPresentation, projectSessionBindings } from './square-projections.js';
7
6
  import { WATCH_STALE_MS } from './runtime.js';
8
- export function hostLedgerForEnv(env) {
9
- return createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
10
- }
11
7
  function notificationKey(membership, actIndex) {
12
8
  return membership.squarePath + '\0' + membership.name.toLocaleLowerCase() + '\0' + actIndex;
13
9
  }
10
+ async function readInboxMembership(binding, signal) {
11
+ const artifact = openSquareArtifact(binding.location, signal);
12
+ try {
13
+ const projection = await projectPresentation({ artifact, binding, now: Date.now() });
14
+ if (!projection.joined)
15
+ return undefined;
16
+ return { name: projection.binding.participant, squarePath: projection.binding.location,
17
+ notifications: [...projection.notifications],
18
+ ...(projection.catchLease === undefined ? {} : { catchLease: projection.catchLease }) };
19
+ }
20
+ finally {
21
+ await artifact.close();
22
+ }
23
+ }
14
24
  export async function sessionInbox(sessionId, env = process.env, signal) {
15
25
  const inbox = [];
16
26
  const hostLedger = hostLedgerForEnv(env);
17
27
  for (const binding of await projectSessionBindings({ hostLedger, sessionId })) {
18
28
  if (signal?.aborted)
19
29
  throw signal.reason;
20
- const artifact = openSquareArtifact(binding.location, signal);
21
30
  try {
22
- const projection = await projectPresentation({ artifact, binding, now: Date.now() });
23
- if (!projection.joined)
24
- continue;
25
- inbox.push({ name: projection.binding.participant, squarePath: projection.binding.location,
26
- notifications: [...projection.notifications],
27
- ...(projection.catchLease === undefined ? {} : { catchLease: projection.catchLease }) });
31
+ const membership = await readInboxMembership(binding, signal);
32
+ if (membership !== undefined)
33
+ inbox.push(membership);
28
34
  }
29
35
  catch (error) {
30
36
  if (signal?.aborted)
31
37
  throw signal.reason ?? error;
32
38
  // A stale or unreadable membership cannot disable delivery for other memberships.
33
39
  }
34
- finally {
35
- await artifact.close();
36
- }
37
40
  }
38
41
  return inbox;
39
42
  }
@@ -41,13 +44,28 @@ async function capture(observer, kind, signal) {
41
44
  const generation = observer.generation;
42
45
  try {
43
46
  const value = await observer.read(signal);
44
- return { kind, wait: (timeout, abort) => observer.changed(value, timeout, abort) };
47
+ return { kind, value, wait: (timeout, abort) => observer.changed(value, timeout, abort) };
45
48
  }
46
49
  catch (error) {
47
50
  if (signal.aborted)
48
51
  throw signal.reason ?? error;
49
- // Keep watching an unreadable/missing artifact so repair can restore delivery.
50
- return { kind, wait: (timeout, abort) => observer.hinted(generation, timeout, abort) };
52
+ // Failed revision reads are not changes; only a successful retry wakes projection.
53
+ return { kind, wait: async (timeout, abort) => {
54
+ const deadline = Date.now() + Math.max(0, timeout);
55
+ let since = generation;
56
+ while (await observer.hinted(since, Math.max(0, deadline - Date.now()), abort)) {
57
+ since = observer.generation;
58
+ try {
59
+ await observer.read(abort);
60
+ return true;
61
+ }
62
+ catch (error) {
63
+ if (abort.aborted)
64
+ throw abort.reason ?? error;
65
+ }
66
+ }
67
+ return false;
68
+ } };
51
69
  }
52
70
  }
53
71
  /** Session-scoped projection over process-shared storage change detectors. */
@@ -71,11 +89,11 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
71
89
  while (!signal.aborted) {
72
90
  // Subscribe/capture the ledger BEFORE discovering bindings; a concurrent join cannot fall in a gap.
73
91
  const sources = await Promise.all([capture(ledger.presence, 'presence', signal), capture(ledger.evidence, 'evidence', signal)]);
74
- const bindings = await projectSessionBindings({ hostLedger, sessionId });
75
- const paths = new Set(await Promise.all(bindings.map((binding) => canonicalFilePath(binding.location))));
76
- for (const [file, observer] of artifacts) {
92
+ const bindings = await Promise.all((await projectSessionBindings({ hostLedger, sessionId })).map(async (binding) => ({ ...binding, location: await canonicalPath(binding.location) })));
93
+ const paths = new Set(bindings.map((binding) => binding.location));
94
+ for (const [file, entry] of artifacts) {
77
95
  if (!paths.has(file)) {
78
- observer.close();
96
+ entry.observer.close();
79
97
  artifacts.delete(file);
80
98
  }
81
99
  }
@@ -87,22 +105,52 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
87
105
  observer.close();
88
106
  return [];
89
107
  }
90
- artifacts.set(file, observer);
108
+ artifacts.set(file, { observer, inbox: new Map() });
91
109
  }
92
- sources.push(...await Promise.all([...artifacts.values()].map((observer) => capture(observer, 'artifact', signal))));
93
- const inbox = await sessionInbox(sessionId, env, signal);
94
- const pending = [];
95
- for (const membership of pendingAtBoundary(inbox)) {
96
- const evidence = await projectPresentationEvidence({ hostLedger, location: membership.squarePath, participant: membership.name, sessionId });
97
- const notifications = membership.notifications.filter((notification) => !options.excludeKeys?.has(notificationKey(membership, notification.actIndex)) &&
98
- !presentationSuppressesWake(evidence.filter((row) => row.activity === formatActivityId(notification.actIndex))));
99
- if (notifications.length > 0)
100
- pending.push({ ...membership, notifications });
110
+ for (const [file, entry] of artifacts) {
111
+ const source = await capture(entry.observer, 'artifact', signal);
112
+ sources.push(source);
113
+ if (source.value === undefined || source.value !== entry.revision)
114
+ entry.inbox.clear();
115
+ entry.revision = source.value;
116
+ const names = new Set(bindings.filter((binding) => binding.location === file).map((binding) => nameKey(binding.participant)));
117
+ for (const name of entry.inbox.keys())
118
+ if (!names.has(name))
119
+ entry.inbox.delete(name);
101
120
  }
102
- const eligible = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => notificationKey(membership, note.actIndex))).sort());
103
- if (previousEligible !== undefined && previousEligible !== eligible)
121
+ const inbox = [];
122
+ for (const binding of bindings) {
123
+ if (signal.aborted)
124
+ return [];
125
+ const entry = artifacts.get(binding.location);
126
+ const name = nameKey(binding.participant);
127
+ try {
128
+ if (!entry.inbox.has(name))
129
+ entry.inbox.set(name, await readInboxMembership(binding, signal));
130
+ const membership = entry.inbox.get(name);
131
+ if (membership === undefined)
132
+ continue;
133
+ const { catchLease, ...rest } = membership;
134
+ const now = Date.now();
135
+ inbox.push(catchLease !== undefined && catchLease.expiresAt > now && now - catchLease.heartbeatAt <= WATCH_STALE_MS
136
+ ? membership : rest);
137
+ }
138
+ catch (error) {
139
+ if (signal.aborted)
140
+ throw signal.reason ?? error;
141
+ // Unreadable projections are retried, never cached as current.
142
+ }
143
+ }
144
+ const eligible = await projectBoundaryEligibility({ hostLedger, sessionId, inbox });
145
+ const excludeKeys = options.excludeKeys;
146
+ const pending = excludeKeys === undefined ? eligible : eligible.flatMap((membership) => {
147
+ const notifications = membership.notifications.filter((notification) => !excludeKeys.has(notificationKey(membership, notification.actIndex)));
148
+ return notifications.length === 0 ? [] : [{ ...membership, notifications }];
149
+ });
150
+ const eligibleKeys = JSON.stringify(pending.flatMap((membership) => membership.notifications.map((note) => notificationKey(membership, note.actIndex))).sort());
151
+ if (previousEligible !== undefined && previousEligible !== eligibleKeys)
104
152
  canReturn = true;
105
- previousEligible = eligible;
153
+ previousEligible = eligibleKeys;
106
154
  if (signal.aborted)
107
155
  return [];
108
156
  if (canReturn && pending.length > 0)
@@ -161,19 +209,9 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
161
209
  lifetime.abort();
162
210
  ledger.presence.close();
163
211
  ledger.evidence.close();
164
- for (const observer of artifacts.values())
165
- observer.close();
212
+ for (const entry of artifacts.values())
213
+ entry.observer.close();
166
214
  artifacts.clear();
167
215
  },
168
216
  };
169
217
  }
170
- /** Finite one-shot consumers retain their API; Pi owns a long-lived observer instead. */
171
- export async function waitForSessionPending(sessionId, timeoutMs, options = {}, env = process.env) {
172
- const observer = await observeSessionPending(sessionId, env);
173
- try {
174
- return await observer.wait(timeoutMs, options);
175
- }
176
- finally {
177
- observer.close();
178
- }
179
- }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { Square } from './square-wiring.js';
2
2
  export { createSquareApplication } from './square-application.js';
3
3
  export { SquareError } from './model.js';
4
- export { bindCurrentParticipant, squareAssignedParticipantName, unbindCurrentParticipant } from './registry.js';
4
+ export { squareAssignedParticipantName } from './registry.js';
5
5
  export { createDefaultWakeTransport } from './notifications.js';
6
6
  export { createHostLedgerPort, FileHostLedgerPort } from './host-ledger-file-adapter.js';
7
7
  export type { HostLedgerPort, PresenceRecord, EvidenceRecord } from './host-ledger.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { Square } from './square-wiring.js';
2
2
  export { createSquareApplication } from './square-application.js';
3
3
  export { SquareError } from './model.js';
4
- export { bindCurrentParticipant, squareAssignedParticipantName, unbindCurrentParticipant } from './registry.js';
4
+ export { squareAssignedParticipantName } from './registry.js';
5
5
  export { createDefaultWakeTransport } from './notifications.js';
6
6
  export { createHostLedgerPort, FileHostLedgerPort } from './host-ledger-file-adapter.js';
package/dist/list.js CHANGED
@@ -15,9 +15,6 @@ const CONTEXT_PREVIEW_LINES = 2;
15
15
  const PARTICIPANT_PREVIEW_COUNT = 3;
16
16
  const DISPLAY_CHARACTER_LIMIT = 160;
17
17
  const LIST_SKIP_DIRS = new Set(['.git', 'node_modules', 'dist']);
18
- function contextLines(lines) {
19
- return lines.map((line) => line.trim()).filter(Boolean);
20
- }
21
18
  async function readSquareListItem(filePath, root) {
22
19
  let stat;
23
20
  try {
@@ -34,7 +31,7 @@ async function readSquareListItem(filePath, root) {
34
31
  return {
35
32
  path: relative,
36
33
  lastActiveAt: stat.mtimeMs,
37
- context: contextLines([...projection.context]),
34
+ context: [...projection.context].map((line) => line.trim()).filter(Boolean),
38
35
  participants: [...projection.participants],
39
36
  activities: projection.activities,
40
37
  };
package/dist/mcp.js CHANGED
@@ -3,45 +3,23 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
3
  import { z } from 'zod/v4';
4
4
  import { SquareError, isSquareError } from './model.js';
5
5
  import { createSquareApplication } from './square-application.js';
6
- const activityIdSchema = { type: 'string', pattern: '^act/(0|[1-9][0-9]*)$' };
7
- const stringArraySchema = { type: 'array', items: { type: 'string' } };
8
- function objectSchema(properties, required = []) { return { properties, required }; }
6
+ import { toPublicActivity } from './views.js';
7
+ const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
8
+ const stringArray = z.array(z.string());
9
9
  const TOOL_DEFINITIONS = [
10
- { name: 'join', description: 'Join the square as the configured participant.', inputSchema: objectSchema({ takeover: { type: 'boolean' } }) },
11
- { name: 'express', description: 'Express a message or embodied action in the square.', inputSchema: objectSchema({ body: { type: 'string' }, mentions: stringArraySchema, reach: { type: 'string', enum: ['bell'] }, reply: activityIdSchema, force: { type: 'boolean' }, noWait: { type: 'boolean' } }, ['body']) },
12
- { name: 'catch', description: 'Catch and consume activities perceptible to the configured participant.', inputSchema: objectSchema({ id: activityIdSchema, idle: { type: 'number', minimum: 0 }, from: stringArraySchema, mention: { type: 'boolean' }, limit: { type: 'integer', minimum: 1 } }) },
13
- { name: 'history', description: 'Read the activity stream using stable activity-id cursors.', inputSchema: objectSchema({ limit: { type: 'integer', minimum: 1 }, order: { type: 'string', enum: ['asc', 'desc'] }, before: activityIdSchema, after: activityIdSchema, grep: { type: 'string' }, fixed: { type: 'string' }, from: stringArraySchema, mention: { type: 'string' } }) },
14
- { name: 'listen', description: 'Listen for future activity from a participant.', inputSchema: objectSchema({ target: { type: 'string' } }, ['target']) },
15
- { name: 'ignore', description: 'Stop listening for future activity from a participant.', inputSchema: objectSchema({ target: { type: 'string' } }, ['target']) },
16
- { name: 'listening', description: 'List participants this participant is listening to.', inputSchema: objectSchema({}) },
17
- { name: 'hold', description: 'Raise a hand to hold the square.', inputSchema: objectSchema({ reason: { type: 'string' } }) },
18
- { name: 'resume', description: 'Release the current hold.', inputSchema: objectSchema({}) },
19
- { name: 'done', description: 'Leave the square without adding a message.', inputSchema: objectSchema({}) },
20
- { name: 'status', description: 'Read the public status of the square.', inputSchema: objectSchema({}) },
21
- { name: 'participants', description: 'List participants standing in or done with the square.', inputSchema: objectSchema({}) },
10
+ { name: 'join', description: 'Join the square as the configured participant.', inputSchema: z.object({ takeover: z.boolean().optional() }).strict() },
11
+ { name: 'express', description: 'Express a message or embodied action in the square.', inputSchema: z.object({ body: z.string(), mentions: stringArray.optional(), reach: z.enum(['bell']).optional(), reply: activityId.optional(), force: z.boolean().optional(), noWait: z.boolean().optional() }).strict() },
12
+ { name: 'catch', description: 'Catch and consume activities perceptible to the configured participant.', inputSchema: z.object({ id: activityId.optional(), idle: z.number().finite().min(0).optional(), from: stringArray.optional(), mention: z.boolean().optional(), limit: z.number().int().min(1).optional() }).strict() },
13
+ { name: 'history', description: 'Read the activity stream using stable activity-id cursors.', inputSchema: z.object({ limit: z.number().int().min(1).optional(), order: z.enum(['asc', 'desc']).optional(), before: activityId.optional(), after: activityId.optional(), grep: z.string().optional(), fixed: z.string().optional(), from: stringArray.optional(), mention: z.string().optional() }).strict() },
14
+ { name: 'listen', description: 'Listen for future activity from a participant.', inputSchema: z.object({ target: z.string() }).strict() },
15
+ { name: 'ignore', description: 'Stop listening for future activity from a participant.', inputSchema: z.object({ target: z.string() }).strict() },
16
+ { name: 'listening', description: 'List participants this participant is listening to.', inputSchema: z.object({}).strict() },
17
+ { name: 'hold', description: 'Raise a hand to hold the square.', inputSchema: z.object({ reason: z.string().optional() }).strict() },
18
+ { name: 'resume', description: 'Release the current hold.', inputSchema: z.object({}).strict() },
19
+ { name: 'done', description: 'Leave the square without adding a message.', inputSchema: z.object({}).strict() },
20
+ { name: 'status', description: 'Read the public status of the square.', inputSchema: z.object({}).strict() },
21
+ { name: 'participants', description: 'List participants standing in or done with the square.', inputSchema: z.object({}).strict() },
22
22
  ];
23
- function zodInputSchema(schema) {
24
- const shape = {};
25
- for (const [key, property] of Object.entries(schema.properties)) {
26
- let field;
27
- if (property.type === 'string' && property.enum !== undefined)
28
- field = z.enum(property.enum);
29
- else if (property.type === 'string')
30
- field = property.pattern === undefined ? z.string() : z.string().regex(new RegExp(property.pattern));
31
- else if (property.type === 'boolean')
32
- field = z.boolean();
33
- else if (property.type === 'number')
34
- field = z.number().finite().min(property.minimum ?? -Infinity);
35
- else if (property.type === 'integer')
36
- field = z.number().int().min(property.minimum ?? -Infinity);
37
- else if (property.type === 'array')
38
- field = z.array(z.string());
39
- else
40
- throw new Error(`Unsupported input schema type: ${property.type}`);
41
- shape[key] = schema.required.includes(key) ? field : field.optional();
42
- }
43
- return z.object(shape).strict();
44
- }
45
23
  function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
46
24
  function publicActivity(activity) { return { ...activity }; }
47
25
  function publicResult(name, value) {
@@ -56,13 +34,13 @@ function publicResult(name, value) {
56
34
  doneCount: rest.doneCount,
57
35
  hold: rest.holdActive ? { by: rest.holdActor, ...(rest.holdReason === undefined ? {} : { reason: rest.holdReason }), ...(rest.holdAt === undefined ? {} : { at: rest.holdAt }) } : null,
58
36
  participants: participants.filter((participant) => participant.state !== 'not joined').map((participant) => ({ name: participant.name, state: participant.state, presence: participant.presence, activityCount: participant.activityCount, ...(participant.lastActiveAt === undefined ? {} : { lastActiveAt: participant.lastActiveAt }), unreadActivityCount: participant.unreadActivityCount, pendingMentionCount: participant.pendingMentionCount, listening: participant.listening })),
59
- ...(latestAct === undefined ? {} : { latestActivity: publicActivity({ id: `act/${latestAct.index}`, at: latestAct.at, kind: latestAct.kind, actor: latestAct.actor ?? '', ...('body' in latestAct && latestAct.body !== undefined ? { body: latestAct.body } : {}), mentions: 'mentions' in latestAct ? latestAct.mentions ?? [] : [], ...('target' in latestAct ? { target: latestAct.target } : {}), ...(latestAct.kind === 'say' && latestAct.reach !== undefined ? { reach: latestAct.reach } : {}), ...(latestAct.kind === 'say' && latestAct.reply !== undefined ? { reply: `act/${latestAct.reply}` } : {}) }) }),
37
+ ...(latestAct === undefined ? {} : { latestActivity: publicActivity(toPublicActivity(latestAct)) }),
60
38
  now: rest.now,
61
39
  };
62
40
  }
63
41
  if (name === 'express' || name === 'hold' || name === 'resume' || name === 'done') {
64
42
  const result = value;
65
- return { ...(result.activity === undefined ? {} : { activity: result.activity === null ? null : publicActivity(result.activity) }), ...(result.waited === undefined ? {} : { waited: result.waited }) };
43
+ return { ...(result.activity === undefined ? {} : { activity: result.activity === null ? null : publicActivity(result.activity) }), ...(result.waited === undefined ? {} : { waited: result.waited }), ...(result.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: result.replyAuthorNotHere }) };
66
44
  }
67
45
  if (name === 'history')
68
46
  return { activities: value };
@@ -98,7 +76,7 @@ export function createSquareMcpServer(options) {
98
76
  instructions: 'The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.',
99
77
  });
100
78
  for (const tool of TOOL_DEFINITIONS) {
101
- server.registerTool(tool.name, { description: tool.description, inputSchema: zodInputSchema(tool.inputSchema) }, async (args, extra) => {
79
+ server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, async (args, extra) => {
102
80
  try {
103
81
  const value = await operation(app, tool.name, args, { signal: extra.signal });
104
82
  const projected = publicResult(tool.name, value);
@@ -118,5 +96,10 @@ export function createSquareMcpServer(options) {
118
96
  return server;
119
97
  }
120
98
  export async function runSquareMcpStdio(options) {
121
- await createSquareMcpServer(options).connect(new StdioServerTransport());
99
+ const server = createSquareMcpServer(options);
100
+ const transport = new StdioServerTransport();
101
+ const close = () => { void server.close(); };
102
+ process.stdin.once('end', close);
103
+ process.stdin.once('close', close);
104
+ await server.connect(transport);
122
105
  }
@@ -54,14 +54,6 @@ function codeSpanRanges(body) {
54
54
  }
55
55
  return ranges;
56
56
  }
57
- function codePointBefore(body, index) {
58
- if (index <= 0)
59
- return undefined;
60
- const unit = body.charCodeAt(index - 1);
61
- if (unit >= 0xdc00 && unit <= 0xdfff && index >= 2)
62
- return String.fromCodePoint(body.codePointAt(index - 2));
63
- return String.fromCodePoint(unit);
64
- }
65
57
  export function scanMentionCandidates(body) {
66
58
  const spans = codeSpanRanges(body);
67
59
  const found = [];
@@ -70,7 +62,9 @@ export function scanMentionCandidates(body) {
70
62
  const start = match.index;
71
63
  if (spans.some(([from, to]) => start >= from && start < to))
72
64
  continue;
73
- const previous = codePointBefore(body, start);
65
+ const unit = start <= 0 ? undefined : body.charCodeAt(start - 1);
66
+ const previous = unit === undefined ? undefined
67
+ : unit >= 0xdc00 && unit <= 0xdfff && start >= 2 ? String.fromCodePoint(body.codePointAt(start - 2)) : String.fromCodePoint(unit);
74
68
  if (previous !== undefined && BOUNDARY_BLOCKER.test(previous))
75
69
  continue;
76
70
  const token = match[1];
package/dist/model.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import type { Act, ActivityId } from './square-core.js';
2
2
  export { formatActivityId, parseActivityId } from './square-core.js';
3
3
  export type { Act, ActivityId, Audience, Reach } from './square-core.js';
4
- export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "pi-extension", "paseo"];
4
+ export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo"];
5
5
  export type WakeRouteKind = typeof WAKE_ROUTE_KINDS[number];
6
6
  export declare function isWakeRouteKind(value: unknown): value is WakeRouteKind;
7
7
  export interface WakeRoute {
8
+ /** Ownership generation, when the receiver publishes a fenced route. */
9
+ epoch?: number;
8
10
  location: string;
9
11
  participant: string;
10
12
  sessionId: string;
@@ -20,6 +22,13 @@ export type InternalSquareErrorCode = SquareErrorCode | 'not_found' | 'cap_reach
20
22
  export interface SquareErrorFacts {
21
23
  /** Express admission rejected before any activity was committed. */
22
24
  activityUnsent?: true;
25
+ /** Why a name could not be resolved: never in the roster, no longer standing in it, a bell
26
+ * colliding with a mention, an @name that matches nobody, or an activity catch cannot address. */
27
+ reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'unmatched_mention' | 'not_addressed';
28
+ /** The activity id a `catch --id` refusal named. */
29
+ activityId?: string;
30
+ /** Whether the artifact holds that activity at all; an unknown id needs an ordinary page. */
31
+ activityExists?: boolean;
23
32
  pending?: number;
24
33
  holder?: string;
25
34
  holdReason?: string;
@@ -62,7 +71,7 @@ export interface WatchLease {
62
71
  expiresAt: number;
63
72
  filter?: WatchLeaseFilter;
64
73
  }
65
- export type DirectedNotificationRoute = 'mention' | 'attention' | 'bell';
74
+ export type DirectedNotificationRoute = 'mention' | 'reply' | 'attention' | 'bell';
66
75
  export interface InboxNotification {
67
76
  actIndex: number;
68
77
  actor: string;
package/dist/model.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Shared model and constants for Square.
2
2
  export { formatActivityId, parseActivityId } from './square-core.js';
3
- export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'pi-extension', 'paseo'];
3
+ export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo'];
4
4
  export function isWakeRouteKind(value) {
5
5
  return typeof value === 'string' && WAKE_ROUTE_KINDS.includes(value);
6
6
  }
@@ -1,27 +1,16 @@
1
- import { planActNotifications, type WakeAdapter } from './delivery.js';
2
- import { type SquareState } from './model.js';
3
- import { matchesMentionTarget } from './runtime.js';
4
- import { type ActivityId } from './square-core.js';
1
+ import { type WakeAdapter } from './delivery.js';
5
2
  import type { WakeTransportPort } from './ports.js';
6
3
  export type { PlannedNotification } from './delivery.js';
7
- export { planActNotifications, matchesMentionTarget };
8
- export { notificationMessageId } from './delivery.js';
9
4
  export declare const PRIVILEGED_HOOK_BUDGET_MS = 3000;
10
5
  export declare function wakeGraceMs(env?: NodeJS.ProcessEnv): number;
11
- export declare function hasDeliveredNotification(squarePath: string, name: string, ref: number | ActivityId): Promise<boolean>;
12
- export declare function hasAttentionNotification(squarePath: string, name: string, ref: number | ActivityId, env?: NodeJS.ProcessEnv): Promise<boolean>;
13
- export declare function waitForDeliveredNotification(squarePath: string, name: string, ref: number | ActivityId, opts?: {
14
- timeoutMs?: number;
15
- signal?: AbortSignal;
16
- }): Promise<boolean>;
17
6
  interface ProcessNotificationOptions {
18
7
  adapters?: WakeAdapter[];
19
8
  env?: NodeJS.ProcessEnv;
20
9
  now?: () => number;
21
10
  }
22
11
  export declare function defaultWakeAdapters(): Promise<WakeAdapter[]>;
23
- export declare function createDefaultWakeTransport(hostLedger: import('./host-ledger.js').HostLedgerPort, clock: () => number, env?: NodeJS.ProcessEnv): Promise<WakeTransportPort>;
24
- export declare function createWakeTransport(adapters: readonly WakeAdapter[], hostLedger: import('./host-ledger.js').HostLedgerPort, clock: () => number): WakeTransportPort;
12
+ export declare function createDefaultWakeTransport(hostLedger: import('./host-ledger.js').HostLedgerPort, clock: () => number, env?: NodeJS.ProcessEnv, suppliedAdapters?: readonly WakeAdapter[]): Promise<WakeTransportPort>;
13
+ export declare function createWakeTransport(adapters: readonly WakeAdapter[], hostLedger: import('./host-ledger.js').HostLedgerPort, clock: () => number, nativePorts?: Readonly<Partial<Record<import('./model.js').WakeRouteKind, WakeTransportPort>>>): WakeTransportPort;
25
14
  export declare function processActNotificationsOnce(squarePath: string, actIndex: number, opts?: ProcessNotificationOptions): Promise<import("./ports.js").DeliveryResult>;
26
15
  /** Privileged hook fallback: sweep indexed squares plus the current cwd's local squares within one boundary budget. */
27
16
  export declare function sweepPrivilegedPending(cwd: string, env?: NodeJS.ProcessEnv, suppliedAdapters?: WakeAdapter[], deadline?: number, signal?: AbortSignal): Promise<void>;
@@ -31,7 +20,5 @@ export interface SweepPendingNotificationsOptions {
31
20
  limit?: number;
32
21
  dispatchCandidate?: (actIndex: number) => void | Promise<void>;
33
22
  }
34
- /** Select sweep candidates from one frozen snapshot and one delivery replay. */
35
- export declare function pendingNotificationSweepFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, limit: number, deriveDelivery?: (snapshot: import('./model.js').SquareState) => ReturnType<typeof import('./delivery.js').deriveDeliveryModel>): Promise<number[]>;
36
23
  /** Select old pending attention at a bounded action boundary for an explicit executor. */
37
24
  export declare function sweepPendingNotifications(squarePath: string, opts?: SweepPendingNotificationsOptions): Promise<number[]>;