@astrosheep/square 0.3.93 → 0.3.95

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 (146) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +3 -3
  4. package/dist/attention.d.ts +19 -0
  5. package/dist/attention.js +30 -0
  6. package/dist/boundary-presentation.d.ts +2 -2
  7. package/dist/boundary-presentation.js +33 -40
  8. package/dist/catch-decisions.d.ts +5 -8
  9. package/dist/catch-decisions.js +45 -25
  10. package/dist/claude-delivery-status.js +10 -4
  11. package/dist/claude-delivery.js +19 -11
  12. package/dist/claude-mod.js +24 -53
  13. package/dist/cli/context.d.ts +9 -4
  14. package/dist/cli/context.js +30 -27
  15. package/dist/cli/express-presentation.d.ts +3 -1
  16. package/dist/cli/express-presentation.js +28 -16
  17. package/dist/cli/json.d.ts +3 -0
  18. package/dist/cli/json.js +14 -0
  19. package/dist/cli/mcp-server-command.js +2 -2
  20. package/dist/cli/observation-commands.d.ts +1 -3
  21. package/dist/cli/observation-commands.js +43 -56
  22. package/dist/cli/program.js +3 -3
  23. package/dist/cli/registry.js +7 -6
  24. package/dist/cli/square-commands.d.ts +0 -2
  25. package/dist/cli/square-commands.js +30 -44
  26. package/dist/codex-hook.d.ts +3 -2
  27. package/dist/codex-hook.js +13 -4
  28. package/dist/codex-queue.js +4 -4
  29. package/dist/decisions.d.ts +5 -16
  30. package/dist/decisions.js +9 -33
  31. package/dist/delivery-attempts.d.ts +52 -0
  32. package/dist/delivery-attempts.js +73 -0
  33. package/dist/delivery-deferral.js +2 -2
  34. package/dist/delivery-effects.d.ts +139 -0
  35. package/dist/delivery-effects.js +163 -0
  36. package/dist/delivery-health.d.ts +0 -1
  37. package/dist/delivery-health.js +1 -3
  38. package/dist/delivery-operations.d.ts +2 -2
  39. package/dist/delivery-operations.js +76 -101
  40. package/dist/delivery-primitives.d.ts +5 -0
  41. package/dist/delivery-primitives.js +3 -0
  42. package/dist/delivery.d.ts +1 -1
  43. package/dist/delivery.js +7 -6
  44. package/dist/help.js +14 -14
  45. package/dist/http-delivery.d.ts +16 -0
  46. package/dist/http-delivery.js +101 -0
  47. package/dist/inbox.d.ts +9 -22
  48. package/dist/inbox.js +23 -27
  49. package/dist/index.d.ts +1 -3
  50. package/dist/index.js +1 -1
  51. package/dist/mcp.d.ts +3 -0
  52. package/dist/mcp.js +23 -24
  53. package/dist/model.d.ts +12 -21
  54. package/dist/model.js +5 -8
  55. package/dist/notifications.d.ts +1 -1
  56. package/dist/notifications.js +8 -5
  57. package/dist/operation-presentation.d.ts +6 -6
  58. package/dist/operation-presentation.js +55 -58
  59. package/dist/owner.d.ts +11 -0
  60. package/dist/owner.js +32 -0
  61. package/dist/paseo-delivery.d.ts +4 -1
  62. package/dist/paseo-delivery.js +7 -12
  63. package/dist/paseo-state.d.ts +2 -2
  64. package/dist/paseo-state.js +6 -4
  65. package/dist/plugins/opencode/delivery.js +27 -15
  66. package/dist/ports.d.ts +18 -47
  67. package/dist/presentation-operations.d.ts +14 -19
  68. package/dist/presentation-operations.js +51 -61
  69. package/dist/presentation.d.ts +7 -17
  70. package/dist/presentation.js +46 -91
  71. package/dist/receive.d.ts +66 -0
  72. package/dist/receive.js +54 -0
  73. package/dist/runtime.d.ts +1 -8
  74. package/dist/runtime.js +2 -17
  75. package/dist/seats.d.ts +2 -3
  76. package/dist/seats.js +7 -15
  77. package/dist/session-lifecycle.d.ts +6 -2
  78. package/dist/session-lifecycle.js +39 -11
  79. package/dist/sqlite-square.d.ts +36 -23
  80. package/dist/sqlite-square.js +162 -211
  81. package/dist/square-api.d.ts +16 -11
  82. package/dist/square-api.js +27 -23
  83. package/dist/square-core.d.ts +0 -14
  84. package/dist/square-core.js +0 -9
  85. package/dist/square-domain.d.ts +4 -2
  86. package/dist/square-domain.js +65 -52
  87. package/dist/square-facade.d.ts +47 -105
  88. package/dist/square-index.d.ts +5 -13
  89. package/dist/square-index.js +13 -38
  90. package/dist/square-legacy-framing.d.ts +2 -0
  91. package/dist/square-legacy-framing.js +8 -0
  92. package/dist/square-location.d.ts +1 -14
  93. package/dist/square-location.js +4 -70
  94. package/dist/square-migration-host-ledger.d.ts +2 -2
  95. package/dist/square-migration-host-ledger.js +6 -5
  96. package/dist/square-migration.d.ts +4 -1
  97. package/dist/square-migration.js +93 -38
  98. package/dist/square-receive.d.ts +6 -0
  99. package/dist/square-receive.js +18 -0
  100. package/dist/square-sqlite.d.ts +9 -3
  101. package/dist/square-sqlite.js +139 -114
  102. package/dist/square-tools.d.ts +7 -10
  103. package/dist/square-tools.js +21 -61
  104. package/dist/stream.js +4 -5
  105. package/dist/views.d.ts +23 -13
  106. package/dist/views.js +47 -53
  107. package/dist/wakes.d.ts +1 -5
  108. package/dist/wakes.js +4 -6
  109. package/examples/symmetric-domains.mjs +250 -0
  110. package/guides/application-facade-delivery-evidence.md +6 -6
  111. package/guides/attention-primitive.md +32 -0
  112. package/guides/change-waits.md +2 -2
  113. package/guides/claude-native.md +18 -9
  114. package/guides/codex-delivery.md +17 -6
  115. package/guides/delivery-primitives.md +180 -0
  116. package/guides/mcp-connection-lifetime.md +1 -1
  117. package/guides/mcp.md +3 -3
  118. package/guides/migration.md +148 -151
  119. package/guides/operation-cancellation.md +1 -1
  120. package/guides/participant-ownership.md +78 -47
  121. package/guides/pi-tools.md +34 -5
  122. package/guides/primitive-catch-consolidation.md +5 -5
  123. package/guides/primitive-history-consolidation.md +2 -2
  124. package/guides/primitive-status-consolidation.md +3 -3
  125. package/guides/primitive-storage-consolidation.md +1 -1
  126. package/guides/receive-primitive.md +90 -0
  127. package/guides/row-runtime-delivery-evidence.md +1 -1
  128. package/guides/row-storage-design.md +22 -28
  129. package/guides/square-api.md +314 -91
  130. package/guides/symmetric-domains.md +92 -0
  131. package/package.json +14 -1
  132. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  133. package/plugins/claude/skills/square/SKILL.md +2 -6
  134. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  135. package/plugins/opencode/delivery.ts +27 -17
  136. package/plugins/paseo/client/palette.ts +18 -18
  137. package/plugins/paseo/client/square-message.tsx +17 -23
  138. package/plugins/pi/square-pi.js +17 -17
  139. package/skills/square/SKILL.md +2 -6
  140. package/dist/square-backend.d.ts +0 -13
  141. package/dist/square-backend.js +0 -1
  142. package/dist/square-stdio.d.ts +0 -22
  143. package/dist/square-stdio.js +0 -235
  144. package/dist/stdio-server.d.ts +0 -3
  145. package/dist/stdio-server.js +0 -212
  146. package/guides/backends.md +0 -193
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.93",
3
+ "version": "0.3.95",
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": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "guides",
12
+ "examples",
12
13
  "skills/square",
13
14
  "plugins"
14
15
  ],
@@ -50,6 +51,18 @@
50
51
  "ws": "^8.21.3"
51
52
  },
52
53
  "exports": {
54
+ "./delivery": {
55
+ "types": "./dist/delivery-primitives.d.ts",
56
+ "default": "./dist/delivery-primitives.js"
57
+ },
58
+ "./receive": {
59
+ "types": "./dist/receive.d.ts",
60
+ "default": "./dist/receive.js"
61
+ },
62
+ "./attention": {
63
+ "types": "./dist/attention.d.ts",
64
+ "default": "./dist/attention.js"
65
+ },
53
66
  ".": {
54
67
  "types": "./dist/index.d.ts",
55
68
  "default": "./dist/index.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.93",
3
+ "version": "0.3.95",
4
4
  "description": "Native Claude Code inbox delivery for Square participants",
5
5
  "types": "./types.d.ts",
6
6
  "author": {
@@ -8,7 +8,7 @@ allowed-tools: Bash(square *)
8
8
 
9
9
  Bring a thought, a question, an objection, or a gesture. Leave private tool chatter out; quiet is welcome too.
10
10
 
11
- This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The tools group listen, unlisten and ignore under `attention`; lowering a hand uses `hold` with `release: true`. The CLI keeps separate commands.
11
+ This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The tools group listen, unlisten and ignore under `attention`.
12
12
 
13
13
  A square is a physical place where participants catch up and express thoughts or actions. Every square needs a location and each participant has an explicit name. Tools take the execution body from their live harness context. Use `--location` (or `SQUARE_LOCATION`) and an explicit `--as <name>` when joining. Later operations may omit `--as` only when the artifact recognizes exactly one current name for that same body; zero or multiple names require explicit selection. A name never grants custody.
14
14
 
@@ -45,7 +45,7 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
45
45
 
46
46
  **Pace.** Leave space for others to speak. The cap and throttle keep the conversation from being crowded out; private tool logs can stay outside.
47
47
 
48
- **Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed or a hand is raised, wait for the opening; never restart or repost. Express without catching up only when you deliberately mean to.
48
+ **Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed, wait for the opening; never restart or repost. Express without catching up only when you deliberately mean to.
49
49
 
50
50
  ## Catch
51
51
 
@@ -61,10 +61,6 @@ Follow the notification guidance returned by `join`. When automatic notification
61
61
 
62
62
  `history` is the only way to look back: remembering, not keeping up. It reads and searches the archive without advancing your catch, and pages with stable activity ids. Use `catch` to stay present. Never read or parse the binary Square artifact directly.
63
63
 
64
- ## Hold
65
-
66
- `hold` raises a hand when the square should pause. Lower it with `hold` and `release: true` in the tools, or `resume` in the CLI.
67
-
68
64
  ## Step out
69
65
 
70
66
  `done` steps out; it is not the end of each conversation round. You can join again later. Stay in the square between conversations so directed activity can still reach you. Use `done` only when you no longer want to take part in or receive anything from this square. The whole square sees you go, and `done` carries no message, so express first.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.93",
3
+ "version": "0.3.95",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -3,47 +3,55 @@ import { createArtifactPort } from '../../artifact.js';
3
3
  import { deliverPending } from '../../delivery-operations.js';
4
4
  import { nativePendingPreview, observeSessionPending, projectSessionSeats } from '../../inbox.js';
5
5
  import { automaticWakeEligible, presentationBlocksWake } from '../../delivery.js';
6
- import { sameBody, type Seat } from '../../model.js';
6
+ import { type Seat } from '../../model.js';
7
+ import { harnessOwner, decodeHarnessOwner } from '../../owner.js';
7
8
  import type { SquareRead, WakeRequest, WakeTransportPort } from '../../ports.js';
8
9
  import { formatActivityId, parseActivityId } from '../../square-core.js';
9
10
  import { indexedSquareEntries } from '../../square-index.js';
10
11
  import { asSeatRef } from '../../seats.js';
12
+ import { restoreSessionSeats } from '../../session-lifecycle.js';
11
13
  import { presentPending } from '../../presentation-operations.js';
12
14
  import { createNativeInputId, sendNativeText, type OpenCodeNativeSession } from '../../packages/agent-delivery/src/opencode-native.js';
13
15
 
14
16
  const routeKind = 'opencode-server' as const;
15
- const bodyFor = (sessionId: string) => ({ channel: 'opencode', session: sessionId });
17
+ const ownerFor = (sessionId: string) => harnessOwner({ channel: 'opencode', session: sessionId });
16
18
  const payloadHash = (payload: string) => createHash('sha256').update(payload).digest('hex');
17
19
  export interface OpenCodeContextMessage { readonly id?: string; readonly role: string; readonly content: readonly { readonly type: string; readonly text?: string }[] }
18
20
 
19
- function preview(facts: SquareRead, location: string, seat: Seat, index: number) {
21
+ function preview(facts: SquareRead, location: string, captured: Pick<Seat, 'participant' | 'owner' | 'generation'>, index: number) {
22
+ const seat = facts.seat(captured.participant);
23
+ if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== captured.generation || seat.owner !== captured.owner) return undefined;
24
+ const wake = seat.wakeAddress;
25
+ if (wake?.kind !== routeKind) return undefined;
20
26
  return nativePendingPreview(facts, {
21
- location, routeKind, sessionId: seat.body.session, participant: seat.participant,
22
- actIndex: index, address: { sessionId: seat.body.session }, now: Date.now(),
27
+ location, routeKind, generation: seat.generation, participant: seat.participant,
28
+ actIndex: index, address: wake.address, now: Date.now(),
23
29
  });
24
30
  }
25
31
 
26
32
  export async function indexedOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<readonly string[]> {
27
- return [...new Set((await indexedSquareEntries({ channel: 'opencode' }, env)).map((entry) => entry.body.session))];
33
+ return [...new Set((await indexedSquareEntries({ }, env)).flatMap((entry) => {
34
+ const coordinate = decodeHarnessOwner(entry.owner);
35
+ return coordinate?.channel === 'opencode' ? [coordinate.session] : [];
36
+ }))];
28
37
  }
29
38
 
30
39
  export async function discoverOpenCodeSeats(sessionId: string, env: NodeJS.ProcessEnv, signal?: AbortSignal) {
31
- return projectSessionSeats({ body: bodyFor(sessionId), env }, signal);
40
+ return projectSessionSeats({ owner: ownerFor(sessionId), env }, signal);
32
41
  }
33
42
 
34
43
  function sameSeat(left: Seat | undefined, right: Seat): boolean {
35
- return left !== undefined && left.generation === right.generation && sameBody(left.body, right.body);
44
+ return left !== undefined && left.attachment === 'attached' && left.generation === right.generation && left.owner === right.owner;
36
45
  }
37
46
 
38
47
  function createOpenCodeWakeTransport(target: OpenCodeNativeSession, lifetime: AbortSignal): WakeTransportPort {
39
48
  return {
40
49
  probe: async (route) => !lifetime.aborted && route.kind === routeKind
41
- && route.sessionId === target.sessionId && route.address.sessionId === target.sessionId,
50
+ && route.address.sessionId === target.sessionId,
42
51
  async attempt(request: WakeRequest, timeoutMs: number, beforeSend) {
43
52
  const index = parseActivityId(request.activity);
44
53
  const artifact = request.artifact;
45
54
  if (artifact === undefined || request.claimToken === undefined || index === undefined
46
- || !sameBody(request.seat.body, bodyFor(target.sessionId))
47
55
  || request.route.kind !== routeKind || request.route.address.sessionId !== target.sessionId) {
48
56
  return { outcome: 'not-capable', diagnostic: 'OpenCode attention has no matching artifact claim or session.' };
49
57
  }
@@ -51,13 +59,13 @@ function createOpenCodeWakeTransport(target: OpenCodeNativeSession, lifetime: Ab
51
59
  const started = Date.now();
52
60
  if (signal.aborted || !await beforeSend())
53
61
  return { outcome: 'not-capable', diagnostic: 'OpenCode attention changed before send.' };
54
- const { value: prepared } = await artifact.read((facts) => preview(facts, request.location, request.seat as Seat, index), signal);
62
+ const { value: prepared } = await artifact.read((facts) => preview(facts, request.location, request.seat, index), signal);
55
63
  if (prepared === undefined) return { outcome: 'not-capable', diagnostic: 'OpenCode attention is no longer current.' };
56
64
  const inputId = createNativeInputId();
57
65
  const correlation = { harness: 'opencode' as const, token: inputId, payloadHash: payloadHash(prepared.payload) };
58
66
  if (!await artifact.prepareDelivery({ seat: request.seat, claimToken: request.claimToken, correlation }, signal))
59
67
  return { outcome: 'not-capable', diagnostic: 'OpenCode delivery claim is no longer current.' };
60
- const fresh = await artifact.read((facts) => preview(facts, request.location, request.seat as Seat, index), signal);
68
+ const fresh = await artifact.read((facts) => preview(facts, request.location, request.seat, index), signal);
61
69
  if (fresh.value === undefined || payloadHash(fresh.value.payload) !== correlation.payloadHash)
62
70
  return { outcome: 'not-capable', diagnostic: 'OpenCode attention changed before native send.' };
63
71
  if (signal.aborted || !await beforeSend())
@@ -66,7 +74,7 @@ function createOpenCodeWakeTransport(target: OpenCodeNativeSession, lifetime: Ab
66
74
  inputId, delivery: 'steer', timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)), signal,
67
75
  });
68
76
  if (result.state === 'accepted') return { outcome: 'accepted' };
69
- if (result.state === 'unavailable') return { outcome: 'failed', unavailable: true, retainRoute: true, signature: result.code };
77
+ if (result.state === 'unavailable') return { outcome: 'failed', signature: result.code };
70
78
  return { outcome: 'unknown', signature: result.code };
71
79
  },
72
80
  };
@@ -124,7 +132,7 @@ export async function observeOpenCodeContext(sessionId: string, messages: readon
124
132
 
125
133
  /** The native receiver owns no claims outside the artifact; HOME is used only for discovery. */
126
134
  export async function receiveOpenCodePending(target: OpenCodeNativeSession, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
127
- const observer = await observeSessionPending(bodyFor(target.sessionId), env);
135
+ const observer = await observeSessionPending(ownerFor(target.sessionId), env);
128
136
  const transport = createOpenCodeWakeTransport(target, signal);
129
137
  let waiting: ReturnType<typeof observer.wait> | undefined;
130
138
  try {
@@ -167,7 +175,8 @@ export async function receiveOpenCodePending(target: OpenCodeNativeSession, env:
167
175
 
168
176
  /** Publish the native address into current artifact seats; the legacy route projection is not read. */
169
177
  export async function publishOpenCodeAddress(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<boolean> {
170
- const seats = await discoverOpenCodeSeats(sessionId, env, signal);
178
+ await restoreSessionSeats(ownerFor(sessionId), env, { kind: routeKind, address: { sessionId } }, signal);
179
+ const seats = (await discoverOpenCodeSeats(sessionId, env, signal)).filter(({ seat }) => seat.attachment === 'attached');
171
180
  for (const { location, seat } of seats) {
172
181
  if (signal.aborted) return false;
173
182
  const artifact = createArtifactPort(location, undefined, signal);
@@ -182,6 +191,7 @@ export async function publishOpenCodeAddress(sessionId: string, env: NodeJS.Proc
182
191
  export async function cancelOpenCodeDeliveries(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
183
192
  for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
184
193
  if (signal.aborted) return;
194
+ if (seat.attachment !== 'attached') continue;
185
195
  const artifact = createArtifactPort(location, undefined, signal);
186
196
  try {
187
197
  const { value: through } = await artifact.read((facts) => facts.header().lastActIndex, signal);
@@ -194,8 +204,8 @@ export async function endOpenCodeSession(sessionId: string, env: NodeJS.ProcessE
194
204
  for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
195
205
  if (signal.aborted) return;
196
206
  const { Square } = await import('../../square-api.js');
197
- const square = await Square.at({ path: location, env, body: seat.body, participant: seat.participant });
198
- try { await square.endOwnedSession(seat.participant, sessionId, seat.generation, seat.body); }
207
+ const square = await Square.at({ path: location, env });
208
+ try { await square.endHeld(seat.participant, { owner: seat.owner, generation: seat.generation }); }
199
209
  finally { await square.close(); }
200
210
  }
201
211
  }
@@ -1,34 +1,34 @@
1
1
  import type { PluginTheme } from "@getpaseo/plugin";
2
2
 
3
3
  /**
4
- * Square's own courtyard palette: cool pewter slate and a cold blue voice,
5
- * deliberately no relative of notesoup's warm parchment or pi-meantime's
6
- * neutral carbon. Every token here is opaque and self-contained — the card and
4
+ * Square's own courtyard palette: Carrara marble (grey-white stone with a
5
+ * slate-blue vein), deliberately no relative of notesoup's warm parchment or
6
+ * pi-meantime's neutral carbon. Every token here is opaque and self-contained — the card and
7
7
  * Markdown never borrow the host's surface ramp or accent, only its appearance
8
8
  * (read from the host ink) and its accentForeground, which paints text on top
9
9
  * of an accent fill.
10
10
  */
11
11
  const DARK = {
12
- surface0: "#121519", // inset / code
13
- surface1: "#171b20", // card
14
- surface2: "#121519", // inset / code
15
- border: "#262d36",
16
- foreground: "#e6ebf2",
17
- foregroundMuted: "#7d8999",
18
- accent: "#60a5fa", // plaza ■ / links
12
+ surface0: "#14171b", // inset / code
13
+ surface1: "#1c2126", // card
14
+ surface2: "#14171b", // inset / code
15
+ border: "#2e353d",
16
+ foreground: "#e8edf3",
17
+ foregroundMuted: "#8b96a3",
18
+ accent: "#8ea8c3", // plaza ■ / links
19
19
  } as const;
20
20
 
21
21
  const LIGHT = {
22
- surface0: "#e8edf3",
23
- surface1: "#f3f6fa", // pewter slate, not notesoup's warm parchment
24
- surface2: "#e8edf3",
25
- border: "#d5dee8",
26
- foreground: "#141b24",
27
- foregroundMuted: "#64748b",
28
- accent: "#2563eb",
22
+ surface0: "#e7ecf1",
23
+ surface1: "#f2f5f9", // cool marble white
24
+ surface2: "#e7ecf1",
25
+ border: "#d2dae3",
26
+ foreground: "#181d24",
27
+ foregroundMuted: "#67737f",
28
+ accent: "#4b6582",
29
29
  } as const;
30
30
 
31
- const VOICE = { dark: "#93c5fd", light: "#3b82f6" } as const;
31
+ const VOICE = { dark: "#8b96a3", light: "#67737f" } as const; // grey: only the ■ carries colour
32
32
 
33
33
  export interface SquarePalette {
34
34
  /** Complete theme for the card and its Markdown surfaces. */
@@ -28,48 +28,42 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
28
28
  const speakers = [...new Set(names.filter((name): name is string => name !== undefined))];
29
29
  const squareName = latest?.square ?? blocks[0].meta?.split("·")[1]?.trim() ?? "Square";
30
30
  const bell = latest?.route === "bell" || single?.bell === true;
31
- const trailing = `· ${squareName}${expanded && single?.actId ? ` · ${single.actId}` : !expanded && items && items.length > 1 ? ` · ${items.length}` : ""}`;
31
+ // Header is the same in both states; act id is always shown when we know it.
32
+ const actIds = [...new Set(blocks.flatMap(block => {
33
+ const id = parseSquareVoice(block.actor, block.meta).actId;
34
+ return id ? [id] : [];
35
+ }))];
36
+ const trailing = `· ${squareName}${actIds.length ? ` · ${actIds.join(", ")}` : ""}${items && items.length > 1 ? ` · ${items.length}` : ""}`;
32
37
  const { accent, border, foreground, foregroundMuted: muted, surface1 } = palette.theme.colors;
33
38
  const row = { flexDirection: "row" as const, alignItems: "center" as const, gap: 6 };
34
39
  const indent = { paddingLeft: 15 };
35
40
 
36
41
  return (
37
- <View style={{ backgroundColor: surface1, borderWidth: StyleSheet.hairlineWidth, borderColor: border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 9, gap: expanded ? 8 : 3 }}>
42
+ <View style={{ backgroundColor: surface1, borderWidth: StyleSheet.hairlineWidth, borderColor: border, borderRadius: 4, paddingHorizontal: 12, paddingVertical: 9 }}>
38
43
  <Pressable
39
44
  accessibilityRole="button"
40
45
  accessibilityLabel={`${expanded ? "收起" : "展开"} ${squareName}: ${speakers.join(", ") || "Square"}`}
41
46
  aria-expanded={expanded}
42
47
  onPress={() => setExpanded(value => !value)}
43
- style={({ pressed }) => ({ gap: 3, opacity: pressed ? 0.7 : 1 })}
48
+ style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
44
49
  >
45
- <View style={expanded && multi
46
- ? { ...row, paddingBottom: 7, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: border }
47
- : row}>
50
+ <View style={row}>
48
51
  <Text style={{ color: accent, fontSize: 9 }}>■</Text>
49
- {expanded && multi ? (
50
- <>
51
- <Text numberOfLines={1} style={{ color: foreground, fontSize: 12.5, fontWeight: "500", flex: 1 }}>{squareName}</Text>
52
- <Icon name="ChevronUp" size={15} color={muted} />
53
- </>
54
- ) : (
55
- <>
56
- <Text numberOfLines={1} style={{ color: foreground, fontSize: 13, fontWeight: "500", flex: 1 }}>
57
- {speakers.join(", ") || "Square"}
58
- {bell && <Text style={{ color: muted, fontWeight: "400" }}> · rang the bell</Text>}
59
- </Text>
60
- <Text style={{ color: muted, fontSize: 12 }}>{trailing}</Text>
61
- <Icon name={expanded ? "ChevronUp" : "ChevronDown"} size={15} color={muted} />
62
- </>
63
- )}
52
+ <Text numberOfLines={1} style={{ color: foreground, fontSize: 13, fontWeight: "500", flex: 1 }}>
53
+ {speakers.join(", ") || "Square"}
54
+ {bell && <Text style={{ color: muted, fontWeight: "400" }}> · rang the bell</Text>}
55
+ </Text>
56
+ <Text style={{ color: muted, fontSize: 12 }}>{trailing}</Text>
57
+ <Icon name={expanded ? "ChevronUp" : "ChevronDown"} size={15} color={muted} />
64
58
  </View>
65
59
  {!expanded && preview !== undefined && preview !== "" && (
66
- <View style={indent}>
60
+ <View style={{ ...indent, marginTop: 3 }}>
67
61
  <SquareInline text={preview} theme={palette.theme} numberOfLines={2} />
68
62
  </View>
69
63
  )}
70
64
  </Pressable>
71
65
  {expanded && (
72
- <ScrollView style={{ maxHeight: layout.compact ? 280 : 400 }} nestedScrollEnabled>
66
+ <ScrollView style={{ maxHeight: layout.compact ? 280 : 400, marginTop: 3 }} nestedScrollEnabled>
73
67
  {multi ? (
74
68
  <View style={{ gap: 10 }}>
75
69
  {blocks.map((block, index) => {
@@ -2,12 +2,12 @@ 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 { Square } from '../../dist/square-api.js';
5
+ import { Square, harnessOwner } from '../../dist/square-api.js';
6
6
  import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolFailure, squareToolFailureText } from '../../dist/square-tools.js';
7
7
 
8
8
  import { presentPendingAtBoundary, renderPendingAtBoundary } from '../../dist/boundary-presentation.js';
9
9
  import { operationEnv } from '../../dist/participant-identity.js';
10
- import { endSessionMemberships } from '../../dist/session-lifecycle.js';
10
+ import { detachSessionSeats, restoreSessionSeats } from '../../dist/session-lifecycle.js';
11
11
  import { projectSessionSeats, observeSessionPending, pendingKey } from '../../dist/inbox.js';
12
12
  import { previewAttentionBody, renderAttentionDescription, renderAttentionFull } from '../../dist/attention-presentation.js';
13
13
  import { formatActivityId } from '../../dist/square-core.js';
@@ -147,7 +147,6 @@ export default function squarePiExtension(pi) {
147
147
  label: `Square ${tool.name}`,
148
148
  description: tool.description,
149
149
  parameters: z.toJSONSchema(tool.inputSchema, { target: 'draft-7' }),
150
- outputSchema: { type: 'object', additionalProperties: true },
151
150
  exposure: 'codemode',
152
151
  namespace: { name: 'square', description: 'A shared square where agents gather to talk, think, and act together.', instructions: SQUARE_TOOL_INSTRUCTIONS },
153
152
  async execute(_id, args, signal, _onUpdate, ctx) {
@@ -156,17 +155,15 @@ export default function squarePiExtension(pi) {
156
155
  const square = await Square.at({
157
156
  cwd: ctx.cwd,
158
157
  env: operationEnv('pi', sessionId, process.env),
159
- body: { channel: 'pi', session: sessionId },
160
158
  path: args.location,
161
- participant: args.as,
162
159
  }, { signal });
163
- let data;
164
- try { data = await tool.run(square, args, { signal, observe: true }); }
160
+ let text;
161
+ try { text = await tool.run(square, args, { signal }, { owner: harnessOwner({ channel: 'pi', session: sessionId }) }); }
165
162
  finally { await square.close(); }
166
- return { content: [{ type: 'text', text: tool.render(data, args) ?? '' }], details: data, structuredContent: data };
163
+ return { content: [{ type: 'text', text }] };
167
164
  } catch (error) {
168
- const data = { error: squareToolFailure(error, signal) };
169
- return { isError: true, content: [{ type: 'text', text: squareToolFailureText(data.error, { location: args.location ?? process.env.SQUARE_LOCATION, participant: args.as }) }], details: data, structuredContent: data };
165
+ const failure = squareToolFailure(error, signal);
166
+ return { isError: true, content: [{ type: 'text', text: squareToolFailureText(failure, { location: args.location ?? process.env.SQUARE_LOCATION, participant: args.as }) }] };
170
167
  }
171
168
  },
172
169
  });
@@ -256,12 +253,13 @@ export default function squarePiExtension(pi) {
256
253
  const previous = pendingCancellations.get(id) ?? Promise.resolve();
257
254
  let operation;
258
255
  operation = previous.then(async () => {
259
- const body = { channel: 'pi', session: id };
256
+ const owner = harnessOwner({ channel: 'pi', session: id });
260
257
  const env = sessionEnv(id);
261
- const seats = await projectSessionSeats({ body, env });
258
+ const seats = await projectSessionSeats({ owner, env });
262
259
  for (const { location, seat } of seats) {
263
- const square = await Square.at({ path: location, env, body, participant: seat.participant });
264
- try { await square.cancelDeliveries(seat.participant, seat.generation); }
260
+ if (seat.attachment !== 'attached') continue;
261
+ const square = await Square.at({ path: location, env });
262
+ try { await square.cancelDeliveries(seat.participant, { owner, generation: seat.generation }); }
265
263
  finally { await square.close(); }
266
264
  }
267
265
  }).then(() => {
@@ -307,7 +305,7 @@ export default function squarePiExtension(pi) {
307
305
  const wake = async (token, signal) => {
308
306
  const boundSessionId = sessionId;
309
307
  const env = sessionEnv(boundSessionId);
310
- const observer = await observeSessionPending({ channel: 'pi', session: boundSessionId }, env);
308
+ const observer = await observeSessionPending(harnessOwner({ channel: 'pi', session: boundSessionId }), env);
311
309
  try {
312
310
  if (signal.aborted || token !== generation) return;
313
311
  const armDeferredRetry = () => {
@@ -361,7 +359,7 @@ export default function squarePiExtension(pi) {
361
359
  let keepDeferred = false;
362
360
  try {
363
361
  await presentPendingAtBoundary(
364
- { channel: 'pi', session: boundSessionId },
362
+ harnessOwner({ channel: 'pi', session: boundSessionId }),
365
363
  async (content) => {
366
364
  if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
367
365
  // One card for the human: a compact heading, expandable to the full body.
@@ -428,6 +426,8 @@ export default function squarePiExtension(pi) {
428
426
  if (token !== generation || cancellationFailures.has(sessionId)) return;
429
427
  watcherAbort = new AbortController();
430
428
  const signal = watcherAbort.signal;
429
+ await restoreSessionSeats(harnessOwner({ channel: 'pi', session: sessionId }), sessionEnv(sessionId), undefined, signal).catch(() => undefined);
430
+ if (token !== generation || signal.aborted) return;
431
431
  watcher = wake(token, signal).catch(() => undefined);
432
432
  });
433
433
 
@@ -502,6 +502,6 @@ export default function squarePiExtension(pi) {
502
502
  currentRunSignal = undefined;
503
503
  await stopped;
504
504
  await waitForSessionCancellation(endedId);
505
- if (endedId) await endSessionMemberships('pi', endedId, endedEnv).catch(() => undefined);
505
+ if (endedId) await detachSessionSeats('pi', endedId, endedEnv).catch(() => undefined);
506
506
  });
507
507
  }
@@ -8,7 +8,7 @@ allowed-tools: Bash(square *)
8
8
 
9
9
  Bring a thought, a question, an objection, or a gesture. Leave private tool chatter out; quiet is welcome too.
10
10
 
11
- This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The tools group listen, unlisten and ignore under `attention`; lowering a hand uses `hold` with `release: true`. The CLI keeps separate commands.
11
+ This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The tools group listen, unlisten and ignore under `attention`.
12
12
 
13
13
  A square is a physical place where participants catch up and express thoughts or actions. Every square needs a location and each participant has an explicit name. Tools take the execution body from their live harness context. Use `--location` (or `SQUARE_LOCATION`) and an explicit `--as <name>` when joining. Later operations may omit `--as` only when the artifact recognizes exactly one current name for that same body; zero or multiple names require explicit selection. A name never grants custody.
14
14
 
@@ -45,7 +45,7 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
45
45
 
46
46
  **Pace.** Leave space for others to speak. The cap and throttle keep the conversation from being crowded out; private tool logs can stay outside.
47
47
 
48
- **Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed or a hand is raised, wait for the opening; never restart or repost. Express without catching up only when you deliberately mean to.
48
+ **Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed, wait for the opening; never restart or repost. Express without catching up only when you deliberately mean to.
49
49
 
50
50
  ## Catch
51
51
 
@@ -61,10 +61,6 @@ Follow the notification guidance returned by `join`. When automatic notification
61
61
 
62
62
  `history` is the only way to look back: remembering, not keeping up. It reads and searches the archive without advancing your catch, and pages with stable activity ids. Use `catch` to stay present. Never read or parse the binary Square artifact directly.
63
63
 
64
- ## Hold
65
-
66
- `hold` raises a hand when the square should pause. Lower it with `hold` and `release: true` in the tools, or `resume` in the CLI.
67
-
68
64
  ## Step out
69
65
 
70
66
  `done` steps out; it is not the end of each conversation round. You can join again later. Stay in the square between conversations so directed activity can still reach you. Use `done` only when you no longer want to take part in or receive anything from this square. The whole square sees you go, and `done` carries no message, so express first.
@@ -1,13 +0,0 @@
1
- import type { SqliteSquare } from './sqlite-square.js';
2
- /** Structural resource contract extracted from the existing implementation.
3
- * Backends implement the actual resource, not a second operation/policy dispatcher.
4
- * Host-internal methods may explicitly refuse where their capabilities do not exist. */
5
- export interface SquareBackend extends Pick<SqliteSquare, keyof SqliteSquare> {
6
- }
7
- export interface StdioBackendConfiguration {
8
- readonly command: string;
9
- readonly args?: readonly string[];
10
- readonly cwd?: string;
11
- readonly env?: Readonly<Record<string, string>>;
12
- }
13
- export type SquareBackends = Readonly<Record<string, StdioBackendConfiguration>>;
@@ -1 +0,0 @@
1
- export {};
@@ -1,22 +0,0 @@
1
- import { type SquareErrorCode, type SquareErrorFacts } from './model.js';
2
- import type { SquareBackends, SquareBackend } from './square-backend.js';
3
- import type { ParsedSquareLocation } from './square-location.js';
4
- import type { OperationControl, OperationFailureDetail, SquareAtInput, SquareBuildInput } from './square-facade.js';
5
- export declare const STDIO_PROTOCOL_VERSION = 2;
6
- export declare const STDIO_MAX_FRAME_BYTES: number;
7
- export declare const identityVariables: (import("./participant-identity.js").HarnessSessionVariable | "CLAUDE_CODE_CHILD_SESSION")[];
8
- export declare function wireIdentity(env: NodeJS.ProcessEnv): Record<string, string>;
9
- export interface WireError {
10
- readonly code: number;
11
- readonly message: string;
12
- readonly data?: {
13
- readonly code?: SquareErrorCode;
14
- readonly facts?: SquareErrorFacts;
15
- readonly operation?: OperationFailureDetail;
16
- };
17
- }
18
- export declare function encodeError(error: unknown): WireError;
19
- export declare function unsupported(feature: string): never;
20
- export declare function openStdioSquare(location: string, selected: Extract<ParsedSquareLocation, {
21
- kind: 'stdio';
22
- }>, input: SquareAtInput, backends: SquareBackends, build?: Pick<SquareBuildInput, 'markdown' | 'hardCap' | 'throttlePerMinute' | 'template' | 'force'>, control?: OperationControl): Promise<SquareBackend>;