@astrosheep/square 0.3.65 → 0.3.66

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.65",
3
+ "version": "0.3.66",
4
4
  "description": "Native Claude Code inbox delivery for Square participants",
5
5
  "types": "./types.d.ts",
6
6
  "author": {
@@ -1,4 +1,4 @@
1
- // Claude Code 2.1.295 mod. No Node imports, shell interpolation, or child credentials.
1
+ // Claude Code mod. No Node imports, shell interpolation, or child credentials.
2
2
  const hasToken = (text) => typeof text === 'string' && /(?:^|\n)\[square-inbox:[a-zA-Z0-9-]+\](?:\n|$)/.test(text);
3
3
 
4
4
  async function coordinate($) {
@@ -55,6 +55,15 @@ async function cancel($) {
55
55
 
56
56
  export function register(on) {
57
57
  on('session.start', async ($, e, next) => {
58
+ // Claude refuses plugin tools in sessions started without built-in tools; the inbox must still reconcile.
59
+ try {
60
+ await $.tool.register({
61
+ name: 'square',
62
+ description: "Run the square CLI. args are exactly the words after `square` in the square skill's commands.",
63
+ inputSchema: { type: 'object', properties: { args: { type: 'array', items: { type: 'string' } }, stdin: { type: 'string' } }, required: ['args'] },
64
+ isDeferred: false,
65
+ });
66
+ } catch (error) { await $.ui.log(`Square tool unavailable: ${error?.message ?? error}`); }
58
67
  await reconcile($).catch(() => undefined);
59
68
  return next(e);
60
69
  });
@@ -63,7 +72,25 @@ export function register(on) {
63
72
  return next(e);
64
73
  });
65
74
  on('tool.call', async ($, e, next) => {
66
- try { return await next(e); }
75
+ try {
76
+ if (e.tool !== 'mcp__square__square') return await next(e);
77
+ const sessionId = await $.session.id();
78
+ const cwd = await $.session.cwd();
79
+ // process.run has no signal option; it belongs to this cancellable dispatch.
80
+ next.signal.throwIfAborted();
81
+ const result = await $.process.run(['square', ...e.args], {
82
+ cwd, stdin: e.stdin,
83
+ env: { CLAUDE_CODE_SESSION_ID: sessionId, CLAUDE_CODE_CHILD_SESSION: e.agentId ? '1' : '' },
84
+ timeoutMs: 600_000,
85
+ });
86
+ let text = result.stdout;
87
+ if (result.stderr) text += `${text && !text.endsWith('\n') ? '\n' : ''}${result.stderr}`;
88
+ if (result.exitCode !== 0) {
89
+ text += `${text && !text.endsWith('\n') ? '\n' : ''}Exit code: ${result.exitCode}`;
90
+ return { deny: text };
91
+ }
92
+ return { result: text };
93
+ }
67
94
  finally { if (!e.agentId) await reconcile($, undefined, 'reconcile').catch(() => undefined); }
68
95
  });
69
96
  on('turn.start', async ($, e, next) => {
@@ -6,6 +6,8 @@ allowed-tools: Bash(square *)
6
6
 
7
7
  # Square
8
8
 
9
+ When a `square` tool is available, call it with the same arguments instead of the shell; pass any `-` body as `stdin`.
10
+
9
11
  Don't treat Square as a public dumping ground. Speak here only when you genuinely think someone else needs to know; otherwise you are needlessly interrupting them.
10
12
 
11
13
  A square is a physical place where participants catch up and express thoughts or actions. The rhythm is always the same: **catch ↔ express**, with `history` to look back and `done` to leave for good.
@@ -3,5 +3,6 @@ export type SquareInboxCoordinate = { sessionId: string; cwd: string; version: s
3
3
  export type SquareInboxCancellation = { at: number; bindings?: SquareInboxBinding[] };
4
4
  export type SquareInboxState = { coordinate: SquareInboxCoordinate; bindings?: SquareInboxBinding[] };
5
5
  declare module 'claude-code' {
6
+ interface McpToolInputs { mcp__square__square: { args: string[]; stdin?: string } }
6
7
  interface PluginState { square: { inbox: StateFamily<SquareInboxState>; cancelAt: StateFamily<SquareInboxCancellation> } }
7
8
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.65",
3
+ "version": "0.3.66",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -109,25 +109,27 @@ export async function automaticSessionEnd(provider, sessionId, cwd, env = proces
109
109
  const scopedEnv = operationEnv(provider, sessionId, env);
110
110
  const hostLedger = hostLedgerForEnv(scopedEnv);
111
111
  const probe = await openSquare(squarePath, { hostLedger, env: scopedEnv });
112
- const binding = (await projectSessionBindings({
112
+ const bindings = await projectSessionBindings({
113
113
  hostLedger,
114
114
  sessionId,
115
115
  location: probe.location,
116
- }))[0];
117
- const expectedEpoch = binding === undefined
118
- ? undefined
119
- : (await readParticipantOwner(squarePath, binding.participant, hostLedger))?.epoch;
116
+ });
120
117
  await closeOpenSquare(probe);
121
118
  const square = await Square.at({ path: squarePath, hostLedger, env: scopedEnv });
122
119
  try {
123
- // The ended owner's done, routes, and presence settle inside the ownership claim
124
- // lock. A same-session rejoin or takeover that lands afterwards claims only after
125
- // that cleanup, so this call never performs post-return ledger or route cleanup that
126
- // could remove the replacement owner's fresh rows.
127
- if (binding === undefined)
120
+ // A harness session may own several names in the same square, so every binding is
121
+ // ended, not just the first. Each ended owner's done, routes, and presence settle
122
+ // inside the ownership claim lock, fenced by its own epoch. A same-session rejoin or
123
+ // takeover that lands afterwards claims only after that cleanup, so this call never
124
+ // performs post-return ledger or route cleanup that could remove the replacement
125
+ // owner's fresh rows.
126
+ if (bindings.length === 0)
128
127
  await square.retireEndedSessionRoutes(sessionId);
129
128
  else
130
- await square.endOwnedSession(binding.participant, sessionId, expectedEpoch);
129
+ for (const binding of bindings) {
130
+ const expectedEpoch = (await readParticipantOwner(squarePath, binding.participant, hostLedger))?.epoch;
131
+ await square.endOwnedSession(binding.participant, sessionId, expectedEpoch);
132
+ }
131
133
  }
132
134
  finally {
133
135
  await square.close();
@@ -10,7 +10,7 @@ import { DeliveryError, connect } from './packages/agent-delivery/src/index.js';
10
10
  const marker = /(?:^|\n)\[square-inbox:([a-zA-Z0-9-]+)\](?:\n|$)/g;
11
11
  export function nativeTokens(text) { return [...text.matchAll(marker)].map((match) => match[1]); }
12
12
  export function nativePayload(token, preview) { return `[square-inbox:${token}]\n${preview}\n[/square-inbox:${token}]`; }
13
- function nativeSupported(route) { return route.address.version === '2.1.295' && route.address.loaded === 'true' && !!route.address.endpoint; }
13
+ function nativeSupported(route) { return route.address.loaded === 'true' && !!route.address.endpoint; }
14
14
  /**
15
15
  * The shared entry owns platform policy, token resolution and frame shape; Square
16
16
  * owns the payload, the correlation and the evidence. A local write is never proof
@@ -33,7 +33,7 @@ export function createClaudeWakeTransport(hostLedger, env = process.env) {
33
33
  return {
34
34
  async probe(route) {
35
35
  if (!nativeSupported(route))
36
- return { outcome: 'not-capable', diagnostic: 'Claude native inbox unavailable: Claude 2.1.295 with a loaded interactive Square mod required.' };
36
+ return { outcome: 'not-capable', diagnostic: 'Claude native inbox unavailable: a loaded interactive Square mod is required.' };
37
37
  try {
38
38
  return (await stat(route.address.endpoint)).isSocket() || { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint is not a socket.' };
39
39
  }
@@ -69,7 +69,7 @@ async function dispatchClaude(request, hostLedger, timeoutMs, beforeSend, env =
69
69
  request = { ...request, location: await canonicalPath(request.location) };
70
70
  const signal = AbortSignal.timeout(timeoutMs);
71
71
  if (!nativeSupported(request.route))
72
- return { outcome: 'not-capable', diagnostic: 'Claude native inbox requires Claude 2.1.295 and a loaded interactive Square mod.' };
72
+ return { outcome: 'not-capable', diagnostic: 'Claude native inbox requires a loaded interactive Square mod.' };
73
73
  return hostLedger.withClaimLock(async () => {
74
74
  if (signal.aborted)
75
75
  return { outcome: 'not-capable', diagnostic: 'Native dispatch deadline elapsed before send.' };
@@ -17,7 +17,7 @@ export async function runClaudeMod(inputText, env = process.env) {
17
17
  const scoped = operationEnv('claude', input.sessionId, env);
18
18
  const ledger = hostLedgerForEnv(env);
19
19
  const signal = AbortSignal.timeout(2500);
20
- const supported = process.platform === 'darwin' && input.version === '2.1.295' && typeof input.endpoint === 'string' && input.endpoint.startsWith('/');
20
+ const supported = process.platform === 'darwin' && typeof input.endpoint === 'string' && input.endpoint.startsWith('/');
21
21
  if (input.operation === 'end') {
22
22
  const retired = [];
23
23
  const captured = [...input.bindings ?? []];
@@ -156,7 +156,7 @@ export async function runClaudeMod(inputText, env = process.env) {
156
156
  await processActNotificationsOnce(binding.location, index, { env: { ...env, SQUARE_NOTIFY_DELIVERY_WAIT_MS: '200' } });
157
157
  }
158
158
  }
159
- return `${JSON.stringify({ available: supported, diagnostic: supported ? undefined : 'Native inbox unavailable: requires macOS Claude 2.1.295, an interactive loaded mod and its real endpoint.', bindings, cancelledBindings })}\n`;
159
+ return `${JSON.stringify({ available: supported, diagnostic: supported ? undefined : 'Native inbox unavailable: requires macOS Claude with an interactive loaded mod and its real endpoint.', bindings, cancelledBindings })}\n`;
160
160
  }
161
161
  if (!['guard', 'admitted', 'stored'].includes(input.operation) || typeof input.text !== 'string')
162
162
  throw new Error('Unknown Claude bridge operation.');
@@ -71,8 +71,8 @@ export async function doctorClaudePlugin(homeDir, run = runClaude) {
71
71
  return [
72
72
  fs.existsSync(bundle) ? `✓ Square Claude plugin bundle ${root}` : `○ Square Claude plugin bundle missing ${root}`,
73
73
  installed ? `✓ ${CLAUDE_PLUGIN_ID} installed` : `○ ${CLAUDE_PLUGIN_ID} unavailable`,
74
- `Claude native inbox: validated on macOS Claude Code 2.1.295 with a trusted, approved interactive mod.`,
75
- `Installation or a socket alone does not prove receiver availability; absent/disabled mods, headless unapproved mods, other builds/platforms are unavailable.`,
74
+ `Claude native inbox: requires macOS Claude Code with a trusted, approved interactive mod.`,
75
+ `Installation or a socket alone does not prove receiver availability; absent/disabled mods, headless unapproved mods, other platforms are unavailable.`,
76
76
  `Queue admission is not model processing. Queued or held native messages cannot be retracted by Square cancellation.`,
77
77
  `Live routes and admission/presentation evidence: square harness doctor delivery. No legacy fallback.`,
78
78
  ];
package/dist/help.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const COMMANDS = [
2
2
  { names: ['help'], usage: 'help [command]', summary: 'Show the command index or help for one command.' },
3
3
  { names: ['version'], usage: 'version', summary: 'Print the installed version.', hiddenFromIndex: true },
4
- { names: ['mcp-server'], usage: 'mcp-server', summary: 'Start the MCP stdio server.', usesSquare: true, hiddenFromIndex: true, details: ['Set SQUARE_LOCATION and SQUARE_PARTICIPANT_NAME, or pass --location and --as before the command.'] },
4
+ { names: ['mcp-server'], usage: 'mcp-server', summary: 'Start the MCP stdio server.', usesSquare: true, hiddenFromIndex: true, details: ['Set SQUARE_LOCATION or pass --location before the command.', 'Participant tools accept as on each call. Set the default with SQUARE_PARTICIPANT_NAME or --as.', 'Session identity comes from request _meta.threadId (Codex) or _meta["ai.opencode/sessionID"] (OpenCode), falling back to the startup environment.'] },
5
5
  {
6
6
  names: ['build'], usage: 'build [--cap <N|unlimited>] [--template <name>] [--throttle N] [-f] < body.md', usesSquare: true, group: 'host',
7
7
  summary: 'Create a square from Markdown on stdin.',
package/dist/mcp.js CHANGED
@@ -2,21 +2,24 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { z } from 'zod/v4';
4
4
  import { SquareError, isSquareError } from './model.js';
5
+ import { operationEnv } from './automatic-session.js';
6
+ import { SQUARE_IDENTITY } from './identity.js';
5
7
  import { createSquareApplication } from './square-application.js';
6
8
  import { toPublicActivity } from './views.js';
7
9
  const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
8
10
  const stringArray = z.array(z.string());
11
+ const participantName = z.string().optional().describe("Participant name, same as --as. Defaults to the server's configured participant.");
9
12
  const TOOL_DEFINITIONS = [
10
- { name: 'join', description: 'Join the square as the configured participant.', inputSchema: z.object({ takeover: z.boolean().optional() }).strict() },
11
- { name: 'express', description: 'Express a message or embodied action in the square.', inputSchema: z.object({ body: z.string(), mentions: stringArray.optional(), reach: z.enum(['bell']).optional(), reply: activityId.optional(), force: z.boolean().optional(), noWait: z.boolean().optional() }).strict() },
12
- { name: 'catch', description: 'Catch and consume activities perceptible to the configured participant.', inputSchema: z.object({ id: activityId.optional(), idle: z.number().finite().min(0).optional(), from: stringArray.optional(), mention: z.boolean().optional(), limit: z.number().int().min(1).optional() }).strict() },
13
+ { name: 'join', description: 'Join the square as a participant.', inputSchema: z.object({ as: participantName, takeover: z.boolean().optional() }).strict() },
14
+ { name: 'express', description: 'Express a message or embodied action in the square.', inputSchema: z.object({ as: participantName, body: z.string(), mentions: stringArray.optional(), reach: z.enum(['bell']).optional(), reply: activityId.optional(), force: z.boolean().optional(), noWait: z.boolean().optional() }).strict() },
15
+ { name: 'catch', description: 'Catch and consume activities perceptible to the participant.', inputSchema: z.object({ as: participantName, id: activityId.optional(), idle: z.number().finite().min(0).optional(), from: stringArray.optional(), mention: z.boolean().optional(), limit: z.number().int().min(1).optional() }).strict() },
13
16
  { name: 'history', description: 'Read the activity stream using stable activity-id cursors.', inputSchema: z.object({ limit: z.number().int().min(1).optional(), order: z.enum(['asc', 'desc']).optional(), before: activityId.optional(), after: activityId.optional(), grep: z.string().optional(), fixed: z.string().optional(), from: stringArray.optional(), mention: z.string().optional() }).strict() },
14
- { name: 'listen', description: 'Listen for future activity from a participant.', inputSchema: z.object({ target: z.string() }).strict() },
15
- { name: 'ignore', description: 'Stop listening for future activity from a participant.', inputSchema: z.object({ target: z.string() }).strict() },
16
- { name: 'listening', description: 'List participants this participant is listening to.', inputSchema: z.object({}).strict() },
17
- { name: 'hold', description: 'Raise a hand to hold the square.', inputSchema: z.object({ reason: z.string().optional() }).strict() },
18
- { name: 'resume', description: 'Release the current hold.', inputSchema: z.object({}).strict() },
19
- { name: 'done', description: 'Leave the square without adding a message.', inputSchema: z.object({}).strict() },
17
+ { name: 'listen', description: 'Listen for future activity from a participant.', inputSchema: z.object({ as: participantName, target: z.string() }).strict() },
18
+ { name: 'ignore', description: 'Stop listening for future activity from a participant.', inputSchema: z.object({ as: participantName, target: z.string() }).strict() },
19
+ { name: 'listening', description: 'List participants this participant is listening to.', inputSchema: z.object({ as: participantName }).strict() },
20
+ { name: 'hold', description: 'Raise a hand to hold the square.', inputSchema: z.object({ as: participantName, reason: z.string().optional() }).strict() },
21
+ { name: 'resume', description: 'Release the current hold.', inputSchema: z.object({ as: participantName }).strict() },
22
+ { name: 'done', description: 'Leave the square without adding a message.', inputSchema: z.object({ as: participantName }).strict() },
20
23
  { name: 'status', description: 'Read the public status of the square.', inputSchema: z.object({}).strict() },
21
24
  { name: 'participants', description: 'List participants standing in or done with the square.', inputSchema: z.object({}).strict() },
22
25
  ];
@@ -71,14 +74,23 @@ function operation(app, name, args, control) {
71
74
  }
72
75
  }
73
76
  export function createSquareMcpServer(options) {
74
- const app = createSquareApplication(options);
75
- const server = new McpServer({ name: 'square', version: '0.3.61' }, {
77
+ const baseEnv = { ...options.env };
78
+ const server = new McpServer({ name: 'square', version: SQUARE_IDENTITY.packageVersion }, {
76
79
  instructions: 'The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.',
77
80
  });
78
81
  for (const tool of TOOL_DEFINITIONS) {
79
82
  server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, async (args, extra) => {
80
83
  try {
81
- const value = await operation(app, tool.name, args, { signal: extra.signal });
84
+ const { as: participant, ...operationArgs } = args;
85
+ const threadId = extra._meta?.threadId;
86
+ const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
87
+ const env = typeof threadId === 'string' && threadId.trim()
88
+ ? operationEnv('codex', threadId, baseEnv)
89
+ : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
90
+ ? operationEnv('opencode', opencodeSessionId, baseEnv)
91
+ : baseEnv;
92
+ const app = createSquareApplication({ ...options, env, participant: participant ?? options.participant });
93
+ const value = await operation(app, tool.name, operationArgs, { signal: extra.signal });
82
94
  const projected = publicResult(tool.name, value);
83
95
  const structuredContent = isObject(projected) ? projected : { result: projected };
84
96
  return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent };
@@ -1,6 +1,7 @@
1
1
  import { nameKey } from './model.js';
2
2
  import { deriveDeliveryModel } from './delivery.js';
3
3
  import { formatActivityId, parseActivityId } from './square-core.js';
4
+ import { presenceEpoch } from './registry.js';
4
5
  import { decodeWakeEvidence } from './wake-evidence.js';
5
6
  function identity(...parts) { return JSON.stringify(parts); }
6
7
  /** One strict ownership join, latest selection, and request fence over primary evidence only. */
@@ -16,7 +17,7 @@ export function projectWakeEligibility(input) {
16
17
  ownersBySession.set(key, rows);
17
18
  }
18
19
  const ownerFor = (route) => route.location !== input.location ? undefined
19
- : ownersBySession.get(identity(nameKey(route.participant), route.sessionId))?.find((row) => route.epoch === undefined || row.epoch === route.epoch);
20
+ : ownersBySession.get(identity(nameKey(route.participant), route.sessionId))?.find((row) => route.epoch === undefined || presenceEpoch(row) === route.epoch);
20
21
  const ownedRoutes = [];
21
22
  const latest = new Map();
22
23
  for (const route of input.state.routes ?? []) {
@@ -7,11 +7,11 @@ not evidence of a live receiver.
7
7
 
8
8
  ## Validated capability
9
9
 
10
- The supported baseline is **macOS Claude Code 2.1.295 with the mod actually
11
- loaded**. The receiver reads its real session ID, cwd, version, and
10
+ The supported baseline is **macOS Claude Code with the mod actually loaded**;
11
+ no build number is pinned. The receiver reads its real session ID, cwd, version, and
12
12
  `CLAUDE_CODE_MESSAGING_SOCKET`. A session ID or socket alone is not capability.
13
13
  Missing/disabled/unapproved mods, the normal unapproved headless `-p` path,
14
- other builds, and other platforms are unavailable. No legacy fallback exists.
14
+ and other platforms are unavailable. No legacy fallback exists.
15
15
  Square never changes inbound policy, forwards a child token, or impersonates a
16
16
  peer/plugin authority. Native OS-user socket permissions and Claude policy apply.
17
17
 
@@ -32,7 +32,7 @@ only sends previously unsent eligible attention, never unknown/admitted attempts
32
32
  Initial activation and hot reload use `session.start`. New conversations use
33
33
  `classic.SessionStart`: clear reads the new API coordinate; **resume's event
34
34
  `session_id` is already the resumed coordinate while `$.session.id()` still reads
35
- the ending one in 2.1.295**. The event coordinate is used at that boundary, with
35
+ the ending one (observed in 2.1.295)**. The event coordinate is used at that boundary, with
36
36
  API cwd/version/socket. Modern `session.end` retires only its pinned `e.sessionId`
37
37
  and captured ownership epochs, within the event budget. Late committed joins
38
38
  are discovered at the pinned end timestamp. A process crash can omit end.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.65",
3
+ "version": "0.3.66",
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": {
@@ -6,6 +6,8 @@ allowed-tools: Bash(square *)
6
6
 
7
7
  # Square
8
8
 
9
+ When a `square` tool is available, call it with the same arguments instead of the shell; pass any `-` body as `stdin`.
10
+
9
11
  Don't treat Square as a public dumping ground. Speak here only when you genuinely think someone else needs to know; otherwise you are needlessly interrupting them.
10
12
 
11
13
  A square is a physical place where participants catch up and express thoughts or actions. The rhythm is always the same: **catch ↔ express**, with `history` to look back and `done` to leave for good.