@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.
- package/dist/card-format.d.ts +2 -0
- package/dist/card-format.js +2 -0
- package/dist/claude-mcp-context.d.ts +0 -1
- package/dist/claude-mcp-context.js +0 -10
- package/dist/claude-mod.d.ts +2 -0
- package/dist/claude-mod.js +211 -0
- package/dist/cli/context.d.ts +2 -2
- package/dist/cli/context.js +14 -16
- package/dist/cli/express-presentation.js +12 -53
- package/dist/cli/json.js +2 -2
- package/dist/cli/observation-commands.d.ts +2 -7
- package/dist/cli/observation-commands.js +31 -97
- package/dist/cli/registry.js +3 -6
- package/dist/cli/square-commands.d.ts +2 -3
- package/dist/cli/square-commands.js +12 -22
- package/dist/decisions.js +5 -4
- package/dist/harness-claude.js +3 -3
- package/dist/help.js +9 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +7 -18
- package/dist/model.d.ts +1 -1
- package/dist/neutral.d.ts +15 -3
- package/dist/notifications.d.ts +1 -3
- package/dist/notifications.js +21 -23
- package/dist/operation-control.d.ts +0 -1
- package/dist/operation-control.js +0 -11
- package/dist/operation-presentation.d.ts +28 -21
- package/dist/operation-presentation.js +90 -87
- package/dist/packages/agent-delivery/src/index.d.ts +4 -9
- package/dist/packages/agent-delivery/src/index.js +0 -9
- package/dist/packages/agent-delivery/src/local-endpoint.d.ts +2 -12
- package/dist/packages/agent-delivery/src/local-endpoint.js +4 -48
- package/dist/packages/agent-delivery/src/pi-client.js +0 -2
- package/dist/packages/agent-delivery/src/types.d.ts +3 -37
- package/dist/participant-identity.d.ts +0 -2
- package/dist/participant-identity.js +0 -13
- package/dist/plugins/paseo/shared/card-format.d.ts +6 -0
- package/dist/plugins/paseo/shared/card-format.js +38 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +3 -1
- package/dist/plugins/paseo/shared/card-presentation.js +3 -1
- package/dist/presentation.d.ts +5 -22
- package/dist/presentation.js +25 -58
- package/dist/runtime.d.ts +6 -6
- package/dist/runtime.js +5 -15
- package/dist/sqlite-square.d.ts +4 -5
- package/dist/sqlite-square.js +29 -32
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-facade.d.ts +5 -14
- package/dist/square-location.d.ts +0 -1
- package/dist/square-posting.d.ts +4 -4
- package/dist/square-posting.js +14 -16
- package/dist/square-tools.d.ts +4 -17
- package/dist/square-tools.js +52 -119
- package/dist/square-values.d.ts +1 -1
- package/dist/square-values.js +1 -1
- package/dist/time.d.ts +1 -0
- package/dist/time.js +10 -0
- package/dist/views.d.ts +1 -1
- package/dist/views.js +2 -2
- package/dist/wake-transports.d.ts +2 -2
- package/dist/wake-transports.js +7 -9
- package/dist/wakes.js +1 -1
- package/examples/symmetric-domains.mjs +1 -2
- package/guides/catch-cold-path-optimization.md +1 -1
- package/guides/claude-native.md +37 -7
- package/guides/delivery-primitives.md +2 -1
- package/guides/mcp.md +3 -3
- package/guides/pi-native-delivery.md +10 -2
- package/guides/pi-tools.md +1 -3
- package/guides/row-storage-design.md +1 -1
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +3 -2
- package/plugins/claude/README.md +9 -0
- package/plugins/claude/hooks/hooks.json +1 -9
- package/plugins/claude/hooks/register.js +178 -0
- package/plugins/claude/skills/square/SKILL.md +2 -2
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/paseo/OVERVIEW.md +1 -1
- package/plugins/paseo/README.md +20 -23
- package/plugins/paseo/client/markdown.tsx +88 -95
- package/plugins/paseo/client/palette.ts +37 -12
- package/plugins/paseo/client/square-message.tsx +52 -76
- package/plugins/paseo/client/transform.ts +4 -6
- package/plugins/paseo/shared/card-format.ts +37 -0
- package/plugins/paseo/shared/card-presentation.ts +6 -2
- package/plugins/paseo/shared/message.ts +1 -6
- package/plugins/pi/square-pi.js +76 -60
- package/skills/square/SKILL.md +2 -2
- package/dist/claude-boundary-state.d.ts +0 -20
- package/dist/claude-boundary-state.js +0 -84
- package/dist/claude-hook.d.ts +0 -2
- package/dist/claude-hook.js +0 -232
- package/dist/packages/agent-delivery/src/claude-auth.d.ts +0 -13
- package/dist/packages/agent-delivery/src/claude-auth.js +0 -75
- package/dist/packages/agent-delivery/src/claude-native.d.ts +0 -30
- package/dist/packages/agent-delivery/src/claude-native.js +0 -75
- package/dist/packages/agent-delivery/src/claude.d.ts +0 -5
- package/dist/packages/agent-delivery/src/claude.js +0 -66
|
@@ -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,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
|
+
}
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/cli/context.js
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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',
|
|
3
|
-
'done', 'status', '
|
|
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
|
-
|
|
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
|
|
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>;
|