@astrosheep/square 0.3.86 → 0.3.88
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 +14 -10
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.js +1 -0
- 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 +8 -11
- package/dist/inbox.d.ts +35 -9
- package/dist/inbox.js +228 -144
- package/dist/index.d.ts +3 -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 +7 -4
- 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.js +8 -3
- 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 +20 -9
- package/dist/sqlite-square.js +476 -341
- package/dist/square-api.d.ts +1 -0
- package/dist/square-api.js +3 -4
- 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-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 +1 -1
- package/dist/square-stdio.js +12 -7
- package/dist/square-tools.d.ts +1 -1
- package/dist/square-tools.js +80 -27
- package/dist/stdio-server.js +4 -2
- package/dist/stream.d.ts +3 -2
- package/dist/stream.js +9 -10
- package/dist/views.d.ts +9 -14
- package/dist/views.js +52 -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/claude-native.md +9 -9
- package/guides/mcp.md +2 -2
- package/guides/migration.md +107 -67
- package/guides/pi-tools.md +1 -1
- package/guides/row-storage-design.md +1 -1
- package/guides/square-api.md +61 -28
- 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 +4 -4
- 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 +56 -18
- package/skills/square/SKILL.md +4 -4
- 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/host-ledger.js +0 -1
- package/dist/ledger-maintenance.d.ts +0 -30
- package/dist/ledger-maintenance.js +0 -110
- package/dist/open-square.d.ts +0 -38
- package/dist/open-square.js +0 -81
- package/dist/participant-host.d.ts +0 -23
- 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
|
@@ -1,60 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { hostLedgerForEnv } from '../../registry.js';
|
|
4
|
-
import { openSquare } from '../../open-square.js';
|
|
5
|
-
import { closeOpenSquare } from '../../open-square.js';
|
|
6
|
-
import { presentationSuppressesWake, projectPresentationEvidence } from '../../square-projections.js';
|
|
7
|
-
import { presentPending } from '../../presentation-operations.js';
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createArtifactPort } from '../../artifact.js';
|
|
8
3
|
import { deliverPending } from '../../delivery-operations.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
4
|
+
import { nativePendingPreview, observeSessionPending, projectSessionSeats } from '../../inbox.js';
|
|
5
|
+
import { automaticWakeEligible, presentationBlocksWake } from '../../delivery.js';
|
|
6
|
+
import { sameBody } from '../../model.js';
|
|
11
7
|
import { formatActivityId, parseActivityId } from '../../square-core.js';
|
|
8
|
+
import { indexedSquareEntries } from '../../square-index.js';
|
|
9
|
+
import { asSeatRef } from '../../seats.js';
|
|
10
|
+
import { presentPending } from '../../presentation-operations.js';
|
|
12
11
|
import { createNativeInputId, sendNativeText } from '../../packages/agent-delivery/src/opencode-native.js';
|
|
13
12
|
const routeKind = 'opencode-server';
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
function preview(facts,
|
|
17
|
-
return nativePendingPreview(facts, {
|
|
13
|
+
const bodyFor = (sessionId) => ({ channel: 'opencode', session: sessionId });
|
|
14
|
+
const payloadHash = (payload) => createHash('sha256').update(payload).digest('hex');
|
|
15
|
+
function preview(facts, location, seat, index) {
|
|
16
|
+
return nativePendingPreview(facts, {
|
|
17
|
+
location, routeKind, sessionId: seat.body.session, participant: seat.participant,
|
|
18
|
+
actIndex: index, address: { sessionId: seat.body.session }, now: Date.now(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export async function indexedOpenCodeSessions(env) {
|
|
22
|
+
return [...new Set((await indexedSquareEntries({ channel: 'opencode' }, env)).map((entry) => entry.body.session))];
|
|
23
|
+
}
|
|
24
|
+
export async function discoverOpenCodeSeats(sessionId, env, signal) {
|
|
25
|
+
return projectSessionSeats({ body: bodyFor(sessionId), env }, signal);
|
|
26
|
+
}
|
|
27
|
+
function sameSeat(left, right) {
|
|
28
|
+
return left !== undefined && left.generation === right.generation && sameBody(left.body, right.body);
|
|
18
29
|
}
|
|
19
|
-
function createOpenCodeWakeTransport(target,
|
|
20
|
-
const ledger = hostLedgerForEnv(env);
|
|
30
|
+
function createOpenCodeWakeTransport(target, lifetime) {
|
|
21
31
|
return {
|
|
22
|
-
probe: async (route) => !lifetime.aborted && route.kind === routeKind
|
|
32
|
+
probe: async (route) => !lifetime.aborted && route.kind === routeKind
|
|
33
|
+
&& route.sessionId === target.sessionId && route.address.sessionId === target.sessionId,
|
|
23
34
|
async attempt(request, timeoutMs, beforeSend) {
|
|
35
|
+
const index = parseActivityId(request.activity);
|
|
36
|
+
const artifact = request.artifact;
|
|
37
|
+
if (artifact === undefined || request.claimToken === undefined || index === undefined
|
|
38
|
+
|| !sameBody(request.seat.body, bodyFor(target.sessionId))
|
|
39
|
+
|| request.route.kind !== routeKind || request.route.address.sessionId !== target.sessionId) {
|
|
40
|
+
return { outcome: 'not-capable', diagnostic: 'OpenCode attention has no matching artifact claim or session.' };
|
|
41
|
+
}
|
|
24
42
|
const signal = AbortSignal.any([lifetime, AbortSignal.timeout(timeoutMs)]);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
activity: request.activity, kind: 'wake', outcome: 'dispatching', routeKind, attemptN: request.attemptN, claimToken: request.claimToken,
|
|
44
|
-
nativeDelivery: { harness: 'opencode', epoch: owner.epoch ?? 0, inputId: createNativeInputId(), payload: prepared.payload } };
|
|
45
|
-
if (!await ledger.prepareNativeWake(row) || !await (beforeSend?.() ?? Promise.resolve(true)) || signal.aborted
|
|
46
|
-
|| (await square.artifact.read((facts) => preview(facts, owner, index)?.payload, signal)).value !== prepared.payload)
|
|
47
|
-
return undefined;
|
|
48
|
-
return { sending: sendNativeText(target, prepared.payload, { inputId: row.nativeDelivery.inputId, delivery: 'steer',
|
|
49
|
-
timeoutMs: Math.max(1, deadline - Date.now()), signal }) };
|
|
50
|
-
}
|
|
51
|
-
finally {
|
|
52
|
-
await closeOpenSquare(square);
|
|
53
|
-
}
|
|
54
|
-
}, signal);
|
|
55
|
-
if (!started)
|
|
56
|
-
return { outcome: 'not-capable', diagnostic: 'OpenCode attention, binding or local delivery generation changed before send.' };
|
|
57
|
-
const result = await started.sending;
|
|
43
|
+
const started = Date.now();
|
|
44
|
+
if (signal.aborted || !await beforeSend())
|
|
45
|
+
return { outcome: 'not-capable', diagnostic: 'OpenCode attention changed before send.' };
|
|
46
|
+
const { value: prepared } = await artifact.read((facts) => preview(facts, request.location, request.seat, index), signal);
|
|
47
|
+
if (prepared === undefined)
|
|
48
|
+
return { outcome: 'not-capable', diagnostic: 'OpenCode attention is no longer current.' };
|
|
49
|
+
const inputId = createNativeInputId();
|
|
50
|
+
const correlation = { harness: 'opencode', token: inputId, payloadHash: payloadHash(prepared.payload) };
|
|
51
|
+
if (!await artifact.prepareDelivery({ seat: request.seat, claimToken: request.claimToken, correlation }, signal))
|
|
52
|
+
return { outcome: 'not-capable', diagnostic: 'OpenCode delivery claim is no longer current.' };
|
|
53
|
+
const fresh = await artifact.read((facts) => preview(facts, request.location, request.seat, index), signal);
|
|
54
|
+
if (fresh.value === undefined || payloadHash(fresh.value.payload) !== correlation.payloadHash)
|
|
55
|
+
return { outcome: 'not-capable', diagnostic: 'OpenCode attention changed before native send.' };
|
|
56
|
+
if (signal.aborted || !await beforeSend())
|
|
57
|
+
return { outcome: 'not-capable', diagnostic: 'OpenCode attention was consumed before send.' };
|
|
58
|
+
const result = await sendNativeText(target, prepared.payload, {
|
|
59
|
+
inputId, delivery: 'steer', timeoutMs: Math.max(1, timeoutMs - (Date.now() - started)), signal,
|
|
60
|
+
});
|
|
58
61
|
if (result.state === 'accepted')
|
|
59
62
|
return { outcome: 'accepted' };
|
|
60
63
|
if (result.state === 'unavailable')
|
|
@@ -63,109 +66,161 @@ function createOpenCodeWakeTransport(target, env, lifetime) {
|
|
|
63
66
|
},
|
|
64
67
|
};
|
|
65
68
|
}
|
|
66
|
-
|
|
69
|
+
function textMessage(message) {
|
|
70
|
+
if (message.role !== 'user' || message.id === undefined || !message.content.every((part) => part.type === 'text' && typeof part.text === 'string'))
|
|
71
|
+
return undefined;
|
|
72
|
+
return message.content.map((part) => part.text).join('');
|
|
73
|
+
}
|
|
74
|
+
/** The exact prepared native message can authorize artifact-backed presentation once. */
|
|
67
75
|
export async function observeOpenCodeContext(sessionId, messages, env, signal) {
|
|
68
|
-
const
|
|
69
|
-
|
|
76
|
+
const observed = new Map(messages.flatMap((message) => {
|
|
77
|
+
const text = textMessage(message);
|
|
78
|
+
return text === undefined ? [] : [[message.id, payloadHash(text)]];
|
|
79
|
+
}));
|
|
80
|
+
if (observed.size === 0 || signal.aborted)
|
|
81
|
+
return;
|
|
82
|
+
for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
|
|
70
83
|
if (signal.aborted)
|
|
71
84
|
return;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
86
|
+
try {
|
|
87
|
+
const { value: matches } = await artifact.read((facts) => {
|
|
88
|
+
if (!sameSeat(facts.seat(seat.participant), seat))
|
|
89
|
+
return [];
|
|
90
|
+
return facts.deliveryEvidence(seat.participant, seat.generation).flatMap((evidence) => {
|
|
91
|
+
const token = evidence.correlation?.harness === 'opencode' ? evidence.correlation.token : undefined;
|
|
92
|
+
const hash = token === undefined ? undefined : observed.get(token);
|
|
93
|
+
const index = parseActivityId(evidence.activity);
|
|
94
|
+
if (token === undefined || hash === undefined || evidence.claimToken === undefined || index === undefined
|
|
95
|
+
|| !['dispatching', 'unknown', 'accepted'].includes(evidence.outcome)
|
|
96
|
+
|| evidence.correlation?.payloadHash !== hash)
|
|
97
|
+
return [];
|
|
98
|
+
const prepared = preview(facts, location, seat, index);
|
|
99
|
+
if (prepared === undefined || payloadHash(prepared.payload) !== hash
|
|
100
|
+
|| presentationBlocksWake(facts.deliveryEvidence(seat.participant, seat.generation, evidence.activity), Date.now()))
|
|
101
|
+
return [];
|
|
102
|
+
return [{ evidence, index, hash, clipped: prepared.clipped }];
|
|
103
|
+
});
|
|
104
|
+
}, signal);
|
|
105
|
+
for (const { evidence, index, hash, clipped } of matches) {
|
|
106
|
+
if (signal.aborted || evidence.claimToken === undefined)
|
|
107
|
+
return;
|
|
108
|
+
await presentPending({ artifact, location, participant: seat.participant,
|
|
109
|
+
activity: formatActivityId(index), seat: asSeatRef(seat), sink: { present() { } }, markSeen: !clipped, signal,
|
|
110
|
+
current: (facts) => {
|
|
111
|
+
const current = facts.seat(seat.participant);
|
|
112
|
+
const prepared = current === undefined || !sameSeat(current, seat) ? undefined : preview(facts, location, seat, index);
|
|
113
|
+
const correlation = facts.deliveryByToken(evidence.claimToken);
|
|
114
|
+
return prepared !== undefined && payloadHash(prepared.payload) === hash
|
|
115
|
+
&& correlation?.correlation?.harness === 'opencode'
|
|
116
|
+
&& correlation.correlation.token !== undefined
|
|
117
|
+
&& correlation.correlation.payloadHash === hash;
|
|
118
|
+
},
|
|
119
|
+
});
|
|
97
120
|
}
|
|
98
121
|
}
|
|
99
|
-
|
|
122
|
+
finally {
|
|
123
|
+
await artifact.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
100
126
|
}
|
|
101
|
-
/**
|
|
127
|
+
/** The native receiver owns no claims outside the artifact; HOME is used only for discovery. */
|
|
102
128
|
export async function receiveOpenCodePending(target, env, signal) {
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const refreshExclusions = async () => {
|
|
107
|
-
excluded.clear();
|
|
108
|
-
const owners = await ledger.listPresence({ session: target.sessionId });
|
|
109
|
-
for (const row of await ledger.listEvidence({ kind: 'wake', session: target.sessionId })) {
|
|
110
|
-
const index = parseActivityId(row.activity);
|
|
111
|
-
const current = owners.some((owner) => owner.location === row.location && nameKey(owner.participant) === nameKey(row.participant) && owner.epoch === row.nativeDelivery?.epoch);
|
|
112
|
-
if (index !== undefined && (['accepted', 'unknown'].includes(row.outcome) || row.outcome === 'dispatching' && current && row.nativeDelivery?.harness === 'opencode'))
|
|
113
|
-
excluded.add(key(row.location, row.participant, index));
|
|
114
|
-
}
|
|
115
|
-
for (const membership of await sessionInbox(target.sessionId, env, signal)) {
|
|
116
|
-
const owner = owners.find((binding) => binding.location === membership.squarePath && nameKey(binding.participant) === nameKey(membership.name));
|
|
117
|
-
for (const note of membership.notifications)
|
|
118
|
-
if (note.actIndex <= (owner?.cancelledThrough ?? -1))
|
|
119
|
-
excluded.add(key(membership.squarePath, membership.name, note.actIndex));
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
const transport = createWakeTransport([], ledger, Date.now, { [routeKind]: createOpenCodeWakeTransport(target, env, signal) });
|
|
129
|
+
const observer = await observeSessionPending(bodyFor(target.sessionId), env);
|
|
130
|
+
const transport = createOpenCodeWakeTransport(target, signal);
|
|
131
|
+
let waiting;
|
|
123
132
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.catch(() => { armed(false); return []; });
|
|
132
|
-
if (!await ready || signal.aborted)
|
|
133
|
+
while (!signal.aborted) {
|
|
134
|
+
const armed = new Promise((resolve) => {
|
|
135
|
+
waiting = observer.wait(Infinity, { signal, skipImmediate: true, onChangeArmed: resolve });
|
|
136
|
+
void waiting.catch(() => resolve(false));
|
|
137
|
+
});
|
|
138
|
+
if (!await armed) {
|
|
139
|
+
await waiting;
|
|
133
140
|
break;
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
}
|
|
142
|
+
for (const membership of await observer.read({ signal })) {
|
|
143
|
+
const { location, seat } = membership;
|
|
144
|
+
if (signal.aborted)
|
|
145
|
+
return;
|
|
146
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
136
147
|
try {
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
const { value: notifications } = await artifact.read((facts) => {
|
|
149
|
+
if (!sameSeat(facts.seat(seat.participant), seat))
|
|
150
|
+
return [];
|
|
151
|
+
return membership.notifications.filter((note) => automaticWakeEligible(facts.deliveryEvidence(seat.participant, seat.generation, formatActivityId(note.actIndex)), Date.now()));
|
|
152
|
+
}, signal);
|
|
153
|
+
for (const note of notifications) {
|
|
154
|
+
if (signal.aborted)
|
|
155
|
+
return;
|
|
156
|
+
await deliverPending({ artifact, transport, location, activity: formatActivityId(note.actIndex), now: Date.now() });
|
|
141
157
|
}
|
|
142
158
|
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (signal.aborted)
|
|
161
|
+
throw signal.reason ?? error;
|
|
162
|
+
// A candidate that became unreadable cannot authorize sending; the shared observer
|
|
163
|
+
// keeps its subscription and will reconsider the artifact after repair.
|
|
164
|
+
}
|
|
143
165
|
finally {
|
|
144
|
-
await
|
|
166
|
+
await artifact.close();
|
|
145
167
|
}
|
|
146
168
|
}
|
|
147
|
-
await
|
|
148
|
-
|
|
169
|
+
await waiting;
|
|
170
|
+
waiting = undefined;
|
|
149
171
|
}
|
|
150
172
|
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (!signal.aborted)
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
151
177
|
finally {
|
|
152
178
|
observer.close();
|
|
179
|
+
await waiting?.catch(() => { });
|
|
153
180
|
}
|
|
154
181
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
finally {
|
|
166
|
-
await closeOpenSquare(square);
|
|
167
|
-
}
|
|
182
|
+
/** Publish the native address into current artifact seats; the legacy route projection is not read. */
|
|
183
|
+
export async function publishOpenCodeAddress(sessionId, env, signal) {
|
|
184
|
+
const seats = await discoverOpenCodeSeats(sessionId, env, signal);
|
|
185
|
+
for (const { location, seat } of seats) {
|
|
186
|
+
if (signal.aborted)
|
|
187
|
+
return false;
|
|
188
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
189
|
+
try {
|
|
190
|
+
await artifact.transactAs({ kind: 'participant', seat: asSeatRef(seat) }, (facts) => facts.updateWakeAddress(asSeatRef(seat), { kind: routeKind, address: { sessionId } }));
|
|
168
191
|
}
|
|
169
|
-
|
|
170
|
-
|
|
192
|
+
finally {
|
|
193
|
+
await artifact.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return seats.length > 0;
|
|
197
|
+
}
|
|
198
|
+
export async function cancelOpenCodeDeliveries(sessionId, env, signal) {
|
|
199
|
+
for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
|
|
200
|
+
if (signal.aborted)
|
|
201
|
+
return;
|
|
202
|
+
const artifact = createArtifactPort(location, undefined, signal);
|
|
203
|
+
try {
|
|
204
|
+
const { value: through } = await artifact.read((facts) => facts.header().lastActIndex, signal);
|
|
205
|
+
if (through >= 0)
|
|
206
|
+
await artifact.cancelDeliveries(asSeatRef(seat), through, signal);
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
await artifact.close();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export async function endOpenCodeSession(sessionId, env, signal) {
|
|
214
|
+
for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
|
|
215
|
+
if (signal.aborted)
|
|
216
|
+
return;
|
|
217
|
+
const { Square } = await import('../../square-api.js');
|
|
218
|
+
const square = await Square.at({ path: location, env, body: seat.body, participant: seat.participant });
|
|
219
|
+
try {
|
|
220
|
+
await square.endOwnedSession(seat.participant, sessionId, seat.generation, seat.body);
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
await square.close();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
171
226
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { hostLedgerForEnv } from '../../registry.js';
|
|
1
|
+
import { observeSquareIndex } from '../../square-index.js';
|
|
3
2
|
import { canonicalPath } from '../../canonical-path.js';
|
|
4
|
-
import { openSquare } from '../../open-square.js';
|
|
5
|
-
import { closeOpenSquare } from '../../open-square.js';
|
|
6
|
-
import { Square } from '../../square-api.js';
|
|
7
3
|
import { connectNative } from '../../packages/agent-delivery/src/opencode-native.js';
|
|
8
|
-
import { observeOpenCodeContext,
|
|
4
|
+
import { cancelOpenCodeDeliveries, endOpenCodeSession, indexedOpenCodeSessions, observeOpenCodeContext, publishOpenCodeAddress, receiveOpenCodePending } from './delivery.js';
|
|
9
5
|
/** Actual OpenCode 2.0.20 server plugin. Harness identity belongs to ctx, never process.env. */
|
|
10
6
|
export default {
|
|
11
7
|
id: 'square',
|
|
@@ -13,7 +9,7 @@ export default {
|
|
|
13
9
|
if (ctx.app.version !== '2.0.20')
|
|
14
10
|
return async () => { };
|
|
15
11
|
const env = { ...process.env };
|
|
16
|
-
const
|
|
12
|
+
const index = await observeSquareIndex(env);
|
|
17
13
|
const lifetime = new AbortController();
|
|
18
14
|
const receivers = new Map(); // Operational handles only; presence owns membership.
|
|
19
15
|
const directory = await canonicalPath(ctx.location.directory);
|
|
@@ -43,7 +39,7 @@ export default {
|
|
|
43
39
|
return target;
|
|
44
40
|
};
|
|
45
41
|
const reconcile = async (activeSession) => {
|
|
46
|
-
const candidates = new Set(
|
|
42
|
+
const candidates = new Set(await indexedOpenCodeSessions(env));
|
|
47
43
|
if (activeSession)
|
|
48
44
|
candidates.add(activeSession);
|
|
49
45
|
for (const sessionId of candidates) {
|
|
@@ -54,8 +50,7 @@ export default {
|
|
|
54
50
|
stop(sessionId);
|
|
55
51
|
continue;
|
|
56
52
|
}
|
|
57
|
-
|
|
58
|
-
if (!await publishOpenCodeRoutes(sessionId, scopedEnv, lifetime.signal)) {
|
|
53
|
+
if (!await publishOpenCodeAddress(sessionId, env, lifetime.signal)) {
|
|
59
54
|
stop(sessionId);
|
|
60
55
|
continue;
|
|
61
56
|
}
|
|
@@ -63,7 +58,7 @@ export default {
|
|
|
63
58
|
continue;
|
|
64
59
|
const abort = new AbortController();
|
|
65
60
|
const signal = AbortSignal.any([lifetime.signal, abort.signal]);
|
|
66
|
-
const receiver = { abort, settled: false, running: receiveOpenCodePending(target,
|
|
61
|
+
const receiver = { abort, settled: false, running: receiveOpenCodePending(target, env, signal).catch(() => { }).finally(() => { receiver.settled = true; }) };
|
|
67
62
|
receivers.set(sessionId, receiver);
|
|
68
63
|
}
|
|
69
64
|
for (const sessionId of receivers.keys())
|
|
@@ -75,31 +70,12 @@ export default {
|
|
|
75
70
|
return;
|
|
76
71
|
// Stop synchronously; persist the cutoff before allowing reconciliation to start fresh work.
|
|
77
72
|
stop(sessionId);
|
|
78
|
-
await
|
|
79
|
-
for (const owner of (await ledger.listPresence({ session: sessionId })).filter((binding) => binding.channel === 'opencode')) {
|
|
80
|
-
const square = await openSquare(owner.location, { hostLedger: ledger, env, signal: lifetime.signal });
|
|
81
|
-
try {
|
|
82
|
-
await ledger.suppressPresence(owner, (await square.artifact.read((facts) => facts.header().nextActIndex - 1, lifetime.signal)).value);
|
|
83
|
-
}
|
|
84
|
-
finally {
|
|
85
|
-
await closeOpenSquare(square);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}, lifetime.signal);
|
|
73
|
+
await cancelOpenCodeDeliveries(sessionId, env, lifetime.signal);
|
|
89
74
|
await reconcile();
|
|
90
75
|
};
|
|
91
76
|
const end = async (sessionId) => {
|
|
92
77
|
stop(sessionId);
|
|
93
|
-
|
|
94
|
-
for (const owner of memberships) {
|
|
95
|
-
const square = await Square.at({ path: owner.location, hostLedger: ledger, env: operationEnv('opencode', sessionId, env) });
|
|
96
|
-
try {
|
|
97
|
-
await square.endOwnedSession(owner.participant, sessionId, owner.epoch);
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
await square.close();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
78
|
+
await endOpenCodeSession(sessionId, env, lifetime.signal);
|
|
103
79
|
};
|
|
104
80
|
await ctx.session.hook('prompt', (event) => coordinate(() => reconcile(event.sessionID)));
|
|
105
81
|
await ctx.session.hook('context', async (event) => {
|
|
@@ -107,15 +83,13 @@ export default {
|
|
|
107
83
|
const receiver = receivers.get(event.sessionID);
|
|
108
84
|
if (!receiver || lifetime.signal.aborted)
|
|
109
85
|
return;
|
|
110
|
-
await observeOpenCodeContext(event.sessionID, event.messages,
|
|
86
|
+
await observeOpenCodeContext(event.sessionID, event.messages, env, AbortSignal.any([lifetime.signal, receiver.abort.signal])).catch(() => { });
|
|
111
87
|
});
|
|
112
|
-
const changes = await ledger.observeChanges();
|
|
113
|
-
changes.evidence.close(); // Manual joins activate an idle receiver; evidence already has a session observer.
|
|
114
88
|
const presenceLoop = (async () => {
|
|
115
89
|
while (!lifetime.signal.aborted) {
|
|
116
|
-
const baseline = await
|
|
90
|
+
const baseline = await index.read(lifetime.signal);
|
|
117
91
|
await coordinate(() => reconcile());
|
|
118
|
-
await
|
|
92
|
+
await index.changed(baseline, Infinity, lifetime.signal);
|
|
119
93
|
}
|
|
120
94
|
})().catch(() => { });
|
|
121
95
|
const eventLoop = (async () => {
|
|
@@ -136,7 +110,7 @@ export default {
|
|
|
136
110
|
// Do not await any native prompt in setup: plugin activation itself is lazy.
|
|
137
111
|
return async () => {
|
|
138
112
|
lifetime.abort();
|
|
139
|
-
|
|
113
|
+
index.close();
|
|
140
114
|
const tasks = [presenceLoop, eventLoop, coordinating, ...[...receivers.keys()].map(stop)];
|
|
141
115
|
let timer;
|
|
142
116
|
try {
|