@astrosheep/square 0.3.85 → 0.3.87
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/artifact.d.ts +6 -1
- package/dist/artifact.js +79 -10
- package/dist/boundary-presentation.d.ts +5 -4
- package/dist/boundary-presentation.js +21 -25
- package/dist/catch-decisions.js +2 -1
- package/dist/claude-delivery-status.d.ts +8 -2
- package/dist/claude-delivery-status.js +26 -10
- package/dist/claude-delivery.d.ts +8 -4
- package/dist/claude-delivery.js +114 -104
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +119 -105
- package/dist/cli/context.d.ts +3 -1
- package/dist/cli/context.js +25 -1
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +15 -12
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +2 -1
- package/dist/cli/registry.js +0 -2
- package/dist/cli/square-commands.d.ts +4 -2
- package/dist/cli/square-commands.js +21 -6
- package/dist/codex-hook.d.ts +3 -3
- package/dist/codex-hook.js +4 -4
- package/dist/codex-queue.d.ts +5 -6
- package/dist/codex-queue.js +9 -5
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +18 -6
- package/dist/delivery-health.d.ts +32 -2
- package/dist/delivery-health.js +108 -45
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +155 -196
- package/dist/delivery.d.ts +14 -23
- package/dist/delivery.js +48 -18
- package/dist/file-lock.d.ts +8 -1
- package/dist/file-lock.js +2 -0
- package/dist/help.js +12 -14
- package/dist/hints.js +2 -3
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -3
- package/dist/list.js +9 -4
- package/dist/mcp.js +12 -1
- package/dist/model.d.ts +39 -5
- package/dist/model.js +84 -0
- package/dist/notifications.d.ts +8 -6
- package/dist/notifications.js +56 -143
- package/dist/operation-presentation.js +12 -14
- package/dist/participant-identity.d.ts +0 -3
- package/dist/participant-identity.js +0 -31
- package/dist/paseo-delivery.d.ts +5 -6
- package/dist/paseo-delivery.js +13 -8
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/delivery.d.ts +8 -3
- package/dist/plugins/opencode/delivery.js +187 -132
- package/dist/plugins/opencode/index.js +12 -38
- package/dist/ports.d.ts +112 -40
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +51 -35
- package/dist/presentation.d.ts +0 -1
- package/dist/presentation.js +18 -13
- package/dist/seats.d.ts +7 -0
- package/dist/seats.js +27 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +10 -12
- package/dist/sqlite-square.d.ts +53 -0
- package/dist/sqlite-square.js +602 -0
- package/dist/square-api.d.ts +12 -41
- package/dist/square-api.js +26 -474
- package/dist/square-backend.d.ts +13 -0
- package/dist/square-core.d.ts +3 -2
- package/dist/square-domain.d.ts +12 -5
- package/dist/square-domain.js +163 -7
- package/dist/square-facade.d.ts +16 -10
- package/dist/square-index.d.ts +24 -0
- package/dist/square-index.js +131 -0
- package/dist/square-legacy-framing.d.ts +9 -0
- package/dist/square-legacy-framing.js +56 -0
- package/dist/square-location.d.ts +15 -0
- package/dist/square-location.js +76 -0
- package/dist/square-migration-host-ledger.d.ts +26 -0
- package/dist/square-migration-host-ledger.js +168 -0
- package/dist/square-migration.d.ts +5 -5
- package/dist/square-migration.js +373 -71
- package/dist/square-sqlite.d.ts +9 -9
- package/dist/square-sqlite.js +329 -79
- package/dist/square-stdio.d.ts +22 -0
- package/dist/square-stdio.js +222 -0
- package/dist/square-tools.js +30 -3
- package/dist/stdio-server.d.ts +3 -0
- package/dist/stdio-server.js +192 -0
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +50 -70
- package/dist/wakes.d.ts +6 -5
- package/dist/wakes.js +20 -17
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/backends.md +180 -0
- package/guides/claude-native.md +9 -9
- package/guides/mcp.md +1 -1
- package/guides/migration.md +107 -67
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +69 -30
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/register.js +1 -1
- package/plugins/claude/skills/square/SKILL.md +7 -7
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +170 -118
- package/plugins/opencode/index.ts +12 -30
- package/plugins/pi/square-pi.js +58 -20
- package/skills/square/SKILL.md +7 -7
- package/dist/cli/ledger-command.d.ts +0 -12
- package/dist/cli/ledger-command.js +0 -72
- package/dist/host-ledger-file-adapter.d.ts +0 -47
- package/dist/host-ledger-file-adapter.js +0 -384
- package/dist/host-ledger-migration.d.ts +0 -27
- package/dist/host-ledger-migration.js +0 -264
- package/dist/host-ledger-root.d.ts +0 -2
- package/dist/host-ledger-root.js +0 -9
- package/dist/host-ledger-sqlite.d.ts +0 -65
- package/dist/host-ledger-sqlite.js +0 -368
- package/dist/host-ledger.d.ts +0 -195
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -36
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -22
- package/dist/participant-host.js +0 -92
- package/dist/registry.d.ts +0 -55
- package/dist/registry.js +0 -123
- package/dist/routes.d.ts +0 -39
- package/dist/routes.js +0 -99
- package/dist/square-actions.d.ts +0 -56
- package/dist/square-actions.js +0 -353
- package/dist/square-projections.d.ts +0 -31
- package/dist/square-projections.js +0 -64
- package/dist/wake-attempts.d.ts +0 -10
- package/dist/wake-attempts.js +0 -39
- package/dist/wake-eligibility.d.ts +0 -53
- package/dist/wake-eligibility.js +0 -123
- package/dist/wake-evidence.d.ts +0 -33
- package/dist/wake-evidence.js +0 -32
- package/guides/host-ledger.md +0 -58
- package/guides/indexed-host-ledger-performance.md +0 -39
- /package/dist/{host-ledger.js → square-backend.js} +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { SquareError } from './model.js';
|
|
5
|
+
import { harnessSessionSources } from './participant-identity.js';
|
|
6
|
+
import { requireJoinName } from './seats.js';
|
|
7
|
+
export const STDIO_PROTOCOL_VERSION = 1;
|
|
8
|
+
export const STDIO_MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
9
|
+
export const identityVariables = [...harnessSessionSources.flatMap(source => [source.variable, ...(source.childVariable ? [source.childVariable] : [])])];
|
|
10
|
+
export function wireIdentity(env) {
|
|
11
|
+
return Object.fromEntries(identityVariables.map(key => [key, env[key] ?? '']));
|
|
12
|
+
}
|
|
13
|
+
export function encodeError(error) {
|
|
14
|
+
const e = error;
|
|
15
|
+
return { code: -32000, message: e.message ?? 'Backend request failed', data: { code: e.code, facts: e.facts, operation: e.operation } };
|
|
16
|
+
}
|
|
17
|
+
function decodeError(error) {
|
|
18
|
+
return Object.assign(error.data?.code ? new SquareError(error.data.code, error.message, error.data.facts) : new Error(error.message), error.data?.operation ? { operation: error.data.operation } : {});
|
|
19
|
+
}
|
|
20
|
+
export function unsupported(feature) {
|
|
21
|
+
throw new SquareError('invalid_args', `The stdio backend does not support ${feature}; use explicit catch/history or a local resource`);
|
|
22
|
+
}
|
|
23
|
+
/** One process per opened Square. No replay, pooling, or operation policy. */
|
|
24
|
+
class Connection {
|
|
25
|
+
nextId = 0;
|
|
26
|
+
closed = false;
|
|
27
|
+
closing;
|
|
28
|
+
pending = new Map();
|
|
29
|
+
child;
|
|
30
|
+
exited;
|
|
31
|
+
constructor(command, args, cwd, env) {
|
|
32
|
+
this.child = spawn(command, [...args], { cwd, env, shell: false, stdio: 'pipe' });
|
|
33
|
+
this.exited = new Promise(resolve => this.child.once('close', () => resolve()));
|
|
34
|
+
// Diagnostic bytes are deliberately not included in errors (they can contain credentials).
|
|
35
|
+
this.child.stderr.resume();
|
|
36
|
+
const lines = createInterface({ input: this.child.stdout, crlfDelay: Infinity });
|
|
37
|
+
let frameBytes = 0;
|
|
38
|
+
this.child.stdout.on('data', chunk => {
|
|
39
|
+
for (const byte of chunk) {
|
|
40
|
+
if (byte === 10)
|
|
41
|
+
frameBytes = 0;
|
|
42
|
+
else if (++frameBytes > STDIO_MAX_FRAME_BYTES) {
|
|
43
|
+
this.fail('Backend frame exceeds the protocol limit');
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
lines.on('line', line => {
|
|
49
|
+
try {
|
|
50
|
+
const message = JSON.parse(line);
|
|
51
|
+
if (message.jsonrpc !== '2.0')
|
|
52
|
+
throw new Error();
|
|
53
|
+
if (message.method === 'square/progress') {
|
|
54
|
+
const pending = this.pending.get(message.params?.id);
|
|
55
|
+
if (pending && message.params?.progress) {
|
|
56
|
+
try {
|
|
57
|
+
pending.control?.onProgress?.(message.params.progress);
|
|
58
|
+
}
|
|
59
|
+
catch { /* progress observers cannot corrupt framing */ }
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!Number.isSafeInteger(message.id) || (Object.hasOwn(message, 'result') === Object.hasOwn(message, 'error')))
|
|
64
|
+
throw new Error();
|
|
65
|
+
const pending = this.pending.get(message.id);
|
|
66
|
+
if (!pending)
|
|
67
|
+
throw new Error();
|
|
68
|
+
this.pending.delete(message.id);
|
|
69
|
+
pending.dispose();
|
|
70
|
+
if (message.error)
|
|
71
|
+
pending.reject(decodeError(message.error));
|
|
72
|
+
else
|
|
73
|
+
pending.resolve(message.result);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
this.fail('Malformed backend protocol response');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
lines.once('close', () => this.fail('Backend stdout ended'));
|
|
80
|
+
this.child.once('error', () => this.fail('Cannot start configured backend; check command and cwd'));
|
|
81
|
+
this.child.once('exit', () => this.fail('Backend process exited'));
|
|
82
|
+
this.child.stdin.on('error', () => this.fail('Backend stdin closed'));
|
|
83
|
+
}
|
|
84
|
+
fail(message) {
|
|
85
|
+
if (this.closed)
|
|
86
|
+
return;
|
|
87
|
+
this.closed = true;
|
|
88
|
+
for (const pending of this.pending.values()) {
|
|
89
|
+
pending.dispose();
|
|
90
|
+
const error = new SquareError('unavailable', message);
|
|
91
|
+
if (pending.method === 'express')
|
|
92
|
+
Object.assign(error, { operation: { operation: 'express', expressEffect: 'unknown' } });
|
|
93
|
+
pending.reject(error);
|
|
94
|
+
}
|
|
95
|
+
this.pending.clear();
|
|
96
|
+
this.child.stdin.destroy();
|
|
97
|
+
this.child.kill();
|
|
98
|
+
const timer = setTimeout(() => this.child.kill('SIGKILL'), 500);
|
|
99
|
+
timer.unref();
|
|
100
|
+
void this.exited.then(() => clearTimeout(timer));
|
|
101
|
+
}
|
|
102
|
+
request(method, params, control) {
|
|
103
|
+
if (control?.signal?.aborted)
|
|
104
|
+
return Promise.reject(control.signal.reason ?? new Error('Operation cancelled'));
|
|
105
|
+
if (this.closed)
|
|
106
|
+
return Promise.reject(new SquareError('unavailable', 'Backend connection is closed'));
|
|
107
|
+
const id = ++this.nextId;
|
|
108
|
+
let frame;
|
|
109
|
+
try {
|
|
110
|
+
frame = JSON.stringify({ jsonrpc: '2.0', id, method, params }, (_key, value) => {
|
|
111
|
+
if (typeof value === 'number' && !Number.isFinite(value))
|
|
112
|
+
throw new Error('Non-finite number');
|
|
113
|
+
return value;
|
|
114
|
+
}) + '\n';
|
|
115
|
+
if (Buffer.byteLength(frame) > STDIO_MAX_FRAME_BYTES)
|
|
116
|
+
throw new Error('Frame too large');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
const error = new SquareError('invalid_args', 'Backend request must be JSON-serializable with finite numbers and fit the protocol frame limit');
|
|
120
|
+
if (method === 'express')
|
|
121
|
+
Object.assign(error, { operation: { operation: 'express', expressEffect: 'unsent' } });
|
|
122
|
+
return Promise.reject(error);
|
|
123
|
+
}
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const abort = () => {
|
|
126
|
+
// Wait for the response: server cleanup (including catch lease release) must finish first.
|
|
127
|
+
if (!this.closed)
|
|
128
|
+
this.child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: '$/cancelRequest', params: { id } }) + '\n');
|
|
129
|
+
};
|
|
130
|
+
this.pending.set(id, { resolve, reject, method, control, dispose: () => control?.signal?.removeEventListener('abort', abort) });
|
|
131
|
+
control?.signal?.addEventListener('abort', abort, { once: true });
|
|
132
|
+
this.child.stdin.write(frame);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
close() {
|
|
136
|
+
return this.closing ??= (async () => {
|
|
137
|
+
const timer = setTimeout(() => this.fail('Backend close timed out'), 1000);
|
|
138
|
+
try {
|
|
139
|
+
if (!this.closed)
|
|
140
|
+
await this.request('square/close', {});
|
|
141
|
+
}
|
|
142
|
+
catch { /* disconnect already rejects outstanding work */ }
|
|
143
|
+
finally {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
this.fail('Backend connection closed');
|
|
146
|
+
await this.exited;
|
|
147
|
+
}
|
|
148
|
+
})();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
class StdioSquare {
|
|
152
|
+
location;
|
|
153
|
+
connection;
|
|
154
|
+
handle;
|
|
155
|
+
constructor(location, connection, handle) {
|
|
156
|
+
this.location = location;
|
|
157
|
+
this.connection = connection;
|
|
158
|
+
this.handle = handle;
|
|
159
|
+
}
|
|
160
|
+
call(method, args, control, participant) {
|
|
161
|
+
return this.connection.request(method, { handle: this.handle, args, ...(participant === undefined ? {} : { participant }) }, control);
|
|
162
|
+
}
|
|
163
|
+
participantHandle(name) {
|
|
164
|
+
return {
|
|
165
|
+
name,
|
|
166
|
+
express: ((bodyOrInput, optionsOrControl, control) => {
|
|
167
|
+
if (typeof bodyOrInput === 'object' && bodyOrInput !== null)
|
|
168
|
+
return this.call('express', [bodyOrInput, undefined], optionsOrControl, name);
|
|
169
|
+
return this.call('express', [bodyOrInput, optionsOrControl], control, name);
|
|
170
|
+
}),
|
|
171
|
+
catch: (options, control) => this.call('catch', [options], control, name),
|
|
172
|
+
listen: (target, options, control) => this.call('listen', [target, options], control, name),
|
|
173
|
+
ignore: (target, options, control) => this.call('ignore', [target, options], control, name),
|
|
174
|
+
unlisten: (target, options, control) => this.call('unlisten', [target, options], control, name),
|
|
175
|
+
listening: (options, control) => this.call('listening', [options], control, name),
|
|
176
|
+
hold: (reason, options, control) => this.call('hold', [reason, options], control, name),
|
|
177
|
+
resume: (options, control) => this.call('resume', [options], control, name),
|
|
178
|
+
done: (body, options, control) => this.call('done', [body, options], control, name),
|
|
179
|
+
history: (query, control) => this.history(query, control),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async participant(name) { return this.participantHandle(await this.call('participant', [name])); }
|
|
183
|
+
async join(...[name, options, control]) {
|
|
184
|
+
requireJoinName(name);
|
|
185
|
+
const result = await this.call('join', [name, options], control);
|
|
186
|
+
return { ...result, participant: this.participantHandle(result.participant) };
|
|
187
|
+
}
|
|
188
|
+
history(...[query, control]) { return this.call('history', [query], control); }
|
|
189
|
+
status(...[options, control]) { return this.call('status', [options], control); }
|
|
190
|
+
participants(...[options, control]) { return this.call('participants', [options], control); }
|
|
191
|
+
recovery(...[name, control]) { return this.call('recovery', [name], control); }
|
|
192
|
+
snapshot() { return Promise.reject(new SquareError('invalid_args', 'stdio does not support function-valued snapshot; use status/participants/history')); }
|
|
193
|
+
stream() { unsupported('stream'); }
|
|
194
|
+
recognize() { return Promise.reject(new SquareError('invalid_args', 'stdio does not support host recognition; select a participant explicitly')); }
|
|
195
|
+
implicitJoin() { unsupported('host implicit join'); }
|
|
196
|
+
endOwnedSession() { unsupported('host session cleanup'); }
|
|
197
|
+
cancelDeliveries() { unsupported('host delivery cancellation'); }
|
|
198
|
+
close() { return this.connection.close(); }
|
|
199
|
+
}
|
|
200
|
+
export async function openStdioSquare(location, selected, input, backends, build, control) {
|
|
201
|
+
if (input.clock || input.wakeTransport)
|
|
202
|
+
unsupported('client clock or wakeTransport injection');
|
|
203
|
+
if (control?.signal?.aborted)
|
|
204
|
+
throw control.signal.reason ?? new Error('Operation cancelled');
|
|
205
|
+
const backend = Object.hasOwn(backends, selected.backend) ? backends[selected.backend] : undefined;
|
|
206
|
+
if (!backend)
|
|
207
|
+
throw new SquareError('invalid_args', `Backend ${selected.backend} is not configured; set backends or SQUARE_BACKENDS_CONFIG`);
|
|
208
|
+
const cwd = input.cwd ?? process.cwd();
|
|
209
|
+
const command = backend.command.includes('/') || backend.command.includes('\\') ? path.resolve(cwd, backend.command) : backend.command;
|
|
210
|
+
const connection = new Connection(command, backend.args ?? [], path.resolve(cwd, backend.cwd ?? '.'), { ...(input.env ?? process.env), ...backend.env });
|
|
211
|
+
try {
|
|
212
|
+
const result = await connection.request('square/open', { version: STDIO_PROTOCOL_VERSION, key: selected.key, location,
|
|
213
|
+
identity: wireIdentity(input.env ?? process.env), body: input.body, participant: input.participant, build }, control);
|
|
214
|
+
if (result.version !== STDIO_PROTOCOL_VERSION || typeof result.handle !== 'string')
|
|
215
|
+
throw new SquareError('unavailable', 'Backend protocol version mismatch');
|
|
216
|
+
return new StdioSquare(location, connection, result.handle);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
await connection.close();
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
package/dist/square-tools.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod/v4';
|
|
|
2
2
|
import { isSquareError } from './model.js';
|
|
3
3
|
import { CATCH_DEFAULT_LIMIT } from './square-api.js';
|
|
4
4
|
import { renderCatchOutcome, renderExpression, renderFailureLines, renderHistoryResult, renderJoinResult, renderLifecycleResult, renderListenerChange, renderListeningResult, renderParticipantsResult, renderStatusResult, } from './operation-presentation.js';
|
|
5
|
-
const location = z.string().optional().describe('
|
|
5
|
+
const location = z.string().optional().describe('Local .square artifact path or configured backend URI (stdio://name/key). Relative paths use the current working directory. Defaults to the configured square.');
|
|
6
6
|
const participant = z.string().optional().describe('Participant name. Defaults to the configured participant or the current session’s unambiguous participant.');
|
|
7
7
|
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
8
8
|
const stringArray = z.array(z.string());
|
|
@@ -14,11 +14,38 @@ function observation(control) {
|
|
|
14
14
|
function rendering(render) {
|
|
15
15
|
return (result, args) => render(result, args);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Tool receipts are the one machine surface without activity metadata: a receipt copy drops every
|
|
19
|
+
* property named `metadata` while domain activities keep theirs for SDK, CLI, stdio and stream readers.
|
|
20
|
+
*/
|
|
21
|
+
function withoutActivityMetadata(value) {
|
|
22
|
+
if (Array.isArray(value))
|
|
23
|
+
return value.map(withoutActivityMetadata);
|
|
24
|
+
if (value === null || typeof value !== 'object')
|
|
25
|
+
return value;
|
|
26
|
+
const prototype = Object.getPrototypeOf(value);
|
|
27
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
28
|
+
return value;
|
|
29
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'metadata')
|
|
30
|
+
.map(([key, item]) => [key, withoutActivityMetadata(item)]));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Human text renders from the unfiltered receipt, so metadata-only says keep the placeholder the
|
|
34
|
+
* CLI prints and every other sentence stays identical. Machines only ever receive the filtered copy.
|
|
35
|
+
*/
|
|
36
|
+
const renderingReceipts = new WeakMap();
|
|
37
|
+
function renderingReceipt(receipt) {
|
|
38
|
+
return (renderingReceipts.get(receipt) ?? receipt);
|
|
39
|
+
}
|
|
17
40
|
function tool(name, description, shape, run, render) {
|
|
18
41
|
const inputSchema = z.object({ ...shape, location }).strict();
|
|
19
|
-
return { name, description, inputSchema, render
|
|
42
|
+
return { name, description, inputSchema, render: (receipt, args) => render(renderingReceipt(receipt), args),
|
|
43
|
+
run: async (square, args, control) => {
|
|
20
44
|
const { location: _location, ...operationArgs } = inputSchema.parse(args);
|
|
21
|
-
|
|
45
|
+
const receipt = await run(square, operationArgs, control);
|
|
46
|
+
const filtered = withoutActivityMetadata(receipt);
|
|
47
|
+
renderingReceipts.set(filtered, receipt);
|
|
48
|
+
return filtered;
|
|
22
49
|
} };
|
|
23
50
|
}
|
|
24
51
|
export const SQUARE_TOOL_INSTRUCTIONS = 'The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.';
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { SqliteSquare } from './sqlite-square.js';
|
|
7
|
+
import { SquareError, validateBody } from './model.js';
|
|
8
|
+
import { canonicalPath } from './canonical-path.js';
|
|
9
|
+
import { STDIO_PROTOCOL_VERSION, STDIO_MAX_FRAME_BYTES, encodeError, identityVariables } from './square-stdio.js';
|
|
10
|
+
import { parseSquareLocation } from './square-location.js';
|
|
11
|
+
/** Serve the existing resource API. This adapter contains no activity policy. */
|
|
12
|
+
export async function runSquareStdioServer(root) {
|
|
13
|
+
await fs.mkdir(root, { recursive: true });
|
|
14
|
+
root = await canonicalPath(root);
|
|
15
|
+
const baseEnv = { ...process.env };
|
|
16
|
+
let square;
|
|
17
|
+
let location;
|
|
18
|
+
let opening = false, closing = false;
|
|
19
|
+
const running = new Map();
|
|
20
|
+
const write = (message) => { if (!process.stdout.destroyed)
|
|
21
|
+
process.stdout.write(JSON.stringify(message) + '\n'); };
|
|
22
|
+
const response = (id, result) => write({ jsonrpc: '2.0', id, result });
|
|
23
|
+
const fail = (id, error) => write({ jsonrpc: '2.0', id, error: encodeError(error) });
|
|
24
|
+
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
25
|
+
let shutdown;
|
|
26
|
+
const close = () => shutdown ??= (async () => {
|
|
27
|
+
closing = true;
|
|
28
|
+
for (const request of running.values())
|
|
29
|
+
request.controller.abort();
|
|
30
|
+
await Promise.allSettled([...running.values()].map(request => request.work));
|
|
31
|
+
await square?.close();
|
|
32
|
+
lines.close();
|
|
33
|
+
process.stdin.pause();
|
|
34
|
+
})();
|
|
35
|
+
const fatal = () => { void close().finally(() => { process.exitCode = 1; }); };
|
|
36
|
+
let frameBytes = 0;
|
|
37
|
+
process.stdin.on('data', chunk => {
|
|
38
|
+
for (const byte of Buffer.from(chunk)) {
|
|
39
|
+
if (byte === 10)
|
|
40
|
+
frameBytes = 0;
|
|
41
|
+
else if (++frameBytes > STDIO_MAX_FRAME_BYTES) {
|
|
42
|
+
fatal();
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
process.stdout.on('error', fatal);
|
|
48
|
+
process.once('SIGTERM', fatal);
|
|
49
|
+
process.once('SIGINT', fatal);
|
|
50
|
+
lines.on('close', () => { if (!closing)
|
|
51
|
+
void close(); });
|
|
52
|
+
async function invoke(method, params, control) {
|
|
53
|
+
if (method === 'square/open') {
|
|
54
|
+
if (square || opening)
|
|
55
|
+
throw new SquareError('invalid_args', 'One logical open is allowed per connection');
|
|
56
|
+
opening = true;
|
|
57
|
+
try {
|
|
58
|
+
if (params.version !== STDIO_PROTOCOL_VERSION)
|
|
59
|
+
throw new SquareError('invalid_args', 'Unsupported Square stdio protocol version');
|
|
60
|
+
const parsed = parseSquareLocation(params.location);
|
|
61
|
+
if (parsed.kind !== 'stdio' || parsed.key !== params.key)
|
|
62
|
+
throw new SquareError('invalid_args', 'Open requires a matching stdio location and key');
|
|
63
|
+
const target = path.join(root, `${parsed.key}.square`);
|
|
64
|
+
const canonical = await canonicalPath(target);
|
|
65
|
+
if (path.dirname(canonical) !== root)
|
|
66
|
+
throw new SquareError('invalid_args', 'Square key escapes the configured backend root');
|
|
67
|
+
const env = { ...baseEnv };
|
|
68
|
+
for (const key of identityVariables) {
|
|
69
|
+
const value = params.identity?.[key];
|
|
70
|
+
if (value !== undefined && typeof value !== 'string')
|
|
71
|
+
throw new SquareError('invalid_args', 'Invalid operation identity');
|
|
72
|
+
env[key] = value ?? '';
|
|
73
|
+
}
|
|
74
|
+
const input = { path: target, env, body: params.body, participant: params.participant };
|
|
75
|
+
if (params.body !== undefined)
|
|
76
|
+
validateBody(params.body);
|
|
77
|
+
square = params.build === undefined ? await SqliteSquare.at(input, control, false)
|
|
78
|
+
: await SqliteSquare.build({ ...params.build, ...input }, control, false);
|
|
79
|
+
location = parsed.uri;
|
|
80
|
+
return { version: STDIO_PROTOCOL_VERSION, handle: 'square' };
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
opening = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!square || params.handle !== 'square')
|
|
87
|
+
throw new SquareError('invalid_args', 'Open a logical square first');
|
|
88
|
+
if (!Array.isArray(params.args))
|
|
89
|
+
throw new SquareError('invalid_args', 'Operation args must be an array');
|
|
90
|
+
// JSON null represents omitted optional arguments, never the meaningful history limit:null inside an options object.
|
|
91
|
+
const args = params.args.map((arg) => arg === null ? undefined : arg);
|
|
92
|
+
let result;
|
|
93
|
+
switch (method) {
|
|
94
|
+
case 'participant': return (await square.participant(args[0])).name;
|
|
95
|
+
case 'join': {
|
|
96
|
+
const joined = await square.join(args[0], args[1], control);
|
|
97
|
+
result = { ...joined, participant: joined.participant.name };
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case 'history':
|
|
101
|
+
result = await square.history(args[0], control);
|
|
102
|
+
break;
|
|
103
|
+
case 'status':
|
|
104
|
+
result = await square.status(args[0], control);
|
|
105
|
+
break;
|
|
106
|
+
case 'participants':
|
|
107
|
+
result = await square.participants(args[0], control);
|
|
108
|
+
break;
|
|
109
|
+
case 'recovery':
|
|
110
|
+
result = await square.recovery(args[0], control);
|
|
111
|
+
break;
|
|
112
|
+
default: {
|
|
113
|
+
if (typeof params.participant !== 'string')
|
|
114
|
+
throw new SquareError('invalid_args', 'Participant operation requires a name');
|
|
115
|
+
const participant = await square.participant(params.participant);
|
|
116
|
+
switch (method) {
|
|
117
|
+
case 'express':
|
|
118
|
+
result = await participant.express(args[0], args[1], control);
|
|
119
|
+
break;
|
|
120
|
+
case 'catch':
|
|
121
|
+
result = await participant.catch(args[0], control);
|
|
122
|
+
break;
|
|
123
|
+
case 'listen':
|
|
124
|
+
result = await participant.listen(args[0], args[1], control);
|
|
125
|
+
break;
|
|
126
|
+
case 'ignore':
|
|
127
|
+
result = await participant.ignore(args[0], args[1], control);
|
|
128
|
+
break;
|
|
129
|
+
case 'unlisten':
|
|
130
|
+
result = await participant.unlisten(args[0], args[1], control);
|
|
131
|
+
break;
|
|
132
|
+
case 'listening':
|
|
133
|
+
result = await participant.listening(args[0], control);
|
|
134
|
+
break;
|
|
135
|
+
case 'hold':
|
|
136
|
+
result = await participant.hold(args[0], args[1], control);
|
|
137
|
+
break;
|
|
138
|
+
case 'resume':
|
|
139
|
+
result = await participant.resume(args[0], control);
|
|
140
|
+
break;
|
|
141
|
+
case 'done':
|
|
142
|
+
result = await participant.done(args[0], args[1], control);
|
|
143
|
+
break;
|
|
144
|
+
default: throw new SquareError('invalid_args', 'Unsupported stdio operation');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (result?.observation)
|
|
149
|
+
result = { ...result, observation: { ...result.observation, location, automaticDeliveryAvailable: false } };
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
lines.on('line', line => {
|
|
153
|
+
if (closing)
|
|
154
|
+
return;
|
|
155
|
+
let message;
|
|
156
|
+
try {
|
|
157
|
+
message = JSON.parse(line);
|
|
158
|
+
if (message.jsonrpc !== '2.0' || typeof message.method !== 'string' || !message.params || typeof message.params !== 'object')
|
|
159
|
+
throw new Error();
|
|
160
|
+
if (message.method === '$/cancelRequest') {
|
|
161
|
+
running.get(message.params.id)?.controller.abort();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (!Number.isSafeInteger(message.id) || running.has(message.id))
|
|
165
|
+
throw new Error();
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
fatal();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (message.method === 'square/close') {
|
|
172
|
+
void close().then(() => { response(message.id, null); process.stdout.end(); });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const controller = new AbortController();
|
|
176
|
+
// Start on a microtask so the request is tracked before progress/cleanup can run.
|
|
177
|
+
const work = Promise.resolve().then(() => invoke(message.method, message.params, {
|
|
178
|
+
signal: controller.signal,
|
|
179
|
+
onProgress: progress => write({ jsonrpc: '2.0', method: 'square/progress', params: { id: message.id, progress } }),
|
|
180
|
+
})).then(result => response(message.id, result), error => fail(message.id, error)).finally(() => { running.delete(message.id); });
|
|
181
|
+
running.set(message.id, { controller, work });
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
185
|
+
if (process.argv.length !== 4 || process.argv[2] !== '--root') {
|
|
186
|
+
process.stderr.write('usage: node dist/stdio-server.js --root <backend-artifact-directory>\n');
|
|
187
|
+
process.exitCode = 2;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
await runSquareStdioServer(path.resolve(process.argv[3]));
|
|
191
|
+
}
|
|
192
|
+
}
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SquareArtifactPort } from './ports.js';
|
|
2
|
+
import type { OperationControl, StreamOptions, StreamRecord } from './square-facade.js';
|
|
2
3
|
/** Pull-based observation; return interrupts an outstanding pull before awaiting its cleanup. */
|
|
3
|
-
export declare function streamSquare(squarePath: string, options?: StreamOptions, control?: OperationControl,
|
|
4
|
+
export declare function streamSquare(squarePath: string, options?: StreamOptions, control?: OperationControl, borrowed?: SquareArtifactPort): AsyncIterable<StreamRecord>;
|
package/dist/stream.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import { SquareError } from './model.js';
|
|
3
|
-
import { observeSquareRevision } from './artifact.js';
|
|
4
|
-
import { openSquare, closeOpenSquare } from './open-square.js';
|
|
3
|
+
import { createArtifactPort, observeSquareRevision } from './artifact.js';
|
|
5
4
|
import { streamProjection, streamTailProjection } from './views.js';
|
|
6
5
|
/** Pull-based observation; return interrupts an outstanding pull before awaiting its cleanup. */
|
|
7
|
-
export function streamSquare(squarePath, options = {}, control,
|
|
6
|
+
export function streamSquare(squarePath, options = {}, control, borrowed) {
|
|
8
7
|
return {
|
|
9
8
|
[Symbol.asyncIterator]() {
|
|
10
9
|
const disposal = new AbortController();
|
|
11
10
|
const signal = control?.signal === undefined ? disposal.signal : AbortSignal.any([control.signal, disposal.signal]);
|
|
12
|
-
const iterationControl = { ...control, signal };
|
|
13
11
|
const records = observe();
|
|
14
12
|
return {
|
|
15
13
|
next: () => records.next(),
|
|
@@ -43,19 +41,20 @@ export function streamSquare(squarePath, options = {}, control, openOptions = {}
|
|
|
43
41
|
let projection;
|
|
44
42
|
try {
|
|
45
43
|
baseline = await observer.read(signal);
|
|
46
|
-
const
|
|
44
|
+
const artifact = borrowed ?? createArtifactPort(squarePath, undefined, signal);
|
|
47
45
|
try {
|
|
48
|
-
projection = initial === undefined
|
|
49
|
-
?
|
|
50
|
-
:
|
|
46
|
+
projection = (await artifact.read(facts => initial === undefined
|
|
47
|
+
? streamProjection(facts, cursor, options.recipient)
|
|
48
|
+
: streamTailProjection(facts, initial.last, options.recipient), signal)).value;
|
|
51
49
|
}
|
|
52
50
|
finally {
|
|
53
|
-
|
|
51
|
+
if (borrowed === undefined)
|
|
52
|
+
await artifact.close();
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
catch (error) {
|
|
57
56
|
signal.throwIfAborted();
|
|
58
|
-
if (error instanceof SquareError && error.code === 'invalid_args')
|
|
57
|
+
if (borrowed !== undefined || error instanceof SquareError && error.code === 'invalid_args')
|
|
59
58
|
throw error;
|
|
60
59
|
// Repair/replacement is a hint, not a reason to scan history on a timer.
|
|
61
60
|
await observer.hinted(generation, Infinity, signal);
|
package/dist/views.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type ActivityId } from './square-core.js';
|
|
2
2
|
import { type StoredAct } from './model.js';
|
|
3
|
-
import type { OpenSquare } from './open-square.js';
|
|
4
3
|
import type { ActivityFilter, SquareRead } from './ports.js';
|
|
5
|
-
import type { Activity, ArchiveRecord, HistoryQuery, HistoryContinuation,
|
|
4
|
+
import type { Activity, ArchiveRecord, HistoryQuery, HistoryContinuation, ParticipantsResult, SquareSnapshot, EntryPresentation, ListPresentation, StatusResult, SquareObservation, CatchPresence, StreamProjection } from './square-facade.js';
|
|
6
5
|
export type { EntryPresentation, ListPresentation, CatchPresence, StreamProjection } from './square-facade.js';
|
|
7
6
|
export declare const HISTORY_DEFAULT_LIMIT = 10;
|
|
8
7
|
export declare const HISTORY_MAX_LIMIT = 100;
|
|
@@ -18,12 +17,8 @@ export declare function selectHistory(facts: SquareRead, query: HistoryQuery, li
|
|
|
18
17
|
totalMatches: number;
|
|
19
18
|
};
|
|
20
19
|
export declare function publicActivities(activities: readonly ArchiveRecord[]): Activity[];
|
|
21
|
-
export declare function
|
|
22
|
-
|
|
23
|
-
}>;
|
|
24
|
-
export declare function currentParticipant(square: OpenSquare, name: string, control?: OperationControl): Promise<string | undefined>;
|
|
25
|
-
export declare function snapshot(square: OpenSquare, control?: OperationControl): Promise<SquareSnapshot>;
|
|
26
|
-
export declare function recoveryObservation(square: OpenSquare, name: string, control?: OperationControl): Promise<{
|
|
20
|
+
export declare function snapshot(facts: SquareRead, now: number): SquareSnapshot;
|
|
21
|
+
export declare function recoveryObservation(facts: SquareRead, name: string, now: number): {
|
|
27
22
|
ownActivityCount: number;
|
|
28
23
|
hardCap: import("./model.js").HardCap;
|
|
29
24
|
holdReason?: string | undefined;
|
|
@@ -32,12 +27,12 @@ export declare function recoveryObservation(square: OpenSquare, name: string, co
|
|
|
32
27
|
activitySummaries: import("./square-facade.js").UnreadActivitySummary[];
|
|
33
28
|
standingCount: number;
|
|
34
29
|
held: boolean;
|
|
35
|
-
}
|
|
36
|
-
export declare function entryPresentation(
|
|
30
|
+
};
|
|
31
|
+
export declare function entryPresentation(facts: SquareRead, name: string, lastN?: number | null): EntryPresentation;
|
|
37
32
|
type ParticipantQueryContext = Pick<SquareObservation, 'location' | 'now' | 'automaticDeliveryAvailable'>;
|
|
38
33
|
export declare function queryParticipants(facts: SquareRead, context: ParticipantQueryContext, observe?: boolean): ParticipantsResult;
|
|
39
|
-
export declare function listPresentation(
|
|
34
|
+
export declare function listPresentation(facts: SquareRead): ListPresentation;
|
|
40
35
|
export declare function queryStatus(facts: SquareRead, context: ParticipantQueryContext, viewer?: string, observe?: boolean): StatusResult;
|
|
41
|
-
export declare function catchPresence(
|
|
42
|
-
export declare function streamProjection(
|
|
43
|
-
export declare function streamTailProjection(
|
|
36
|
+
export declare function catchPresence(facts: SquareRead, now: number): CatchPresence;
|
|
37
|
+
export declare function streamProjection(facts: SquareRead, cursor: number, recipient?: string): StreamProjection;
|
|
38
|
+
export declare function streamTailProjection(facts: SquareRead, last?: number, recipient?: string): StreamProjection;
|