@astrosheep/square 0.4.0 → 0.4.1

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