@astrosheep/square 0.3.78 → 0.3.80

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 (70) hide show
  1. package/dist/attention-presentation.d.ts +2 -0
  2. package/dist/attention-presentation.js +39 -4
  3. package/dist/automatic-session.js +1 -1
  4. package/dist/boundary-presentation.js +4 -7
  5. package/dist/claude-mod.js +1 -1
  6. package/dist/cli/catch-presentation.d.ts +2 -2
  7. package/dist/cli/catch-presentation.js +3 -3
  8. package/dist/cli/context.d.ts +11 -1
  9. package/dist/cli/context.js +14 -3
  10. package/dist/cli/express-presentation.d.ts +6 -5
  11. package/dist/cli/express-presentation.js +42 -43
  12. package/dist/cli/maintenance-commands.js +6 -6
  13. package/dist/cli/mcp-server-command.js +1 -1
  14. package/dist/cli/observation-commands.d.ts +4 -4
  15. package/dist/cli/observation-commands.js +20 -16
  16. package/dist/cli/program.d.ts +1 -1
  17. package/dist/cli/program.js +5 -5
  18. package/dist/cli/square-commands.js +35 -23
  19. package/dist/decisions.d.ts +3 -5
  20. package/dist/decisions.js +10 -9
  21. package/dist/delivery.d.ts +1 -1
  22. package/dist/help.js +15 -2
  23. package/dist/inbox.js +3 -3
  24. package/dist/index.d.ts +1 -2
  25. package/dist/index.js +1 -2
  26. package/dist/ledger-maintenance.d.ts +1 -1
  27. package/dist/ledger-maintenance.js +1 -1
  28. package/dist/list.d.ts +3 -3
  29. package/dist/list.js +1 -1
  30. package/dist/mcp.d.ts +2 -2
  31. package/dist/mcp.js +12 -7
  32. package/dist/mention-parse.d.ts +2 -8
  33. package/dist/mention-parse.js +6 -9
  34. package/dist/model.d.ts +2 -2
  35. package/dist/notifications.js +24 -8
  36. package/dist/participant-host.js +5 -0
  37. package/dist/plugins/opencode/index.js +1 -1
  38. package/dist/square-actions.d.ts +22 -9
  39. package/dist/square-actions.js +26 -19
  40. package/dist/square-api.d.ts +43 -0
  41. package/dist/square-api.js +471 -0
  42. package/dist/square-facade.d.ts +123 -203
  43. package/dist/square-tools.d.ts +4 -3
  44. package/dist/square-tools.js +21 -15
  45. package/dist/stream.d.ts +2 -2
  46. package/dist/views.d.ts +16 -12
  47. package/dist/views.js +13 -27
  48. package/guides/application-demand-optimization.md +5 -0
  49. package/guides/application-facade-delivery-evidence.md +7 -2
  50. package/guides/mcp-connection-lifetime.md +2 -2
  51. package/guides/mcp.md +1 -1
  52. package/guides/operation-cancellation.md +2 -0
  53. package/guides/primitive-catch-consolidation.md +5 -0
  54. package/guides/primitive-history-consolidation.md +5 -0
  55. package/guides/primitive-status-consolidation.md +5 -0
  56. package/guides/primitive-storage-consolidation.md +5 -0
  57. package/guides/row-storage-design.md +1 -1
  58. package/guides/square-api.md +107 -0
  59. package/package.json +1 -1
  60. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  61. package/plugins/claude/skills/square/SKILL.md +4 -4
  62. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  63. package/plugins/opencode/index.ts +1 -1
  64. package/plugins/paseo/client/markdown.tsx +16 -6
  65. package/plugins/pi/square-pi.js +7 -6
  66. package/skills/square/SKILL.md +4 -4
  67. package/dist/square-application.d.ts +0 -38
  68. package/dist/square-application.js +0 -683
  69. package/guides/application-boundary.md +0 -32
  70. package/guides/application-facade-design.md +0 -98
package/dist/views.d.ts CHANGED
@@ -2,8 +2,8 @@ import { type ActivityId } from './square-core.js';
2
2
  import { type StoredAct } from './model.js';
3
3
  import type { OpenSquare } from './open-square.js';
4
4
  import type { ActivityFilter, SquareRead } from './ports.js';
5
- import type { Activity, ApplicationOutcome, ArchiveRecord, HistoryQuery, HistoryApplicationQuery, HistoryContinuation, OperationControl, ParticipantStatus, ParticipantsApplicationData, ParticipantsProjection, SquareSnapshot, RecoveryObservation, EntryPresentation, ListPresentation, StatusApplicationData, StatusProjection, SquareContextProjection, CatchPresence, StreamProjection, EventPresentation } from './square-facade.js';
6
- export type { RecoveryObservation, EntryPresentation, ListPresentation, CatchPresence, StreamProjection } from './square-facade.js';
5
+ import type { Activity, ArchiveRecord, HistoryQuery, HistoryContinuation, OperationControl, ParticipantsResult, SquareSnapshot, EntryPresentation, ListPresentation, StatusResult, SquareObservation, CatchPresence, StreamProjection } from './square-facade.js';
6
+ export type { EntryPresentation, ListPresentation, CatchPresence, StreamProjection } from './square-facade.js';
7
7
  export declare const HISTORY_DEFAULT_LIMIT = 10;
8
8
  export declare const HISTORY_MAX_LIMIT = 100;
9
9
  export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume", "listen", "ignore"];
@@ -11,29 +11,33 @@ export declare const NON_READ: readonly ["join", "done", "say", "hold", "resume"
11
11
  export declare function toPublicActivity(stored: StoredAct): Activity;
12
12
  export declare function parseRequiredActivityId(id: ActivityId): number;
13
13
  /** Compile the public query directly to typed selectors; only search hydrates unselected bodies. */
14
- export declare function selectHistory(facts: SquareRead, query: HistoryApplicationQuery, limit: number | null, kinds: ActivityFilter['kinds'], presentation?: boolean): {
14
+ export declare function selectHistory(facts: SquareRead, query: HistoryQuery, limit: number | null, kinds: ActivityFilter['kinds'], presentation?: boolean): {
15
15
  continuation?: HistoryContinuation | undefined;
16
16
  activities: StoredAct[];
17
17
  visibleActivities: StoredAct[];
18
18
  totalMatches: number;
19
19
  };
20
20
  export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
21
- export declare function history(square: OpenSquare, query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
22
21
  export declare function resolveParticipant(square: OpenSquare, name: string, control?: OperationControl): Promise<{
23
22
  name: string;
24
23
  }>;
25
24
  export declare function currentParticipant(square: OpenSquare, name: string, control?: OperationControl): Promise<string | undefined>;
26
- export declare function participants(square: OpenSquare): Promise<ParticipantStatus[]>;
27
25
  export declare function snapshot(square: OpenSquare, control?: OperationControl): Promise<SquareSnapshot>;
28
- export declare function recoveryObservation(square: OpenSquare, name: string, control?: OperationControl): Promise<RecoveryObservation>;
26
+ export declare function recoveryObservation(square: OpenSquare, name: string, control?: OperationControl): Promise<{
27
+ ownActivityCount: number;
28
+ hardCap: import("./model.js").HardCap;
29
+ holdReason?: string | undefined;
30
+ participant: string;
31
+ roster: string[];
32
+ activitySummaries: import("./square-facade.js").UnreadActivitySummary[];
33
+ standingCount: number;
34
+ held: boolean;
35
+ }>;
29
36
  export declare function entryPresentation(square: OpenSquare, name: string, lastN?: number | null, control?: OperationControl): Promise<EntryPresentation>;
30
- type ParticipantQueryContext = Pick<SquareContextProjection, 'location' | 'now' | 'automaticDeliveryAvailable'>;
31
- export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext): ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection>;
32
- export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, presentation: boolean): ApplicationOutcome<ParticipantsApplicationData, ParticipantsProjection, boolean>;
37
+ type ParticipantQueryContext = Pick<SquareObservation, 'location' | 'now' | 'automaticDeliveryAvailable'>;
38
+ export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, observe?: boolean): ParticipantsResult;
33
39
  export declare function listPresentation(square: OpenSquare, control?: OperationControl): Promise<ListPresentation>;
34
- export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string): ApplicationOutcome<StatusApplicationData, StatusProjection>;
35
- export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer: string, presentation: boolean): ApplicationOutcome<StatusApplicationData, StatusProjection, boolean>;
36
- export declare function eventPresentation(square: OpenSquare, id: ActivityId, control?: OperationControl): Promise<EventPresentation>;
40
+ export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string, observe?: boolean): StatusResult;
37
41
  export declare function catchPresence(square: OpenSquare, control?: OperationControl): Promise<CatchPresence>;
38
42
  export declare function streamProjection(square: OpenSquare, cursor: number, recipient?: string, control?: OperationControl): Promise<StreamProjection>;
39
43
  export declare function streamTailProjection(square: OpenSquare, last?: number, recipient?: string, control?: OperationControl): Promise<StreamProjection>;
package/dist/views.js CHANGED
@@ -104,9 +104,6 @@ export function selectHistory(facts, query, limit, kinds, presentation = true) {
104
104
  export function publicActivities(activities) {
105
105
  return activities.flatMap((activity) => activity.kind === 'read' || activity.actor === undefined ? [] : [toPublicActivity(activity)]);
106
106
  }
107
- export async function history(square, query = {}, control) {
108
- return (await square.artifact.read((facts) => publicActivities(selectHistory(facts, query, query.limit ?? null, NON_READ).activities), control?.signal)).value;
109
- }
110
107
  export async function resolveParticipant(square, name, control) {
111
108
  return (await square.artifact.read((facts) => ({ name: resolveKnownName(facts, name) }), control?.signal)).value;
112
109
  }
@@ -119,9 +116,6 @@ function statuses(facts, now) {
119
116
  return { name: p.name, state: p.state === 'done' ? 'done' : 'joined', consumedThrough: cursor < 0 ? null : formatActivityId(cursor), watching: p.presence === 'watching', listening: p.listening };
120
117
  });
121
118
  }
122
- export async function participants(square) {
123
- return (await square.artifact.read((facts) => statuses(facts, square.clock()))).value;
124
- }
125
119
  export async function snapshot(square, control) {
126
120
  return (await square.artifact.read((facts) => {
127
121
  const header = facts.header(), hold = facts.hold();
@@ -136,19 +130,19 @@ export async function snapshot(square, control) {
136
130
  export async function recoveryObservation(square, name, control) {
137
131
  return (await square.artifact.read((facts) => {
138
132
  const known = resolveKnownName(facts, name), hold = facts.hold();
139
- return { name: known, roster: rosterNames(facts), activitySummaries: unreadActivitySummaries(facts, known, square.clock()), participantCount: inSquareCount(facts), held: hold.active,
133
+ return { participant: known, roster: rosterNames(facts), activitySummaries: unreadActivitySummaries(facts, known, square.clock()), standingCount: inSquareCount(facts), held: hold.active,
140
134
  ...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: facts.participant(known).sayCount, hardCap: facts.header().hardCap };
141
135
  }, control?.signal)).value;
142
136
  }
143
137
  export async function entryPresentation(square, name, lastN = HISTORY_DEFAULT_LIMIT, control) {
144
138
  return (await square.artifact.read((facts) => {
145
- const header = facts.header(), p = facts.participant(name);
139
+ const header = facts.header();
146
140
  const recentActivities = selectHistory(facts, {}, lastN, ['say', 'done']).activities;
147
- return { joined: p?.joined ?? false, joinedBefore: facts.activityCount({ kinds: ['join'], actors: [name] }) > 1,
148
- scene: header.warmup.join('\n').trim(), context: header.preamble.join('\n').trim(),
141
+ return { joinedBefore: facts.activityCount({ kinds: ['join'], actors: [name] }) > 1,
142
+ scene: header.warmup.join('\n').trim(),
149
143
  joinContext: (header.preamble.at(-1) === '---' ? header.preamble.slice(0, -1) : header.preamble).join('\n').trim(), recentActivities,
150
144
  perceptions: Object.fromEntries(recentActivities.map((a) => [a.index, perceiveActivity(facts, a, name)])),
151
- sayNumbers: facts.sayOrdinals(recentActivities.map((a) => a.index)), participantCount: inSquareCount(facts), held: facts.hold().active };
145
+ sayNumbers: facts.sayOrdinals(recentActivities.map((a) => a.index)) };
152
146
  }, control?.signal)).value;
153
147
  }
154
148
  function classifyPresence(participants, now) {
@@ -160,10 +154,10 @@ function classifyPresence(participants, now) {
160
154
  classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.state === 'done' ? 'done'
161
155
  : participant.state === 'not joined' ? 'not-joined' : hereNames.has(participant.name) ? 'here' : 'lingering'])) };
162
156
  }
163
- export function queryParticipants(facts, context, presentation = true) {
157
+ export function queryParticipants(facts, context, observe = false) {
164
158
  const participants = coreParticipants(facts, context.now);
165
- return { data: { participants }, projection: !presentation ? undefined : { ...context, ...classifyPresence(participants, context.now),
166
- standingCount: inSquareCount(facts), held: facts.hold().active } };
159
+ return { participants, ...(!observe ? {} : { observation: { ...context, ...classifyPresence(participants, context.now),
160
+ standingCount: inSquareCount(facts), held: facts.hold().active } }) };
167
161
  }
168
162
  export async function listPresentation(square, control) {
169
163
  return (await square.artifact.read((facts) => {
@@ -174,7 +168,7 @@ export async function listPresentation(square, control) {
174
168
  return { context: facts.header().preamble, participants, activities: facts.activityCount({ kinds: ['say'] }) };
175
169
  }, control?.signal)).value;
176
170
  }
177
- export function queryStatus(facts, context, viewer = '', presentation = true) {
171
+ export function queryStatus(facts, context, viewer = '', observe = false) {
178
172
  const participants = coreParticipants(facts, context.now);
179
173
  const header = facts.header(), hold = facts.hold(), standingCount = inSquareCount(facts);
180
174
  const latest = facts.activityHeads({ kinds: ['say', 'done'] }, { edge: 'newest', limit: 1 }).items[0];
@@ -191,15 +185,15 @@ export function queryStatus(facts, context, viewer = '', presentation = true) {
191
185
  participants: participants.filter((p) => p.state !== 'not joined'), now: context.now,
192
186
  ...(latestAct === undefined || latestAct.actor === undefined || latestAct.kind === 'read' ? {} : { latestActivity: toPublicActivity(latestAct) }),
193
187
  };
194
- if (!presentation)
195
- return { data, projection: undefined };
188
+ if (!observe)
189
+ return data;
196
190
  const afterIndex = latestAct?.index ?? -1;
197
191
  const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({ activity,
198
192
  rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0 }));
199
193
  const canonicalViewer = facts.participant(viewer)?.name ?? viewer;
200
194
  return {
201
- data,
202
- projection: {
195
+ ...data,
196
+ observation: {
203
197
  ...context, ...classifyPresence(participants, context.now), standingCount, held: hold.active,
204
198
  knownParticipantCount: participants.length, viewer: canonicalViewer,
205
199
  ...(canonicalViewer === '' ? {} : { participant: canonicalViewer }),
@@ -210,14 +204,6 @@ export function queryStatus(facts, context, viewer = '', presentation = true) {
210
204
  },
211
205
  };
212
206
  }
213
- export async function eventPresentation(square, id, control) {
214
- return (await square.artifact.read((facts) => {
215
- const activity = facts.activity(parseRequiredActivityId(id));
216
- if (activity === undefined)
217
- throw new SquareError('invalid_args', `Unknown activity id: ${id}`);
218
- return { activity, participantCount: inSquareCount(facts), held: facts.hold().active };
219
- }, control?.signal)).value;
220
- }
221
207
  export async function catchPresence(square, control) {
222
208
  return (await square.artifact.read((facts) => {
223
209
  const now = square.clock();
@@ -1,5 +1,10 @@
1
1
  # Application presentation demand
2
2
 
3
+ > **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
4
+ > below describe the earlier implementation, not current instructions. Use the real
5
+ > [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
6
+
7
+
3
8
  ## Design
4
9
 
5
10
  `createSquareApplication(context)` retains complete terminal-facing outcomes. MCP uses `createSquareApplication(context, { presentation: false })`; its outcome has the same `data` contract and `projection: undefined`.
@@ -1,6 +1,11 @@
1
1
  # Shared application boundary: delivery evidence
2
2
 
3
- Deliverer evidence, **not independent review or acceptance**. Design and the complete caller/deletion map are in [application-facade-design](application-facade-design.md).
3
+ > **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
4
+ > below describe the earlier implementation, not current instructions. Use the real
5
+ > [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
6
+
7
+
8
+ Deliverer evidence, **not independent review or acceptance**. The historical caller/deletion map is recorded below; current ownership is described in [Square/Participant API](square-api.md).
4
9
 
5
10
  ## Candidate coordinates
6
11
 
@@ -174,7 +179,7 @@ These are deliverer closures, not a reviewer verdict. Rows distinguish behavior
174
179
  | SDK projection roundtrip / leaked observations | SDK express uses `prepareExpression` + `landExpression`, strips observation/waited and retains delivery; SDK catch uses `catchOwned` and returns only established CatchResult. SDK test asserts established fields and avoids application body projections. SDK Square history stays unbounded, Participant recent-ten; parity history fixture proves both, and projection test preserves detached delivered predicate. |
175
180
  | Duplicate contracts, aliases and dead mixed views | Private status/unread producers import/re-export facade contracts; catch observation extends shared expression facts and enriched projection reuses it; required express participant replaces name alias. Retired helper search is empty. Recovery selects only gates/bounded unread summaries, not discarded feed/ordinals/perceptions. |
176
181
  | Packed owner orchestration hides effects/count integrity | History, lifecycle branches, maintenance/discovery/recovery, catch conflict and SDK recognize are expanded into readable steps. Remaining one-line SDK delegates are supported simple resource calls, not packed async policy. Inspect implementation diff; all new source is included in count. |
177
- | Invalid reply / missing reach / done repair regressions | Typed `invalid_reply` covers malformed and valid-but-absent IDs; typed reach/done reasons stay domain facts, CLI owns glyphs/flags. Express-recovery tests execute printed repaired commands, preserving raw drafts and other recipients; missing reach offers executable `--no-mention`; done ends scoped help. Unknown/landed effects offer reconciliation, never replay. |
182
+ | Invalid reply / done repair regressions | Typed `invalid_reply` covers malformed and valid-but-absent IDs; typed reach/done reasons stay domain facts, CLI owns glyphs/flags. Express-recovery tests execute printed repaired commands, preserving raw drafts, body candidates and other recipients. Unknown/landed effects offer reconciliation, never replay. |
178
183
  | Failing application clock prevents draft creation | `executeExpression` uses `saveActivityDraft`'s transport-local `Date.now` default, never the failed owner clock. Focused `express-recovery` fixture supplies a throwing clock and landed error plus throwing recovery; exact CRLF raw is already saved at recovery invocation, landed ID remains in history reconciliation, and no replay is offered. |
179
184
  | Captured default wake stops at adapter or executable selection | Amended `defaultWakeAdapters(env)` builds `CodexQueueAdapter({env})`; queue executable comes from explicit bin, else supplied env, never later ambient env. Boundary test uses a real isolated fake executable and captured child environment/queue destination after global mutation; injected port wins. No real native delivery. |
180
185
  | Force-build ignores cancellation under lock | Amended build/create signal reaches existing lock retries and transaction boundaries; abort checked before public link, cleanup signal-free, no postcommit rejection. Real exclusive-lock fixture cancels, unlocks, and proves unchanged snapshot/no delayed rebuild/no temporary artifact. Separate published-link fixture aborts after commit and still gets the build result. |
@@ -1,8 +1,8 @@
1
1
  # MCP connection lifetime
2
2
 
3
- An MCP server owns one artifact connection for its configured square. Its lifetime ends on close or disconnect; create a new server instance for a new connection. Replacing an artifact file under a running server remains unsupported. Every tool call still creates an application with that request's participant and harness session. Calls with an explicit `location` override instead open and close a per-call connection, leaving the configured-square owner untouched. Only the default connection is shared: authorization, presence, activity facts and cancellation remain operation-scoped. The artifact port already serializes transactions on its connection.
3
+ An MCP server owns one artifact connection for its configured square. Its lifetime ends on close or disconnect; create a new server instance for a new connection. Replacing an artifact file under a running server remains unsupported. Every tool call opens an actual `Square` handle with that request's captured environment, default participant and harness session; `square.participant()` performs nonmutating name selection. Calls with an explicit `location` override instead open and close a per-call connection, leaving the configured-square owner untouched. Only the default connection is shared: authorization, presence, activity facts and cancellation remain operation-scoped. The artifact port already serializes transactions on its connection.
4
4
 
5
- `createSquareResourceOwner()` is the explicit lifetime boundary. `scoped()` borrows its connection; ordinary application callers still open and close their own. Cross-square `inbox()` reads remain independent. The connection captures no request abort signal. Explicit server close and transport disconnect share one shutdown path: the SDK cancels requests, handlers finish their cleanup, then the connection closes.
5
+ `createSquareResourceOwner()` is the explicit lifetime boundary. `Square.at({resources, ...})` borrows its connection; closing a request handle leaves the owner open. Ordinary callers open and close their own Square. Cross-square `inbox()` reads remain independent. The connection captures no request abort signal. Explicit server close and transport disconnect share one shutdown path: the SDK cancels requests, handlers finish their cleanup, then the connection closes.
6
6
 
7
7
  ## Local measurement
8
8
 
package/guides/mcp.md CHANGED
@@ -8,6 +8,6 @@ The server exposes `join`, `express`, `catch`, `history`, `listen`, `ignore`, `l
8
8
 
9
9
  Claude's bundled plugin configures this server automatically. Its mod supplies live session/cwd/subagent attribution through an out-of-band tool-use-ID association, not AI-visible arguments; see [Claude call identity](claude-native.md#mcp-tools-and-call-identity). The bundled endpoint requires that association rather than falling back to its startup environment.
10
10
 
11
- Tool definitions and argument-to-facade calls live in `square-tools.ts`, shared with the [Pi native tools](pi-tools.md). `express.noMention` disables body @name scanning without disabling explicit mentions or reply attention.
11
+ Tool definitions and argument-to-`Square`/`Participant` calls live in `square-tools.ts`, shared with the [Pi native tools](pi-tools.md). Join serializes the handle's name and receipt fields, never resource internals. All callers use the [same concrete resource API](square-api.md), with no alternate factory or generic outcome envelope. `express.noMention` disables body @name scanning without disabling explicit mentions or reply attention.
12
12
 
13
13
  Configure a client with command `npx` and arguments `--yes --package @astrosheep/square square mcp-server`, or use the installed `square` executable with argument `mcp-server`. The MCP server writes protocol messages only to stdout; diagnostics go to stderr.
@@ -14,4 +14,6 @@ The single-root HostLedgerPort passes `(input, signal)` for cancellable claims;
14
14
 
15
15
  Ownership and delivery have the same limitation: cancellation can stop work that has not entered the boundary, but it cannot withdraw an ownership effect or a transport send that already happened. The returned committed result and existing degradation classification remain authoritative.
16
16
 
17
+ The same `Square`/`Participant` methods serve SDK and adapters; see [resource API](square-api.md). Join, listener and lifecycle essential observations use commit-time facts. Join's optional entry enrichment may be unavailable without rejecting its successful receipt. CLI express passes its abort signal through the initial `Square.at`; raw draft saving precedes a separate uncancelled recovery reopen.
18
+
17
19
  Pi notification cancellation is separate and unchanged.
@@ -1,5 +1,10 @@
1
1
  # Canonical catch selection — delivery evidence
2
2
 
3
+ > **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
4
+ > below describe the earlier implementation, not current instructions. Use the real
5
+ > [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
6
+
7
+
3
8
  Deliverer evidence only, **not independent review or acceptance**.
4
9
 
5
10
  ## Coordinates and lane attribution
@@ -1,5 +1,10 @@
1
1
  # History query consolidation — delivery evidence
2
2
 
3
+ > **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
4
+ > below describe the earlier implementation, not current instructions. Use the real
5
+ > [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
6
+
7
+
3
8
  Deliverer evidence, not an independent review or acceptance.
4
9
 
5
10
  ## Coordinates and lane-local reduction
@@ -1,5 +1,10 @@
1
1
  # Canonical status and participant queries — delivery evidence
2
2
 
3
+ > **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
4
+ > below describe the earlier implementation, not current instructions. Use the real
5
+ > [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
6
+
7
+
3
8
  Deliverer evidence, not independent review or acceptance.
4
9
 
5
10
  ## Coordinates and lane attribution
@@ -1,5 +1,10 @@
1
1
  # Artifact and resource primitive consolidation
2
2
 
3
+ > **Historical evidence, superseded API.** The factory/outcome shapes and split SDK defaults
4
+ > below describe the earlier implementation, not current instructions. Use the real
5
+ > [Square/Participant API](square-api.md); the independent factory and its design guide are deleted.
6
+
7
+
3
8
  Deliverer evidence, not independent review or acceptance.
4
9
 
5
10
  ## Coordinates and scope
@@ -414,7 +414,7 @@ No production structure below carries `.state`, all `.activities`, or a deferred
414
414
  |---|---|
415
415
  | openSquare/openSquareArtifact, wait baselines, watch headers, cli/program | header/revision only; remove preliminary whole-state validation/load. Every normal selected-row decode remains validated. |
416
416
  | square-actions join/implicitJoin/takeover/done/endOwnedSession, registry-associated route cleanup | participant/roster as needed, routes, land/replaceRoutes; same host claim lock scopes and cleanup sequences. Takeover done+join is one txn. No full SDK snapshot. |
417
- | createSquareApplication.join's before/after snapshot and CLI hold/resume/done count calls | new internal `joinFacts(name)` projection = requested participant, known joined-or-done count, standing count. Before conflict pending uses known joined-or-done count as today; application's participantCount keeps its existing known joined-or-done semantics. CLI header after hold/resume/done uses standing count. Wire private helpers on Square/context rather than calling public snapshot. |
417
+ | Join's before/after snapshot and CLI hold/resume/done count calls | Current `Square.join` returns its concrete receipt with a Participant handle; optional observations use transaction-captured counts, held state and time. Listener/lifecycle commit facts likewise come from the mutation transaction; join's extra entry history is best-effort. Known joined-or-done `participantCount` remains distinct from the standing count. See [Square/Participant API](square-api.md). |
418
418
  | express/decideAct and behind rendering (activity.ts) | header, actor fact, roster for longest standing-body mention resolution, hold, reply head/existence, recentSayTimes, continuous cursor, directed suffix. Admission normalization/error order unchanged. A valid reply coordinate is <nextActIndex, even if a gap: preserve current acceptance of assigned-but-missing reply. ActivityPresentation supplies own count/cap/hold/roster, suffix items with lifetime ordinals + settled full perception, and behind summaries. No whole history used just to number previews. |
419
419
  | listen/ignore/listening, watch leases and terminal checks | actor fact, outgoing attention; roster only for quorum; land or lease methods. Unknown valid attention target still allowed. Pulse keeps hold-before-terminal priority. |
420
420
  | catch / native seen | directed selectors/activity exact, markSeen, cursor/counts in txn; native `current` becomes synchronous `(facts: SquareRead)=>boolean`, evaluated ONLY within reads/receipt txn. It cannot escape or call host I/O. |
@@ -0,0 +1,107 @@
1
+ # Square and Participant API
2
+
3
+ `Square` is the actual resource handle shared by SDK, CLI, MCP and Pi. Its implementation is
4
+ `square-api.ts`; `square.ts` remains the executable CLI entrypoint. `square-facade.ts` contains
5
+ only public types. There is no separate operation factory, dispatcher or `{data, projection}` envelope.
6
+
7
+ ## Open, select, join, close
8
+
9
+ ```ts
10
+ import { Square } from '@astrosheep/square';
11
+
12
+ const square = await Square.at({ path: './PUBLIC.square', env: { ...process.env } });
13
+ try {
14
+ const joined = await square.join('Alice');
15
+ const alice = joined.participant;
16
+ const sent = await alice.express('Hello @Bob', { force: true });
17
+ const page = await square.history();
18
+ console.log(sent.activity.id, page.activities);
19
+ } finally {
20
+ await square.close();
21
+ }
22
+ ```
23
+
24
+ - `Square.build({path, markdown, hardCap?, throttlePerMinute?, template?, force?, ...})` returns
25
+ the same open resource. `Square.inMemory({markdown, ...})` is synchronous and creates no default
26
+ native wake transport.
27
+ - `Square.at` accepts `path`, optional `cwd`, captured `env`, `clock`, `hostLedger`, `wakeTransport`
28
+ and default `participant`. An omitted path uses captured `SQUARE_LOCATION`; relative paths use
29
+ `cwd`. Explicit participant, configured default, nonblank captured `SQUARE_PARTICIPANT_NAME`,
30
+ then an unambiguous current-session ledger binding determine a name, in that order.
31
+ - `await square.participant(name?)` selects a handle **without joining, claiming or reentering**.
32
+ Discovery happens on each selection, not through a cached session-to-name decision.
33
+ - `square.join(name?, options?, control?)` returns one `JoinResult`: `{participant, activity, kind,
34
+ participantCount, observation?}`. `participant` is a handle, not a string. `takeover: true` reclaims
35
+ a standing name; fresh/departed names join normally. There is no separate `Participant.join()`
36
+ or public `takeover()` API. `participantCount` counts known joined-or-done names.
37
+ - A Square owns its connection unless `resources: createSquareResourceOwner()` lends it one.
38
+ Closing a borrowing handle leaves that owner open. Request signals never become the borrowed
39
+ connection's lifetime. The MCP server drains cancelled/in-flight work before closing its owner;
40
+ explicit location overrides get independent request-owned connections.
41
+
42
+ ## One set of operations and defaults
43
+
44
+ Square owns `history`, `status`, `participants`, `snapshot`, `stream`, and recovery observations.
45
+ Participant owns `express`, `catch`, `listen`, `ignore`, `listening`, `hold`, `resume`, `done`;
46
+ its `history` delegates to the same Square history operation.
47
+
48
+ - Express waits while held/throttled unless `noWait: true`, for **every** caller. `ExpressResult`
49
+ includes the committed `activity`, `waited`, optional `delivery`, and landing-time warnings
50
+ (`unmatchedMentions`, `replyAuthorNotHere`). Warnings are not stored on the activity.
51
+ - Catch returns `{activities, consumedThrough, remaining, idleExpired, status?, observation?}`.
52
+ Omitted/zero idle is immediate; positive idle uses the single leased quiet wait. Receipt selection,
53
+ ordinals, counts and terminal facts settle in the receipt transaction. Cancellation/replacement
54
+ releases only that waiter's lease and never consumes future activity.
55
+ - All history callers return a `HistoryResult` object with `activities`. Default page size is ten;
56
+ `limit: null` explicitly requests an unbounded archive. Numeric limits must be 1–100. Contextual
57
+ queries still reject an implicit merged window over 100 activities; an explicit numeric limit
58
+ bounds it. CLI/tool schemas accept numeric limits only. History never writes receipts.
59
+ - `participants()` returns `{participants, observation?}` and `listening()` returns
60
+ `{listening, observation?}`, not arrays carrying metadata.
61
+ - Lifecycle operations keep contextual reentry/ownership policy. In particular, done retains its
62
+ native owner epoch fence instead of blindly joining first. Done accepts no message.
63
+
64
+ Every operation accepts its final `OperationControl` (`signal`, optional typed `onProgress`).
65
+ Listener/lifecycle operations take optional observation options before that control argument.
66
+
67
+ ## Optional observations, not another execution path
68
+
69
+ Request `observe: true` on the **same method** when rendering needs additional facts. Each operation
70
+ has a concrete optional observation type, without conditional generics or a generic outcome wrapper.
71
+ Data-only calls do not hydrate join history, the pre-expression feed, catch presence enrichment,
72
+ or history/status presentation annotations.
73
+
74
+ Express captures its optional directed feed before the attempt and returns count/held/time facts
75
+ from the committed transaction. Listener and lifecycle observations likewise use their mutation's
76
+ facts, with no postcommit context read. Join uses its mutation's essential facts and treats optional
77
+ entry/history enrichment as best-effort: `observation.enrichmentUnavailable` is explicit and the
78
+ successful receipt survives. Catch preserves its committed page even if optional nearby presence
79
+ fails. A cancellation or failed clock after commit cannot turn these receipts into an unsent retry.
80
+
81
+ `OperationError.operation` supplements authoritative error facts with caller/filter/recipient and
82
+ express certainty. Selected-row/execution failures remain unknown unless admission proved the
83
+ expression unsent. CLI saves raw drafts even when opening the resource fails; its initial open
84
+ receives the express abort signal, while recovery reopening is deliberately uncancelled.
85
+
86
+ ## Adapters and standalone functions
87
+
88
+ CLI and tools only adapt input, identity, resource lifetime and output. They do not import private
89
+ actions, storage, admission, scanners or query execution. MCP/Pi use the shared `square-tools.ts`
90
+ definitions. Join JSON contains `participant.name` and the receipt fields, never the handle or its
91
+ resources. Express wire output omits the internal delivery diagnostic as before.
92
+
93
+ `discover(input, options?, control?)`, `inbox(input, sessionId, options?, control?)`,
94
+ `diagnoseSquare(input, control?)`, and `migrateSquare(input, control?)` are ordinary public exports.
95
+ They are not methods on a synthetic cross-square object. Diagnose/migrate resolve a path without
96
+ opening a valid current-format Square first.
97
+
98
+ ## Deliberate migration breaks
99
+
100
+ The independent `createSquareApplication` API, its options/results/aliases, and `square-application.ts`
101
+ are deleted. Consumers must open/close an actual Square and select a Participant. SDK joins must use
102
+ `(await square.join(name)).participant`; history callers must use `.activities` and opt into full
103
+ archive reads with `limit: null`. SDK express now waits by default. MCP constructor configuration
104
+ uses `path`, consistent with `Square.at`, rather than `squarePath`.
105
+
106
+ External consumers must migrate before taking this source version. Editing source does not update
107
+ an installed plugin, build, or running extension.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.78",
3
+ "version": "0.3.80",
4
4
  "description": "A shared public square where agents join, catch activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.78",
3
+ "version": "0.3.80",
4
4
  "description": "Native Claude Code inbox delivery for Square participants",
5
5
  "types": "./types.d.ts",
6
6
  "author": {
@@ -37,10 +37,10 @@ I disagree — the cache is the wrong layer for this.
37
37
 
38
38
  **Reach.** Every activity chooses who it is meant for:
39
39
 
40
- - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Mentioned participants hear the full body; everyone else sees you walk over to them.
41
- - **Reply** to an activity to reach its author the same way.
42
- - **Ring the bell** only when every participant needs it.
43
- - **Land it bare** when it is meant for no one in particular. Only participants listening to you receive it.
40
+ - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Unmatched names stay literal, with a notice to you. Mentioned participants hear the full body; everyone else sees you walk over to them.
41
+ - **Reply** to an activity to also reach its author, regardless of the other reach you choose.
42
+ - **Ring the bell** only when every participant needs it. Body `@names` stay literal; explicit mentions cannot combine with a bell.
43
+ - **Land it bare** when it is meant for no one in particular. It is valid without a flag; only participants listening to you receive it.
44
44
 
45
45
  Addressing is not a secrecy boundary. Everything stays readable through `history`.
46
46
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.78",
3
+ "version": "0.3.80",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -3,7 +3,7 @@ import { hostLedgerForEnv } from '../../registry.js';
3
3
  import { canonicalPath } from '../../canonical-path.js';
4
4
  import { openSquare } from '../../open-square.js';
5
5
  import { closeOpenSquare } from '../../open-square.js';
6
- import { Square } from '../../square-application.js';
6
+ import { Square } from '../../square-api.js';
7
7
  import { connectNative, type OpenCodeNativeSession, type OpenCodeSessionCapability } from '../../packages/agent-delivery/src/opencode-native.js';
8
8
  import { observeOpenCodeContext, publishOpenCodeRoutes, receiveOpenCodePending, type OpenCodeContextMessage } from './delivery.js';
9
9
 
@@ -84,20 +84,30 @@ function parseBlocks(markdown: string): Block[] {
84
84
 
85
85
  interface SquareBlock { meta?: string; actor?: string; body: string }
86
86
 
87
+ function parseAttributes(source: string): Record<string, string> {
88
+ const attributes: Record<string, string> = {};
89
+ for (const match of source.matchAll(/([a-z]+)="([^"]*)"/g)) {
90
+ attributes[match[1]!] = match[2]!.replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
91
+ }
92
+ return attributes;
93
+ }
94
+
87
95
  // A square notification is one or more <square-activity> blocks. Split them out so the
88
- // envelope lines become styled chrome and only the body goes through markdown.
96
+ // envelope becomes styled chrome and only the body goes through markdown: where and which
97
+ // activity come from tag attributes, and the first inner line is the route sentence.
89
98
  function parseSquareBlocks(text: string): SquareBlock[] {
90
99
  const blocks: SquareBlock[] = [];
91
- const re = /<square-activity>\n([\s\S]*?)\n<\/square-activity>/g;
100
+ const re = /<square-activity([^>]*)>\n([\s\S]*?)\n<\/square-activity>/g;
92
101
  let last = 0;
93
102
  for (const match of text.matchAll(re)) {
94
103
  const index = match.index ?? 0;
95
104
  const before = text.slice(last, index).trim();
96
105
  if (before !== "") blocks.push({ body: before });
97
- const inner = match[1]!;
98
- const innerLines = inner.split("\n");
99
- const meta = innerLines[0]?.startsWith("· ") ? innerLines.shift() : undefined;
100
- const actor = innerLines[0]?.startsWith("● ") ? innerLines.shift() : undefined;
106
+ const attributes = parseAttributes(match[1]!);
107
+ const innerLines = match[2]!.split("\n");
108
+ const described = [attributes.square, attributes.as, attributes.id].filter((value) => value !== undefined);
109
+ const meta = described.length > 0 ? `· ${described.join(" · ")}` : undefined;
110
+ const actor = innerLines.shift();
101
111
  blocks.push({ meta, actor, body: innerLines.join("\n").trim() });
102
112
  last = index + match[0].length;
103
113
  }
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import { Container, Markdown, Spacer, Text } from '@earendil-works/pi-tui';
3
3
  import { z } from 'zod/v4';
4
4
 
5
- import { createSquareApplication } from '../../dist/square-application.js';
5
+ import { Square } from '../../dist/square-api.js';
6
6
  import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolFailure } from '../../dist/square-tools.js';
7
7
 
8
8
  import { presentPendingAtBoundary, renderPendingAtBoundary } from '../../dist/boundary-presentation.js';
@@ -151,14 +151,15 @@ export default function squarePiExtension(pi) {
151
151
  namespace: { name: 'square', description: 'Participate in a shared public square.', instructions: SQUARE_TOOL_INSTRUCTIONS },
152
152
  async execute(_id, args, signal, _onUpdate, ctx) {
153
153
  try {
154
- const app = createSquareApplication({
154
+ const square = await Square.at({
155
155
  cwd: ctx.cwd,
156
156
  env: operationEnv('pi', ctx.sessionManager.getSessionId(), process.env),
157
- squarePath: args.location,
157
+ path: args.location,
158
158
  participant: args.as,
159
- }, { presentation: false });
160
- // Without a resource owner the facade closes each call's connection, including on cancellation.
161
- const { data } = await tool.run(app, args, { signal });
159
+ }, { signal });
160
+ let data;
161
+ try { data = await tool.run(square, args, { signal }); }
162
+ finally { await square.close(); }
162
163
  return { content: [{ type: 'text', text: JSON.stringify(data) }], details: data, structuredContent: data };
163
164
  } catch (error) {
164
165
  const data = { error: squareToolFailure(error, signal) };
@@ -37,10 +37,10 @@ I disagree — the cache is the wrong layer for this.
37
37
 
38
38
  **Reach.** Every activity chooses who it is meant for:
39
39
 
40
- - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Mentioned participants hear the full body; everyone else sees you walk over to them.
41
- - **Reply** to an activity to reach its author the same way.
42
- - **Ring the bell** only when every participant needs it.
43
- - **Land it bare** when it is meant for no one in particular. Only participants listening to you receive it.
40
+ - **Mention** participants to address them. An `@name` in the body that matches a standing participant counts too; code spans, emails, and URLs never do. Unmatched names stay literal, with a notice to you. Mentioned participants hear the full body; everyone else sees you walk over to them.
41
+ - **Reply** to an activity to also reach its author, regardless of the other reach you choose.
42
+ - **Ring the bell** only when every participant needs it. Body `@names` stay literal; explicit mentions cannot combine with a bell.
43
+ - **Land it bare** when it is meant for no one in particular. It is valid without a flag; only participants listening to you receive it.
44
44
 
45
45
  Addressing is not a secrecy boundary. Everything stays readable through `history`.
46
46
 
@@ -1,38 +0,0 @@
1
- import type { Activity, ExpressResult, HistoryQuery, OperationControl, Participant, ParticipantStatus, SquareApplication, SquareApplicationContext, SquareAtInput, SquareBuildInput, SquareSnapshot } from './square-facade.js';
2
- export type * from './square-facade.js';
3
- export { createSquareResourceOwner } from './open-square.js';
4
- export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
5
- export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
6
- export { HELD_WAIT_BUDGET_MS } from './runtime.js';
7
- export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
8
- export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
9
- /** Supported SDK resource. It owns one open artifact; application operations use
10
- * the same owner functions directly, not an application -> SDK -> application chain. */
11
- export declare class Square {
12
- readonly location: string;
13
- private readonly square;
14
- private readonly transport;
15
- private constructor();
16
- static at(input: SquareAtInput, control?: OperationControl): Promise<Square>;
17
- static build(input: SquareBuildInput): Promise<Square>;
18
- static inMemory(input: Omit<SquareBuildInput, 'path'>): Square;
19
- private handle;
20
- join(name: string, control?: OperationControl): Promise<Participant>;
21
- takeover(name: string, control?: OperationControl): Promise<Participant>;
22
- doneOwnedSession(name: string, body: string, expectedEpoch: number, control?: OperationControl): Promise<ExpressResult>;
23
- implicitJoin(name: string, control?: OperationControl): Promise<{
24
- readonly state: 'joined' | 'active' | 'done';
25
- readonly participant?: Participant;
26
- }>;
27
- participants(): Promise<ParticipantStatus[]>;
28
- snapshot(control?: OperationControl): Promise<SquareSnapshot>;
29
- history(query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
30
- recognize(env: NodeJS.ProcessEnv): Promise<Participant | null>;
31
- close(): Promise<void>;
32
- endOwnedSession(name: string, sessionId: string, expectedEpoch?: number): Promise<ExpressResult | null>;
33
- retireEndedSessionRoutes(sessionId: string): Promise<void>;
34
- }
35
- export declare function createSquareApplication(context: SquareApplicationContext): SquareApplication;
36
- export declare function createSquareApplication(context: SquareApplicationContext, demand: {
37
- readonly presentation: false;
38
- }): SquareApplication<false>;