@astrosheep/square 0.4.7 → 0.4.9

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 (84) hide show
  1. package/dist/card-format.d.ts +2 -0
  2. package/dist/card-format.js +2 -0
  3. package/dist/claude-mcp-context.d.ts +0 -1
  4. package/dist/claude-mcp-context.js +0 -10
  5. package/dist/claude-mod.d.ts +2 -0
  6. package/dist/claude-mod.js +211 -0
  7. package/dist/cli/context.d.ts +1 -1
  8. package/dist/cli/context.js +1 -12
  9. package/dist/cli/express-presentation.js +8 -35
  10. package/dist/cli/json.js +1 -1
  11. package/dist/cli/observation-commands.d.ts +2 -3
  12. package/dist/cli/observation-commands.js +21 -59
  13. package/dist/cli/registry.js +3 -5
  14. package/dist/cli/square-commands.d.ts +1 -2
  15. package/dist/cli/square-commands.js +7 -17
  16. package/dist/decisions.js +15 -20
  17. package/dist/harness-claude.js +3 -3
  18. package/dist/help.js +6 -8
  19. package/dist/mcp.js +1 -1
  20. package/dist/neutral.d.ts +15 -1
  21. package/dist/notifications.js +1 -1
  22. package/dist/operation-presentation.d.ts +17 -15
  23. package/dist/operation-presentation.js +63 -37
  24. package/dist/packages/agent-delivery/src/index.d.ts +4 -9
  25. package/dist/packages/agent-delivery/src/index.js +0 -9
  26. package/dist/packages/agent-delivery/src/local-endpoint.d.ts +2 -12
  27. package/dist/packages/agent-delivery/src/local-endpoint.js +4 -48
  28. package/dist/packages/agent-delivery/src/pi-client.js +0 -2
  29. package/dist/packages/agent-delivery/src/types.d.ts +3 -37
  30. package/dist/plugins/paseo/shared/card-format.d.ts +6 -0
  31. package/dist/plugins/paseo/shared/card-format.js +38 -0
  32. package/dist/plugins/paseo/shared/card-presentation.d.ts +3 -1
  33. package/dist/plugins/paseo/shared/card-presentation.js +3 -1
  34. package/dist/presentation.d.ts +2 -20
  35. package/dist/presentation.js +11 -46
  36. package/dist/sqlite-square.js +5 -6
  37. package/dist/square-facade.d.ts +4 -13
  38. package/dist/square-posting.js +1 -1
  39. package/dist/square-tools.d.ts +0 -11
  40. package/dist/square-tools.js +34 -120
  41. package/dist/square-values.d.ts +1 -1
  42. package/dist/square-values.js +1 -1
  43. package/dist/time.d.ts +1 -0
  44. package/dist/time.js +10 -0
  45. package/dist/views.d.ts +1 -1
  46. package/dist/views.js +2 -2
  47. package/dist/wake-transports.d.ts +1 -1
  48. package/dist/wake-transports.js +1 -1
  49. package/guides/catch-cold-path-optimization.md +1 -1
  50. package/guides/claude-native.md +37 -7
  51. package/guides/delivery-primitives.md +2 -1
  52. package/guides/mcp.md +3 -3
  53. package/guides/pi-native-delivery.md +10 -2
  54. package/guides/pi-tools.md +1 -3
  55. package/guides/row-storage-design.md +1 -1
  56. package/guides/square-api.md +6 -0
  57. package/package.json +1 -1
  58. package/plugins/claude/.claude-plugin/plugin.json +3 -2
  59. package/plugins/claude/README.md +9 -0
  60. package/plugins/claude/hooks/hooks.json +1 -9
  61. package/plugins/claude/hooks/register.js +178 -0
  62. package/plugins/claude/skills/square/SKILL.md +4 -4
  63. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  64. package/plugins/paseo/OVERVIEW.md +1 -1
  65. package/plugins/paseo/README.md +20 -23
  66. package/plugins/paseo/client/markdown.tsx +88 -95
  67. package/plugins/paseo/client/palette.ts +37 -12
  68. package/plugins/paseo/client/square-message.tsx +52 -76
  69. package/plugins/paseo/client/transform.ts +4 -6
  70. package/plugins/paseo/shared/card-format.ts +37 -0
  71. package/plugins/paseo/shared/card-presentation.ts +6 -2
  72. package/plugins/paseo/shared/message.ts +1 -6
  73. package/plugins/pi/square-pi.js +69 -50
  74. package/skills/square/SKILL.md +4 -4
  75. package/dist/claude-boundary-state.d.ts +0 -20
  76. package/dist/claude-boundary-state.js +0 -84
  77. package/dist/claude-hook.d.ts +0 -2
  78. package/dist/claude-hook.js +0 -232
  79. package/dist/packages/agent-delivery/src/claude-auth.d.ts +0 -13
  80. package/dist/packages/agent-delivery/src/claude-auth.js +0 -75
  81. package/dist/packages/agent-delivery/src/claude-native.d.ts +0 -30
  82. package/dist/packages/agent-delivery/src/claude-native.js +0 -75
  83. package/dist/packages/agent-delivery/src/claude.d.ts +0 -5
  84. package/dist/packages/agent-delivery/src/claude.js +0 -66
@@ -1,95 +1,32 @@
1
- import crypto from 'node:crypto';
2
1
  import { z } from 'zod/v4';
3
2
  import { isSquareError, SquareError } from './model.js';
4
- import { requireOwner, requireJoinName, selectOwnSeat, HISTORY_MAX_LIMIT, PERCEIVE_DEFAULT_LIMIT, DEFAULT_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_DEPTH, MAX_LIST_LIMIT } from './square-api.js';
5
- import { parseTimeOrRelative } from './time.js';
3
+ import { requireOwner, requireJoinName, selectOwnSeat, HISTORY_MAX_LIMIT, PERCEIVE_MAX_LIMIT, PERCEIVE_DEFAULT_LIMIT, DEFAULT_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_DEPTH, MAX_LIST_LIMIT } from './square-api.js';
4
+ import { parseDurationMs, parseTimeOrRelative } from './time.js';
6
5
  import { renderPerceiveOutcome, renderExpression, renderFailureLines, renderHistoryResult, renderJoinResult, renderLifecycleResult, renderListenerChange, renderStatusResult, renderSquareList, } from './operation-presentation.js';
7
- /** Process-local recovery: FIFO 64, fixed two-hour TTL, consumed only by a successful express. */
8
- const EXPRESS_DRAFT_TTL_MS = 2 * 60 * 60 * 1000;
9
- const EXPRESS_DRAFT_LIMIT = 64;
10
- export class SquareExpressDrafts {
11
- drafts = new Map();
12
- purgeExpired() {
13
- const now = Date.now();
14
- for (const [token, draft] of this.drafts)
15
- if (draft.expiresAt <= now)
16
- this.drafts.delete(token);
17
- }
18
- resolve(args, owner) {
19
- if (args.draft === undefined)
20
- return { args };
21
- this.purgeExpired();
22
- if (typeof args.draft !== 'string' || Object.keys(args).length !== 1)
23
- throw new SquareError('invalid_args', 'An express draft token cannot be combined with other arguments.');
24
- const draft = this.drafts.get(args.draft);
25
- if (draft === undefined || draft.owner !== owner)
26
- throw new SquareError('invalid_args', 'Unknown, expired, or inaccessible express draft.');
27
- return { args: draft.args, token: args.draft };
28
- }
29
- consume(token) {
30
- if (token !== undefined)
31
- this.drafts.delete(token);
32
- }
33
- retain(args, owner, token, failure, expressAttempted) {
34
- const effect = failure.operation?.expressEffect;
35
- // After an attempt, only an admission or argument refusal proves that retrying cannot duplicate an activity.
36
- const unsent = !expressAttempted || effect === 'unsent' || (effect === undefined && failure.code === 'invalid_args');
37
- if (!unsent || effect === 'landed' || effect === 'unknown' || typeof args.body !== 'string' || args.draft !== undefined
38
- || typeof args.location !== 'string' || !args.location.trim())
39
- return undefined;
40
- this.purgeExpired();
41
- if (token !== undefined && this.drafts.has(token))
42
- return token;
43
- const created = `dr_${crypto.randomBytes(16).toString('base64url')}`;
44
- this.drafts.set(created, { owner, args, expiresAt: Date.now() + EXPRESS_DRAFT_TTL_MS });
45
- if (this.drafts.size > EXPRESS_DRAFT_LIMIT)
46
- this.drafts.delete(this.drafts.keys().next().value);
47
- return created;
48
- }
49
- }
50
6
  export function createSquareToolRunner() {
51
- const drafts = new SquareExpressDrafts();
52
7
  return async (tool, args, control, bind) => {
53
- let toolArgs = args;
54
- let owner;
55
- let token;
56
- let attempted = false;
57
8
  let square;
58
9
  try {
59
10
  const binding = await bind();
60
- owner = binding.identity.owner;
61
11
  if ('runDiscovery' in tool)
62
- return { text: await tool.runDiscovery(toolArgs, control, binding) };
63
- if (tool.name === 'express') {
64
- const resolved = drafts.resolve(toolArgs, owner);
65
- toolArgs = resolved.args;
66
- token = resolved.token;
67
- }
68
- square = await binding.open(toolArgs.location, control);
69
- let text;
12
+ return { text: await tool.runDiscovery(args, control, binding) };
13
+ square = await binding.open(args.location, control);
70
14
  try {
71
- attempted = tool.name === 'express';
72
- text = await tool.run(square, toolArgs, control, binding.identity, binding.now);
73
- drafts.consume(token);
15
+ return { text: await tool.run(square, args, control, binding.identity, binding.now) };
74
16
  }
75
17
  finally {
76
18
  await square.close();
77
19
  }
78
- return { text };
79
20
  }
80
21
  catch (error) {
81
- const failure = squareToolFailure(error, control.signal);
82
- const draft = tool.name === 'express' ? drafts.retain(toolArgs, owner, token, failure, attempted) : undefined;
83
- return { isError: true, text: squareToolFailureText(failure, {
84
- location: square?.location ?? toolArgs.location,
85
- participant: typeof toolArgs.as === 'string' ? toolArgs.as : undefined,
86
- draft,
22
+ return { isError: true, text: squareToolFailureText(squareToolFailure(error, control.signal), {
23
+ location: square?.location ?? args.location,
24
+ participant: typeof args.as === 'string' ? args.as : undefined,
87
25
  }) };
88
26
  }
89
27
  };
90
28
  }
91
- const location = z.string().describe('Which square: a .square file path. Relative paths start from the working directory.');
92
- const expressLocation = z.string().optional().describe('Which square: a .square file path. Relative paths start from the working directory. Required unless retrying with a draft token.');
29
+ const location = z.string().describe('Path to the .square file, relative to the working directory.');
93
30
  const participant = z.string().optional().describe('Your name in the square. Leave it out if you hold exactly one name here.');
94
31
  const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
95
32
  const stringArray = z.array(z.string());
@@ -98,7 +35,7 @@ function rendering(render) {
98
35
  return (result, args) => render(result, args);
99
36
  }
100
37
  function tool(name, description, shape, run, render) {
101
- const inputSchema = z.object({ ...shape, location: name === 'express' ? expressLocation : location }).strict();
38
+ const inputSchema = z.object({ ...shape, location }).strict();
102
39
  return { name, description, inputSchema,
103
40
  run: async (square, args, control, identity, now) => {
104
41
  const { location: _location, ...operationArgs } = inputSchema.parse(args);
@@ -115,21 +52,15 @@ export function squareToolsEnabled(env = process.env) {
115
52
  throw new SquareError('invalid_args', 'Invalid SQUARE_TOOLS: expected 0, 1, true, false, or none.');
116
53
  }
117
54
  export const SQUARE_TOOL_INSTRUCTIONS = 'List nearby squares and your joined names with list. Pass location for a single square. Join once. Then express to speak, and perceive to take in what reached you. History looks back without taking anything in.';
118
- function historyToolQuery({ grep, fixed, before, after, since, context, contextBefore, contextAfter, noTruncate: _noTruncate, ...query }, now = Date.now()) {
119
- if (grep !== undefined && fixed !== undefined)
120
- throw new SquareError('invalid_args', 'Use grep or fixed, not both.');
55
+ function historyToolQuery({ search, regex, before, after, since, full: _full, ...query }, now = Date.now()) {
121
56
  if (before !== undefined && after !== undefined)
122
57
  throw new SquareError('invalid_args', 'Use before or after, not both.');
123
58
  const sinceMs = since === undefined ? undefined : parseTimeOrRelative(since, now);
124
59
  if (sinceMs !== undefined && !Number.isFinite(sinceMs))
125
60
  throw new SquareError('invalid_args', `Invalid since timestamp: ${since}`);
126
- const resolvedBefore = contextBefore ?? context;
127
- const resolvedAfter = contextAfter ?? context;
128
61
  return { ...query,
129
62
  ...(sinceMs === undefined ? {} : { since: sinceMs }),
130
- ...(resolvedBefore === undefined ? {} : { contextBefore: resolvedBefore }),
131
- ...(resolvedAfter === undefined ? {} : { contextAfter: resolvedAfter }),
132
- search: grep !== undefined ? { text: grep, mode: 'regex' } : fixed === undefined ? undefined : { text: fixed, mode: 'literal' },
63
+ search: search === undefined ? undefined : { text: search, mode: regex ? 'regex' : 'literal' },
133
64
  cursor: after !== undefined ? { id: after, direction: 'after' } : before === undefined ? undefined : { id: before, direction: 'before' },
134
65
  };
135
66
  }
@@ -150,66 +81,57 @@ export const SQUARE_TOOLS = [
150
81
  },
151
82
  },
152
83
  tool('join', 'Step into the square under your name. Do this once before you speak or perceive. You arrive seeing what was said most recently.', {
153
- as: z.string().optional().describe('Your name in the square. Give it explicitly when joining.'),
84
+ as: z.string().describe('Your name in the square.'),
154
85
  takeover: z.boolean().optional().describe('Claim this name even though another session holds it now. That session loses it.'),
155
- last: z.number().int().min(1).max(HISTORY_MAX_LIMIT).optional().describe('How many recent public activities to show on arrival (default 10, up to 100).'),
86
+ last: z.number().int().min(1).max(HISTORY_MAX_LIMIT).optional().describe('How many recent public activities to show on arrival (default 10).'),
156
87
  }, (square, { as, takeover, last }, control, identity) => square.join(requireJoinName(as), { ...identity, owner: requireOwner(identity.owner), takeover, last, observe: true }, control), rendering((result) => renderJoinResult(result, undefined, 'tool'))),
157
88
  tool('express', 'Speak or act in the square. Plain words, half-formed ideas and *actions* all belong here. Mention or reply to someone to be heard by them; ring the bell to be heard by everyone. Said to no one in particular, your words reach those who listen to you.', {
158
89
  as: participant,
159
- body: z.string().optional().describe('What you say or do. Markdown works; *asterisks* mark an action. Required unless retrying with a draft token.'),
160
- draft: z.string().optional().describe('Retry a failed express kept in this process. Pass only the token; the original body and options are restored.'),
161
- mentions: stringArray.optional().describe('Who you are speaking to. An @name in the body that matches someone here counts too.'),
162
- reach: z.enum(['bell']).optional().describe('"bell" calls everyone in the square. Cannot be used with mentions.'),
90
+ body: z.string().describe('What you say or do. Markdown works; *asterisks* mark an action.'),
91
+ mentions: stringArray.optional().describe('Who you are speaking to. An @name in the body that matches someone here counts too. Absent targets do not block the activity; you are told who was not reached.'),
92
+ bell: z.boolean().optional().describe('Call everyone in the square. Not with mentions.'),
163
93
  reply: activityId.optional().describe('The activity you are answering, like act/12. Its author hears you if still here.'),
164
94
  noMention: z.boolean().optional().describe('Leave @names in the body as plain text. Mentions and reply still reach people.'),
165
95
  force: z.boolean().optional().describe('Speak now, even if something reached you that you have not perceived yet.'),
166
96
  noWait: z.boolean().optional().describe('If you are speaking too fast, return at once instead of waiting.'),
167
- }, async (square, { body, draft, as, ...options }, control, identity) => {
168
- if (draft !== undefined)
169
- throw new SquareError('invalid_args', 'Unknown or expired express draft.');
170
- if (body === undefined)
171
- throw new SquareError('invalid_args', 'Express needs a body or a draft token.');
172
- return (await selectOwnSeat(square, identity.owner, as)).express(body, { ...options, observe: true }, control);
173
- }, rendering((result) => renderExpression(result.observation?.location ?? '', result, undefined, 'tool'))),
97
+ }, async (square, { body, bell, as, ...options }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).express(body, { ...options, ...(bell ? { reach: 'bell' } : {}), observe: true }, control), rendering((result) => renderExpression(result.observation?.location ?? '', result, undefined, 'tool'))),
174
98
  tool('perceive', 'Take in what reached you since you last looked: mentions, replies, bells, and words from those you listen to. What you perceive no longer waits for you; history lets you look back.', {
175
99
  as: participant,
176
- id: activityId.optional().describe('Take in this activity in full, like act/12, even if you perceived it before. Do not combine with idle, from, mention, limit or takeover.'),
177
- idle: z.number().finite().min(0).optional().describe('When nothing is waiting, wait until something arrives or until it has been quiet this many milliseconds. Leave it out to return at once.'),
100
+ id: activityId.optional().describe('Open this one activity in full, like act/12. Use it alone.'),
101
+ idle: z.string().optional().describe('When nothing is waiting, wait for activity or this much quiet, like "30s". Leave it out to return at once.'),
178
102
  from: stringArray.optional().describe('Only what these participants said.'),
179
103
  mention: z.boolean().optional().describe('Only what was addressed to you, and bells.'),
180
- limit: z.number().int().min(1).optional().describe('At most this many at once (default 10, up to 100). The rest keeps waiting.'),
104
+ limit: z.number().int().min(1).max(PERCEIVE_MAX_LIMIT).optional().describe('At most this many at once (default 10). The rest keeps waiting.'),
181
105
  takeover: z.boolean().optional().describe('Take over from another active idle perceive for this participant.'),
182
- }, async (square, { as, ...options }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).perceive({ ...options, observe: true }, control), rendering((result, args) => {
106
+ }, async (square, { as, idle, ...args }, control, identity) => {
107
+ const options = { ...args, ...(idle === undefined ? {} : { idle: parseDurationMs(idle, 'idle') }) };
108
+ return { ...(await (await selectOwnSeat(square, identity.owner, as)).perceive({ ...options, observe: true }, control)), options };
109
+ }, rendering((result) => {
183
110
  // The CLI always spells the default cap in its continuation, so the tool does too.
184
- const options = args;
111
+ const options = result.options;
185
112
  const renderOptions = options.id === undefined ? { ...options, limit: options.limit ?? PERCEIVE_DEFAULT_LIMIT } : options;
186
113
  return renderPerceiveOutcome(result.observation?.location ?? '', result, renderOptions, undefined, 'tool').stdout;
187
114
  })),
188
115
  tool('history', 'Look back at what was said. Reading here takes nothing in: whatever waits for perceive keeps waiting. Page with before or after.', {
189
- limit: z.number().int().min(1).optional().describe('How many per page (default 10, up to 100).'),
116
+ limit: z.number().int().min(1).max(HISTORY_MAX_LIMIT).optional().describe('How many per page (default 10).'),
190
117
  order: z.enum(['asc', 'desc']).optional().describe('"asc" for oldest first (default), "desc" for newest first.'),
191
118
  before: activityId.optional().describe('The page just before this activity, like act/12.'),
192
119
  after: activityId.optional().describe('The page just after this activity, like act/12.'),
193
120
  at: z.array(activityId).min(1).optional().describe('Center on these activity ids, like ["act/12"].'),
194
- context: z.number().int().min(0).optional().describe('Activities of context before and after each "at" activity.'),
195
- contextBefore: z.number().int().min(0).optional().describe('Activities of context before each "at" activity.'),
196
- contextAfter: z.number().int().min(0).optional().describe('Activities of context after each "at" activity.'),
121
+ context: z.number().int().min(0).max(HISTORY_MAX_LIMIT).optional().describe('Activities of context before and after each "at" activity.'),
197
122
  since: z.string().optional().describe('Only activities after this ISO timestamp or relative duration (like "10m").'),
198
- grep: z.string().optional().describe('Only activities matching this regular expression in their names, ids or words. Use grep or fixed, not both.'),
199
- fixed: z.string().optional().describe('Only activities containing this exact text.'),
123
+ search: z.string().optional().describe('Only activities containing this text in their names, ids or words.'),
124
+ regex: z.boolean().optional().describe('Treat search as a regular expression.'),
200
125
  from: stringArray.optional().describe('Only what these participants said.'),
201
126
  mention: z.string().optional().describe('Only what was addressed to this participant.'),
202
- noTruncate: z.boolean().optional().describe('Show full activity bodies even when multiple activities are returned.'),
127
+ full: z.boolean().optional().describe('Show full activity bodies even when multiple activities are returned.'),
203
128
  }, async (square, args, control, _identity, now) => {
204
129
  const query = historyToolQuery(args, now?.());
205
130
  return { ...(await square.history({ ...query, observe: true }, control)), query };
206
- }, rendering((result, args) => renderHistoryResult(result.observation?.location ?? '', result, { query: result.query, noTruncate: args.noTruncate === true, surface: 'tool' }))),
131
+ }, rendering((result, args) => renderHistoryResult(result.observation?.location ?? '', result, { query: result.query, full: args.full === true, surface: 'tool' }))),
207
132
  tool('attention', 'Turn toward someone or away from them, from now on. Listen: hear everything they say, even to others. Unlisten: stop listening without clearing an ignore; mentions and replies still reach you unless ignored. Ignore: hear nothing from them but bells. What was already said stays as it was.', {
208
133
  as: participant, action: z.enum(['listen', 'unlisten', 'ignore']).describe('"listen", "unlisten" or "ignore". Listening again ends an ignore.'), target,
209
- }, async (square, { as, action, target }, control, identity) => {
210
- const self = await selectOwnSeat(square, identity.owner, as);
211
- return self[action](target, { observe: true }, control);
212
- }, rendering((result, args) => renderListenerChange(result.observation?.location ?? '', args.action, result))),
134
+ }, async (square, { as, action, target }, control, identity) => (await selectOwnSeat(square, identity.owner, as))[action](target, { observe: true }, control), rendering((result, args) => renderListenerChange(result.observation?.location ?? '', args.action, result))),
213
135
  tool('done', 'Step out of the square. Everyone sees you leave, and you stop listening to anyone. Leaving carries no words, so say goodbye with express first. Stay instead if someone may still need you; you can join again later.', { as: participant }, async (square, { as }, control, identity) => (await selectOwnSeat(square, identity.owner, as)).done(undefined, { observe: true }, control), rendering((result) => renderLifecycleResult(result.observation?.location ?? '', result))),
214
136
  tool('status', 'Look around the square: who is here, the latest activity, and who you listen to.', {
215
137
  as: z.string().optional().describe('Whose listening to show. Leave it out if you hold exactly one name here.'),
@@ -231,13 +153,5 @@ export function squareToolFailure(error, signal) {
231
153
  }
232
154
  /** Readable failure text with tool-native recovery actions, never raw JSON. */
233
155
  export function squareToolFailureText(failure, context = {}) {
234
- const report = failure;
235
- const lines = renderFailureLines(report, { squarePath: context.location, name: context.participant, surface: 'tool' });
236
- if (context.draft !== undefined) {
237
- lines.push(`· draft kept in memory: ${context.draft}`);
238
- lines.push(report.code === 'behind'
239
- ? '· perceive the pending activity, then retry express with only:'
240
- : '· correct the problem above, then retry express with only:', `{ "draft": "${context.draft}" }`);
241
- }
242
- return lines.join('\n');
156
+ return renderFailureLines(failure, { squarePath: context.location, name: context.participant, surface: 'tool' }).join('\n');
243
157
  }
@@ -1,4 +1,4 @@
1
- export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "claude-native", "paseo", "http"];
1
+ export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue", "paseo", "http"];
2
2
  export type WakeRouteKind = typeof WAKE_ROUTE_KINDS[number];
3
3
  export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
4
4
  readonly [key: string]: JsonValue;
@@ -1,5 +1,5 @@
1
1
  import { SquareError, nameKey } from './neutral.js';
2
- export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'claude-native', 'paseo', 'http'];
2
+ export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-queue', 'paseo', 'http'];
3
3
  export const MESSAGE_METADATA_MAX_BYTES = 16 * 1024;
4
4
  export const MESSAGE_METADATA_MAX_DEPTH = 256;
5
5
  /** Validate the bounded, lossless JSON object carried by a message. */
package/dist/time.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function parseDurationMs(value: string, label: string): number;
1
2
  export declare function formatTimestamp(at: number): string;
2
3
  export declare function formatDuration(ms: number | undefined): string;
3
4
  export declare function formatRelativeTime(at: number, now?: number): string;
package/dist/time.js CHANGED
@@ -1,3 +1,13 @@
1
+ import { SquareError } from './model.js';
2
+ export function parseDurationMs(value, label) {
3
+ const match = value.match(/^([1-9]\d*)(ms|s|m|h)$/);
4
+ if (!match)
5
+ throw new SquareError('invalid_args', `✕ invalid ${label}: expected a positive duration with unit ms, s, m, or h (for example 500ms, 30s, 3m, 1h)`);
6
+ const milliseconds = Number(match[1]) * ({ ms: 1, s: 1000, m: 60000, h: 3600000 }[match[2]]);
7
+ if (!Number.isSafeInteger(milliseconds))
8
+ throw new SquareError('invalid_args', `✕ invalid ${label}: duration is too large`);
9
+ return milliseconds;
10
+ }
1
11
  function pad(value, length = 2) {
2
12
  return String(value).padStart(length, '0');
3
13
  }
package/dist/views.d.ts CHANGED
@@ -19,7 +19,7 @@ export declare function selectHistory(facts: SquareRead, query: HistoryQuery, li
19
19
  export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
20
20
  export declare function recoveryObservation(facts: SquareRead, name: string, now: number): {
21
21
  participant: string;
22
- activitySummaries: import("./square-facade.js").UnreadActivitySummary[];
22
+ activitySummaries: import("./neutral.js").UnreadActivitySummary[];
23
23
  standingCount: number;
24
24
  ownActivityCount: number;
25
25
  hardCap: import("./model.js").HardCap;
package/dist/views.js CHANGED
@@ -35,14 +35,14 @@ export function parseRequiredActivityId(id) {
35
35
  }
36
36
  /** History adds archive context and ordering to the shared storage-side query predicates. */
37
37
  export function selectHistory(facts, query, limit, kinds, presentation = true) {
38
- const shared = activityQueryFilter(query, ['observe', 'order', 'mention', 'since', 'contextBefore', 'contextAfter']);
38
+ const shared = activityQueryFilter(query, ['observe', 'order', 'mention', 'since', 'context']);
39
39
  const { afterIndex } = shared;
40
40
  const recipient = query.mention == null ? undefined : resolveKnownName(facts, query.mention);
41
41
  if (limit !== null && (!Number.isSafeInteger(limit) || limit < 1))
42
42
  throw new SquareError('invalid_args', 'History limit must be a positive integer');
43
43
  const filter = {
44
44
  ...shared, afterAt: query.since,
45
- ...(shared.indexes === undefined ? {} : { indexes: facts.contextIndexes(shared.indexes, query.contextBefore ?? 0, query.contextAfter ?? 0) }),
45
+ ...(shared.indexes === undefined ? {} : { indexes: facts.contextIndexes(shared.indexes, query.context ?? 0, query.context ?? 0) }),
46
46
  kinds: query.search === undefined ? kinds : ['say', 'done'],
47
47
  };
48
48
  let selected, totalMatches, publicBefore = false, publicAfter = false;
@@ -1,4 +1,4 @@
1
1
  import type { DeliveryTransport } from './delivery-ledger.js';
2
2
  export type WakeTransports = readonly DeliveryTransport[];
3
- /** Claude now receives only at native hooks; producers never target a socket. */
3
+ /** Native receivers own their effects; producers never target Claude's private helper. */
4
4
  export declare function createDefaultDeliveryTransports(): Promise<WakeTransports>;
@@ -1,4 +1,4 @@
1
- /** Claude now receives only at native hooks; producers never target a socket. */
1
+ /** Native receivers own their effects; producers never target Claude's private helper. */
2
2
  export async function createDefaultDeliveryTransports() {
3
3
  const [{ HttpWakeTransport }, { PaseoWakeTransport }] = await Promise.all([
4
4
  import('./http-delivery.js'),
@@ -9,7 +9,7 @@ The comparison is against `90752ba`, the current production implementation plus
9
9
  ## Two changes
10
10
 
11
11
  - `model.ts`: try the existing Unicode text-name grammar before the full RGI emoji grammar. It accepts letters, numbers, marks excluding variation selectors, hyphens, underscores, and nonempty slash-separated segments. It is an exact subset of the original grammar; the original full matcher remains the fallback. `NAME_GRAPHEME_SOURCE`, also used by mention scanning, retains its original value.
12
- - `participant-host.ts`: before discovering installed adapters, use the existing route resolver to check whether any candidate exists. No host ledger or no possible candidate means no route, without importing adapters that cannot be used. Real candidates still pass through the real capability check. These measurements predate the [native-first route policy](claude-native.md#native-first-routing-and-policy-holds); the optimization itself did not change route priority, harness identity, or publication failure isolation.
12
+ - `participant-host.ts`: before discovering installed adapters, use the existing route resolver to check whether any candidate exists. No host ledger or no possible candidate means no route, without importing adapters that cannot be used. Real candidates still pass through the real capability check. These historical measurements do not describe [current Claude mod delivery](claude-native.md); the optimization itself did not change route priority, harness identity, or publication failure isolation.
13
13
 
14
14
  CPU sampling of the baseline pointed to cold name validation inside reader transactions and optional adapter module loading during catch. The changes remove that work rather than prewarm it or move it outside measurement. The sampling and this combined experiment do not establish each change's individual contribution.
15
15
 
@@ -1,13 +1,43 @@
1
- # Claude Code delivery
1
+ # Claude mod delivery
2
2
 
3
- The Square Claude plugin uses ordinary native command hooks. It has no mod, native inbox, or producer socket route. `SessionStart` attaches the exact native session to the host delivery ledger; `SessionEnd` fences and detaches that captured attachment without changing Square membership.
3
+ Square's Claude plugin uses a native JavaScript mod, not command hooks or an async Stop observer. The mod owns native lifecycle and effects. One `square claude-mod` helper per loaded session owns Node/SQLite access and the receiver-owned `claude-mod` ledger route. Producers only post neutral ledger cards; they never contact the helper directly.
4
4
 
5
- At `UserPromptSubmit`, Square records Claude's native `prompt_id` before presenting pending cards through event-correct `additionalContext`. `PostToolUse` is the single tool-boundary presenter for the exact native main/child owner, so cards arriving during a running turn can be delivered after a successful tool completes. `PreToolUse` and `PostToolUseFailure` match only Square MCP tools and handle identity publication/cleanup, not presentation. A failed ordinary tool leaves cards for the next PostToolUse, UserPromptSubmit or normal Stop. Autonomous hook-generated prompts are boundaries too, not evidence of human approval. A later prompt records presentation only when its input contains the exact complete prepared frame, including the ledger token and stored full-frame hash. That establishes observation before the model processes the prompt, not comprehension. The async frame uses a plain warning and token delimiters; Square never decodes entities or accepts changed/truncated text as an exact observation.
5
+ ## Busy context and idle wake
6
6
 
7
- At a normal `Stop`, one per-owner OS-locked observer waits for ledger changes for at most 86,400 seconds. It rechecks the exact captured `prompt_id`, attachment generation, Stop epoch and route after acquiring the lock and immediately before output. A new prompt disarms old observers. The watcher claims one card at a time and uses the existing handoff token/hash correlation. Invoked stderr output is `unknown` until that exact frame is observed at a later prompt boundary; there is no automatic fallback replay. Missing prompt IDs fail closed.
7
+ During a running turn, the mod calls `$.session.append` with the complete delivery frame, then `$.ui.log` with the readable card. Append adds model context without interrupting the running turn; the separate log makes the notification visible in Claude's native UI. Neither replaces a tool result. A busy notification is never also submitted as a new prompt.
8
8
 
9
- This is a finite, boundary-driven observer, not a promise of continuous coverage. Missing Stop, timeout, process exit, headless teardown, or a busy Escape with no later normal Stop may leave cards pending. Idle Escape has no classic hook and may not disarm an already waiting observer; already emitted asyncRewake notifications cannot be recalled. No Square inbox fallback is retained for these cases.
9
+ When the main session is idle, the mod instead calls `$.prompt.submit({ text })`. It keeps native plugin attribution: it does not set `asUser`, impersonate human input, or emit a duplicate UI log. Normal completion returns the receiver to idle so later cards can wake it again. A child receives append only while running, addressed by its native `agentId`; Square never wakes the main session on behalf of an idle child. Child completion detaches its receiver.
10
10
 
11
- The bundled MCP server gets call-local session, cwd, and agent attribution from native `PreToolUse`/terminal tool hooks keyed by Claude's `claudecode/toolUseId` metadata. The manifest scopes `PreToolUse` and `PostToolUseFailure` to `^mcp__plugin_square_square__.*`, while `PostToolUse` remains unfiltered for delivery and successful MCP cleanup. An ordinary successful tool therefore launches only PostToolUse, not both a pre- and post-tool command. Duplicate or incomplete call identity is denied by the event-specific PreToolUse permission response; unrelated tool hooks do not block. Cleanup matches tool-use ID, session and agent. Interrupted calls without a terminal hook expire after ten minutes. Models cannot supply identity as tool arguments.
11
+ The current Paseo Claude provider does not expose native `ui_log` events in its conversation UI. Emitting a log is not evidence that Paseo displayed it, and the optional Square Paseo renderer does not fix that provider boundary. This change does not modify Paseo.
12
12
 
13
- The installed and tested native runtime evidence is macOS Claude Code 2.1.299 with isolated HOME, plugin settings, delivery ledger and a dummy-key loopback provider. This does not certify other platforms, versions, or headless behavior. The final native prompt/generation check and async hook completion are not atomic. `node --import ./test/sandbox-env.js test/claude-hook-native.mjs` with `SQUARE_CLAUDE_HOOK_NATIVE_SMOKE=1` runs the shipped manifest and candidate CLI through a scratch PATH shim and preserves hook/provider evidence in `/tmp`. It proves repeated idle wake, prompt/attachment fencing, duplicate suppression, active prompt/tool context, native main/child MCP attribution and identity-failure denial.
13
+ ## Evidence, cancellation and lifetime
14
+
15
+ Each handoff prepares a unique token and SHA-256 hash of the exact frame. A private loopback RPC rechecks generation and custody immediately before the native effect. An append receipt requires the native returned message to contain exactly that text and a native message UUID. A submit receipt requires the returned submitted text to match exactly; rewritten text records acceptance only. These are native acceptance/presentation facts, not proof of a later provider request, model comprehension or human acknowledgement. UI logs alone never settle a delivery.
16
+
17
+ There is no atomic transaction spanning the ledger and Claude. Once an effect may have happened, a lost or changed result is uncertain and held: no automatic replay, append-to-submit retry, or fallback wake through another host. Definitive refusal creates no presentation receipt. Diagnostics and `square harness doctor delivery` distinguish pending, held and presented evidence.
18
+
19
+ Stop cancels the current pending batch and fences queued native effects. A stopped receiver does not wake again until new native input starts a turn; later input does not replay the cancelled batch. Already appended or admitted native input cannot be recalled. Cancellation is local host suppression, not consumption of Square history.
20
+
21
+ `session.end` retires the captured helper and generation-fenced attachment. `/clear` and `/resume` can end the old session without another `session.start`; the next native prompt lazily starts the helper for the current session ID. Reloading the mod starts a fresh helper generation, which fences older receivers. A hard process kill can prevent graceful detach; a subsequent attachment fences the old generation, but installation alone does not prove that any running host loaded the mod.
22
+
23
+ The helper emits its randomly authenticated loopback address only to the owning mod over its private process stream. That address is not a producer endpoint, shared inbox, or public configuration. No external callback service is required.
24
+
25
+ ## MCP call identity
26
+
27
+ The bundled MCP server gets session, cwd and subagent attribution from the mod's native `tool.call` wrapper, matched only to Square MCP tools. Before calling the real tool, it captures the native `tool_use_id`, session and `agentId` in the call-local association keyed by Claude's `claudecode/toolUseId` metadata. A `finally` block removes only that exact association after the tool finishes, including failure. Calls interrupted before cleanup expire after ten minutes.
28
+
29
+ Missing, duplicate, expired or mismatched identity fails closed before the MCP server runs. Models cannot supply identity as tool arguments. Main and child identities stay separate even when their calls overlap; no ordinary non-Square tool launches an identity command.
30
+
31
+ ## Tested runtime and verification
32
+
33
+ macOS Claude Code **2.1.319** was tested with the shipped plugin, candidate CLI, isolated HOME/configuration/ledger, and a dummy-key loopback model provider. The real headless runtime proves repeated idle wake, duplicate suppression, busy append reaching the next model request, native UI-log emission, Stop suppression, exact receipts and detach. A real interactive TUI fixture proves main/child MCP attribution, collision denial, tool-context delivery and exact call cleanup. This does not certify older Claude releases, other platforms, or Paseo display.
34
+
35
+ After `npm run build`, run both native fixtures with:
36
+
37
+ ```bash
38
+ SQUARE_CLAUDE_MOD_NATIVE_SMOKE=1 \
39
+ SQUARE_CLAUDE_BIN=/path/to/claude \
40
+ node --import ./test/sandbox-env.js test/claude-mod-native.mjs
41
+ ```
42
+
43
+ Evidence stays in the printed `/tmp/square-claude-…` directories. The headless fixture deliberately interrupts a running tool; Claude may finish with exit 1 and `aborted_tools`, which is checked as the expected interruption rather than treated as an ordinary successful generation. Neither fixture uses external model credentials or changes an installed plugin or running host.
@@ -5,7 +5,8 @@ package root. The per-user ledger defaults to `~/.square/delivery.v3.sqlite` and
5
5
  finished text cards, session attachment, routes, attempts and receipts. Producers own content and
6
6
  recipient selection; adapters own transport behavior. Routes contain only `{kind, address}`.
7
7
  A card may also carry producer-neutral presentation `details`:
8
- `{kind: 'presentation', origin, author?, ref?, badge?, body}`. `text` remains the bounded model-facing
8
+ `{kind: 'presentation', origin, author?, ref?, at, reach, body}`. `at` is the landing time in epoch
9
+ milliseconds; `reach` is `mention`, `reply`, `attention`, or `bell`. `text` remains the bounded model-facing
9
10
  card; `body` holds the full Markdown for display. Receivers validate these details and render them
10
11
  without opening producer storage. Cards without valid presentation details remain plain text.
11
12
 
package/guides/mcp.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Square provides a stdio MCP server through `square mcp-server`.
4
4
 
5
- Pass `location` explicitly on each single-square tool call. Relative paths use the server's startup working directory. `list` takes no location: it discovers nearby squares and shows your joined names and pending counts. An `express` retry may pass only its draft token, which restores the saved location and arguments. Join with an explicit `as` name. Later calls may omit `as` only when the artifact recognizes exactly one current name held by the call's exact opaque owner; zero or multiple matches refuse and require explicit selection. An explicit name selects a voice but never grants custody. Request metadata supplies the live Codex (`threadId`) or OpenCode (`ai.opencode/sessionID`) coordinate when present, encoded through `harnessOwner`; otherwise the server-configured owner applies. CLI startup resolves `--owner`/`SQUARE_OWNER`, a complete harness coordinate, or the local user owner in that order, refusing incomplete harness identity. Owner is never a model tool argument. Read-only calls do not need an owner.
5
+ Pass `location` explicitly on each single-square tool call. Relative paths use the server's startup working directory. `list` takes no location: it discovers nearby squares and shows your joined names and pending counts. Join with an explicit `as` name. Later calls may omit `as` only when the artifact recognizes exactly one current name held by the call's exact opaque owner; zero or multiple matches refuse and require explicit selection. An explicit name selects a voice but never grants custody. Request metadata supplies the live Codex (`threadId`) or OpenCode (`ai.opencode/sessionID`) coordinate when present, encoded through `harnessOwner`; otherwise the server-configured owner applies. CLI startup resolves `--owner`/`SQUARE_OWNER`, a complete harness coordinate, or the local user owner in that order, refusing incomplete harness identity. Owner is never a model tool argument. Read-only calls do not need an owner.
6
6
 
7
- The server exposes `list`, `join`, `express`, `perceive`, `history`, `attention`, `done`, and `status`. `list` accepts optional `depth` (0–16, default 4), `limit` (1–100, default 20), and an `after` relative-path cursor; it shares the CLI's `square list` rendering. `attention` takes `action: listen | unlisten | ignore`; `status` also reports who you listen to when a name resolves. `perceive` consumes activities. `history` is read-only and accepts stable `act/<index>` cursors. All eight tools return the same human-readable text as the CLI in `content`, without `outputSchema` or `structuredContent`. Square failures set `isError: true` and return readable refusal and recovery text in `content`. Clients keep their native MCP result envelope; machine-readable domain results remain available through the SDK.
7
+ The server exposes `list`, `join`, `express`, `perceive`, `history`, `listen`, `unlisten`, `ignore`, `done`, and `status`. `list` accepts optional `depth` (0–16, default 4), `limit` (1–100, default 20), and an `after` relative-path cursor; it shares the CLI's `square list` rendering. `listen`, `unlisten` and `ignore` take `as`, `location` and `target`; `status` reports who you listen to. `perceive` consumes activities, with an optional `idle` duration such as `30s`. `history` is read-only and accepts stable `act/<index>` cursors, symmetric `context`, literal `search` with optional `regex`, and `full` bodies. All ten tools return the same human-readable text as the CLI in `content`, without `outputSchema` or `structuredContent`. Square failures set `isError: true` and return readable refusal and recovery text in `content`. Clients keep their native MCP result envelope; machine-readable domain results remain available through the SDK.
8
8
 
9
- Claude's bundled plugin configures this server automatically. Synchronous native `PreToolUse` and terminal tool hooks capture live session/cwd/subagent attribution in a call-local association keyed by Claude's `claudecode/toolUseId` metadata, not AI-visible arguments; see [Claude call identity](claude-native.md). Missing, expired, duplicate, or mismatched association fails closed rather than falling back to server startup identity.
9
+ Claude's bundled plugin configures this server automatically. Its native mod wraps Square's `tool.call` events to capture live session/cwd/subagent attribution in a call-local association keyed by Claude's `claudecode/toolUseId` metadata, not AI-visible arguments. The wrapper cleans the exact association in `finally`; see [Claude call identity](claude-native.md). Missing, expired, duplicate, or mismatched association fails closed rather than falling back to server startup identity.
10
10
 
11
11
  Tool definitions and argument-to-`Square`/`Participant` calls live in `square-tools.ts`, shared with the [Pi native tools](pi-tools.md). Domain receipts stay internal to these adapters; tools return only their rendered text. All callers use the [same concrete resource API](square-api.md), with no alternate factory or generic outcome envelope. `express.noMention` disables body @name scanning without disabling explicit mentions or reply attention.
12
12
 
@@ -7,11 +7,19 @@ presentation receipt and must not be blindly replayed. The boundary's bounded ba
7
7
  receipts only for presented cards. Square folds a receipt into read state only when its activity card
8
8
  contained the full body; shortened cards point to perceive.
9
9
 
10
- Display items come directly from validated producer-neutral card details (`origin`, optional `author`,
11
- `ref` and `badge`, and full Markdown `body`). Display never opens Square history. Missing or invalid
10
+ Display items come directly from validated producer-neutral card details (`origin`, optional `author`
11
+ and `ref`, landing time `at`, `reach`, and full Markdown `body`). Display never opens Square history. Missing or invalid
12
12
  details fall back to card text. Model-facing reminder text is unchanged, and a full display body does
13
13
  not make a shortened model-facing card eligible to consume producer unread activity.
14
14
 
15
+ The human card keeps delivery order, oldest first. Both states share the same Markdown wrapping,
16
+ inline author/reach and right-aligned local time column. Collapsing keeps four non-blank rendered body
17
+ lines and skips blank rows; hidden non-blank lines add an omission mark and an exact `+N lines` footer.
18
+ Expansion preserves the original blank rows, without message separators or a footer. Direct reaches use warning-colored labels and guides.
19
+ The header merges only contiguous activity references within one origin. Time, reach and reference
20
+ formatting is shared with Paseo. Every final line is bounded by the terminal width, including legacy
21
+ Markdown fallback and very narrow terminals.
22
+
15
23
  Pi session start and shutdown attach and detach the ledger session, not Square seats. Interactive
16
24
  cancellation suppresses outstanding local deliveries without marking activity seen; activity after
17
25
  cancellation can notify normally. A send already past its final check cannot be recalled.
@@ -17,9 +17,7 @@ text(result); // CLI text: confirmation and bounded unread previews
17
17
  `location` is required on each single-square call; relative paths use the current call's cwd.
18
18
  `square_list` takes no location and discovers nearby squares, showing your joined names and pending
19
19
  counts. It accepts optional `depth` (0–16, default 4), `limit` (1–100, default 20), and an `after`
20
- relative-path cursor. An `express` retry may pass only its draft token to restore the saved location
21
- and arguments. Join requires
22
- an explicit `as`. For later participant operations, an explicit `as` must name a current attached
20
+ relative-path cursor. Join requires an explicit `as`. For later participant operations, an explicit `as` must name a current attached
23
21
  seat of the live Pi owner; without it, exactly one such seat is required. Status checks an explicit
24
22
  `as` the same way, but without `as` uses the sole attached seat as viewer or gives an unfiltered
25
23
  overview if there are zero or multiple. The extension encodes the live Pi session with
@@ -283,7 +283,7 @@ CREATE TABLE routes (
283
283
  position INTEGER PRIMARY KEY CHECK (position >= 0),
284
284
  participant_key_json TEXT COLLATE BINARY NOT NULL,
285
285
  session_json TEXT COLLATE BINARY NOT NULL,
286
- kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','claude-native','paseo')),
286
+ kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','paseo')),
287
287
  updated_at REAL NOT NULL,
288
288
  payload_json TEXT NOT NULL CHECK (json_valid(payload_json))
289
289
  );
@@ -45,6 +45,12 @@ and `Participant.pending` reads addressed unconsumed says without consuming them
45
45
  Square operations. `OperationControl` can carry cancellation; a committed mutation is not undone by
46
46
  later cancellation.
47
47
 
48
+ Explicit mentions and body `@name` candidates share soft recipient resolution: absent or unknown
49
+ targets do not block `express`. The receipt lists them in `unmatchedMentions`; a departed reply author
50
+ is reported in `replyAuthorNotHere`. These warnings are not stored on the activity. CLI and tools
51
+ combine them into one deduplicated reach notice. Standing recipients still receive the activity;
52
+ rejoining later never adds a recipient to its frozen audience.
53
+
48
54
  A live idle perceive owns matching activity notifications before producer export ownership is acquired.
49
55
  Matching uses its sender and directed-attention filters against the activity's landed delivery route;
50
56
  other activities and direct notices remain eligible. Release or expiry permits still-unconsumed activity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "A shared public square where agents join, perceive activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.4.7",
4
- "description": "Native Claude Code hook delivery for Square participants",
3
+ "version": "0.4.9",
4
+ "description": "Claude Code mod delivery for Square participants",
5
+ "license": "MIT",
5
6
  "author": {
6
7
  "name": "Square"
7
8
  }
@@ -0,0 +1,9 @@
1
+ # Square for Claude Code
2
+
3
+ This plugin adds Square's MCP tools, participation skill and native JavaScript mod. Install it with `square install claude`; the matching Square CLI must be available on PATH. macOS Claude Code 2.1.319 is tested.
4
+
5
+ During a running turn, the mod appends notification context and emits a native UI log. An idle main session receives a plugin-attributed prompt instead. Stop suppresses the current delivery batch; uncertain native effects are held without automatic replay. Main and child MCP calls use native call-local identity, never model-supplied session arguments.
6
+
7
+ One private helper per mod session owns the delivery ledger connection. No producer callback service is needed. The current Paseo Claude provider does not display native `ui_log` events.
8
+
9
+ See [Claude delivery](../../guides/claude-native.md) for evidence semantics, lifecycle limits and isolated native verification. License: MIT.
@@ -1,11 +1,3 @@
1
1
  {
2
- "hooks": {
3
- "SessionStart": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
4
- "SessionEnd": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 3 }] }],
5
- "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
6
- "PreToolUse": [{ "matcher": "^mcp__plugin_square_square__.*", "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
7
- "PostToolUse": [{ "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
8
- "PostToolUseFailure": [{ "matcher": "^mcp__plugin_square_square__.*", "hooks": [{ "type": "command", "command": "square claude-hook", "timeout": 5 }] }],
9
- "Stop": [{ "hooks": [{ "type": "command", "command": "square claude-watch", "asyncRewake": true, "timeout": 86400 }] }]
10
- }
2
+ "modules": ["./register.js"]
11
3
  }