@astrosheep/square 0.3.85 → 0.3.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact.d.ts +6 -1
- package/dist/artifact.js +79 -10
- package/dist/boundary-presentation.d.ts +5 -4
- package/dist/boundary-presentation.js +21 -25
- package/dist/catch-decisions.js +2 -1
- package/dist/claude-delivery-status.d.ts +8 -2
- package/dist/claude-delivery-status.js +26 -10
- package/dist/claude-delivery.d.ts +8 -4
- package/dist/claude-delivery.js +114 -104
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +119 -105
- package/dist/cli/context.d.ts +3 -1
- package/dist/cli/context.js +25 -1
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +15 -12
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +2 -1
- package/dist/cli/registry.js +0 -2
- package/dist/cli/square-commands.d.ts +4 -2
- package/dist/cli/square-commands.js +21 -6
- package/dist/codex-hook.d.ts +3 -3
- package/dist/codex-hook.js +4 -4
- package/dist/codex-queue.d.ts +5 -6
- package/dist/codex-queue.js +9 -5
- package/dist/decisions.d.ts +3 -2
- package/dist/decisions.js +18 -6
- package/dist/delivery-health.d.ts +32 -2
- package/dist/delivery-health.js +108 -45
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +155 -196
- package/dist/delivery.d.ts +14 -23
- package/dist/delivery.js +48 -18
- package/dist/file-lock.d.ts +8 -1
- package/dist/file-lock.js +2 -0
- package/dist/help.js +12 -14
- package/dist/hints.js +2 -3
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -3
- package/dist/list.js +9 -4
- package/dist/mcp.js +12 -1
- package/dist/model.d.ts +39 -5
- package/dist/model.js +84 -0
- package/dist/notifications.d.ts +8 -6
- package/dist/notifications.js +56 -143
- package/dist/operation-presentation.js +12 -14
- package/dist/participant-identity.d.ts +0 -3
- package/dist/participant-identity.js +0 -31
- package/dist/paseo-delivery.d.ts +5 -6
- package/dist/paseo-delivery.js +13 -8
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/delivery.d.ts +8 -3
- package/dist/plugins/opencode/delivery.js +187 -132
- package/dist/plugins/opencode/index.js +12 -38
- package/dist/ports.d.ts +112 -40
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +51 -35
- package/dist/presentation.d.ts +0 -1
- package/dist/presentation.js +18 -13
- package/dist/seats.d.ts +7 -0
- package/dist/seats.js +27 -0
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +10 -12
- package/dist/sqlite-square.d.ts +53 -0
- package/dist/sqlite-square.js +602 -0
- package/dist/square-api.d.ts +12 -41
- package/dist/square-api.js +26 -474
- package/dist/square-backend.d.ts +13 -0
- package/dist/square-core.d.ts +3 -2
- package/dist/square-domain.d.ts +12 -5
- package/dist/square-domain.js +163 -7
- package/dist/square-facade.d.ts +16 -10
- package/dist/square-index.d.ts +24 -0
- package/dist/square-index.js +131 -0
- package/dist/square-legacy-framing.d.ts +9 -0
- package/dist/square-legacy-framing.js +56 -0
- package/dist/square-location.d.ts +15 -0
- package/dist/square-location.js +76 -0
- package/dist/square-migration-host-ledger.d.ts +26 -0
- package/dist/square-migration-host-ledger.js +168 -0
- package/dist/square-migration.d.ts +5 -5
- package/dist/square-migration.js +373 -71
- package/dist/square-sqlite.d.ts +9 -9
- package/dist/square-sqlite.js +329 -79
- package/dist/square-stdio.d.ts +22 -0
- package/dist/square-stdio.js +222 -0
- package/dist/square-tools.js +30 -3
- package/dist/stdio-server.d.ts +3 -0
- package/dist/stdio-server.js +192 -0
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +50 -70
- package/dist/wakes.d.ts +6 -5
- package/dist/wakes.js +20 -17
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/backends.md +180 -0
- package/guides/claude-native.md +9 -9
- package/guides/mcp.md +1 -1
- package/guides/migration.md +107 -67
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +69 -30
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/register.js +1 -1
- package/plugins/claude/skills/square/SKILL.md +7 -7
- package/plugins/claude/types.d.ts +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +170 -118
- package/plugins/opencode/index.ts +12 -30
- package/plugins/pi/square-pi.js +58 -20
- package/skills/square/SKILL.md +7 -7
- package/dist/cli/ledger-command.d.ts +0 -12
- package/dist/cli/ledger-command.js +0 -72
- package/dist/host-ledger-file-adapter.d.ts +0 -47
- package/dist/host-ledger-file-adapter.js +0 -384
- package/dist/host-ledger-migration.d.ts +0 -27
- package/dist/host-ledger-migration.js +0 -264
- package/dist/host-ledger-root.d.ts +0 -2
- package/dist/host-ledger-root.js +0 -9
- package/dist/host-ledger-sqlite.d.ts +0 -65
- package/dist/host-ledger-sqlite.js +0 -368
- package/dist/host-ledger.d.ts +0 -195
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -36
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -22
- package/dist/participant-host.js +0 -92
- package/dist/registry.d.ts +0 -55
- package/dist/registry.js +0 -123
- package/dist/routes.d.ts +0 -39
- package/dist/routes.js +0 -99
- package/dist/square-actions.d.ts +0 -56
- package/dist/square-actions.js +0 -353
- package/dist/square-projections.d.ts +0 -31
- package/dist/square-projections.js +0 -64
- package/dist/wake-attempts.d.ts +0 -10
- package/dist/wake-attempts.js +0 -39
- package/dist/wake-eligibility.d.ts +0 -53
- package/dist/wake-eligibility.js +0 -123
- package/dist/wake-evidence.d.ts +0 -33
- package/dist/wake-evidence.js +0 -32
- package/guides/host-ledger.md +0 -58
- package/guides/indexed-host-ledger-performance.md +0 -39
- /package/dist/{host-ledger.js → square-backend.js} +0 -0
package/dist/claude-delivery.js
CHANGED
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import {
|
|
4
|
-
import { closeOpenSquare } from './open-square.js';
|
|
5
|
-
import { hostLedgerForEnv } from './registry.js';
|
|
6
|
-
import { projectPresentationEvidence, presentationSuppressesWake } from './square-projections.js';
|
|
7
|
-
import { nativePendingPreview } from './inbox.js';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { createArtifactPort } from './artifact.js';
|
|
8
4
|
import { presentPending } from './presentation-operations.js';
|
|
9
|
-
import {
|
|
5
|
+
import { projectSessionCandidates, nativePendingPreview } from './inbox.js';
|
|
6
|
+
import { sameBody } from './model.js';
|
|
10
7
|
import { parseActivityId } from './square-core.js';
|
|
8
|
+
import { asSeatRef } from './seats.js';
|
|
11
9
|
import { writeClaudeNative } from './packages/agent-delivery/src/claude-native.js';
|
|
12
10
|
const marker = /(?:^|\n)\[square-inbox:([a-zA-Z0-9-]+)\](?:\n|$)/g;
|
|
13
11
|
export function nativeTokens(text) { return [...text.matchAll(marker)].map((match) => match[1]); }
|
|
14
12
|
export function nativePayload(token, preview) { return `[square-inbox:${token}]\n${preview}\n[/square-inbox:${token}]`; }
|
|
15
13
|
function nativeSupported(route) { return route.address.loaded === 'true' && !!route.address.endpoint; }
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
function sameAddress(left, right) {
|
|
15
|
+
if (left === undefined)
|
|
16
|
+
return false;
|
|
17
|
+
const keys = Object.keys(left);
|
|
18
|
+
return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]);
|
|
19
|
+
}
|
|
20
|
+
function hashPayload(payload) { return createHash('sha256').update(payload).digest('hex'); }
|
|
21
|
+
function previewFacts(facts, request) {
|
|
22
|
+
const index = parseActivityId(request.activity);
|
|
23
|
+
if (index === undefined || request.route.kind !== 'claude-native' || request.route.address.endpoint === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
return nativePendingPreview(facts, { location: request.location, routeKind: 'claude-native', sessionId: request.route.sessionId,
|
|
26
|
+
participant: request.participant, actIndex: index, address: { endpoint: request.route.address.endpoint }, now: Date.now() });
|
|
27
|
+
}
|
|
28
|
+
async function preview(artifact, request) {
|
|
29
|
+
return (await artifact.read((facts) => previewFacts(facts, request))).value;
|
|
30
|
+
}
|
|
21
31
|
async function writeClaudeWake(sessionId, endpoint, messageId, payload, timeoutMs, signal) {
|
|
22
32
|
const result = await writeClaudeNative({ sessionId, endpoint, messageId }, payload, { deadline: Date.now() + timeoutMs, signal });
|
|
23
33
|
if (result.outcome === 'unavailable')
|
|
24
34
|
return { outcome: 'failed', unavailable: true, retainRoute: true, signature: 'claude_endpoint_unavailable', message: result.message };
|
|
25
35
|
return { outcome: 'unknown', signature: 'native_admission_unconfirmed', message: result.outcome === 'written' ? 'Claude accepted the local write; native admission is unconfirmed.' : result.message };
|
|
26
36
|
}
|
|
27
|
-
export function createClaudeWakeTransport(
|
|
37
|
+
export function createClaudeWakeTransport(env = process.env) {
|
|
28
38
|
return {
|
|
29
39
|
async probe(route) {
|
|
30
40
|
if (!nativeSupported(route))
|
|
@@ -36,108 +46,108 @@ export function createClaudeWakeTransport(hostLedger, env = process.env) {
|
|
|
36
46
|
return { outcome: 'not-capable', diagnostic: 'Claude native inbox endpoint unavailable.' };
|
|
37
47
|
}
|
|
38
48
|
},
|
|
39
|
-
attempt: (request, timeoutMs, beforeSend) => dispatchClaude(request,
|
|
49
|
+
attempt: (request, timeoutMs, beforeSend) => dispatchClaude(request, timeoutMs, beforeSend, env),
|
|
40
50
|
};
|
|
41
51
|
}
|
|
42
|
-
async function
|
|
43
|
-
|
|
52
|
+
async function dispatchClaude(request, timeoutMs, beforeSend, env = process.env) {
|
|
53
|
+
void env;
|
|
54
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
55
|
+
if (!nativeSupported(request.route) || request.artifact === undefined || request.claimToken === undefined)
|
|
56
|
+
return { outcome: 'not-capable', diagnostic: 'Claude native delivery needs a current artifact claim and loaded inbox.' };
|
|
57
|
+
if (signal.aborted || !await beforeSend())
|
|
58
|
+
return { outcome: 'unknown', signature: 'native_send_suppressed' };
|
|
59
|
+
const prepared = await preview(request.artifact, request);
|
|
60
|
+
if (prepared === undefined)
|
|
61
|
+
return { outcome: 'not-capable', diagnostic: 'Claude activity is no longer current.' };
|
|
62
|
+
const messageId = randomUUID();
|
|
63
|
+
const payload = nativePayload(request.claimToken, prepared.payload);
|
|
64
|
+
const correlation = { harness: 'claude', token: request.claimToken, messageId, payloadHash: hashPayload(payload) };
|
|
65
|
+
if (!await request.artifact.prepareDelivery({ seat: request.seat, claimToken: request.claimToken, correlation }))
|
|
66
|
+
return { outcome: 'not-capable', diagnostic: 'Claude delivery claim is no longer current.' };
|
|
67
|
+
const fresh = await preview(request.artifact, request);
|
|
68
|
+
if (fresh === undefined || nativePayload(request.claimToken, fresh.payload) !== payload)
|
|
69
|
+
return { outcome: 'not-capable', diagnostic: 'Claude activity changed before native write.' };
|
|
70
|
+
if (signal.aborted || !await beforeSend())
|
|
71
|
+
return { outcome: 'not-capable', diagnostic: 'Claude activity was consumed before native write.' };
|
|
72
|
+
return writeClaudeWake(request.route.sessionId, request.route.address.endpoint, messageId, payload, timeoutMs, signal);
|
|
73
|
+
}
|
|
74
|
+
function currentPayload(facts, request) {
|
|
75
|
+
const seat = facts.seat(request.participant), evidence = facts.deliveryByToken(request.claimToken);
|
|
76
|
+
if (seat === undefined || evidence === undefined || seat.generation !== evidence.generation
|
|
77
|
+
|| seat.generation !== request.seat.generation || !sameBody(seat.body, request.seat.body)
|
|
78
|
+
|| seat.body.channel !== 'claude-code' || seat.body.session !== request.route.sessionId
|
|
79
|
+
|| evidence.activity !== request.activity || !['dispatching', 'unknown', 'accepted'].includes(evidence.outcome)
|
|
80
|
+
|| seat.wakeAddress?.kind !== 'claude-native' || !sameAddress(seat.wakeAddress.address, request.route.address))
|
|
44
81
|
return undefined;
|
|
45
|
-
const native =
|
|
46
|
-
|
|
47
|
-
const index = parseActivityId(row.activity);
|
|
48
|
-
if (!owner || index === undefined || !row.claimToken)
|
|
82
|
+
const native = previewFacts(facts, request);
|
|
83
|
+
if (native === undefined)
|
|
49
84
|
return undefined;
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const { value: preview } = await square.artifact.read((facts) => nativePendingPreview(facts, { location: row.location, routeKind: 'claude-native', sessionId: row.session, participant: row.participant, actIndex: index, epoch: native.epoch, address: { endpoint: native.endpoint }, cancelledThrough: owner.cancelledThrough, now: Date.now() }));
|
|
53
|
-
if (!preview || nativePayload(row.claimToken, preview.payload) !== native.payload)
|
|
54
|
-
return undefined;
|
|
55
|
-
return { cancelledThrough: owner.cancelledThrough, clipped: preview.clipped };
|
|
56
|
-
}
|
|
57
|
-
finally {
|
|
58
|
-
await closeOpenSquare(square);
|
|
59
|
-
}
|
|
85
|
+
const payload = nativePayload(request.claimToken, native.payload);
|
|
86
|
+
return evidence.correlation?.payloadHash === hashPayload(payload) ? payload : undefined;
|
|
60
87
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
return { outcome: 'not-capable', diagnostic: 'Claude native inbox requires a loaded interactive Square mod.' };
|
|
67
|
-
return hostLedger.withClaimLock(async () => {
|
|
68
|
-
if (signal.aborted)
|
|
69
|
-
return { outcome: 'not-capable', diagnostic: 'Native dispatch deadline elapsed before send.' };
|
|
70
|
-
if (!request.claimToken || !await (beforeSend?.() ?? Promise.resolve(true)))
|
|
71
|
-
return { outcome: 'unknown', signature: 'native_send_suppressed' };
|
|
72
|
-
const owner = (await hostLedger.listPresence({ location: request.location, participant: request.participant })).find((binding) => binding.session === request.route.sessionId && binding.epoch === request.route.epoch);
|
|
73
|
-
if (!owner)
|
|
74
|
-
return { outcome: 'not-capable', diagnostic: 'Claude binding changed.' };
|
|
75
|
-
const square = await openSquare(request.location, { hostLedger });
|
|
76
|
-
let payload;
|
|
88
|
+
async function rowsForSession(sessionId, env, tokens) {
|
|
89
|
+
const rows = [];
|
|
90
|
+
const paths = new Set((await projectSessionCandidates({ sessionId, channel: 'claude-code', env })).map(candidate => candidate.location));
|
|
91
|
+
for (const location of paths) {
|
|
92
|
+
const artifact = createArtifactPort(location);
|
|
77
93
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
const found = await artifact.read((facts) => tokens.flatMap((token) => {
|
|
95
|
+
const evidence = facts.deliveryByToken(token);
|
|
96
|
+
if (evidence?.kind !== 'wake' || evidence.correlation?.harness !== 'claude' || evidence.correlation.token !== token
|
|
97
|
+
|| evidence.route?.kind !== 'claude-native' || evidence.route.address.sessionId !== sessionId)
|
|
98
|
+
return [];
|
|
99
|
+
const row = { artifact, evidence };
|
|
100
|
+
const seat = facts.seat(evidence.participant), index = parseActivityId(evidence.activity);
|
|
101
|
+
const item = index === undefined ? undefined : facts.activity(index);
|
|
102
|
+
if (seat === undefined || item?.kind !== 'say')
|
|
103
|
+
return [row];
|
|
104
|
+
const route = { location, participant: seat.participant, sessionId, channel: 'claude-code',
|
|
105
|
+
...evidence.route, updatedAt: evidence.at };
|
|
106
|
+
const request = { location, participant: seat.participant, activity: evidence.activity,
|
|
107
|
+
actor: item.actor, seat: asSeatRef(seat), claimToken: token, attemptN: evidence.attemptN, route, artifact };
|
|
108
|
+
return [{ ...row, request, payload: currentPayload(facts, request) }];
|
|
109
|
+
}));
|
|
110
|
+
rows.push(...found.value);
|
|
111
|
+
if (found.value.length === 0)
|
|
112
|
+
await artifact.close();
|
|
83
113
|
}
|
|
84
|
-
|
|
85
|
-
await
|
|
114
|
+
catch {
|
|
115
|
+
await artifact.close();
|
|
86
116
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const native = { harness: 'claude', payload, epoch: owner.epoch ?? 0, endpoint: request.route.address.endpoint, messageId: randomUUID() };
|
|
90
|
-
const row = { location: request.location, participant: request.participant, session: request.route.sessionId, activity: request.activity, kind: 'wake', outcome: 'dispatching', routeKind: 'claude-native', attemptN: request.attemptN, claimToken: request.claimToken, nativeDelivery: native };
|
|
91
|
-
if (!await currentDelivery(row, hostLedger) || !await hostLedger.prepareNativeWake(row))
|
|
92
|
-
return { outcome: 'not-capable', diagnostic: 'Native delivery is no longer current.' };
|
|
93
|
-
if (!await (beforeSend?.() ?? Promise.resolve(true)) || !await currentDelivery(row, hostLedger))
|
|
94
|
-
return { outcome: 'not-capable', diagnostic: 'Catch, cancellation or consumption won before native write.' };
|
|
95
|
-
return writeClaudeWake(row.session, native.endpoint, native.messageId, payload, timeoutMs, signal);
|
|
96
|
-
}, signal);
|
|
117
|
+
}
|
|
118
|
+
return rows;
|
|
97
119
|
}
|
|
98
|
-
/**
|
|
120
|
+
/** Recognize the whole frame first; one inactive token vetoes it before any evidence/receipt write. */
|
|
99
121
|
export async function observeClaudeDelivery(sessionId, text, operation, env = process.env) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
const tokens = nativeTokens(text);
|
|
123
|
+
if (tokens.length === 0)
|
|
124
|
+
return { recognized: false, current: false };
|
|
125
|
+
const rows = await rowsForSession(sessionId, env, [...new Set(tokens)]);
|
|
126
|
+
const recognized = rows.length > 0;
|
|
127
|
+
const frames = [...text.matchAll(/(?:^|\n)(\[square-inbox:([a-zA-Z0-9-]+)\]\n[\s\S]*?\n\[\/square-inbox:\2\])(?=\n|$)/g)];
|
|
128
|
+
try {
|
|
129
|
+
if (!recognized || rows.some(row => {
|
|
130
|
+
const matching = frames.filter(frame => frame[2] === row.evidence.claimToken);
|
|
131
|
+
return row.payload === undefined || matching.length !== tokens.filter(token => token === row.evidence.claimToken).length
|
|
132
|
+
|| matching.some(frame => frame[1] !== row.payload);
|
|
133
|
+
}))
|
|
134
|
+
return { recognized, current: false };
|
|
109
135
|
for (const row of rows) {
|
|
110
|
-
if (
|
|
136
|
+
if (operation === 'guard')
|
|
111
137
|
continue;
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (operation === '
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (operation === 'stored') {
|
|
122
|
-
await ledger.confirmWakeAdmission({ ...row, claimToken: row.claimToken });
|
|
123
|
-
const prior = await projectPresentationEvidence({ hostLedger: ledger, location: row.location, participant: row.participant, sessionId, activity: row.activity });
|
|
124
|
-
if (presentationSuppressesWake(prior))
|
|
125
|
-
continue;
|
|
126
|
-
const square = await openSquare(row.location, { hostLedger: ledger, env });
|
|
127
|
-
try {
|
|
128
|
-
await presentPending({ artifact: square.artifact, hostLedger: ledger, location: row.location, participant: row.participant, session: sessionId, activity: row.activity, sink: { present() { } }, markSeen: !pending.clipped, current: (facts) => {
|
|
129
|
-
const index = parseActivityId(row.activity);
|
|
130
|
-
if (index === undefined)
|
|
131
|
-
return false;
|
|
132
|
-
const preview = nativePendingPreview(facts, { location: row.location, routeKind: 'claude-native', sessionId: row.session, participant: row.participant, actIndex: index, epoch: native.epoch, address: { endpoint: native.endpoint }, cancelledThrough: pending.cancelledThrough, now: Date.now() });
|
|
133
|
-
return preview !== undefined && nativePayload(row.claimToken, preview.payload) === native.payload;
|
|
134
|
-
} });
|
|
135
|
-
}
|
|
136
|
-
finally {
|
|
137
|
-
await closeOpenSquare(square);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
138
|
+
const request = row.request, payload = row.payload;
|
|
139
|
+
const admitted = await row.artifact.transactAs({ kind: 'participant', seat: request.seat }, facts => currentPayload(facts, request) === payload && facts.completeDelivery({ seat: request.seat,
|
|
140
|
+
activity: row.evidence.activity, claimToken: request.claimToken, outcome: 'accepted', at: Date.now() })).catch(() => ({ value: false }));
|
|
141
|
+
if (!admitted.value)
|
|
142
|
+
return { recognized, current: false };
|
|
143
|
+
if (operation === 'stored')
|
|
144
|
+
await presentPending({ artifact: row.artifact, location: request.location,
|
|
145
|
+
participant: request.participant, activity: request.activity, seat: request.seat, sink: { present() { } },
|
|
146
|
+
current: facts => currentPayload(facts, request) === payload });
|
|
140
147
|
}
|
|
141
|
-
return { recognized
|
|
142
|
-
}
|
|
148
|
+
return { recognized, current: true };
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
await Promise.all([...new Set(rows.map(row => row.artifact))].map(artifact => artifact.close()));
|
|
152
|
+
}
|
|
143
153
|
}
|
|
@@ -3,7 +3,7 @@ import { promises as fs } from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { z } from 'zod/v4';
|
|
5
5
|
import { withFileLock } from './file-lock.js';
|
|
6
|
-
import {
|
|
6
|
+
import { squareIndexPath } from './square-index.js';
|
|
7
7
|
import { SquareError } from './model.js';
|
|
8
8
|
const LEASE_MS = 10 * 60 * 1000;
|
|
9
9
|
const text = z.string().max(4096).refine(value => value.trim().length > 0 && !/[\x00-\x1f\x7f]/u.test(value));
|
|
@@ -12,7 +12,7 @@ const coordinate = z.object({
|
|
|
12
12
|
});
|
|
13
13
|
const record = coordinate.extend({ token: z.string().regex(/^[a-f0-9]{64}$/u), expiresAt: z.number() });
|
|
14
14
|
const unavailable = () => new SquareError('unavailable', 'Claude MCP call context unavailable');
|
|
15
|
-
const storeRoot = (env) => path.
|
|
15
|
+
const storeRoot = (env) => path.join(path.dirname(squareIndexPath(env)), 'claude-mcp-calls');
|
|
16
16
|
const callFile = (root, id) => path.join(root, `${createHash('sha256').update(id).digest('hex')}.json`);
|
|
17
17
|
async function readRecord(file) {
|
|
18
18
|
try {
|
package/dist/claude-mod.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { operationEnv } from './participant-identity.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { closeOpenSquare } from './open-square.js';
|
|
5
|
-
import { publishWakeRoute, retireWakeRouteFromArtifact } from './routes.js';
|
|
2
|
+
import { createArtifactPort } from './artifact.js';
|
|
3
|
+
import { recordClaudeDeliveryStatus } from './claude-delivery-status.js';
|
|
6
4
|
import { Square } from './square-api.js';
|
|
7
5
|
import { observeClaudeDelivery } from './claude-delivery.js';
|
|
8
6
|
import { processActNotificationsOnce } from './notifications.js';
|
|
9
7
|
import { sweepPending } from './delivery-operations.js';
|
|
10
8
|
import { beginClaudeMcpCall, endClaudeMcpCall } from './claude-mcp-context.js';
|
|
11
|
-
import {
|
|
9
|
+
import { asSeatRef } from './seats.js';
|
|
10
|
+
import { projectSessionSeats, projectSessionCandidates } from './inbox.js';
|
|
11
|
+
import { InternalSquareError, SquareError, sameBody } from './model.js';
|
|
12
|
+
/** Persisted hook state keeps its wire coordinate; internal consumers keep the located seat. */
|
|
13
|
+
function claudeBinding({ location, seat }) {
|
|
14
|
+
return { location, participant: seat.participant, session: seat.body.session, generation: seat.generation };
|
|
15
|
+
}
|
|
12
16
|
/** JSON stdin bridge. Identity is explicit per operation; process.env is never rewritten. */
|
|
13
17
|
export async function runClaudeMod(inputText, env = process.env) {
|
|
14
18
|
if (inputText.length > 512_000)
|
|
@@ -28,31 +32,30 @@ export async function runClaudeMod(inputText, env = process.env) {
|
|
|
28
32
|
return '{}\n';
|
|
29
33
|
}
|
|
30
34
|
const scoped = operationEnv('claude', input.sessionId, env);
|
|
31
|
-
const ledger = hostLedgerForEnv(env);
|
|
32
35
|
const signal = AbortSignal.timeout(2500);
|
|
36
|
+
const body = { channel: 'claude-code', session: input.sessionId, ...(input.agentId === undefined ? {} : { agent: input.agentId }) };
|
|
33
37
|
const supported = process.platform === 'darwin' && typeof input.endpoint === 'string' && input.endpoint.startsWith('/');
|
|
38
|
+
const sessionSeats = () => projectSessionSeats({ body, env }, signal);
|
|
34
39
|
if (input.operation === 'end') {
|
|
35
40
|
const retired = [];
|
|
36
41
|
const captured = [...input.bindings ?? []];
|
|
37
42
|
// Discover committed late joins at the pinned end boundary, never later owners.
|
|
38
43
|
if (input.endedAt !== undefined)
|
|
39
|
-
for (const
|
|
40
|
-
if (
|
|
41
|
-
|
|
44
|
+
for (const member of await sessionSeats()) {
|
|
45
|
+
if (member.joinedAt > input.endedAt || captured.some((item) => item.location === member.location
|
|
46
|
+
&& item.participant === member.seat.participant && item.generation === member.seat.generation))
|
|
47
|
+
continue;
|
|
48
|
+
captured.push(claudeBinding(member));
|
|
42
49
|
}
|
|
43
50
|
for (const binding of captured) {
|
|
44
|
-
if (binding.session !== input.sessionId
|
|
51
|
+
if (binding.session !== input.sessionId)
|
|
45
52
|
continue;
|
|
46
|
-
const square = await Square.at({ path: binding.location,
|
|
53
|
+
const square = await Square.at({ path: binding.location, env: scoped, body,
|
|
54
|
+
participant: binding.participant });
|
|
47
55
|
try {
|
|
48
|
-
const ended = await square.endOwnedSession(binding.participant, input.sessionId, binding.
|
|
49
|
-
if (ended?.activity)
|
|
50
|
-
retired.push(binding);
|
|
51
|
-
const row = { location: binding.location, participant: binding.participant, session: input.sessionId, activity: ended.activity.id, kind: 'lifecycle', bindingEpoch: binding.epoch, outcome: 'retired' };
|
|
52
|
-
const claim = await ledger.claimEvidence({ ...row, leaseMs: 1000 });
|
|
53
|
-
if (claim.status === 'acquired')
|
|
54
|
-
await ledger.appendEvidence({ ...row, claimToken: claim.claimToken });
|
|
55
|
-
}
|
|
56
|
+
const ended = await square.endOwnedSession(binding.participant, input.sessionId, binding.generation, body);
|
|
57
|
+
if (ended?.activity)
|
|
58
|
+
retired.push({ ...binding, generation: binding.generation });
|
|
56
59
|
}
|
|
57
60
|
finally {
|
|
58
61
|
await square.close();
|
|
@@ -64,122 +67,133 @@ export async function runClaudeMod(inputText, env = process.env) {
|
|
|
64
67
|
}
|
|
65
68
|
const recovery = new Set();
|
|
66
69
|
const cancelledBindings = [...input.cancelledBindings ?? []];
|
|
67
|
-
if (input.operation === 'start') {
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
70
|
+
if (input.operation === 'start' && input.resume) {
|
|
71
|
+
// Resume is proven by the artifact's latest participant lifecycle, while HOME
|
|
72
|
+
// contributes only candidate paths. A stale candidate never establishes a seat.
|
|
73
|
+
const candidates = await projectSessionCandidates({ body, env });
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
for (const candidate of candidates) {
|
|
76
|
+
if (signal.aborted)
|
|
77
|
+
break;
|
|
78
|
+
const probe = createArtifactPort(candidate.location, undefined, signal);
|
|
79
|
+
let names;
|
|
80
|
+
try {
|
|
81
|
+
names = (await probe.read(facts => facts.completedSeatsForBody(body).map(seat => seat.participant), signal)).value;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof InternalSquareError && error.code === 'not_found')
|
|
85
|
+
throw new SquareError('unavailable', `Square is unavailable at ${candidate.location}`);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await probe.close();
|
|
90
|
+
}
|
|
91
|
+
for (const participant of names) {
|
|
92
|
+
const key = `${candidate.location}\0${participant}`;
|
|
93
|
+
if (seen.has(key))
|
|
89
94
|
continue;
|
|
90
|
-
|
|
95
|
+
seen.add(key);
|
|
96
|
+
const square = await Square.at({ path: candidate.location, env: scoped, body, participant });
|
|
91
97
|
try {
|
|
92
|
-
await square.join(
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
await ledger.removePresenceIfUnchanged(claimed.record);
|
|
98
|
+
await square.join(participant);
|
|
96
99
|
}
|
|
97
100
|
finally {
|
|
98
101
|
await square.close();
|
|
99
102
|
}
|
|
100
103
|
}
|
|
104
|
+
}
|
|
101
105
|
}
|
|
102
106
|
if (input.operation === 'start' || input.operation === 'reconcile' || input.operation === 'cancel' || input.cancelAt !== undefined) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
107
|
+
for (const member of await sessionSeats()) {
|
|
108
|
+
if (signal.aborted)
|
|
109
|
+
break;
|
|
110
|
+
const { location, seat: capturedSeat, joinedAt } = member;
|
|
111
|
+
const matches = (item) => item !== undefined
|
|
112
|
+
&& item.location === location && item.participant === capturedSeat.participant && item.session === capturedSeat.body.session
|
|
113
|
+
&& (item.generation === undefined || item.generation === capturedSeat.generation);
|
|
114
|
+
const expected = input.bindings?.find(matches);
|
|
115
|
+
const cancelled = input.cancelledBindings?.find(matches) ?? (input.operation === 'cancel' ? expected : undefined);
|
|
116
|
+
const lateCancelledJoin = input.operation === 'cancel' && input.cancelAt !== undefined
|
|
117
|
+
&& joinedAt <= input.cancelAt && expected === undefined;
|
|
118
|
+
if (lateCancelledJoin && !cancelledBindings.some(matches))
|
|
119
|
+
cancelledBindings.push(claudeBinding(member));
|
|
120
|
+
if (input.operation !== 'start' && input.operation !== 'reconcile' && !lateCancelledJoin && expected === undefined)
|
|
117
121
|
continue;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (!page.hasMore)
|
|
143
|
-
return -1;
|
|
144
|
-
beforeIndex = page.items[0].index;
|
|
145
|
-
}
|
|
146
|
-
}, signal);
|
|
147
|
-
await ledger.suppressPresence({ ...current, epoch }, through);
|
|
148
|
-
}
|
|
149
|
-
if (!existing || existing.address.endpoint !== input.endpoint)
|
|
150
|
-
recovery.add(binding.location);
|
|
151
|
-
await publishWakeRoute(square.artifact, { location: binding.location, participant: binding.participant, sessionId: input.sessionId, channel: 'claude-code', kind: 'claude-native', epoch, address: { sessionId: input.sessionId, endpoint: input.endpoint, version: input.version, platform: 'darwin', loaded: 'true' } }, { requireCurrentSession: true });
|
|
122
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
123
|
+
try {
|
|
124
|
+
const { value: currentSeat } = await artifact.read((facts) => facts.seat(capturedSeat.participant), signal);
|
|
125
|
+
if (currentSeat === undefined || currentSeat.generation !== capturedSeat.generation
|
|
126
|
+
|| !sameBody(currentSeat.body, capturedSeat.body))
|
|
127
|
+
continue;
|
|
128
|
+
const seat = asSeatRef(currentSeat);
|
|
129
|
+
const existing = currentSeat.wakeAddress?.kind === 'claude-native' ? currentSeat.wakeAddress : undefined;
|
|
130
|
+
const cutoff = input.cancelAt !== undefined && (cancelled !== undefined || lateCancelledJoin) ? input.cancelAt : undefined;
|
|
131
|
+
if (cutoff !== undefined) {
|
|
132
|
+
const { value: through } = await artifact.read((facts) => {
|
|
133
|
+
let beforeIndex;
|
|
134
|
+
for (;;) {
|
|
135
|
+
const page = facts.activityHeads({ beforeIndex }, { edge: 'newest', limit: 100 });
|
|
136
|
+
const last = [...page.items].reverse().find((activity) => activity.at <= cutoff);
|
|
137
|
+
if (last !== undefined)
|
|
138
|
+
return last.index;
|
|
139
|
+
if (!page.hasMore)
|
|
140
|
+
return -1;
|
|
141
|
+
beforeIndex = page.items[0].index;
|
|
142
|
+
}
|
|
143
|
+
}, signal);
|
|
144
|
+
if (through >= 0)
|
|
145
|
+
await artifact.cancelDeliveries(seat, through, signal);
|
|
152
146
|
}
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
if (!supported) {
|
|
148
|
+
if (existing !== undefined)
|
|
149
|
+
await artifact.transactAs({ kind: 'participant', seat }, (facts) => facts.updateWakeAddress(seat, undefined), signal);
|
|
150
|
+
continue;
|
|
155
151
|
}
|
|
156
|
-
|
|
152
|
+
if (existing === undefined || existing.address.endpoint !== input.endpoint)
|
|
153
|
+
recovery.add(location);
|
|
154
|
+
await artifact.transactAs({ kind: 'participant', seat }, (facts) => facts.updateWakeAddress(seat, {
|
|
155
|
+
kind: 'claude-native', address: { sessionId: input.sessionId, endpoint: input.endpoint, version: input.version, platform: 'darwin', loaded: 'true' },
|
|
156
|
+
}), signal);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error instanceof InternalSquareError && error.code === 'not_found')
|
|
160
|
+
throw new SquareError('unavailable', `Square is unavailable at ${location}`);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
await artifact.close();
|
|
165
|
+
}
|
|
157
166
|
}
|
|
158
167
|
}
|
|
159
168
|
if (input.operation === 'start' || input.operation === 'reconcile' || input.operation === 'cancel') {
|
|
160
|
-
const
|
|
169
|
+
const members = await sessionSeats();
|
|
161
170
|
// One startup recovery pass for known unsent work. Unknown/admitted sends never replay.
|
|
162
171
|
if (supported && input.operation !== 'cancel')
|
|
163
|
-
for (const
|
|
172
|
+
for (const { location } of members) {
|
|
164
173
|
if (signal.aborted)
|
|
165
174
|
break;
|
|
166
|
-
if (input.operation !== 'start' && !recovery.has(
|
|
175
|
+
if (input.operation !== 'start' && !recovery.has(location))
|
|
167
176
|
continue;
|
|
168
|
-
const
|
|
177
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
169
178
|
let indices;
|
|
170
179
|
try {
|
|
171
|
-
indices = await sweepPending({ artifact
|
|
180
|
+
indices = await sweepPending({ artifact, location, now: Date.now(), graceMs: 0, limit: 8 });
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if (error instanceof InternalSquareError && error.code === 'not_found')
|
|
184
|
+
throw new SquareError('unavailable', `Square is unavailable at ${location}`);
|
|
185
|
+
throw error;
|
|
172
186
|
}
|
|
173
187
|
finally {
|
|
174
|
-
await
|
|
188
|
+
await artifact.close();
|
|
175
189
|
}
|
|
176
190
|
for (const index of indices) {
|
|
177
191
|
if (signal.aborted)
|
|
178
192
|
break;
|
|
179
|
-
await processActNotificationsOnce(
|
|
193
|
+
await processActNotificationsOnce(location, index, { env: { ...env, SQUARE_NOTIFY_DELIVERY_WAIT_MS: '200' } });
|
|
180
194
|
}
|
|
181
195
|
}
|
|
182
|
-
return `${JSON.stringify({ available: supported, diagnostic: supported ? undefined : 'Native inbox unavailable: requires macOS Claude with an interactive loaded mod and its real endpoint.', bindings, cancelledBindings })}\n`;
|
|
196
|
+
return `${JSON.stringify({ available: supported, diagnostic: supported ? undefined : 'Native inbox unavailable: requires macOS Claude with an interactive loaded mod and its real endpoint.', bindings: members.map(claudeBinding), cancelledBindings })}\n`;
|
|
183
197
|
}
|
|
184
198
|
if (!['guard', 'admitted', 'stored'].includes(input.operation) || typeof input.text !== 'string')
|
|
185
199
|
throw new Error('Unknown Claude bridge operation.');
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type HardCap } from '../model.js';
|
|
2
2
|
import { Square } from '../square-api.js';
|
|
3
|
-
import type { OperationControl } from '../square-facade.js';
|
|
3
|
+
import type { Body, OperationControl } from '../square-facade.js';
|
|
4
4
|
export interface CommandContext {
|
|
5
5
|
cwd: string;
|
|
6
6
|
env: NodeJS.ProcessEnv;
|
|
@@ -36,6 +36,8 @@ export declare function parseGlobalArgs(rawArgs: string[]): Promise<ParsedGlobal
|
|
|
36
36
|
export declare function locationIsRequired(command: string): boolean;
|
|
37
37
|
export declare function defaultContext(command: string, squarePath?: string, name?: string): CommandContext;
|
|
38
38
|
export declare function squareInput(context: CommandContext): {
|
|
39
|
+
wakeAddress?: import("../model.js").WakeAddress | undefined;
|
|
40
|
+
body?: Body | undefined;
|
|
39
41
|
cwd: string;
|
|
40
42
|
env: NodeJS.ProcessEnv;
|
|
41
43
|
path: string | undefined;
|