@astrosheep/square 0.3.63 → 0.3.65

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.
Files changed (171) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +3 -2
  2. package/claude-plugin/hooks/hooks.json +1 -22
  3. package/claude-plugin/hooks/register.js +119 -0
  4. package/claude-plugin/types.d.ts +7 -0
  5. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  6. package/dist/activity-feed.d.ts +1 -10
  7. package/dist/activity-feed.js +1 -27
  8. package/dist/activity.js +57 -25
  9. package/dist/artifact.d.ts +1 -1
  10. package/dist/artifact.js +24 -25
  11. package/dist/attention-presentation.js +1 -0
  12. package/dist/automatic-session.d.ts +3 -1
  13. package/dist/automatic-session.js +21 -34
  14. package/dist/boundary-presentation.js +4 -13
  15. package/dist/canonical-path.d.ts +11 -0
  16. package/dist/canonical-path.js +47 -0
  17. package/dist/catch-decisions.js +14 -10
  18. package/dist/catch-selection.d.ts +6 -0
  19. package/dist/catch-selection.js +14 -0
  20. package/dist/claude-delivery.d.ts +11 -0
  21. package/dist/claude-delivery.js +145 -0
  22. package/dist/claude-mod.d.ts +8 -0
  23. package/dist/claude-mod.js +164 -0
  24. package/dist/cli/context.d.ts +0 -1
  25. package/dist/cli/context.js +9 -21
  26. package/dist/cli/harness-command.d.ts +0 -1
  27. package/dist/cli/harness-command.js +0 -6
  28. package/dist/cli/observation-commands.d.ts +1 -1
  29. package/dist/cli/observation-commands.js +31 -58
  30. package/dist/cli/program.js +20 -18
  31. package/dist/cli/registry.js +2 -2
  32. package/dist/cli/square-commands.js +10 -6
  33. package/dist/codex-boundary-state.d.ts +2 -2
  34. package/dist/codex-boundary-state.js +4 -4
  35. package/dist/codex-hook.js +12 -15
  36. package/dist/codex-queue.d.ts +2 -2
  37. package/dist/codex-queue.js +7 -5
  38. package/dist/decisions.d.ts +2 -6
  39. package/dist/decisions.js +16 -17
  40. package/dist/delivery-health.d.ts +1 -2
  41. package/dist/delivery-health.js +4 -5
  42. package/dist/delivery-operations.d.ts +2 -5
  43. package/dist/delivery-operations.js +77 -214
  44. package/dist/delivery.d.ts +25 -47
  45. package/dist/delivery.js +14 -46
  46. package/dist/diagnostic-redaction.js +7 -9
  47. package/dist/file-changes.d.ts +0 -1
  48. package/dist/file-changes.js +2 -19
  49. package/dist/harness-claude.js +8 -6
  50. package/dist/harness-codex.d.ts +0 -2
  51. package/dist/harness-codex.js +0 -2
  52. package/dist/harness-links.js +13 -17
  53. package/dist/harness.js +3 -7
  54. package/dist/help.js +4 -3
  55. package/dist/host-ledger-file-adapter.d.ts +17 -8
  56. package/dist/host-ledger-file-adapter.js +164 -80
  57. package/dist/host-ledger.d.ts +55 -50
  58. package/dist/inbox.d.ts +0 -3
  59. package/dist/inbox.js +88 -50
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/list.js +1 -4
  63. package/dist/mcp.js +24 -41
  64. package/dist/mention-parse.js +3 -9
  65. package/dist/model.d.ts +11 -2
  66. package/dist/model.js +1 -1
  67. package/dist/notifications.d.ts +3 -16
  68. package/dist/notifications.js +41 -122
  69. package/dist/opencode-delivery.d.ts +14 -0
  70. package/dist/opencode-delivery.js +171 -0
  71. package/dist/opencode.d.ts +39 -18
  72. package/dist/opencode.js +145 -40
  73. package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
  74. package/dist/packages/agent-delivery/src/claude-auth.js +75 -0
  75. package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
  76. package/dist/packages/agent-delivery/src/claude-native.js +68 -0
  77. package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
  78. package/dist/packages/agent-delivery/src/claude.js +66 -0
  79. package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
  80. package/dist/packages/agent-delivery/src/deadline.js +41 -0
  81. package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
  82. package/dist/packages/agent-delivery/src/errors.js +41 -0
  83. package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
  84. package/dist/packages/agent-delivery/src/guards.js +6 -0
  85. package/dist/packages/agent-delivery/src/index.d.ts +83 -0
  86. package/dist/packages/agent-delivery/src/index.js +110 -0
  87. package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
  88. package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
  89. package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
  90. package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
  91. package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
  92. package/dist/packages/agent-delivery/src/opencode.js +127 -0
  93. package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
  94. package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
  95. package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
  96. package/dist/packages/agent-delivery/src/paseo.js +106 -0
  97. package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
  98. package/dist/packages/agent-delivery/src/pi-client.js +106 -0
  99. package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
  100. package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
  101. package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
  102. package/dist/packages/agent-delivery/src/pi-send.js +4 -0
  103. package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
  104. package/dist/packages/agent-delivery/src/pi.js +209 -0
  105. package/dist/packages/agent-delivery/src/types.d.ts +120 -0
  106. package/dist/participant-host.js +12 -13
  107. package/dist/participant-identity.d.ts +21 -0
  108. package/dist/participant-identity.js +32 -11
  109. package/dist/paseo-delivery.d.ts +2 -2
  110. package/dist/paseo-delivery.js +35 -27
  111. package/dist/paseo-timeline.js +25 -21
  112. package/dist/paseo.d.ts +2 -2
  113. package/dist/paseo.js +1 -2
  114. package/dist/ports.d.ts +7 -20
  115. package/dist/presentation-operations.js +16 -4
  116. package/dist/presentation.d.ts +19 -10
  117. package/dist/presentation.js +99 -177
  118. package/dist/registry.d.ts +15 -66
  119. package/dist/registry.js +45 -123
  120. package/dist/routes.d.ts +1 -28
  121. package/dist/routes.js +22 -104
  122. package/dist/runtime.d.ts +3 -9
  123. package/dist/runtime.js +14 -67
  124. package/dist/search.d.ts +0 -2
  125. package/dist/search.js +3 -10
  126. package/dist/square-actions.js +48 -72
  127. package/dist/square-application.js +32 -30
  128. package/dist/square-core.d.ts +3 -0
  129. package/dist/square-core.js +22 -5
  130. package/dist/square-facade.d.ts +2 -0
  131. package/dist/square-file-adapter.d.ts +2 -1
  132. package/dist/square-file-adapter.js +6 -9
  133. package/dist/square-projections.d.ts +40 -45
  134. package/dist/square-projections.js +60 -74
  135. package/dist/square-state.js +1 -4
  136. package/dist/square-storage.d.ts +5 -8
  137. package/dist/square-storage.js +13 -36
  138. package/dist/square-wiring.d.ts +2 -7
  139. package/dist/square-wiring.js +6 -24
  140. package/dist/stream.js +4 -6
  141. package/dist/time.js +2 -7
  142. package/dist/views.d.ts +6 -18
  143. package/dist/views.js +14 -21
  144. package/dist/wake-attempts.d.ts +2 -56
  145. package/dist/wake-attempts.js +15 -110
  146. package/dist/wake-eligibility.d.ts +43 -0
  147. package/dist/wake-eligibility.js +106 -0
  148. package/dist/wake-evidence.d.ts +32 -9
  149. package/dist/wake-evidence.js +29 -24
  150. package/dist/wake-sink.d.ts +13 -9
  151. package/dist/wake-sink.js +35 -54
  152. package/dist/watch.js +13 -21
  153. package/extensions/square-pi.js +27 -13
  154. package/guides/change-waits.md +1 -1
  155. package/guides/claude-native.md +101 -0
  156. package/guides/host-ledger.md +3 -1
  157. package/guides/participant.md +14 -7
  158. package/guides/pi-native-delivery.md +49 -0
  159. package/package.json +4 -3
  160. package/dist/claude-hook.d.ts +0 -10
  161. package/dist/claude-hook.js +0 -60
  162. package/dist/paseo-connection.d.ts +0 -15
  163. package/dist/paseo-connection.js +0 -141
  164. package/dist/presence.d.ts +0 -2
  165. package/dist/presence.js +0 -24
  166. package/dist/presented.d.ts +0 -2
  167. package/dist/presented.js +0 -10
  168. package/dist/state-cell.d.ts +0 -14
  169. package/dist/wake-port.d.ts +0 -23
  170. package/dist/wake-port.js +0 -27
  171. /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
@@ -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, observeSquareChanges } from './square-file-adapter.js';
6
+ import { openSquare } from './square-file-adapter.js';
10
7
  import { closeOpenSquare } from './open-square.js';
11
- import { notificationDelivered, resolveParticipant } from './views.js';
12
- import { deliverPending, observeSquare, sweepPending, sweepPendingFromState } from './delivery-operations.js';
13
- import { nameKey } from './model.js';
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
- return createWakeTransport(env.SQUARE_DISABLE_PASEO_WAKE === '1' ? adapters.filter((adapter) => adapter.kind !== 'paseo') : adapters, hostLedger, clock);
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 wakeRequestCurrentness(request, hostLedger, now) {
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
- square = await openSquare(request.location, { hostLedger });
120
- const current = await observeSquare({ artifact: square.artifact, hostLedger, location: request.location, now });
121
- const activityPending = current.pending.some((entry) => nameKey(entry.recipient) === nameKey(request.participant)
122
- && entry.notifications.some((notification) => notification.item.index === activity));
123
- const sessionBound = current.bindings.some((binding) => nameKey(binding.participant) === nameKey(request.route.participant)
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, observationAvailable: 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
- let revalidation;
163
- const result = await adapter.dispatch(request.route.address, renderWakePayload(request), async () => {
164
- if (!(await (beforeSend ?? (async () => true))()))
165
- return false;
166
- revalidation = await wakeRequestCurrentness(request, hostLedger, clock());
167
- return revalidation.current;
168
- }, timeoutMs);
169
- if (result.outcome === 'accepted')
170
- return { outcome: 'accepted' };
171
- if (result.outcome === 'failed')
172
- return { outcome: 'failed', ...(result.signature === undefined ? {} : { signature: result.signature }), message: result.message, ...(result.diagnostic === undefined ? {} : { diagnostic: result.diagnostic }) };
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 adapters = opts.adapters ?? await defaultWakeAdapters();
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 adapters = suppliedAdapters ?? await defaultWakeAdapters();
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
+ }
@@ -1,19 +1,40 @@
1
- /** OpenCode's server plugin entrypoint for the published Square package. */
2
- export default function squareOpenCodePlugin(): Promise<{
3
- event: ({ event }: {
4
- event: {
5
- type?: string;
6
- properties?: Record<string, any>;
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
- }) => Promise<void>;
9
- 'shell.env': (input: {
10
- sessionID?: string;
11
- }, output: {
12
- env: Record<string, string>;
13
- }) => Promise<void>;
14
- 'tool.execute.after': (input: {
15
- sessionID: string;
16
- }, output: {
17
- output: string;
18
- }) => Promise<void>;
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;