@astrosheep/square 0.4.6 → 0.4.8

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 (101) 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 +2 -2
  8. package/dist/cli/context.js +14 -16
  9. package/dist/cli/express-presentation.js +12 -53
  10. package/dist/cli/json.js +2 -2
  11. package/dist/cli/observation-commands.d.ts +2 -7
  12. package/dist/cli/observation-commands.js +31 -97
  13. package/dist/cli/registry.js +3 -6
  14. package/dist/cli/square-commands.d.ts +2 -3
  15. package/dist/cli/square-commands.js +12 -22
  16. package/dist/decisions.js +5 -4
  17. package/dist/harness-claude.js +3 -3
  18. package/dist/help.js +9 -16
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/mcp.d.ts +1 -0
  22. package/dist/mcp.js +7 -18
  23. package/dist/model.d.ts +1 -1
  24. package/dist/neutral.d.ts +15 -3
  25. package/dist/notifications.d.ts +1 -3
  26. package/dist/notifications.js +21 -23
  27. package/dist/operation-control.d.ts +0 -1
  28. package/dist/operation-control.js +0 -11
  29. package/dist/operation-presentation.d.ts +28 -21
  30. package/dist/operation-presentation.js +90 -87
  31. package/dist/packages/agent-delivery/src/index.d.ts +4 -9
  32. package/dist/packages/agent-delivery/src/index.js +0 -9
  33. package/dist/packages/agent-delivery/src/local-endpoint.d.ts +2 -12
  34. package/dist/packages/agent-delivery/src/local-endpoint.js +4 -48
  35. package/dist/packages/agent-delivery/src/pi-client.js +0 -2
  36. package/dist/packages/agent-delivery/src/types.d.ts +3 -37
  37. package/dist/participant-identity.d.ts +0 -2
  38. package/dist/participant-identity.js +0 -13
  39. package/dist/plugins/paseo/shared/card-format.d.ts +6 -0
  40. package/dist/plugins/paseo/shared/card-format.js +38 -0
  41. package/dist/plugins/paseo/shared/card-presentation.d.ts +3 -1
  42. package/dist/plugins/paseo/shared/card-presentation.js +3 -1
  43. package/dist/presentation.d.ts +5 -22
  44. package/dist/presentation.js +25 -58
  45. package/dist/runtime.d.ts +6 -6
  46. package/dist/runtime.js +5 -15
  47. package/dist/sqlite-square.d.ts +4 -5
  48. package/dist/sqlite-square.js +29 -32
  49. package/dist/square-api.d.ts +1 -1
  50. package/dist/square-api.js +1 -1
  51. package/dist/square-facade.d.ts +5 -14
  52. package/dist/square-location.d.ts +0 -1
  53. package/dist/square-posting.d.ts +4 -4
  54. package/dist/square-posting.js +14 -16
  55. package/dist/square-tools.d.ts +4 -17
  56. package/dist/square-tools.js +52 -119
  57. package/dist/square-values.d.ts +1 -1
  58. package/dist/square-values.js +1 -1
  59. package/dist/time.d.ts +1 -0
  60. package/dist/time.js +10 -0
  61. package/dist/views.d.ts +1 -1
  62. package/dist/views.js +2 -2
  63. package/dist/wake-transports.d.ts +2 -2
  64. package/dist/wake-transports.js +7 -9
  65. package/dist/wakes.js +1 -1
  66. package/examples/symmetric-domains.mjs +1 -2
  67. package/guides/catch-cold-path-optimization.md +1 -1
  68. package/guides/claude-native.md +37 -7
  69. package/guides/delivery-primitives.md +2 -1
  70. package/guides/mcp.md +3 -3
  71. package/guides/pi-native-delivery.md +10 -2
  72. package/guides/pi-tools.md +1 -3
  73. package/guides/row-storage-design.md +1 -1
  74. package/package.json +1 -1
  75. package/plugins/claude/.claude-plugin/plugin.json +3 -2
  76. package/plugins/claude/README.md +9 -0
  77. package/plugins/claude/hooks/hooks.json +1 -9
  78. package/plugins/claude/hooks/register.js +178 -0
  79. package/plugins/claude/skills/square/SKILL.md +2 -2
  80. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  81. package/plugins/paseo/OVERVIEW.md +1 -1
  82. package/plugins/paseo/README.md +20 -23
  83. package/plugins/paseo/client/markdown.tsx +88 -95
  84. package/plugins/paseo/client/palette.ts +37 -12
  85. package/plugins/paseo/client/square-message.tsx +52 -76
  86. package/plugins/paseo/client/transform.ts +4 -6
  87. package/plugins/paseo/shared/card-format.ts +37 -0
  88. package/plugins/paseo/shared/card-presentation.ts +6 -2
  89. package/plugins/paseo/shared/message.ts +1 -6
  90. package/plugins/pi/square-pi.js +76 -60
  91. package/skills/square/SKILL.md +2 -2
  92. package/dist/claude-boundary-state.d.ts +0 -20
  93. package/dist/claude-boundary-state.js +0 -84
  94. package/dist/claude-hook.d.ts +0 -2
  95. package/dist/claude-hook.js +0 -232
  96. package/dist/packages/agent-delivery/src/claude-auth.d.ts +0 -13
  97. package/dist/packages/agent-delivery/src/claude-auth.js +0 -75
  98. package/dist/packages/agent-delivery/src/claude-native.d.ts +0 -30
  99. package/dist/packages/agent-delivery/src/claude-native.js +0 -75
  100. package/dist/packages/agent-delivery/src/claude.d.ts +0 -5
  101. package/dist/packages/agent-delivery/src/claude.js +0 -66
@@ -0,0 +1,2 @@
1
+ /** Receiver formatting shared by the Paseo and Pi cards. */
2
+ export { cardReferences, reachLabel, timeLabel } from './plugins/paseo/shared/card-format.js';
@@ -0,0 +1,2 @@
1
+ /** Receiver formatting shared by the Paseo and Pi cards. */
2
+ export { cardReferences, reachLabel, timeLabel } from './plugins/paseo/shared/card-format.js';
@@ -10,6 +10,5 @@ export declare function beginClaudeMcpCall(input: ClaudeMcpCall, env?: NodeJS.Pr
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, signal?: AbortSignal): Promise<void>;
14
13
  export declare function readClaudeMcpCall(toolUseId: unknown, env?: NodeJS.ProcessEnv): Promise<ClaudeMcpCall>;
15
14
  export {};
@@ -71,16 +71,6 @@ 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, signal) {
75
- if (!text.safeParse(toolUseId).success || !text.safeParse(sessionId).success)
76
- throw unavailable();
77
- await mutate(env, async (root) => {
78
- const file = callFile(root, toolUseId);
79
- const stored = await readRecord(file);
80
- if (stored?.toolUseId === toolUseId && stored.sessionId === sessionId && stored.agentId === agentId)
81
- await fs.unlink(file);
82
- }, signal);
83
- }
84
74
  export async function readClaudeMcpCall(toolUseId, env = process.env) {
85
75
  if (!text.safeParse(toolUseId).success)
86
76
  throw unavailable();
@@ -0,0 +1,2 @@
1
+ /** One mod-owned helper. Only this process touches Node/SQLite; no producer socket or inbox. */
2
+ export declare function runClaudeMod(input: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal, output?: (line: string) => void | Promise<void>): Promise<string>;
@@ -0,0 +1,211 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import { once } from 'node:events';
4
+ import { z } from 'zod/v4';
5
+ import { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
6
+ import { retainDeliveryLedger } from './delivery-lifetime.js';
7
+ import { harnessOwner } from './owner.js';
8
+ import { attachDeliverySession, detachDeliverySession } from './session-lifecycle.js';
9
+ import { beginClaudeMcpCall, endClaudeMcpCall } from './claude-mcp-context.js';
10
+ import { renderDeliveryReminder } from './delivery-frame.js';
11
+ const routeKind = 'claude-mod';
12
+ const nativeId = z.string().min(1).max(4096).regex(/^[^\x00-\x1f\x7f]+$/u);
13
+ const coordinate = z.object({ agentId: nativeId.optional() });
14
+ const request = z.discriminatedUnion('op', [
15
+ coordinate.extend({ op: z.literal('state'), mode: z.enum(['busy', 'idle', 'stopped', 'ended']) }),
16
+ z.object({ op: z.literal('guard'), token: z.string().uuid() }),
17
+ z.object({ op: z.literal('settle'), token: z.string().uuid(), kind: z.enum(['append', 'submit', 'failed', 'unknown', 'released']),
18
+ text: z.string().optional(), uuid: nativeId.optional() }),
19
+ coordinate.extend({ op: z.literal('mcp-begin'), toolUseId: nativeId, cwd: z.string().min(1) }),
20
+ z.object({ op: z.literal('mcp-end'), toolUseId: nativeId, token: nativeId }),
21
+ z.object({ op: z.literal('close') }),
22
+ ]);
23
+ /** One mod-owned helper. Only this process touches Node/SQLite; no producer socket or inbox. */
24
+ export async function runClaudeMod(input, env = process.env, signal, output = line => new Promise((resolve, reject) => {
25
+ process.stdout.write(line, error => error ? reject(error) : resolve());
26
+ })) {
27
+ const { sessionId } = z.object({ sessionId: nativeId }).parse(JSON.parse(input));
28
+ const stop = new AbortController();
29
+ const abort = () => stop.abort();
30
+ const lifetimeSignal = signal ? AbortSignal.any([signal, stop.signal]) : stop.signal;
31
+ const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal: AbortSignal.any([lifetimeSignal, AbortSignal.timeout(5000)]) });
32
+ process.once('SIGTERM', abort);
33
+ process.once('SIGINT', abort);
34
+ const lifetime = retainDeliveryLedger(ledger);
35
+ const receivers = new Map();
36
+ const offers = new Map();
37
+ const secret = `/${randomBytes(32).toString('hex')}`;
38
+ let revision = 0;
39
+ const ownerFor = (agentId) => harnessOwner({ channel: 'claude-code', session: sessionId, ...(agentId === undefined ? {} : { agent: agentId }) });
40
+ async function state(agentId, mode, signal = lifetimeSignal) {
41
+ const key = agentId ?? '';
42
+ let receiver = receivers.get(key);
43
+ if ((!receiver || receiver.mode === 'ended') && (mode === 'busy' || mode === 'idle')) {
44
+ const owner = ownerFor(agentId);
45
+ const attached = await attachDeliverySession(owner, env, [{ kind: routeKind, address: { sessionId, ...(agentId === undefined ? {} : { agentId }) } }], signal);
46
+ if (!attached)
47
+ return false;
48
+ receiver = { agentId, owner, generation: attached.generation, mode, attempted: new Set() };
49
+ receivers.set(key, receiver);
50
+ }
51
+ if (!receiver)
52
+ return false;
53
+ if (mode === 'busy' && receiver.mode === 'stopped')
54
+ receiver.attempted.clear();
55
+ receiver.mode = mode;
56
+ revision++;
57
+ if (mode === 'stopped')
58
+ await ledger.cancel({ owner: receiver.owner, generation: receiver.generation }, signal);
59
+ if (mode === 'ended')
60
+ await detachDeliverySession(receiver.owner, receiver.generation, env, signal);
61
+ if (mode === 'stopped' || mode === 'ended')
62
+ for (const offer of offers.values()) {
63
+ if (offer.receiver === receiver)
64
+ offer.finish(offer.invoked ? 'unknown' : 'not-capable');
65
+ }
66
+ return true;
67
+ }
68
+ async function rpc(data) {
69
+ switch (data.op) {
70
+ case 'state': return { ok: await state(data.agentId, data.mode) };
71
+ case 'guard': {
72
+ const offer = offers.get(data.token);
73
+ if (!offer || !['busy', 'idle'].includes(offer.receiver.mode) || !await offer.guard())
74
+ return { ok: false };
75
+ // Crossing this fence may invoke a native effect. Cancellation thereafter is uncertain.
76
+ offer.invoked = true;
77
+ return { ok: true };
78
+ }
79
+ case 'settle': {
80
+ const offer = offers.get(data.token);
81
+ if (!offer)
82
+ return { ok: false };
83
+ const exact = data.text === offer.text;
84
+ const outcome = data.kind === 'append' ? exact && data.uuid ? 'presented' : 'unknown'
85
+ : data.kind === 'submit' ? exact ? 'presented' : 'accepted'
86
+ : data.kind === 'released' ? 'not-capable' : data.kind;
87
+ offer.finish(outcome);
88
+ return { ok: true };
89
+ }
90
+ case 'mcp-begin': {
91
+ const receiver = receivers.get(data.agentId ?? '');
92
+ if (!receiver || receiver.mode !== 'busy') {
93
+ // Some native child starts are nested under another mod's event. The
94
+ // actual tool event is still authoritative, never its model arguments.
95
+ if (receiver?.mode === 'stopped' || !await state(data.agentId, 'busy'))
96
+ throw new Error('Square MCP call identity unavailable');
97
+ }
98
+ return beginClaudeMcpCall({ toolUseId: data.toolUseId, sessionId, cwd: data.cwd, agentId: data.agentId }, env, lifetimeSignal);
99
+ }
100
+ case 'mcp-end':
101
+ await endClaudeMcpCall(data.toolUseId, data.token, env);
102
+ return { ok: true };
103
+ case 'close':
104
+ // The mod may kill its process stream as soon as this RPC returns.
105
+ // Commit detach before acknowledging, not only in process teardown.
106
+ for (const receiver of receivers.values())
107
+ await state(receiver.agentId, 'ended');
108
+ stop.abort();
109
+ return { ok: true };
110
+ }
111
+ }
112
+ const server = createServer(async (req, res) => {
113
+ if (req.method !== 'POST' || req.url !== secret) {
114
+ res.writeHead(404).end();
115
+ return;
116
+ }
117
+ try {
118
+ let body = '';
119
+ for await (const chunk of req) {
120
+ body += chunk.toString();
121
+ if (body.length > 1_048_576)
122
+ throw new Error('Request too large');
123
+ }
124
+ const result = await rpc(request.parse(JSON.parse(body)));
125
+ res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(result));
126
+ }
127
+ catch {
128
+ res.writeHead(400, { 'content-type': 'application/json' }).end(JSON.stringify({ error: 'Square Claude mod request failed' }));
129
+ }
130
+ });
131
+ let observer;
132
+ try {
133
+ server.listen(0, '127.0.0.1');
134
+ await once(server, 'listening', { signal: lifetimeSignal });
135
+ const address = server.address();
136
+ if (!address || typeof address === 'string')
137
+ throw new Error('Claude helper address unavailable');
138
+ if (!await state(undefined, 'idle', AbortSignal.any([lifetimeSignal, AbortSignal.timeout(5000)])))
139
+ throw new Error('Claude helper attachment unavailable');
140
+ observer = await ledger.observe(ownerFor());
141
+ await output(`${JSON.stringify({ type: 'ready', url: `http://127.0.0.1:${address.port}${secret}` })}\n`);
142
+ while (!lifetimeSignal.aborted) {
143
+ let version = await observer.read(lifetimeSignal);
144
+ const previousRevision = revision;
145
+ for (const receiver of receivers.values()) {
146
+ if (!['busy', 'idle'].includes(receiver.mode))
147
+ continue;
148
+ const current = await ledger.session(receiver.owner, lifetimeSignal);
149
+ if (current?.state !== 'attached' || current.generation !== receiver.generation) {
150
+ if (receiver.agentId === undefined)
151
+ return '';
152
+ receiver.mode = 'ended';
153
+ continue;
154
+ }
155
+ const held = new Set((await ledger.handoffs(receiver.owner, { generation: receiver.generation }, lifetimeSignal))
156
+ .filter(attempt => !['failed', 'released'].includes(attempt.outcome)).map(attempt => attempt.seq));
157
+ for (const card of await ledger.pending(receiver.owner, { generation: receiver.generation }, lifetimeSignal)) {
158
+ if (lifetimeSignal.aborted || !['busy', 'idle'].includes(receiver.mode))
159
+ break;
160
+ if (held.has(card.seq) || receiver.attempted.has(card.key))
161
+ continue;
162
+ // An idle child cannot be woken with the main session's prompt.submit.
163
+ if (receiver.agentId !== undefined && receiver.mode !== 'busy')
164
+ continue;
165
+ receiver.attempted.add(card.key);
166
+ const transport = {
167
+ kind: routeKind,
168
+ async send(deliveryCard, _address, context) {
169
+ const text = renderDeliveryReminder([`[square-delivery:${context.token}]\n${deliveryCard.text}\n[/square-delivery:${context.token}]`], { tagged: false });
170
+ if (!await context.prepare({ token: context.token, messageId: context.token, payloadHash: createHash('sha256').update(text).digest('hex') })
171
+ || !await context.beforeSend() || !['busy', 'idle'].includes(receiver.mode))
172
+ return 'not-capable';
173
+ const completion = Promise.withResolvers();
174
+ offers.set(context.token, { receiver, text, invoked: false, guard: context.beforeSend, finish: completion.resolve });
175
+ try {
176
+ await output(`${JSON.stringify({ type: 'delivery', token: context.token, agentId: receiver.agentId, text, display: deliveryCard.text })}\n`);
177
+ return await completion.promise;
178
+ }
179
+ finally {
180
+ offers.delete(context.token);
181
+ }
182
+ },
183
+ };
184
+ lifetime.track(await ledger.dispatchHandoff(receiver.owner, card.key, transport, { generation: receiver.generation, timeoutMs: 5000, signal: lifetimeSignal }));
185
+ // Settlement changes are our own; drain them rather than busy-retrying.
186
+ version = await observer.read(lifetimeSignal);
187
+ }
188
+ }
189
+ if (!lifetimeSignal.aborted && previousRevision === revision)
190
+ await observer.changed(version, 1000, lifetimeSignal);
191
+ }
192
+ }
193
+ catch (error) {
194
+ if (!lifetimeSignal.aborted)
195
+ throw error;
196
+ }
197
+ finally {
198
+ stop.abort();
199
+ for (const offer of offers.values())
200
+ offer.finish(offer.invoked ? 'unknown' : 'not-capable');
201
+ server.closeAllConnections();
202
+ await new Promise(resolve => server.close(() => resolve()));
203
+ observer?.close();
204
+ for (const receiver of receivers.values())
205
+ await detachDeliverySession(receiver.owner, receiver.generation, env, AbortSignal.timeout(1500)).catch(() => undefined);
206
+ await lifetime.close();
207
+ process.off('SIGTERM', abort);
208
+ process.off('SIGINT', abort);
209
+ }
210
+ return '';
211
+ }
@@ -22,7 +22,7 @@ export declare function resolveBody(arg: string): Promise<string>;
22
22
  export declare function fail(message: string, exitCode?: number): never;
23
23
  export declare function usage(command: string): never;
24
24
  export declare function requireValue(args: string[], index: number, flag: string): string;
25
- export declare function parseDurationMs(value: string, flag: string): number;
25
+ export { parseDurationMs } from '../time.js';
26
26
  export declare function parsePositiveInteger(value: string, flag: string): number;
27
27
  export declare function parseBoundedLimit(value: string | undefined, flag: string, max: number, retry: string): number;
28
28
  export declare function parseNonNegativeInteger(value: string, flag: string): number;
@@ -41,8 +41,8 @@ export declare function locationIsRequired(command: string): boolean;
41
41
  export declare function defaultContext(command: string, squarePath?: string, name?: string): CommandContext;
42
42
  export declare function squareInput(context: CommandContext): {
43
43
  cwd: string;
44
- env: NodeJS.ProcessEnv;
45
44
  path: string | undefined;
45
+ clock: () => number;
46
46
  };
47
47
  export declare function commandIdentity(env: NodeJS.ProcessEnv, explicit?: Owner): {
48
48
  owner: Owner;
@@ -3,8 +3,18 @@ import { harnessOwner, localUserOwner } from '../square-api.js';
3
3
  import { harnessSessionSources } from '../participant-identity.js';
4
4
  import { codexWakeAddress } from '../codex-address.js';
5
5
  import { commandUsageHint } from '../help.js';
6
- import { parseParticipantList, validateName } from '../model.js';
7
- import { Square, squareClock } from '../square-api.js';
6
+ import { SquareError, parseParticipantList, validateName } from '../model.js';
7
+ import { Square } from '../square-api.js';
8
+ function squareClock(env, clock) {
9
+ if (clock !== undefined)
10
+ return clock;
11
+ if (env.SQUARE_NOW_MS === undefined)
12
+ return Date.now;
13
+ const value = Number.parseInt(env.SQUARE_NOW_MS, 10);
14
+ if (!Number.isFinite(value))
15
+ throw new SquareError('invalid_args', 'Invalid SQUARE_NOW_MS: expected integer milliseconds.');
16
+ return () => value;
17
+ }
8
18
  export async function readStdin() {
9
19
  try {
10
20
  let content = '';
@@ -34,17 +44,7 @@ export function requireValue(args, index, flag) {
34
44
  fail(`✕ missing value for ${flag}`);
35
45
  return value;
36
46
  }
37
- export function parseDurationMs(value, flag) {
38
- const match = value.match(/^([1-9]\d*)(ms|s|m|h)$/);
39
- if (!match)
40
- fail(`✕ invalid ${flag}: expected a positive duration with unit ms, s, m, or h (for example 500ms, 30s, 3m, 1h)`);
41
- const amount = Number.parseInt(match[1], 10);
42
- const multiplier = { ms: 1, s: 1000, m: 60000, h: 3600000 };
43
- const milliseconds = amount * multiplier[match[2]];
44
- if (!Number.isSafeInteger(milliseconds))
45
- fail(`✕ invalid ${flag}: duration is too large`);
46
- return milliseconds;
47
- }
47
+ export { parseDurationMs } from '../time.js';
48
48
  export function parsePositiveInteger(value, flag) {
49
49
  if (!/^[1-9]\d*$/.test(value))
50
50
  fail(`✕ invalid ${flag}: expected a positive integer`);
@@ -97,11 +97,9 @@ const LOCATION_REQUIRED_COMMANDS = new Set([
97
97
  'listen',
98
98
  'ignore',
99
99
  'unlisten',
100
- 'listening',
101
100
  'done',
102
101
  'history',
103
102
  'status',
104
- 'participants',
105
103
  'doctor',
106
104
  ]);
107
105
  export async function parseGlobalArgs(rawArgs) {
@@ -142,7 +140,7 @@ export function defaultContext(command, squarePath, name) {
142
140
  return { command, cwd: process.cwd(), env: { ...process.env }, squarePath, name, owner: undefined, homeDir: os.homedir() };
143
141
  }
144
142
  export function squareInput(context) {
145
- return { cwd: context.cwd, env: context.env, path: context.squarePath };
143
+ return { cwd: context.cwd, path: context.squarePath, clock: squareClock(context.env) };
146
144
  }
147
145
  export function commandIdentity(env, explicit) {
148
146
  if (explicit !== undefined)
@@ -3,9 +3,8 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { isSquareError } from '../model.js';
5
5
  import { selectOwnSeat, formatActivityId, parseActivityId } from '../square-api.js';
6
- import { formatRefusal, refusalText, renderActivityBlocked, renderActivityLimit, renderActivityUncertain, renderExpressNoWait, renderExpressWaiting, joinRecoveryCommand, participantsRecoveryCommand, participantCommandPrefix, commandPrefix, quoteShell, takeoverRecoveryLines, withPathOutput, } from '../presentation.js';
7
- import { renderExpression } from '../operation-presentation.js';
8
- import { formatDuration } from '../time.js';
6
+ import { formatRefusal, renderActivityUncertain, renderExpressWaiting, participantCommandPrefix, commandPrefix, quoteShell, withPathOutput, } from '../presentation.js';
7
+ import { isExpressGate, renderExpression, renderFailureLines } from '../operation-presentation.js';
9
8
  import { jsonResult } from './json.js';
10
9
  export { renderExpression } from '../operation-presentation.js';
11
10
  export function saveActivityDraft(squarePath, name, body, now = Date.now()) {
@@ -101,76 +100,36 @@ export async function executeExpression(open, squarePath, requestedName, rawInpu
101
100
  exitCode(1);
102
101
  return;
103
102
  }
104
- if (isSquareError(error)) {
105
- if (error.code === 'behind' && fresh?.activitySummaries !== undefined) {
106
- process.stdout.write(renderActivityBlocked({ ...output, retryCommand: retryInput,
107
- activitySummaries: fresh.activitySummaries,
108
- }));
109
- exitCode(1);
110
- return;
111
- }
112
- if (error.code === 'capped') {
113
- process.stdout.write(renderActivityLimit({ ...output, count: fresh?.ownActivityCount,
114
- ...(fresh?.hardCap == null ? {} : { hardCap: fresh.hardCap }),
115
- }));
116
- exitCode(1);
117
- return;
118
- }
119
- if (error.code === 'throttled') {
120
- process.stdout.write(renderExpressNoWait({ ...output, reason: error.code, retryCommand: retryInput,
121
- delayMs: error.facts?.retryAfterMs ?? 1,
122
- }));
123
- exitCode(1);
124
- return;
125
- }
126
- if (error.code === 'bell_quota') {
127
- process.stdout.write(withPathOutput(squarePath, [
128
- '✕ the bell stays quiet for now', ` · you can ring it again in ${formatDuration(error.facts?.retryAfterMs ?? 1)}`,
129
- `· draft kept: ${draftPath}`, ' · retry when the bell is ready:', retryInput,
130
- ].join('\n'), output));
131
- exitCode(1);
132
- return;
133
- }
103
+ if (isSquareError(error) && isExpressGate(error.code)) {
104
+ process.stdout.write(withPathOutput(squarePath, renderFailureLines({ code: error.code, message: error.message, facts: error.facts }, { squarePath, name, owner: opts.recoveryOwner, draftPath, retry: retryInput }).join('\n'), output));
105
+ exitCode(1);
106
+ return;
134
107
  }
135
108
  const facts = isSquareError(error) ? error.facts : undefined;
136
109
  const reason = facts?.reason;
137
- const message = reason === 'bell_mention_conflict'
138
- ? '✕ a bell cannot be combined with a mention'
139
- : reason === 'invalid_reply'
140
- ? facts?.activityExists === false
141
- ? `Invalid --reply: ${facts.activityId ?? opts.reply ?? 'that activity'} is not in this square's history`
142
- : 'Invalid --reply: expected an activity id like act/12'
143
- : refusalText(error);
144
- const lines = [message, `· draft kept: ${draftPath}`];
110
+ const lines = renderFailureLines({ code: isSquareError(error) ? error.code : '', message: error instanceof Error ? error.message : String(error), facts, operation: detail }, { squarePath, name, owner: opts.recoveryOwner, reply: opts.reply });
111
+ const headlineLines = reason !== 'needs_seat' && isSquareError(error) && error.code === 'already_joined' ? 2 : (reason === 'never_joined' || reason === 'not_standing') ? (error instanceof Error ? error.message : String(error)).split('\n').length : 1;
112
+ lines.splice(headlineLines, 0, `· draft kept: ${draftPath}`);
145
113
  if (reason === 'needs_seat') {
146
- lines[0] = `✕ ${message}`;
147
- lines.push(joinRecoveryCommand(squarePath, name, opts.recoveryOwner));
148
114
  process.stderr.write(formatRefusal(squarePath, lines, output));
149
115
  exitCode(2);
150
116
  return;
151
117
  }
152
118
  const repair = reason === 'bell_mention_conflict' || (reason === 'not_standing' && detail?.subject === 'recipient');
153
119
  if (isSquareError(error)) {
154
- if ((reason === 'never_joined' && detail?.subject === 'caller') || error.code === 'unknown_participant' || error.code === 'not_joined') {
155
- lines.push(joinRecoveryCommand(squarePath, name, opts.recoveryOwner));
156
- }
157
- if (error.code === 'already_joined')
158
- lines.push(...takeoverRecoveryLines(squarePath, name, opts.recoveryOwner));
159
120
  if (repair) {
160
- lines.push(participantsRecoveryCommand(squarePath), reason === 'bell_mention_conflict'
161
- ? ' · ring the bell without explicit mentions:'
162
- : ' · retry without explicit recipients:', `${activityRetryCommand(squarePath, name, { ...opts, mentions: [] })}${opts.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
121
+ lines.push(reason === 'bell_mention_conflict' ? ' · ring the bell without explicit mentions:' : ' · retry without explicit recipients:', `${activityRetryCommand(squarePath, name, { ...opts, mentions: [] })}${opts.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
163
122
  }
164
123
  else if (error.code === 'invalid_args' || error.code === 'invalid_name') {
165
124
  if (opts.mentions?.length)
166
- lines.push(participantsRecoveryCommand(squarePath));
125
+ lines.push(`${commandPrefix(squarePath)} status`);
167
126
  if (opts.reply !== undefined && reason !== 'invalid_reply')
168
127
  lines.push(`${commandPrefix(squarePath)} history --limit 10`);
169
128
  }
170
129
  }
171
130
  if (reason === 'invalid_reply') {
172
131
  const withoutReply = { ...opts, reply: undefined };
173
- lines.push(`${commandPrefix(squarePath)} history --limit 10`, ' · choose a valid --reply from history, or send without a reply using this repair:', `${activityRetryCommand(squarePath, name, withoutReply)}${withoutReply.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
132
+ lines.push(' · choose a valid --reply from history, or send without a reply using this repair:', `${activityRetryCommand(squarePath, name, withoutReply)}${withoutReply.bodyProvided === false ? '' : ` < ${quoteShell(draftPath)}`}`);
174
133
  }
175
134
  else if (!repair) {
176
135
  lines.push(' · correct the problem above before retrying; keep your intended recipients and reply:', `${retryInput}`);
package/dist/cli/json.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export const JSON_COMMANDS = new Set([
2
- 'join', 'express', 'perceive', 'history', 'listen', 'ignore', 'unlisten', 'listening',
3
- 'done', 'status', 'participants', 'inbox',
2
+ 'join', 'express', 'perceive', 'history', 'listen', 'ignore', 'unlisten',
3
+ 'done', 'status', 'inbox',
4
4
  ]);
5
5
  /** Serialize the SDK receipt, not its optional presentation or live participant handle. */
6
6
  export function jsonResult(result) {
@@ -2,7 +2,7 @@ import type { PerceiveOptions, DiscoveryOptions, HistoryQuery, StreamStart } fro
2
2
  import { type CommandSpec } from './context.js';
3
3
  interface HistoryCommandOptions {
4
4
  query: HistoryQuery;
5
- noTruncate: boolean;
5
+ full: boolean;
6
6
  format?: string[];
7
7
  continuationArgs: string[];
8
8
  }
@@ -15,18 +15,13 @@ interface StreamIntent {
15
15
  export declare const streamCommand: CommandSpec<StreamIntent>;
16
16
  export declare const perceiveCommand: CommandSpec<PerceiveOptions>;
17
17
  export declare const historyCommand: CommandSpec<HistoryCommandOptions, string>;
18
- interface ParticipantsCommandOptions {
19
- limit: number;
20
- }
21
- export declare const participantsCommand: CommandSpec<ParticipantsCommandOptions, string>;
22
18
  export declare const statusCommand: CommandSpec<undefined, string>;
23
19
  interface InboxIntent {
24
20
  owner: string;
25
21
  limit: number;
26
22
  }
27
23
  export declare const inboxCommand: CommandSpec<InboxIntent, string>;
28
- export declare const claudeHookCommand: CommandSpec<undefined, string>;
29
- export declare const claudeWatchCommand: CommandSpec<undefined, string>;
24
+ export declare const claudeModCommand: CommandSpec<undefined, string>;
30
25
  export declare const codexHookCommand: CommandSpec<undefined, string>;
31
26
  export declare const codexWatchCommand: CommandSpec<undefined, string>;
32
27
  export declare const codexInterruptCommand: CommandSpec<undefined, string>;