@astrosheep/square 0.3.69 → 0.3.71

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 (132) hide show
  1. package/dist/activity-feed.d.ts +4 -5
  2. package/dist/activity-feed.js +14 -10
  3. package/dist/artifact.d.ts +26 -33
  4. package/dist/artifact.js +346 -317
  5. package/dist/attention-presentation.js +2 -9
  6. package/dist/automatic-session.js +6 -6
  7. package/dist/boundary-presentation.js +3 -3
  8. package/dist/catch-decisions.d.ts +19 -12
  9. package/dist/catch-decisions.js +32 -68
  10. package/dist/claude-delivery.js +7 -7
  11. package/dist/claude-mod.js +18 -7
  12. package/dist/cli/catch-presentation.d.ts +7 -0
  13. package/dist/cli/catch-presentation.js +55 -0
  14. package/dist/cli/context.d.ts +1 -1
  15. package/dist/cli/context.js +5 -23
  16. package/dist/cli/express-presentation.d.ts +10 -0
  17. package/dist/cli/express-presentation.js +196 -0
  18. package/dist/cli/maintenance-commands.d.ts +4 -0
  19. package/dist/cli/maintenance-commands.js +29 -6
  20. package/dist/cli/observation-commands.d.ts +7 -5
  21. package/dist/cli/observation-commands.js +275 -262
  22. package/dist/cli/program.d.ts +1 -1
  23. package/dist/cli/program.js +43 -56
  24. package/dist/cli/registry.js +2 -1
  25. package/dist/cli/square-commands.d.ts +3 -9
  26. package/dist/cli/square-commands.js +134 -255
  27. package/dist/codex-queue.js +4 -1
  28. package/dist/decisions.d.ts +26 -74
  29. package/dist/decisions.js +190 -335
  30. package/dist/delivery-health.js +13 -22
  31. package/dist/delivery-operations.d.ts +0 -11
  32. package/dist/delivery-operations.js +44 -33
  33. package/dist/delivery.d.ts +33 -32
  34. package/dist/delivery.js +70 -81
  35. package/dist/harness-claude.js +1 -1
  36. package/dist/harness-codex.js +1 -1
  37. package/dist/harness-pi.js +1 -1
  38. package/dist/help.js +6 -1
  39. package/dist/inbox.d.ts +27 -0
  40. package/dist/inbox.js +55 -18
  41. package/dist/index.d.ts +2 -4
  42. package/dist/index.js +1 -1
  43. package/dist/list.d.ts +7 -1
  44. package/dist/list.js +49 -122
  45. package/dist/mcp.d.ts +1 -1
  46. package/dist/mcp.js +7 -37
  47. package/dist/model.d.ts +4 -44
  48. package/dist/model.js +1 -3
  49. package/dist/notifications.d.ts +1 -1
  50. package/dist/notifications.js +13 -7
  51. package/dist/open-square.d.ts +16 -2
  52. package/dist/open-square.js +40 -1
  53. package/dist/perception-projection.d.ts +6 -15
  54. package/dist/perception-projection.js +6 -26
  55. package/dist/{opencode-delivery.d.ts → plugins/opencode/delivery.d.ts} +1 -1
  56. package/dist/{opencode-delivery.js → plugins/opencode/delivery.js} +18 -18
  57. package/dist/{opencode.d.ts → plugins/opencode/index.d.ts} +2 -2
  58. package/dist/{opencode.js → plugins/opencode/index.js} +9 -9
  59. package/dist/ports.d.ts +156 -11
  60. package/dist/presentation-operations.d.ts +1 -1
  61. package/dist/presentation-operations.js +35 -44
  62. package/dist/presentation.d.ts +12 -19
  63. package/dist/presentation.js +33 -54
  64. package/dist/routes.d.ts +7 -14
  65. package/dist/routes.js +33 -62
  66. package/dist/runtime.d.ts +12 -39
  67. package/dist/runtime.js +33 -164
  68. package/dist/square-actions.d.ts +11 -21
  69. package/dist/square-actions.js +171 -271
  70. package/dist/square-application.d.ts +31 -55
  71. package/dist/square-application.js +637 -141
  72. package/dist/square-core.d.ts +3 -94
  73. package/dist/square-core.js +8 -353
  74. package/dist/square-domain.d.ts +24 -0
  75. package/dist/square-domain.js +77 -0
  76. package/dist/square-facade.d.ts +360 -9
  77. package/dist/square-migration.d.ts +24 -0
  78. package/dist/square-migration.js +211 -0
  79. package/dist/square-projections.d.ts +2 -42
  80. package/dist/square-projections.js +1 -37
  81. package/dist/square-sqlite.d.ts +59 -0
  82. package/dist/square-sqlite.js +702 -0
  83. package/dist/stream.d.ts +3 -10
  84. package/dist/stream.js +81 -52
  85. package/dist/views.d.ts +27 -95
  86. package/dist/views.js +217 -87
  87. package/dist/wake-eligibility.d.ts +15 -5
  88. package/dist/wake-eligibility.js +32 -16
  89. package/dist/wakes.d.ts +3 -3
  90. package/dist/wakes.js +31 -27
  91. package/guides/application-facade-delivery-evidence.md +200 -0
  92. package/guides/application-facade-design.md +95 -0
  93. package/guides/change-waits.md +4 -4
  94. package/guides/migration.md +77 -0
  95. package/guides/pi-native-delivery.md +1 -1
  96. package/guides/primitive-catch-consolidation.md +85 -0
  97. package/guides/primitive-history-consolidation.md +76 -0
  98. package/guides/primitive-status-consolidation.md +87 -0
  99. package/guides/primitive-storage-consolidation.md +128 -0
  100. package/guides/row-runtime-delivery-evidence.md +160 -0
  101. package/guides/row-storage-design.md +567 -0
  102. package/package.json +5 -7
  103. package/{claude-plugin → plugins/claude}/.claude-plugin/plugin.json +1 -1
  104. package/{codex-plugin → plugins/codex}/.codex-plugin/plugin.json +1 -1
  105. package/plugins/opencode/delivery.ts +149 -0
  106. package/plugins/opencode/index.ts +136 -0
  107. package/plugins/paseo/OVERVIEW.md +14 -0
  108. package/plugins/paseo/README.md +54 -0
  109. package/plugins/paseo/client/square-message.tsx +106 -0
  110. package/plugins/paseo/client/transform.ts +24 -0
  111. package/plugins/paseo/index.client.tsx +19 -0
  112. package/plugins/paseo/package.json +19 -0
  113. package/plugins/paseo/paseo-plugin.json +7 -0
  114. package/plugins/paseo/shared/message.ts +29 -0
  115. package/{extensions → plugins/pi}/square-pi.js +8 -5
  116. package/dist/activity.d.ts +0 -14
  117. package/dist/activity.js +0 -248
  118. package/dist/square-file-adapter.d.ts +0 -26
  119. package/dist/square-file-adapter.js +0 -85
  120. package/dist/square-state.d.ts +0 -6
  121. package/dist/square-state.js +0 -138
  122. package/dist/square-storage.d.ts +0 -14
  123. package/dist/square-storage.js +0 -188
  124. package/dist/square-wiring.d.ts +0 -33
  125. package/dist/square-wiring.js +0 -109
  126. package/dist/watch.d.ts +0 -8
  127. package/dist/watch.js +0 -252
  128. /package/{claude-plugin → plugins/claude}/hooks/hooks.json +0 -0
  129. /package/{claude-plugin → plugins/claude}/hooks/register.js +0 -0
  130. /package/{claude-plugin → plugins/claude}/skills/square/SKILL.md +0 -0
  131. /package/{claude-plugin → plugins/claude}/types.d.ts +0 -0
  132. /package/{codex-plugin → plugins/codex}/hooks/hooks.json +0 -0
package/dist/inbox.js CHANGED
@@ -1,21 +1,55 @@
1
1
  import { nameKey } from './model.js';
2
- import { openSquareArtifact, observeSquareChanges } from './square-file-adapter.js';
2
+ import { createArtifactPort, observeSquareRevision } from './artifact.js';
3
3
  import { hostLedgerForEnv } from './registry.js';
4
4
  import { canonicalPath } from './canonical-path.js';
5
- import { projectBoundaryEligibility, projectPresentation, projectSessionBindings } from './square-projections.js';
6
- import { WATCH_STALE_MS } from './runtime.js';
5
+ import { projectBoundaryEligibility, projectSessionBindings } from './square-projections.js';
6
+ import { freshWatchLease, WATCH_STALE_MS } from './runtime.js';
7
+ import { pendingHead, pendingNotifications, leaseOwnsNotification } from './delivery.js';
8
+ import { attentionBodyIsClipped, renderAttentionPreview } from './attention-presentation.js';
9
+ function bindingCoordinate(binding) {
10
+ return JSON.stringify([binding.location, nameKey(binding.participant), binding.sessionId, binding.channel, binding.epoch, binding.cancelledThrough ?? -1]);
11
+ }
7
12
  function notificationKey(membership, actIndex) {
8
13
  return membership.squarePath + '\0' + membership.name.toLocaleLowerCase() + '\0' + actIndex;
9
14
  }
15
+ export function projectPresentation(facts, requested, now = Date.now()) {
16
+ const participant = facts.participant(requested.participant);
17
+ if (!participant?.joined)
18
+ return { binding: requested, joined: false, notifications: [] };
19
+ const binding = { ...requested, participant: participant.name };
20
+ const notifications = pendingNotifications(facts, participant.name).map(({ item, route }) => ({
21
+ actIndex: item.index, actor: item.actor, at: item.at, body: item.body, route,
22
+ }));
23
+ const catchLease = freshWatchLease(facts, participant.name, now);
24
+ return { binding, joined: true, notifications, ...(catchLease === undefined ? {} : { catchLease }) };
25
+ }
26
+ /** First matching ordered route, exact native epoch/address and catch ownership. */
27
+ export function nativePendingPreview(facts, input) {
28
+ const route = facts.routes().find((candidate) => candidate.kind === input.routeKind && candidate.sessionId === input.sessionId
29
+ && candidate.epoch === input.epoch && nameKey(candidate.participant) === nameKey(input.participant)
30
+ && (input.routeKind === 'claude-native' ? candidate.address.endpoint === input.address.endpoint : candidate.address.sessionId === input.address.sessionId));
31
+ if (!route || input.actIndex <= (input.cancelledThrough ?? -1))
32
+ return undefined;
33
+ const head = pendingHead(facts, input.participant, input.actIndex);
34
+ const lease = freshWatchLease(facts, input.participant, input.now);
35
+ if (!head || (lease !== undefined && leaseOwnsNotification(lease, head)))
36
+ return undefined;
37
+ const item = facts.activity(input.actIndex);
38
+ if (item?.kind !== 'say')
39
+ return undefined;
40
+ return { payload: renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: item.index,
41
+ actor: item.actor, route: head.route, body: item.body }), clipped: attentionBodyIsClipped(item.body) };
42
+ }
10
43
  async function readInboxMembership(binding, signal) {
11
- const artifact = openSquareArtifact(binding.location, signal);
44
+ const artifact = createArtifactPort(binding.location, undefined, signal);
12
45
  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 }) };
46
+ return await artifact.read((facts) => {
47
+ const projection = projectPresentation(facts, binding);
48
+ if (!projection.joined)
49
+ return undefined;
50
+ return { name: projection.binding.participant, squarePath: projection.binding.location, notifications: [...projection.notifications],
51
+ ...(projection.catchLease === undefined ? {} : { catchLease: projection.catchLease }) };
52
+ }, signal);
19
53
  }
20
54
  finally {
21
55
  await artifact.close();
@@ -28,7 +62,7 @@ export async function sessionInbox(sessionId, env = process.env, signal) {
28
62
  if (signal?.aborted)
29
63
  throw signal.reason;
30
64
  try {
31
- const membership = await readInboxMembership(binding, signal);
65
+ const { value: membership } = await readInboxMembership(binding, signal);
32
66
  if (membership !== undefined)
33
67
  inbox.push(membership);
34
68
  }
@@ -100,19 +134,18 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
100
134
  for (const file of paths) {
101
135
  if (artifacts.has(file))
102
136
  continue;
103
- const observer = await observeSquareChanges(file);
137
+ const observer = await observeSquareRevision(file);
104
138
  if (signal.aborted) {
105
139
  observer.close();
106
140
  return [];
107
141
  }
108
142
  artifacts.set(file, { observer, inbox: new Map() });
109
143
  }
144
+ const revisions = new Map();
110
145
  for (const [file, entry] of artifacts) {
111
146
  const source = await capture(entry.observer, 'artifact', signal);
112
147
  sources.push(source);
113
- if (source.value === undefined || source.value !== entry.revision)
114
- entry.inbox.clear();
115
- entry.revision = source.value;
148
+ revisions.set(file, source.value);
116
149
  const names = new Set(bindings.filter((binding) => binding.location === file).map((binding) => nameKey(binding.participant)));
117
150
  for (const name of entry.inbox.keys())
118
151
  if (!names.has(name))
@@ -125,9 +158,13 @@ export async function observeSessionPending(sessionId, suppliedEnv = process.env
125
158
  const entry = artifacts.get(binding.location);
126
159
  const name = nameKey(binding.participant);
127
160
  try {
128
- if (!entry.inbox.has(name))
129
- entry.inbox.set(name, await readInboxMembership(binding, signal));
130
- const membership = entry.inbox.get(name);
161
+ const coordinate = bindingCoordinate(binding);
162
+ const cached = entry.inbox.get(name);
163
+ if (cached?.version !== revisions.get(binding.location) || cached?.binding !== coordinate) {
164
+ const projection = await readInboxMembership(binding, signal);
165
+ entry.inbox.set(name, { ...projection, binding: coordinate });
166
+ }
167
+ const membership = entry.inbox.get(name)?.value;
131
168
  if (membership === undefined)
132
169
  continue;
133
170
  const { catchLease, ...rest } = membership;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { Square } from './square-wiring.js';
1
+ export { Square } from './square-application.js';
2
2
  export { createSquareApplication } from './square-application.js';
3
3
  export { SquareError } from './model.js';
4
4
  export { squareAssignedParticipantName } from './registry.js';
@@ -6,6 +6,4 @@ 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';
8
8
  export type { PresentationSinkPort } from './ports.js';
9
- export type { SquareApplication, SquareApplicationContext, JoinApplicationOptions, JoinApplicationResult, ExpressApplicationOptions, ApplicationStatus, ApplicationStatusProjection, ApplicationParticipants } from './square-application.js';
10
- export type { ActivityId } from './square-core.js';
11
- export type { Activity, CatchOptions, CatchResult, ExpressOptions, ExpressResult, HistoryQuery, OperationControl, OperationProgress, ListenerChangeResult, OpenOptions, Participant, ParticipantStatus, PerceivedActivity, SquareAtInput, SquareBuildInput, SquareSnapshot, SquareSource, } from './square-facade.js';
9
+ export type * from './square-facade.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Square } from './square-wiring.js';
1
+ export { Square } from './square-application.js';
2
2
  export { createSquareApplication } from './square-application.js';
3
3
  export { SquareError } from './model.js';
4
4
  export { squareAssignedParticipantName } from './registry.js';
package/dist/list.d.ts CHANGED
@@ -1 +1,7 @@
1
- export declare function cmdListSquares(args: string[], usage: () => void): Promise<void>;
1
+ import type { DiscoveryApplicationData, DiscoveryApplicationOptions, OpenOptions, OperationControl } from './square-facade.js';
2
+ export declare const DEFAULT_LIST_DEPTH = 4;
3
+ export declare const MAX_LIST_DEPTH = 16;
4
+ export declare const DEFAULT_LIST_LIMIT = 20;
5
+ export declare const MAX_LIST_LIMIT = 100;
6
+ /** Bounded deterministic discovery. Only the application calls this private operation. */
7
+ export declare function discoverSquares(root: string, options?: DiscoveryApplicationOptions, control?: OperationControl, openOptions?: OpenOptions): Promise<DiscoveryApplicationData>;
package/dist/list.js CHANGED
@@ -1,46 +1,51 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { probeSquare } from './square-file-adapter.js';
3
+ import { openSquare } from './open-square.js';
4
4
  import { closeOpenSquare } from './open-square.js';
5
5
  import { listPresentation } from './views.js';
6
- import { formatRelativeTime } from './time.js';
7
- import { participantIdentity, quoteShell } from './presentation.js';
8
- import { style } from './tty-style.js';
9
- const DEFAULT_LIST_DEPTH = 4;
10
- const MAX_LIST_DEPTH = 16;
11
- const DEFAULT_LIST_LIMIT = 20;
12
- const MAX_LIST_LIMIT = 100;
6
+ import { SquareError } from './model.js';
7
+ export const DEFAULT_LIST_DEPTH = 4;
8
+ export const MAX_LIST_DEPTH = 16;
9
+ export const DEFAULT_LIST_LIMIT = 20;
10
+ export const MAX_LIST_LIMIT = 100;
13
11
  const LIST_DISCOVERY_BUDGET = 10_000;
14
- const CONTEXT_PREVIEW_LINES = 2;
15
- const PARTICIPANT_PREVIEW_COUNT = 3;
16
- const DISPLAY_CHARACTER_LIMIT = 160;
17
12
  const LIST_SKIP_DIRS = new Set(['.git', 'node_modules', 'dist']);
18
- async function readSquareListItem(filePath, root) {
19
- let stat;
13
+ async function readSquareListItem(filePath, root, options, control) {
14
+ if (!filePath.endsWith('.square'))
15
+ return null;
20
16
  try {
21
- stat = await fs.promises.stat(filePath);
17
+ control?.signal?.throwIfAborted();
18
+ const stat = await fs.promises.stat(filePath);
19
+ const square = await openSquare(filePath, { ...options, env: options.env ?? {}, signal: control?.signal }, control);
20
+ const projection = await listPresentation(square, control).finally(() => closeOpenSquare(square));
21
+ return {
22
+ path: path.relative(root, filePath) || path.basename(filePath),
23
+ lastActiveAt: stat.mtimeMs,
24
+ context: [...projection.context].map((line) => line.trim()).filter(Boolean),
25
+ participants: [...projection.participants],
26
+ activities: projection.activities,
27
+ };
22
28
  }
23
- catch {
29
+ catch (error) {
30
+ control?.signal?.throwIfAborted();
31
+ // A vanished, unreadable or undecodable candidate does not abort discovery.
24
32
  return null;
25
33
  }
26
- const square = await probeSquare(filePath);
27
- if (square === undefined)
28
- return null;
29
- const projection = await listPresentation(square).finally(() => closeOpenSquare(square));
30
- const relative = path.relative(root, filePath) || path.basename(filePath);
31
- return {
32
- path: relative,
33
- lastActiveAt: stat.mtimeMs,
34
- context: [...projection.context].map((line) => line.trim()).filter(Boolean),
35
- participants: [...projection.participants],
36
- activities: projection.activities,
37
- };
38
34
  }
39
- async function collectSquareList(root, maxDepth) {
35
+ /** Bounded deterministic discovery. Only the application calls this private operation. */
36
+ export async function discoverSquares(root, options = {}, control, openOptions = {}) {
37
+ const depth = options.depth ?? DEFAULT_LIST_DEPTH;
38
+ const limit = options.limit ?? DEFAULT_LIST_LIMIT;
39
+ if (!Number.isSafeInteger(depth) || depth < 0 || depth > MAX_LIST_DEPTH
40
+ || !Number.isSafeInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT
41
+ || (options.after !== undefined && (options.after.length === 0 || path.isAbsolute(options.after)))) {
42
+ throw new SquareError('invalid_args', 'Invalid discovery depth, limit or relative cursor');
43
+ }
40
44
  const items = [];
41
45
  let examined = 0;
42
46
  let stopped = false;
43
- async function walk(dir, depth) {
47
+ async function walk(dir, level) {
48
+ control?.signal?.throwIfAborted();
44
49
  if (stopped)
45
50
  return;
46
51
  let entries;
@@ -48,110 +53,32 @@ async function collectSquareList(root, maxDepth) {
48
53
  entries = await fs.promises.readdir(dir, { withFileTypes: true });
49
54
  }
50
55
  catch {
51
- // Directory vanished or became unreadable mid-walk — skip it, don't abort the scan.
56
+ control?.signal?.throwIfAborted();
52
57
  return;
53
58
  }
54
59
  entries.sort((left, right) => left.name.localeCompare(right.name));
55
60
  for (const entry of entries) {
61
+ control?.signal?.throwIfAborted();
56
62
  if (stopped)
57
63
  return;
58
- examined += 1;
59
- if (examined === LIST_DISCOVERY_BUDGET)
64
+ if (++examined === LIST_DISCOVERY_BUDGET)
60
65
  stopped = true;
61
66
  const fullPath = path.join(dir, entry.name);
62
67
  if (entry.isDirectory()) {
63
- if (!stopped && depth < maxDepth && !LIST_SKIP_DIRS.has(entry.name))
64
- await walk(fullPath, depth + 1);
65
- continue;
68
+ if (!stopped && level < depth && !LIST_SKIP_DIRS.has(entry.name))
69
+ await walk(fullPath, level + 1);
66
70
  }
67
- if (!entry.isFile())
68
- continue;
69
- const item = await readSquareListItem(fullPath, root);
70
- if (item)
71
- items.push(item);
72
- }
73
- }
74
- await walk(root, 0);
75
- return { items: items.sort((a, b) => a.path.localeCompare(b.path)), stopped };
76
- }
77
- function characterPreview(value) {
78
- const characters = Array.from(value);
79
- return characters.length <= DISPLAY_CHARACTER_LIMIT ? value : `${characters.slice(0, DISPLAY_CHARACTER_LIMIT - 1).join('')}…`;
80
- }
81
- function renderSquareList(items, stopped, depth, limit, after) {
82
- const page = after === undefined ? items : items.filter((item) => item.path.localeCompare(after) > 0);
83
- const shownItems = page.slice(0, limit);
84
- if (items.length === 0) {
85
- return [`(no squares found)`, ...(stopped ? [style('dim', `○ the walk stopped early — deeper squares may still be hiding`)] : [])].join('\n') + '\n';
86
- }
87
- const now = Date.now();
88
- const lines = ['squares'];
89
- for (const item of shownItems) {
90
- const glyph = item.activities > 0 ? '●' : style('dim', '○');
91
- lines.push(`${glyph} ${characterPreview(item.path)}${style('dim', ` · ${formatRelativeTime(item.lastActiveAt, now)} · ${item.participants.length} in the square · ${item.activities} ${item.activities === 1 ? 'activity' : 'activities'}`)}`);
92
- const shownContext = item.context.slice(0, CONTEXT_PREVIEW_LINES);
93
- if (shownContext.length === 0) {
94
- lines.push(` ${style('dim', 'context')} · (none)`);
95
- }
96
- else {
97
- shownContext.forEach((line, index) => lines.push(` ${index === 0 ? style('dim', 'context') : ' '} · ${characterPreview(line)}`));
98
- const hiddenContext = item.context.length - shownContext.length;
99
- if (hiddenContext > 0)
100
- lines.push(style('dim', ` · … ${hiddenContext} more ${hiddenContext === 1 ? 'line' : 'lines'}`));
101
- }
102
- const shownParticipants = item.participants.slice(0, PARTICIPANT_PREVIEW_COUNT);
103
- const hiddenParticipants = item.participants.length - shownParticipants.length;
104
- const participantNames = shownParticipants.length === 0 ? 'nobody' : shownParticipants.map((participant) => participantIdentity(characterPreview(participant))).join(' · ');
105
- lines.push(` ${style('dim', 'participants')} · ${participantNames}${hiddenParticipants > 0 ? style('dim', ` · … ${hiddenParticipants} more`) : ''}`);
106
- }
107
- const hiddenItems = page.length - shownItems.length;
108
- if (hiddenItems > 0) {
109
- const cursor = shownItems.at(-1)?.path;
110
- lines.push(style('dim', stopped ? ' … more squares' : ` … ${hiddenItems} more ${hiddenItems === 1 ? 'square' : 'squares'}`));
111
- if (cursor !== undefined)
112
- lines.push(`square list --depth ${depth} --limit ${limit} --after ${quoteShell(cursor)}`);
113
- }
114
- if (stopped)
115
- lines.push(style('dim', `○ the walk stopped early — deeper squares may still be hiding`));
116
- return lines.join('\n') + '\n';
117
- }
118
- function parseListOptions(args, usage) {
119
- let depth = DEFAULT_LIST_DEPTH;
120
- let limit = DEFAULT_LIST_LIMIT;
121
- let after;
122
- for (let index = 0; index < args.length; index += 2) {
123
- const option = args[index];
124
- const value = args[index + 1];
125
- if (value === undefined || (option !== '--depth' && option !== '--limit' && option !== '--after')) {
126
- usage();
127
- return { depth, limit };
128
- }
129
- if (option === '--after') {
130
- if (after !== undefined || value.length === 0 || path.isAbsolute(value)) {
131
- usage();
132
- return { depth, limit };
71
+ else if (entry.isFile()) {
72
+ const item = await readSquareListItem(fullPath, root, openOptions, control);
73
+ if (item !== null)
74
+ items.push(item);
133
75
  }
134
- after = value;
135
- continue;
136
76
  }
137
- if (!/^\d+$/.test(value)) {
138
- usage();
139
- return { depth, limit };
140
- }
141
- const parsed = Number(value);
142
- if (!Number.isSafeInteger(parsed) || (option === '--depth' ? parsed > MAX_LIST_DEPTH : parsed === 0 || parsed > MAX_LIST_LIMIT)) {
143
- usage();
144
- return { depth, limit };
145
- }
146
- if (option === '--depth')
147
- depth = parsed;
148
- else
149
- limit = parsed;
150
77
  }
151
- return { depth, limit, after };
152
- }
153
- export async function cmdListSquares(args, usage) {
154
- const options = parseListOptions(args, usage);
155
- const discovery = await collectSquareList(process.cwd(), options.depth);
156
- process.stdout.write(renderSquareList(discovery.items, discovery.stopped, options.depth, options.limit, options.after));
78
+ await walk(root, 0);
79
+ const page = items.sort((a, b) => a.path.localeCompare(b.path))
80
+ .filter((item) => options.after === undefined || item.path.localeCompare(options.after) > 0);
81
+ const selected = page.slice(0, limit);
82
+ const remaining = page.length - selected.length;
83
+ return { items: selected, total: items.length, stopped, remaining, ...(remaining > 0 ? { next: selected.at(-1).path } : {}) };
157
84
  }
package/dist/mcp.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { type SquareApplicationContext } from './square-application.js';
2
+ import type { SquareApplicationContext } from './square-facade.js';
3
3
  export interface SquareMcpOptions extends SquareApplicationContext {
4
4
  }
5
5
  export type SquareMcpServer = McpServer;
package/dist/mcp.js CHANGED
@@ -5,7 +5,6 @@ import { SquareError, isSquareError } from './model.js';
5
5
  import { operationEnv } from './automatic-session.js';
6
6
  import { SQUARE_IDENTITY } from './identity.js';
7
7
  import { createSquareApplication } from './square-application.js';
8
- import { toPublicActivity } from './views.js';
9
8
  const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
10
9
  const stringArray = z.array(z.string());
11
10
  const participantName = z.string().optional().describe("Participant name, same as --as. Defaults to the server's configured participant.");
@@ -23,36 +22,6 @@ const TOOL_DEFINITIONS = [
23
22
  { name: 'status', description: 'Read the public status of the square.', inputSchema: z.object({}).strict() },
24
23
  { name: 'participants', description: 'List participants standing in or done with the square.', inputSchema: z.object({}).strict() },
25
24
  ];
26
- function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
27
- function publicActivity(activity) { return { ...activity }; }
28
- function publicResult(name, value) {
29
- if (name === 'status') {
30
- const result = value;
31
- const state = result.status;
32
- const { latestAct, participants, ...rest } = state;
33
- return {
34
- hardCap: rest.hardCap,
35
- ...(rest.throttlePerMinute === undefined ? {} : { throttlePerMinute: rest.throttlePerMinute }),
36
- activeCount: rest.activeCount,
37
- doneCount: rest.doneCount,
38
- hold: rest.holdActive ? { by: rest.holdActor, ...(rest.holdReason === undefined ? {} : { reason: rest.holdReason }), ...(rest.holdAt === undefined ? {} : { at: rest.holdAt }) } : null,
39
- 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 })),
40
- ...(latestAct === undefined ? {} : { latestActivity: publicActivity(toPublicActivity(latestAct)) }),
41
- now: rest.now,
42
- };
43
- }
44
- if (name === 'express' || name === 'hold' || name === 'resume' || name === 'done') {
45
- const result = value;
46
- return { ...(result.activity === undefined ? {} : { activity: result.activity === null ? null : publicActivity(result.activity) }), ...(result.waited === undefined ? {} : { waited: result.waited }), ...(result.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: result.replyAuthorNotHere }) };
47
- }
48
- if (name === 'history')
49
- return { activities: value };
50
- if (name === 'participants')
51
- return { participants: value };
52
- if (name === 'listening')
53
- return { listening: value };
54
- return value;
55
- }
56
25
  function operation(app, name, args, control) {
57
26
  switch (name) {
58
27
  case 'join': return app.join({ takeover: args.takeover }, control);
@@ -68,8 +37,8 @@ function operation(app, name, args, control) {
68
37
  case 'hold': return app.hold(args.reason, control);
69
38
  case 'resume': return app.resume(control);
70
39
  case 'done': return app.done(undefined, control);
71
- case 'status': return app.status();
72
- case 'participants': return app.participants();
40
+ case 'status': return app.status(undefined, control);
41
+ case 'participants': return app.participants(control);
73
42
  default: throw new SquareError('invalid_args', `Unknown tool: ${name}`);
74
43
  }
75
44
  }
@@ -90,9 +59,8 @@ export function createSquareMcpServer(options) {
90
59
  ? operationEnv('opencode', opencodeSessionId, baseEnv)
91
60
  : baseEnv;
92
61
  const app = createSquareApplication({ ...options, env, participant: participant ?? options.participant });
93
- const value = await operation(app, tool.name, operationArgs, { signal: extra.signal });
94
- const projected = publicResult(tool.name, value);
95
- const structuredContent = isObject(projected) ? projected : { result: projected };
62
+ const outcome = await operation(app, tool.name, operationArgs, { signal: extra.signal });
63
+ const structuredContent = outcome.data;
96
64
  return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent };
97
65
  }
98
66
  catch (error) {
@@ -101,7 +69,9 @@ export function createSquareMcpServer(options) {
101
69
  : isSquareError(error)
102
70
  ? { code: error.code, message: error.message, ...(error.facts === undefined ? {} : { facts: error.facts }) }
103
71
  : { code: 'internal', message: error instanceof Error ? error.message : String(error) };
104
- return { isError: true, content: [{ type: 'text', text: JSON.stringify(normalized) }], structuredContent: { error: normalized } };
72
+ const operation = error instanceof Error ? error.operation : undefined;
73
+ const failure = operation === undefined ? normalized : { ...normalized, operation };
74
+ return { isError: true, content: [{ type: 'text', text: JSON.stringify(failure) }], structuredContent: { error: failure } };
105
75
  }
106
76
  });
107
77
  }
package/dist/model.d.ts CHANGED
@@ -15,8 +15,6 @@ export interface WakeRoute {
15
15
  address: Record<string, string>;
16
16
  updatedAt: number;
17
17
  }
18
- export interface ReceiverRoute extends WakeRoute {
19
- }
20
18
  export type SquareErrorCode = 'invalid_name' | 'invalid_args' | 'unknown_participant' | 'not_joined' | 'already_joined' | 'already_done' | 'held' | 'capped' | 'throttled' | 'bell_quota' | 'behind' | 'io' | 'unavailable';
21
19
  export type InternalSquareErrorCode = SquareErrorCode | 'not_found' | 'cap_reached' | 'conflict' | 'pending_peer';
22
20
  export interface SquareErrorFacts {
@@ -24,9 +22,11 @@ export interface SquareErrorFacts {
24
22
  activityUnsent?: true;
25
23
  /** Why a name could not be resolved: never in the roster, no longer standing in it, a bell
26
24
  * 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';
25
+ reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'unmatched_mention' | 'not_addressed' | 'migration_required' | 'explicit_reach_required' | 'invalid_reply' | 'invalid_done_body';
28
26
  /** The activity id a `catch --id` refusal named. */
29
27
  activityId?: string;
28
+ /** Requested recipient spellings, without parsing presentation copy. */
29
+ targets?: readonly string[];
30
30
  /** Whether the artifact holds that activity at all; an unknown id needs an ordinary page. */
31
31
  activityExists?: boolean;
32
32
  pending?: number;
@@ -37,6 +37,7 @@ export interface SquareErrorFacts {
37
37
  declare class SquareErrorBase<Code extends string> extends Error {
38
38
  code: Code;
39
39
  facts?: SquareErrorFacts | undefined;
40
+ operation?: import('./square-facade.js').ApplicationFailureDetail;
40
41
  constructor(code: Code, message: string, facts?: SquareErrorFacts | undefined);
41
42
  }
42
43
  export declare class SquareError extends SquareErrorBase<SquareErrorCode> {
@@ -55,7 +56,6 @@ export type StoredAct = Act & {
55
56
  index: number;
56
57
  at: number;
57
58
  };
58
- export type StoredActHead = StoredAct extends infer T ? T extends StoredAct ? Omit<T, 'body' | 'through' | 'index'> : never : never;
59
59
  export type ObservationState = 'seen';
60
60
  export interface ActivityObservation {
61
61
  state: ObservationState;
@@ -85,40 +85,6 @@ export interface InboxMembership {
85
85
  notifications: InboxNotification[];
86
86
  catchLease?: WatchLease;
87
87
  }
88
- export interface SquareRuntimeState {
89
- nextActIndex: number;
90
- observations: Record<string, Record<string, ActivityObservation>>;
91
- leases: Record<string, WatchLease>;
92
- }
93
- export interface SquareState {
94
- hardCap: HardCap;
95
- throttlePerMinute?: number;
96
- preamble: string[];
97
- warmup: string[];
98
- acts: StoredAct[];
99
- routes?: ReceiverRoute[];
100
- runtime: SquareRuntimeState;
101
- }
102
- export interface ActivitiesOptions {
103
- lastN?: number | null;
104
- participants?: string[];
105
- /** Exclusive lower bound on act.at (ms). Alias of --since. */
106
- after?: number;
107
- /** Exclusive lower bound on stable act index (--after act/<index>). */
108
- afterIndex?: number;
109
- /** Exclusive upper bound on stable act index (--before act/<index>). */
110
- beforeIndex?: number;
111
- /** Center act indexes for context windows (--at). */
112
- atIndexes?: number[];
113
- beforeContext?: number;
114
- afterContext?: number;
115
- mention?: string;
116
- grep?: string;
117
- fixed?: string;
118
- order?: 'asc' | 'desc';
119
- format?: string[];
120
- json?: boolean;
121
- }
122
88
  export interface WatchOptions {
123
89
  id?: ActivityId;
124
90
  participants?: string[];
@@ -134,16 +100,10 @@ export type PublicAct = Extract<StoredAct, {
134
100
  export type RoomChangeAct = Extract<StoredAct, {
135
101
  kind: 'join' | 'done' | 'hold' | 'resume' | 'listen' | 'ignore';
136
102
  }>;
137
- export interface HoldState {
138
- active: boolean;
139
- at?: number;
140
- reason?: string;
141
- }
142
103
  export declare function formatHardCap(hardCap: HardCap): string;
143
104
  export declare function parseParticipantList(value: string): string[];
144
105
  export declare function nameKey(name: string): string;
145
106
  export declare function sameName(a: string, b: string): boolean;
146
- export declare function findParticipantName(participants: string[], name: string): string | undefined;
147
107
  /** One name grapheme: Unicode letters, digits, marks, hyphens, or underscores (no isolated variation selectors), or a complete RGI emoji. Shared by name validation and body @mention scanning. */
148
108
  export declare const NAME_GRAPHEME_SOURCE: string;
149
109
  export declare function validateName(name: string): void;
package/dist/model.js CHANGED
@@ -7,6 +7,7 @@ export function isWakeRouteKind(value) {
7
7
  class SquareErrorBase extends Error {
8
8
  code;
9
9
  facts;
10
+ operation;
10
11
  constructor(code, message, facts) {
11
12
  super(message);
12
13
  this.code = code;
@@ -36,9 +37,6 @@ export function nameKey(name) {
36
37
  export function sameName(a, b) {
37
38
  return nameKey(a) === nameKey(b);
38
39
  }
39
- export function findParticipantName(participants, name) {
40
- return participants.find((participant) => sameName(participant, name));
41
- }
42
40
  /** One name grapheme: Unicode letters, digits, marks, hyphens, or underscores (no isolated variation selectors), or a complete RGI emoji. Shared by name validation and body @mention scanning. */
43
41
  export const NAME_GRAPHEME_SOURCE = String.raw `[[\p{L}\p{N}\p{M}_\x2D]--[\p{Variation_Selector}]]|\p{RGI_Emoji}`;
44
42
  const NAME_PATTERN = new RegExp(`^(?:${NAME_GRAPHEME_SOURCE})+(?:/(?:${NAME_GRAPHEME_SOURCE})+)*$`, 'v');
@@ -8,7 +8,7 @@ interface ProcessNotificationOptions {
8
8
  env?: NodeJS.ProcessEnv;
9
9
  now?: () => number;
10
10
  }
11
- export declare function defaultWakeAdapters(): Promise<WakeAdapter[]>;
11
+ export declare function defaultWakeAdapters(env?: NodeJS.ProcessEnv): Promise<WakeAdapter[]>;
12
12
  export declare function createDefaultWakeTransport(hostLedger: import('./host-ledger.js').HostLedgerPort, clock: () => number, env?: NodeJS.ProcessEnv, suppliedAdapters?: readonly WakeAdapter[]): Promise<WakeTransportPort>;
13
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;
14
14
  export declare function processActNotificationsOnce(squarePath: string, actIndex: number, opts?: ProcessNotificationOptions): Promise<import("./ports.js").DeliveryResult>;
@@ -3,10 +3,12 @@ import path from 'node:path';
3
3
  import fs from 'node:fs';
4
4
  import { SquareError } from './model.js';
5
5
  import { displayAttentionPath } from './attention-presentation.js';
6
- import { openSquare } from './square-file-adapter.js';
6
+ import { openSquare } from './open-square.js';
7
7
  import { closeOpenSquare } from './open-square.js';
8
8
  import { deliverPending, sweepPending } from './delivery-operations.js';
9
- import { projectWakeEvidenceFromState } from './square-projections.js';
9
+ import { projectWakeEvidence } from './wake-eligibility.js';
10
+ import { pendingHead } from './delivery.js';
11
+ import { parseActivityId } from './square-core.js';
10
12
  import { canonicalPath } from './canonical-path.js';
11
13
  import { createHostLedgerPort } from './host-ledger-file-adapter.js';
12
14
  export const PRIVILEGED_HOOK_BUDGET_MS = 3000;
@@ -17,11 +19,11 @@ export function wakeGraceMs(env = process.env) {
17
19
  }
18
20
  return value;
19
21
  }
20
- export async function defaultWakeAdapters() {
22
+ export async function defaultWakeAdapters(env = process.env) {
21
23
  const adapters = [];
22
24
  try {
23
25
  const { CodexQueueAdapter } = await import('./codex-queue.js');
24
- adapters.push(new CodexQueueAdapter());
26
+ adapters.push(new CodexQueueAdapter({ env }));
25
27
  }
26
28
  catch {
27
29
  // Codex is unavailable only when this build omits its local adapter.
@@ -36,7 +38,7 @@ export async function defaultWakeAdapters() {
36
38
  return adapters;
37
39
  }
38
40
  export async function createDefaultWakeTransport(hostLedger, clock, env = process.env, suppliedAdapters) {
39
- const adapters = suppliedAdapters ?? await defaultWakeAdapters();
41
+ const adapters = suppliedAdapters ?? await defaultWakeAdapters(env);
40
42
  const { createClaudeWakeTransport } = await import('./claude-delivery.js');
41
43
  return createWakeTransport(suppliedAdapters === undefined && env.SQUARE_DISABLE_PASEO_WAKE === '1' ? adapters.filter((adapter) => adapter.kind !== 'paseo') : adapters, hostLedger, clock, { 'claude-native': createClaudeWakeTransport(hostLedger, env) });
42
44
  }
@@ -45,8 +47,12 @@ async function observeWakeRequest(request, hostLedger, now) {
45
47
  try {
46
48
  const location = await canonicalPath(request.location);
47
49
  square = await openSquare(location, { hostLedger });
48
- const { state } = await square.artifact.read();
49
- const eligibility = await projectWakeEvidenceFromState({ state, hostLedger, location, now });
50
+ const { value: facts } = await square.artifact.read((facts) => {
51
+ const index = parseActivityId(request.activity);
52
+ const head = index === undefined ? undefined : pendingHead(facts, request.participant, index);
53
+ return { routes: facts.routes(), pending: head === undefined ? [] : [head] };
54
+ });
55
+ const eligibility = await projectWakeEvidence({ ...facts, hostLedger, location, now });
50
56
  return { ...eligibility.currentness({ ...request, location }), observationAvailable: true };
51
57
  }
52
58
  catch {