@astrosheep/square 0.3.105 → 0.4.0

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 (143) hide show
  1. package/dist/activity-feed.js +1 -1
  2. package/dist/artifact-store.d.ts +41 -0
  3. package/dist/artifact-store.js +272 -0
  4. package/dist/artifact.d.ts +8 -12
  5. package/dist/artifact.js +66 -343
  6. package/dist/attention-presentation.d.ts +33 -7
  7. package/dist/attention-presentation.js +33 -69
  8. package/dist/boundary-presentation.d.ts +0 -2
  9. package/dist/boundary-presentation.js +17 -22
  10. package/dist/catch-decisions.d.ts +2 -2
  11. package/dist/catch-decisions.js +14 -4
  12. package/dist/catch-selection.d.ts +8 -0
  13. package/dist/catch-selection.js +5 -0
  14. package/dist/channel-artifact.d.ts +10 -0
  15. package/dist/channel-artifact.js +55 -0
  16. package/dist/channel-delivery.d.ts +11 -0
  17. package/dist/channel-delivery.js +51 -0
  18. package/dist/channel-domain.d.ts +3 -0
  19. package/dist/channel-domain.js +83 -0
  20. package/dist/channel-facade.d.ts +76 -0
  21. package/dist/channel-facade.js +1 -0
  22. package/dist/channel-internal.d.ts +126 -0
  23. package/dist/channel-internal.js +440 -0
  24. package/dist/channel-metadata.d.ts +11 -0
  25. package/dist/channel-metadata.js +1 -0
  26. package/dist/channel-ports.d.ts +241 -0
  27. package/dist/channel-ports.js +1 -0
  28. package/dist/channel-presentation.d.ts +17 -0
  29. package/dist/channel-presentation.js +22 -0
  30. package/dist/channel-sqlite-schema.d.ts +13 -0
  31. package/dist/channel-sqlite-schema.js +81 -0
  32. package/dist/channel-transport.d.ts +62 -0
  33. package/dist/channel-transport.js +1 -0
  34. package/dist/channel-values.d.ts +27 -0
  35. package/dist/channel-values.js +90 -0
  36. package/dist/channel.d.ts +31 -21
  37. package/dist/channel.js +42 -74
  38. package/dist/claude-delivery-status.js +1 -1
  39. package/dist/claude-delivery.d.ts +2 -5
  40. package/dist/claude-delivery.js +56 -103
  41. package/dist/claude-mod.js +4 -4
  42. package/dist/claude-transport.d.ts +5 -0
  43. package/dist/claude-transport.js +58 -0
  44. package/dist/cli/context.d.ts +2 -2
  45. package/dist/cli/context.js +0 -1
  46. package/dist/cli/maintenance-commands.d.ts +0 -4
  47. package/dist/cli/maintenance-commands.js +2 -29
  48. package/dist/cli/program.js +1 -1
  49. package/dist/cli/registry.js +1 -2
  50. package/dist/codex-address.d.ts +1 -1
  51. package/dist/codex-queue.d.ts +2 -2
  52. package/dist/codex-queue.js +1 -3
  53. package/dist/decisions.js +3 -3
  54. package/dist/delivery-deferral.d.ts +9 -18
  55. package/dist/delivery-deferral.js +4 -4
  56. package/dist/delivery-health.d.ts +4 -4
  57. package/dist/delivery-health.js +11 -11
  58. package/dist/delivery-operations.d.ts +15 -2
  59. package/dist/delivery-operations.js +60 -30
  60. package/dist/delivery.d.ts +44 -33
  61. package/dist/delivery.js +49 -75
  62. package/dist/help.js +1 -6
  63. package/dist/http-delivery.d.ts +2 -2
  64. package/dist/http-delivery.js +2 -4
  65. package/dist/inbox.d.ts +10 -18
  66. package/dist/inbox.js +44 -53
  67. package/dist/index.d.ts +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/mcp.d.ts +2 -1
  70. package/dist/mcp.js +40 -70
  71. package/dist/message.d.ts +7 -0
  72. package/dist/message.js +23 -0
  73. package/dist/model.d.ts +41 -77
  74. package/dist/model.js +7 -139
  75. package/dist/neutral.d.ts +72 -0
  76. package/dist/neutral.js +27 -0
  77. package/dist/notifications.d.ts +2 -8
  78. package/dist/notifications.js +15 -50
  79. package/dist/operation-presentation.d.ts +1 -2
  80. package/dist/operation-presentation.js +1 -7
  81. package/dist/owner.d.ts +2 -2
  82. package/dist/owner.js +1 -1
  83. package/dist/paseo-delivery.d.ts +1 -1
  84. package/dist/paseo-delivery.js +2 -4
  85. package/dist/paseo-timeline.js +1 -0
  86. package/dist/plugins/opencode/delivery.js +27 -42
  87. package/dist/ports.d.ts +46 -168
  88. package/dist/presentation-operations.d.ts +1 -14
  89. package/dist/presentation-operations.js +16 -16
  90. package/dist/presentation.d.ts +5 -5
  91. package/dist/presentation.js +1 -0
  92. package/dist/runtime.js +2 -2
  93. package/dist/search.js +1 -1
  94. package/dist/sqlite-channel.d.ts +15 -0
  95. package/dist/sqlite-channel.js +600 -0
  96. package/dist/sqlite-framing.d.ts +8 -0
  97. package/dist/sqlite-framing.js +31 -0
  98. package/dist/sqlite-square.d.ts +12 -13
  99. package/dist/sqlite-square.js +192 -248
  100. package/dist/sqlite-values.d.ts +21 -0
  101. package/dist/sqlite-values.js +78 -0
  102. package/dist/square-api.d.ts +1 -1
  103. package/dist/square-api.js +1 -1
  104. package/dist/square-core.d.ts +1 -1
  105. package/dist/square-domain.d.ts +21 -7
  106. package/dist/square-domain.js +93 -94
  107. package/dist/square-facade.d.ts +12 -66
  108. package/dist/square-index.d.ts +1 -1
  109. package/dist/square-index.js +1 -1
  110. package/dist/square-location.d.ts +6 -1
  111. package/dist/square-location.js +1 -1
  112. package/dist/square-receive.d.ts +2 -3
  113. package/dist/square-sqlite.d.ts +45 -44
  114. package/dist/square-sqlite.js +308 -764
  115. package/dist/square-tools.d.ts +8 -0
  116. package/dist/square-tools.js +39 -0
  117. package/dist/views.d.ts +6 -6
  118. package/dist/views.js +15 -9
  119. package/dist/wake-preview.d.ts +7 -0
  120. package/dist/wake-preview.js +15 -0
  121. package/dist/wake-sink.d.ts +2 -2
  122. package/dist/wake-sink.js +1 -1
  123. package/dist/wake-transports.d.ts +7 -0
  124. package/dist/wake-transports.js +42 -0
  125. package/guides/claude-native.md +13 -10
  126. package/guides/codex-delivery.md +3 -1
  127. package/guides/participant-ownership.md +1 -1
  128. package/guides/row-runtime-delivery-evidence.md +2 -0
  129. package/guides/row-storage-design.md +19 -44
  130. package/guides/square-api.md +137 -60
  131. package/package.json +3 -3
  132. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  133. package/plugins/claude/types.d.ts +1 -1
  134. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  135. package/plugins/opencode/delivery.ts +28 -41
  136. package/plugins/pi/square-pi.js +28 -51
  137. package/dist/square-legacy-framing.d.ts +0 -11
  138. package/dist/square-legacy-framing.js +0 -64
  139. package/dist/square-migration-host-ledger.d.ts +0 -26
  140. package/dist/square-migration-host-ledger.js +0 -169
  141. package/dist/square-migration.d.ts +0 -30
  142. package/dist/square-migration.js +0 -599
  143. package/guides/migration.md +0 -156
package/dist/artifact.js CHANGED
@@ -1,15 +1,27 @@
1
- import { DatabaseSync } from 'node:sqlite';
2
- import { retrySqliteBusy, inSqliteTransaction } from './sqlite-transaction.js';
3
- import { randomUUID } from 'node:crypto';
4
- import fs from 'node:fs';
5
1
  import path from 'node:path';
6
- import { pathToFileURL } from 'node:url';
7
- import { setTimeout as sleep } from 'node:timers/promises';
8
- import { observeFileVersion } from './file-changes.js';
2
+ import fs from 'node:fs';
9
3
  import { canonicalPath } from './canonical-path.js';
10
4
  import { InternalSquareError, SquareError } from './model.js';
11
5
  import { wrapSquareWrite } from './square-domain.js';
12
- import { APPLICATION_ID, CURRENT_USER_VERSION, createSqliteDriver, initializeSqlite, exportSqliteState, inspectSqlite, invalidArtifact, validateSqliteHeader, emptyRuntimeState } from './square-sqlite.js';
6
+ import { createSqliteDriver, initializeSqlite, exportSqliteState, inspectSqlite, emptyRuntimeState, squareStoreFormat } from './square-sqlite.js';
7
+ import { createChannelSqlite } from './sqlite-channel.js';
8
+ import { channelLowerSchema } from './channel-sqlite-schema.js';
9
+ import { resetStorageMeta } from './sqlite-framing.js';
10
+ import { createArtifactStore, createStoreFile, replaceStoreFile, inspectStore, observeStoreRevision } from './artifact-store.js';
11
+ import { channelReads, channelWrites } from './channel-artifact.js';
12
+ export { openExistingSquareDatabase } from './artifact-store.js';
13
+ export function channelArtifact(port) {
14
+ return {
15
+ read: (select, signal) => port.read(facts => select(facts.channel), signal),
16
+ transactAs: (authority, change, signal) => port.transactAs(authority, facts => change(facts.channel), signal),
17
+ claimDelivery: (input, signal) => port.claimDelivery(input, signal),
18
+ prepareDelivery: (input, signal) => port.prepareDelivery(input, signal),
19
+ completeDelivery: (input, signal) => port.completeDelivery(input, signal),
20
+ cancelDeliveries: (seat, through, signal) => port.cancelDeliveries(seat, through, signal),
21
+ changed: (since, timeout, signal) => port.changed(since, timeout, signal), close: () => port.close(),
22
+ };
23
+ }
24
+ const format = squareStoreFormat(channelLowerSchema, resetStorageMeta);
13
25
  /** Explicit-lifetime holder of one reused artifact connection. Request cancellation
14
26
  * never becomes the shared connection's lifetime; close drains the owner once. */
15
27
  export function createSquareResourceOwner() {
@@ -53,26 +65,26 @@ function normalizedLines(value) {
53
65
  return normalized === '' ? [] : normalized.split('\n');
54
66
  }
55
67
  /** All build paths share configuration validation and lazy guide preparation. */
56
- export function prepareSquareBuild(options, snippet) {
68
+ export function prepareSquareBuild(options, snippet, includeGuides = true) {
57
69
  if (options.hardCap !== undefined && options.hardCap !== null
58
70
  && (!Number.isSafeInteger(options.hardCap) || options.hardCap < 1))
59
71
  throw new SquareError('invalid_args', 'hardCap must be a positive integer or null');
60
72
  if (options.throttlePerMinute !== undefined
61
73
  && (!Number.isSafeInteger(options.throttlePerMinute) || options.throttlePerMinute < 1))
62
74
  throw new SquareError('invalid_args', 'throttlePerMinute must be a positive integer');
63
- const guides = [readGuide('participant')];
64
- if (options.template !== undefined)
75
+ const guides = includeGuides ? [readGuide('participant')] : [];
76
+ if (includeGuides && options.template !== undefined)
65
77
  guides.push(readGuide(options.template));
66
78
  return {
67
79
  hardCap: options.hardCap ?? null,
68
80
  ...(options.throttlePerMinute === undefined ? {} : { throttlePerMinute: options.throttlePerMinute }),
69
- preamble: normalizedLines(snippet), warmup: normalizedLines(guides.join('\n\n')),
81
+ preamble: normalizedLines(snippet), warmup: includeGuides ? normalizedLines(guides.join('\n\n')) : [],
70
82
  acts: [], runtime: emptyRuntimeState(),
71
83
  };
72
84
  }
73
85
  /** File build owns publication and CLI conflict semantics, not host resources. */
74
- export async function createSquare(location, options, snippet, signal) {
75
- const state = prepareSquareBuild(options, snippet);
86
+ export async function createSquare(location, options, snippet, signal, includeGuides = true) {
87
+ const state = prepareSquareBuild(options, snippet, includeGuides);
76
88
  if (options.force) {
77
89
  await writeSquareFile(location, state, signal);
78
90
  }
@@ -81,359 +93,70 @@ export async function createSquare(location, options, snippet, signal) {
81
93
  }
82
94
  return { hardCap: state.hardCap, ...(state.throttlePerMinute === undefined ? {} : { throttlePerMinute: state.throttlePerMinute }) };
83
95
  }
84
- function requireSquareExtension(location) {
85
- if (!location.endsWith('.square'))
86
- throw new SquareError('invalid_args', `Square artifacts must use the .square extension: ${location}`);
87
- }
88
- function storageError(error, location) {
89
- if (!(error instanceof Error) || error instanceof SquareError || error instanceof InternalSquareError)
90
- return error;
91
- if (/unable to open database file|no such file|SQLITE_CANTOPEN/i.test(error.message))
92
- return new InternalSquareError('not_found', `square file not found: ${location}`);
93
- if (/not a database|SQLITE_NOTADB/i.test(error.message))
94
- return invalidArtifact('not a SQLite database.');
95
- return error;
96
- }
97
- /** Private artifact-boundary connection shared with explicit migration. Never creates a path. */
98
- export function openExistingSquareDatabase(location) {
99
- requireSquareExtension(location);
100
- const uri = pathToFileURL(path.resolve(location));
101
- uri.searchParams.set('mode', 'rw');
102
- try {
103
- return new DatabaseSync(uri.href);
104
- }
105
- catch (error) {
106
- throw storageError(error, location);
107
- }
108
- }
109
- function abort(signal) {
110
- if (signal?.aborted)
111
- throw signal.reason ?? new Error('Square artifact operation aborted');
112
- }
113
- function synchronous(value) {
114
- if (value !== null && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function') {
115
- void Promise.resolve(value).catch(() => undefined);
116
- throw new TypeError('Square artifact callbacks must be synchronous.');
117
- }
118
- }
119
96
  const readMethods = [
120
97
  'header', 'participant', 'roster', 'attention', 'incomingAttention',
121
- 'activity', 'activityHeads', 'activities', 'activityCount', 'contextIndexes', 'sayOrdinals', 'recentSayTimes',
122
- 'audience', 'directed', 'directedAt', 'directedCount', 'pendingAudience',
98
+ 'activity', 'activityByMessage', 'activityHeads', 'activities', 'activityCount', 'contextIndexes', 'sayOrdinals', 'recentSayTimes',
99
+ 'audience', 'directed', 'directedAt', 'directedCount',
123
100
  'receipt', 'receiptFacts', 'lease', 'participantCounts', 'seat', 'seatsForOwner',
124
101
  'deliveryEvidence', 'deliveryByToken', 'cancelledThrough', 'deliveryDeferrals',
125
102
  ];
126
- const writeMethods = ['applyLanding', 'markSeen', 'putLease', 'removeLease', 'storeSeat', 'removeSeat', 'updateWakeAddress', 'reattachSeat', 'detachSeat', 'cancelThrough', 'claimDelivery', 'prepareDelivery', 'completeDelivery', 'observeNativeDelivery', 'putDeliveryDeferral', 'renewDeliveryDeferral', 'releaseDeliveryDeferral'];
127
- /** One connection, ordered operations, guarded synchronous transaction capabilities. */
103
+ const writeMethods = ['applyLanding', 'putLease', 'removeLease', 'storeSeat', 'removeSeat', 'updateWakeAddress', 'reattachSeat', 'detachSeat'];
104
+ /** Agora composition over the same lower SQL and ordered store used by standalone Channel. */
128
105
  export function createArtifactPort(location, initial, externalSignal) {
129
- let database;
130
- let closed = false, inCallback = false;
131
- let tail = Promise.resolve();
132
- const cancel = new AbortController();
133
- const canonical = initial === undefined ? canonicalPath(location) : Promise.resolve(location);
134
- if (initial !== undefined) {
135
- database = new DatabaseSync(':memory:');
136
- try {
137
- initializeSqlite(database, initial);
138
- database.exec(`PRAGMA application_id=${APPLICATION_ID}; PRAGMA user_version=${CURRENT_USER_VERSION}`);
139
- }
140
- catch (error) {
141
- database.close();
142
- throw error;
143
- }
144
- }
145
- const combined = (signal) => AbortSignal.any([cancel.signal, ...(externalSignal ? [externalSignal] : []), ...(signal ? [signal] : [])]);
146
- const validate = (db) => validateSqliteHeader(db, initial === undefined ? location : ':memory:');
147
- async function connection(signal) {
148
- if (database)
149
- return database;
150
- location = await canonical;
151
- abort(signal);
152
- database = openExistingSquareDatabase(location);
153
- try {
154
- await retrySqliteBusy(() => validate(database), signal);
155
- database.exec('PRAGMA busy_timeout=0; PRAGMA synchronous=FULL');
156
- }
157
- catch (error) {
158
- database.close();
159
- database = undefined;
160
- throw storageError(error, location);
161
- }
162
- return database;
163
- }
164
- function queue(operation, operationSignal) {
165
- if (inCallback)
166
- throw new TypeError('Square artifact operations cannot nest inside a callback.');
167
- if (closed)
168
- return Promise.reject(new Error('Square artifact is closed'));
169
- const result = tail.then(() => {
170
- abort(operationSignal);
171
- return operation(operationSignal);
172
- });
173
- tail = result.then(() => undefined, () => undefined);
174
- return result;
175
- }
176
- async function transaction(callback, write, signal, authority) {
177
- const db = await connection(signal);
178
- return inSqliteTransaction(db, write, () => validate(db), (header) => {
179
- const driver = createSqliteDriver(db), version = header.revision;
180
- let active = true, failed = false, failure;
181
- function guard(fn) {
182
- return ((...args) => {
183
- if (!active)
184
- throw new Error('Square artifact transaction capability expired');
185
- abort(signal);
186
- if (failed)
187
- throw failure;
188
- try {
189
- return fn(...args);
190
- }
191
- catch (error) {
192
- failed = true;
193
- failure = storageError(error, location);
194
- throw failure;
195
- }
196
- });
197
- }
198
- const reads = Object.fromEntries(readMethods.map(name => [name, guard(driver[name])]));
199
- const writes = Object.fromEntries(writeMethods.map(name => [name, guard(driver[name])]));
106
+ const store = createArtifactStore(location, format, initial === undefined ? undefined : db => initializeSqlite(db, initial, 0, channelLowerSchema), externalSignal);
107
+ function transaction(select, write, authority, signal, settlement = false) {
108
+ return store.run(write, ({ db, guard, track }) => {
109
+ const channel = createChannelSqlite(db);
110
+ const driver = createSqliteDriver(db, channel);
111
+ track(driver);
112
+ const reads = Object.assign(Object.fromEntries(readMethods.map(name => [name, guard(driver[name])])), { channel: channelReads(channel, guard) });
113
+ const writes = Object.assign(Object.fromEntries(writeMethods.map(name => [name, guard(driver[name])])), { channel: channelWrites(channel, guard) });
200
114
  const gate = write ? wrapSquareWrite(reads, writes, authority) : undefined;
201
- const facts = gate?.facts ?? reads;
202
- let value;
203
- inCallback = true;
204
- try {
205
- value = callback(facts);
206
- synchronous(value);
207
- if (!failed)
208
- gate?.complete();
209
- }
210
- finally {
211
- inCallback = false;
212
- active = false;
213
- }
214
- if (failed)
215
- throw failure;
216
- const changed = write && driver.changed;
217
- const deliveryChanged = write && driver.deliveryChanged;
218
- if (changed || deliveryChanged) {
219
- if (version === Number.MAX_SAFE_INTEGER)
220
- throw invalidArtifact('revision cannot advance beyond the safe integer limit.');
221
- db.prepare('UPDATE square_meta SET revision=revision+?, delivery_revision=delivery_revision+? WHERE id=1').run(Number(changed), Number(deliveryChanged));
222
- }
223
- return {
224
- value, version: version + Number(changed), changed, deliveryChanged
225
- };
226
- }, signal);
227
- }
228
- const revision = signal => queue(async (s) => {
229
- const db = await connection(s);
230
- return inSqliteTransaction(db, false, () => validate(db), (header) => header.revision, s);
231
- }, combined(signal));
232
- return {
233
- read: (select, signal) => queue(async (s) => {
234
- const { value, version } = await transaction(select, false, s);
235
- return { value, version };
236
- }, combined(signal)),
237
- transactAs: (authority, change, signal) => queue(s => transaction(change, true, s, authority), combined(signal)),
238
- // Keep the acquired connection and authority, not the cancelled work lifetime. A
239
- // blocked evidence commit/cleanup gets five seconds, and close still cancels it.
240
- settleAs: (authority, change) => queue(s => transaction(change, true, s, authority), AbortSignal.any([cancel.signal, AbortSignal.timeout(5000)])),
241
- revision,
242
- async deliveryRevision(signal) {
243
- return queue(async (s) => {
244
- const db = await connection(s);
245
- return inSqliteTransaction(db, false, () => validate(db), (header) => header.deliveryRevision, s);
246
- }, combined(signal));
247
- },
248
- async claimDelivery(input, signal) {
249
- return (await this.transactAs({ kind: 'participant', seat: input.seat }, (facts) => facts.claimDelivery(input), signal)).value;
250
- },
251
- async prepareDelivery(input, signal) {
252
- return (await this.transactAs({ kind: 'participant', seat: input.seat }, facts => facts.prepareDelivery(input), signal)).value;
253
- },
254
- async completeDelivery(input, signal) {
255
- return (await this.transactAs({ kind: 'participant', seat: input.seat }, facts => facts.completeDelivery(input), signal)).value;
256
- },
115
+ const value = select(gate?.facts ?? reads);
116
+ gate?.complete();
117
+ return value;
118
+ }, signal, settlement);
119
+ }
120
+ const port = {
121
+ read: (select, signal) => transaction(select, false, undefined, signal),
122
+ transactAs: (authority, change, signal) => transaction(change, true, authority, signal),
123
+ settleAs: (authority, change) => transaction(change, true, authority, undefined, true),
124
+ revision: store.revision, deliveryRevision: store.deliveryRevision,
125
+ async claimDelivery(input, signal) { return (await port.transactAs({ kind: 'participant', seat: input.seat }, facts => facts.channel.claimDelivery(input), signal)).value; },
126
+ async prepareDelivery(input, signal) { return (await port.transactAs({ kind: 'participant', seat: input.seat }, facts => facts.channel.prepareDelivery(input), signal)).value; },
127
+ async completeDelivery(input, signal) { return (await port.transactAs({ kind: 'participant', seat: input.seat }, facts => facts.channel.completeDelivery(input), signal)).value; },
257
128
  async cancelDeliveries(seat, through, signal) {
258
- return (await this.transactAs({ kind: 'participant', seat }, facts => {
259
- const boundary = through ?? facts.header().lastActIndex;
260
- return boundary < 0 ? false : facts.cancelThrough(seat, boundary);
129
+ return (await port.transactAs({ kind: 'participant', seat }, facts => {
130
+ const boundary = through ?? facts.channel.header().lastMessageIndex;
131
+ return boundary < 0 ? false : facts.channel.cancelThrough(seat, boundary);
261
132
  }, signal)).value;
262
133
  },
263
- async changed(since, timeout, signal) {
264
- if (inCallback)
265
- throw new TypeError('Square artifact operations cannot nest inside a callback.');
266
- if (closed)
267
- return false;
268
- const s = combined(signal), deadline = Date.now() + Math.max(0, timeout);
269
- if (initial !== undefined) {
270
- try {
271
- do {
272
- if (await revision(s) > since)
273
- return true;
274
- if (Date.now() >= deadline)
275
- return false;
276
- await sleep(Math.min(25, deadline - Date.now()), undefined, { signal: s });
277
- } while (!closed);
278
- }
279
- catch (error) {
280
- if (closed)
281
- return false;
282
- abort(s);
283
- throw error;
284
- }
285
- return false;
286
- }
287
- const observer = await observeSquareRevision(await canonical);
288
- try {
289
- return await observer.changed(since, Math.max(0, deadline - Date.now()), s);
290
- }
291
- catch (error) {
292
- if (closed)
293
- return false;
294
- throw error;
295
- }
296
- finally {
297
- observer.close();
298
- }
299
- },
300
- async close() {
301
- if (inCallback)
302
- throw new TypeError('Square artifact cannot close inside a callback.');
303
- if (closed) {
304
- await tail;
305
- return;
306
- }
307
- closed = true;
308
- cancel.abort(new Error('Square artifact is closed'));
309
- await tail;
310
- database?.close();
311
- database = undefined;
312
- },
134
+ changed: store.changed, close: store.close,
313
135
  };
136
+ return port;
314
137
  }
315
- async function temporaryArtifact(location) {
316
- await fs.promises.mkdir(path.dirname(location), { recursive: true });
317
- const temporary = path.join(path.dirname(location), `.${path.basename(location)}.${process.pid}.${randomUUID()}.tmp`);
318
- const handle = await fs.promises.open(temporary, 'wx');
319
- await handle.close();
320
- return temporary;
321
- }
322
- async function initializeFile(location, state, signal) {
323
- const db = new DatabaseSync(location);
324
- try {
325
- db.exec('PRAGMA journal_mode=DELETE; PRAGMA synchronous=FULL');
326
- await inSqliteTransaction(db, true, () => { }, () => {
327
- initializeSqlite(db, state);
328
- db.exec(`PRAGMA application_id=${APPLICATION_ID}; PRAGMA user_version=${CURRENT_USER_VERSION}`);
329
- }, signal);
330
- }
331
- finally {
332
- db.close();
333
- }
334
- const handle = await fs.promises.open(location, 'r+');
335
- try {
336
- await handle.sync();
337
- }
338
- finally {
339
- await handle.close();
340
- }
341
- }
342
- /** New-format creation; initial authority is accepted only at this explicit boundary. */
343
- export async function createSquareFile(location, state, signal) {
344
- abort(signal);
345
- location = await canonicalPath(location);
346
- requireSquareExtension(location);
347
- const temporary = await temporaryArtifact(location);
348
- try {
349
- await initializeFile(temporary, state, signal);
350
- abort(signal);
351
- try {
352
- await fs.promises.link(temporary, location);
353
- return true;
354
- }
355
- catch (error) {
356
- if (error.code === 'EEXIST')
357
- return false;
358
- throw error;
359
- }
360
- }
361
- finally {
362
- await fs.promises.unlink(temporary).catch(() => undefined);
363
- }
364
- }
365
- /** Explicit build --force / fixture import, never a runtime state-replacement callback. */
366
- export async function writeSquareFile(location, state, signal) {
367
- location = await canonicalPath(location);
368
- if (await createSquareFile(location, state, signal))
369
- return;
370
- abort(signal);
371
- const db = openExistingSquareDatabase(location);
372
- try {
373
- await retrySqliteBusy(() => validateSqliteHeader(db, location), signal);
374
- db.exec('PRAGMA busy_timeout=0; PRAGMA synchronous=FULL');
375
- await inSqliteTransaction(db, true, () => validateSqliteHeader(db, location), () => {
376
- const revision = createSqliteDriver(db).header().revision;
377
- if (revision === Number.MAX_SAFE_INTEGER)
378
- throw invalidArtifact('revision cannot advance beyond the safe integer limit.');
379
- for (const table of ['landed_audience', 'attention', 'participants', 'delivery_deferrals', 'delivery_cancellations', 'delivery_evidence', 'seats', 'catch_leases', 'receipts', 'activities', 'square_meta'])
380
- db.exec(`DROP TABLE ${table}`);
381
- initializeSqlite(db, state, revision + 1);
382
- }, signal);
383
- }
384
- finally {
385
- db.close();
386
- }
138
+ export function createSquareFile(location, state, signal) {
139
+ return createStoreFile(location, format, db => initializeSqlite(db, state, 0, channelLowerSchema), signal);
387
140
  }
388
- async function inspectFile(location, select, signal) {
389
- const db = openExistingSquareDatabase(location);
390
- try {
391
- return await inSqliteTransaction(db, false, () => validateSqliteHeader(db, location), () => select(db), signal);
392
- }
393
- catch (error) {
394
- throw storageError(error, location);
395
- }
396
- finally {
397
- db.close();
398
- }
141
+ export function writeSquareFile(location, state, signal) {
142
+ return replaceStoreFile(location, format, (db, revision) => initializeSqlite(db, state, revision, channelLowerSchema), signal);
399
143
  }
400
- /** Explicit test/export projection. Hot callers must use the typed port. */
401
144
  export function readSquareSnapshot(location, signal) {
402
- return inspectFile(location, db => ({ state: exportSqliteState(db), revision: createSqliteDriver(db).header().revision }), signal);
145
+ return inspectStore(location, format, db => ({ state: exportSqliteState(db), revision: createSqliteDriver(db).header().revision }), signal);
403
146
  }
404
147
  export function readSquareRevision(location, signal) {
405
- return inspectFile(location, db => createSqliteDriver(db).header().revision, signal);
148
+ return inspectStore(location, format, db => createSqliteDriver(db).header().revision, signal);
406
149
  }
407
150
  export function readSquareDeliveryRevision(location, signal) {
408
- return inspectFile(location, db => createSqliteDriver(db).header().deliveryRevision, signal);
409
- }
410
- function observeRevision(location, revision) {
411
- const read = revision === 'square-revision' ? readSquareRevision : readSquareDeliveryRevision;
412
- return observeFileVersion(location, revision, async (file, signal) => {
413
- try {
414
- return await read(file, signal);
415
- }
416
- catch (error) {
417
- abort(signal);
418
- if (error instanceof InternalSquareError && error.code === 'not_found')
419
- return null;
420
- throw error;
421
- }
422
- });
423
- }
424
- export function observeSquareRevision(location) {
425
- return observeRevision(location, 'square-revision');
426
- }
427
- export function observeSquareDeliveryRevision(location) {
428
- return observeRevision(location, 'square-delivery-revision');
151
+ return inspectStore(location, format, db => createSqliteDriver(db).header().deliveryRevision, signal);
429
152
  }
153
+ export const observeSquareRevision = (location) => observeStoreRevision(location, format);
154
+ export const observeSquareDeliveryRevision = (location) => observeStoreRevision(location, format, true);
430
155
  export async function diagnoseSquareFile(location) {
431
156
  try {
432
- return { problems: [], ...await inspectFile(await canonicalPath(location), inspectSqlite) };
157
+ return { problems: [], ...await inspectStore(await canonicalPath(location), format, db => inspectSqlite(db, channelLowerSchema)) };
433
158
  }
434
159
  catch (error) {
435
- if (error instanceof SquareError && error.facts?.reason === 'migration_required')
436
- return { migrationRequired: error.message, problems: [] };
437
160
  return { unfixable: error instanceof Error ? error.message : String(error), problems: [] };
438
161
  }
439
162
  }
@@ -1,4 +1,5 @@
1
- import type { ActivityDelivery, DirectedNotificationRoute } from './model.js';
1
+ export { projectChannelNotification, renderSquareReminder } from './channel-presentation.js';
2
+ import type { DirectedNotificationRoute } from './model.js';
2
3
  export declare const ATTENTION_BODY_MAX = 200;
3
4
  export interface AttentionPreview {
4
5
  squarePath: string;
@@ -7,14 +8,39 @@ export interface AttentionPreview {
7
8
  actor: string;
8
9
  route: DirectedNotificationRoute;
9
10
  body: string;
10
- delivery?: ActivityDelivery;
11
+ preview?: string;
11
12
  }
12
- export declare function previewAttentionBody(body: string, delivery?: ActivityDelivery): string;
13
- export declare function attentionBodyIsClipped(body: string, delivery?: ActivityDelivery): boolean;
13
+ export declare function previewAttentionBody(body: string): string;
14
+ export declare function attentionBodyIsClipped(body: string): boolean;
14
15
  export declare function displayAttentionPath(squarePath: string): string;
15
16
  export declare function renderAttentionDescription({ actor, route }: Pick<AttentionPreview, 'actor' | 'route'>): string;
16
- /** Full notification text for human-facing expansion, never the bounded model preview. */
17
+ /** The sole source adapter: transports and cards consume this projection, not delivery modes. */
18
+ export declare function projectNotification(attention: AttentionPreview): {
19
+ text: string;
20
+ full: string;
21
+ preview: string;
22
+ description: string;
23
+ clipped: boolean;
24
+ agora: boolean;
25
+ };
26
+ /** One stored message: Agora framing when an upper say owns it, Channel's raw frame otherwise. */
27
+ export declare function projectMessageNotification(input: {
28
+ squarePath: string;
29
+ recipient: string;
30
+ actor: string;
31
+ body: string;
32
+ preview?: string;
33
+ agora?: {
34
+ readonly actIndex: number;
35
+ readonly route: DirectedNotificationRoute;
36
+ };
37
+ }): {
38
+ text: string;
39
+ full: string;
40
+ preview: string;
41
+ description: string;
42
+ clipped: boolean;
43
+ };
44
+ /** Full human expansion never substitutes a sender-owned preview for the stored body. */
17
45
  export declare function renderAttentionFull(attention: AttentionPreview): string;
18
- /** Shared model-facing envelope; transport correlation and human cards stay outside it. */
19
- export declare function renderSquareReminder(previews: readonly string[], deliveries?: readonly (ActivityDelivery | undefined)[]): string;
20
46
  export declare function renderAttentionPreview(attention: AttentionPreview): string;
@@ -1,21 +1,17 @@
1
+ import { escapeSquareTags, projectChannelNotification } from './channel-presentation.js';
2
+ export { projectChannelNotification, renderSquareReminder } from './channel-presentation.js';
1
3
  import { homedir } from 'node:os';
2
4
  import { formatActivityId } from './square-core.js';
3
5
  export const ATTENTION_BODY_MAX = 200;
4
- export function previewAttentionBody(body, delivery) {
5
- const compact = (delivery?.mode === 'direct' ? delivery.preview ?? body : body).replace(/\r\n/g, '\n');
6
- if (delivery?.mode === 'direct' || compact.length <= ATTENTION_BODY_MAX)
7
- return compact;
8
- return `${compact.slice(0, ATTENTION_BODY_MAX).trimEnd()}…`;
6
+ export function previewAttentionBody(body) {
7
+ const compact = body.replace(/\r\n/g, '\n');
8
+ return compact.length <= ATTENTION_BODY_MAX ? compact : `${compact.slice(0, ATTENTION_BODY_MAX).trimEnd()}…`;
9
9
  }
10
- export function attentionBodyIsClipped(body, delivery) {
11
- return delivery?.mode === 'direct'
12
- ? delivery.preview !== undefined
13
- : body.replace(/\r\n/g, '\n').length > ATTENTION_BODY_MAX;
10
+ export function attentionBodyIsClipped(body) {
11
+ return body.replace(/\r\n/g, '\n').length > ATTENTION_BODY_MAX;
14
12
  }
15
13
  export function displayAttentionPath(squarePath) {
16
- return squarePath.startsWith(homedir())
17
- ? `~${squarePath.slice(homedir().length)}`
18
- : squarePath;
14
+ return squarePath.startsWith(homedir()) ? `~${squarePath.slice(homedir().length)}` : squarePath;
19
15
  }
20
16
  export function renderAttentionDescription({ actor, route }) {
21
17
  switch (route) {
@@ -25,56 +21,15 @@ export function renderAttentionDescription({ actor, route }) {
25
21
  case 'bell': return `${actor} rang the bell`;
26
22
  }
27
23
  }
28
- /** Full notification text for human-facing expansion, never the bounded model preview. */
29
- export function renderAttentionFull(attention) {
30
- return attention.delivery?.mode === 'direct' ? renderDirectBlock(attention) : renderAttentionBlock(attention, attention.body);
31
- }
32
- /** Attribute values keep their quotes; the stored body and the human card stay unescaped. */
33
24
  function escapeAttribute(value) {
34
- return value
35
- .replace(/&/g, '&amp;')
36
- .replace(/"/g, '&quot;')
37
- .replace(/</g, '&lt;')
38
- .replace(/>/g, '&gt;');
39
- }
40
- /** A body may quote the envelope's own tags; in the preview they must never read as one. */
41
- function escapeSquareTags(text) {
42
- return text.replace(/<(\/?)(square-|system-reminder)/gi, '<\\$1$2');
43
- }
44
- function renderAttentionBlock(attention, body) {
45
- return [
46
- '<square-activity>',
47
- `· ${displayAttentionPath(attention.squarePath)} · ${attention.recipient} · ${formatActivityId(attention.actIndex)}`,
48
- `● ${renderAttentionDescription(attention)}`,
49
- '',
50
- body,
51
- '</square-activity>',
52
- ].join('\n');
53
- }
54
- function renderDirectBlock(attention) {
55
- return escapeSquareTags(previewAttentionBody(attention.body, attention.delivery));
56
- }
57
- /** Shared model-facing envelope; transport correlation and human cards stay outside it. */
58
- export function renderSquareReminder(previews, deliveries = []) {
59
- if (previews.length === 0)
60
- return '';
61
- const header = deliveries.some(item => item?.mode === 'direct')
62
- ? 'This is an external notification, not a direct message from the user.'
63
- : 'This is an external reminder from Square, not a direct message from the user.';
64
- return [
65
- '',
66
- '<system-reminder>',
67
- header,
68
- '',
69
- previews.join('\n\n'),
70
- '</system-reminder>',
71
- '',
72
- ].join('\n');
73
- }
74
- export function renderAttentionPreview(attention) {
75
- if (attention.delivery?.mode === 'direct')
76
- return renderDirectBlock(attention);
77
- const remaining = attention.body.replace(/\r\n/g, '\n').length - ATTENTION_BODY_MAX;
25
+ return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
26
+ }
27
+ /** The sole source adapter: transports and cards consume this projection, not delivery modes. */
28
+ export function projectNotification(attention) {
29
+ const body = attention.body.replace(/\r\n/g, '\n');
30
+ const description = renderAttentionDescription(attention);
31
+ const remaining = body.length - ATTENTION_BODY_MAX;
32
+ const preview = previewAttentionBody(body);
78
33
  const attributes = [
79
34
  `square="${escapeAttribute(displayAttentionPath(attention.squarePath))}"`,
80
35
  `as="${escapeAttribute(attention.recipient)}"`,
@@ -82,11 +37,20 @@ export function renderAttentionPreview(attention) {
82
37
  `from="${escapeAttribute(attention.actor)}"`,
83
38
  ...(remaining > 0 ? [`truncated="${remaining} more chars"`] : []),
84
39
  ];
85
- return [
86
- `<square-activity ${attributes.join(' ')}>`,
87
- `${renderAttentionDescription(attention)}.`,
88
- '',
89
- escapeSquareTags(previewAttentionBody(attention.body)),
90
- '</square-activity>',
91
- ].join('\n');
92
- }
40
+ return {
41
+ text: [`<square-activity ${attributes.join(' ')}>`, `${description}.`, '', escapeSquareTags(preview), '</square-activity>'].join('\n'),
42
+ full: ['<square-activity>', `· ${displayAttentionPath(attention.squarePath)} · ${attention.recipient} · ${formatActivityId(attention.actIndex)}`,
43
+ `● ${description}`, '', body, '</square-activity>'].join('\n'),
44
+ preview, description, clipped: remaining > 0, agora: true,
45
+ };
46
+ }
47
+ /** One stored message: Agora framing when an upper say owns it, Channel's raw frame otherwise. */
48
+ export function projectMessageNotification(input) {
49
+ if (input.agora === undefined)
50
+ return projectChannelNotification(input);
51
+ return projectNotification({ squarePath: input.squarePath, recipient: input.recipient, actor: input.actor,
52
+ route: input.agora.route, actIndex: input.agora.actIndex, body: input.body });
53
+ }
54
+ /** Full human expansion never substitutes a sender-owned preview for the stored body. */
55
+ export function renderAttentionFull(attention) { return projectNotification(attention).full; }
56
+ export function renderAttentionPreview(attention) { return projectNotification(attention).text; }
@@ -1,6 +1,4 @@
1
- import { pendingAtBoundary } from './inbox.js';
2
1
  import type { Owner, PendingMembership } from './model.js';
3
- export { pendingAtBoundary };
4
2
  export declare function renderPendingAtBoundary(inbox: PendingMembership[]): string;
5
3
  /**
6
4
  * Serialize a boundary across hook processes so one stale inbox cannot render twice.