@astrosheep/square 0.4.0 → 0.4.2
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 +1 -3
- package/dist/artifact.js +12 -36
- package/dist/attention-presentation.d.ts +2 -22
- package/dist/attention-presentation.js +9 -12
- package/dist/boundary-presentation.d.ts +3 -14
- package/dist/boundary-presentation.js +29 -104
- package/dist/card-presentation.d.ts +1 -0
- package/dist/card-presentation.js +2 -0
- package/dist/catch-decisions.js +1 -7
- package/dist/claude-boundary-state.d.ts +20 -0
- package/dist/claude-boundary-state.js +84 -0
- package/dist/claude-hook.d.ts +2 -0
- package/dist/claude-hook.js +231 -0
- package/dist/claude-mcp-context.d.ts +1 -1
- package/dist/claude-mcp-context.js +12 -3
- package/dist/cli/context.js +7 -9
- package/dist/cli/observation-commands.d.ts +4 -1
- package/dist/cli/observation-commands.js +37 -4
- package/dist/cli/registry.js +5 -2
- package/dist/codex-address.d.ts +2 -2
- package/dist/codex-boundary-state.d.ts +1 -5
- package/dist/codex-boundary-state.js +0 -4
- package/dist/codex-hook.d.ts +7 -10
- package/dist/codex-hook.js +69 -33
- package/dist/codex-queue.d.ts +10 -11
- package/dist/codex-queue.js +32 -32
- package/dist/codex-watch.d.ts +2 -0
- package/dist/codex-watch.js +100 -0
- package/dist/decisions.js +2 -2
- package/dist/delivery-deferral.d.ts +8 -17
- package/dist/delivery-deferral.js +0 -28
- package/dist/delivery-frame.d.ts +7 -0
- package/dist/delivery-frame.js +11 -0
- package/dist/delivery-ledger-sqlite.d.ts +97 -0
- package/dist/delivery-ledger-sqlite.js +496 -0
- package/dist/delivery-ledger.d.ts +196 -0
- package/dist/delivery-ledger.js +268 -0
- package/dist/delivery-lifetime.d.ts +6 -0
- package/dist/delivery-lifetime.js +45 -0
- package/dist/harness-claude.js +3 -4
- package/dist/harness-links.js +63 -7
- package/dist/harness.js +12 -17
- package/dist/help.js +4 -1
- package/dist/http-delivery.d.ts +5 -9
- package/dist/http-delivery.js +20 -77
- package/dist/inbox.d.ts +6 -49
- package/dist/inbox.js +24 -371
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -1
- package/dist/mcp.js +2 -3
- package/dist/model.d.ts +7 -47
- package/dist/model.js +1 -7
- package/dist/notifications.d.ts +24 -20
- package/dist/notifications.js +39 -108
- package/dist/operation-control.d.ts +4 -0
- package/dist/operation-control.js +20 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
- package/dist/packages/agent-delivery/src/claude-native.js +5 -0
- package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
- package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
- package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
- package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
- package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
- package/dist/packages/agent-delivery/src/codex.js +41 -4
- package/dist/packages/agent-delivery/src/index.d.ts +3 -1
- package/dist/packages/agent-delivery/src/index.js +1 -1
- package/dist/packages/agent-delivery/src/paseo.js +11 -2
- package/dist/packages/agent-delivery/src/types.d.ts +1 -0
- package/dist/paseo-delivery.d.ts +5 -5
- package/dist/paseo-delivery.js +19 -97
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/index.d.ts +5 -3
- package/dist/plugins/opencode/index.js +99 -21
- package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
- package/dist/plugins/opencode-shared/delivery.js +109 -0
- package/dist/plugins/opencode-v1/index.d.ts +28 -0
- package/dist/plugins/opencode-v1/index.js +41 -0
- package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
- package/dist/plugins/opencode-v1/runtime.js +152 -0
- package/dist/plugins/opencode-v1/transport.d.ts +13 -0
- package/dist/plugins/opencode-v1/transport.js +50 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
- package/dist/plugins/paseo/shared/card-presentation.js +7 -0
- package/dist/ports.d.ts +31 -90
- package/dist/session-lifecycle.d.ts +8 -7
- package/dist/session-lifecycle.js +40 -42
- package/dist/sqlite-square.d.ts +14 -23
- package/dist/sqlite-square.js +132 -139
- package/dist/square-api.js +4 -7
- package/dist/square-core.d.ts +0 -1
- package/dist/square-domain.d.ts +5 -17
- package/dist/square-domain.js +104 -221
- package/dist/square-facade.d.ts +18 -15
- package/dist/square-post.d.ts +53 -0
- package/dist/square-post.js +4 -0
- package/dist/square-posting.d.ts +13 -0
- package/dist/square-posting.js +174 -0
- package/dist/square-producer-sqlite.d.ts +11 -0
- package/dist/square-producer-sqlite.js +210 -0
- package/dist/square-sqlite.d.ts +15 -25
- package/dist/square-sqlite.js +150 -226
- package/dist/views.d.ts +2 -2
- package/dist/views.js +0 -1
- package/dist/wake-sink.d.ts +4 -11
- package/dist/wake-sink.js +8 -32
- package/dist/wake-transports.d.ts +4 -7
- package/dist/wake-transports.js +7 -39
- package/examples/symmetric-domains.mjs +29 -249
- package/guides/change-waits.md +13 -53
- package/guides/claude-native.md +7 -171
- package/guides/codex-delivery.md +55 -80
- package/guides/delivery-primitives.md +48 -207
- package/guides/mcp.md +1 -1
- package/guides/operation-cancellation.md +2 -5
- package/guides/participant-ownership.md +25 -89
- package/guides/pi-native-delivery.md +19 -56
- package/guides/pi-tools.md +6 -8
- package/guides/square-api.md +113 -615
- package/package.json +8 -8
- package/plugins/claude/.claude-plugin/plugin.json +2 -3
- package/plugins/claude/hooks/hooks.json +11 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/hooks/hooks.json +11 -23
- package/plugins/opencode/index.ts +91 -23
- package/plugins/opencode-shared/delivery.ts +91 -0
- package/plugins/opencode-v1/index.ts +36 -0
- package/plugins/opencode-v1/runtime.ts +134 -0
- package/plugins/opencode-v1/transport.ts +41 -0
- package/plugins/paseo/OVERVIEW.md +5 -5
- package/plugins/paseo/README.md +7 -6
- package/plugins/paseo/client/markdown.tsx +5 -5
- package/plugins/paseo/client/square-message.tsx +28 -24
- package/plugins/paseo/client/transform.ts +8 -5
- package/plugins/paseo/shared/card-presentation.ts +16 -0
- package/plugins/paseo/shared/message.ts +6 -11
- package/plugins/pi/square-pi.js +156 -417
- package/dist/channel-artifact.d.ts +0 -10
- package/dist/channel-artifact.js +0 -55
- package/dist/channel-delivery.d.ts +0 -11
- package/dist/channel-delivery.js +0 -51
- package/dist/channel-domain.d.ts +0 -3
- package/dist/channel-domain.js +0 -83
- package/dist/channel-facade.d.ts +0 -76
- package/dist/channel-facade.js +0 -1
- package/dist/channel-internal.d.ts +0 -126
- package/dist/channel-internal.js +0 -440
- package/dist/channel-metadata.d.ts +0 -11
- package/dist/channel-metadata.js +0 -1
- package/dist/channel-ports.d.ts +0 -241
- package/dist/channel-ports.js +0 -1
- package/dist/channel-presentation.d.ts +0 -17
- package/dist/channel-presentation.js +0 -22
- package/dist/channel-sqlite-schema.d.ts +0 -13
- package/dist/channel-sqlite-schema.js +0 -81
- package/dist/channel-transport.d.ts +0 -62
- package/dist/channel-transport.js +0 -1
- package/dist/channel.d.ts +0 -84
- package/dist/channel.js +0 -60
- package/dist/claude-delivery-status.d.ts +0 -13
- package/dist/claude-delivery-status.js +0 -46
- package/dist/claude-delivery.d.ts +0 -12
- package/dist/claude-delivery.js +0 -108
- package/dist/claude-mod.d.ts +0 -8
- package/dist/claude-mod.js +0 -172
- package/dist/claude-transport.d.ts +0 -5
- package/dist/claude-transport.js +0 -58
- package/dist/cmd/notify-once.d.ts +0 -2
- package/dist/cmd/notify-once.js +0 -29
- package/dist/delivery-attempts.d.ts +0 -52
- package/dist/delivery-attempts.js +0 -73
- package/dist/delivery-effects.d.ts +0 -139
- package/dist/delivery-effects.js +0 -163
- package/dist/delivery-health.d.ts +0 -51
- package/dist/delivery-health.js +0 -173
- package/dist/delivery-operations.d.ts +0 -22
- package/dist/delivery-operations.js +0 -198
- package/dist/delivery-primitives.d.ts +0 -5
- package/dist/delivery-primitives.js +0 -3
- package/dist/delivery.d.ts +0 -56
- package/dist/delivery.js +0 -80
- package/dist/plugins/opencode/delivery.d.ts +0 -19
- package/dist/plugins/opencode/delivery.js +0 -223
- package/dist/presentation-operations.d.ts +0 -5
- package/dist/presentation-operations.js +0 -86
- package/dist/sqlite-channel.d.ts +0 -15
- package/dist/sqlite-channel.js +0 -600
- package/dist/square-index.d.ts +0 -16
- package/dist/square-index.js +0 -106
- package/dist/wake-preview.d.ts +0 -7
- package/dist/wake-preview.js +0 -15
- package/plugins/claude/hooks/register.js +0 -132
- package/plugins/claude/types.d.ts +0 -7
- package/plugins/opencode/delivery.ts +0 -198
- /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
- /package/dist/{channel-values.js → square-values.js} +0 -0
package/dist/square-index.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
5
|
-
import { withFileLock } from './file-lock.js';
|
|
6
|
-
import { canonicalPath } from './canonical-path.js';
|
|
7
|
-
import { observeFileMetadata } from './file-changes.js';
|
|
8
|
-
import { validateOwner } from './neutral.js';
|
|
9
|
-
export function squareIndexPath(env = process.env) {
|
|
10
|
-
const configured = env.SQUARE_INDEX?.trim();
|
|
11
|
-
return path.resolve(configured ?? path.join(os.homedir(), '.square', 'index.ndjsonl'));
|
|
12
|
-
}
|
|
13
|
-
function lockPath(index) {
|
|
14
|
-
return `${index}.lock`;
|
|
15
|
-
}
|
|
16
|
-
function validEntry(value) {
|
|
17
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
18
|
-
return undefined;
|
|
19
|
-
const row = value;
|
|
20
|
-
if (typeof row.location !== 'string' || !path.isAbsolute(row.location) || typeof row.owner !== 'string')
|
|
21
|
-
return undefined;
|
|
22
|
-
try {
|
|
23
|
-
validateOwner(row.owner);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
if (Object.keys(row).some((key) => !['location', 'owner'].includes(key)))
|
|
29
|
-
return undefined;
|
|
30
|
-
return { location: row.location, owner: row.owner };
|
|
31
|
-
}
|
|
32
|
-
async function readEntries(index) {
|
|
33
|
-
let content;
|
|
34
|
-
try {
|
|
35
|
-
content = await fs.readFile(index, 'utf8');
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
if (error.code === 'ENOENT')
|
|
39
|
-
return [];
|
|
40
|
-
throw error;
|
|
41
|
-
}
|
|
42
|
-
const entries = [];
|
|
43
|
-
for (const line of content.split('\n')) {
|
|
44
|
-
if (line.trim() === '')
|
|
45
|
-
continue;
|
|
46
|
-
try {
|
|
47
|
-
const entry = validEntry(JSON.parse(line));
|
|
48
|
-
if (entry !== undefined)
|
|
49
|
-
entries.push(entry);
|
|
50
|
-
}
|
|
51
|
-
catch { /* Disposable discovery input. */ }
|
|
52
|
-
}
|
|
53
|
-
return entries;
|
|
54
|
-
}
|
|
55
|
-
function matches(entry, query) {
|
|
56
|
-
return query.owner === undefined || entry.owner === query.owner;
|
|
57
|
-
}
|
|
58
|
-
export async function indexedSquareEntries(query = {}, env = process.env) {
|
|
59
|
-
const entries = await readEntries(squareIndexPath(env));
|
|
60
|
-
const result = [];
|
|
61
|
-
const seen = new Set();
|
|
62
|
-
for (const entry of entries) {
|
|
63
|
-
if (!matches(entry, query))
|
|
64
|
-
continue;
|
|
65
|
-
let location;
|
|
66
|
-
try {
|
|
67
|
-
location = await canonicalPath(entry.location);
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const key = JSON.stringify([location, entry.owner]);
|
|
73
|
-
if (seen.has(key))
|
|
74
|
-
continue;
|
|
75
|
-
seen.add(key);
|
|
76
|
-
result.push({ location, owner: entry.owner });
|
|
77
|
-
}
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
export async function indexedSquarePaths(query = {}, env = process.env) {
|
|
81
|
-
return [...new Set((await indexedSquareEntries(query, env)).map((entry) => entry.location))];
|
|
82
|
-
}
|
|
83
|
-
/** Best-effort discovery only: a busy index skips publication, never queues or delays artifact work. */
|
|
84
|
-
export async function rememberSquarePath(location, owner, index) {
|
|
85
|
-
validateOwner(owner);
|
|
86
|
-
const canonical = await canonicalPath(location);
|
|
87
|
-
await fs.mkdir(path.dirname(index), { recursive: true });
|
|
88
|
-
return await withFileLock(lockPath(index), { retryMs: 10, skipIfBusy: true }, async () => {
|
|
89
|
-
const entries = await readEntries(index);
|
|
90
|
-
if (entries.some((entry) => entry.location === canonical && entry.owner === owner))
|
|
91
|
-
return false;
|
|
92
|
-
const next = [...entries, { location: canonical, owner }];
|
|
93
|
-
const temporary = `${index}.${process.pid}.${randomUUID()}.tmp`;
|
|
94
|
-
try {
|
|
95
|
-
await fs.writeFile(temporary, next.map((entry) => JSON.stringify(entry)).join('\n') + '\n', { flag: 'wx', mode: 0o600 });
|
|
96
|
-
await fs.rename(temporary, index);
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
await fs.rm(temporary, { force: true });
|
|
100
|
-
}
|
|
101
|
-
return true;
|
|
102
|
-
}) ?? false;
|
|
103
|
-
}
|
|
104
|
-
export function observeSquareIndex(env = process.env) {
|
|
105
|
-
return observeFileMetadata(squareIndexPath(env));
|
|
106
|
-
}
|
package/dist/wake-preview.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { WakeProjectionContext, WakeProjection, SquareArtifactPort } from './ports.js';
|
|
2
|
-
export declare function squareWakeProjection(artifact: SquareArtifactPort): WakeProjection;
|
|
3
|
-
/** Agora-framed projection over the narrow captured delivery context. */
|
|
4
|
-
export declare function wakePreview(facts: import('./ports.js').SquareRead, request: WakeProjectionContext): {
|
|
5
|
-
payload: string;
|
|
6
|
-
coverage: "full" | "clipped";
|
|
7
|
-
} | undefined;
|
package/dist/wake-preview.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { parseMessageId } from './message.js';
|
|
2
|
-
import { nativePendingPreview } from './inbox.js';
|
|
3
|
-
import { canonicalPathSync } from './canonical-path.js';
|
|
4
|
-
export function squareWakeProjection(artifact) {
|
|
5
|
-
return async (context) => (await artifact.read(facts => wakePreview(facts, context))).value;
|
|
6
|
-
}
|
|
7
|
-
/** Agora-framed projection over the narrow captured delivery context. */
|
|
8
|
-
export function wakePreview(facts, request) {
|
|
9
|
-
const index = parseMessageId(request.message);
|
|
10
|
-
if (index === undefined)
|
|
11
|
-
return undefined;
|
|
12
|
-
const preview = nativePendingPreview(facts, { location: canonicalPathSync(request.location), participant: request.participant,
|
|
13
|
-
generation: request.seat.generation, owner: request.seat.owner, index, route: request.route, now: Date.now() });
|
|
14
|
-
return preview === undefined ? undefined : { payload: preview.payload, coverage: preview.clipped ? 'clipped' : 'full' };
|
|
15
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
// Claude Code mod. No Node imports, shell interpolation, or child credentials.
|
|
2
|
-
const hasToken = (text) => typeof text === 'string' && /(?:^|\n)\[square-inbox:[a-zA-Z0-9-]+\](?:\n|$)/.test(text);
|
|
3
|
-
|
|
4
|
-
async function coordinate($) {
|
|
5
|
-
return { sessionId: await $.session.id(), cwd: await $.session.cwd(), version: (await $.session.version()).version, endpoint: await $.env.get('CLAUDE_CODE_MESSAGING_SOCKET') };
|
|
6
|
-
}
|
|
7
|
-
async function bridge($, value, budget = 2800) {
|
|
8
|
-
const result = await $.process.run(['square', 'claude-mod'], { cwd: value.cwd, stdin: JSON.stringify(value), timeoutMs: Math.max(1, Math.min(2800, budget)) });
|
|
9
|
-
if (result.exitCode !== 0 || result.isStdoutTruncated) throw new Error('Square native inbox bridge unavailable');
|
|
10
|
-
return JSON.parse(result.stdout);
|
|
11
|
-
}
|
|
12
|
-
async function snapshot($, id) { return (await $.state.get({ plugin: 'square', key: 'inbox', id })).value; }
|
|
13
|
-
async function cancellation($, id) { return (await $.state.get({ plugin: 'square', key: 'cancelAt', id })).value; }
|
|
14
|
-
function confirmLater($, c, operation, text) {
|
|
15
|
-
// State reads are dispatch snapshots. This ONE fresh timer dispatch is not polling.
|
|
16
|
-
// Reload cancels it; lost evidence stays unknown and is never blindly replayed.
|
|
17
|
-
try { $.clock.after(0, () => {
|
|
18
|
-
(async () => {
|
|
19
|
-
if (await $.session.id() !== c.sessionId) return;
|
|
20
|
-
const old = await snapshot($, c.sessionId);
|
|
21
|
-
const cancelled = await cancellation($, c.sessionId);
|
|
22
|
-
await bridge($, { ...c, cancelAt: cancelled?.at, cancelledBindings: cancelled?.bindings, bindings: old?.bindings, operation, text });
|
|
23
|
-
})().catch(() => undefined);
|
|
24
|
-
}); } catch { /* denied scheduling leaves evidence unknown, never a second next */ }
|
|
25
|
-
}
|
|
26
|
-
async function save($, id, update, version) {
|
|
27
|
-
return $.state.set({ plugin: 'square', key: 'inbox', id }, update, version === undefined ? undefined : { ifVersion: version });
|
|
28
|
-
}
|
|
29
|
-
async function reconcile($, sessionId, operation = 'start', resume = false) {
|
|
30
|
-
const read = await coordinate($);
|
|
31
|
-
const c = sessionId ? { ...read, sessionId } : read;
|
|
32
|
-
const held = await $.state.get({ plugin: 'square', key: 'inbox', id: c.sessionId });
|
|
33
|
-
const old = held.value;
|
|
34
|
-
const cancelled = await cancellation($, c.sessionId);
|
|
35
|
-
const result = await bridge($, { ...c, operation, cancelAt: cancelled?.at, cancelledBindings: cancelled?.bindings, bindings: old?.bindings, resume });
|
|
36
|
-
// A concurrent abort must win over a stale reconciliation write.
|
|
37
|
-
await save($, c.sessionId, { coordinate: c, bindings: result.bindings }, held.version);
|
|
38
|
-
if (!result.available) await $.ui.log(result.diagnostic);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function cancel($) {
|
|
42
|
-
const c = await coordinate($);
|
|
43
|
-
const old = await snapshot($, c.sessionId);
|
|
44
|
-
const cancelAt = await $.clock.now();
|
|
45
|
-
const captured = old?.bindings ?? [];
|
|
46
|
-
await $.state.set({ plugin: 'square', key: 'cancelAt', id: c.sessionId }, { at: cancelAt, bindings: captured });
|
|
47
|
-
const result = await bridge($, { ...c, bindings: captured, cancelAt, cancelledBindings: captured, operation: 'cancel' });
|
|
48
|
-
try { $.clock.after(0, () => {
|
|
49
|
-
(async () => {
|
|
50
|
-
const held = await $.state.get({ plugin: 'square', key: 'cancelAt', id: c.sessionId });
|
|
51
|
-
if (held.value?.at === cancelAt) await $.state.set({ plugin: 'square', key: 'cancelAt', id: c.sessionId }, { at: cancelAt, bindings: result.cancelledBindings }, { ifVersion: held.version });
|
|
52
|
-
})().catch(() => undefined);
|
|
53
|
-
}); } catch { /* captured cancellation remains engine-owned */ }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function register(on) {
|
|
57
|
-
on('session.start', async ($, e, next) => {
|
|
58
|
-
await reconcile($).catch(() => undefined);
|
|
59
|
-
return next(e);
|
|
60
|
-
});
|
|
61
|
-
on('classic.SessionStart', async ($, e, next) => {
|
|
62
|
-
await reconcile($, e.session_id, 'start', e.source === 'resume').catch(() => undefined);
|
|
63
|
-
return next(e);
|
|
64
|
-
});
|
|
65
|
-
on('tool.call', async ($, e, next) => {
|
|
66
|
-
try {
|
|
67
|
-
if (typeof e.tool !== 'string' || !e.tool.startsWith('mcp__plugin_square_square__')) return await next(e);
|
|
68
|
-
next.signal.throwIfAborted();
|
|
69
|
-
const c = await coordinate($);
|
|
70
|
-
const call = { ...c, toolUseId: e.tool_use_id, agentId: e.agentId };
|
|
71
|
-
const { token } = await bridge($, { ...call, operation: 'tool-begin' });
|
|
72
|
-
try {
|
|
73
|
-
next.signal.throwIfAborted();
|
|
74
|
-
// MCP receives the original business arguments; identity travels only by tool-use ID.
|
|
75
|
-
return await next(e);
|
|
76
|
-
} finally {
|
|
77
|
-
await bridge($, { ...call, token, operation: 'tool-end' }).catch(() => undefined);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
finally { if (!e.agentId) await reconcile($, undefined, 'reconcile').catch(() => undefined); }
|
|
81
|
-
});
|
|
82
|
-
on('turn.start', async ($, e, next) => {
|
|
83
|
-
if (!e.agentId) await reconcile($, undefined, 'reconcile').catch(() => undefined);
|
|
84
|
-
return next(e);
|
|
85
|
-
});
|
|
86
|
-
on('session.end', async ($, e, next) => {
|
|
87
|
-
// Pinned old ID and captured seat generations, never a later current coordinate.
|
|
88
|
-
const endedAt = await $.clock.now().catch(() => undefined);
|
|
89
|
-
const held = await $.state.get({ plugin: 'square', key: 'inbox', id: e.sessionId }).catch(() => undefined);
|
|
90
|
-
const old = held?.value;
|
|
91
|
-
const c = old?.coordinate ?? await coordinate($).catch(() => undefined);
|
|
92
|
-
if (c) {
|
|
93
|
-
const result = await bridge($, { ...c, sessionId: e.sessionId, bindings: old?.bindings, endedAt, operation: 'end' }, next.budget.remainingMs).catch(() => undefined);
|
|
94
|
-
// Retired membership lives in the host ledger; conversation state may be cleared.
|
|
95
|
-
if (result) await save($, e.sessionId, { coordinate: { ...c, sessionId: e.sessionId }, bindings: [] }, held?.version).catch(() => undefined);
|
|
96
|
-
}
|
|
97
|
-
return next(e);
|
|
98
|
-
});
|
|
99
|
-
on('turn.abort', async ($, e, next) => {
|
|
100
|
-
await cancel($).catch(() => undefined);
|
|
101
|
-
return next(e);
|
|
102
|
-
});
|
|
103
|
-
on('turn.complete', async ($, e, next) => {
|
|
104
|
-
// Terminal Esc aborts are reported here, not through the plugin's turn.abort op.
|
|
105
|
-
if (!e.agentId && e.reason === 'aborted') await cancel($).catch(() => undefined);
|
|
106
|
-
return next(e);
|
|
107
|
-
});
|
|
108
|
-
on('session.receive', async ($, e, next) => {
|
|
109
|
-
if (e.agentId || e.origin?.kind !== 'peer' || !hasToken(e.text)) return next(e);
|
|
110
|
-
const c = await coordinate($).catch(() => undefined);
|
|
111
|
-
if (!c) return next(e);
|
|
112
|
-
const old = await snapshot($, c.sessionId).catch(() => undefined);
|
|
113
|
-
const cancelled = await cancellation($, c.sessionId).catch(() => undefined);
|
|
114
|
-
const guard = await bridge($, { ...c, cancelAt: cancelled?.at, cancelledBindings: cancelled?.bindings, bindings: old?.bindings, operation: 'guard', text: e.text }).catch(() => undefined);
|
|
115
|
-
if (guard?.recognized && !guard.current) return { consumed: 'Square delivery is stale or cancelled' };
|
|
116
|
-
// Preserve native policy: hold/refuse reject this gate; never call next twice.
|
|
117
|
-
const result = await next(e);
|
|
118
|
-
if (guard?.current && result.text === e.text) confirmLater($, c, 'admitted', result.text);
|
|
119
|
-
return result;
|
|
120
|
-
});
|
|
121
|
-
on('session.append', async ($, e, next) => {
|
|
122
|
-
if (e.agentId || e.origin?.kind !== 'peer') return next(e);
|
|
123
|
-
const c = await coordinate($).catch(() => undefined);
|
|
124
|
-
const result = await next(e);
|
|
125
|
-
if (!c || result.message?.role !== 'user') return result;
|
|
126
|
-
// Match a complete payload inside one returned stored text block, not entry text.
|
|
127
|
-
for (const block of result.message.content ?? []) {
|
|
128
|
-
if (block.type === 'text' && hasToken(block.text)) confirmLater($, c, 'stored', block.text);
|
|
129
|
-
}
|
|
130
|
-
return result;
|
|
131
|
-
});
|
|
132
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export type SquareInboxBinding = { location: string; participant: string; session: string; generation?: string };
|
|
2
|
-
export type SquareInboxCoordinate = { sessionId: string; cwd: string; version: string; endpoint?: string };
|
|
3
|
-
export type SquareInboxCancellation = { at: number; bindings?: SquareInboxBinding[] };
|
|
4
|
-
export type SquareInboxState = { coordinate: SquareInboxCoordinate; bindings?: SquareInboxBinding[] };
|
|
5
|
-
declare module 'claude-code' {
|
|
6
|
-
interface PluginState { square: { inbox: StateFamily<SquareInboxState>; cancelAt: StateFamily<SquareInboxCancellation> } }
|
|
7
|
-
}
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { squareWakeProjection } from '../../wake-preview.js';
|
|
3
|
-
import { channelArtifact, createArtifactPort } from '../../artifact.js';
|
|
4
|
-
import { deliverPending } from '../../delivery-operations.js';
|
|
5
|
-
import { nativePendingSource, observeSessionPending, projectSessionSeats } from '../../inbox.js';
|
|
6
|
-
import { automaticWakeEligible, nativePendingHead, presentationBlocksWake } from '../../delivery.js';
|
|
7
|
-
import { type Seat } from '../../model.js';
|
|
8
|
-
import { harnessOwner, decodeHarnessOwner } from '../../owner.js';
|
|
9
|
-
import type { WakeRequest, WakeTransportPort } from '../../ports.js';
|
|
10
|
-
import { formatMessageId, parseMessageId } from '../../message.js';
|
|
11
|
-
import { indexedSquareEntries } from '../../square-index.js';
|
|
12
|
-
import { asSeatRef } from '../../seats.js';
|
|
13
|
-
import { restoreSessionSeats } from '../../session-lifecycle.js';
|
|
14
|
-
import { presentPending } from '../../presentation-operations.js';
|
|
15
|
-
import { createNativeInputId, sendNativeText, type OpenCodeNativeSession } from '../../packages/agent-delivery/src/opencode-native.js';
|
|
16
|
-
|
|
17
|
-
const routeKind = 'opencode-server' as const;
|
|
18
|
-
const ownerFor = (sessionId: string) => harnessOwner({ channel: 'opencode', session: sessionId });
|
|
19
|
-
const payloadHash = (payload: string) => createHash('sha256').update(payload).digest('hex');
|
|
20
|
-
export interface OpenCodeContextMessage { readonly id?: string; readonly role: string; readonly content: readonly { readonly type: string; readonly text?: string }[] }
|
|
21
|
-
|
|
22
|
-
export async function indexedOpenCodeSessions(env: NodeJS.ProcessEnv): Promise<readonly string[]> {
|
|
23
|
-
return [...new Set((await indexedSquareEntries({ }, env)).flatMap((entry) => {
|
|
24
|
-
const coordinate = decodeHarnessOwner(entry.owner);
|
|
25
|
-
return coordinate?.channel === 'opencode' ? [coordinate.session] : [];
|
|
26
|
-
}))];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function discoverOpenCodeSeats(sessionId: string, env: NodeJS.ProcessEnv, signal?: AbortSignal) {
|
|
30
|
-
return projectSessionSeats({ owner: ownerFor(sessionId), env }, signal);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function sameSeat(left: Seat | undefined, right: Seat): boolean {
|
|
34
|
-
return left !== undefined && left.attachment === 'attached' && left.generation === right.generation && left.owner === right.owner;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function createOpenCodeWakeTransport(target: OpenCodeNativeSession, lifetime: AbortSignal): WakeTransportPort {
|
|
38
|
-
return {
|
|
39
|
-
probe: async (route) => !lifetime.aborted && route.kind === routeKind
|
|
40
|
-
&& route.address.sessionId === target.sessionId,
|
|
41
|
-
async attempt(request: WakeRequest, timeoutMs: number, beforeSend) {
|
|
42
|
-
const presentation = request.presentation;
|
|
43
|
-
if (request.claimToken === undefined || parseMessageId(request.message) === undefined || presentation === undefined
|
|
44
|
-
|| request.route.kind !== routeKind || request.route.address.sessionId !== target.sessionId) {
|
|
45
|
-
return { outcome: 'not-capable', diagnostic: 'OpenCode attention has no matching artifact claim or session.' };
|
|
46
|
-
}
|
|
47
|
-
const signal = AbortSignal.any([lifetime, AbortSignal.timeout(timeoutMs)]);
|
|
48
|
-
const started = Date.now();
|
|
49
|
-
if (signal.aborted || !await beforeSend())
|
|
50
|
-
return { outcome: 'not-capable', diagnostic: 'OpenCode attention changed before send.' };
|
|
51
|
-
const inputId = createNativeInputId();
|
|
52
|
-
const correlation = { harness: 'opencode' as const, token: inputId, payloadHash: payloadHash(presentation.payload) };
|
|
53
|
-
if (!await request.channel.prepare({ correlation,
|
|
54
|
-
coverage: presentation.coverage, ...(presentation.description === undefined ? {} : { description: presentation.description }) }, signal))
|
|
55
|
-
return { outcome: 'not-capable', diagnostic: 'OpenCode delivery claim is no longer current.' };
|
|
56
|
-
if (signal.aborted || !await beforeSend())
|
|
57
|
-
return { outcome: 'not-capable', diagnostic: 'OpenCode attention was consumed before send.' };
|
|
58
|
-
const result = await sendNativeText(target, presentation.payload, {
|
|
59
|
-
inputId, delivery: 'steer', timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)), signal,
|
|
60
|
-
});
|
|
61
|
-
if (result.state === 'accepted') return { outcome: 'accepted' };
|
|
62
|
-
if (result.state === 'unavailable') return { outcome: 'failed', signature: result.code };
|
|
63
|
-
return { outcome: 'unknown', signature: result.code };
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function textMessage(message: OpenCodeContextMessage): string | undefined {
|
|
69
|
-
if (message.role !== 'user' || message.id === undefined || !message.content.every((part) => part.type === 'text' && typeof part.text === 'string')) return undefined;
|
|
70
|
-
return message.content.map((part) => part.text!).join('');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** The exact prepared native message can authorize artifact-backed presentation once. */
|
|
74
|
-
export async function observeOpenCodeContext(sessionId: string, messages: readonly OpenCodeContextMessage[], env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
|
|
75
|
-
const observed = new Map(messages.flatMap((message) => {
|
|
76
|
-
const text = textMessage(message);
|
|
77
|
-
return text === undefined ? [] : [[message.id!, payloadHash(text)] as const];
|
|
78
|
-
}));
|
|
79
|
-
if (observed.size === 0 || signal.aborted) return;
|
|
80
|
-
for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
|
|
81
|
-
if (signal.aborted) return;
|
|
82
|
-
const artifact = createArtifactPort(location, undefined, signal);
|
|
83
|
-
try {
|
|
84
|
-
const { value: matches } = await artifact.read((facts) => {
|
|
85
|
-
if (!sameSeat(facts.seat(seat.participant), seat)) return [];
|
|
86
|
-
return facts.deliveryEvidence(seat.participant, seat.generation).flatMap((evidence) => {
|
|
87
|
-
const token = evidence.correlation?.harness === 'opencode' ? evidence.correlation.token : undefined;
|
|
88
|
-
const hash = token === undefined ? undefined : observed.get(token);
|
|
89
|
-
const index = parseMessageId(evidence.message), now = Date.now();
|
|
90
|
-
if (token === undefined || hash === undefined || evidence.claimToken === undefined || index === undefined
|
|
91
|
-
|| evidence.route === undefined || !['dispatching', 'unknown', 'accepted'].includes(evidence.outcome)
|
|
92
|
-
|| evidence.correlation?.payloadHash !== hash
|
|
93
|
-
|| evidence.route.kind !== routeKind
|
|
94
|
-
|| presentationBlocksWake(facts.deliveryEvidence(seat.participant, seat.generation, evidence.message), now)) return [];
|
|
95
|
-
const source = nativePendingSource(facts, { location, participant: seat.participant, generation: seat.generation,
|
|
96
|
-
owner: seat.owner, index, route: evidence.route, now });
|
|
97
|
-
if (source === undefined) return [];
|
|
98
|
-
return [{ evidence, index, hash, clipped: evidence.coverage === 'clipped' }];
|
|
99
|
-
});
|
|
100
|
-
}, signal);
|
|
101
|
-
for (const { evidence, index, hash, clipped } of matches) {
|
|
102
|
-
if (signal.aborted || evidence.claimToken === undefined) return;
|
|
103
|
-
await presentPending({ artifact, location, participant: seat.participant,
|
|
104
|
-
message: formatMessageId(index), seat: asSeatRef(seat), observationOf: evidence.claimToken, sink: { present() {} }, markSeen: !clipped, signal,
|
|
105
|
-
current: (facts) => {
|
|
106
|
-
const correlation = facts.deliveryByToken(evidence.claimToken!);
|
|
107
|
-
return nativePendingHead(facts, { location, participant: seat.participant, generation: seat.generation,
|
|
108
|
-
owner: seat.owner, index, route: evidence.route!, now: Date.now() }) !== undefined
|
|
109
|
-
&& ['dispatching', 'unknown', 'accepted'].includes(correlation?.outcome ?? '')
|
|
110
|
-
&& correlation?.correlation?.harness === 'opencode'
|
|
111
|
-
&& correlation.correlation.token !== undefined
|
|
112
|
-
&& correlation.correlation.payloadHash === hash;
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
} finally { await artifact.close(); }
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/** The native receiver owns no claims outside the artifact; HOME is used only for discovery. */
|
|
121
|
-
export async function receiveOpenCodePending(target: OpenCodeNativeSession, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
|
|
122
|
-
const observer = await observeSessionPending(ownerFor(target.sessionId), env);
|
|
123
|
-
const transport = createOpenCodeWakeTransport(target, signal);
|
|
124
|
-
let waiting: ReturnType<typeof observer.wait> | undefined;
|
|
125
|
-
try {
|
|
126
|
-
while (!signal.aborted) {
|
|
127
|
-
const armed = new Promise<boolean>((resolve) => {
|
|
128
|
-
waiting = observer.wait(Infinity, { signal, skipImmediate: true, onChangeArmed: resolve });
|
|
129
|
-
void waiting.catch(() => resolve(false));
|
|
130
|
-
});
|
|
131
|
-
if (!await armed) { await waiting; break; }
|
|
132
|
-
for (const membership of await observer.read({ signal })) {
|
|
133
|
-
const { location, seat } = membership;
|
|
134
|
-
if (signal.aborted) return;
|
|
135
|
-
const artifact = createArtifactPort(location, undefined, signal);
|
|
136
|
-
try {
|
|
137
|
-
const { value: notifications } = await artifact.read((facts) => {
|
|
138
|
-
if (!sameSeat(facts.seat(seat.participant), seat)) return [];
|
|
139
|
-
return membership.notifications.filter((note) => automaticWakeEligible(
|
|
140
|
-
facts.deliveryEvidence(seat.participant, seat.generation, formatMessageId(note.messageIndex)), Date.now()));
|
|
141
|
-
}, signal);
|
|
142
|
-
for (const note of notifications) {
|
|
143
|
-
if (signal.aborted) return;
|
|
144
|
-
await deliverPending({ artifact: channelArtifact(artifact), preparePresentation: squareWakeProjection(artifact), transport, location, message: formatMessageId(note.messageIndex), now: Date.now() });
|
|
145
|
-
}
|
|
146
|
-
} catch (error) {
|
|
147
|
-
if (signal.aborted) throw signal.reason ?? error;
|
|
148
|
-
// A candidate that became unreadable cannot authorize sending; the shared observer
|
|
149
|
-
// keeps its subscription and will reconsider the artifact after repair.
|
|
150
|
-
} finally { await artifact.close(); }
|
|
151
|
-
}
|
|
152
|
-
await waiting;
|
|
153
|
-
waiting = undefined;
|
|
154
|
-
}
|
|
155
|
-
} catch (error) {
|
|
156
|
-
if (!signal.aborted) throw error;
|
|
157
|
-
} finally {
|
|
158
|
-
observer.close();
|
|
159
|
-
await waiting?.catch(() => {});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Publish the native address into current artifact seats; the legacy route projection is not read. */
|
|
164
|
-
export async function publishOpenCodeAddress(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<boolean> {
|
|
165
|
-
await restoreSessionSeats(ownerFor(sessionId), env, { kind: routeKind, address: { sessionId } }, signal);
|
|
166
|
-
const seats = (await discoverOpenCodeSeats(sessionId, env, signal)).filter(({ seat }) => seat.attachment === 'attached');
|
|
167
|
-
for (const { location, seat } of seats) {
|
|
168
|
-
if (signal.aborted) return false;
|
|
169
|
-
const artifact = createArtifactPort(location, undefined, signal);
|
|
170
|
-
try {
|
|
171
|
-
await artifact.transactAs({ kind: 'participant', seat: asSeatRef(seat) }, (facts) =>
|
|
172
|
-
facts.updateWakeAddress(asSeatRef(seat), { kind: routeKind, address: { sessionId } }));
|
|
173
|
-
} finally { await artifact.close(); }
|
|
174
|
-
}
|
|
175
|
-
return seats.length > 0;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export async function cancelOpenCodeDeliveries(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
|
|
179
|
-
for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
|
|
180
|
-
if (signal.aborted) return;
|
|
181
|
-
if (seat.attachment !== 'attached') continue;
|
|
182
|
-
const artifact = createArtifactPort(location, undefined, signal);
|
|
183
|
-
try {
|
|
184
|
-
const { value: through } = await artifact.read((facts) => facts.channel.header().lastMessageIndex, signal);
|
|
185
|
-
if (through >= 0) await artifact.cancelDeliveries(asSeatRef(seat), through, signal);
|
|
186
|
-
} finally { await artifact.close(); }
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export async function endOpenCodeSession(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void> {
|
|
191
|
-
for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
|
|
192
|
-
if (signal.aborted) return;
|
|
193
|
-
const { Square } = await import('../../square-api.js');
|
|
194
|
-
const square = await Square.at({ path: location, env });
|
|
195
|
-
try { await square.endHeld(seat.participant, { owner: seat.owner, generation: seat.generation }); }
|
|
196
|
-
finally { await square.close(); }
|
|
197
|
-
}
|
|
198
|
-
}
|
|
File without changes
|
|
File without changes
|