@astrosheep/square 0.3.61 → 0.3.62
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/skills/square/SKILL.md +5 -7
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.d.ts +6 -2
- package/dist/activity.js +168 -133
- package/dist/artifact.d.ts +4 -0
- package/dist/artifact.js +43 -14
- package/dist/attention-presentation.d.ts +1 -0
- package/dist/attention-presentation.js +8 -6
- package/dist/automatic-session.js +14 -24
- package/dist/boundary-presentation.d.ts +1 -2
- package/dist/boundary-presentation.js +4 -19
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +2 -18
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/mcp-server-command.d.ts +2 -0
- package/dist/cli/mcp-server-command.js +13 -0
- package/dist/cli/observation-commands.js +47 -19
- package/dist/cli/program.js +5 -2
- package/dist/cli/registry.js +2 -0
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +93 -125
- package/dist/decisions.d.ts +2 -0
- package/dist/decisions.js +44 -3
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +1 -4
- package/dist/file-changes.d.ts +12 -0
- package/dist/file-changes.js +235 -0
- package/dist/help.js +4 -8
- package/dist/host-ledger-file-adapter.d.ts +15 -13
- package/dist/host-ledger-file-adapter.js +131 -79
- package/dist/host-ledger-root.d.ts +2 -0
- package/dist/host-ledger-root.js +9 -0
- package/dist/host-ledger.d.ts +4 -24
- package/dist/inbox.d.ts +10 -5
- package/dist/inbox.js +152 -67
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +122 -0
- package/dist/mention-parse.d.ts +8 -0
- package/dist/mention-parse.js +107 -0
- package/dist/model.d.ts +5 -0
- package/dist/model.js +4 -1
- package/dist/notifications.d.ts +1 -0
- package/dist/notifications.js +25 -20
- package/dist/participant-host.js +2 -2
- package/dist/ports.d.ts +2 -9
- package/dist/presentation.d.ts +12 -1
- package/dist/presentation.js +42 -12
- package/dist/presented.js +2 -1
- package/dist/registry.d.ts +8 -4
- package/dist/registry.js +41 -74
- package/dist/routes.d.ts +19 -2
- package/dist/routes.js +48 -5
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +2 -0
- package/dist/square-actions.d.ts +26 -13
- package/dist/square-actions.js +200 -84
- package/dist/square-application.d.ts +58 -0
- package/dist/square-application.js +183 -0
- package/dist/square-facade.d.ts +24 -9
- package/dist/square-file-adapter.d.ts +5 -12
- package/dist/square-file-adapter.js +12 -64
- package/dist/square-projections.d.ts +3 -2
- package/dist/square-projections.js +12 -3
- package/dist/square-storage.js +27 -34
- package/dist/square-wiring.d.ts +12 -7
- package/dist/square-wiring.js +36 -32
- package/dist/state-cell.d.ts +1 -1
- package/dist/stream.js +30 -26
- package/dist/views.d.ts +3 -3
- package/dist/views.js +4 -4
- package/dist/wake-attempts.js +2 -1
- package/dist/wake-evidence.js +2 -8
- package/dist/wakes.d.ts +1 -0
- package/dist/wakes.js +5 -5
- package/dist/watch.d.ts +6 -1
- package/dist/watch.js +20 -18
- package/extensions/square-pi.js +100 -112
- package/guides/application-boundary.md +32 -0
- package/guides/change-waits.md +28 -0
- package/guides/host-ledger.md +9 -0
- package/guides/mcp.md +9 -0
- package/guides/operation-cancellation.md +17 -0
- package/package.json +8 -2
- package/skills/square/SKILL.md +5 -7
package/extensions/square-pi.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
|
|
2
2
|
import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
|
|
3
|
-
import { sessionInbox,
|
|
4
|
-
import {
|
|
5
|
-
import { createHostLedgerPort } from '../dist/host-ledger-file-adapter.js';
|
|
3
|
+
import { sessionInbox, observeSessionPending } from '../dist/inbox.js';
|
|
4
|
+
import { renderAttentionDescription } from '../dist/attention-presentation.js';
|
|
6
5
|
|
|
7
6
|
class PiDeliveryDroppedError extends Error {
|
|
8
7
|
constructor() {
|
|
@@ -11,10 +10,6 @@ class PiDeliveryDroppedError extends Error {
|
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
function sessionBindings(sessionId) {
|
|
15
|
-
return projectSessionBindings({ hostLedger: createHostLedgerPort(), sessionId });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
export function pendingInbox(inbox) {
|
|
19
14
|
return inbox.filter((item) => item.notifications?.length > 0);
|
|
20
15
|
}
|
|
@@ -37,14 +32,10 @@ function framePiMessage(content) {
|
|
|
37
32
|
|
|
38
33
|
export function summarizePendingForNotify(pending) {
|
|
39
34
|
const lines = pending.flatMap((membership) => membership.notifications.map((note) => {
|
|
40
|
-
const
|
|
41
|
-
? 'rang the bell'
|
|
42
|
-
: note.route === 'mention'
|
|
43
|
-
? `called your name (${membership.name})`
|
|
44
|
-
: `spoke within your earshot (${membership.name})`;
|
|
35
|
+
const description = renderAttentionDescription({ actor: note.actor, recipient: membership.name, route: note.route });
|
|
45
36
|
const body = note.body.replace(/\s+/g, ' ').trim();
|
|
46
37
|
const clipped = body.length > NOTIFY_BODY_MAX ? `${body.slice(0, NOTIFY_BODY_MAX).trimEnd()}…` : body;
|
|
47
|
-
return `${
|
|
38
|
+
return `${description}${clipped ? ` — ${clipped}` : ''}`;
|
|
48
39
|
}));
|
|
49
40
|
if (lines.length === 0) return undefined;
|
|
50
41
|
return lines.length === 1 ? `■ square · ${lines[0]}` : `■ square · ${lines[0]} (+${lines.length - 1} more)`;
|
|
@@ -133,6 +124,7 @@ export default function squarePiExtension(pi) {
|
|
|
133
124
|
};
|
|
134
125
|
|
|
135
126
|
const pause = (signal, delayMs) => new Promise((resolve) => {
|
|
127
|
+
if (signal.aborted) { resolve(); return; }
|
|
136
128
|
const finish = () => {
|
|
137
129
|
signal.removeEventListener('abort', finish);
|
|
138
130
|
clearTimeout(timer);
|
|
@@ -143,115 +135,111 @@ export default function squarePiExtension(pi) {
|
|
|
143
135
|
});
|
|
144
136
|
|
|
145
137
|
const wake = async (token, signal) => {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
138
|
+
const boundSessionId = sessionId;
|
|
139
|
+
const env = sessionEnv(boundSessionId);
|
|
140
|
+
const observer = await observeSessionPending(boundSessionId, env);
|
|
141
|
+
try {
|
|
142
|
+
if (signal.aborted || token !== generation) return;
|
|
143
|
+
const armDeferredRetry = () => {
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
let resolveArmed;
|
|
146
|
+
const armed = new Promise((resolve) => { resolveArmed = resolve; });
|
|
147
|
+
const abort = () => controller.abort();
|
|
148
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
149
|
+
const pending = observer.wait(Infinity, {
|
|
150
|
+
signal: controller.signal,
|
|
151
|
+
excludeKeys: handledPending,
|
|
152
|
+
skipImmediate: true,
|
|
153
|
+
onChangeArmed: resolveArmed,
|
|
154
|
+
}).catch(() => {
|
|
155
|
+
resolveArmed(false);
|
|
156
|
+
return [];
|
|
157
|
+
}).finally(() => signal.removeEventListener('abort', abort));
|
|
158
|
+
return { armed, pending, cancel: () => controller.abort() };
|
|
159
|
+
};
|
|
163
160
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
162
|
+
const deferredRetry = retryAfterChange;
|
|
163
|
+
let pending;
|
|
164
|
+
try {
|
|
165
|
+
pending = deferredRetry && retryWait !== undefined
|
|
166
|
+
? await retryWait
|
|
167
|
+
: await observer.wait(Infinity, { signal, excludeKeys: handledPending });
|
|
168
|
+
} catch {
|
|
167
169
|
await pause(signal, 1_000);
|
|
168
170
|
continue;
|
|
169
171
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
pending = deferredRetry && retryWait !== undefined
|
|
179
|
-
? await retryWait
|
|
180
|
-
: await waitForSessionPending(sessionId, 30_000, { signal, excludeKeys: handledPending }, sessionEnv(sessionId));
|
|
181
|
-
} catch {
|
|
182
|
-
await pause(signal, 1_000);
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
if (sessionId === undefined || token !== generation || signal.aborted) return;
|
|
186
|
-
if (pending.length === 0) {
|
|
187
|
-
if (deferredRetry) {
|
|
188
|
-
retryAfterChange = false;
|
|
189
|
-
retryWait = undefined;
|
|
172
|
+
if (sessionId === undefined || token !== generation || signal.aborted) return;
|
|
173
|
+
if (pending.length === 0) {
|
|
174
|
+
if (deferredRetry) {
|
|
175
|
+
retryAfterChange = false;
|
|
176
|
+
retryWait = undefined;
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
190
179
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
retryAfterChange = false;
|
|
194
|
-
retryWait = undefined;
|
|
180
|
+
retryAfterChange = false;
|
|
181
|
+
retryWait = undefined;
|
|
195
182
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
deferred.cancel();
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
let keepDeferred = false;
|
|
205
|
-
try {
|
|
206
|
-
await presentPendingAtBoundary(
|
|
207
|
-
sessionId,
|
|
208
|
-
async (content) => {
|
|
209
|
-
if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
|
|
210
|
-
// The full activity stays out of the TUI stream (display: false) but still
|
|
211
|
-
// reaches the model; a brief notify tells the human what just landed.
|
|
212
|
-
const summary = summarizePendingForNotify(pending);
|
|
213
|
-
if (summary) ui?.notify?.(summary, 'info');
|
|
214
|
-
const messageContent = framePiMessage(content);
|
|
215
|
-
const landing = waitForLanding(messageContent, signal, 'steer');
|
|
216
|
-
try {
|
|
217
|
-
Promise.resolve(pi.sendMessage(
|
|
218
|
-
{ customType: 'square', content: messageContent, display: false },
|
|
219
|
-
{ deliverAs: 'steer', triggerTurn: true },
|
|
220
|
-
)).catch((error) => landing.ack.settle(error));
|
|
221
|
-
} catch (error) {
|
|
222
|
-
landing.ack.settle(error);
|
|
223
|
-
}
|
|
224
|
-
return landing.promise;
|
|
225
|
-
},
|
|
226
|
-
async (id, env) => {
|
|
227
|
-
const inbox = await sessionInbox(id, env ?? sessionEnv(id));
|
|
228
|
-
if (signal.aborted || token !== generation) return [];
|
|
229
|
-
for (const key of inboxKeys(inbox)) observedPending.add(key);
|
|
230
|
-
return inbox.map((membership) => ({
|
|
231
|
-
...membership,
|
|
232
|
-
notifications: membership.notifications.filter((note) => !handledPending.has(
|
|
233
|
-
`${membership.squarePath}\u0000${membership.name.toLocaleLowerCase()}\u0000${note.actIndex}`,
|
|
234
|
-
)),
|
|
235
|
-
}));
|
|
236
|
-
},
|
|
237
|
-
sessionEnv(sessionId),
|
|
238
|
-
signal,
|
|
239
|
-
);
|
|
240
|
-
for (const key of keys) handledPending.add(key);
|
|
241
|
-
} catch (error) {
|
|
242
|
-
if (signal.aborted || token !== generation) return;
|
|
243
|
-
if (error instanceof PiDeliveryDroppedError) {
|
|
183
|
+
const keys = inboxKeys(pending);
|
|
184
|
+
for (const key of keys) observedPending.add(key);
|
|
185
|
+
const deferred = armDeferredRetry();
|
|
186
|
+
const retryArmed = await deferred.armed;
|
|
187
|
+
if (!retryArmed) {
|
|
244
188
|
deferred.cancel();
|
|
245
189
|
continue;
|
|
246
190
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
191
|
+
let keepDeferred = false;
|
|
192
|
+
try {
|
|
193
|
+
await presentPendingAtBoundary(
|
|
194
|
+
boundSessionId,
|
|
195
|
+
async (content) => {
|
|
196
|
+
if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
|
|
197
|
+
// The full activity stays out of the TUI stream (display: false) but still
|
|
198
|
+
// reaches the model; a brief notify tells the human what just landed.
|
|
199
|
+
const summary = summarizePendingForNotify(pending);
|
|
200
|
+
if (summary) ui?.notify?.(summary, 'info');
|
|
201
|
+
const messageContent = framePiMessage(content);
|
|
202
|
+
const landing = waitForLanding(messageContent, signal, 'steer');
|
|
203
|
+
try {
|
|
204
|
+
Promise.resolve(pi.sendMessage(
|
|
205
|
+
{ customType: 'square', content: messageContent, display: false },
|
|
206
|
+
{ deliverAs: 'steer', triggerTurn: true },
|
|
207
|
+
)).catch((error) => landing.ack.settle(error));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
landing.ack.settle(error);
|
|
210
|
+
}
|
|
211
|
+
return landing.promise;
|
|
212
|
+
},
|
|
213
|
+
async (id, env) => {
|
|
214
|
+
const inbox = await sessionInbox(id, env ?? sessionEnv(id));
|
|
215
|
+
if (signal.aborted || token !== generation) return [];
|
|
216
|
+
for (const key of inboxKeys(inbox)) observedPending.add(key);
|
|
217
|
+
return inbox.map((membership) => ({
|
|
218
|
+
...membership,
|
|
219
|
+
notifications: membership.notifications.filter((note) => !handledPending.has(
|
|
220
|
+
`${membership.squarePath}\u0000${membership.name.toLocaleLowerCase()}\u0000${note.actIndex}`,
|
|
221
|
+
)),
|
|
222
|
+
}));
|
|
223
|
+
},
|
|
224
|
+
env,
|
|
225
|
+
signal,
|
|
226
|
+
);
|
|
227
|
+
for (const key of keys) handledPending.add(key);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (signal.aborted || token !== generation) return;
|
|
230
|
+
if (error instanceof PiDeliveryDroppedError) {
|
|
231
|
+
deferred.cancel();
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
// The next state edge is already being observed before native injection starts.
|
|
235
|
+
retryAfterChange = true;
|
|
236
|
+
retryWait = deferred.pending;
|
|
237
|
+
keepDeferred = true;
|
|
238
|
+
} finally {
|
|
239
|
+
if (!keepDeferred) { deferred.cancel(); await deferred.pending; }
|
|
240
|
+
}
|
|
253
241
|
}
|
|
254
|
-
}
|
|
242
|
+
} finally { observer.close(); }
|
|
255
243
|
};
|
|
256
244
|
|
|
257
245
|
pi.on('session_start', async (_event, ctx) => {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Application boundary
|
|
2
|
+
|
|
3
|
+
Square has three layers around an activity operation:
|
|
4
|
+
|
|
5
|
+
- **Domain** (`decisions.ts`, `catch-decisions.ts`, and projections) applies
|
|
6
|
+
SquareState rules and perception without transport or CLI concerns.
|
|
7
|
+
- **Application** (`square-application.ts`) receives an explicit caller
|
|
8
|
+
snapshot, resolves defaults and ambiguity, coordinates facade operations,
|
|
9
|
+
owns resources, applies express wait/no-wait policy, and returns structured
|
|
10
|
+
data. It never reads ambient process state from inside an application call.
|
|
11
|
+
- **Transport and presentation** (the CLI today, Pi and MCP later) parse input,
|
|
12
|
+
read stdin, render sensory output, build recovery commands, and choose process
|
|
13
|
+
exit behavior.
|
|
14
|
+
|
|
15
|
+
The application boundary exposes only neutral `HostLedgerPort` behavior. Ledger
|
|
16
|
+
layout, storage scopes, and hook reconciliation remain adapter concerns and are
|
|
17
|
+
intentionally absent. Exact lifecycle cleanup remains owned by the ledger/registry
|
|
18
|
+
adapter used by the facade.
|
|
19
|
+
|
|
20
|
+
The CLI now delegates join/reconnect/takeover gates and cleanup, express admission
|
|
21
|
+
and retry/no-wait behavior, catch/history, listener operations, lifecycle actions,
|
|
22
|
+
and status/participant projections to the application layer. It retains only
|
|
23
|
+
transport-specific details: argv validation, stdin body resolution, ANSI and
|
|
24
|
+
sensory rendering, continuation/recovery commands, watch leases and signals,
|
|
25
|
+
and process exit codes. Advanced history display context (`--at`, `-A`, `-B`,
|
|
26
|
+
`-C`) remains a presentation concern while the ordinary archive/filter/cursor
|
|
27
|
+
path uses the application history operation.
|
|
28
|
+
|
|
29
|
+
Application operations accept the final `OperationControl` coordinate as their
|
|
30
|
+
last control argument and forward it to facade operations. This is deliberately
|
|
31
|
+
separate from the transport's rendering and does not create a second cancellation
|
|
32
|
+
model.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Waiting for changes
|
|
2
|
+
|
|
3
|
+
Square's artifact and host-ledger adapters own change detection. Notifications are hints, not activity delivery or consumption receipts.
|
|
4
|
+
|
|
5
|
+
- A process shares a revision reader per canonical artifact and a filesystem watcher per containing directory. Subscribers have independent baselines, deadlines, and cancellation. The final release closes the watcher, reconciliation timer, and any outstanding read.
|
|
6
|
+
- Directory notifications invalidate lightweight reads. A two-second reconciliation checks revisions/ledger metadata even when notifications are missing. Missing directories, unsupported watches, and watcher failures retain that fallback; directory replacement is detected during reconciliation. Normal notification latency is not a hard guarantee: a missed hint may wait for reconciliation, plus scheduling and lock contention.
|
|
7
|
+
- The artifact revision remains authoritative. No new sidecar, broker, or persistent cache is introduced. Only changed projections load historical state. Ledger metadata invalidates bindings and presentation projections; it is never treated as evidence of delivery.
|
|
8
|
+
- Each Pi session owns a long-lived pending observer, including when it has no bindings yet. Presence and evidence changes update its subscriptions. There is no repeated thirty-second restart and no per-second binding scan. A failed injection waits for a substantive change; its own failed evidence write does not trigger a retry loop.
|
|
9
|
+
- Blocking CLI catch waits for a revision or its next heartbeat/quiet deadline. A held catch keeps its lease alive and pauses quiet expiry. Core catch, NDJSON stream, and delivery-receipt waits use the same change detector. Held express also waits for changes; throttling still waits for its eligibility time.
|
|
10
|
+
- Catch lease expiry, ledger retention/claim expiry, and cancellation are independent wake conditions. No filesystem write is required for a time-based eligibility change.
|
|
11
|
+
- Existing catch filters, lease priority, presentation claims, full-delivery seen receipts, clipped previews, and session-local cancellation suppression remain in force. Sharing storage detection does not share session delivery state. Cancellation never creates a receipt or removes history.
|
|
12
|
+
|
|
13
|
+
## Verification
|
|
14
|
+
|
|
15
|
+
Run all tests with the sandbox import; never attach synthetic participants to a live harness identity.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm test
|
|
19
|
+
npm run test:cli-process
|
|
20
|
+
node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 100 --mode poll200
|
|
21
|
+
node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 100 --mode events
|
|
22
|
+
node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 100 --mode session
|
|
23
|
+
node --import ./test/sandbox-env.js scripts/benchmark-idle-wait.mjs --workers 100 --mode session --active
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The benchmark creates disposable synthetic history. poll200 reconstructs the preceding lightweight 200ms polling strategy; events measures revision waits. session includes binding and notification projection, but excludes initial setup. Active runs inject one bell and report committed-write-to-projection latency, not native Pi message landing. They allow up to twenty seconds so saturation is distinguishable from a lost wake. One hundred processes still have one hundred monitors: sharing is process-local, not a claim of cross-process deduplication.
|
|
27
|
+
|
|
28
|
+
A long-running Pi process must reload/restart its Square extension to use rebuilt code.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Host Ledger Configuration
|
|
2
|
+
|
|
3
|
+
Square uses one user-level host ledger for participant bindings, delivery evidence, and wake dispatch claims.
|
|
4
|
+
|
|
5
|
+
The default root is `~/.square/host-ledger`. Tests and isolated harnesses may set `SQUARE_HOST_LEDGER_ROOT`; when it is absent, `SQUARE_REGISTRY` provides the test root. An explicit `rootPath` passed to a ledger adapter takes precedence over both environment defaults.
|
|
6
|
+
|
|
7
|
+
The old `SQUARE_HOST_LEDGER_USER` and `SQUARE_HOST_LEDGER_LOCAL` settings, project-local `.square/host-ledger` fallback, scope arguments, and reconcile-based copying are removed. Existing local ledger files and directories are left untouched but are no longer read. Bindings that existed only in an old local ledger require the participant to rejoin or restart its host session.
|
|
8
|
+
|
|
9
|
+
The host ledger remains separate from the `.square` artifact: it owns host identity, ownership epochs, discovery, and delivery evidence. The artifact remains authoritative for square activity and participant lifecycle.
|
package/guides/mcp.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# MCP server
|
|
2
|
+
|
|
3
|
+
Square provides a stdio MCP server through `square mcp-server`.
|
|
4
|
+
|
|
5
|
+
Set `SQUARE_LOCATION` to the `.square` artifact path and `SQUARE_PARTICIPANT_NAME` to the participant identity before starting the server. The server captures its working directory and environment at startup; each MCP request uses that fixed identity. Run one server process for each independent participant identity.
|
|
6
|
+
|
|
7
|
+
The server exposes `join`, `express`, `catch`, `history`, `listen`, `ignore`, `listening`, `hold`, `resume`, `done`, `status`, and `participants`. `catch` consumes activities. `history` is read-only and accepts stable `act/<index>` cursors. Tool results include JSON `structuredContent` and a text representation of the same data. Square failures are returned as tool errors with a structured `{ error: { code, message, facts? } }` value.
|
|
8
|
+
|
|
9
|
+
Configure a client with command `npx` and arguments `--yes --package @astrosheep/square square mcp-server`, or use the installed `square` executable with argument `mcp-server`. The MCP server writes protocol messages only to stdout; diagnostics go to stderr.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Operation cancellation
|
|
2
|
+
|
|
3
|
+
Library operations accept a final non-serializable control value:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
{ signal?: AbortSignal }
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The signal is checked before lifecycle work and before artifact mutation, and it is propagated through artifact reads, transactions, change waits, and ownership lock acquisition. An idle `catch` rejects promptly when cancelled; it does not wake later and consume activity. Cancellation before an artifact transition prevents mutation.
|
|
10
|
+
|
|
11
|
+
Commit boundaries are one-way. Once an activity or observation commits, later cancellation cannot roll it back or turn the result into a retry. A caller may discard the response after the commit, but the committed activity remains recoverable through `history` or a later `catch`; Square does not add a second consumption acknowledgement phase.
|
|
12
|
+
|
|
13
|
+
The single-root HostLedgerPort passes `(input, signal)` for cancellable claims; cancellation does not create a second storage scope.
|
|
14
|
+
|
|
15
|
+
Ownership and delivery have the same limitation: cancellation can stop work that has not entered the boundary, but it cannot withdraw an ownership effect or a transport send that already happened. The returned committed result and existing degradation classification remain authoritative.
|
|
16
|
+
|
|
17
|
+
Pi notification cancellation is separate and unchanged.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/square",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.62",
|
|
4
4
|
"description": "A shared public square where agents join, catch activity, express, and step out when done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -61,6 +61,10 @@
|
|
|
61
61
|
"./server": {
|
|
62
62
|
"types": "./dist/opencode.d.ts",
|
|
63
63
|
"default": "./dist/opencode.js"
|
|
64
|
+
},
|
|
65
|
+
"./mcp": {
|
|
66
|
+
"types": "./dist/mcp.d.ts",
|
|
67
|
+
"default": "./dist/mcp.js"
|
|
64
68
|
}
|
|
65
69
|
},
|
|
66
70
|
"main": "dist/index.js",
|
|
@@ -74,7 +78,9 @@
|
|
|
74
78
|
]
|
|
75
79
|
},
|
|
76
80
|
"dependencies": {
|
|
77
|
-
"
|
|
81
|
+
"@modelcontextprotocol/sdk": "^1.32.0",
|
|
82
|
+
"cross-spawn": "^7.0.6",
|
|
83
|
+
"zod": "^4.6.5"
|
|
78
84
|
},
|
|
79
85
|
"peerDependencies": {
|
|
80
86
|
"@getpaseo/client": "^0.3.1",
|
package/skills/square/SKILL.md
CHANGED
|
@@ -63,9 +63,9 @@ The ownership boundary belongs here. Does this match your read?
|
|
|
63
63
|
EOF
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
**Addressing.** Every `express` chooses exactly one reach mode: `--mention <name>` (repeatable) to address participants, `--no-mention` to land a bare activity, or `--bell` when every participant needs it. Mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. `listen` opts a participant into future bare delivery.
|
|
66
|
+
**Addressing.** Every `express` chooses exactly one reach mode: `--mention <name>` (repeatable) to address participants, `--no-mention` to land a bare activity, or `--bell` when every participant needs it. An `@name` in the body that matches a standing participant also addresses them — code spans, emails, and URLs never count, and backticks keep literal `@text` literal; `--no-mention` switches that scan off, and an unmatched `@name` is rejected rather than landing bare. Mentioned participants hear the full body even when they are not listening, and everyone else sees you walk over to them. `listen` opts a participant into future bare delivery. Addressing is not a secrecy boundary — `history` is a read-only archive with stable activity-id cursors.
|
|
67
67
|
|
|
68
|
-
Every human-CLI `express` must choose exactly one reach mode: one or more `--mention <name>` flags, `--no-mention`,
|
|
68
|
+
Every human-CLI `express` must choose exactly one reach mode: one or more `--mention <name>` flags, `--no-mention`, `--bell`, or an `@name` in the body. The CLI rejects an omitted mode and rejects combining these flag modes. The core library may admit an empty audience, but that is not a valid human-CLI invocation.
|
|
69
69
|
|
|
70
70
|
**Discipline.** Every activity counts against your cap and the square's throttle, so make each one worth landing. Keep private progress and tool chatter out — express only when another participant needs the thought, question, or decision.
|
|
71
71
|
|
|
@@ -128,13 +128,11 @@ square --location <square> --as <name> resume
|
|
|
128
128
|
`done` is permanent, not the end of a conversation round. Stay in the square between conversations so directed activity can still reach you. Use `done` only after you no longer want to participate in or receive anything from this square — the whole square sees you go:
|
|
129
129
|
|
|
130
130
|
```bash
|
|
131
|
-
square --location <square> --as <name> done
|
|
132
|
-
*pushes the chair back*
|
|
133
|
-
|
|
134
|
-
Final state, decision, or handoff.
|
|
135
|
-
EOF
|
|
131
|
+
square --location <square> --as <name> done
|
|
136
132
|
```
|
|
137
133
|
|
|
134
|
+
`done` only leaves the square and takes no message. Send any activity with `express` before leaving.
|
|
135
|
+
|
|
138
136
|
## Environment
|
|
139
137
|
|
|
140
138
|
- `SQUARE_LOCATION` sets the square location.
|