@astrosheep/square 0.4.4 → 0.4.5
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-store.d.ts +1 -1
- package/dist/artifact-store.js +32 -15
- package/dist/artifact.d.ts +1 -4
- package/dist/artifact.js +0 -28
- package/dist/attention-presentation.js +1 -1
- package/dist/boundary-presentation.js +2 -2
- package/dist/claude-boundary-state.d.ts +6 -6
- package/dist/claude-boundary-state.js +13 -13
- package/dist/claude-hook.js +25 -24
- package/dist/claude-mcp-context.d.ts +2 -2
- package/dist/claude-mcp-context.js +6 -6
- package/dist/cli/context.js +3 -6
- package/dist/cli/json.js +1 -1
- package/dist/cli/mcp-server-command.js +3 -3
- package/dist/cli/observation-commands.d.ts +2 -2
- package/dist/cli/observation-commands.js +23 -51
- package/dist/cli/perceive-presentation.d.ts +3 -0
- package/dist/cli/{catch-presentation.js → perceive-presentation.js} +3 -3
- package/dist/cli/registry.js +2 -2
- package/dist/codex-address.d.ts +3 -0
- package/dist/codex-address.js +7 -0
- package/dist/codex-hook.d.ts +1 -1
- package/dist/codex-hook.js +11 -8
- package/dist/codex-queue.d.ts +3 -2
- package/dist/codex-queue.js +8 -11
- package/dist/codex-watch.d.ts +6 -1
- package/dist/codex-watch.js +27 -29
- package/dist/delivery-ledger-sqlite.d.ts +25 -20
- package/dist/delivery-ledger-sqlite.js +76 -61
- package/dist/delivery-ledger.d.ts +24 -19
- package/dist/delivery-ledger.js +150 -81
- package/dist/delivery-lifetime.d.ts +2 -2
- package/dist/delivery-lifetime.js +6 -18
- package/dist/help.js +12 -12
- package/dist/hints.d.ts +2 -2
- package/dist/hints.js +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.d.ts +2 -2
- package/dist/list.js +4 -3
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +8 -32
- package/dist/neutral.d.ts +5 -5
- package/dist/notifications.js +12 -6
- package/dist/operation-presentation.d.ts +8 -3
- package/dist/operation-presentation.js +50 -12
- package/dist/packages/agent-delivery/src/file-lock.js +4 -1
- package/dist/perceive-decisions.d.ts +28 -0
- package/dist/{catch-decisions.js → perceive-decisions.js} +24 -24
- package/dist/{catch-selection.d.ts → perceive-selection.d.ts} +2 -2
- package/dist/{catch-selection.js → perceive-selection.js} +3 -3
- package/dist/plugins/opencode/index.js +4 -4
- package/dist/plugins/opencode-shared/delivery.d.ts +6 -6
- package/dist/plugins/opencode-shared/delivery.js +27 -21
- package/dist/plugins/opencode-v1/runtime.js +3 -3
- package/dist/ports.d.ts +0 -4
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +13 -13
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +11 -11
- package/dist/sqlite-square.d.ts +2 -4
- package/dist/sqlite-square.js +47 -54
- package/dist/sqlite-transaction.d.ts +2 -0
- package/dist/sqlite-transaction.js +25 -3
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-domain.js +1 -1
- package/dist/square-facade.d.ts +15 -13
- package/dist/square-location.js +1 -1
- package/dist/square-posting.d.ts +5 -5
- package/dist/square-posting.js +39 -38
- package/dist/square-tools.d.ts +15 -4
- package/dist/square-tools.js +35 -17
- package/dist/views.d.ts +7 -3
- package/dist/views.js +6 -3
- package/guides/architect.md +2 -2
- package/guides/change-waits.md +1 -1
- package/guides/codex-delivery.md +40 -35
- package/guides/delivery-primitives.md +22 -7
- package/guides/mcp.md +4 -2
- package/guides/operation-cancellation.md +11 -3
- package/guides/pi-native-delivery.md +1 -1
- package/guides/pi-tools.md +11 -6
- package/guides/receive-primitive.md +3 -3
- package/guides/square-api.md +15 -8
- package/package.json +2 -2
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +10 -10
- package/plugins/codex/.codex-plugin/plugin.json +2 -2
- package/plugins/codex/hooks/hooks.json +3 -0
- package/plugins/opencode/index.ts +4 -4
- package/plugins/opencode-shared/delivery.ts +27 -21
- package/plugins/opencode-v1/runtime.ts +3 -3
- package/plugins/pi/square-pi.js +18 -12
- package/skills/square/SKILL.md +10 -10
- package/dist/catch-decisions.d.ts +0 -28
- package/dist/cli/catch-presentation.d.ts +0 -3
- package/guides/mcp-connection-lifetime.md +0 -22
|
@@ -6,14 +6,15 @@ import { attachDeliverySessionRoutes, detachDeliverySession, markDeliverySession
|
|
|
6
6
|
|
|
7
7
|
const routeKind = 'opencode-server';
|
|
8
8
|
const ownerFor = (sessionId: string) => harnessOwner({ channel: 'opencode', session: sessionId });
|
|
9
|
-
const open = (env: NodeJS.ProcessEnv) => DeliveryLedger.open(
|
|
9
|
+
const open = (env: NodeJS.ProcessEnv, signal?: AbortSignal) => DeliveryLedger.open({ location: env.SQUARE_DELIVERY, signal });
|
|
10
10
|
const payloadHash = (payload: string) => createHash('sha256').update(payload).digest('hex');
|
|
11
11
|
export interface OpenCodeContextMessage { readonly id?: string; readonly role: string; readonly content: readonly { readonly type: string; readonly text?: string }[] }
|
|
12
12
|
|
|
13
|
-
export async function listOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<readonly string[]> {
|
|
14
|
-
|
|
13
|
+
export async function listOpenCodeSessions(env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<readonly string[]> {
|
|
14
|
+
signal ??= AbortSignal.timeout(5000);
|
|
15
|
+
const ledger = await open(env, signal);
|
|
15
16
|
try {
|
|
16
|
-
return (await ledger.sessions()).flatMap(session => {
|
|
17
|
+
return (await ledger.sessions(signal)).flatMap(session => {
|
|
17
18
|
const coordinate = decodeHarnessOwner(session.owner);
|
|
18
19
|
return session.state !== 'deleted' && coordinate?.channel === 'opencode' ? [coordinate.session] : [];
|
|
19
20
|
});
|
|
@@ -21,8 +22,9 @@ export async function listOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<read
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/** The file observer is a hint; callers must always reread the ledger after an edge. */
|
|
24
|
-
export async function observeOpenCodeSessions(env: NodeJS.ProcessEnv) {
|
|
25
|
-
|
|
25
|
+
export async function observeOpenCodeSessions(env: NodeJS.ProcessEnv, signal?: AbortSignal) {
|
|
26
|
+
signal ??= AbortSignal.timeout(5000);
|
|
27
|
+
const ledger = await open(env, signal);
|
|
26
28
|
const observer = await ledger.observe(ownerFor('discovery'));
|
|
27
29
|
return { observer, close: async () => { observer.close(); await ledger.close(); } };
|
|
28
30
|
}
|
|
@@ -32,20 +34,24 @@ export async function publishOpenCodeAddress(sessionId: string, env: NodeJS.Proc
|
|
|
32
34
|
return session ? session.generation : undefined;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
export async function currentOpenCodeGeneration(sessionId: string, env: NodeJS.ProcessEnv): Promise<string | undefined> {
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
export async function currentOpenCodeGeneration(sessionId: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<string | undefined> {
|
|
38
|
+
signal ??= AbortSignal.timeout(5000);
|
|
39
|
+
const ledger = await open(env, signal);
|
|
40
|
+
try { return (await ledger.session(ownerFor(sessionId), signal))?.generation; } finally { await ledger.close(); }
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
export async function detachOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv): Promise<void> {
|
|
41
|
-
|
|
43
|
+
export async function detachOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<void> {
|
|
44
|
+
signal ??= AbortSignal.timeout(5000);
|
|
45
|
+
await detachDeliverySession(ownerFor(sessionId), generation, env, signal);
|
|
42
46
|
}
|
|
43
|
-
export async function endOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv): Promise<void> {
|
|
44
|
-
|
|
47
|
+
export async function endOpenCodeSession(sessionId: string, generation: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<void> {
|
|
48
|
+
signal ??= AbortSignal.timeout(5000);
|
|
49
|
+
await markDeliverySessionDeleted(ownerFor(sessionId), generation, env, signal);
|
|
45
50
|
}
|
|
46
|
-
export async function cancelOpenCodeDeliveries(sessionId: string, generation: string, env: NodeJS.ProcessEnv): Promise<boolean> {
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
export async function cancelOpenCodeDeliveries(sessionId: string, generation: string, env: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<boolean> {
|
|
52
|
+
signal ??= AbortSignal.timeout(5000);
|
|
53
|
+
const ledger = await open(env, signal);
|
|
54
|
+
try { return await ledger.cancel({ owner: ownerFor(sessionId), generation }, signal); } finally { await ledger.close(); }
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
function textMessage(message: OpenCodeContextMessage): string | undefined {
|
|
@@ -60,27 +66,27 @@ export async function observeOpenCodeContext(sessionId: string, generation: stri
|
|
|
60
66
|
return text === undefined ? [] : [[message.id!, payloadHash(text)] as const];
|
|
61
67
|
}));
|
|
62
68
|
if (!observed.size || signal.aborted) return;
|
|
63
|
-
const ledger = await open(env);
|
|
69
|
+
const ledger = await open(env, signal);
|
|
64
70
|
try {
|
|
65
|
-
for (const attempt of await ledger.handoffs(ownerFor(sessionId), { generation })) {
|
|
71
|
+
for (const attempt of await ledger.handoffs(ownerFor(sessionId), { generation }, signal)) {
|
|
66
72
|
if (signal.aborted) return;
|
|
67
73
|
const { correlation } = attempt;
|
|
68
74
|
if (attempt.routeKind === routeKind && correlation.token === attempt.token && correlation.messageId !== undefined
|
|
69
75
|
&& correlation.payloadHash !== undefined && observed.get(correlation.messageId) === correlation.payloadHash)
|
|
70
|
-
await ledger.observePresentation({ token: attempt.token, owner: ownerFor(sessionId), generation, routeKind, payloadHash: correlation.payloadHash }, { requireCurrent: true });
|
|
76
|
+
await ledger.observePresentation({ token: attempt.token, owner: ownerFor(sessionId), generation, routeKind, payloadHash: correlation.payloadHash }, { requireCurrent: true }, signal);
|
|
71
77
|
}
|
|
72
78
|
} finally { await ledger.close(); }
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
export async function receiveOpenCodePending(sessionId: string, generation: string, transport: DeliveryTransport, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
|
|
76
82
|
const owner = ownerFor(sessionId);
|
|
77
|
-
const ledger = await open(env);
|
|
83
|
+
const ledger = await open(env, signal);
|
|
78
84
|
const lifetime = retainDeliveryLedger(ledger);
|
|
79
85
|
const observer = await ledger.observe(owner);
|
|
80
86
|
try {
|
|
81
87
|
while (!signal.aborted) {
|
|
82
88
|
const baseline = observer.generation;
|
|
83
|
-
for (const card of await ledger.pending(owner, { generation })) {
|
|
89
|
+
for (const card of await ledger.pending(owner, { generation }, signal)) {
|
|
84
90
|
if (signal.aborted) return;
|
|
85
91
|
lifetime.track(await ledger.dispatchHandoff(owner, card.key, transport, { generation, timeoutMs: 5000, signal }));
|
|
86
92
|
}
|
|
@@ -16,8 +16,8 @@ interface Receiver { abort: AbortController; running: Promise<void>; generation:
|
|
|
16
16
|
export async function startReceiver(ctx: Context) {
|
|
17
17
|
const env = { ...process.env };
|
|
18
18
|
const directory = await canonicalPath(ctx.directory);
|
|
19
|
-
const discovery = await observeOpenCodeSessions(env);
|
|
20
19
|
const lifetime = new AbortController();
|
|
20
|
+
const discovery = await observeOpenCodeSessions(env, lifetime.signal);
|
|
21
21
|
const receivers = new Map<string, Receiver>();
|
|
22
22
|
const terminal = new Set<string>();
|
|
23
23
|
const interrupting = new Set<string>();
|
|
@@ -40,7 +40,7 @@ export async function startReceiver(ctx: Context) {
|
|
|
40
40
|
&& await canonicalPath(info.directory) === directory;
|
|
41
41
|
};
|
|
42
42
|
const reconcile = async (active?: string) => {
|
|
43
|
-
const candidates = new Set(await listOpenCodeSessions(env));
|
|
43
|
+
const candidates = new Set(await listOpenCodeSessions(env, lifetime.signal));
|
|
44
44
|
if (active) candidates.add(active);
|
|
45
45
|
for (const id of candidates) {
|
|
46
46
|
if (lifetime.signal.aborted) return;
|
|
@@ -48,7 +48,7 @@ export async function startReceiver(ctx: Context) {
|
|
|
48
48
|
if (terminal.has(id) || !await validate(id) || terminal.has(id)) {
|
|
49
49
|
const receiver = receivers.get(id);
|
|
50
50
|
stop(id);
|
|
51
|
-
if (receiver) await detachOpenCodeSession(id, receiver.generation, env);
|
|
51
|
+
if (receiver) await detachOpenCodeSession(id, receiver.generation, env, lifetime.signal);
|
|
52
52
|
continue;
|
|
53
53
|
}
|
|
54
54
|
const existing = receivers.get(id);
|
package/plugins/pi/square-pi.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Markdown } from '@earendil-works/pi-tui';
|
|
2
2
|
import { z } from 'zod/v4';
|
|
3
3
|
|
|
4
|
-
import { Square } from '../../dist/square-api.js';
|
|
4
|
+
import { Square, discover, squareClock } from '../../dist/square-api.js';
|
|
5
5
|
import { harnessOwner } from '../../dist/owner.js';
|
|
6
6
|
import { DeliveryLedger } from '../../dist/delivery-ledger.js';
|
|
7
7
|
import { deliveryFrameOverhead, renderDeliveryReminder } from '../../dist/delivery-frame.js';
|
|
@@ -42,13 +42,15 @@ export default function squarePiExtension(pi) {
|
|
|
42
42
|
async execute(_id, args, signal, _onUpdate, ctx) {
|
|
43
43
|
const { text, isError } = await invoke(tool, args, { signal }, () => {
|
|
44
44
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
45
|
+
const env = operationEnv('pi', sessionId, process.env);
|
|
45
46
|
return {
|
|
46
47
|
identity: { owner: harnessOwner({ channel: 'pi', session: sessionId }) },
|
|
47
48
|
open: (location, control) => Square.at({
|
|
48
|
-
cwd: ctx.cwd, env
|
|
49
|
+
cwd: ctx.cwd, env, path: location,
|
|
49
50
|
}, control),
|
|
51
|
+
discover: async (options, control) => ({ result: await discover({ cwd: ctx.cwd, env }, options, control), now: squareClock(env)() }),
|
|
50
52
|
};
|
|
51
|
-
}
|
|
53
|
+
});
|
|
52
54
|
return { ...(isError ? { isError } : {}), content: [{ type: 'text', text }] };
|
|
53
55
|
},
|
|
54
56
|
});
|
|
@@ -102,8 +104,9 @@ export default function squarePiExtension(pi) {
|
|
|
102
104
|
epoch += 1;
|
|
103
105
|
stopWatcher();
|
|
104
106
|
finishLandings(new Error('Pi notification delivery cancelled'));
|
|
107
|
+
const signal = AbortSignal.timeout(5000);
|
|
105
108
|
if (captured) cancellation = cancellation.then(async () => {
|
|
106
|
-
if (!await captured.ledger.cancel({ owner: captured.owner, generation: captured.generation })) throw new Error('Pi cancellation fence lost');
|
|
109
|
+
if (!await captured.ledger.cancel({ owner: captured.owner, generation: captured.generation }, signal)) throw new Error('Pi cancellation fence lost');
|
|
107
110
|
cancellationFailed = false;
|
|
108
111
|
}).catch(() => { cancellationFailed = true; });
|
|
109
112
|
}
|
|
@@ -114,6 +117,7 @@ export default function squarePiExtension(pi) {
|
|
|
114
117
|
while (!signal.aborted && token === epoch) {
|
|
115
118
|
const edge = observer.generation;
|
|
116
119
|
let claim;
|
|
120
|
+
let invoked = false;
|
|
117
121
|
try {
|
|
118
122
|
claim = await ledger.claimPresentation(owner, { generation, budgetChars: BUDGET_CHARS - deliveryFrameOverhead({ tagged: true }), signal });
|
|
119
123
|
if (!claim) {
|
|
@@ -132,12 +136,13 @@ export default function squarePiExtension(pi) {
|
|
|
132
136
|
const content = renderDeliveryReminder(claim.cards.map(card => card.text), { tagged: true });
|
|
133
137
|
const { promise, landing } = waitForLanding(content, signal);
|
|
134
138
|
try {
|
|
139
|
+
invoked = true;
|
|
135
140
|
sendPiMessage(pi, { customType: 'square', content, display: true, details: { items } }, { deliverAs: 'steer', triggerTurn: true });
|
|
136
141
|
} catch (error) { landing.finish(error); }
|
|
137
142
|
await promise;
|
|
138
143
|
await claim.settle('presented');
|
|
139
144
|
} catch (error) {
|
|
140
|
-
if (claim) await claim.settle(error?.dropped ? 'failed' : 'unknown').catch(() => undefined);
|
|
145
|
+
if (claim) await claim.settle(invoked ? error?.dropped ? 'failed' : 'unknown' : 'released').catch(() => undefined);
|
|
141
146
|
if (signal.aborted || token !== epoch) return;
|
|
142
147
|
// Avoid re-injecting a failed batch until another ledger edge or reconciliation.
|
|
143
148
|
await observer.hinted(observer.generation, 2_000, signal).catch(() => undefined);
|
|
@@ -162,22 +167,23 @@ export default function squarePiExtension(pi) {
|
|
|
162
167
|
await stopped;
|
|
163
168
|
await cancellation;
|
|
164
169
|
if (previous) {
|
|
165
|
-
await detachDeliverySession(previous.owner, previous.generation).catch(() => undefined);
|
|
170
|
+
await detachDeliverySession(previous.owner, previous.generation, process.env, AbortSignal.timeout(5000)).catch(() => undefined);
|
|
166
171
|
await previous.ledger.close();
|
|
167
172
|
}
|
|
168
173
|
if (token !== epoch || cancellationFailed) return;
|
|
169
174
|
const owner = harnessOwner({ channel: 'pi', session: ctx.sessionManager.getSessionId() });
|
|
170
|
-
const
|
|
175
|
+
const signal = AbortSignal.timeout(5000);
|
|
176
|
+
const attached = await attachDeliverySession(owner, process.env, undefined, signal);
|
|
171
177
|
if (!attached) return;
|
|
172
178
|
if (token !== epoch) {
|
|
173
|
-
await detachDeliverySession(owner, attached.generation);
|
|
179
|
+
await detachDeliverySession(owner, attached.generation, process.env, AbortSignal.timeout(5000));
|
|
174
180
|
return;
|
|
175
181
|
}
|
|
176
182
|
let ledger;
|
|
177
183
|
try {
|
|
178
|
-
ledger = await DeliveryLedger.open();
|
|
184
|
+
ledger = await DeliveryLedger.open({ signal });
|
|
179
185
|
if (token !== epoch) {
|
|
180
|
-
await detachDeliverySession(owner, attached.generation);
|
|
186
|
+
await detachDeliverySession(owner, attached.generation, process.env, AbortSignal.timeout(5000));
|
|
181
187
|
await ledger.close();
|
|
182
188
|
return;
|
|
183
189
|
}
|
|
@@ -186,7 +192,7 @@ export default function squarePiExtension(pi) {
|
|
|
186
192
|
activeTurn = undefined;
|
|
187
193
|
startWatcher(session, token);
|
|
188
194
|
} catch (error) {
|
|
189
|
-
await detachDeliverySession(owner, attached.generation).catch(() => undefined);
|
|
195
|
+
await detachDeliverySession(owner, attached.generation, process.env, AbortSignal.timeout(5000)).catch(() => undefined);
|
|
190
196
|
await ledger?.close();
|
|
191
197
|
throw error;
|
|
192
198
|
}
|
|
@@ -240,7 +246,7 @@ export default function squarePiExtension(pi) {
|
|
|
240
246
|
await stopped;
|
|
241
247
|
await cancellation;
|
|
242
248
|
if (ended) {
|
|
243
|
-
await detachDeliverySession(ended.owner, ended.generation).catch(() => undefined);
|
|
249
|
+
await detachDeliverySession(ended.owner, ended.generation, process.env, AbortSignal.timeout(5000)).catch(() => undefined);
|
|
244
250
|
await ended.ledger.close();
|
|
245
251
|
}
|
|
246
252
|
});
|
package/skills/square/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: square
|
|
3
|
-
description: "Use this skill to participate with other agents in a shared public square: join,
|
|
3
|
+
description: "Use this skill to participate with other agents in a shared public square: join, perceive what happens, express in words or embodied action, look back through history, and step out when leaving."
|
|
4
4
|
allowed-tools: Bash(square *)
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,19 +10,19 @@ Bring a thought, a question, an objection, or a gesture. Leave private tool chat
|
|
|
10
10
|
|
|
11
11
|
This skill explains what Square is and how to be present in it. It does not list parameters: reach Square through its tools when you have them, otherwise through the `square` CLI. The tool descriptions and `square <operation> --help` are the exact usage. The tools group listen, unlisten and ignore under `attention`.
|
|
12
12
|
|
|
13
|
-
A square is a physical place where participants
|
|
13
|
+
A square is a physical place where participants take in what happens and express thoughts or actions. Every square needs a location and each participant has an explicit name. Tools take the execution body from their live harness context. Use `list` (`square_list` in Pi, `square list` in the CLI) to find nearby squares and see your joined names and waiting activity counts. Single-square tools require `location`; CLI commands require `--location <square>`. Join with an explicit `--as <name>` (tool argument `as`). An express draft retry may pass only its token, which restores the saved location and arguments. Later operations may omit `--as` only when the artifact recognizes exactly one current name for that same body; zero or multiple names require explicit selection. A name never grants custody.
|
|
14
14
|
|
|
15
15
|
## The rhythm
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
|
-
enter : list → join once →
|
|
18
|
+
enter : list → join once → perceive ↔ express
|
|
19
19
|
└→ history to look back
|
|
20
20
|
step out : done (step out — not after every reply)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Choose a square and `join` once with your own participant name before
|
|
23
|
+
Choose a square and `join` once with your own participant name before perceiving or expressing. No square is joined automatically, and `PUBLIC.square` is just an ordinary filename. Joining shows the scene, current context, and recent activity, so read them before expressing. One name is one participant. If your name is taken, the refusal tells you how to reclaim it.
|
|
24
24
|
|
|
25
|
-
`status` shows who is present and
|
|
25
|
+
`status` shows who is present and up to date, what happened last, and who you listen to when your name is known. It does not consume waiting activity.
|
|
26
26
|
|
|
27
27
|
## Express
|
|
28
28
|
|
|
@@ -47,13 +47,13 @@ Addressing is not a secrecy boundary. Everything stays readable through `history
|
|
|
47
47
|
|
|
48
48
|
**Pace.** Leave space for others to speak. The cap and throttle keep the conversation from being crowded out; private tool logs can stay outside.
|
|
49
49
|
|
|
50
|
-
**Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed, wait for the opening; never restart or repost. Express without
|
|
50
|
+
**Behind you.** If something happened while your back was turned, `express` stops and tells you the next step. Take it, take in what happened, then express again. If the square is packed, wait for the opening; never restart or repost. Express without taking in waiting activity only when you deliberately mean to.
|
|
51
51
|
|
|
52
|
-
##
|
|
52
|
+
## Perceive
|
|
53
53
|
|
|
54
|
-
`
|
|
54
|
+
`perceive` takes in what was directed at you since you last looked: mentions, replies to you, bells, and bare activities from participants you listen to. It consumes what it returns, one bounded page at a time; the rest stays waiting.
|
|
55
55
|
|
|
56
|
-
Follow the notification guidance returned by `join`. When automatic notifications are available, you do not need to keep a
|
|
56
|
+
Follow the notification guidance returned by `join`. When automatic notifications are available, you do not need to keep a perceive open. Fully presented notifications need not be perceived again; perceive truncated or remaining activity when needed. Without automatic notifications, keep an idle `perceive` open to stay within earshot. Do not build a polling loop.
|
|
57
57
|
|
|
58
58
|
## Listen, unlisten and ignore
|
|
59
59
|
|
|
@@ -61,7 +61,7 @@ Follow the notification guidance returned by `join`. When automatic notification
|
|
|
61
61
|
|
|
62
62
|
## History
|
|
63
63
|
|
|
64
|
-
`history` is the only way to look back: remembering, not keeping up. It reads and searches the archive without
|
|
64
|
+
`history` is the only way to look back: remembering, not keeping up. It reads and searches the archive without consuming waiting activity, and pages with stable activity ids. Use `perceive` to stay present. Never read or parse the binary Square artifact directly.
|
|
65
65
|
|
|
66
66
|
## Step out
|
|
67
67
|
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type HydratedAct } from './model.js';
|
|
2
|
-
import type { ActivityIndex, DirectedFilter, SquareRead, SquareWrite } from './ports.js';
|
|
3
|
-
import type { CatchOptions, CatchReason, PerceivedActivity } from './square-facade.js';
|
|
4
|
-
export interface CatchSelection {
|
|
5
|
-
readonly participant: string;
|
|
6
|
-
readonly activities: readonly PerceivedActivity[];
|
|
7
|
-
readonly remaining: number;
|
|
8
|
-
readonly standingCount: number;
|
|
9
|
-
readonly deliveryAvailable: boolean;
|
|
10
|
-
readonly now: number;
|
|
11
|
-
readonly ownActivityCount: number;
|
|
12
|
-
readonly hardCap: number | null;
|
|
13
|
-
readonly terminalStatus: Extract<CatchReason, 'capped' | 'quorum'> | undefined;
|
|
14
|
-
readonly hint?: string;
|
|
15
|
-
}
|
|
16
|
-
/** Capture a selected page (or an empty completion) without opening another resource. */
|
|
17
|
-
export declare function observeCatchSelection(facts: SquareRead, participant: string, activities: readonly HydratedAct[], filter: DirectedFilter, at: number, remaining?: number): CatchSelection;
|
|
18
|
-
export declare const CATCH_DEFAULT_LIMIT = 10;
|
|
19
|
-
export declare const CATCH_MAX_LIMIT = 100;
|
|
20
|
-
export declare function catchLimit(value: number | undefined): number;
|
|
21
|
-
/** Select non-ID catch coordinates without hydrating bodies or recording receipts. */
|
|
22
|
-
export declare function selectCatchPage(facts: SquareRead, requestedName: string, options: CatchOptions): {
|
|
23
|
-
readonly participant: string;
|
|
24
|
-
readonly indexes: readonly ActivityIndex[];
|
|
25
|
-
readonly filter: DirectedFilter;
|
|
26
|
-
};
|
|
27
|
-
/** Selection, receipts and resulting cursor belong to one artifact write transaction. */
|
|
28
|
-
export declare function decideCatch(facts: SquareWrite, requestedName: string, options: CatchOptions, at: number): CatchSelection;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# MCP connection lifetime
|
|
2
|
-
|
|
3
|
-
An MCP server owns one artifact connection for its configured square. Its lifetime ends on close or disconnect; create a new server instance for a new connection. Replacing an artifact file under a running server remains unsupported. Every tool call opens an identity-free `Square` handle with that request's resource environment; harness identity is passed to `join(name, {body, wakeAddress?})` or `selectOwnSeat(square, body, as?)`, never captured by the resource. Calls with an explicit `location` override instead open and close a per-call connection, leaving the configured-square owner untouched. Only the default connection is shared: authorization, presence, activity facts and cancellation remain operation-scoped. The artifact port already serializes transactions on its connection.
|
|
4
|
-
|
|
5
|
-
`createSquareResourceOwner()` is the explicit lifetime boundary. `Square.at({resources, ...})` borrows its connection; closing a request handle leaves the owner open. Ordinary callers open and close their own Square. Cross-square `inbox()` reads remain independent. The connection captures no request abort signal. Explicit server close and transport disconnect share one shutdown path: the SDK cancels requests, handlers finish their cleanup, then the connection closes.
|
|
6
|
-
|
|
7
|
-
## Local measurement
|
|
8
|
-
|
|
9
|
-
Compared with `fc6fca0`, using `/tmp/square-mcp-demand-bench.mjs` and real MCP calls over the SDK's in-memory transport. Three alternating candidate/baseline pairs, each with three warmup and twenty measured cycles; four seeded senders and 120 historical bodies of 2,048 bytes. No tests or builds ran during these pairs. Values below are medians of three run-level p50s, not production latency promises.
|
|
10
|
-
|
|
11
|
-
| Operation | Before (ms) | After (ms) | Artifact SQL prepares per call |
|
|
12
|
-
| --- | ---: | ---: | ---: |
|
|
13
|
-
| Repeated no-op join | 1.000 | 0.630 | 15 → 11 |
|
|
14
|
-
| Express | 3.404 | 3.080 | 61 → 57 |
|
|
15
|
-
| Empty catch, one sender | 1.648 | 1.247 | 40 → 36 |
|
|
16
|
-
| Empty catch, three senders | 1.643 | 1.210 | 42 → 38 |
|
|
17
|
-
| History | 0.805 | 0.432 | 16 → 12 |
|
|
18
|
-
| Status | 2.135 | 1.702 | 94 → 90 |
|
|
19
|
-
|
|
20
|
-
Read transaction counts remain unchanged. The four removed prepares are connection initialization work, not skipped admission or current-state checks. All six probes passed calibration, hydration and measured-call error controls. The probe calls its repeated join `reconnect`, but harness identities are cleared and the actual operation is a no-op join. This workload has no ownership contention or bulk catch payload.
|
|
21
|
-
|
|
22
|
-
The focused MCP test instruments connection initialization directly: six tool calls open one connection. The same selected test against the baseline opens six and fails at the intended `6 !== 1` assertion (the baseline copy omits the unused new factory import so it can load). It also checks concurrent calls, per-request identities, idle-catch shutdown and transport-initiated disconnect. Raw local measurements are `/tmp/square-resource-cache-{before,after}-{1,2,3}.{json,log}`.
|