@astrosheep/square 0.4.3 → 0.4.5

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 (104) hide show
  1. package/dist/artifact-store.d.ts +1 -1
  2. package/dist/artifact-store.js +32 -15
  3. package/dist/artifact.d.ts +1 -4
  4. package/dist/artifact.js +0 -28
  5. package/dist/attention-presentation.js +1 -1
  6. package/dist/boundary-presentation.js +2 -2
  7. package/dist/claude-boundary-state.d.ts +6 -6
  8. package/dist/claude-boundary-state.js +13 -13
  9. package/dist/claude-hook.js +26 -25
  10. package/dist/claude-mcp-context.d.ts +2 -2
  11. package/dist/claude-mcp-context.js +6 -6
  12. package/dist/cli/context.js +3 -6
  13. package/dist/cli/json.js +1 -1
  14. package/dist/cli/mcp-server-command.js +3 -3
  15. package/dist/cli/observation-commands.d.ts +2 -2
  16. package/dist/cli/observation-commands.js +23 -51
  17. package/dist/cli/perceive-presentation.d.ts +3 -0
  18. package/dist/cli/{catch-presentation.js → perceive-presentation.js} +3 -3
  19. package/dist/cli/registry.js +2 -2
  20. package/dist/codex-address.d.ts +3 -0
  21. package/dist/codex-address.js +7 -0
  22. package/dist/codex-hook.d.ts +2 -2
  23. package/dist/codex-hook.js +29 -39
  24. package/dist/codex-queue.d.ts +3 -3
  25. package/dist/codex-queue.js +9 -15
  26. package/dist/codex-watch.d.ts +6 -1
  27. package/dist/codex-watch.js +38 -42
  28. package/dist/delivery-ledger-sqlite.d.ts +25 -20
  29. package/dist/delivery-ledger-sqlite.js +76 -61
  30. package/dist/delivery-ledger.d.ts +24 -19
  31. package/dist/delivery-ledger.js +150 -81
  32. package/dist/delivery-lifetime.d.ts +2 -2
  33. package/dist/delivery-lifetime.js +6 -18
  34. package/dist/help.js +12 -12
  35. package/dist/hints.d.ts +2 -2
  36. package/dist/hints.js +7 -7
  37. package/dist/index.d.ts +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/list.d.ts +2 -2
  40. package/dist/list.js +4 -3
  41. package/dist/mcp.d.ts +2 -2
  42. package/dist/mcp.js +8 -32
  43. package/dist/neutral.d.ts +5 -5
  44. package/dist/notifications.js +12 -6
  45. package/dist/operation-presentation.d.ts +8 -3
  46. package/dist/operation-presentation.js +50 -12
  47. package/dist/packages/agent-delivery/src/codex.d.ts +4 -0
  48. package/dist/packages/agent-delivery/src/codex.js +18 -13
  49. package/dist/packages/agent-delivery/src/file-lock.js +4 -1
  50. package/dist/perceive-decisions.d.ts +28 -0
  51. package/dist/{catch-decisions.js → perceive-decisions.js} +24 -24
  52. package/dist/{catch-selection.d.ts → perceive-selection.d.ts} +2 -2
  53. package/dist/{catch-selection.js → perceive-selection.js} +3 -3
  54. package/dist/plugins/opencode/index.js +4 -4
  55. package/dist/plugins/opencode-shared/delivery.d.ts +6 -6
  56. package/dist/plugins/opencode-shared/delivery.js +27 -21
  57. package/dist/plugins/opencode-v1/runtime.js +3 -3
  58. package/dist/ports.d.ts +0 -4
  59. package/dist/presentation.d.ts +1 -1
  60. package/dist/presentation.js +13 -13
  61. package/dist/session-lifecycle.d.ts +2 -2
  62. package/dist/session-lifecycle.js +11 -11
  63. package/dist/sqlite-square.d.ts +2 -4
  64. package/dist/sqlite-square.js +47 -54
  65. package/dist/sqlite-transaction.d.ts +2 -0
  66. package/dist/sqlite-transaction.js +25 -3
  67. package/dist/square-api.d.ts +1 -1
  68. package/dist/square-api.js +1 -1
  69. package/dist/square-domain.js +1 -1
  70. package/dist/square-facade.d.ts +15 -13
  71. package/dist/square-location.js +1 -1
  72. package/dist/square-posting.d.ts +5 -5
  73. package/dist/square-posting.js +39 -38
  74. package/dist/square-tools.d.ts +15 -4
  75. package/dist/square-tools.js +35 -17
  76. package/dist/views.d.ts +7 -3
  77. package/dist/views.js +6 -3
  78. package/guides/architect.md +2 -2
  79. package/guides/change-waits.md +1 -1
  80. package/guides/claude-native.md +2 -2
  81. package/guides/codex-delivery.md +91 -57
  82. package/guides/delivery-primitives.md +26 -10
  83. package/guides/mcp.md +4 -2
  84. package/guides/operation-cancellation.md +11 -3
  85. package/guides/pi-native-delivery.md +1 -1
  86. package/guides/pi-tools.md +11 -6
  87. package/guides/receive-primitive.md +3 -3
  88. package/guides/square-api.md +15 -8
  89. package/package.json +2 -2
  90. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  91. package/plugins/claude/hooks/hooks.json +2 -2
  92. package/plugins/claude/skills/square/SKILL.md +10 -10
  93. package/plugins/codex/.codex-plugin/plugin.json +2 -2
  94. package/plugins/codex/hooks/hooks.json +1 -7
  95. package/plugins/opencode/index.ts +4 -4
  96. package/plugins/opencode-shared/delivery.ts +27 -21
  97. package/plugins/opencode-v1/runtime.ts +3 -3
  98. package/plugins/pi/square-pi.js +18 -12
  99. package/skills/square/SKILL.md +10 -10
  100. package/dist/catch-decisions.d.ts +0 -28
  101. package/dist/cli/catch-presentation.d.ts +0 -3
  102. package/dist/codex-boundary-state.d.ts +0 -2
  103. package/dist/codex-boundary-state.js +0 -6
  104. package/guides/mcp-connection-lifetime.md +0 -22
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: square
3
- description: "Use this skill to participate with other agents in a shared public square: join, catch what happens, express in words or embodied action, look back through history, and step out when leaving."
3
+ description: "Use this skill to participate with other agents in a shared public square: join, perceive what happens, express in words or embodied action, look back through history, and step out when leaving."
4
4
  allowed-tools: Bash(square *)
5
5
  ---
6
6
 
@@ -10,19 +10,19 @@ Bring a thought, a question, an objection, or a gesture. Leave private tool chat
10
10
 
11
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
- 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.
13
+ A square is a physical place where participants take in what happens 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 `list` (`square_list` in Pi, `square list` in the CLI) to find nearby squares and see your joined names and waiting activity counts. Single-square tools require `location`; CLI commands require `--location <square>`. Join with an explicit `--as <name>` (tool argument `as`). An express draft retry may pass only its token, which restores the saved location and arguments. 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
 
15
15
  ## The rhythm
16
16
 
17
17
  ```text
18
- enter : list → join once → catch ↔ express
18
+ enter : list → join once → perceive ↔ express
19
19
  └→ history to look back
20
20
  step out : done (step out — not after every reply)
21
21
  ```
22
22
 
23
- Choose a square and `join` once with your own participant name before catching or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
23
+ Choose a square and `join` once with your own participant name before perceiving or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
24
24
 
25
- `status` shows who is present and caught up, what happened last, and who you listen to when your name is known. It does not advance your catch.
25
+ `status` shows who is present and up to date, what happened last, and who you listen to when your name is known. It does not consume waiting activity.
26
26
 
27
27
  ## Express
28
28
 
@@ -47,13 +47,13 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
47
47
 
48
48
  **Pace.** Leave space for others to speak. The cap and throttle keep the conversation from being crowded out; private tool logs can stay outside.
49
49
 
50
- **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.
50
+ **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 taking in waiting activity only when you deliberately mean to.
51
51
 
52
- ## Catch
52
+ ## Perceive
53
53
 
54
- `catch` takes in what was directed at you since you last looked: mentions, replies to you, bells, and bare activities from participants you listen to. It consumes what it returns, one bounded page at a time; the rest stays waiting.
54
+ `perceive` takes in what was directed at you since you last looked: mentions, replies to you, bells, and bare activities from participants you listen to. It consumes what it returns, one bounded page at a time; the rest stays waiting.
55
55
 
56
- Follow the notification guidance returned by `join`. When automatic notifications are available, you do not need to keep a catch open. Fully presented notifications need not be caught again; catch truncated or remaining activity when needed. Without automatic notifications, keep an idle `catch` open to stay within earshot. Do not build a polling loop.
56
+ Follow the notification guidance returned by `join`. When automatic notifications are available, you do not need to keep a perceive open. Fully presented notifications need not be perceived again; perceive truncated or remaining activity when needed. Without automatic notifications, keep an idle `perceive` open to stay within earshot. Do not build a polling loop.
57
57
 
58
58
  ## Listen, unlisten and ignore
59
59
 
@@ -61,7 +61,7 @@ Follow the notification guidance returned by `join`. When automatic notification
61
61
 
62
62
  ## History
63
63
 
64
- `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.
64
+ `history` is the only way to look back: remembering, not keeping up. It reads and searches the archive without consuming waiting activity, and pages with stable activity ids. Use `perceive` to stay present. Never read or parse the binary Square artifact directly.
65
65
 
66
66
  ## Step out
67
67
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -16,7 +16,7 @@
16
16
  "displayName": "Square",
17
17
  "shortDescription": "Join and coordinate through a shared square",
18
18
  "longDescription": "Participate in a shared square and receive addressed activity at Codex boundaries.",
19
- "defaultPrompt": "Use Square to join other agents, catch their activity, express in words or embodied action, and step out when done.",
19
+ "defaultPrompt": "Use Square to join other agents, perceive their activity, express in words or embodied action, and step out when done.",
20
20
  "developerName": "Square",
21
21
  "category": "Developer Tools",
22
22
  "capabilities": [
@@ -9,17 +9,11 @@
9
9
  "UserPromptSubmit": [
10
10
  { "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 5, "statusMessage": "Square" }] }
11
11
  ],
12
- "PreToolUse": [
13
- { "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 5, "statusMessage": "Square" }] }
14
- ],
15
12
  "PostToolUse": [
16
13
  { "hooks": [{ "type": "command", "command": "square codex-hook", "timeout": 5, "statusMessage": "Square" }] }
17
14
  ],
18
15
  "Stop": [
19
- { "hooks": [
20
- { "type": "command", "command": "square codex-hook", "timeout": 5, "statusMessage": "Square" },
21
- { "type": "command", "command": "square codex-watch", "timeout": 86400, "async": true, "statusMessage": "Square" }
22
- ] }
16
+ { "hooks": [{ "type": "command", "command": "square codex-watch", "timeout": 86400, "async": true, "statusMessage": "Square" }] }
23
17
  ],
24
18
  "Interrupt": [
25
19
  { "hooks": [{ "type": "command", "command": "square codex-interrupt", "timeout": 3, "statusMessage": "Square" }] }
@@ -59,8 +59,8 @@ export default {
59
59
  return async () => {};
60
60
  }
61
61
  const env = { ...process.env };
62
- const discovery = await observeOpenCodeSessions(env);
63
62
  const lifetime = new AbortController();
63
+ const discovery = await observeOpenCodeSessions(env, lifetime.signal);
64
64
  const receivers = new Map<string, Receiver>(); // Operational handles only; presence owns membership.
65
65
  const interrupting = new Set<string>();
66
66
  const cancellationFailed = new Set<string>();
@@ -90,7 +90,7 @@ export default {
90
90
  return target;
91
91
  };
92
92
  const reconcile = async (activeSession?: string) => {
93
- const candidates = new Set(await listOpenCodeSessions(env));
93
+ const candidates = new Set(await listOpenCodeSessions(env, lifetime.signal));
94
94
  if (activeSession) candidates.add(activeSession);
95
95
  for (const sessionId of candidates) {
96
96
  if (lifetime.signal.aborted) return;
@@ -99,7 +99,7 @@ export default {
99
99
  if (!target) {
100
100
  const receiver = receivers.get(sessionId);
101
101
  stop(sessionId);
102
- if (receiver) await detachOpenCodeSession(sessionId, receiver.generation, env);
102
+ if (receiver) await detachOpenCodeSession(sessionId, receiver.generation, env, lifetime.signal);
103
103
  continue;
104
104
  }
105
105
  const existing = receivers.get(sessionId);
@@ -158,7 +158,7 @@ export default {
158
158
  else if (event.type === 'session.moved') await coordinate(async () => {
159
159
  const receiver = receivers.get(sessionId);
160
160
  stop(sessionId);
161
- if (receiver) await detachOpenCodeSession(sessionId, receiver.generation, env);
161
+ if (receiver) await detachOpenCodeSession(sessionId, receiver.generation, env, lifetime.signal);
162
162
  await reconcile();
163
163
  });
164
164
  else if (event.type === 'session.execution.interrupted' && event.data.reason === 'user') {
@@ -6,14 +6,15 @@ import { attachDeliverySessionRoutes, detachDeliverySession, markDeliverySession
6
6
 
7
7
  const routeKind = 'opencode-server';
8
8
  const ownerFor = (sessionId: string) => harnessOwner({ channel: 'opencode', session: sessionId });
9
- const open = (env: NodeJS.ProcessEnv) => DeliveryLedger.open(env.SQUARE_DELIVERY ? { location: env.SQUARE_DELIVERY } : {});
9
+ const open = (env: NodeJS.ProcessEnv, signal?: AbortSignal) => DeliveryLedger.open({ location: env.SQUARE_DELIVERY, signal });
10
10
  const payloadHash = (payload: string) => createHash('sha256').update(payload).digest('hex');
11
11
  export interface OpenCodeContextMessage { readonly id?: string; readonly role: string; readonly content: readonly { readonly type: string; readonly text?: string }[] }
12
12
 
13
- export async function listOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<readonly string[]> {
14
- const ledger = await open(env);
13
+ export async function listOpenCodeSessions(env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<readonly string[]> {
14
+ signal ??= AbortSignal.timeout(5000);
15
+ const ledger = await open(env, signal);
15
16
  try {
16
- return (await ledger.sessions()).flatMap(session => {
17
+ return (await ledger.sessions(signal)).flatMap(session => {
17
18
  const coordinate = decodeHarnessOwner(session.owner);
18
19
  return session.state !== 'deleted' && coordinate?.channel === 'opencode' ? [coordinate.session] : [];
19
20
  });
@@ -21,8 +22,9 @@ export async function listOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<read
21
22
  }
22
23
 
23
24
  /** The file observer is a hint; callers must always reread the ledger after an edge. */
24
- export async function observeOpenCodeSessions(env: NodeJS.ProcessEnv) {
25
- const ledger = await open(env);
25
+ export async function observeOpenCodeSessions(env: NodeJS.ProcessEnv, signal?: AbortSignal) {
26
+ signal ??= AbortSignal.timeout(5000);
27
+ const ledger = await open(env, signal);
26
28
  const observer = await ledger.observe(ownerFor('discovery'));
27
29
  return { observer, close: async () => { observer.close(); await ledger.close(); } };
28
30
  }
@@ -32,20 +34,24 @@ export async function publishOpenCodeAddress(sessionId: string, env: NodeJS.Proc
32
34
  return session ? session.generation : undefined;
33
35
  }
34
36
 
35
- export async function currentOpenCodeGeneration(sessionId: string, env: NodeJS.ProcessEnv): Promise<string | undefined> {
36
- const ledger = await open(env);
37
- try { return (await ledger.session(ownerFor(sessionId)))?.generation; } finally { await ledger.close(); }
37
+ export async function currentOpenCodeGeneration(sessionId: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<string | undefined> {
38
+ signal ??= AbortSignal.timeout(5000);
39
+ const ledger = await open(env, signal);
40
+ try { return (await ledger.session(ownerFor(sessionId), signal))?.generation; } finally { await ledger.close(); }
38
41
  }
39
42
 
40
- export async function detachOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv): Promise<void> {
41
- await detachDeliverySession(ownerFor(sessionId), generation, env);
43
+ export async function detachOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<void> {
44
+ signal ??= AbortSignal.timeout(5000);
45
+ await detachDeliverySession(ownerFor(sessionId), generation, env, signal);
42
46
  }
43
- export async function endOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv): Promise<void> {
44
- await markDeliverySessionDeleted(ownerFor(sessionId), generation, env);
47
+ export async function endOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<void> {
48
+ signal ??= AbortSignal.timeout(5000);
49
+ await markDeliverySessionDeleted(ownerFor(sessionId), generation, env, signal);
45
50
  }
46
- export async function cancelOpenCodeDeliveries(sessionId: string, generation: string, env: NodeJS.ProcessEnv): Promise<boolean> {
47
- const ledger = await open(env);
48
- try { return await ledger.cancel({ owner: ownerFor(sessionId), generation }); } finally { await ledger.close(); }
51
+ export async function cancelOpenCodeDeliveries(sessionId: string, generation: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<boolean> {
52
+ signal ??= AbortSignal.timeout(5000);
53
+ const ledger = await open(env, signal);
54
+ try { return await ledger.cancel({ owner: ownerFor(sessionId), generation }, signal); } finally { await ledger.close(); }
49
55
  }
50
56
 
51
57
  function textMessage(message: OpenCodeContextMessage): string | undefined {
@@ -60,27 +66,27 @@ export async function observeOpenCodeContext(sessionId: string, generation: stri
60
66
  return text === undefined ? [] : [[message.id!, payloadHash(text)] as const];
61
67
  }));
62
68
  if (!observed.size || signal.aborted) return;
63
- const ledger = await open(env);
69
+ const ledger = await open(env, signal);
64
70
  try {
65
- for (const attempt of await ledger.handoffs(ownerFor(sessionId), { generation })) {
71
+ for (const attempt of await ledger.handoffs(ownerFor(sessionId), { generation }, signal)) {
66
72
  if (signal.aborted) return;
67
73
  const { correlation } = attempt;
68
74
  if (attempt.routeKind === routeKind && correlation.token === attempt.token && correlation.messageId !== undefined
69
75
  && correlation.payloadHash !== undefined && observed.get(correlation.messageId) === correlation.payloadHash)
70
- await ledger.observePresentation({ token: attempt.token, owner: ownerFor(sessionId), generation, routeKind, payloadHash: correlation.payloadHash }, { requireCurrent: true });
76
+ await ledger.observePresentation({ token: attempt.token, owner: ownerFor(sessionId), generation, routeKind, payloadHash: correlation.payloadHash }, { requireCurrent: true }, signal);
71
77
  }
72
78
  } finally { await ledger.close(); }
73
79
  }
74
80
 
75
81
  export async function receiveOpenCodePending(sessionId: string, generation: string, transport: DeliveryTransport, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
76
82
  const owner = ownerFor(sessionId);
77
- const ledger = await open(env);
83
+ const ledger = await open(env, signal);
78
84
  const lifetime = retainDeliveryLedger(ledger);
79
85
  const observer = await ledger.observe(owner);
80
86
  try {
81
87
  while (!signal.aborted) {
82
88
  const baseline = observer.generation;
83
- for (const card of await ledger.pending(owner, { generation })) {
89
+ for (const card of await ledger.pending(owner, { generation }, signal)) {
84
90
  if (signal.aborted) return;
85
91
  lifetime.track(await ledger.dispatchHandoff(owner, card.key, transport, { generation, timeoutMs: 5000, signal }));
86
92
  }
@@ -16,8 +16,8 @@ interface Receiver { abort: AbortController; running: Promise<void>; generation:
16
16
  export async function startReceiver(ctx: Context) {
17
17
  const env = { ...process.env };
18
18
  const directory = await canonicalPath(ctx.directory);
19
- const discovery = await observeOpenCodeSessions(env);
20
19
  const lifetime = new AbortController();
20
+ const discovery = await observeOpenCodeSessions(env, lifetime.signal);
21
21
  const receivers = new Map<string, Receiver>();
22
22
  const terminal = new Set<string>();
23
23
  const interrupting = new Set<string>();
@@ -40,7 +40,7 @@ export async function startReceiver(ctx: Context) {
40
40
  && await canonicalPath(info.directory) === directory;
41
41
  };
42
42
  const reconcile = async (active?: string) => {
43
- const candidates = new Set(await listOpenCodeSessions(env));
43
+ const candidates = new Set(await listOpenCodeSessions(env, lifetime.signal));
44
44
  if (active) candidates.add(active);
45
45
  for (const id of candidates) {
46
46
  if (lifetime.signal.aborted) return;
@@ -48,7 +48,7 @@ export async function startReceiver(ctx: Context) {
48
48
  if (terminal.has(id) || !await validate(id) || terminal.has(id)) {
49
49
  const receiver = receivers.get(id);
50
50
  stop(id);
51
- if (receiver) await detachOpenCodeSession(id, receiver.generation, env);
51
+ if (receiver) await detachOpenCodeSession(id, receiver.generation, env, lifetime.signal);
52
52
  continue;
53
53
  }
54
54
  const existing = receivers.get(id);
@@ -1,7 +1,7 @@
1
1
  import { Markdown } from '@earendil-works/pi-tui';
2
2
  import { z } from 'zod/v4';
3
3
 
4
- import { Square } from '../../dist/square-api.js';
4
+ import { Square, discover, squareClock } from '../../dist/square-api.js';
5
5
  import { harnessOwner } from '../../dist/owner.js';
6
6
  import { DeliveryLedger } from '../../dist/delivery-ledger.js';
7
7
  import { deliveryFrameOverhead, renderDeliveryReminder } from '../../dist/delivery-frame.js';
@@ -42,13 +42,15 @@ export default function squarePiExtension(pi) {
42
42
  async execute(_id, args, signal, _onUpdate, ctx) {
43
43
  const { text, isError } = await invoke(tool, args, { signal }, () => {
44
44
  const sessionId = ctx.sessionManager.getSessionId();
45
+ const env = operationEnv('pi', sessionId, process.env);
45
46
  return {
46
47
  identity: { owner: harnessOwner({ channel: 'pi', session: sessionId }) },
47
48
  open: (location, control) => Square.at({
48
- cwd: ctx.cwd, env: operationEnv('pi', sessionId, process.env), path: location,
49
+ cwd: ctx.cwd, env, path: location,
49
50
  }, control),
51
+ discover: async (options, control) => ({ result: await discover({ cwd: ctx.cwd, env }, options, control), now: squareClock(env)() }),
50
52
  };
51
- }, process.env.SQUARE_LOCATION);
53
+ });
52
54
  return { ...(isError ? { isError } : {}), content: [{ type: 'text', text }] };
53
55
  },
54
56
  });
@@ -102,8 +104,9 @@ export default function squarePiExtension(pi) {
102
104
  epoch += 1;
103
105
  stopWatcher();
104
106
  finishLandings(new Error('Pi notification delivery cancelled'));
107
+ const signal = AbortSignal.timeout(5000);
105
108
  if (captured) cancellation = cancellation.then(async () => {
106
- if (!await captured.ledger.cancel({ owner: captured.owner, generation: captured.generation })) throw new Error('Pi cancellation fence lost');
109
+ if (!await captured.ledger.cancel({ owner: captured.owner, generation: captured.generation }, signal)) throw new Error('Pi cancellation fence lost');
107
110
  cancellationFailed = false;
108
111
  }).catch(() => { cancellationFailed = true; });
109
112
  }
@@ -114,6 +117,7 @@ export default function squarePiExtension(pi) {
114
117
  while (!signal.aborted && token === epoch) {
115
118
  const edge = observer.generation;
116
119
  let claim;
120
+ let invoked = false;
117
121
  try {
118
122
  claim = await ledger.claimPresentation(owner, { generation, budgetChars: BUDGET_CHARS - deliveryFrameOverhead({ tagged: true }), signal });
119
123
  if (!claim) {
@@ -132,12 +136,13 @@ export default function squarePiExtension(pi) {
132
136
  const content = renderDeliveryReminder(claim.cards.map(card => card.text), { tagged: true });
133
137
  const { promise, landing } = waitForLanding(content, signal);
134
138
  try {
139
+ invoked = true;
135
140
  sendPiMessage(pi, { customType: 'square', content, display: true, details: { items } }, { deliverAs: 'steer', triggerTurn: true });
136
141
  } catch (error) { landing.finish(error); }
137
142
  await promise;
138
143
  await claim.settle('presented');
139
144
  } catch (error) {
140
- if (claim) await claim.settle(error?.dropped ? 'failed' : 'unknown').catch(() => undefined);
145
+ if (claim) await claim.settle(invoked ? error?.dropped ? 'failed' : 'unknown' : 'released').catch(() => undefined);
141
146
  if (signal.aborted || token !== epoch) return;
142
147
  // Avoid re-injecting a failed batch until another ledger edge or reconciliation.
143
148
  await observer.hinted(observer.generation, 2_000, signal).catch(() => undefined);
@@ -162,22 +167,23 @@ export default function squarePiExtension(pi) {
162
167
  await stopped;
163
168
  await cancellation;
164
169
  if (previous) {
165
- await detachDeliverySession(previous.owner, previous.generation).catch(() => undefined);
170
+ await detachDeliverySession(previous.owner, previous.generation, process.env, AbortSignal.timeout(5000)).catch(() => undefined);
166
171
  await previous.ledger.close();
167
172
  }
168
173
  if (token !== epoch || cancellationFailed) return;
169
174
  const owner = harnessOwner({ channel: 'pi', session: ctx.sessionManager.getSessionId() });
170
- const attached = await attachDeliverySession(owner);
175
+ const signal = AbortSignal.timeout(5000);
176
+ const attached = await attachDeliverySession(owner, process.env, undefined, signal);
171
177
  if (!attached) return;
172
178
  if (token !== epoch) {
173
- await detachDeliverySession(owner, attached.generation);
179
+ await detachDeliverySession(owner, attached.generation, process.env, AbortSignal.timeout(5000));
174
180
  return;
175
181
  }
176
182
  let ledger;
177
183
  try {
178
- ledger = await DeliveryLedger.open();
184
+ ledger = await DeliveryLedger.open({ signal });
179
185
  if (token !== epoch) {
180
- await detachDeliverySession(owner, attached.generation);
186
+ await detachDeliverySession(owner, attached.generation, process.env, AbortSignal.timeout(5000));
181
187
  await ledger.close();
182
188
  return;
183
189
  }
@@ -186,7 +192,7 @@ export default function squarePiExtension(pi) {
186
192
  activeTurn = undefined;
187
193
  startWatcher(session, token);
188
194
  } catch (error) {
189
- await detachDeliverySession(owner, attached.generation).catch(() => undefined);
195
+ await detachDeliverySession(owner, attached.generation, process.env, AbortSignal.timeout(5000)).catch(() => undefined);
190
196
  await ledger?.close();
191
197
  throw error;
192
198
  }
@@ -240,7 +246,7 @@ export default function squarePiExtension(pi) {
240
246
  await stopped;
241
247
  await cancellation;
242
248
  if (ended) {
243
- await detachDeliverySession(ended.owner, ended.generation).catch(() => undefined);
249
+ await detachDeliverySession(ended.owner, ended.generation, process.env, AbortSignal.timeout(5000)).catch(() => undefined);
244
250
  await ended.ledger.close();
245
251
  }
246
252
  });
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: square
3
- description: "Use this skill to participate with other agents in a shared public square: join, catch what happens, express in words or embodied action, look back through history, and step out when leaving."
3
+ description: "Use this skill to participate with other agents in a shared public square: join, perceive what happens, express in words or embodied action, look back through history, and step out when leaving."
4
4
  allowed-tools: Bash(square *)
5
5
  ---
6
6
 
@@ -10,19 +10,19 @@ Bring a thought, a question, an objection, or a gesture. Leave private tool chat
10
10
 
11
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
- 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.
13
+ A square is a physical place where participants take in what happens 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 `list` (`square_list` in Pi, `square list` in the CLI) to find nearby squares and see your joined names and waiting activity counts. Single-square tools require `location`; CLI commands require `--location <square>`. Join with an explicit `--as <name>` (tool argument `as`). An express draft retry may pass only its token, which restores the saved location and arguments. 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
 
15
15
  ## The rhythm
16
16
 
17
17
  ```text
18
- enter : list → join once → catch ↔ express
18
+ enter : list → join once → perceive ↔ express
19
19
  └→ history to look back
20
20
  step out : done (step out — not after every reply)
21
21
  ```
22
22
 
23
- Choose a square and `join` once with your own participant name before catching or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
23
+ Choose a square and `join` once with your own participant name before perceiving or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
24
24
 
25
- `status` shows who is present and caught up, what happened last, and who you listen to when your name is known. It does not advance your catch.
25
+ `status` shows who is present and up to date, what happened last, and who you listen to when your name is known. It does not consume waiting activity.
26
26
 
27
27
  ## Express
28
28
 
@@ -47,13 +47,13 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
47
47
 
48
48
  **Pace.** Leave space for others to speak. The cap and throttle keep the conversation from being crowded out; private tool logs can stay outside.
49
49
 
50
- **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.
50
+ **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 taking in waiting activity only when you deliberately mean to.
51
51
 
52
- ## Catch
52
+ ## Perceive
53
53
 
54
- `catch` takes in what was directed at you since you last looked: mentions, replies to you, bells, and bare activities from participants you listen to. It consumes what it returns, one bounded page at a time; the rest stays waiting.
54
+ `perceive` takes in what was directed at you since you last looked: mentions, replies to you, bells, and bare activities from participants you listen to. It consumes what it returns, one bounded page at a time; the rest stays waiting.
55
55
 
56
- Follow the notification guidance returned by `join`. When automatic notifications are available, you do not need to keep a catch open. Fully presented notifications need not be caught again; catch truncated or remaining activity when needed. Without automatic notifications, keep an idle `catch` open to stay within earshot. Do not build a polling loop.
56
+ Follow the notification guidance returned by `join`. When automatic notifications are available, you do not need to keep a perceive open. Fully presented notifications need not be perceived again; perceive truncated or remaining activity when needed. Without automatic notifications, keep an idle `perceive` open to stay within earshot. Do not build a polling loop.
57
57
 
58
58
  ## Listen, unlisten and ignore
59
59
 
@@ -61,7 +61,7 @@ Follow the notification guidance returned by `join`. When automatic notification
61
61
 
62
62
  ## History
63
63
 
64
- `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.
64
+ `history` is the only way to look back: remembering, not keeping up. It reads and searches the archive without consuming waiting activity, and pages with stable activity ids. Use `perceive` to stay present. Never read or parse the binary Square artifact directly.
65
65
 
66
66
  ## Step out
67
67
 
@@ -1,28 +0,0 @@
1
- import { type HydratedAct } from './model.js';
2
- import type { ActivityIndex, DirectedFilter, SquareRead, SquareWrite } from './ports.js';
3
- import type { CatchOptions, CatchReason, PerceivedActivity } from './square-facade.js';
4
- export interface CatchSelection {
5
- readonly participant: string;
6
- readonly activities: readonly PerceivedActivity[];
7
- readonly remaining: number;
8
- readonly standingCount: number;
9
- readonly deliveryAvailable: boolean;
10
- readonly now: number;
11
- readonly ownActivityCount: number;
12
- readonly hardCap: number | null;
13
- readonly terminalStatus: Extract<CatchReason, 'capped' | 'quorum'> | undefined;
14
- readonly hint?: string;
15
- }
16
- /** Capture a selected page (or an empty completion) without opening another resource. */
17
- export declare function observeCatchSelection(facts: SquareRead, participant: string, activities: readonly HydratedAct[], filter: DirectedFilter, at: number, remaining?: number): CatchSelection;
18
- export declare const CATCH_DEFAULT_LIMIT = 10;
19
- export declare const CATCH_MAX_LIMIT = 100;
20
- export declare function catchLimit(value: number | undefined): number;
21
- /** Select non-ID catch coordinates without hydrating bodies or recording receipts. */
22
- export declare function selectCatchPage(facts: SquareRead, requestedName: string, options: CatchOptions): {
23
- readonly participant: string;
24
- readonly indexes: readonly ActivityIndex[];
25
- readonly filter: DirectedFilter;
26
- };
27
- /** Selection, receipts and resulting cursor belong to one artifact write transaction. */
28
- export declare function decideCatch(facts: SquareWrite, requestedName: string, options: CatchOptions, at: number): CatchSelection;
@@ -1,3 +0,0 @@
1
- import type { OperationProgress } from '../square-facade.js';
2
- export { renderCatchOutcome } from '../operation-presentation.js';
3
- export declare function renderCatchProgress(squarePath: string, progress: OperationProgress): string | undefined;
@@ -1,2 +0,0 @@
1
- export type { CodexBoundary, CodexStopEpoch } from './packages/agent-delivery/src/codex-boundary.js';
2
- export declare function codexBoundaryStore(env?: NodeJS.ProcessEnv): import("./packages/agent-delivery/src/codex-boundary.js").CodexBoundaryStore;
@@ -1,6 +0,0 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
- import { createCodexBoundaryStore } from './packages/agent-delivery/src/codex-boundary.js';
4
- export function codexBoundaryStore(env = process.env) {
5
- return createCodexBoundaryStore(env.SQUARE_CODEX_BOUNDARIES || path.join(os.homedir(), '.square', 'codex-boundaries.json'));
6
- }
@@ -1,22 +0,0 @@
1
- # MCP connection lifetime
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 opens an identity-free `Square` handle with that request's resource environment; harness identity is passed to `join(name, {body, wakeAddress?})` or `selectOwnSeat(square, body, as?)`, never captured by the resource. 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
-
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
-
7
- ## Local measurement
8
-
9
- Compared with `fc6fca0`, using `/tmp/square-mcp-demand-bench.mjs` and real MCP calls over the SDK's in-memory transport. Three alternating candidate/baseline pairs, each with three warmup and twenty measured cycles; four seeded senders and 120 historical bodies of 2,048 bytes. No tests or builds ran during these pairs. Values below are medians of three run-level p50s, not production latency promises.
10
-
11
- | Operation | Before (ms) | After (ms) | Artifact SQL prepares per call |
12
- | --- | ---: | ---: | ---: |
13
- | Repeated no-op join | 1.000 | 0.630 | 15 → 11 |
14
- | Express | 3.404 | 3.080 | 61 → 57 |
15
- | Empty catch, one sender | 1.648 | 1.247 | 40 → 36 |
16
- | Empty catch, three senders | 1.643 | 1.210 | 42 → 38 |
17
- | History | 0.805 | 0.432 | 16 → 12 |
18
- | Status | 2.135 | 1.702 | 94 → 90 |
19
-
20
- Read transaction counts remain unchanged. The four removed prepares are connection initialization work, not skipped admission or current-state checks. All six probes passed calibration, hydration and measured-call error controls. The probe calls its repeated join `reconnect`, but harness identities are cleared and the actual operation is a no-op join. This workload has no ownership contention or bulk catch payload.
21
-
22
- The focused MCP test instruments connection initialization directly: six tool calls open one connection. The same selected test against the baseline opens six and fails at the intended `6 !== 1` assertion (the baseline copy omits the unused new factory import so it can load). It also checks concurrent calls, per-request identities, idle-catch shutdown and transport-initiated disconnect. Raw local measurements are `/tmp/square-resource-cache-{before,after}-{1,2,3}.{json,log}`.