@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.
- 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 +1 -1
- package/dist/cli/context.js +1 -12
- package/dist/cli/express-presentation.js +8 -35
- package/dist/cli/json.js +1 -1
- package/dist/cli/observation-commands.d.ts +2 -3
- package/dist/cli/observation-commands.js +21 -59
- package/dist/cli/registry.js +3 -5
- package/dist/cli/square-commands.d.ts +1 -2
- package/dist/cli/square-commands.js +7 -17
- package/dist/decisions.js +15 -20
- package/dist/harness-claude.js +3 -3
- package/dist/help.js +6 -8
- package/dist/mcp.js +1 -1
- package/dist/neutral.d.ts +15 -1
- package/dist/notifications.js +1 -1
- package/dist/operation-presentation.d.ts +17 -15
- package/dist/operation-presentation.js +63 -37
- 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/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 +2 -20
- package/dist/presentation.js +11 -46
- package/dist/sqlite-square.js +5 -6
- package/dist/square-facade.d.ts +4 -13
- package/dist/square-posting.js +1 -1
- package/dist/square-tools.d.ts +0 -11
- package/dist/square-tools.js +34 -120
- 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 +1 -1
- package/dist/wake-transports.js +1 -1
- 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/guides/square-api.md +6 -0
- 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 +4 -4
- 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 +69 -50
- package/skills/square/SKILL.md +4 -4
- 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
package/dist/claude-hook.js
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
|
|
3
|
-
import { claudeBoundaryStore } from './claude-boundary-state.js';
|
|
4
|
-
import { harnessOwner } from './owner.js';
|
|
5
|
-
import { attachDeliverySessionRoutes, detachDeliverySession } from './session-lifecycle.js';
|
|
6
|
-
import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
7
|
-
import { renderDeliveryReminder } from './delivery-frame.js';
|
|
8
|
-
import { beginClaudeMcpCall, endClaudeMcpCallForSession } from './claude-mcp-context.js';
|
|
9
|
-
import { retainDeliveryLedger } from './delivery-lifetime.js';
|
|
10
|
-
import { z } from 'zod/v4';
|
|
11
|
-
const nativeId = z.string().min(1).max(4096).regex(/^[^\x00-\x1f\x7f]+$/u);
|
|
12
|
-
const hookInput = z.object({
|
|
13
|
-
hook_event_name: nativeId, session_id: nativeId, cwd: z.string().optional(),
|
|
14
|
-
prompt_id: nativeId.optional().catch(undefined), prompt: z.string().optional(),
|
|
15
|
-
tool_use_id: nativeId.optional().catch(undefined), tool_name: z.string().optional(), agent_id: nativeId.optional(),
|
|
16
|
-
});
|
|
17
|
-
const route = 'claude-async-hook';
|
|
18
|
-
const ownerFor = (input) => harnessOwner({ channel: 'claude-code', session: input.session_id });
|
|
19
|
-
function parseInput(text) {
|
|
20
|
-
try {
|
|
21
|
-
const value = hookInput.safeParse(JSON.parse(text));
|
|
22
|
-
return value.success ? value.data : undefined;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function observedFrames(prompt) {
|
|
29
|
-
const prefix = renderDeliveryReminder([''], { tagged: false });
|
|
30
|
-
const result = [];
|
|
31
|
-
for (const match of prompt.matchAll(/\[square-delivery:([a-f0-9-]+)\]\n[\s\S]*?\n\[\/square-delivery:\1\]/g)) {
|
|
32
|
-
const start = match.index - prefix.length;
|
|
33
|
-
if (start < 0 || prompt.slice(start, match.index) !== prefix)
|
|
34
|
-
continue;
|
|
35
|
-
result.push({ token: match[1], payloadHash: createHash('sha256').update(prefix + match[0]).digest('hex') });
|
|
36
|
-
}
|
|
37
|
-
return result;
|
|
38
|
-
}
|
|
39
|
-
function claudeFrame(card, token) {
|
|
40
|
-
return renderDeliveryReminder([`[square-delivery:${token}]\n${card.text}\n[/square-delivery:${token}]`], { tagged: false });
|
|
41
|
-
}
|
|
42
|
-
export async function runClaudeHook(text, env = process.env, sink = value => { process.stdout.write(value); }) {
|
|
43
|
-
const input = parseInput(text);
|
|
44
|
-
if (!input) {
|
|
45
|
-
let raw;
|
|
46
|
-
try {
|
|
47
|
-
raw = JSON.parse(text);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return '';
|
|
51
|
-
}
|
|
52
|
-
if (raw?.hook_event_name === 'PreToolUse' && typeof raw.tool_name === 'string' && raw.tool_name.startsWith('mcp__plugin_square_square__'))
|
|
53
|
-
await denyMcpTool(sink);
|
|
54
|
-
return '';
|
|
55
|
-
}
|
|
56
|
-
const signal = AbortSignal.timeout(4500);
|
|
57
|
-
const boundaries = claudeBoundaryStore(input.session_id, env);
|
|
58
|
-
const mainOwner = ownerFor(input);
|
|
59
|
-
const owner = harnessOwner({ channel: 'claude-code', session: input.session_id, ...(input.agent_id === undefined ? {} : { agent: input.agent_id }) });
|
|
60
|
-
if (input.hook_event_name === 'SessionStart') {
|
|
61
|
-
await boundaries.start(async () => {
|
|
62
|
-
const session = await attachDeliverySessionRoutes(mainOwner, env, [{ kind: route, address: { sessionId: input.session_id } }], signal);
|
|
63
|
-
if (!session)
|
|
64
|
-
return false;
|
|
65
|
-
const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal });
|
|
66
|
-
try {
|
|
67
|
-
await ledger.updateRoutes({ owner: mainOwner, generation: session.generation, routes: session.routes.filter(item => item.kind !== 'claude-native') }, signal);
|
|
68
|
-
}
|
|
69
|
-
finally {
|
|
70
|
-
await ledger.close();
|
|
71
|
-
}
|
|
72
|
-
return session.generation;
|
|
73
|
-
}, signal);
|
|
74
|
-
return '';
|
|
75
|
-
}
|
|
76
|
-
if (input.hook_event_name === 'SessionEnd') {
|
|
77
|
-
const captured = await boundaries.end(signal);
|
|
78
|
-
if (captured)
|
|
79
|
-
await detachDeliverySession(mainOwner, captured.generation, env, signal);
|
|
80
|
-
return '';
|
|
81
|
-
}
|
|
82
|
-
if (input.hook_event_name === 'UserPromptSubmit') {
|
|
83
|
-
await boundaries.prompt(input.prompt_id, signal);
|
|
84
|
-
if (!input.prompt_id)
|
|
85
|
-
return '';
|
|
86
|
-
if (input.prompt) {
|
|
87
|
-
const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal });
|
|
88
|
-
try {
|
|
89
|
-
for (const frame of observedFrames(input.prompt)) {
|
|
90
|
-
const handoff = await ledger.handoff(frame.token, signal);
|
|
91
|
-
if (!handoff || handoff.owner !== owner || handoff.routeKind !== route || handoff.payloadHash !== frame.payloadHash)
|
|
92
|
-
continue;
|
|
93
|
-
await ledger.observePresentation(handoff, undefined, signal);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
finally {
|
|
97
|
-
await ledger.close();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
await presentPendingAtBoundary(owner, async (context) => {
|
|
101
|
-
await sink(`${JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: context } })}\n`);
|
|
102
|
-
}, env, signal);
|
|
103
|
-
return '';
|
|
104
|
-
}
|
|
105
|
-
if (input.hook_event_name === 'PreToolUse' && input.tool_name?.startsWith('mcp__plugin_square_square__')) {
|
|
106
|
-
if (!input.tool_use_id || !input.cwd) {
|
|
107
|
-
await denyMcpTool(sink);
|
|
108
|
-
return '';
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
await beginClaudeMcpCall({ toolUseId: input.tool_use_id, sessionId: input.session_id, cwd: input.cwd, agentId: input.agent_id }, env, signal);
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
await denyMcpTool(sink);
|
|
115
|
-
return '';
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if ((input.hook_event_name === 'PostToolUse' || input.hook_event_name === 'PostToolUseFailure')
|
|
119
|
-
&& input.tool_name?.startsWith('mcp__plugin_square_square__') && input.tool_use_id) {
|
|
120
|
-
await endClaudeMcpCallForSession(input.tool_use_id, input.session_id, env, input.agent_id, signal).catch(() => undefined);
|
|
121
|
-
}
|
|
122
|
-
if (input.hook_event_name === 'PostToolUse') {
|
|
123
|
-
await presentPendingAtBoundary(owner, async (context) => {
|
|
124
|
-
await sink(`${JSON.stringify({ hookSpecificOutput: { hookEventName: input.hook_event_name, additionalContext: context } })}\n`);
|
|
125
|
-
}, env, signal);
|
|
126
|
-
}
|
|
127
|
-
return '';
|
|
128
|
-
}
|
|
129
|
-
async function denyMcpTool(sink) {
|
|
130
|
-
try {
|
|
131
|
-
await sink(`${JSON.stringify({ hookSpecificOutput: {
|
|
132
|
-
hookEventName: 'PreToolUse', permissionDecision: 'deny',
|
|
133
|
-
permissionDecisionReason: 'Square MCP call identity is unavailable; this call did not run.',
|
|
134
|
-
} })}\n`);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
process.exitCode = 2;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
export async function runClaudeWatch(text, env = process.env, signal, output = frame => new Promise((resolve, reject) => {
|
|
141
|
-
process.stderr.write(frame, error => error ? reject(error) : resolve());
|
|
142
|
-
})) {
|
|
143
|
-
const input = parseInput(text);
|
|
144
|
-
if (!input || input.hook_event_name !== 'Stop' || !input.prompt_id)
|
|
145
|
-
return '';
|
|
146
|
-
const owner = ownerFor(input);
|
|
147
|
-
const boundaries = claudeBoundaryStore(input.session_id, env);
|
|
148
|
-
const controller = new AbortController();
|
|
149
|
-
const timer = setTimeout(() => controller.abort(), 86_400_000);
|
|
150
|
-
const stop = () => controller.abort();
|
|
151
|
-
process.once('SIGTERM', stop);
|
|
152
|
-
process.once('SIGINT', stop);
|
|
153
|
-
const combined = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
154
|
-
try {
|
|
155
|
-
const captured = await boundaries.capture(input.prompt_id, combined);
|
|
156
|
-
if (!captured)
|
|
157
|
-
return '';
|
|
158
|
-
await boundaries.watch(combined, async () => {
|
|
159
|
-
if (!await boundaries.current(captured, combined))
|
|
160
|
-
return;
|
|
161
|
-
const ledger = await DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal: combined });
|
|
162
|
-
const lifetime = retainDeliveryLedger(ledger);
|
|
163
|
-
const observer = await ledger.observe(owner);
|
|
164
|
-
try {
|
|
165
|
-
while (!combined.aborted) {
|
|
166
|
-
let version = await observer.read(combined);
|
|
167
|
-
const live = await boundaries.current(captured, combined);
|
|
168
|
-
const session = await ledger.session(owner, combined);
|
|
169
|
-
if (!live || session?.state !== 'attached' || session.generation !== captured.generation || !session.routes.some(item => item.kind === route))
|
|
170
|
-
return;
|
|
171
|
-
const cards = await ledger.pending(owner, { generation: captured.generation }, combined);
|
|
172
|
-
const held = new Set((await ledger.handoffs(owner, { generation: captured.generation }, combined))
|
|
173
|
-
.filter(attempt => !['failed', 'released'].includes(attempt.outcome)).map(attempt => attempt.seq));
|
|
174
|
-
for (const card of cards) {
|
|
175
|
-
if (held.has(card.seq))
|
|
176
|
-
continue;
|
|
177
|
-
const transport = {
|
|
178
|
-
kind: route,
|
|
179
|
-
async send(deliveryCard, address, context) {
|
|
180
|
-
if (address.sessionId !== input.session_id)
|
|
181
|
-
return 'not-capable';
|
|
182
|
-
const frame = claudeFrame(deliveryCard, context.token);
|
|
183
|
-
const payloadHash = createHash('sha256').update(frame).digest('hex');
|
|
184
|
-
if (!await context.prepare({ token: context.token, messageId: context.token, payloadHash }))
|
|
185
|
-
return 'not-capable';
|
|
186
|
-
const currentSession = await ledger.session(owner, context.signal);
|
|
187
|
-
if (currentSession?.state !== 'attached' || currentSession.generation !== captured.generation
|
|
188
|
-
|| !currentSession.routes.some(item => item.kind === route) || !await context.beforeSend())
|
|
189
|
-
return 'not-capable';
|
|
190
|
-
const native = await boundaries.current(captured, context.signal);
|
|
191
|
-
if (!native || context.signal.aborted || Date.now() >= context.deadline)
|
|
192
|
-
return 'not-capable';
|
|
193
|
-
try {
|
|
194
|
-
await output(frame);
|
|
195
|
-
}
|
|
196
|
-
finally {
|
|
197
|
-
await boundaries.consume(captured, AbortSignal.timeout(5000));
|
|
198
|
-
}
|
|
199
|
-
process.exitCode = 2;
|
|
200
|
-
return 'unknown';
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
const result = await ledger.dispatchHandoff(owner, card.key, transport, {
|
|
204
|
-
generation: captured.generation, timeoutMs: 5000, signal: combined,
|
|
205
|
-
});
|
|
206
|
-
lifetime.track(result);
|
|
207
|
-
if (result.outcome === 'unknown' || result.outcome === 'accepted' || result.outcome === 'presented')
|
|
208
|
-
return;
|
|
209
|
-
// Drain our own unsuccessful claim/settlement edge before waiting;
|
|
210
|
-
// a not-capable route must not make filesystem hints a retry loop.
|
|
211
|
-
version = await observer.read(combined);
|
|
212
|
-
}
|
|
213
|
-
await observer.changed(version, 1000, combined);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
finally {
|
|
217
|
-
observer.close();
|
|
218
|
-
await lifetime.close();
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
if (!combined.aborted)
|
|
224
|
-
return '';
|
|
225
|
-
}
|
|
226
|
-
finally {
|
|
227
|
-
clearTimeout(timer);
|
|
228
|
-
process.off('SIGTERM', stop);
|
|
229
|
-
process.off('SIGINT', stop);
|
|
230
|
-
}
|
|
231
|
-
return '';
|
|
232
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type LocalEndpoint } from './local-endpoint.js';
|
|
2
|
-
export interface ClaudeAuthOptions {
|
|
3
|
-
readonly endpoint: LocalEndpoint | string;
|
|
4
|
-
readonly token?: string;
|
|
5
|
-
readonly claudeHome?: string;
|
|
6
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
7
|
-
readonly platform?: NodeJS.Platform;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Explicit token, then the environment token bound to this exact endpoint, then
|
|
11
|
-
* the newest readable peer key for it. Windows requires a token; POSIX does not.
|
|
12
|
-
*/
|
|
13
|
-
export declare function resolveClaudeToken(options: ClaudeAuthOptions, signal?: AbortSignal): Promise<string | undefined>;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { DeliveryError } from './errors.js';
|
|
6
|
-
import { parseLocalEndpoint } from './local-endpoint.js';
|
|
7
|
-
const TOKEN = /^[0-9a-f]{32}$/;
|
|
8
|
-
const KEY = /^(\d+)\.([0-9a-f]{64})\.key$/;
|
|
9
|
-
function validToken(value) { return typeof value === 'string' && TOKEN.test(value); }
|
|
10
|
-
/**
|
|
11
|
-
* Explicit token, then the environment token bound to this exact endpoint, then
|
|
12
|
-
* the newest readable peer key for it. Windows requires a token; POSIX does not.
|
|
13
|
-
*/
|
|
14
|
-
export async function resolveClaudeToken(options, signal) {
|
|
15
|
-
signal?.throwIfAborted();
|
|
16
|
-
const endpoint = typeof options.endpoint === 'string' ? parseLocalEndpoint(options.endpoint) : options.endpoint;
|
|
17
|
-
const platform = options.platform ?? process.platform;
|
|
18
|
-
const env = options.env ?? process.env;
|
|
19
|
-
if (options.token !== undefined) {
|
|
20
|
-
if (!validToken(options.token))
|
|
21
|
-
throw new DeliveryError('authentication_failed', false);
|
|
22
|
-
return options.token;
|
|
23
|
-
}
|
|
24
|
-
if (validToken(env.CLAUDE_CODE_MESSAGING_TOKEN) && env.CLAUDE_CODE_MESSAGING_SOCKET !== undefined) {
|
|
25
|
-
try {
|
|
26
|
-
if (parseLocalEndpoint(env.CLAUDE_CODE_MESSAGING_SOCKET).canonical === endpoint.canonical)
|
|
27
|
-
return env.CLAUDE_CODE_MESSAGING_TOKEN;
|
|
28
|
-
}
|
|
29
|
-
catch { /* an unrelated malformed environment coordinate is ignored */ }
|
|
30
|
-
}
|
|
31
|
-
const home = options.claudeHome ?? env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude');
|
|
32
|
-
const digest = createHash('sha256').update(endpoint.canonical).digest('hex');
|
|
33
|
-
const keys = [];
|
|
34
|
-
try {
|
|
35
|
-
for (const name of await readdir(join(home, 'sessions'))) {
|
|
36
|
-
signal?.throwIfAborted();
|
|
37
|
-
const match = KEY.exec(name);
|
|
38
|
-
if (!match || match[2] !== digest)
|
|
39
|
-
continue;
|
|
40
|
-
try {
|
|
41
|
-
keys.push({ name, pid: Number(match[1]), mtimeMs: (await stat(join(home, 'sessions', name))).mtimeMs });
|
|
42
|
-
}
|
|
43
|
-
catch { /* unreadable entries are skipped */ }
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch { /* no registry is not an error; it only means no peer key */ }
|
|
47
|
-
signal?.throwIfAborted();
|
|
48
|
-
keys.sort((a, b) => Number(isAlive(b.pid)) - Number(isAlive(a.pid)) || b.mtimeMs - a.mtimeMs);
|
|
49
|
-
for (const key of keys) {
|
|
50
|
-
signal?.throwIfAborted();
|
|
51
|
-
try {
|
|
52
|
-
const value = JSON.parse(await readFile(join(home, 'sessions', key.name), { encoding: 'utf8', signal }));
|
|
53
|
-
if (value && typeof value === 'object' && validToken(value.peerToken)) {
|
|
54
|
-
return value.peerToken;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
catch { /* the next readable matching key wins */ }
|
|
58
|
-
}
|
|
59
|
-
signal?.throwIfAborted();
|
|
60
|
-
if (platform === 'win32')
|
|
61
|
-
throw new DeliveryError('authentication_failed', false);
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
/** A pid that exists, including one owned by another user, holds a live inbox. */
|
|
65
|
-
function isAlive(pid) {
|
|
66
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
67
|
-
return false;
|
|
68
|
-
try {
|
|
69
|
-
process.kill(pid, 0);
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
return error.code === 'EPERM';
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** Plain native coordinate; no Square facts, credentials or claimed authority. */
|
|
2
|
-
export interface ClaudeNativeTarget {
|
|
3
|
-
readonly sessionId: string;
|
|
4
|
-
readonly endpoint: string;
|
|
5
|
-
readonly token?: string;
|
|
6
|
-
readonly claudeHome?: string;
|
|
7
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
8
|
-
readonly platform?: NodeJS.Platform;
|
|
9
|
-
readonly messageId?: string;
|
|
10
|
-
}
|
|
11
|
-
export interface ClaudeNativeControl {
|
|
12
|
-
readonly deadline: number;
|
|
13
|
-
readonly signal?: AbortSignal;
|
|
14
|
-
/** Final asynchronous eligibility check after connecting, before writing bytes. */
|
|
15
|
-
readonly beforeSend?: () => Promise<boolean>;
|
|
16
|
-
}
|
|
17
|
-
export type NativeWriteResult = {
|
|
18
|
-
readonly outcome: 'written';
|
|
19
|
-
readonly message?: never;
|
|
20
|
-
} | {
|
|
21
|
-
readonly outcome: 'unknown';
|
|
22
|
-
readonly code: 'aborted' | 'timeout' | 'transport';
|
|
23
|
-
readonly message: string;
|
|
24
|
-
} | {
|
|
25
|
-
readonly outcome: 'unavailable';
|
|
26
|
-
readonly code: 'aborted' | 'timeout' | 'endpoint_unavailable' | 'invalid_arguments' | 'authentication_failed';
|
|
27
|
-
readonly message: string;
|
|
28
|
-
};
|
|
29
|
-
/** Submit once. Written means local bytes only, never native admission or consumption. */
|
|
30
|
-
export declare function writeClaudeNative(target: ClaudeNativeTarget, text: string, control: ClaudeNativeControl): Promise<NativeWriteResult>;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { deadline } from './deadline.js';
|
|
3
|
-
import { DeliveryError } from './errors.js';
|
|
4
|
-
import { connectLocal, LocalEndpointError, parseLocalEndpoint, writeFrames } from './local-endpoint.js';
|
|
5
|
-
import { resolveClaudeToken } from './claude-auth.js';
|
|
6
|
-
const detail = (code) => code === 'aborted' ? 'Native inbox write aborted.'
|
|
7
|
-
: code === 'timeout' ? 'Native inbox write deadline elapsed.'
|
|
8
|
-
: code === 'authentication_failed' ? 'Claude authentication failed.'
|
|
9
|
-
: 'Native inbox endpoint unavailable.';
|
|
10
|
-
/** Submit once. Written means local bytes only, never native admission or consumption. */
|
|
11
|
-
export async function writeClaudeNative(target, text, control) {
|
|
12
|
-
const remaining = control?.deadline - Date.now();
|
|
13
|
-
let endpoint;
|
|
14
|
-
try {
|
|
15
|
-
endpoint = parseLocalEndpoint(target?.endpoint);
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return { outcome: 'unavailable', code: 'invalid_arguments', message: 'Invalid native inbox arguments.' };
|
|
19
|
-
}
|
|
20
|
-
if (!target || typeof target.sessionId !== 'string' || !target.sessionId.trim() ||
|
|
21
|
-
typeof text !== 'string' || !text.length || !control ||
|
|
22
|
-
!Number.isFinite(control.deadline) || control.deadline <= 0 || remaining > 2_147_483_647 ||
|
|
23
|
-
(control.signal !== undefined && !(control.signal instanceof AbortSignal))) {
|
|
24
|
-
return { outcome: 'unavailable', code: 'invalid_arguments', message: 'Invalid native inbox arguments.' };
|
|
25
|
-
}
|
|
26
|
-
if (control.signal?.aborted)
|
|
27
|
-
return { outcome: 'unavailable', code: 'aborted', message: detail('aborted') };
|
|
28
|
-
if (remaining <= 0)
|
|
29
|
-
return { outcome: 'unavailable', code: 'timeout', message: detail('timeout') };
|
|
30
|
-
const scope = deadline(control.deadline - Date.now(), control.signal);
|
|
31
|
-
let socket;
|
|
32
|
-
try {
|
|
33
|
-
scope.check();
|
|
34
|
-
const token = await scope.wait(resolveClaudeToken({ endpoint, token: target.token, claudeHome: target.claudeHome, env: target.env, platform: target.platform }, scope.signal));
|
|
35
|
-
const messageId = target.messageId ?? randomUUID();
|
|
36
|
-
const frames = [
|
|
37
|
-
...(token === undefined ? [] : [{ type: 'auth', token }]),
|
|
38
|
-
// Claude's SDK custody events echo uuid, not the peer-protocol msg_id.
|
|
39
|
-
{ msgV: 1, msg_id: messageId, uuid: messageId, type: 'user', session_id: target.sessionId, message: { role: 'user', content: text }, priority: 'next' },
|
|
40
|
-
];
|
|
41
|
-
scope.check();
|
|
42
|
-
if (Date.now() >= control.deadline)
|
|
43
|
-
throw new LocalEndpointError('timeout');
|
|
44
|
-
socket = await connectLocal(endpoint, { signal: scope.signal });
|
|
45
|
-
scope.check();
|
|
46
|
-
if (Date.now() >= control.deadline)
|
|
47
|
-
throw new LocalEndpointError('timeout');
|
|
48
|
-
if (control.beforeSend && !await scope.wait(control.beforeSend()))
|
|
49
|
-
return { outcome: 'unavailable', code: 'aborted', message: 'Native inbox write suppressed.' };
|
|
50
|
-
scope.check();
|
|
51
|
-
if (Date.now() >= control.deadline)
|
|
52
|
-
throw new LocalEndpointError('timeout');
|
|
53
|
-
// A receiver that never drains must not hold the caller past its deadline.
|
|
54
|
-
await scope.wait(writeFrames(socket, frames));
|
|
55
|
-
scope.check();
|
|
56
|
-
return { outcome: 'written' };
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
const local = error instanceof LocalEndpointError ? error : undefined;
|
|
60
|
-
const code = scope.code ?? (error instanceof DeliveryError ? error.code : local?.code);
|
|
61
|
-
if (code === 'authentication_failed') {
|
|
62
|
-
return { outcome: 'unavailable', code, message: detail(code) };
|
|
63
|
-
}
|
|
64
|
-
if (code === 'aborted' || code === 'timeout') {
|
|
65
|
-
return { outcome: socket ? 'unknown' : 'unavailable', code, message: detail(code) };
|
|
66
|
-
}
|
|
67
|
-
if (socket)
|
|
68
|
-
return { outcome: 'unknown', code: 'transport', message: 'Native inbox write unconfirmed.' };
|
|
69
|
-
return { outcome: 'unavailable', code: 'endpoint_unavailable', message: detail('unavailable') };
|
|
70
|
-
}
|
|
71
|
-
finally {
|
|
72
|
-
socket?.destroy();
|
|
73
|
-
scope.close();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { ClaudeConnectOptions, ClaudeDeliveryResult, ClaudeSendTextOptions, ClaudeTarget } from './types.js';
|
|
2
|
-
/** Resolve an explicitly identified existing Claude native inbox; never discovers or starts one. */
|
|
3
|
-
export declare function connectExisting(options: ClaudeConnectOptions): Promise<ClaudeTarget>;
|
|
4
|
-
/** Write once to Claude's next native boundary; local write is never native admission. */
|
|
5
|
-
export declare function sendText(target: ClaudeTarget, text: string, options?: ClaudeSendTextOptions): Promise<ClaudeDeliveryResult>;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { deadline, validSignal, validTimeout } from './deadline.js';
|
|
3
|
-
import { ConnectionError, DeliveryError } from './errors.js';
|
|
4
|
-
import { isIdentity, isRecord } from './guards.js';
|
|
5
|
-
import { parseLocalEndpoint } from './local-endpoint.js';
|
|
6
|
-
import { writeClaudeNative } from './claude-native.js';
|
|
7
|
-
import { resolveClaudeToken } from './claude-auth.js';
|
|
8
|
-
const coordinates = new WeakMap();
|
|
9
|
-
/** Resolve an explicitly identified existing Claude native inbox; never discovers or starts one. */
|
|
10
|
-
export async function connectExisting(options) {
|
|
11
|
-
if (!isRecord(options) || options.harness !== 'claude' || !isIdentity(options.sessionId) ||
|
|
12
|
-
typeof options.endpoint !== 'string' || options.endpoint.includes('\0') ||
|
|
13
|
-
!validTimeout(options.timeoutMs) || !validSignal(options.signal))
|
|
14
|
-
throw new ConnectionError('invalid_arguments');
|
|
15
|
-
let endpoint;
|
|
16
|
-
try {
|
|
17
|
-
endpoint = parseLocalEndpoint(options.endpoint);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
throw new ConnectionError('invalid_arguments');
|
|
21
|
-
}
|
|
22
|
-
if (!(process.platform === 'darwin' || process.platform === 'linux' || process.platform === 'win32'))
|
|
23
|
-
throw new ConnectionError('unsupported_platform');
|
|
24
|
-
if (process.platform === 'win32' ? endpoint.kind !== 'pipe' : endpoint.kind !== 'unix')
|
|
25
|
-
throw new ConnectionError('invalid_arguments');
|
|
26
|
-
const scope = deadline(options.timeoutMs, options.signal);
|
|
27
|
-
try {
|
|
28
|
-
scope.check();
|
|
29
|
-
// The ambient environment and platform are the only auth coordinates here.
|
|
30
|
-
const token = await scope.wait(resolveClaudeToken({ endpoint, token: options.token, claudeHome: options.claudeHome }, scope.signal));
|
|
31
|
-
scope.check();
|
|
32
|
-
const target = Object.freeze({ harness: 'claude', sessionId: options.sessionId });
|
|
33
|
-
coordinates.set(target, { endpoint: options.endpoint, token, claudeHome: options.claudeHome });
|
|
34
|
-
return target;
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
if (scope.code !== undefined)
|
|
38
|
-
throw new ConnectionError(scope.code);
|
|
39
|
-
if (error instanceof ConnectionError)
|
|
40
|
-
throw error;
|
|
41
|
-
if (error instanceof DeliveryError)
|
|
42
|
-
throw new ConnectionError(error.code);
|
|
43
|
-
throw new ConnectionError('invalid_response');
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
scope.close();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** Write once to Claude's next native boundary; local write is never native admission. */
|
|
50
|
-
export async function sendText(target, text, options = {}) {
|
|
51
|
-
const coordinate = isRecord(target) ? coordinates.get(target) : undefined;
|
|
52
|
-
if (!coordinate || target.harness !== 'claude' || !isIdentity(target.sessionId) || typeof text !== 'string' || !text.length || !isRecord(options) ||
|
|
53
|
-
(options.delivery !== undefined && options.delivery !== 'steer') || options.inputId !== undefined ||
|
|
54
|
-
!validTimeout(options.timeoutMs) || !validSignal(options.signal))
|
|
55
|
-
throw new TypeError('Invalid Claude sendText arguments.');
|
|
56
|
-
const messageId = randomUUID();
|
|
57
|
-
const result = await writeClaudeNative({ ...coordinate, sessionId: target.sessionId, messageId }, text, { deadline: Date.now() + (options.timeoutMs ?? 5_000), signal: options.signal });
|
|
58
|
-
const attempt = { harness: 'claude', sessionId: target.sessionId };
|
|
59
|
-
if (result.outcome === 'written')
|
|
60
|
-
return { ...attempt, state: 'written', messageId };
|
|
61
|
-
if (result.outcome === 'unknown')
|
|
62
|
-
return { ...attempt, state: 'unknown', code: result.code };
|
|
63
|
-
if (result.code === 'invalid_arguments')
|
|
64
|
-
throw new TypeError('Invalid Claude sendText arguments.');
|
|
65
|
-
return { ...attempt, state: 'unavailable', code: result.code };
|
|
66
|
-
}
|