@astrosheep/square 0.4.4 → 0.4.6

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 (100) 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 +25 -24
  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 +1 -1
  23. package/dist/codex-hook.js +11 -8
  24. package/dist/codex-queue.d.ts +3 -2
  25. package/dist/codex-queue.js +8 -11
  26. package/dist/codex-watch.d.ts +6 -1
  27. package/dist/codex-watch.js +27 -29
  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/file-lock.js +4 -1
  48. package/dist/perceive-decisions.d.ts +28 -0
  49. package/dist/{catch-decisions.js → perceive-decisions.js} +24 -24
  50. package/dist/{catch-selection.d.ts → perceive-selection.d.ts} +2 -2
  51. package/dist/{catch-selection.js → perceive-selection.js} +3 -3
  52. package/dist/plugins/opencode/index.js +4 -4
  53. package/dist/plugins/opencode-shared/delivery.d.ts +6 -6
  54. package/dist/plugins/opencode-shared/delivery.js +27 -21
  55. package/dist/plugins/opencode-v1/runtime.js +3 -3
  56. package/dist/ports.d.ts +0 -4
  57. package/dist/presentation.d.ts +1 -1
  58. package/dist/presentation.js +13 -13
  59. package/dist/session-lifecycle.d.ts +2 -2
  60. package/dist/session-lifecycle.js +11 -11
  61. package/dist/sqlite-square.d.ts +2 -4
  62. package/dist/sqlite-square.js +47 -54
  63. package/dist/sqlite-transaction.d.ts +2 -0
  64. package/dist/sqlite-transaction.js +25 -3
  65. package/dist/square-api.d.ts +1 -1
  66. package/dist/square-api.js +1 -1
  67. package/dist/square-domain.js +1 -1
  68. package/dist/square-facade.d.ts +15 -13
  69. package/dist/square-location.js +1 -1
  70. package/dist/square-posting.d.ts +5 -5
  71. package/dist/square-posting.js +39 -38
  72. package/dist/square-tools.d.ts +15 -4
  73. package/dist/square-tools.js +35 -17
  74. package/dist/views.d.ts +7 -3
  75. package/dist/views.js +6 -3
  76. package/guides/architect.md +2 -2
  77. package/guides/change-waits.md +1 -1
  78. package/guides/codex-delivery.md +40 -35
  79. package/guides/delivery-primitives.md +22 -7
  80. package/guides/mcp.md +4 -2
  81. package/guides/operation-cancellation.md +11 -3
  82. package/guides/pi-native-delivery.md +1 -1
  83. package/guides/pi-tools.md +11 -6
  84. package/guides/receive-primitive.md +3 -3
  85. package/guides/square-api.md +15 -8
  86. package/package.json +2 -2
  87. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  88. package/plugins/claude/skills/square/SKILL.md +10 -10
  89. package/plugins/codex/.codex-plugin/plugin.json +2 -2
  90. package/plugins/codex/hooks/hooks.json +3 -0
  91. package/plugins/opencode/index.ts +4 -4
  92. package/plugins/opencode-shared/delivery.ts +27 -21
  93. package/plugins/opencode-v1/runtime.ts +3 -3
  94. package/plugins/paseo/client/markdown.tsx +9 -26
  95. package/plugins/paseo/client/square-message.tsx +10 -14
  96. package/plugins/pi/square-pi.js +100 -20
  97. package/skills/square/SKILL.md +10 -10
  98. package/dist/catch-decisions.d.ts +0 -28
  99. package/dist/cli/catch-presentation.d.ts +0 -3
  100. package/guides/mcp-connection-lifetime.md +0 -22
@@ -19,7 +19,7 @@ export interface StoreTransaction {
19
19
  export declare function requireSquareExtension(location: string): void;
20
20
  export declare function storageError(error: unknown, location: string): unknown;
21
21
  /** Private storage opening; never creates a missing file. */
22
- export declare function openExistingSquareDatabase(location: string): DatabaseSync;
22
+ export declare function openExistingSquareDatabase(location: string, readOnly?: boolean): DatabaseSync;
23
23
  export declare function abort(signal?: AbortSignal): void;
24
24
  /** One ordered connection and transaction lifetime, shared by Channel and Agora compositions. */
25
25
  export declare function createArtifactStore(location: string, format: StoreFormat, initialize?: StoreInitializer, externalSignal?: AbortSignal): {
@@ -6,7 +6,7 @@ import { pathToFileURL } from 'node:url';
6
6
  import { setTimeout as sleep } from 'node:timers/promises';
7
7
  import { canonicalPath } from './canonical-path.js';
8
8
  import { observeFileVersion } from './file-changes.js';
9
- import { retrySqliteBusy, inSqliteTransaction } from './sqlite-transaction.js';
9
+ import { retrySqliteBusy, inSqliteTransaction, awaitSqliteQueue } from './sqlite-transaction.js';
10
10
  import { InternalSquareError, SquareError } from './neutral.js';
11
11
  import { readStorageRevision } from './sqlite-framing.js';
12
12
  import { invalidArtifact } from './sqlite-values.js';
@@ -24,12 +24,12 @@ export function storageError(error, location) {
24
24
  return error;
25
25
  }
26
26
  /** Private storage opening; never creates a missing file. */
27
- export function openExistingSquareDatabase(location) {
27
+ export function openExistingSquareDatabase(location, readOnly = false) {
28
28
  requireSquareExtension(location);
29
29
  const uri = pathToFileURL(path.resolve(location));
30
- uri.searchParams.set('mode', 'rw');
30
+ uri.searchParams.set('mode', readOnly ? 'ro' : 'rw');
31
31
  try {
32
- return new DatabaseSync(uri.href);
32
+ return new DatabaseSync(uri.href, { readOnly });
33
33
  }
34
34
  catch (error) {
35
35
  throw storageError(error, location);
@@ -69,10 +69,19 @@ export function createArtifactStore(location, format, initialize, externalSignal
69
69
  return database;
70
70
  location = await canonical;
71
71
  abort(signal);
72
- database = openExistingSquareDatabase(location);
72
+ const probe = await retrySqliteBusy(() => openExistingSquareDatabase(location, true), signal);
73
73
  try {
74
- await retrySqliteBusy(() => validate(database), signal);
75
- database.exec('PRAGMA busy_timeout=0; PRAGMA synchronous=FULL');
74
+ await retrySqliteBusy(() => validate(probe), signal);
75
+ }
76
+ catch (error) {
77
+ throw storageError(error, location);
78
+ }
79
+ finally {
80
+ probe.close();
81
+ }
82
+ database = await retrySqliteBusy(() => openExistingSquareDatabase(location), signal);
83
+ try {
84
+ await retrySqliteBusy(() => { validate(database); database.exec('PRAGMA busy_timeout=0; PRAGMA synchronous=FULL'); }, signal);
76
85
  }
77
86
  catch (error) {
78
87
  database.close();
@@ -86,9 +95,10 @@ export function createArtifactStore(location, format, initialize, externalSignal
86
95
  throw new TypeError('Square artifact operations cannot nest inside a callback.');
87
96
  if (closed)
88
97
  return Promise.reject(new Error('Square artifact is closed'));
89
- const result = tail.then(() => { abort(signal); return operation(signal); });
98
+ let started = false;
99
+ const result = tail.then(() => { abort(signal); started = true; return operation(signal); });
90
100
  tail = result.then(() => undefined, () => undefined);
91
- return result;
101
+ return awaitSqliteQueue(result, signal, () => started);
92
102
  }
93
103
  function transaction(write, callback, signal) {
94
104
  return connection(signal).then(db => inSqliteTransaction(db, write, () => validate(db), version => {
@@ -193,7 +203,7 @@ export function createArtifactStore(location, format, initialize, externalSignal
193
203
  };
194
204
  }
195
205
  export async function inspectStore(location, format, select, signal) {
196
- const db = openExistingSquareDatabase(location);
206
+ const db = await retrySqliteBusy(() => openExistingSquareDatabase(location, true), signal);
197
207
  return inSqliteTransaction(db, false, () => format.validate(db, location), () => select(db), signal)
198
208
  .catch(error => { throw storageError(error, location); }).finally(() => db.close());
199
209
  }
@@ -220,9 +230,9 @@ export async function createStoreFile(location, format, initialize, signal) {
220
230
  const handle = await fs.promises.open(temporary, 'wx');
221
231
  await handle.close();
222
232
  try {
223
- const db = new DatabaseSync(temporary);
233
+ const db = await retrySqliteBusy(() => new DatabaseSync(temporary), signal);
224
234
  try {
225
- db.exec('PRAGMA journal_mode=DELETE; PRAGMA synchronous=FULL');
235
+ await retrySqliteBusy(() => db.exec('PRAGMA journal_mode=DELETE; PRAGMA synchronous=FULL'), signal);
226
236
  await inSqliteTransaction(db, true, () => { }, () => { initialize(db); db.exec(`PRAGMA application_id=${format.applicationId}; PRAGMA user_version=${format.userVersion}`); }, signal);
227
237
  }
228
238
  finally {
@@ -254,10 +264,17 @@ export async function replaceStoreFile(location, format, initialize, signal) {
254
264
  if (await createStoreFile(location, format, db => initialize(db, 0), signal))
255
265
  return;
256
266
  abort(signal);
257
- const db = openExistingSquareDatabase(await canonicalPath(location));
267
+ location = await canonicalPath(location);
268
+ const probe = await retrySqliteBusy(() => openExistingSquareDatabase(location, true), signal);
269
+ try {
270
+ await retrySqliteBusy(() => format.validate(probe, location), signal);
271
+ }
272
+ finally {
273
+ probe.close();
274
+ }
275
+ const db = await retrySqliteBusy(() => openExistingSquareDatabase(location), signal);
258
276
  try {
259
- await retrySqliteBusy(() => format.validate(db, location), signal);
260
- db.exec('PRAGMA busy_timeout=0; PRAGMA synchronous=FULL');
277
+ await retrySqliteBusy(() => { format.validate(db, location); db.exec('PRAGMA busy_timeout=0; PRAGMA synchronous=FULL'); }, signal);
261
278
  await inSqliteTransaction(db, true, () => format.validate(db, location), () => {
262
279
  const revision = readStorageRevision(db);
263
280
  if (revision === Number.MAX_SAFE_INTEGER)
@@ -1,10 +1,7 @@
1
1
  import { type BuildOptions } from './model.js';
2
- import type { SquareResourceOwner, SquareArtifactPort } from './ports.js';
2
+ import type { SquareArtifactPort } from './ports.js';
3
3
  import { type SquareState } from './square-sqlite.js';
4
4
  export { openExistingSquareDatabase } from './artifact-store.js';
5
- /** Explicit-lifetime holder of one reused artifact connection. Request cancellation
6
- * never becomes the shared connection's lifetime; close drains the owner once. */
7
- export declare function createSquareResourceOwner(): SquareResourceOwner;
8
5
  /** All build paths share configuration validation and lazy guide preparation. */
9
6
  export declare function prepareSquareBuild(options: Omit<BuildOptions, 'force'>, snippet: string, includeGuides?: boolean): SquareState;
10
7
  /** File build owns publication and CLI conflict semantics, not host resources. */
package/dist/artifact.js CHANGED
@@ -1,4 +1,3 @@
1
- import path from 'node:path';
2
1
  import fs from 'node:fs';
3
2
  import { canonicalPath } from './canonical-path.js';
4
3
  import { InternalSquareError, SquareError } from './model.js';
@@ -8,33 +7,6 @@ import { resetStorageMeta } from './sqlite-framing.js';
8
7
  import { createArtifactStore, createStoreFile, replaceStoreFile, inspectStore, observeStoreRevision } from './artifact-store.js';
9
8
  export { openExistingSquareDatabase } from './artifact-store.js';
10
9
  const format = squareStoreFormat(resetStorageMeta);
11
- /** Explicit-lifetime holder of one reused artifact connection. Request cancellation
12
- * never becomes the shared connection's lifetime; close drains the owner once. */
13
- export function createSquareResourceOwner() {
14
- let held;
15
- let closed = false;
16
- return {
17
- artifact(location) {
18
- if (closed)
19
- throw new SquareError('unavailable', 'Square resource owner is closed');
20
- const key = path.resolve(location);
21
- if (held === undefined)
22
- held = { location: key, artifact: createArtifactPort(key) };
23
- if (held.location !== key)
24
- throw new SquareError('unavailable', `Square resource owner already holds ${held.location}; it cannot also open ${key}`);
25
- return held.artifact;
26
- },
27
- async close() {
28
- if (closed)
29
- return;
30
- closed = true;
31
- const artifact = held?.artifact;
32
- held = undefined;
33
- if (artifact !== undefined)
34
- await artifact.close();
35
- },
36
- };
37
- }
38
10
  const guideContents = new Map();
39
11
  function readGuide(name) {
40
12
  if (!['participant', 'architect', 'brainstorm'].includes(name))
@@ -41,7 +41,7 @@ export function projectNotification(attention) {
41
41
  ];
42
42
  return {
43
43
  text: [`<square-activity ${attributes.join(' ')}>`, `${description}.`, '', escapeSquareTags(preview),
44
- ...(!isFull ? ['', escapeSquareTags(`Use square_catch with location=${JSON.stringify(attention.squarePath)}, as=${JSON.stringify(attention.recipient)}, id=${JSON.stringify(formatActivityId(attention.actIndex))} for the full activity.`)] : []),
44
+ ...(!isFull ? ['', escapeSquareTags(`Use square_perceive with location=${JSON.stringify(attention.squarePath)}, as=${JSON.stringify(attention.recipient)}, id=${JSON.stringify(formatActivityId(attention.actIndex))} for the full activity.`)] : []),
45
45
  '</square-activity>'].join('\n'),
46
46
  full: ['<square-activity>', `· ${displayAttentionPath(attention.squarePath)} · ${attention.recipient} · ${formatActivityId(attention.actIndex)}`,
47
47
  `● ${description}`, '', body, '</square-activity>'].join('\n'),
@@ -3,9 +3,9 @@ import { deliveryFrameOverhead, renderDeliveryReminder } from './delivery-frame.
3
3
  const CONTEXT_MAX = 1200;
4
4
  /** The sink must resolve only after the host has actually accepted the output. */
5
5
  export async function presentPendingAtBoundary(owner, present, env = process.env, signal) {
6
- const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY });
6
+ const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY, signal });
7
7
  try {
8
- const session = await ledger.session(owner);
8
+ const session = await ledger.session(owner, signal);
9
9
  if (!session || session.state !== 'attached' || signal?.aborted)
10
10
  return undefined;
11
11
  const claim = await ledger.claimPresentation(owner, { generation: session.generation, budgetChars: CONTEXT_MAX - deliveryFrameOverhead({ tagged: true }), signal });
@@ -9,12 +9,12 @@ declare const stateSchema: z.ZodObject<{
9
9
  export type ClaudeBoundary = z.infer<typeof stateSchema>;
10
10
  /** Receiver operational facts only: no cards, claims, or payload copies. */
11
11
  export declare function claudeBoundaryStore(session: string, env: NodeJS.ProcessEnv): {
12
- start(attach: () => Promise<string | false>): Promise<void>;
13
- end(): Promise<ClaudeBoundary | undefined>;
14
- prompt(promptId: string | undefined): Promise<void>;
15
- capture(promptId: string): Promise<ClaudeBoundary | undefined>;
16
- current(captured: ClaudeBoundary): Promise<boolean>;
17
- consume(captured: ClaudeBoundary): Promise<void>;
12
+ start(attach: () => Promise<string | false>, signal?: AbortSignal): Promise<void>;
13
+ end(signal?: AbortSignal): Promise<ClaudeBoundary | undefined>;
14
+ prompt(promptId: string | undefined, signal?: AbortSignal): Promise<void>;
15
+ capture(promptId: string, signal?: AbortSignal): Promise<ClaudeBoundary | undefined>;
16
+ current(captured: ClaudeBoundary, signal?: AbortSignal): Promise<boolean>;
17
+ consume(captured: ClaudeBoundary, signal?: AbortSignal): Promise<void>;
18
18
  watch<T>(signal: AbortSignal, run: () => Promise<T>): Promise<T>;
19
19
  };
20
20
  export {};
@@ -13,10 +13,10 @@ export function claudeBoundaryStore(session, env) {
13
13
  const root = path.join(path.dirname(deliveryLedgerLocation(env)), 'claude-hooks');
14
14
  const key = createHash('sha256').update(session).digest('hex');
15
15
  const file = path.join(root, `${key}.json`);
16
- async function mutate(change) {
16
+ async function mutate(change, signal) {
17
17
  await fs.mkdir(root, { recursive: true, mode: 0o700 });
18
18
  await fs.chmod(root, 0o700);
19
- return withFileLock(path.join(root, `${key}.lock`), { retryMs: 10, signal: AbortSignal.timeout(2500) }, async () => {
19
+ return withFileLock(path.join(root, `${key}.lock`), { retryMs: 10, signal: signal ?? AbortSignal.timeout(2500) }, async () => {
20
20
  let state;
21
21
  try {
22
22
  state = stateSchema.parse(JSON.parse(await fs.readFile(file, 'utf8')));
@@ -40,37 +40,37 @@ export function claudeBoundaryStore(session, env) {
40
40
  const matches = (state, captured) => !!state && !state.ended && !state.consumed
41
41
  && state.generation === captured.generation && state.promptId === captured.promptId && state.stopEpoch === captured.stopEpoch;
42
42
  return {
43
- async start(attach) {
43
+ async start(attach, signal) {
44
44
  await mutate(async (state, save) => {
45
45
  if (state)
46
46
  await save({ ...state, promptId: undefined, ended: true, stopEpoch: randomUUID() });
47
47
  const generation = await attach();
48
48
  if (generation)
49
49
  await save({ generation, stopEpoch: randomUUID(), consumed: false, ended: false });
50
- });
50
+ }, signal);
51
51
  },
52
- async end() {
52
+ async end(signal) {
53
53
  return mutate(async (state, save) => {
54
54
  if (state)
55
55
  await save({ ...state, ended: true, stopEpoch: randomUUID() });
56
56
  return state;
57
- });
57
+ }, signal);
58
58
  },
59
- async prompt(promptId) {
59
+ async prompt(promptId, signal) {
60
60
  await mutate(async (state, save) => {
61
61
  if (state && !state.ended)
62
62
  await save({ ...state, promptId, stopEpoch: randomUUID(), consumed: false });
63
- });
63
+ }, signal);
64
64
  },
65
- capture(promptId) {
66
- return mutate(async (state) => state && !state.ended && !state.consumed && state.promptId === promptId ? state : undefined);
65
+ capture(promptId, signal) {
66
+ return mutate(async (state) => state && !state.ended && !state.consumed && state.promptId === promptId ? state : undefined, signal);
67
67
  },
68
- current(captured) { return mutate(async (state) => matches(state, captured)); },
69
- async consume(captured) {
68
+ current(captured, signal) { return mutate(async (state) => matches(state, captured), signal); },
69
+ async consume(captured, signal) {
70
70
  await mutate(async (state, save) => {
71
71
  if (matches(state, captured))
72
72
  await save({ ...state, consumed: true });
73
- });
73
+ }, signal);
74
74
  },
75
75
  async watch(signal, run) {
76
76
  await fs.mkdir(root, { recursive: true, mode: 0o700 });
@@ -53,43 +53,44 @@ export async function runClaudeHook(text, env = process.env, sink = value => { p
53
53
  await denyMcpTool(sink);
54
54
  return '';
55
55
  }
56
+ const signal = AbortSignal.timeout(4500);
56
57
  const boundaries = claudeBoundaryStore(input.session_id, env);
57
58
  const mainOwner = ownerFor(input);
58
59
  const owner = harnessOwner({ channel: 'claude-code', session: input.session_id, ...(input.agent_id === undefined ? {} : { agent: input.agent_id }) });
59
60
  if (input.hook_event_name === 'SessionStart') {
60
61
  await boundaries.start(async () => {
61
- const session = await attachDeliverySessionRoutes(mainOwner, env, [{ kind: route, address: { sessionId: input.session_id } }], AbortSignal.timeout(4500));
62
+ const session = await attachDeliverySessionRoutes(mainOwner, env, [{ kind: route, address: { sessionId: input.session_id } }], signal);
62
63
  if (!session)
63
64
  return false;
64
- const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env) });
65
+ const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal });
65
66
  try {
66
- await ledger.updateRoutes({ owner: mainOwner, generation: session.generation, routes: session.routes.filter(item => item.kind !== 'claude-native') });
67
+ await ledger.updateRoutes({ owner: mainOwner, generation: session.generation, routes: session.routes.filter(item => item.kind !== 'claude-native') }, signal);
67
68
  }
68
69
  finally {
69
70
  await ledger.close();
70
71
  }
71
72
  return session.generation;
72
- });
73
+ }, signal);
73
74
  return '';
74
75
  }
75
76
  if (input.hook_event_name === 'SessionEnd') {
76
- const captured = await boundaries.end();
77
+ const captured = await boundaries.end(signal);
77
78
  if (captured)
78
- await detachDeliverySession(mainOwner, captured.generation, env);
79
+ await detachDeliverySession(mainOwner, captured.generation, env, signal);
79
80
  return '';
80
81
  }
81
82
  if (input.hook_event_name === 'UserPromptSubmit') {
82
- await boundaries.prompt(input.prompt_id);
83
+ await boundaries.prompt(input.prompt_id, signal);
83
84
  if (!input.prompt_id)
84
85
  return '';
85
86
  if (input.prompt) {
86
- const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env) });
87
+ const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal });
87
88
  try {
88
89
  for (const frame of observedFrames(input.prompt)) {
89
- const handoff = await ledger.handoff(frame.token);
90
+ const handoff = await ledger.handoff(frame.token, signal);
90
91
  if (!handoff || handoff.owner !== owner || handoff.routeKind !== route || handoff.payloadHash !== frame.payloadHash)
91
92
  continue;
92
- await ledger.observePresentation(handoff);
93
+ await ledger.observePresentation(handoff, undefined, signal);
93
94
  }
94
95
  }
95
96
  finally {
@@ -98,7 +99,7 @@ export async function runClaudeHook(text, env = process.env, sink = value => { p
98
99
  }
99
100
  await presentPendingAtBoundary(owner, async (context) => {
100
101
  await sink(`${JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: context } })}\n`);
101
- }, env, AbortSignal.timeout(4500));
102
+ }, env, signal);
102
103
  return '';
103
104
  }
104
105
  if (input.hook_event_name === 'PreToolUse' && input.tool_name?.startsWith('mcp__plugin_square_square__')) {
@@ -107,7 +108,7 @@ export async function runClaudeHook(text, env = process.env, sink = value => { p
107
108
  return '';
108
109
  }
109
110
  try {
110
- await beginClaudeMcpCall({ toolUseId: input.tool_use_id, sessionId: input.session_id, cwd: input.cwd, agentId: input.agent_id }, env);
111
+ await beginClaudeMcpCall({ toolUseId: input.tool_use_id, sessionId: input.session_id, cwd: input.cwd, agentId: input.agent_id }, env, signal);
111
112
  }
112
113
  catch {
113
114
  await denyMcpTool(sink);
@@ -116,12 +117,12 @@ export async function runClaudeHook(text, env = process.env, sink = value => { p
116
117
  }
117
118
  if ((input.hook_event_name === 'PostToolUse' || input.hook_event_name === 'PostToolUseFailure')
118
119
  && input.tool_name?.startsWith('mcp__plugin_square_square__') && input.tool_use_id) {
119
- await endClaudeMcpCallForSession(input.tool_use_id, input.session_id, env, input.agent_id).catch(() => undefined);
120
+ await endClaudeMcpCallForSession(input.tool_use_id, input.session_id, env, input.agent_id, signal).catch(() => undefined);
120
121
  }
121
122
  if (input.hook_event_name === 'PostToolUse') {
122
123
  await presentPendingAtBoundary(owner, async (context) => {
123
124
  await sink(`${JSON.stringify({ hookSpecificOutput: { hookEventName: input.hook_event_name, additionalContext: context } })}\n`);
124
- }, env, AbortSignal.timeout(4500));
125
+ }, env, signal);
125
126
  }
126
127
  return '';
127
128
  }
@@ -151,24 +152,24 @@ export async function runClaudeWatch(text, env = process.env, signal, output = f
151
152
  process.once('SIGINT', stop);
152
153
  const combined = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
153
154
  try {
154
- const captured = await boundaries.capture(input.prompt_id);
155
+ const captured = await boundaries.capture(input.prompt_id, combined);
155
156
  if (!captured)
156
157
  return '';
157
158
  await boundaries.watch(combined, async () => {
158
- if (!await boundaries.current(captured))
159
+ if (!await boundaries.current(captured, combined))
159
160
  return;
160
- const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env) });
161
+ const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal: combined });
161
162
  const lifetime = retainDeliveryLedger(ledger);
162
163
  const observer = await ledger.observe(owner);
163
164
  try {
164
165
  while (!combined.aborted) {
165
166
  let version = await observer.read(combined);
166
- const live = await boundaries.current(captured);
167
- const session = await ledger.session(owner);
167
+ const live = await boundaries.current(captured, combined);
168
+ const session = await ledger.session(owner, combined);
168
169
  if (!live || session?.state !== 'attached' || session.generation !== captured.generation || !session.routes.some(item => item.kind === route))
169
170
  return;
170
- const cards = await ledger.pending(owner, { generation: captured.generation });
171
- const held = new Set((await ledger.handoffs(owner, { generation: captured.generation }))
171
+ const cards = await ledger.pending(owner, { generation: captured.generation }, combined);
172
+ const held = new Set((await ledger.handoffs(owner, { generation: captured.generation }, combined))
172
173
  .filter(attempt => !['failed', 'released'].includes(attempt.outcome)).map(attempt => attempt.seq));
173
174
  for (const card of cards) {
174
175
  if (held.has(card.seq))
@@ -182,18 +183,18 @@ export async function runClaudeWatch(text, env = process.env, signal, output = f
182
183
  const payloadHash = createHash('sha256').update(frame).digest('hex');
183
184
  if (!await context.prepare({ token: context.token, messageId: context.token, payloadHash }))
184
185
  return 'not-capable';
185
- const currentSession = await ledger.session(owner);
186
+ const currentSession = await ledger.session(owner, context.signal);
186
187
  if (currentSession?.state !== 'attached' || currentSession.generation !== captured.generation
187
188
  || !currentSession.routes.some(item => item.kind === route) || !await context.beforeSend())
188
189
  return 'not-capable';
189
- const native = await boundaries.current(captured);
190
+ const native = await boundaries.current(captured, context.signal);
190
191
  if (!native || context.signal.aborted || Date.now() >= context.deadline)
191
192
  return 'not-capable';
192
193
  try {
193
194
  await output(frame);
194
195
  }
195
196
  finally {
196
- await boundaries.consume(captured);
197
+ await boundaries.consume(captured, AbortSignal.timeout(5000));
197
198
  }
198
199
  process.exitCode = 2;
199
200
  return 'unknown';
@@ -6,10 +6,10 @@ declare const coordinate: z.ZodObject<{
6
6
  agentId: z.ZodOptional<z.ZodString>;
7
7
  }, z.core.$strip>;
8
8
  type ClaudeMcpCall = z.infer<typeof coordinate>;
9
- export declare function beginClaudeMcpCall(input: ClaudeMcpCall, env?: NodeJS.ProcessEnv): Promise<{
9
+ export declare function beginClaudeMcpCall(input: ClaudeMcpCall, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<{
10
10
  token: string;
11
11
  }>;
12
12
  export declare function endClaudeMcpCall(toolUseId: string, token: string, env?: NodeJS.ProcessEnv): Promise<void>;
13
- export declare function endClaudeMcpCallForSession(toolUseId: string, sessionId: string, env?: NodeJS.ProcessEnv, agentId?: string): Promise<void>;
13
+ export declare function endClaudeMcpCallForSession(toolUseId: string, sessionId: string, env?: NodeJS.ProcessEnv, agentId?: string, signal?: AbortSignal): Promise<void>;
14
14
  export declare function readClaudeMcpCall(toolUseId: unknown, env?: NodeJS.ProcessEnv): Promise<ClaudeMcpCall>;
15
15
  export {};
@@ -26,13 +26,13 @@ async function readRecord(file) {
26
26
  }
27
27
  // Only publishers/cleanup mutate this private transport directory. Readers see an
28
28
  // immutable record via atomic rename, so they need neither a lock nor housekeeping.
29
- async function mutate(env, fn) {
29
+ async function mutate(env, fn, signal) {
30
30
  const root = storeRoot(env);
31
31
  await fs.mkdir(root, { recursive: true, mode: 0o700 });
32
32
  await fs.chmod(root, 0o700);
33
- return withFileLock(path.join(root, 'store.lock'), { retryMs: 10, signal: AbortSignal.timeout(2000) }, () => fn(root));
33
+ return withFileLock(path.join(root, 'store.lock'), { retryMs: 10, signal: signal ?? AbortSignal.timeout(2000) }, () => fn(root));
34
34
  }
35
- export async function beginClaudeMcpCall(input, env = process.env) {
35
+ export async function beginClaudeMcpCall(input, env = process.env, signal) {
36
36
  const parsed = coordinate.safeParse(input);
37
37
  if (!parsed.success)
38
38
  throw unavailable();
@@ -59,7 +59,7 @@ export async function beginClaudeMcpCall(input, env = process.env) {
59
59
  await fs.rm(temporary, { force: true });
60
60
  }
61
61
  return { token };
62
- });
62
+ }, signal);
63
63
  }
64
64
  export async function endClaudeMcpCall(toolUseId, token, env = process.env) {
65
65
  if (!text.safeParse(toolUseId).success || !text.safeParse(token).success)
@@ -71,7 +71,7 @@ export async function endClaudeMcpCall(toolUseId, token, env = process.env) {
71
71
  await fs.unlink(file);
72
72
  });
73
73
  }
74
- export async function endClaudeMcpCallForSession(toolUseId, sessionId, env = process.env, agentId) {
74
+ export async function endClaudeMcpCallForSession(toolUseId, sessionId, env = process.env, agentId, signal) {
75
75
  if (!text.safeParse(toolUseId).success || !text.safeParse(sessionId).success)
76
76
  throw unavailable();
77
77
  await mutate(env, async (root) => {
@@ -79,7 +79,7 @@ export async function endClaudeMcpCallForSession(toolUseId, sessionId, env = pro
79
79
  const stored = await readRecord(file);
80
80
  if (stored?.toolUseId === toolUseId && stored.sessionId === sessionId && stored.agentId === agentId)
81
81
  await fs.unlink(file);
82
- });
82
+ }, signal);
83
83
  }
84
84
  export async function readClaudeMcpCall(toolUseId, env = process.env) {
85
85
  if (!text.safeParse(toolUseId).success)
@@ -92,7 +92,7 @@ const LOCATION_REQUIRED_COMMANDS = new Set([
92
92
  'build',
93
93
  'stream',
94
94
  'join',
95
- 'catch',
95
+ 'perceive',
96
96
  'express',
97
97
  'listen',
98
98
  'ignore',
@@ -130,13 +130,10 @@ export async function parseGlobalArgs(rawArgs) {
130
130
  if (name !== undefined)
131
131
  validateName(name);
132
132
  const explicitSquarePath = requestedPath !== undefined;
133
- const configuredLocation = process.env.SQUARE_LOCATION?.trim();
134
- const configured = configuredLocation === '' ? undefined : configuredLocation;
135
- if (command !== undefined && !['--help', '-h'].includes(command) && locationIsRequired(command) && requestedPath === undefined && configured === undefined) {
133
+ if (command !== undefined && !['--help', '-h'].includes(command) && locationIsRequired(command) && requestedPath === undefined) {
136
134
  fail(`✕ ${command} needs a square location\nsquare ls`);
137
135
  }
138
- const squarePath = requestedPath ?? configured;
139
- return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, owner, args };
136
+ return { squarePath: requestedPath, explicitSquarePath, multipleSquares: false, name, owner, args };
140
137
  }
141
138
  export function locationIsRequired(command) {
142
139
  return LOCATION_REQUIRED_COMMANDS.has(command);
package/dist/cli/json.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const JSON_COMMANDS = new Set([
2
- 'join', 'express', 'catch', 'history', 'listen', 'ignore', 'unlisten', 'listening',
2
+ 'join', 'express', 'perceive', 'history', 'listen', 'ignore', 'unlisten', 'listening',
3
3
  'done', 'status', 'participants', 'inbox',
4
4
  ]);
5
5
  /** Serialize the SDK receipt, not its optional presentation or live participant handle. */
@@ -1,13 +1,13 @@
1
- import { fail, squareInput, commandIdentity } from './context.js';
1
+ import { fail, commandIdentity } from './context.js';
2
2
  export const mcpServerCommand = {
3
3
  parse(argv) {
4
4
  if (argv.length > 0)
5
- fail('Usage: square [--location <square>] [--as <name>] mcp-server');
5
+ fail('Usage: square mcp-server');
6
6
  return undefined;
7
7
  },
8
8
  async execute(_intent, context) {
9
9
  const { runSquareMcpStdio } = await import('../mcp.js');
10
- await runSquareMcpStdio({ ...squareInput(context), ...commandIdentity(context.env, context.owner) });
10
+ await runSquareMcpStdio({ cwd: context.cwd, env: context.env, ...commandIdentity(context.env, context.owner) });
11
11
  },
12
12
  present() { },
13
13
  };
@@ -1,4 +1,4 @@
1
- import type { CatchOptions, DiscoveryOptions, HistoryQuery, StreamStart } from '../square-facade.js';
1
+ import type { PerceiveOptions, DiscoveryOptions, HistoryQuery, StreamStart } from '../square-facade.js';
2
2
  import { type CommandSpec } from './context.js';
3
3
  interface HistoryCommandOptions {
4
4
  query: HistoryQuery;
@@ -13,7 +13,7 @@ interface StreamIntent {
13
13
  start: StreamStart;
14
14
  }
15
15
  export declare const streamCommand: CommandSpec<StreamIntent>;
16
- export declare const catchCommand: CommandSpec<CatchOptions>;
16
+ export declare const perceiveCommand: CommandSpec<PerceiveOptions>;
17
17
  export declare const historyCommand: CommandSpec<HistoryCommandOptions, string>;
18
18
  interface ParticipantsCommandOptions {
19
19
  limit: number;