@astrosheep/square 0.3.76 → 0.3.78
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/attention-presentation.d.ts +1 -1
- package/dist/attention-presentation.js +7 -10
- package/dist/boundary-presentation.js +3 -2
- package/dist/claude-mcp-context.d.ts +15 -0
- package/dist/claude-mcp-context.js +82 -0
- package/dist/claude-mod.js +8 -0
- package/dist/cli/context.js +0 -1
- package/dist/cli/maintenance-commands.js +2 -2
- package/dist/cli/program.js +8 -2
- package/dist/host-ledger-migration.js +2 -6
- package/dist/host-ledger-sqlite.js +2 -2
- package/dist/mcp.js +28 -57
- package/dist/model.d.ts +2 -0
- package/dist/square-sqlite.d.ts +2 -8
- package/dist/square-sqlite.js +3 -11
- package/dist/square-tools.d.ts +14 -0
- package/dist/square-tools.js +50 -0
- package/guides/application-facade-design.md +4 -1
- package/guides/claude-native.md +32 -1
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +5 -1
- package/guides/migration.md +2 -1
- package/guides/pi-tools.md +21 -0
- package/guides/row-storage-design.md +1 -1
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/.mcp.json +11 -0
- package/plugins/claude/hooks/register.js +10 -24
- package/plugins/claude/skills/square/SKILL.md +29 -100
- package/plugins/claude/types.d.ts +0 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/square-pi.js +95 -25
- package/skills/square/SKILL.md +29 -100
|
@@ -11,7 +11,7 @@ export interface AttentionPreview {
|
|
|
11
11
|
export declare function previewAttentionBody(body: string): string;
|
|
12
12
|
export declare function attentionBodyIsClipped(body: string): boolean;
|
|
13
13
|
export declare function displayAttentionPath(squarePath: string): string;
|
|
14
|
-
export declare function renderAttentionDescription({ actor,
|
|
14
|
+
export declare function renderAttentionDescription({ actor, route }: Pick<AttentionPreview, 'actor' | 'route'>): string;
|
|
15
15
|
/** Full notification text for human-facing expansion, never the bounded model preview. */
|
|
16
16
|
export declare function renderAttentionFull(attention: AttentionPreview): string;
|
|
17
17
|
export declare function renderAttentionPreview(attention: AttentionPreview): string;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { formatActivityId } from './square-core.js';
|
|
3
|
-
import { participantCommandPrefix } from './presentation.js';
|
|
4
3
|
export const ATTENTION_BODY_MAX = 200;
|
|
5
4
|
export function previewAttentionBody(body) {
|
|
6
5
|
const compact = body.replace(/\r\n/g, '\n');
|
|
@@ -16,12 +15,12 @@ export function displayAttentionPath(squarePath) {
|
|
|
16
15
|
? `~${squarePath.slice(homedir().length)}`
|
|
17
16
|
: squarePath;
|
|
18
17
|
}
|
|
19
|
-
export function renderAttentionDescription({ actor,
|
|
18
|
+
export function renderAttentionDescription({ actor, route }) {
|
|
20
19
|
switch (route) {
|
|
21
|
-
case 'mention': return `${actor} spoke your name
|
|
22
|
-
case 'reply': return `${actor} answered you
|
|
23
|
-
case 'attention': return `${actor} spoke · you’re listening
|
|
24
|
-
case 'bell': return `${actor} rang the bell
|
|
20
|
+
case 'mention': return `${actor} spoke your name`;
|
|
21
|
+
case 'reply': return `${actor} answered you`;
|
|
22
|
+
case 'attention': return `${actor} spoke · you’re listening`;
|
|
23
|
+
case 'bell': return `${actor} rang the bell`;
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
/** Full notification text for human-facing expansion, never the bounded model preview. */
|
|
@@ -31,7 +30,7 @@ export function renderAttentionFull(attention) {
|
|
|
31
30
|
function renderAttentionBlock(attention, body) {
|
|
32
31
|
return [
|
|
33
32
|
'<square-activity>',
|
|
34
|
-
`· ${displayAttentionPath(attention.squarePath)} · ${formatActivityId(attention.actIndex)}`,
|
|
33
|
+
`· ${displayAttentionPath(attention.squarePath)} · ${attention.recipient} · ${formatActivityId(attention.actIndex)}`,
|
|
35
34
|
`● ${renderAttentionDescription(attention)}`,
|
|
36
35
|
'',
|
|
37
36
|
body,
|
|
@@ -41,10 +40,8 @@ function renderAttentionBlock(attention, body) {
|
|
|
41
40
|
export function renderAttentionPreview(attention) {
|
|
42
41
|
return [
|
|
43
42
|
renderAttentionBlock(attention, previewAttentionBody(attention.body)),
|
|
44
|
-
// A clipped body ends with the full command to read it all: a labeled caption, then the
|
|
45
|
-
// command bare on its own line so it can be copied and run as it is.
|
|
46
43
|
...(attentionBodyIsClipped(attention.body)
|
|
47
|
-
? ['· clipped —
|
|
44
|
+
? ['· clipped — catch it to read the rest']
|
|
48
45
|
: []),
|
|
49
46
|
].join('\n');
|
|
50
47
|
}
|
|
@@ -46,6 +46,7 @@ function renderBoundary(inbox) {
|
|
|
46
46
|
const blocks = [];
|
|
47
47
|
const complete = [];
|
|
48
48
|
let omitted = 0;
|
|
49
|
+
const omission = (count) => `… ${count} more — catch up to read them`;
|
|
49
50
|
for (const [index, entry] of queued.entries()) {
|
|
50
51
|
const { membership, notification } = entry;
|
|
51
52
|
const block = [
|
|
@@ -59,7 +60,7 @@ function renderBoundary(inbox) {
|
|
|
59
60
|
}),
|
|
60
61
|
].join('\n');
|
|
61
62
|
const omittedAfter = omitted + queued.length - index - 1;
|
|
62
|
-
const prospective = frameBoundary([...blocks, block, ...(omittedAfter > 0 ? [
|
|
63
|
+
const prospective = frameBoundary([...blocks, block, ...(omittedAfter > 0 ? [omission(omittedAfter)] : [])]);
|
|
63
64
|
if (prospective.length > CONTEXT_MAX) {
|
|
64
65
|
omitted += 1;
|
|
65
66
|
continue;
|
|
@@ -72,7 +73,7 @@ function renderBoundary(inbox) {
|
|
|
72
73
|
});
|
|
73
74
|
}
|
|
74
75
|
return {
|
|
75
|
-
context: frameBoundary([...blocks, ...(omitted > 0 ? [
|
|
76
|
+
context: frameBoundary([...blocks, ...(omitted > 0 ? [omission(omitted)] : [])]),
|
|
76
77
|
complete,
|
|
77
78
|
};
|
|
78
79
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
declare const coordinate: z.ZodObject<{
|
|
3
|
+
toolUseId: z.ZodString;
|
|
4
|
+
sessionId: z.ZodString;
|
|
5
|
+
cwd: z.ZodString;
|
|
6
|
+
agentId: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
type ClaudeMcpCall = z.infer<typeof coordinate>;
|
|
9
|
+
export declare function beginClaudeMcpCall(input: ClaudeMcpCall, env?: NodeJS.ProcessEnv): Promise<{
|
|
10
|
+
token: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function endClaudeMcpCall(toolUseId: string, token: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
13
|
+
/** Expiry gates lookup only; an executing tool keeps its captured coordinate. */
|
|
14
|
+
export declare function readClaudeMcpCall(toolUseId: unknown, env?: NodeJS.ProcessEnv): Promise<ClaudeMcpCall>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { z } from 'zod/v4';
|
|
5
|
+
import { withFileLock } from './file-lock.js';
|
|
6
|
+
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
7
|
+
import { SquareError } from './model.js';
|
|
8
|
+
const LEASE_MS = 10 * 60 * 1000;
|
|
9
|
+
const text = z.string().max(4096).refine(value => value.trim().length > 0 && !/[\x00-\x1f\x7f]/u.test(value));
|
|
10
|
+
const coordinate = z.object({
|
|
11
|
+
toolUseId: text, sessionId: text, cwd: text.refine(path.isAbsolute), agentId: text.optional(),
|
|
12
|
+
});
|
|
13
|
+
const record = coordinate.extend({ token: z.string().regex(/^[a-f0-9]{64}$/u), expiresAt: z.number() });
|
|
14
|
+
const unavailable = () => new SquareError('unavailable', 'Claude MCP call context unavailable');
|
|
15
|
+
const storeRoot = (env) => path.resolve(hostLedgerRoot(env), 'claude-mcp-calls');
|
|
16
|
+
const callFile = (root, id) => path.join(root, `${createHash('sha256').update(id).digest('hex')}.json`);
|
|
17
|
+
async function readRecord(file) {
|
|
18
|
+
try {
|
|
19
|
+
return record.parse(JSON.parse(await fs.readFile(file, 'utf8')));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code === 'ENOENT')
|
|
23
|
+
return undefined;
|
|
24
|
+
throw unavailable();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Only publishers/cleanup mutate this private transport directory. Readers see an
|
|
28
|
+
// immutable record via atomic rename, so they need neither a lock nor housekeeping.
|
|
29
|
+
async function mutate(env, fn) {
|
|
30
|
+
const root = storeRoot(env);
|
|
31
|
+
await fs.mkdir(root, { recursive: true, mode: 0o700 });
|
|
32
|
+
await fs.chmod(root, 0o700);
|
|
33
|
+
return withFileLock(path.join(root, 'store.lock'), { retryMs: 10, signal: AbortSignal.timeout(2000) }, () => fn(root));
|
|
34
|
+
}
|
|
35
|
+
export async function beginClaudeMcpCall(input, env = process.env) {
|
|
36
|
+
const parsed = coordinate.safeParse(input);
|
|
37
|
+
if (!parsed.success)
|
|
38
|
+
throw unavailable();
|
|
39
|
+
return mutate(env, async (root) => {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
for (const name of await fs.readdir(root)) {
|
|
42
|
+
if (!/^[a-f0-9]{64}\.json$/u.test(name))
|
|
43
|
+
continue;
|
|
44
|
+
const file = path.join(root, name);
|
|
45
|
+
const old = await readRecord(file).catch(() => undefined);
|
|
46
|
+
if (old && old.expiresAt <= now)
|
|
47
|
+
await fs.unlink(file);
|
|
48
|
+
}
|
|
49
|
+
const file = callFile(root, parsed.data.toolUseId);
|
|
50
|
+
if (await readRecord(file))
|
|
51
|
+
throw unavailable();
|
|
52
|
+
const token = randomBytes(32).toString('hex');
|
|
53
|
+
const temporary = path.join(root, `${token}.tmp`);
|
|
54
|
+
try {
|
|
55
|
+
await fs.writeFile(temporary, JSON.stringify({ ...parsed.data, token, expiresAt: now + LEASE_MS }), { flag: 'wx', mode: 0o600 });
|
|
56
|
+
await fs.rename(temporary, file);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
await fs.rm(temporary, { force: true });
|
|
60
|
+
}
|
|
61
|
+
return { token };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export async function endClaudeMcpCall(toolUseId, token, env = process.env) {
|
|
65
|
+
if (!text.safeParse(toolUseId).success || !text.safeParse(token).success)
|
|
66
|
+
throw unavailable();
|
|
67
|
+
await mutate(env, async (root) => {
|
|
68
|
+
const file = callFile(root, toolUseId);
|
|
69
|
+
const stored = await readRecord(file);
|
|
70
|
+
if (stored?.toolUseId === toolUseId && stored.token === token)
|
|
71
|
+
await fs.unlink(file);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Expiry gates lookup only; an executing tool keeps its captured coordinate. */
|
|
75
|
+
export async function readClaudeMcpCall(toolUseId, env = process.env) {
|
|
76
|
+
if (!text.safeParse(toolUseId).success)
|
|
77
|
+
throw unavailable();
|
|
78
|
+
const stored = await readRecord(callFile(storeRoot(env), toolUseId));
|
|
79
|
+
if (!stored || stored.toolUseId !== toolUseId || stored.expiresAt <= Date.now())
|
|
80
|
+
throw unavailable();
|
|
81
|
+
return coordinate.parse(stored);
|
|
82
|
+
}
|
package/dist/claude-mod.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Square } from './square-application.js';
|
|
|
7
7
|
import { observeClaudeDelivery } from './claude-delivery.js';
|
|
8
8
|
import { processActNotificationsOnce } from './notifications.js';
|
|
9
9
|
import { sweepPending } from './delivery-operations.js';
|
|
10
|
+
import { beginClaudeMcpCall, endClaudeMcpCall } from './claude-mcp-context.js';
|
|
10
11
|
/** JSON stdin bridge. Identity is explicit per operation; process.env is never rewritten. */
|
|
11
12
|
export async function runClaudeMod(inputText, env = process.env) {
|
|
12
13
|
if (inputText.length > 512_000)
|
|
@@ -14,6 +15,13 @@ export async function runClaudeMod(inputText, env = process.env) {
|
|
|
14
15
|
const input = JSON.parse(inputText);
|
|
15
16
|
if (!input || typeof input.sessionId !== 'string' || !input.sessionId || typeof input.cwd !== 'string')
|
|
16
17
|
throw new Error('Claude bridge coordinate missing.');
|
|
18
|
+
if (input.operation === 'tool-begin') {
|
|
19
|
+
return `${JSON.stringify(await beginClaudeMcpCall({ toolUseId: input.toolUseId, sessionId: input.sessionId, cwd: input.cwd, agentId: input.agentId }, env))}\n`;
|
|
20
|
+
}
|
|
21
|
+
if (input.operation === 'tool-end') {
|
|
22
|
+
await endClaudeMcpCall(input.toolUseId, input.token, env);
|
|
23
|
+
return '{}\n';
|
|
24
|
+
}
|
|
17
25
|
const scoped = operationEnv('claude', input.sessionId, env);
|
|
18
26
|
const ledger = hostLedgerForEnv(env);
|
|
19
27
|
const signal = AbortSignal.timeout(2500);
|
package/dist/cli/context.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSquareApplication } from '../square-application.js';
|
|
2
|
-
import { renderDoctorClean, renderDoctorUnfixable, withPathOutput } from '../presentation.js';
|
|
2
|
+
import { commandPrefix, renderDoctorClean, renderDoctorUnfixable, withPathOutput } from '../presentation.js';
|
|
3
3
|
import { requireSquarePath, usage } from './context.js';
|
|
4
4
|
export const migrateCommand = {
|
|
5
5
|
parse(argv, context) {
|
|
@@ -32,7 +32,7 @@ export const doctorCommand = {
|
|
|
32
32
|
const squarePath = requireSquarePath(context);
|
|
33
33
|
const { data: diagnosis, projection } = await createSquareApplication({ cwd: context.cwd, env: context.env, squarePath }).diagnose();
|
|
34
34
|
if (diagnosis.migrationRequired !== undefined) {
|
|
35
|
-
return { output: withPathOutput(squarePath, `${diagnosis.migrationRequired}\n`), exitCode: 2 };
|
|
35
|
+
return { output: withPathOutput(squarePath, `${diagnosis.migrationRequired}\n${commandPrefix(squarePath)} migrate\n`), exitCode: 2 };
|
|
36
36
|
}
|
|
37
37
|
if (diagnosis.unfixable !== undefined || diagnosis.standingCount === undefined) {
|
|
38
38
|
return {
|
package/dist/cli/program.js
CHANGED
|
@@ -8,11 +8,17 @@ async function handleSquareError(error, context) {
|
|
|
8
8
|
throw error;
|
|
9
9
|
const detail = error.operation;
|
|
10
10
|
const squarePath = context?.squarePath;
|
|
11
|
-
const recovery = context === undefined || squarePath === undefined ? undefined
|
|
11
|
+
const recovery = error.facts?.reason === 'migration_required' || context === undefined || squarePath === undefined ? undefined
|
|
12
12
|
: await applicationFor({ ...context, name: detail?.participant ?? context.name }).recovery().catch(() => undefined);
|
|
13
13
|
const name = detail?.participant ?? recovery?.data.participant ?? context?.name;
|
|
14
14
|
const existing = [refusalText(error)];
|
|
15
|
-
if (
|
|
15
|
+
if (error.facts?.reason === 'migration_required') {
|
|
16
|
+
if (error.facts.ledgerRoot !== undefined)
|
|
17
|
+
existing.push('square ledger migrate', 'square ledger verify');
|
|
18
|
+
else if (squarePath !== undefined)
|
|
19
|
+
existing.push(`${commandPrefix(squarePath)} migrate`);
|
|
20
|
+
}
|
|
21
|
+
else if (squarePath !== undefined && name !== undefined && error.code === 'already_joined') {
|
|
16
22
|
existing.splice(0, 1, `✕ ${participantIdentity(name)} shoos you out of the square`, ` · ${participantIdentity(name)} already stands here — another session holds the name`);
|
|
17
23
|
existing.push(...takeoverRecoveryLines(squarePath, name));
|
|
18
24
|
}
|
|
@@ -5,7 +5,7 @@ import { DatabaseSync } from 'node:sqlite';
|
|
|
5
5
|
import { canonicalPathSync } from './canonical-path.js';
|
|
6
6
|
import { withFileLock } from './file-lock.js';
|
|
7
7
|
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
8
|
-
import { LEDGER_FILENAME, initializeLedger, insertLedgerRecord, ledgerRecordColumns, setLedgerRevisions, upgradeLedger, validateLedger, verifyLedger, inspectLedgerIdentity, } from './host-ledger-sqlite.js';
|
|
8
|
+
import { LEDGER_FILENAME, initializeLedger, insertLedgerRecord, ledgerRecordColumns, setLedgerRevisions, upgradeLedger, validateLedger, verifyLedger, inspectLedgerIdentity, migrationRequired, } from './host-ledger-sqlite.js';
|
|
9
9
|
import { inSqliteTransaction } from './sqlite-transaction.js';
|
|
10
10
|
function rootFor(options) {
|
|
11
11
|
return canonicalPathSync(options.rootPath ?? hostLedgerRoot(options.env));
|
|
@@ -25,10 +25,6 @@ async function exists(file) {
|
|
|
25
25
|
throw error;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
function migrationGuidance(root, file) {
|
|
29
|
-
const name = file ?? path.join(root, LEDGER_FILENAME);
|
|
30
|
-
return `Host ledger at ${root} has a supported older format (${name}).\nStop agent sessions and MCP services using Square with this ledger; keep them stopped until migration and verification succeed; then start them with the updated Square version.\nsquare ledger migrate\nsquare ledger verify`;
|
|
31
|
-
}
|
|
32
28
|
function verificationFailure(root, file, format) {
|
|
33
29
|
const name = file ?? path.join(root, LEDGER_FILENAME);
|
|
34
30
|
const detail = format === 'unsupported'
|
|
@@ -62,7 +58,7 @@ async function verifyDatabase(file, root, signal) {
|
|
|
62
58
|
db = undefined;
|
|
63
59
|
}
|
|
64
60
|
if (format === 'old-sqlite')
|
|
65
|
-
throw
|
|
61
|
+
throw migrationRequired(root);
|
|
66
62
|
throw verificationFailure(root, file, format);
|
|
67
63
|
}
|
|
68
64
|
finally {
|
|
@@ -3,7 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { nameKey } from './model.js';
|
|
6
|
+
import { SquareError, nameKey } from './model.js';
|
|
7
7
|
import { inSqliteTransaction, retrySqliteBusy } from './sqlite-transaction.js';
|
|
8
8
|
export const LEDGER_FILENAME = 'host-ledger.sqlite';
|
|
9
9
|
export const LEDGER_APPLICATION_ID = 0x5351484c;
|
|
@@ -175,7 +175,7 @@ catch (error) {
|
|
|
175
175
|
throw error;
|
|
176
176
|
} }
|
|
177
177
|
export function migrationRequired(root) {
|
|
178
|
-
return new
|
|
178
|
+
return new SquareError('invalid_args', `Host ledger at ${root} needs offline migration.\nStop sessions and MCP services using it, back it up, then migrate and verify from the same environment before restarting.`, { reason: 'migration_required', ledgerRoot: root });
|
|
179
179
|
}
|
|
180
180
|
/** Compact runtime framing. Full schema, projections and integrity belong to explicit verify. */
|
|
181
181
|
function validateLedgerHeader(db, version = LEDGER_VERSION) {
|
package/dist/mcp.js
CHANGED
|
@@ -1,80 +1,51 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
4
|
-
import { SquareError, isSquareError } from './model.js';
|
|
3
|
+
import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolFailure } from './square-tools.js';
|
|
5
4
|
import { operationEnv } from './automatic-session.js';
|
|
5
|
+
import { readClaudeMcpCall } from './claude-mcp-context.js';
|
|
6
6
|
import { SQUARE_IDENTITY } from './identity.js';
|
|
7
7
|
import { createSquareApplication, createSquareResourceOwner } from './square-application.js';
|
|
8
|
-
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
9
|
-
const stringArray = z.array(z.string());
|
|
10
|
-
const participantName = z.string().optional().describe("Participant name, same as --as. Defaults to the server's configured participant.");
|
|
11
|
-
const TOOL_DEFINITIONS = [
|
|
12
|
-
{ name: 'join', description: 'Join the square as a participant.', inputSchema: z.object({ as: participantName, takeover: z.boolean().optional() }).strict() },
|
|
13
|
-
{ name: 'express', description: 'Express a message or embodied action in the square.', inputSchema: z.object({ as: participantName, body: z.string(), mentions: stringArray.optional(), reach: z.enum(['bell']).optional(), reply: activityId.optional(), force: z.boolean().optional(), noWait: z.boolean().optional() }).strict() },
|
|
14
|
-
{ name: 'catch', description: 'Catch and consume activities perceptible to the participant.', inputSchema: z.object({ as: participantName, id: activityId.optional(), idle: z.number().finite().min(0).optional(), from: stringArray.optional(), mention: z.boolean().optional(), limit: z.number().int().min(1).optional() }).strict() },
|
|
15
|
-
{ name: 'history', description: 'Read the activity stream using stable activity-id cursors.', inputSchema: z.object({ limit: z.number().int().min(1).optional(), order: z.enum(['asc', 'desc']).optional(), before: activityId.optional(), after: activityId.optional(), grep: z.string().optional(), fixed: z.string().optional(), from: stringArray.optional(), mention: z.string().optional() }).strict() },
|
|
16
|
-
{ name: 'listen', description: 'Listen for future activity from a participant.', inputSchema: z.object({ as: participantName, target: z.string() }).strict() },
|
|
17
|
-
{ name: 'ignore', description: 'Stop listening for future activity from a participant.', inputSchema: z.object({ as: participantName, target: z.string() }).strict() },
|
|
18
|
-
{ name: 'listening', description: 'List participants this participant is listening to.', inputSchema: z.object({ as: participantName }).strict() },
|
|
19
|
-
{ name: 'hold', description: 'Raise a hand to hold the square.', inputSchema: z.object({ as: participantName, reason: z.string().optional() }).strict() },
|
|
20
|
-
{ name: 'resume', description: 'Release the current hold.', inputSchema: z.object({ as: participantName }).strict() },
|
|
21
|
-
{ name: 'done', description: 'Leave the square without adding a message.', inputSchema: z.object({ as: participantName }).strict() },
|
|
22
|
-
{ name: 'status', description: 'Read the public status of the square.', inputSchema: z.object({}).strict() },
|
|
23
|
-
{ name: 'participants', description: 'List participants standing in or done with the square.', inputSchema: z.object({}).strict() },
|
|
24
|
-
];
|
|
25
|
-
function operation(app, name, args, control) {
|
|
26
|
-
switch (name) {
|
|
27
|
-
case 'join': return app.join({ takeover: args.takeover }, control);
|
|
28
|
-
case 'express': {
|
|
29
|
-
const { body, noWait, ...options } = args;
|
|
30
|
-
return app.express(body, { ...options, noWait }, control);
|
|
31
|
-
}
|
|
32
|
-
case 'catch': return app.catch(args, control);
|
|
33
|
-
case 'history': return app.history(args, control);
|
|
34
|
-
case 'listen': return app.listen(args.target, control);
|
|
35
|
-
case 'ignore': return app.ignore(args.target, control);
|
|
36
|
-
case 'listening': return app.listening(control);
|
|
37
|
-
case 'hold': return app.hold(args.reason, control);
|
|
38
|
-
case 'resume': return app.resume(control);
|
|
39
|
-
case 'done': return app.done(undefined, control);
|
|
40
|
-
case 'status': return app.status(undefined, control);
|
|
41
|
-
case 'participants': return app.participants(control);
|
|
42
|
-
default: throw new SquareError('invalid_args', `Unknown tool: ${name}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
8
|
export function createSquareMcpServer(options) {
|
|
46
9
|
const baseEnv = { ...options.env };
|
|
47
10
|
const resources = createSquareResourceOwner();
|
|
48
|
-
//
|
|
11
|
+
// Calls to the configured square borrow one server-owned connection.
|
|
49
12
|
const inFlight = new Set();
|
|
50
13
|
const server = new McpServer({ name: 'square', version: SQUARE_IDENTITY.packageVersion }, {
|
|
51
|
-
instructions:
|
|
14
|
+
instructions: SQUARE_TOOL_INSTRUCTIONS,
|
|
52
15
|
});
|
|
53
|
-
for (const tool of
|
|
16
|
+
for (const tool of SQUARE_TOOLS) {
|
|
54
17
|
server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, (args, extra) => {
|
|
55
18
|
const call = (async () => {
|
|
56
19
|
try {
|
|
57
|
-
const { as: participant,
|
|
20
|
+
const { as: participant, location } = args;
|
|
58
21
|
const threadId = extra._meta?.threadId;
|
|
59
22
|
const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
23
|
+
// Claude's plugin endpoint requires the mod's call-local coordinate, never startup identity.
|
|
24
|
+
const claude = baseEnv.SQUARE_CLAUDE_MCP_CONTEXT === '1'
|
|
25
|
+
? await readClaudeMcpCall(extra._meta?.['claudecode/toolUseId'], baseEnv)
|
|
26
|
+
: undefined;
|
|
27
|
+
const env = claude !== undefined
|
|
28
|
+
? { ...operationEnv('claude', claude.sessionId, baseEnv),
|
|
29
|
+
// Preserve Claude's session + child flag semantics; an agent loop ID is not a session ID.
|
|
30
|
+
CLAUDE_CODE_CHILD_SESSION: claude.agentId === undefined ? '' : '1' }
|
|
31
|
+
: typeof threadId === 'string' && threadId.trim()
|
|
32
|
+
? operationEnv('codex', threadId, baseEnv)
|
|
33
|
+
: typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
|
|
34
|
+
? operationEnv('opencode', opencodeSessionId, baseEnv)
|
|
35
|
+
: baseEnv;
|
|
36
|
+
const app = createSquareApplication({
|
|
37
|
+
...options, env, cwd: claude?.cwd ?? options.cwd,
|
|
38
|
+
squarePath: location ?? options.squarePath,
|
|
39
|
+
participant: participant ?? options.participant,
|
|
40
|
+
// The server owner is bound to its configured square; overrides close after each call.
|
|
41
|
+
resources: location === undefined && claude === undefined ? resources : undefined,
|
|
42
|
+
}, { presentation: false });
|
|
43
|
+
const outcome = await tool.run(app, args, { signal: extra.signal });
|
|
67
44
|
const structuredContent = outcome.data;
|
|
68
45
|
return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent };
|
|
69
46
|
}
|
|
70
47
|
catch (error) {
|
|
71
|
-
const
|
|
72
|
-
? { code: 'cancelled', message: 'The MCP request was cancelled' }
|
|
73
|
-
: isSquareError(error)
|
|
74
|
-
? { code: error.code, message: error.message, ...(error.facts === undefined ? {} : { facts: error.facts }) }
|
|
75
|
-
: { code: 'internal', message: error instanceof Error ? error.message : String(error) };
|
|
76
|
-
const operation = error instanceof Error ? error.operation : undefined;
|
|
77
|
-
const failure = operation === undefined ? normalized : { ...normalized, operation };
|
|
48
|
+
const failure = squareToolFailure(error, extra.signal);
|
|
78
49
|
return { isError: true, content: [{ type: 'text', text: JSON.stringify(failure) }], structuredContent: { error: failure } };
|
|
79
50
|
}
|
|
80
51
|
})();
|
package/dist/model.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface SquareErrorFacts {
|
|
|
24
24
|
reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'not_addressed' | 'migration_required' | 'explicit_reach_required' | 'invalid_reply' | 'invalid_done_body';
|
|
25
25
|
/** The activity id a `catch --id` refusal named. */
|
|
26
26
|
activityId?: string;
|
|
27
|
+
/** The host ledger requiring offline migration, rather than the selected artifact. */
|
|
28
|
+
ledgerRoot?: string;
|
|
27
29
|
/** Requested recipient spellings, without parsing presentation copy. */
|
|
28
30
|
targets?: readonly string[];
|
|
29
31
|
/** Whether the artifact holds that activity at all; an unknown id needs an ordinary page. */
|
package/dist/square-sqlite.d.ts
CHANGED
|
@@ -25,17 +25,11 @@ export interface LegacyFraming {
|
|
|
25
25
|
readonly userVersion: number;
|
|
26
26
|
readonly journalMode: string;
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
* The one recovery command for an owned legacy artifact, with a shell-quoted
|
|
30
|
-
* path. Kept beside framing recognition so the refusal and migration
|
|
31
|
-
* instruction cannot drift apart. This codec stays free of the renderer.
|
|
32
|
-
*/
|
|
33
|
-
export declare function migrationRecoveryCommand(location: string): string;
|
|
34
28
|
/**
|
|
35
29
|
* Read-only legacy framing: application id, format version, journal mode and the
|
|
36
30
|
* exact owned snapshot schema. It decodes no snapshot payload, writes no pragma
|
|
37
|
-
* and runs no DDL, so ordinary open can establish ownership before
|
|
38
|
-
*
|
|
31
|
+
* and runs no DDL, so ordinary open can establish ownership before requiring
|
|
32
|
+
* migration. Anything else is unrelated or unsupported, not repaired.
|
|
39
33
|
*/
|
|
40
34
|
export declare function validateLegacyFraming(database: DatabaseSync, applicationId: number, location?: string): LegacyFraming;
|
|
41
35
|
export declare const SCHEMA_SQL = "CREATE TABLE square_meta (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n revision INTEGER NOT NULL CHECK (revision BETWEEN 0 AND 9007199254740991),\n next_act_index INTEGER NOT NULL CHECK (next_act_index BETWEEN 0 AND 9007199254740991),\n hard_cap INTEGER CHECK (hard_cap BETWEEN 1 AND 9007199254740991),\n throttle_per_minute INTEGER CHECK (throttle_per_minute BETWEEN 1 AND 9007199254740991),\n preamble_json TEXT NOT NULL CHECK (json_valid(preamble_json)),\n warmup_json TEXT NOT NULL CHECK (json_valid(warmup_json))\n);\nCREATE TABLE activities (\n act_index INTEGER PRIMARY KEY CHECK (act_index BETWEEN 0 AND 9007199254740991),\n at REAL NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('join','done','say','hold','resume','read','listen','ignore')),\n actor_json TEXT COLLATE BINARY,\n actor_key_json TEXT COLLATE BINARY,\n reach TEXT CHECK (reach = 'bell'),\n reply_index INTEGER CHECK (reply_index >= 0 AND reply_index < act_index),\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json)),\n CHECK ((actor_json IS NULL) = (actor_key_json IS NULL)),\n CHECK (kind IN ('hold','resume') OR actor_json IS NOT NULL),\n CHECK (kind = 'say' OR (reach IS NULL AND reply_index IS NULL))\n);\nCREATE INDEX activity_kind_index ON activities(kind, act_index);\nCREATE INDEX activity_actor_index ON activities(actor_key_json, act_index);\nCREATE INDEX activity_say_actor_index ON activities(actor_key_json, act_index) WHERE kind='say';\nCREATE INDEX activity_say_time ON activities(at, act_index) WHERE kind='say';\nCREATE INDEX activity_bell_actor_time ON activities(actor_key_json, at, act_index) WHERE kind='say' AND reach='bell';\n\nCREATE TABLE receipts (\n owner_json TEXT COLLATE BINARY NOT NULL,\n act_index INTEGER NOT NULL CHECK (act_index BETWEEN 0 AND 9007199254740991),\n at REAL NOT NULL,\n PRIMARY KEY (owner_json, act_index)\n) WITHOUT ROWID;\nCREATE INDEX receipt_owner_time ON receipts(owner_json, at);\nCREATE TABLE catch_leases (\n owner_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,\n lease_id_json TEXT NOT NULL,\n heartbeat_at REAL NOT NULL,\n expires_at REAL NOT NULL CHECK (expires_at >= heartbeat_at),\n filter_json TEXT CHECK (filter_json IS NULL OR json_valid(filter_json))\n) WITHOUT ROWID;\n\nCREATE TABLE routes (\n position INTEGER PRIMARY KEY CHECK (position >= 0),\n participant_key_json TEXT COLLATE BINARY NOT NULL,\n session_json TEXT COLLATE BINARY NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('opencode-server','codex-queue','claude-native','paseo')),\n updated_at REAL NOT NULL,\n payload_json TEXT NOT NULL CHECK (json_valid(payload_json))\n);\nCREATE INDEX route_participant ON routes(participant_key_json, position);\nCREATE INDEX route_session ON routes(session_json, position);\n\nCREATE TABLE participants (\n name_key_json TEXT COLLATE BINARY PRIMARY KEY NOT NULL,\n name_json TEXT NOT NULL,\n first_index INTEGER NOT NULL UNIQUE,\n joined INTEGER NOT NULL CHECK (joined IN (0,1)),\n done INTEGER NOT NULL CHECK (done IN (0,1)),\n say_count INTEGER NOT NULL CHECK (say_count >= 0),\n last_active_at REAL,\n last_join_index INTEGER,\n CHECK (NOT (joined=1 AND done=1))\n) WITHOUT ROWID;\nCREATE TABLE attention (\n listener_key_json TEXT COLLATE BINARY NOT NULL,\n target_key_json TEXT COLLATE BINARY NOT NULL,\n target_json TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('listen','ignore')),\n since_index INTEGER NOT NULL,\n PRIMARY KEY (listener_key_json, target_key_json)\n) WITHOUT ROWID;\nCREATE INDEX attention_target ON attention(target_key_json, kind, listener_key_json);\nCREATE TABLE landed_audience (\n act_index INTEGER NOT NULL,\n recipient_key_json TEXT COLLATE BINARY NOT NULL,\n recipient_order INTEGER NOT NULL CHECK (recipient_order >= 0),\n route TEXT NOT NULL CHECK (route IN ('mention','reply','attention','bell')),\n PRIMARY KEY (act_index, recipient_key_json),\n UNIQUE (act_index, recipient_order)\n) WITHOUT ROWID;\nCREATE INDEX audience_recipient ON landed_audience(recipient_key_json, act_index);";
|
package/dist/square-sqlite.js
CHANGED
|
@@ -13,19 +13,11 @@ const V1_SNAPSHOT_COLUMNS = [
|
|
|
13
13
|
{ cid: 1, name: 'revision', type: 'INTEGER', notnull: 1, dflt_value: null, pk: 0, hidden: 0 },
|
|
14
14
|
{ cid: 2, name: 'state', type: 'TEXT', notnull: 1, dflt_value: null, pk: 0, hidden: 0 },
|
|
15
15
|
];
|
|
16
|
-
/**
|
|
17
|
-
* The one recovery command for an owned legacy artifact, with a shell-quoted
|
|
18
|
-
* path. Kept beside framing recognition so the refusal and migration
|
|
19
|
-
* instruction cannot drift apart. This codec stays free of the renderer.
|
|
20
|
-
*/
|
|
21
|
-
export function migrationRecoveryCommand(location) {
|
|
22
|
-
return `square --location '${location.replace(/'/g, `'\\''`)}' migrate`;
|
|
23
|
-
}
|
|
24
16
|
/**
|
|
25
17
|
* Read-only legacy framing: application id, format version, journal mode and the
|
|
26
18
|
* exact owned snapshot schema. It decodes no snapshot payload, writes no pragma
|
|
27
|
-
* and runs no DDL, so ordinary open can establish ownership before
|
|
28
|
-
*
|
|
19
|
+
* and runs no DDL, so ordinary open can establish ownership before requiring
|
|
20
|
+
* migration. Anything else is unrelated or unsupported, not repaired.
|
|
29
21
|
*/
|
|
30
22
|
export function validateLegacyFraming(database, applicationId, location = '') {
|
|
31
23
|
const identity = database.prepare('SELECT application_id, user_version FROM pragma_application_id, pragma_user_version').get();
|
|
@@ -328,7 +320,7 @@ export function validateSqliteHeader(db, location = '') {
|
|
|
328
320
|
requireFact(identity.application_id === APPLICATION_ID, 'unrelated SQLite database.');
|
|
329
321
|
if (identity.user_version === 1) {
|
|
330
322
|
validateLegacyFraming(db, APPLICATION_ID, location);
|
|
331
|
-
throw new SquareError('invalid_args', `Square artifact
|
|
323
|
+
throw new SquareError('invalid_args', `Square artifact at ${location} needs migration.\nStop sessions using it, back it up, then migrate and verify before restarting.`, { reason: 'migration_required' });
|
|
332
324
|
}
|
|
333
325
|
requireFact(identity.user_version === 2, 'unsupported SQLite format version.');
|
|
334
326
|
const actual = db.prepare("SELECT sql FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*' AND sql IS NOT NULL").all().map(r => normalizedSql(string(r.sql))).sort();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import type { ApplicationOutcome, OperationControl, SquareApplication } from './square-facade.js';
|
|
3
|
+
type JsonObject = Record<string, unknown>;
|
|
4
|
+
type Outcome = Promise<ApplicationOutcome<object, unknown, false>>;
|
|
5
|
+
interface ToolDefinition {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: z.ZodObject<z.ZodRawShape>;
|
|
9
|
+
run(app: SquareApplication<false>, args: JsonObject, control: OperationControl): Outcome;
|
|
10
|
+
}
|
|
11
|
+
export declare const SQUARE_TOOL_INSTRUCTIONS = "The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.";
|
|
12
|
+
export declare const SQUARE_TOOLS: readonly ToolDefinition[];
|
|
13
|
+
export declare function squareToolFailure(error: unknown, signal?: AbortSignal): JsonObject;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { isSquareError } from './model.js';
|
|
3
|
+
const location = z.string().optional().describe('Path to the .square artifact, relative to the current working directory. Defaults to the configured square.');
|
|
4
|
+
const participant = z.string().optional().describe('Participant name. Defaults to the configured participant or the current session’s unambiguous participant.');
|
|
5
|
+
const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
|
|
6
|
+
const stringArray = z.array(z.string());
|
|
7
|
+
function tool(name, description, shape, run) {
|
|
8
|
+
const inputSchema = z.object({ ...shape, location }).strict();
|
|
9
|
+
return { name, description, inputSchema, run: (app, args, control) => {
|
|
10
|
+
const { location: _location, ...operationArgs } = inputSchema.parse(args);
|
|
11
|
+
return run(app, operationArgs, control);
|
|
12
|
+
} };
|
|
13
|
+
}
|
|
14
|
+
export const SQUARE_TOOL_INSTRUCTIONS = 'The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.';
|
|
15
|
+
export const SQUARE_TOOLS = [
|
|
16
|
+
tool('join', 'Join the square as a participant.', { as: participant, takeover: z.boolean().optional().describe('Take the name from its current session owner.') }, (app, { takeover }, control) => app.join({ takeover }, control)),
|
|
17
|
+
tool('express', 'Express a message or embodied action in the square. Mentions and replies direct attention; bell reaches everyone. Bare activity is valid even with no listeners.', {
|
|
18
|
+
as: participant, body: z.string(), mentions: stringArray.optional(), reach: z.enum(['bell']).optional(),
|
|
19
|
+
reply: activityId.optional(), noMention: z.boolean().optional().describe('Disable body @name scanning; explicit mentions and reply still apply.'),
|
|
20
|
+
force: z.boolean().optional().describe('Speak without first catching unread activity or satisfying attention etiquette.'),
|
|
21
|
+
noWait: z.boolean().optional().describe('Return an error rather than waiting when held or throttled.'),
|
|
22
|
+
}, (app, { body, as: _participant, ...options }, control) => app.express(body, options, control)),
|
|
23
|
+
tool('catch', 'Catch and consume activities perceptible to the participant. idle waits for relevant activity or this many milliseconds of quiet; omitted means immediate. id catches one activity and cannot be combined with filters, idle or limit.', {
|
|
24
|
+
as: participant, id: activityId.optional(), idle: z.number().finite().min(0).optional(), from: stringArray.optional(),
|
|
25
|
+
mention: z.boolean().optional(), limit: z.number().int().min(1).optional(),
|
|
26
|
+
}, (app, { as: _participant, ...options }, control) => app.catch(options, control)),
|
|
27
|
+
tool('history', 'Read the activity stream without consuming it, using stable activity-id cursors. Default page size is 10, maximum 100. grep is a regex; fixed is literal text; use only one.', {
|
|
28
|
+
limit: z.number().int().min(1).optional(), order: z.enum(['asc', 'desc']).optional(), before: activityId.optional(), after: activityId.optional(),
|
|
29
|
+
grep: z.string().optional(), fixed: z.string().optional(), from: stringArray.optional(), mention: z.string().optional(),
|
|
30
|
+
}, (app, args, control) => app.history(args, control)),
|
|
31
|
+
tool('listen', 'Listen for future bare activity from a participant, clearing any ignore of them.', { as: participant, target: z.string() }, (app, { target }, control) => app.listen(target, control)),
|
|
32
|
+
tool('ignore', 'Block a participant’s future mentions and bare activity. Listening again clears the ignore; already landed activity is unchanged.', { as: participant, target: z.string() }, (app, { target }, control) => app.ignore(target, control)),
|
|
33
|
+
tool('listening', 'List participants this participant is listening to.', { as: participant }, (app, _args, control) => app.listening(control)),
|
|
34
|
+
tool('hold', 'Raise a hand to hold the square.', { as: participant, reason: z.string().optional() }, (app, { reason }, control) => app.hold(reason, control)),
|
|
35
|
+
tool('resume', 'Release the current hold.', { as: participant }, (app, _args, control) => app.resume(control)),
|
|
36
|
+
tool('done', 'Leave the square without adding a message.', { as: participant }, (app, _args, control) => app.done(undefined, control)),
|
|
37
|
+
tool('status', 'Read the public status of the square.', {}, (app, _args, control) => app.status(undefined, control)),
|
|
38
|
+
tool('participants', 'List participants standing in or done with the square.', {}, (app, _args, control) => app.participants(control)),
|
|
39
|
+
];
|
|
40
|
+
export function squareToolFailure(error, signal) {
|
|
41
|
+
const normalized = signal?.aborted
|
|
42
|
+
? { code: 'cancelled', message: 'The Square operation was cancelled' }
|
|
43
|
+
: isSquareError(error)
|
|
44
|
+
? { code: error.code, message: error.message, ...(error.facts === undefined ? {} : { facts: error.facts }) }
|
|
45
|
+
: error instanceof z.ZodError
|
|
46
|
+
? { code: 'invalid_args', message: error.message }
|
|
47
|
+
: { code: 'internal', message: error instanceof Error ? error.message : String(error) };
|
|
48
|
+
const operation = error instanceof Error ? error.operation : undefined;
|
|
49
|
+
return operation === undefined ? normalized : { ...normalized, operation };
|
|
50
|
+
}
|
|
@@ -81,7 +81,10 @@ Array-bearing results use object envelopes (`{activities}`, `{participants}`, `{
|
|
|
81
81
|
- **Adapter boundary.** CLI/MCP reach storage, actions, decisions, views, wakes, list, stream, diagnosis/migration, inbox and notification execution only through `square-application.ts`. Private concern helpers retain their existing typed ports and internal dependencies; they are not callable from adapter execution paths.
|
|
82
82
|
- **Facade.** `square-facade.ts` defines contracts; it must not derive contracts from `ReturnType`/`Awaited<ReturnType>` of private functions, and public activity IDs are formatted/parsed only by the existing core implementation.
|
|
83
83
|
- **CLI (`cli/**`).** Owns argv/stdin, scoped help, shell quoting, sensory rendering/recovery, drafts, stdout/stderr, exit codes, SIGINT -> `AbortSignal` and stream backpressure. It maps returned classifications to copy/exit status, and preserves query argv in continuation commands; it cannot derive admission, commit certainty, terminal states or pagination eligibility from display text. File path display and draft-file I/O are allowed; opening an artifact or resolving ownership is not path formatting.
|
|
84
|
-
- **
|
|
84
|
+
- **Tools (`square-tools.ts`).** Owns shared Zod input schemas, descriptions and typed argument-to-facade calls for MCP and Pi. No session, environment, connection lifetime or business policy lives here.
|
|
85
|
+
- **MCP (`mcp.ts`).** Owns registration, request-local `location`/`as`/metadata session mapping, signal forwarding, JSON-RPC envelopes and stdio lifecycle. Request environment derivation stays immutable; no shared `process.env` writes and no invented session identity.
|
|
86
|
+
- **Claude mod (`plugins/claude/hooks/register.js`).** Owns the pending MCP call's identity association, publishing before `next(e)` and removing its exact token in `finally` through the existing native helper. `claude-mcp-context.ts` stores only transient transport coordinates, outside Square domain/artifact state. The bundled MCP adapter reads them using Claude's tool-use metadata; model-visible schemas contain no identity fields.
|
|
87
|
+
- **Pi (`plugins/pi/square-pi.js`).** Registers the same operations as native codemode tools. Each call derives its identity and cwd from the live Pi context and uses the facade's per-operation connection lifetime. JSON schemas come from the shared Zod definitions; Pi has no separate parameter contract.
|
|
85
88
|
- **Closed exceptions.** Harness install/uninstall/doctor, scoped help/version, meta commands and native hook input/output keep their existing protocol owners; that exception does not cover join, expression, catch, history, status, participants, build, stream, discovery, inbox, artifact doctor or migrate. Native changes stay limited to the required owner import migration.
|
|
86
89
|
- **Storage/native scope.** SQLite schema/framing, artifact version, migration protocol, receipt model, ownership CAS, notification cancellation suppression and delivery eligibility rules are unchanged. Transactions stay synchronous and typed. The journaled amendment permits only build/create signal propagation through the existing adapter/storage/artifact retries and transactions, and captured-env propagation through default Codex wake construction/executable selection. A committed build is returned despite later cancellation; temporary-file cleanup does not reuse the aborted signal.
|
|
87
90
|
|
package/guides/claude-native.md
CHANGED
|
@@ -19,6 +19,35 @@ peer/plugin authority. Native OS-user socket permissions and Claude policy apply
|
|
|
19
19
|
`square harness doctor delivery` derives current routes/evidence without sending.
|
|
20
20
|
An absent/broken endpoint is unavailable; it does not consume Square activity.
|
|
21
21
|
|
|
22
|
+
## MCP tools and call identity
|
|
23
|
+
|
|
24
|
+
The plugin's `.mcp.json` starts the existing Square MCP server. The mod no longer
|
|
25
|
+
registers an argv wrapper or executes participant CLI commands. MCP and Pi native
|
|
26
|
+
tools share their operation definitions and facade calls in `square-tools.ts`.
|
|
27
|
+
|
|
28
|
+
Before a bundled MCP call, the mod uses the existing `square claude-mod` helper
|
|
29
|
+
to publish `{toolUseId, sessionId, cwd, agentId?}`. It then calls `next(e)` unchanged.
|
|
30
|
+
The server correlates `_meta["claudecode/toolUseId"]` with that association: no
|
|
31
|
+
session, agent, token or other transport field is added to the model's schema or
|
|
32
|
+
arguments. Claude's native **session ID plus child flag** semantics are preserved;
|
|
33
|
+
the child loop's `agentId` is retained for attribution, not substituted as a session ID.
|
|
34
|
+
Calls use the live cwd and session even if the MCP process started earlier.
|
|
35
|
+
|
|
36
|
+
`SQUARE_CLAUDE_MCP_CONTEXT=1` is set only by the bundled MCP configuration. That
|
|
37
|
+
endpoint fails closed if an association is missing or expired; it never silently
|
|
38
|
+
uses the startup parent identity. Generic MCP configurations retain their existing
|
|
39
|
+
identity handling. The hook targets `mcp__plugin_square_square__*`, not unrelated
|
|
40
|
+
servers with similar operation names.
|
|
41
|
+
|
|
42
|
+
The mod owns publication and token-matched removal in `finally`; the server only
|
|
43
|
+
reads. Associations are private, transient transport files under
|
|
44
|
+
`<host-ledger-root>/claude-mcp-calls`, not Square domain state or another artifact.
|
|
45
|
+
Normal completion/errors remove them. If abrupt cancellation, unload or a crash
|
|
46
|
+
prevents cleanup, they expire after ten minutes and the next publication prunes
|
|
47
|
+
them. Expiry gates lookup, not an operation that already captured its context.
|
|
48
|
+
MCP owns operation cancellation and connection shutdown. No second execution
|
|
49
|
+
protocol, resident service or ledger schema migration is introduced.
|
|
50
|
+
|
|
22
51
|
## Membership and lifecycle
|
|
23
52
|
|
|
24
53
|
If `.square/PUBLIC.square` already exists, the mod joins the automatic participant.
|
|
@@ -46,7 +75,9 @@ old ends cannot retire that replacement. Unchanged reload preserves epochs.
|
|
|
46
75
|
## Body, admission, and presentation
|
|
47
76
|
|
|
48
77
|
The coordinator uses Square's canonical audience projection and bounded body
|
|
49
|
-
preview.
|
|
78
|
+
preview. Each notification identifies the square, receiving participant and
|
|
79
|
+
activity before its event and body. A clipped preview describes catching that
|
|
80
|
+
activity to read it in full; it never embeds shell commands or tool-call syntax.
|
|
50
81
|
Opaque text correlation resolves through bound memberships and the existing
|
|
51
82
|
attempt ledger, never a path or asserted source from incoming text.
|
|
52
83
|
|