@astrosheep/square 0.3.63 → 0.3.64
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/claude-plugin/.claude-plugin/plugin.json +3 -2
- package/claude-plugin/hooks/hooks.json +1 -22
- package/claude-plugin/hooks/register.js +119 -0
- package/claude-plugin/types.d.ts +7 -0
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity-feed.d.ts +1 -10
- package/dist/activity-feed.js +1 -27
- package/dist/activity.js +57 -25
- package/dist/artifact.js +12 -24
- package/dist/attention-presentation.js +1 -0
- package/dist/automatic-session.d.ts +3 -1
- package/dist/automatic-session.js +21 -34
- package/dist/boundary-presentation.js +4 -13
- package/dist/canonical-path.d.ts +11 -0
- package/dist/canonical-path.js +47 -0
- package/dist/catch-decisions.js +14 -10
- package/dist/catch-selection.d.ts +6 -0
- package/dist/catch-selection.js +14 -0
- package/dist/claude-delivery.d.ts +11 -0
- package/dist/claude-delivery.js +145 -0
- package/dist/claude-mod.d.ts +8 -0
- package/dist/claude-mod.js +164 -0
- package/dist/cli/context.d.ts +0 -1
- package/dist/cli/context.js +9 -21
- package/dist/cli/harness-command.d.ts +0 -1
- package/dist/cli/harness-command.js +0 -6
- package/dist/cli/observation-commands.d.ts +1 -1
- package/dist/cli/observation-commands.js +31 -58
- package/dist/cli/program.js +20 -18
- package/dist/cli/registry.js +2 -2
- package/dist/cli/square-commands.js +10 -6
- package/dist/codex-hook.js +9 -12
- package/dist/codex-queue.d.ts +2 -2
- package/dist/codex-queue.js +7 -5
- package/dist/decisions.d.ts +2 -6
- package/dist/decisions.js +16 -17
- package/dist/delivery-health.d.ts +1 -2
- package/dist/delivery-health.js +4 -5
- package/dist/delivery-operations.d.ts +2 -5
- package/dist/delivery-operations.js +77 -214
- package/dist/delivery.d.ts +25 -47
- package/dist/delivery.js +14 -46
- package/dist/diagnostic-redaction.js +7 -9
- package/dist/file-changes.d.ts +0 -1
- package/dist/file-changes.js +2 -19
- package/dist/harness-claude.js +8 -6
- package/dist/harness-codex.d.ts +0 -2
- package/dist/harness-codex.js +0 -2
- package/dist/harness-links.js +13 -17
- package/dist/harness.js +3 -7
- package/dist/help.js +4 -3
- package/dist/host-ledger-file-adapter.d.ts +17 -8
- package/dist/host-ledger-file-adapter.js +164 -80
- package/dist/host-ledger.d.ts +55 -50
- package/dist/inbox.d.ts +0 -3
- package/dist/inbox.js +13 -30
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.js +1 -4
- package/dist/mcp.js +18 -40
- package/dist/mention-parse.js +3 -9
- package/dist/model.d.ts +11 -2
- package/dist/model.js +1 -1
- package/dist/notifications.d.ts +3 -16
- package/dist/notifications.js +41 -122
- package/dist/opencode-delivery.d.ts +14 -0
- package/dist/opencode-delivery.js +171 -0
- package/dist/opencode.d.ts +39 -18
- package/dist/opencode.js +145 -40
- package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
- package/dist/packages/agent-delivery/src/claude-auth.js +70 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
- package/dist/packages/agent-delivery/src/claude-native.js +65 -0
- package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
- package/dist/packages/agent-delivery/src/claude.js +66 -0
- package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
- package/dist/packages/agent-delivery/src/deadline.js +41 -0
- package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
- package/dist/packages/agent-delivery/src/errors.js +41 -0
- package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
- package/dist/packages/agent-delivery/src/guards.js +6 -0
- package/dist/packages/agent-delivery/src/index.d.ts +83 -0
- package/dist/packages/agent-delivery/src/index.js +110 -0
- package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
- package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
- package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
- package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
- package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
- package/dist/packages/agent-delivery/src/opencode.js +127 -0
- package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
- package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
- package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
- package/dist/packages/agent-delivery/src/paseo.js +106 -0
- package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
- package/dist/packages/agent-delivery/src/pi-client.js +106 -0
- package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
- package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
- package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
- package/dist/packages/agent-delivery/src/pi-send.js +4 -0
- package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
- package/dist/packages/agent-delivery/src/pi.js +209 -0
- package/dist/packages/agent-delivery/src/types.d.ts +120 -0
- package/dist/participant-host.js +12 -13
- package/dist/participant-identity.d.ts +21 -0
- package/dist/participant-identity.js +32 -11
- package/dist/paseo-delivery.d.ts +2 -2
- package/dist/paseo-delivery.js +35 -27
- package/dist/paseo-timeline.js +25 -21
- package/dist/paseo.d.ts +2 -2
- package/dist/paseo.js +1 -2
- package/dist/ports.d.ts +7 -20
- package/dist/presentation-operations.js +16 -4
- package/dist/presentation.d.ts +19 -10
- package/dist/presentation.js +99 -177
- package/dist/registry.d.ts +15 -66
- package/dist/registry.js +45 -123
- package/dist/routes.d.ts +1 -28
- package/dist/routes.js +22 -104
- package/dist/runtime.d.ts +3 -9
- package/dist/runtime.js +14 -67
- package/dist/search.d.ts +0 -2
- package/dist/search.js +3 -10
- package/dist/square-actions.js +48 -72
- package/dist/square-application.js +25 -23
- package/dist/square-core.d.ts +3 -0
- package/dist/square-core.js +22 -5
- package/dist/square-facade.d.ts +2 -0
- package/dist/square-file-adapter.js +4 -7
- package/dist/square-projections.d.ts +40 -45
- package/dist/square-projections.js +43 -73
- package/dist/square-state.js +1 -4
- package/dist/square-storage.d.ts +5 -8
- package/dist/square-storage.js +13 -36
- package/dist/square-wiring.d.ts +0 -5
- package/dist/square-wiring.js +3 -21
- package/dist/stream.js +4 -6
- package/dist/time.js +2 -7
- package/dist/views.d.ts +4 -16
- package/dist/views.js +12 -19
- package/dist/wake-attempts.d.ts +2 -56
- package/dist/wake-attempts.js +15 -110
- package/dist/wake-eligibility.d.ts +43 -0
- package/dist/wake-eligibility.js +106 -0
- package/dist/wake-evidence.d.ts +32 -9
- package/dist/wake-evidence.js +29 -24
- package/dist/wake-sink.d.ts +13 -9
- package/dist/wake-sink.js +35 -54
- package/dist/watch.js +13 -21
- package/extensions/square-pi.js +10 -5
- package/guides/claude-native.md +101 -0
- package/guides/host-ledger.md +3 -1
- package/guides/participant.md +14 -7
- package/guides/pi-native-delivery.md +43 -0
- package/package.json +4 -3
- package/dist/claude-hook.d.ts +0 -10
- package/dist/claude-hook.js +0 -60
- package/dist/paseo-connection.d.ts +0 -15
- package/dist/paseo-connection.js +0 -141
- package/dist/presence.d.ts +0 -2
- package/dist/presence.js +0 -24
- package/dist/presented.d.ts +0 -2
- package/dist/presented.js +0 -10
- package/dist/state-cell.d.ts +0 -14
- package/dist/wake-port.d.ts +0 -23
- package/dist/wake-port.js +0 -27
- /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
package/dist/notifications.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
|
-
import { planActNotifications, } from './delivery.js';
|
|
5
4
|
import { SquareError } from './model.js';
|
|
6
|
-
import { matchesMentionTarget } from './runtime.js';
|
|
7
|
-
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
8
5
|
import { displayAttentionPath } from './attention-presentation.js';
|
|
9
|
-
import { openSquare
|
|
6
|
+
import { openSquare } from './square-file-adapter.js';
|
|
10
7
|
import { closeOpenSquare } from './open-square.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { projectPresentationEvidence } from './square-projections.js';
|
|
8
|
+
import { deliverPending, sweepPending } from './delivery-operations.js';
|
|
9
|
+
import { projectWakeEvidenceFromState } from './square-projections.js';
|
|
10
|
+
import { canonicalPath } from './canonical-path.js';
|
|
15
11
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
16
|
-
export { planActNotifications, matchesMentionTarget };
|
|
17
|
-
export { notificationMessageId } from './delivery.js';
|
|
18
12
|
export const PRIVILEGED_HOOK_BUDGET_MS = 3000;
|
|
19
13
|
export function wakeGraceMs(env = process.env) {
|
|
20
14
|
const value = Number.parseInt(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? '5000', 10);
|
|
@@ -23,71 +17,6 @@ export function wakeGraceMs(env = process.env) {
|
|
|
23
17
|
}
|
|
24
18
|
return value;
|
|
25
19
|
}
|
|
26
|
-
function wakeLabel(kind) {
|
|
27
|
-
if (kind === 'paseo')
|
|
28
|
-
return 'paseo';
|
|
29
|
-
if (kind.startsWith('codex'))
|
|
30
|
-
return 'codex-queue';
|
|
31
|
-
return kind;
|
|
32
|
-
}
|
|
33
|
-
function renderWakePayload(request) {
|
|
34
|
-
return [
|
|
35
|
-
`<system-reminder source="square" wake="${wakeLabel(request.route.kind)}">`,
|
|
36
|
-
`square: ${displayAttentionPath(request.location)}`,
|
|
37
|
-
`attention: ${request.activity} for ${request.participant} from ${request.actor}`,
|
|
38
|
-
'</system-reminder>',
|
|
39
|
-
].join('\n');
|
|
40
|
-
}
|
|
41
|
-
function notificationIndex(ref) {
|
|
42
|
-
if (typeof ref === 'number')
|
|
43
|
-
return ref;
|
|
44
|
-
const index = parseActivityId(ref);
|
|
45
|
-
if (index === undefined)
|
|
46
|
-
throw new Error(`Invalid act ref: ${ref}`);
|
|
47
|
-
return index;
|
|
48
|
-
}
|
|
49
|
-
export async function hasDeliveredNotification(squarePath, name, ref) {
|
|
50
|
-
const square = await openSquare(squarePath);
|
|
51
|
-
try {
|
|
52
|
-
const recipient = (await resolveParticipant(square, name)).name;
|
|
53
|
-
return notificationDelivered(square, recipient, notificationIndex(ref));
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
await closeOpenSquare(square);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
export async function hasAttentionNotification(squarePath, name, ref, env = process.env) {
|
|
60
|
-
const square = await openSquare(squarePath);
|
|
61
|
-
try {
|
|
62
|
-
const recipient = (await resolveParticipant(square, name)).name;
|
|
63
|
-
const index = notificationIndex(ref);
|
|
64
|
-
if (await notificationDelivered(square, recipient, index))
|
|
65
|
-
return true;
|
|
66
|
-
const root = hostLedgerRoot(env);
|
|
67
|
-
const hostLedger = createHostLedgerPort({ rootPath: root });
|
|
68
|
-
return (await projectPresentationEvidence({ hostLedger, location: squarePath, participant: recipient, activity: formatActivityId(index), now: Date.now() })).some((row) => row.outcome === 'presented');
|
|
69
|
-
}
|
|
70
|
-
finally {
|
|
71
|
-
await closeOpenSquare(square);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export async function waitForDeliveredNotification(squarePath, name, ref, opts = {}) {
|
|
75
|
-
const deadline = Date.now() + (opts.timeoutMs ?? 30000);
|
|
76
|
-
const observer = await observeSquareChanges(squarePath);
|
|
77
|
-
try {
|
|
78
|
-
while (Date.now() <= deadline) {
|
|
79
|
-
const baseline = await observer.read(opts.signal);
|
|
80
|
-
if (await hasDeliveredNotification(squarePath, name, ref))
|
|
81
|
-
return true;
|
|
82
|
-
if (!await observer.changed(baseline, Math.max(0, deadline - Date.now()), opts.signal))
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
finally {
|
|
88
|
-
observer.close();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
20
|
export async function defaultWakeAdapters() {
|
|
92
21
|
const adapters = [];
|
|
93
22
|
try {
|
|
@@ -106,41 +35,35 @@ export async function defaultWakeAdapters() {
|
|
|
106
35
|
}
|
|
107
36
|
return adapters;
|
|
108
37
|
}
|
|
109
|
-
export async function createDefaultWakeTransport(hostLedger, clock, env = process.env) {
|
|
110
|
-
const adapters = await defaultWakeAdapters();
|
|
111
|
-
|
|
38
|
+
export async function createDefaultWakeTransport(hostLedger, clock, env = process.env, suppliedAdapters) {
|
|
39
|
+
const adapters = suppliedAdapters ?? await defaultWakeAdapters();
|
|
40
|
+
const { createClaudeWakeTransport } = await import('./claude-delivery.js');
|
|
41
|
+
return createWakeTransport(suppliedAdapters === undefined && env.SQUARE_DISABLE_PASEO_WAKE === '1' ? adapters.filter((adapter) => adapter.kind !== 'paseo') : adapters, hostLedger, clock, { 'claude-native': createClaudeWakeTransport(hostLedger, env) });
|
|
112
42
|
}
|
|
113
|
-
async function
|
|
114
|
-
const activity = parseActivityId(request.activity);
|
|
115
|
-
if (activity === undefined)
|
|
116
|
-
return { current: false, activityPending: false, sessionBound: false, routePublished: false, observationAvailable: true };
|
|
43
|
+
async function observeWakeRequest(request, hostLedger, now) {
|
|
117
44
|
let square;
|
|
118
45
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
&& binding.sessionId === request.route.sessionId
|
|
125
|
-
&& binding.location === request.route.location);
|
|
126
|
-
const routePublished = (current.state.routes ?? []).some((route) => route.location === request.route.location
|
|
127
|
-
&& nameKey(route.participant) === nameKey(request.route.participant)
|
|
128
|
-
&& route.sessionId === request.route.sessionId
|
|
129
|
-
&& route.kind === request.route.kind
|
|
130
|
-
&& JSON.stringify(route.address) === JSON.stringify(request.route.address));
|
|
131
|
-
return { current: activityPending && sessionBound && routePublished, activityPending, sessionBound, routePublished, observationAvailable: true };
|
|
46
|
+
const location = await canonicalPath(request.location);
|
|
47
|
+
square = await openSquare(location, { hostLedger });
|
|
48
|
+
const { state } = await square.artifact.read();
|
|
49
|
+
const eligibility = await projectWakeEvidenceFromState({ state, hostLedger, location, now });
|
|
50
|
+
return { ...eligibility.currentness({ ...request, location }), observationAvailable: true };
|
|
132
51
|
}
|
|
133
52
|
catch {
|
|
134
|
-
return { current: false, activityPending: false, sessionBound: false, routePublished: false,
|
|
53
|
+
return { current: false, activityPending: false, sessionBound: false, routePublished: false,
|
|
54
|
+
selectedOwner: false, cancelled: false, presented: false, observationAvailable: false };
|
|
135
55
|
}
|
|
136
56
|
finally {
|
|
137
57
|
if (square !== undefined)
|
|
138
58
|
await closeOpenSquare(square);
|
|
139
59
|
}
|
|
140
60
|
}
|
|
141
|
-
export function createWakeTransport(adapters, hostLedger, clock) {
|
|
61
|
+
export function createWakeTransport(adapters, hostLedger, clock, nativePorts = {}) {
|
|
142
62
|
return {
|
|
143
63
|
probe: async (route) => {
|
|
64
|
+
const native = nativePorts[route.kind];
|
|
65
|
+
if (native !== undefined)
|
|
66
|
+
return native.probe?.(route) ?? true;
|
|
144
67
|
const adapter = adapters.find((candidate) => candidate.kind === route.kind);
|
|
145
68
|
if (adapter === undefined)
|
|
146
69
|
return { outcome: 'not-capable', diagnostic: `no adapter for ${route.kind}` };
|
|
@@ -155,26 +78,29 @@ export function createWakeTransport(adapters, hostLedger, clock) {
|
|
|
155
78
|
}
|
|
156
79
|
},
|
|
157
80
|
attempt: async (request, timeoutMs, beforeSend) => {
|
|
81
|
+
let revalidation;
|
|
82
|
+
const finalGate = beforeSend ?? (async () => {
|
|
83
|
+
revalidation = await observeWakeRequest(request, hostLedger, clock());
|
|
84
|
+
return revalidation.current;
|
|
85
|
+
});
|
|
86
|
+
const native = nativePorts[request.route.kind];
|
|
87
|
+
if (native !== undefined)
|
|
88
|
+
return native.attempt(request, timeoutMs, finalGate);
|
|
158
89
|
const adapter = adapters.find((candidate) => candidate.kind === request.route.kind);
|
|
159
90
|
if (adapter === undefined)
|
|
160
91
|
return { outcome: 'not-capable', diagnostic: `no adapter for ${request.route.kind}` };
|
|
161
92
|
try {
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (result.outcome === 'unavailable')
|
|
174
|
-
return { outcome: 'failed', signature: result.signature, message: result.message, ...(result.diagnostic === undefined ? {} : { diagnostic: result.diagnostic }), unavailable: true, ...(result.retainRoute === true ? { retainRoute: true } : {}), ...(result.routeStale === true ? { routeStale: true } : {}) };
|
|
175
|
-
if (result.outcome === 'unknown')
|
|
176
|
-
return { outcome: 'unknown', ...(result.signature === undefined ? {} : { signature: result.signature }), ...(result.message === undefined ? {} : { message: result.message }), ...(result.diagnostic === undefined ? {} : { diagnostic: result.diagnostic }) };
|
|
177
|
-
if (result.outcome === 'cancelled' && revalidation !== undefined && !revalidation.current) {
|
|
93
|
+
const kind = request.route.kind;
|
|
94
|
+
const payload = [
|
|
95
|
+
`<system-reminder source="square" wake="${kind === 'paseo' ? 'paseo' : kind.startsWith('codex') ? 'codex-queue' : kind}">`,
|
|
96
|
+
`square: ${displayAttentionPath(request.location)}`,
|
|
97
|
+
`attention: ${request.activity} for ${request.participant} from ${request.actor}`,
|
|
98
|
+
'</system-reminder>',
|
|
99
|
+
].join('\n');
|
|
100
|
+
const result = await adapter.dispatch(request.route.address, payload, finalGate, timeoutMs, request);
|
|
101
|
+
if (result.outcome !== 'gate-rejected')
|
|
102
|
+
return result;
|
|
103
|
+
if (revalidation !== undefined && !revalidation.current) {
|
|
178
104
|
return {
|
|
179
105
|
outcome: 'failed',
|
|
180
106
|
signature: 'pre_send_revalidation_failed',
|
|
@@ -202,8 +128,7 @@ export async function processActNotificationsOnce(squarePath, actIndex, opts = {
|
|
|
202
128
|
});
|
|
203
129
|
const square = await openSquare(squarePath, { clock: now, hostLedger, env });
|
|
204
130
|
try {
|
|
205
|
-
const
|
|
206
|
-
const transport = createWakeTransport(adapters, hostLedger, now);
|
|
131
|
+
const transport = await createDefaultWakeTransport(hostLedger, now, env, opts.adapters);
|
|
207
132
|
try {
|
|
208
133
|
return await deliverPending({ artifact: square.artifact, hostLedger, transport, location: squarePath, activity: actIndex, timeoutMs: Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000), now: now() });
|
|
209
134
|
}
|
|
@@ -237,7 +162,7 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
|
|
|
237
162
|
catch { /* no local square directory */ }
|
|
238
163
|
if (remainingMs() === 0 || signal?.aborted)
|
|
239
164
|
return;
|
|
240
|
-
const
|
|
165
|
+
const transport = await createDefaultWakeTransport(hostLedger, Date.now, env, suppliedAdapters);
|
|
241
166
|
for (const squarePath of paths) {
|
|
242
167
|
if (remainingMs() === 0 || signal?.aborted)
|
|
243
168
|
break;
|
|
@@ -249,7 +174,6 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
|
|
|
249
174
|
const limit = Number.parseInt(env.SQUARE_NOTIFY_SWEEP_LIMIT ?? '8', 10);
|
|
250
175
|
const graceMs = 0;
|
|
251
176
|
const selected = await sweepPending({ artifact: square.artifact, hostLedger, location: squarePath, now: Date.now(), graceMs, limit: Number.isFinite(limit) && limit > 0 ? limit : 8 }).catch(() => []);
|
|
252
|
-
const transport = createWakeTransport(adapters, hostLedger, Date.now);
|
|
253
177
|
for (const actIndex of selected) {
|
|
254
178
|
const remaining = remainingMs();
|
|
255
179
|
if (remaining === 0 || signal?.aborted)
|
|
@@ -266,11 +190,6 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
|
|
|
266
190
|
catch { /* stale index entries are ignored by the hook */ }
|
|
267
191
|
}
|
|
268
192
|
}
|
|
269
|
-
/** Select sweep candidates from one frozen snapshot and one delivery replay. */
|
|
270
|
-
export async function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery) {
|
|
271
|
-
const ledger = createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
|
|
272
|
-
return sweepPendingFromState({ state, hostLedger: ledger, location: squarePath, now, graceMs: wakeGraceMs(env), limit, deriveDelivery });
|
|
273
|
-
}
|
|
274
193
|
/** Select old pending attention at a bounded action boundary for an explicit executor. */
|
|
275
194
|
export async function sweepPendingNotifications(squarePath, opts = {}) {
|
|
276
195
|
const env = opts.env ?? process.env;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type OpenCodeNativeSession } from './packages/agent-delivery/src/opencode-native.js';
|
|
2
|
+
export interface OpenCodeContextMessage {
|
|
3
|
+
readonly id?: string;
|
|
4
|
+
readonly role: string;
|
|
5
|
+
readonly content: readonly {
|
|
6
|
+
readonly type: string;
|
|
7
|
+
readonly text?: string;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
/** Only the matching prepared primary context authorizes presentation, never admission. */
|
|
11
|
+
export declare function observeOpenCodeContext(sessionId: string, messages: readonly OpenCodeContextMessage[], env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
|
|
12
|
+
/** One existing observer and the existing dispatch owner; no second queue or evidence store. */
|
|
13
|
+
export declare function receiveOpenCodePending(target: OpenCodeNativeSession, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<void>;
|
|
14
|
+
export declare function publishOpenCodeRoutes(sessionId: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<boolean>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { nameKey } from './model.js';
|
|
2
|
+
import { observeSessionPending, sessionInbox } from './inbox.js';
|
|
3
|
+
import { hostLedgerForEnv } from './registry.js';
|
|
4
|
+
import { openSquare } from './square-file-adapter.js';
|
|
5
|
+
import { closeOpenSquare } from './open-square.js';
|
|
6
|
+
import { nativePendingPreview, presentationSuppressesWake, projectPresentationEvidence } from './square-projections.js';
|
|
7
|
+
import { presentPending } from './presentation-operations.js';
|
|
8
|
+
import { deliverPending } from './delivery-operations.js';
|
|
9
|
+
import { createWakeTransport } from './notifications.js';
|
|
10
|
+
import { publishWakeRoute } from './routes.js';
|
|
11
|
+
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
12
|
+
import { createNativeInputId, sendNativeText } from './packages/agent-delivery/src/opencode-native.js';
|
|
13
|
+
const routeKind = 'opencode-server';
|
|
14
|
+
const key = (location, participant, index) => `${location}\0${nameKey(participant)}\0${index}`;
|
|
15
|
+
/** The one native pending preview: the audience/catch/epoch fence before send and inside presentation. */
|
|
16
|
+
function preview(state, owner, index) {
|
|
17
|
+
return nativePendingPreview(state, { location: owner.location, routeKind, sessionId: owner.session, participant: owner.participant, actIndex: index, epoch: owner.epoch, address: { sessionId: owner.session }, cancelledThrough: owner.cancelledThrough, now: Date.now() });
|
|
18
|
+
}
|
|
19
|
+
function createOpenCodeWakeTransport(target, env, lifetime) {
|
|
20
|
+
const ledger = hostLedgerForEnv(env);
|
|
21
|
+
return {
|
|
22
|
+
probe: async (route) => !lifetime.aborted && route.kind === routeKind && route.sessionId === target.sessionId && route.address.sessionId === target.sessionId,
|
|
23
|
+
async attempt(request, timeoutMs, beforeSend) {
|
|
24
|
+
const signal = AbortSignal.any([lifetime, AbortSignal.timeout(timeoutMs)]);
|
|
25
|
+
const deadline = Date.now() + timeoutMs;
|
|
26
|
+
// Start the native call under the ownership fence, but await it OUTSIDE: prompt may
|
|
27
|
+
// enter our context hook synchronously and that hook needs the same ownership lock.
|
|
28
|
+
const started = await ledger.withClaimLock(async () => {
|
|
29
|
+
if (signal.aborted || request.route.sessionId !== target.sessionId || !request.claimToken
|
|
30
|
+
|| !await (beforeSend?.() ?? Promise.resolve(true)))
|
|
31
|
+
return undefined;
|
|
32
|
+
const owner = (await ledger.listPresence({ location: request.location, participant: request.participant, session: target.sessionId }))
|
|
33
|
+
.find((binding) => binding.channel === 'opencode' && binding.epoch === request.route.epoch);
|
|
34
|
+
const index = parseActivityId(request.activity);
|
|
35
|
+
if (!owner || index === undefined)
|
|
36
|
+
return undefined;
|
|
37
|
+
const square = await openSquare(owner.location, { hostLedger: ledger, env, signal });
|
|
38
|
+
try {
|
|
39
|
+
const prepared = preview((await square.artifact.read(signal)).state, owner, index);
|
|
40
|
+
if (!prepared)
|
|
41
|
+
return undefined;
|
|
42
|
+
const row = { location: owner.location, participant: owner.participant, session: target.sessionId,
|
|
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
|
+
|| preview((await square.artifact.read(signal)).state, owner, index)?.payload !== 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;
|
|
58
|
+
if (result.state === 'accepted')
|
|
59
|
+
return { outcome: 'accepted' };
|
|
60
|
+
if (result.state === 'unavailable')
|
|
61
|
+
return { outcome: 'failed', unavailable: true, retainRoute: true, signature: result.code };
|
|
62
|
+
return { outcome: 'unknown', signature: result.code };
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Only the matching prepared primary context authorizes presentation, never admission. */
|
|
67
|
+
export async function observeOpenCodeContext(sessionId, messages, env, signal) {
|
|
68
|
+
const ledger = hostLedgerForEnv(env);
|
|
69
|
+
await ledger.withClaimLock(async () => {
|
|
70
|
+
if (signal.aborted)
|
|
71
|
+
return;
|
|
72
|
+
const rows = (await ledger.listEvidence({ kind: 'wake', session: sessionId })).filter((row) => row.nativeDelivery?.harness === 'opencode' && !!row.claimToken && ['dispatching', 'unknown', 'accepted'].includes(row.outcome));
|
|
73
|
+
for (const row of rows) {
|
|
74
|
+
if (signal.aborted || !messages.some((message) => message.id === row.nativeDelivery.inputId && message.role === 'user'
|
|
75
|
+
&& message.content.every((part) => part.type === 'text' && typeof part.text === 'string')
|
|
76
|
+
&& message.content.map((part) => part.text).join('') === row.nativeDelivery.payload))
|
|
77
|
+
continue;
|
|
78
|
+
const owner = (await ledger.listPresence({ location: row.location, participant: row.participant, session: sessionId }))
|
|
79
|
+
.find((binding) => binding.channel === 'opencode' && binding.epoch === row.nativeDelivery.epoch);
|
|
80
|
+
const index = parseActivityId(row.activity);
|
|
81
|
+
if (!owner || index === undefined)
|
|
82
|
+
continue;
|
|
83
|
+
const square = await openSquare(row.location, { hostLedger: ledger, env, signal });
|
|
84
|
+
try {
|
|
85
|
+
const prepared = preview((await square.artifact.read(signal)).state, owner, index);
|
|
86
|
+
if (!prepared || prepared.payload !== row.nativeDelivery.payload)
|
|
87
|
+
continue;
|
|
88
|
+
const prior = await projectPresentationEvidence({ hostLedger: ledger, location: row.location, participant: row.participant, sessionId, activity: row.activity });
|
|
89
|
+
if (presentationSuppressesWake(prior))
|
|
90
|
+
continue;
|
|
91
|
+
await presentPending({ artifact: square.artifact, hostLedger: ledger, location: row.location, participant: row.participant, session: sessionId,
|
|
92
|
+
activity: row.activity, sink: { present() { } }, markSeen: !prepared.clipped, signal,
|
|
93
|
+
current: (state) => !signal.aborted && preview(state, owner, index)?.payload === row.nativeDelivery.payload });
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await closeOpenSquare(square);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}, signal);
|
|
100
|
+
}
|
|
101
|
+
/** One existing observer and the existing dispatch owner; no second queue or evidence store. */
|
|
102
|
+
export async function receiveOpenCodePending(target, env, signal) {
|
|
103
|
+
const ledger = hostLedgerForEnv(env);
|
|
104
|
+
const observer = await observeSessionPending(target.sessionId, env);
|
|
105
|
+
const excluded = new Set();
|
|
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) });
|
|
123
|
+
try {
|
|
124
|
+
await refreshExclusions();
|
|
125
|
+
let pending = await observer.wait(Infinity, { signal, excludeKeys: excluded });
|
|
126
|
+
while (!signal.aborted && pending.length > 0) {
|
|
127
|
+
let armed;
|
|
128
|
+
const ready = new Promise((resolve) => { armed = resolve; });
|
|
129
|
+
// Capture the next state edge BEFORE dispatch. Evidence failures alone do not retry.
|
|
130
|
+
const next = observer.wait(Infinity, { signal, excludeKeys: excluded, skipImmediate: true, onChangeArmed: armed })
|
|
131
|
+
.catch(() => { armed(false); return []; });
|
|
132
|
+
if (!await ready || signal.aborted)
|
|
133
|
+
break;
|
|
134
|
+
for (const membership of pending) {
|
|
135
|
+
const square = await openSquare(membership.squarePath, { hostLedger: ledger, env, signal });
|
|
136
|
+
try {
|
|
137
|
+
for (const note of membership.notifications) {
|
|
138
|
+
if (signal.aborted || excluded.has(key(membership.squarePath, membership.name, note.actIndex)))
|
|
139
|
+
continue;
|
|
140
|
+
await deliverPending({ artifact: square.artifact, hostLedger: ledger, transport, location: square.location, activity: formatActivityId(note.actIndex), now: Date.now() });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await closeOpenSquare(square);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
await refreshExclusions();
|
|
148
|
+
pending = await next;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
observer.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export async function publishOpenCodeRoutes(sessionId, env, signal) {
|
|
156
|
+
const ledger = hostLedgerForEnv(env);
|
|
157
|
+
return ledger.withClaimLock(async () => {
|
|
158
|
+
const owners = (await ledger.listPresence({ session: sessionId })).filter((binding) => binding.channel === 'opencode');
|
|
159
|
+
for (const owner of owners) {
|
|
160
|
+
const square = await openSquare(owner.location, { hostLedger: ledger, env, signal });
|
|
161
|
+
try {
|
|
162
|
+
await publishWakeRoute(square.artifact, { location: owner.location, participant: owner.participant, sessionId, channel: 'opencode',
|
|
163
|
+
kind: routeKind, address: { sessionId }, epoch: owner.epoch }, { requireCurrentSession: true });
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
await closeOpenSquare(square);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return owners.length > 0;
|
|
170
|
+
}, signal);
|
|
171
|
+
}
|
package/dist/opencode.d.ts
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { type OpenCodeSessionCapability } from './packages/agent-delivery/src/opencode-native.js';
|
|
2
|
+
import { type OpenCodeContextMessage } from './opencode-delivery.js';
|
|
3
|
+
interface OpenCodePluginContext {
|
|
4
|
+
readonly app: {
|
|
5
|
+
readonly version: string;
|
|
6
|
+
};
|
|
7
|
+
readonly location: {
|
|
8
|
+
readonly directory: string;
|
|
9
|
+
readonly project: {
|
|
10
|
+
readonly id: string;
|
|
7
11
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
12
|
+
readonly workspaceID?: string;
|
|
13
|
+
};
|
|
14
|
+
readonly session: OpenCodeSessionCapability & {
|
|
15
|
+
hook(name: 'prompt', callback: (event: {
|
|
16
|
+
sessionID: string;
|
|
17
|
+
}) => Promise<void>): Promise<unknown>;
|
|
18
|
+
hook(name: 'context', callback: (event: {
|
|
19
|
+
sessionID: string;
|
|
20
|
+
messages: OpenCodeContextMessage[];
|
|
21
|
+
}) => Promise<void>): Promise<unknown>;
|
|
22
|
+
};
|
|
23
|
+
readonly event: {
|
|
24
|
+
subscribe(options: {
|
|
25
|
+
signal: AbortSignal;
|
|
26
|
+
}): AsyncIterable<{
|
|
27
|
+
type: string;
|
|
28
|
+
data: {
|
|
29
|
+
sessionID?: string;
|
|
30
|
+
reason?: string;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Actual OpenCode 2.0.20 server plugin. Harness identity belongs to ctx, never process.env. */
|
|
36
|
+
declare const _default: {
|
|
37
|
+
id: string;
|
|
38
|
+
setup(ctx: OpenCodePluginContext): Promise<() => Promise<void>>;
|
|
39
|
+
};
|
|
40
|
+
export default _default;
|