@astrosheep/square 0.3.93 → 0.3.95
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/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +3 -3
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +5 -8
- package/dist/catch-decisions.js +45 -25
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -27
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +28 -16
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +3 -3
- package/dist/cli/registry.js +7 -6
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +30 -44
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -16
- package/dist/decisions.js +9 -33
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +76 -101
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +7 -6
- package/dist/help.js +14 -14
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +12 -21
- package/dist/model.js +5 -8
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +8 -5
- package/dist/operation-presentation.d.ts +6 -6
- package/dist/operation-presentation.js +55 -58
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +18 -47
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +51 -61
- package/dist/presentation.d.ts +7 -17
- package/dist/presentation.js +46 -91
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +1 -8
- package/dist/runtime.js +2 -17
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -23
- package/dist/sqlite-square.js +162 -211
- package/dist/square-api.d.ts +16 -11
- package/dist/square-api.js +27 -23
- package/dist/square-core.d.ts +0 -14
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +65 -52
- package/dist/square-facade.d.ts +47 -105
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -70
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +4 -1
- package/dist/square-migration.js +93 -38
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +9 -3
- package/dist/square-sqlite.js +139 -114
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +21 -61
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +23 -13
- package/dist/views.js +47 -53
- package/dist/wakes.d.ts +1 -5
- package/dist/wakes.js +4 -6
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +6 -6
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +148 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +34 -5
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +22 -28
- package/guides/square-api.md +314 -91
- package/guides/symmetric-domains.md +92 -0
- package/package.json +14 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/skills/square/SKILL.md +2 -6
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/square-stdio.js
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { createInterface } from 'node:readline';
|
|
6
|
-
import { SquareError } from './model.js';
|
|
7
|
-
import { harnessSessionSources } from './participant-identity.js';
|
|
8
|
-
import { requireJoinName } from './seats.js';
|
|
9
|
-
export const STDIO_PROTOCOL_VERSION = 2;
|
|
10
|
-
export const STDIO_MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
11
|
-
export const identityVariables = [...harnessSessionSources.flatMap(source => [source.variable, ...(source.childVariable ? [source.childVariable] : [])])];
|
|
12
|
-
export function wireIdentity(env) {
|
|
13
|
-
return Object.fromEntries(identityVariables.map(key => [key, env[key] ?? '']));
|
|
14
|
-
}
|
|
15
|
-
export function encodeError(error) {
|
|
16
|
-
const e = error;
|
|
17
|
-
return { code: -32000, message: e.message ?? 'Backend request failed', data: { code: e.code, facts: e.facts, operation: e.operation } };
|
|
18
|
-
}
|
|
19
|
-
function decodeError(error) {
|
|
20
|
-
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 } : {});
|
|
21
|
-
}
|
|
22
|
-
export function unsupported(feature) {
|
|
23
|
-
throw new SquareError('invalid_args', `The stdio backend does not support ${feature}; use explicit catch/history or a local resource`);
|
|
24
|
-
}
|
|
25
|
-
/** One process per opened Square. No replay, pooling, or operation policy. */
|
|
26
|
-
class Connection {
|
|
27
|
-
nextId = 0;
|
|
28
|
-
closed = false;
|
|
29
|
-
closing;
|
|
30
|
-
pending = new Map();
|
|
31
|
-
child;
|
|
32
|
-
exited;
|
|
33
|
-
constructor(command, args, cwd, env) {
|
|
34
|
-
this.child = spawn(command, [...args], { cwd, env, shell: false, stdio: 'pipe' });
|
|
35
|
-
this.exited = new Promise(resolve => this.child.once('close', () => resolve()));
|
|
36
|
-
// Diagnostic bytes are deliberately not included in errors (they can contain credentials).
|
|
37
|
-
this.child.stderr.resume();
|
|
38
|
-
const lines = createInterface({ input: this.child.stdout, crlfDelay: Infinity });
|
|
39
|
-
let frameBytes = 0;
|
|
40
|
-
this.child.stdout.on('data', chunk => {
|
|
41
|
-
for (const byte of chunk) {
|
|
42
|
-
if (byte === 10)
|
|
43
|
-
frameBytes = 0;
|
|
44
|
-
else if (++frameBytes > STDIO_MAX_FRAME_BYTES) {
|
|
45
|
-
this.fail('Backend frame exceeds the protocol limit');
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
lines.on('line', line => {
|
|
51
|
-
try {
|
|
52
|
-
const message = JSON.parse(line);
|
|
53
|
-
if (message.jsonrpc !== '2.0')
|
|
54
|
-
throw new Error();
|
|
55
|
-
if (message.method === 'square/progress') {
|
|
56
|
-
const pending = this.pending.get(message.params?.id);
|
|
57
|
-
if (pending && message.params?.progress) {
|
|
58
|
-
try {
|
|
59
|
-
pending.control?.onProgress?.(message.params.progress);
|
|
60
|
-
}
|
|
61
|
-
catch { /* progress observers cannot corrupt framing */ }
|
|
62
|
-
}
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (!Number.isSafeInteger(message.id) || (Object.hasOwn(message, 'result') === Object.hasOwn(message, 'error')))
|
|
66
|
-
throw new Error();
|
|
67
|
-
const pending = this.pending.get(message.id);
|
|
68
|
-
if (!pending)
|
|
69
|
-
throw new Error();
|
|
70
|
-
this.pending.delete(message.id);
|
|
71
|
-
pending.dispose();
|
|
72
|
-
if (message.error)
|
|
73
|
-
pending.reject(decodeError(message.error));
|
|
74
|
-
else
|
|
75
|
-
pending.resolve(message.result);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
this.fail('Malformed backend protocol response');
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
lines.once('close', () => this.fail('Backend stdout ended'));
|
|
82
|
-
this.child.once('error', () => this.fail('Cannot start configured backend; check command and cwd'));
|
|
83
|
-
this.child.once('exit', () => this.fail('Backend process exited'));
|
|
84
|
-
this.child.stdin.on('error', () => this.fail('Backend stdin closed'));
|
|
85
|
-
}
|
|
86
|
-
fail(message) {
|
|
87
|
-
if (this.closed)
|
|
88
|
-
return;
|
|
89
|
-
this.closed = true;
|
|
90
|
-
for (const pending of this.pending.values()) {
|
|
91
|
-
pending.dispose();
|
|
92
|
-
const error = new SquareError('unavailable', message);
|
|
93
|
-
if (pending.method === 'express')
|
|
94
|
-
Object.assign(error, { operation: { operation: 'express', expressEffect: 'unknown' } });
|
|
95
|
-
pending.reject(error);
|
|
96
|
-
}
|
|
97
|
-
this.pending.clear();
|
|
98
|
-
this.child.stdin.destroy();
|
|
99
|
-
this.child.kill();
|
|
100
|
-
const timer = setTimeout(() => this.child.kill('SIGKILL'), 500);
|
|
101
|
-
timer.unref();
|
|
102
|
-
void this.exited.then(() => clearTimeout(timer));
|
|
103
|
-
}
|
|
104
|
-
request(method, params, control) {
|
|
105
|
-
if (control?.signal?.aborted)
|
|
106
|
-
return Promise.reject(control.signal.reason ?? new Error('Operation cancelled'));
|
|
107
|
-
if (this.closed)
|
|
108
|
-
return Promise.reject(new SquareError('unavailable', 'Backend connection is closed'));
|
|
109
|
-
const id = ++this.nextId;
|
|
110
|
-
let frame;
|
|
111
|
-
try {
|
|
112
|
-
frame = JSON.stringify({ jsonrpc: '2.0', id, method, params }, (_key, value) => {
|
|
113
|
-
if (typeof value === 'number' && !Number.isFinite(value))
|
|
114
|
-
throw new Error('Non-finite number');
|
|
115
|
-
return value;
|
|
116
|
-
}) + '\n';
|
|
117
|
-
if (Buffer.byteLength(frame) > STDIO_MAX_FRAME_BYTES)
|
|
118
|
-
throw new Error('Frame too large');
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
const error = new SquareError('invalid_args', 'Backend request must be JSON-serializable with finite numbers and fit the protocol frame limit');
|
|
122
|
-
if (method === 'express')
|
|
123
|
-
Object.assign(error, { operation: { operation: 'express', expressEffect: 'unsent' } });
|
|
124
|
-
return Promise.reject(error);
|
|
125
|
-
}
|
|
126
|
-
return new Promise((resolve, reject) => {
|
|
127
|
-
const abort = () => {
|
|
128
|
-
// Wait for the response: server cleanup (including catch lease release) must finish first.
|
|
129
|
-
if (!this.closed)
|
|
130
|
-
this.child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: '$/cancelRequest', params: { id } }) + '\n');
|
|
131
|
-
};
|
|
132
|
-
this.pending.set(id, { resolve, reject, method, control, dispose: () => control?.signal?.removeEventListener('abort', abort) });
|
|
133
|
-
control?.signal?.addEventListener('abort', abort, { once: true });
|
|
134
|
-
this.child.stdin.write(frame);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
close() {
|
|
138
|
-
return this.closing ??= (async () => {
|
|
139
|
-
const timer = setTimeout(() => this.fail('Backend close timed out'), 1000);
|
|
140
|
-
try {
|
|
141
|
-
if (!this.closed)
|
|
142
|
-
await this.request('square/close', {});
|
|
143
|
-
}
|
|
144
|
-
catch { /* disconnect already rejects outstanding work */ }
|
|
145
|
-
finally {
|
|
146
|
-
clearTimeout(timer);
|
|
147
|
-
this.fail('Backend connection closed');
|
|
148
|
-
await this.exited;
|
|
149
|
-
}
|
|
150
|
-
})();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
class StdioSquare {
|
|
154
|
-
location;
|
|
155
|
-
connection;
|
|
156
|
-
handle;
|
|
157
|
-
constructor(location, connection, handle) {
|
|
158
|
-
this.location = location;
|
|
159
|
-
this.connection = connection;
|
|
160
|
-
this.handle = handle;
|
|
161
|
-
}
|
|
162
|
-
call(method, args, control, participant, participantHandle) {
|
|
163
|
-
return this.connection.request(method, { handle: this.handle, args, ...(participant === undefined ? {} : { participant, participantHandle }) }, control);
|
|
164
|
-
}
|
|
165
|
-
participantHandle(name, reference) {
|
|
166
|
-
const call = (method, args, control) => this.call(method, args, control, name, reference);
|
|
167
|
-
return {
|
|
168
|
-
name,
|
|
169
|
-
express: ((bodyOrInput, optionsOrControl, control) => {
|
|
170
|
-
if (typeof bodyOrInput === 'object' && bodyOrInput !== null)
|
|
171
|
-
return call('express', [bodyOrInput, undefined], optionsOrControl);
|
|
172
|
-
return call('express', [bodyOrInput, optionsOrControl], control);
|
|
173
|
-
}),
|
|
174
|
-
catch: (options, control) => call('catch', [options], control),
|
|
175
|
-
listen: (target, options, control) => call('listen', [target, options], control),
|
|
176
|
-
ignore: (target, options, control) => call('ignore', [target, options], control),
|
|
177
|
-
unlisten: (target, options, control) => call('unlisten', [target, options], control),
|
|
178
|
-
listening: (options, control) => call('listening', [options], control),
|
|
179
|
-
hold: (reason, options, control) => call('hold', [reason, options], control),
|
|
180
|
-
resume: (options, control) => call('resume', [options], control),
|
|
181
|
-
done: (body, options, control) => call('done', [body, options], control),
|
|
182
|
-
deferDelivery: async (input, control) => {
|
|
183
|
-
const match = validateMetadataMatch(input?.match), durationMs = validateDeferralDuration(input?.durationMs);
|
|
184
|
-
return call('deferDelivery', [{ match, durationMs }], control);
|
|
185
|
-
},
|
|
186
|
-
renewDeliveryDeferral: async (id, durationMs, control) => call('renewDeliveryDeferral', [validateDeferralId(id), validateDeferralDuration(durationMs)], control),
|
|
187
|
-
releaseDeliveryDeferral: async (id, control) => call('releaseDeliveryDeferral', [validateDeferralId(id)], control),
|
|
188
|
-
history: (query, control) => this.history(query, control),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
async participant(name) {
|
|
192
|
-
const reference = randomUUID();
|
|
193
|
-
return this.participantHandle(await this.call('participant', [name, reference]), reference);
|
|
194
|
-
}
|
|
195
|
-
async join(...[name, options, control]) {
|
|
196
|
-
requireJoinName(name);
|
|
197
|
-
const reference = randomUUID();
|
|
198
|
-
const result = await this.call('join', [name, options, reference], control);
|
|
199
|
-
return { ...result, participant: this.participantHandle(result.participant, reference) };
|
|
200
|
-
}
|
|
201
|
-
history(...[query, control]) { return this.call('history', [query], control); }
|
|
202
|
-
status(...[options, control]) { return this.call('status', [options], control); }
|
|
203
|
-
participants(...[options, control]) { return this.call('participants', [options], control); }
|
|
204
|
-
recovery(...[name, control]) { return this.call('recovery', [name], control); }
|
|
205
|
-
snapshot() { return Promise.reject(new SquareError('invalid_args', 'stdio does not support function-valued snapshot; use status/participants/history')); }
|
|
206
|
-
stream() { unsupported('stream'); }
|
|
207
|
-
recognize() { return Promise.reject(new SquareError('invalid_args', 'stdio does not support host recognition; select a participant explicitly')); }
|
|
208
|
-
implicitJoin() { unsupported('host implicit join'); }
|
|
209
|
-
endOwnedSession() { unsupported('host session cleanup'); }
|
|
210
|
-
cancelDeliveries() { unsupported('host delivery cancellation'); }
|
|
211
|
-
close() { return this.connection.close(); }
|
|
212
|
-
}
|
|
213
|
-
export async function openStdioSquare(location, selected, input, backends, build, control) {
|
|
214
|
-
if (input.clock || input.wakeTransport)
|
|
215
|
-
unsupported('client clock or wakeTransport injection');
|
|
216
|
-
if (control?.signal?.aborted)
|
|
217
|
-
throw control.signal.reason ?? new Error('Operation cancelled');
|
|
218
|
-
const backend = Object.hasOwn(backends, selected.backend) ? backends[selected.backend] : undefined;
|
|
219
|
-
if (!backend)
|
|
220
|
-
throw new SquareError('invalid_args', `Backend ${selected.backend} is not configured; set backends or SQUARE_BACKENDS_CONFIG`);
|
|
221
|
-
const cwd = input.cwd ?? process.cwd();
|
|
222
|
-
const command = backend.command.includes('/') || backend.command.includes('\\') ? path.resolve(cwd, backend.command) : backend.command;
|
|
223
|
-
const connection = new Connection(command, backend.args ?? [], path.resolve(cwd, backend.cwd ?? '.'), { ...(input.env ?? process.env), ...backend.env });
|
|
224
|
-
try {
|
|
225
|
-
const result = await connection.request('square/open', { version: STDIO_PROTOCOL_VERSION, key: selected.key, location,
|
|
226
|
-
identity: wireIdentity(input.env ?? process.env), body: input.body, participant: input.participant, build }, control);
|
|
227
|
-
if (result.version !== STDIO_PROTOCOL_VERSION || typeof result.handle !== 'string')
|
|
228
|
-
throw new SquareError('unavailable', 'Backend protocol version mismatch');
|
|
229
|
-
return new StdioSquare(location, connection, result.handle);
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
await connection.close();
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
}
|
package/dist/stdio-server.d.ts
DELETED
package/dist/stdio-server.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
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 participants = new Map();
|
|
20
|
-
const captureParticipant = (reference, participant) => {
|
|
21
|
-
if (typeof reference !== 'string' || reference.length === 0 || participants.has(reference))
|
|
22
|
-
throw new SquareError('invalid_args', 'Participant handle must be a fresh connection-local reference');
|
|
23
|
-
participants.set(reference, participant);
|
|
24
|
-
return participant.name;
|
|
25
|
-
};
|
|
26
|
-
const running = new Map();
|
|
27
|
-
const write = (message) => { if (!process.stdout.destroyed)
|
|
28
|
-
process.stdout.write(JSON.stringify(message) + '\n'); };
|
|
29
|
-
const response = (id, result) => write({ jsonrpc: '2.0', id, result });
|
|
30
|
-
const fail = (id, error) => write({ jsonrpc: '2.0', id, error: encodeError(error) });
|
|
31
|
-
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
32
|
-
let shutdown;
|
|
33
|
-
const close = () => shutdown ??= (async () => {
|
|
34
|
-
closing = true;
|
|
35
|
-
for (const request of running.values())
|
|
36
|
-
request.controller.abort();
|
|
37
|
-
await Promise.allSettled([...running.values()].map(request => request.work));
|
|
38
|
-
await square?.close();
|
|
39
|
-
lines.close();
|
|
40
|
-
process.stdin.pause();
|
|
41
|
-
})();
|
|
42
|
-
const fatal = () => { void close().finally(() => { process.exitCode = 1; }); };
|
|
43
|
-
let frameBytes = 0;
|
|
44
|
-
process.stdin.on('data', chunk => {
|
|
45
|
-
for (const byte of Buffer.from(chunk)) {
|
|
46
|
-
if (byte === 10)
|
|
47
|
-
frameBytes = 0;
|
|
48
|
-
else if (++frameBytes > STDIO_MAX_FRAME_BYTES) {
|
|
49
|
-
fatal();
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
process.stdout.on('error', fatal);
|
|
55
|
-
process.once('SIGTERM', fatal);
|
|
56
|
-
process.once('SIGINT', fatal);
|
|
57
|
-
lines.on('close', () => { if (!closing)
|
|
58
|
-
void close(); });
|
|
59
|
-
async function invoke(method, params, control) {
|
|
60
|
-
if (method === 'square/open') {
|
|
61
|
-
if (square || opening)
|
|
62
|
-
throw new SquareError('invalid_args', 'One logical open is allowed per connection');
|
|
63
|
-
opening = true;
|
|
64
|
-
try {
|
|
65
|
-
if (params.version !== STDIO_PROTOCOL_VERSION)
|
|
66
|
-
throw new SquareError('invalid_args', 'Unsupported Square stdio protocol version');
|
|
67
|
-
const parsed = parseSquareLocation(params.location);
|
|
68
|
-
if (parsed.kind !== 'stdio' || parsed.key !== params.key)
|
|
69
|
-
throw new SquareError('invalid_args', 'Open requires a matching stdio location and key');
|
|
70
|
-
const target = path.join(root, `${parsed.key}.square`);
|
|
71
|
-
const canonical = await canonicalPath(target);
|
|
72
|
-
if (path.dirname(canonical) !== root)
|
|
73
|
-
throw new SquareError('invalid_args', 'Square key escapes the configured backend root');
|
|
74
|
-
const env = { ...baseEnv };
|
|
75
|
-
for (const key of identityVariables) {
|
|
76
|
-
const value = params.identity?.[key];
|
|
77
|
-
if (value !== undefined && typeof value !== 'string')
|
|
78
|
-
throw new SquareError('invalid_args', 'Invalid operation identity');
|
|
79
|
-
env[key] = value ?? '';
|
|
80
|
-
}
|
|
81
|
-
const input = { path: target, env, body: params.body, participant: params.participant };
|
|
82
|
-
if (params.body !== undefined)
|
|
83
|
-
validateBody(params.body);
|
|
84
|
-
square = params.build === undefined ? await SqliteSquare.at(input, control, false)
|
|
85
|
-
: await SqliteSquare.build({ ...params.build, ...input }, control, false);
|
|
86
|
-
location = parsed.uri;
|
|
87
|
-
return { version: STDIO_PROTOCOL_VERSION, handle: 'square' };
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
opening = false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (!square || params.handle !== 'square')
|
|
94
|
-
throw new SquareError('invalid_args', 'Open a logical square first');
|
|
95
|
-
if (!Array.isArray(params.args))
|
|
96
|
-
throw new SquareError('invalid_args', 'Operation args must be an array');
|
|
97
|
-
// JSON null represents omitted optional arguments, never the meaningful history limit:null inside an options object.
|
|
98
|
-
const args = params.args.map((arg) => arg === null ? undefined : arg);
|
|
99
|
-
let result;
|
|
100
|
-
switch (method) {
|
|
101
|
-
case 'participant': return captureParticipant(args[1], await square.participant(args[0]));
|
|
102
|
-
case 'join': {
|
|
103
|
-
if (typeof args[2] !== 'string' || args[2].length === 0 || participants.has(args[2]))
|
|
104
|
-
throw new SquareError('invalid_args', 'Participant handle must be a fresh connection-local reference');
|
|
105
|
-
const joined = await square.join(args[0], args[1], control);
|
|
106
|
-
result = { ...joined, participant: captureParticipant(args[2], joined.participant) };
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
case 'history':
|
|
110
|
-
result = await square.history(args[0], control);
|
|
111
|
-
break;
|
|
112
|
-
case 'status':
|
|
113
|
-
result = await square.status(args[0], control);
|
|
114
|
-
break;
|
|
115
|
-
case 'participants':
|
|
116
|
-
result = await square.participants(args[0], control);
|
|
117
|
-
break;
|
|
118
|
-
case 'recovery':
|
|
119
|
-
result = await square.recovery(args[0], control);
|
|
120
|
-
break;
|
|
121
|
-
default: {
|
|
122
|
-
if (typeof params.participant !== 'string')
|
|
123
|
-
throw new SquareError('invalid_args', 'Participant operation requires a name');
|
|
124
|
-
const participant = participants.get(params.participantHandle);
|
|
125
|
-
if (participant === undefined || participant.name !== params.participant)
|
|
126
|
-
throw new SquareError('invalid_args', 'Select a captured participant handle on this connection');
|
|
127
|
-
switch (method) {
|
|
128
|
-
case 'deferDelivery':
|
|
129
|
-
result = await participant.deferDelivery(args[0], control);
|
|
130
|
-
break;
|
|
131
|
-
case 'renewDeliveryDeferral':
|
|
132
|
-
result = await participant.renewDeliveryDeferral(args[0], args[1], control);
|
|
133
|
-
break;
|
|
134
|
-
case 'releaseDeliveryDeferral':
|
|
135
|
-
result = await participant.releaseDeliveryDeferral(args[0], control);
|
|
136
|
-
break;
|
|
137
|
-
case 'express':
|
|
138
|
-
result = await participant.express(args[0], args[1], control);
|
|
139
|
-
break;
|
|
140
|
-
case 'catch':
|
|
141
|
-
result = await participant.catch(args[0], control);
|
|
142
|
-
break;
|
|
143
|
-
case 'listen':
|
|
144
|
-
result = await participant.listen(args[0], args[1], control);
|
|
145
|
-
break;
|
|
146
|
-
case 'ignore':
|
|
147
|
-
result = await participant.ignore(args[0], args[1], control);
|
|
148
|
-
break;
|
|
149
|
-
case 'unlisten':
|
|
150
|
-
result = await participant.unlisten(args[0], args[1], control);
|
|
151
|
-
break;
|
|
152
|
-
case 'listening':
|
|
153
|
-
result = await participant.listening(args[0], control);
|
|
154
|
-
break;
|
|
155
|
-
case 'hold':
|
|
156
|
-
result = await participant.hold(args[0], args[1], control);
|
|
157
|
-
break;
|
|
158
|
-
case 'resume':
|
|
159
|
-
result = await participant.resume(args[0], control);
|
|
160
|
-
break;
|
|
161
|
-
case 'done':
|
|
162
|
-
result = await participant.done(args[0], args[1], control);
|
|
163
|
-
break;
|
|
164
|
-
default: throw new SquareError('invalid_args', 'Unsupported stdio operation');
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (result?.observation)
|
|
169
|
-
result = { ...result, observation: { ...result.observation, location, automaticDeliveryAvailable: false } };
|
|
170
|
-
return result;
|
|
171
|
-
}
|
|
172
|
-
lines.on('line', line => {
|
|
173
|
-
if (closing)
|
|
174
|
-
return;
|
|
175
|
-
let message;
|
|
176
|
-
try {
|
|
177
|
-
message = JSON.parse(line);
|
|
178
|
-
if (message.jsonrpc !== '2.0' || typeof message.method !== 'string' || !message.params || typeof message.params !== 'object')
|
|
179
|
-
throw new Error();
|
|
180
|
-
if (message.method === '$/cancelRequest') {
|
|
181
|
-
running.get(message.params.id)?.controller.abort();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (!Number.isSafeInteger(message.id) || running.has(message.id))
|
|
185
|
-
throw new Error();
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
fatal();
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (message.method === 'square/close') {
|
|
192
|
-
void close().then(() => { response(message.id, null); process.stdout.end(); });
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const controller = new AbortController();
|
|
196
|
-
// Start on a microtask so the request is tracked before progress/cleanup can run.
|
|
197
|
-
const work = Promise.resolve().then(() => invoke(message.method, message.params, {
|
|
198
|
-
signal: controller.signal,
|
|
199
|
-
onProgress: progress => write({ jsonrpc: '2.0', method: 'square/progress', params: { id: message.id, progress } }),
|
|
200
|
-
})).then(result => response(message.id, result), error => fail(message.id, error)).finally(() => { running.delete(message.id); });
|
|
201
|
-
running.set(message.id, { controller, work });
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
205
|
-
if (process.argv.length !== 4 || process.argv[2] !== '--root') {
|
|
206
|
-
process.stderr.write('usage: node dist/stdio-server.js --root <backend-artifact-directory>\n');
|
|
207
|
-
process.exitCode = 2;
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
await runSquareStdioServer(path.resolve(process.argv[3]));
|
|
211
|
-
}
|
|
212
|
-
}
|
package/guides/backends.md
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
# Locations and the stdio backend
|
|
2
|
-
|
|
3
|
-
Square and Participant are the same public resource API for local SQLite, local in-memory,
|
|
4
|
-
and stdio-backed squares. There is no separate participant policy or caller-specific dispatch.
|
|
5
|
-
`Square.at` / `Square.build` choose the backend before opening storage. CLI, MCP and Pi tools use
|
|
6
|
-
that same boundary. No Slack backend or external messaging integration is included.
|
|
7
|
-
|
|
8
|
-
## Runnable setup
|
|
9
|
-
|
|
10
|
-
From a built source checkout (`npm run build`), create a JSON configuration file, substituting
|
|
11
|
-
absolute paths for this machine:
|
|
12
|
-
|
|
13
|
-
```json
|
|
14
|
-
{
|
|
15
|
-
"backends": {
|
|
16
|
-
"local": {
|
|
17
|
-
"command": "/absolute/path/to/node",
|
|
18
|
-
"args": ["/absolute/path/to/square/dist/stdio-server.js", "--root", "/absolute/path/to/squares"]
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The bundled backend's standalone startup command is:
|
|
25
|
-
|
|
26
|
-
```sh
|
|
27
|
-
node /absolute/path/to/square/dist/stdio-server.js --root /absolute/path/to/squares
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Normally Square spawns it automatically, without a shell, using the configured command. Its
|
|
31
|
-
stdin/stdout are the protocol, not an interactive terminal. Stderr is diagnostics. The client
|
|
32
|
-
drains stderr without inserting it into participant output or error messages.
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
export SQUARE_BACKENDS_CONFIG=/absolute/path/to/backends.json
|
|
36
|
-
export SQUARE_LOCATION=stdio://local/design
|
|
37
|
-
printf '# Design\nA place to compare ideas.\n' | node dist/square.js build -
|
|
38
|
-
node dist/square.js --as Alice join
|
|
39
|
-
node dist/square.js --as Bob join
|
|
40
|
-
node dist/square.js --as Alice express --force 'What do you think @Bob?'
|
|
41
|
-
node dist/square.js --as Bob history
|
|
42
|
-
node dist/square.js --as Bob catch --now
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
History does not consume. Catch consumes eligible directed activity once. A reply uses the usual
|
|
46
|
-
`--reply act/N`; mentions, replies, listening, ownership, hold/throttle, reentry and receipt semantics
|
|
47
|
-
are the existing SQLite implementation. Independent processes address the same `design.square`
|
|
48
|
-
inside the backend root. They do **not** share an in-memory resource.
|
|
49
|
-
|
|
50
|
-
SDK callers may provide typed configuration directly:
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
import { Square, type SquareBackends } from '@astrosheep/square';
|
|
54
|
-
|
|
55
|
-
const backends: SquareBackends = {
|
|
56
|
-
local: { command: process.execPath, args: ['/absolute/path/to/square/dist/stdio-server.js', '--root', '/tmp/squares'] },
|
|
57
|
-
};
|
|
58
|
-
const square = await Square.at({ path: 'stdio://local/design', backends, participant: 'Bob',
|
|
59
|
-
body: { channel: 'example', session: 'bob-process' } });
|
|
60
|
-
try {
|
|
61
|
-
const bob = (await square.join('Bob')).participant;
|
|
62
|
-
const caught = await bob.catch();
|
|
63
|
-
console.log(caught.activities);
|
|
64
|
-
} finally {
|
|
65
|
-
await square.close();
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Use the same `path` and `backends` in `createSquareMcpServer` options. Tool calls can override
|
|
70
|
-
`location: "stdio://local/design"`; the override owns its process rather than borrowing the
|
|
71
|
-
configured local SQLite connection. Pi tools accept the identical location and read the captured
|
|
72
|
-
`SQUARE_BACKENDS_CONFIG`. Native session identity is still chosen by each adapter on each call.
|
|
73
|
-
|
|
74
|
-
## Locations and configuration
|
|
75
|
-
|
|
76
|
-
- Plain locations are filesystem paths, resolved against captured input `cwd`. `inMemory` is always
|
|
77
|
-
local. Remote `build` creates a square through the backend; it never writes a URI-named local file.
|
|
78
|
-
- Stdio URI grammar is `stdio://<alias>/<encoded-key>`. Aliases are case-sensitive ASCII letters,
|
|
79
|
-
digits, `_` and `-`. The key is one nonempty Unicode name, **not** a hierarchy: encode it with
|
|
80
|
-
`encodeURIComponent`, decoded exactly once. Encoded slashes/backslashes, controls, `.` and `..`
|
|
81
|
-
are refused. Canonical URIs re-encode the decoded key, so `hello%20world` is unambiguous.
|
|
82
|
-
Credentials, ports, query strings, fragments, extra path segments and unknown schemes are refused.
|
|
83
|
-
- A key `design` maps to `design.square` in the bundled server root. The root is created if needed;
|
|
84
|
-
resolved artifact targets outside it (including existing escaping symlinks) are refused. Use a root
|
|
85
|
-
managed by the backend owner, not an attacker-writable directory. Different aliases need not refer
|
|
86
|
-
to different servers, but ownership is established by backend persistence, not by the alias.
|
|
87
|
-
- An alias is explicit configuration, never an executable in a URI. An entry is
|
|
88
|
-
`{command: string, args?: string[], cwd?: string, env?: Record<string,string>}`. `command` is a
|
|
89
|
-
program name on PATH or a path. Relative command paths, entry `cwd`, and the config-file path are
|
|
90
|
-
resolved against captured input `cwd`, **not** relative to the config file. Commands spawn with
|
|
91
|
-
no shell and inherit captured caller environment plus only the entry's explicit env overrides.
|
|
92
|
-
- SDK `backends` (including `{}`) wins over file configuration. Otherwise Square reads
|
|
93
|
-
`SQUARE_BACKENDS_CONFIG`, default `~/.config/square/backends.json` (`HOME` from captured env).
|
|
94
|
-
A missing default file is harmless for local resources. Explicit missing/invalid files, malformed
|
|
95
|
-
configuration and missing selected aliases refuse with `invalid_args`. Error text never dumps
|
|
96
|
-
configuration values or environment secrets.
|
|
97
|
-
- Each remote open owns one child process/connection, reused across its operations. There is no
|
|
98
|
-
global pool. A supplied SQLite `resources` owner is not used for remote opens; close still closes
|
|
99
|
-
that remote process. Separate opens share state only through the backend's persistence.
|
|
100
|
-
- CLI failed-expression drafts stay in local `./drafts` for backend locations; recovery commands and
|
|
101
|
-
display labels retain the URI. Local-artifact draft placement is unchanged.
|
|
102
|
-
|
|
103
|
-
## Protocol version 2
|
|
104
|
-
|
|
105
|
-
Newline-delimited JSON-RPC 2.0 over UTF-8 stdin/stdout, one object per line, at most 8 MiB per frame.
|
|
106
|
-
Stdout is protocol only. No batch requests. The initial versioned handshake opens one logical square
|
|
107
|
-
per connection and returns an opaque handle. IDs are unique positive integers on the client;
|
|
108
|
-
requests may overlap and responses may arrive out of order.
|
|
109
|
-
|
|
110
|
-
```json
|
|
111
|
-
{"jsonrpc":"2.0","id":1,"method":"square/open","params":{"version":2,"key":"design","location":"stdio://local/design","identity":{"PI_SESSION_ID":"session-id"},"body":{"channel":"pi","session":"session-id"},"participant":"Bob"}}
|
|
112
|
-
{"jsonrpc":"2.0","id":1,"result":{"version":2,"handle":"square"}}
|
|
113
|
-
{"jsonrpc":"2.0","id":2,"method":"join","params":{"handle":"square","args":["Bob",{"observe":true},"fresh-participant-reference"]}}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Open may include `build: {markdown, hardCap?, throttlePerMinute?, template?, force?}` instead of
|
|
117
|
-
opening an existing artifact. The client transmits only recognized harness session coordinates,
|
|
118
|
-
the Claude child flag and configured participant name as identity facts, **not the whole environment
|
|
119
|
-
in the protocol**. The backend captures its own process environment, clears **all** these startup
|
|
120
|
-
coordinates (including PASEO_AGENT_ID), then overlays explicit call-local facts without mutating
|
|
121
|
-
`process.env`. Participant defaults remain separate from square keys. Each new tool open therefore
|
|
122
|
-
uses that call's identity, not MCP/Pi/backend startup identity.
|
|
123
|
-
|
|
124
|
-
After open, square methods take `{handle, args}` and Participant methods additionally require
|
|
125
|
-
`{participant, participantHandle}`. Selection/join supply a fresh opaque connection-local reference;
|
|
126
|
-
the server retains the actual captured Participant under it instead of reselecting by name per
|
|
127
|
-
request. Version 2 deliberately refuses older peers rather than accepting unfenced handles.
|
|
128
|
-
`args` is the public operation's positional arguments excluding `OperationControl`, plus the
|
|
129
|
-
selection reference noted below; trailing JSON nulls represent omitted optional arguments.
|
|
130
|
-
Nested nulls such as `history({limit:null})` retain their public meaning.
|
|
131
|
-
|
|
132
|
-
| Methods | args | participant field |
|
|
133
|
-
| --- | --- | --- |
|
|
134
|
-
| `participant` | `[name?, freshReference]` | no; selection only, not joining |
|
|
135
|
-
| `join` | `[name, options?, freshReference]` | no |
|
|
136
|
-
| `history`, `status`, `participants` | `[options?]` | no |
|
|
137
|
-
| `recovery` | `[name?]` | no |
|
|
138
|
-
| `express` | `[body, options?]` | required |
|
|
139
|
-
| `catch`, `listening`, `resume` | `[options?]` | required |
|
|
140
|
-
| `listen`, `ignore`, `unlisten` | `[target, options?]` | required |
|
|
141
|
-
| `hold`, `done` | `[body?, options?]` | required |
|
|
142
|
-
| `deferDelivery` | `[{match, durationMs}]` | required |
|
|
143
|
-
| `renewDeliveryDeferral` | `[id, durationMs]` | required |
|
|
144
|
-
| `releaseDeliveryDeferral` | `[id]` | required |
|
|
145
|
-
|
|
146
|
-
Results are the concrete public operation results, including requested observations. Participant
|
|
147
|
-
handles serialize as names for `participant` and `join.participant` and become real local handles
|
|
148
|
-
on the client, using their retained connection-local references. Done/takeover/rejoin fences old
|
|
149
|
-
references even when the same Body later reacquires the name. Observation locations are canonical
|
|
150
|
-
remote URIs and automatic delivery is false. SDK/stdio deferrals have identical validation, independent
|
|
151
|
-
lease and owner-generation semantics; create/renew return only `{id, expiresAt}`, renew returns null
|
|
152
|
-
and release false for an inactive ID, and stale handles receive the normal ownership refusal.
|
|
153
|
-
No CLI/MCP/Pi deferral-authoring command is included. Errors are JSON-RPC `{code:-32000,message,data:{code?,facts?,operation?}}`; Square codes, structured
|
|
154
|
-
facts, history bounds and express certainty survive the boundary. The client does not infer
|
|
155
|
-
`unsent` from a connection failure. There is no mutation retry or replay.
|
|
156
|
-
|
|
157
|
-
Progress is a notification
|
|
158
|
-
`{jsonrpc:"2.0",method:"square/progress",params:{id,progress}}` where progress is the typed public
|
|
159
|
-
`OperationProgress`. Request abort sends `$/cancelRequest` with `{id}`; the server aborts only that
|
|
160
|
-
request and returns its response **after** cleanup, including releasing a catch lease. Another
|
|
161
|
-
request on the same handle continues normally. Cancellation never undoes an explicitly committed
|
|
162
|
-
receipt or lease mutation. A committed deferral remains until release, expiry or seat retirement;
|
|
163
|
-
connection close does not cancel it. `square/close` with `{}` aborts/drains outstanding requests, closes SQLite and acknowledges
|
|
164
|
-
with a null result. Client close is idempotent, waits briefly for graceful cleanup, then terminates
|
|
165
|
-
an unresponsive process. EOF, malformed framing, version mismatch and process exit reject pending
|
|
166
|
-
requests and release the connection. A lost express response after dispatch has unknown effect;
|
|
167
|
-
inspect history before deciding whether to send again.
|
|
168
|
-
|
|
169
|
-
## Explicit limitations
|
|
170
|
-
|
|
171
|
-
- No automatic wake, route registration, native notification delivery or cross-host inbox for stdio.
|
|
172
|
-
The server disables wake dispatch and route publication explicitly, while retaining backend-local
|
|
173
|
-
session ownership. A harness identity does not imply wake capability. Use explicit `catch`
|
|
174
|
-
(including cancellable positive-idle catch) or history.
|
|
175
|
-
- `snapshot` (which contains a function), `stream`, host recognition/implicit join, owner-session
|
|
176
|
-
cleanup and route-retirement operations refuse explicitly. No partial JSON snapshot or fake stream
|
|
177
|
-
is offered. Use status/participants/history and explicit participant lifecycle methods.
|
|
178
|
-
- Client clock functions, host-ledger objects and wake transport objects cannot cross the process
|
|
179
|
-
boundary and are refused. Backend process configuration owns its environment, clock and ledger.
|
|
180
|
-
- File doctor, migrate and discovery are local-only and refuse a selected backend URI. Run file
|
|
181
|
-
diagnostics/migration locally on the backend host with the artifact path while clients are stopped.
|
|
182
|
-
Local harness discovery/inbox do not enumerate remote backends; remote membership is not registered
|
|
183
|
-
into the client's host ledger or notification watchers.
|
|
184
|
-
- No process pooling, reconnection, automatic retries, server daemon, Slack IDs, credentials, live
|
|
185
|
-
external sends, installation or release management is introduced.
|
|
186
|
-
|
|
187
|
-
## Slack is a bridge, not a backend
|
|
188
|
-
|
|
189
|
-
Slack never becomes a Square location and does not change the backend grammar. To bring Slack humans
|
|
190
|
-
into a square, run `packages/slack-bridge` (`square-slack-bridge`) against one channel and one local
|
|
191
|
-
artifact: Slack messages land as ordinary activities from `slack/<name>` participants, and Square
|
|
192
|
-
`say` activities appear in the channel. The artifact stays authoritative, so mentions, replies, catch,
|
|
193
|
-
notifications and previews are the existing native behavior; no core, CLI, MCP or Pi code changes.
|