@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,9 +1,7 @@
1
1
  import { type BuildOptions } from './model.js';
2
2
  import type { SquareResourceOwner, SquareArtifactPort } from './ports.js';
3
3
  import { type SquareState } from './square-sqlite.js';
4
- import type { ChannelArtifact } from './channel-ports.js';
5
4
  export { openExistingSquareDatabase } from './artifact-store.js';
6
- export declare function channelArtifact(port: SquareArtifactPort): ChannelArtifact;
7
5
  /** Explicit-lifetime holder of one reused artifact connection. Request cancellation
8
6
  * never becomes the shared connection's lifetime; close drains the owner once. */
9
7
  export declare function createSquareResourceOwner(): SquareResourceOwner;
@@ -24,7 +22,7 @@ export interface SquareSnapshot {
24
22
  readonly state: SquareState;
25
23
  readonly revision: number;
26
24
  }
27
- /** Agora composition over the same lower SQL and ordered store used by standalone Channel. */
25
+ /** Square social state and producer state share one synchronous artifact transaction. */
28
26
  export declare function createArtifactPort(location: string, initial?: SquareState, externalSignal?: AbortSignal): SquareArtifactPort;
29
27
  export declare function createSquareFile(location: string, state: SquareState, signal?: AbortSignal): Promise<boolean>;
30
28
  export declare function writeSquareFile(location: string, state: SquareState, signal?: AbortSignal): Promise<void>;
package/dist/artifact.js CHANGED
@@ -4,24 +4,10 @@ import { canonicalPath } from './canonical-path.js';
4
4
  import { InternalSquareError, SquareError } from './model.js';
5
5
  import { wrapSquareWrite } from './square-domain.js';
6
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
7
  import { resetStorageMeta } from './sqlite-framing.js';
10
8
  import { createArtifactStore, createStoreFile, replaceStoreFile, inspectStore, observeStoreRevision } from './artifact-store.js';
11
- import { channelReads, channelWrites } from './channel-artifact.js';
12
9
  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);
10
+ const format = squareStoreFormat(resetStorageMeta);
25
11
  /** Explicit-lifetime holder of one reused artifact connection. Request cancellation
26
12
  * never becomes the shared connection's lifetime; close drains the owner once. */
27
13
  export function createSquareResourceOwner() {
@@ -95,22 +81,21 @@ export async function createSquare(location, options, snippet, signal, includeGu
95
81
  }
96
82
  const readMethods = [
97
83
  'header', 'participant', 'roster', 'attention', 'incomingAttention',
98
- 'activity', 'activityByMessage', 'activityHeads', 'activities', 'activityCount', 'contextIndexes', 'sayOrdinals', 'recentSayTimes',
84
+ 'activity', 'activityHeads', 'activities', 'activityCount', 'contextIndexes', 'sayOrdinals', 'recentSayTimes',
99
85
  'audience', 'directed', 'directedAt', 'directedCount',
100
86
  'receipt', 'receiptFacts', 'lease', 'participantCounts', 'seat', 'seatsForOwner',
101
- 'deliveryEvidence', 'deliveryByToken', 'cancelledThrough', 'deliveryDeferrals',
87
+ 'pendingPosts', 'postedPosts', 'ownsPost',
102
88
  ];
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. */
89
+ const writeMethods = ['applyLanding', 'putLease', 'removeLease', 'storeSeat', 'removeSeat', 'markSeen', 'setSeatAttachment', 'insertNotice', 'ackMatching', 'acquirePost', 'settlePost', 'foldReceipt'];
90
+ /** Square social state and producer state share one synchronous artifact transaction. */
105
91
  export function createArtifactPort(location, initial, externalSignal) {
106
- const store = createArtifactStore(location, format, initial === undefined ? undefined : db => initializeSqlite(db, initial, 0, channelLowerSchema), externalSignal);
92
+ const store = createArtifactStore(location, format, initial === undefined ? undefined : db => initializeSqlite(db, initial, 0), externalSignal);
107
93
  function transaction(select, write, authority, signal, settlement = false) {
108
94
  return store.run(write, ({ db, guard, track }) => {
109
- const channel = createChannelSqlite(db);
110
- const driver = createSqliteDriver(db, channel);
95
+ const driver = createSqliteDriver(db);
111
96
  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) });
97
+ const reads = Object.fromEntries(readMethods.map(name => [name, guard(driver[name])]));
98
+ const writes = Object.fromEntries(writeMethods.map(name => [name, guard(driver[name])]));
114
99
  const gate = write ? wrapSquareWrite(reads, writes, authority) : undefined;
115
100
  const value = select(gate?.facts ?? reads);
116
101
  gate?.complete();
@@ -122,24 +107,15 @@ export function createArtifactPort(location, initial, externalSignal) {
122
107
  transactAs: (authority, change, signal) => transaction(change, true, authority, signal),
123
108
  settleAs: (authority, change) => transaction(change, true, authority, undefined, true),
124
109
  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; },
128
- async cancelDeliveries(seat, through, signal) {
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);
132
- }, signal)).value;
133
- },
134
110
  changed: store.changed, close: store.close,
135
111
  };
136
112
  return port;
137
113
  }
138
114
  export function createSquareFile(location, state, signal) {
139
- return createStoreFile(location, format, db => initializeSqlite(db, state, 0, channelLowerSchema), signal);
115
+ return createStoreFile(location, format, db => initializeSqlite(db, state, 0), signal);
140
116
  }
141
117
  export function writeSquareFile(location, state, signal) {
142
- return replaceStoreFile(location, format, (db, revision) => initializeSqlite(db, state, revision, channelLowerSchema), signal);
118
+ return replaceStoreFile(location, format, (db, revision) => initializeSqlite(db, state, revision), signal);
143
119
  }
144
120
  export function readSquareSnapshot(location, signal) {
145
121
  return inspectStore(location, format, db => ({ state: exportSqliteState(db), revision: createSqliteDriver(db).header().revision }), signal);
@@ -154,7 +130,7 @@ export const observeSquareRevision = (location) => observeStoreRevision(location
154
130
  export const observeSquareDeliveryRevision = (location) => observeStoreRevision(location, format, true);
155
131
  export async function diagnoseSquareFile(location) {
156
132
  try {
157
- return { problems: [], ...await inspectStore(await canonicalPath(location), format, db => inspectSqlite(db, channelLowerSchema)) };
133
+ return { problems: [], ...await inspectStore(await canonicalPath(location), format, db => inspectSqlite(db)) };
158
134
  }
159
135
  catch (error) {
160
136
  return { unfixable: error instanceof Error ? error.message : String(error), problems: [] };
@@ -1,4 +1,3 @@
1
- export { projectChannelNotification, renderSquareReminder } from './channel-presentation.js';
2
1
  import type { DirectedNotificationRoute } from './model.js';
3
2
  export declare const ATTENTION_BODY_MAX = 200;
4
3
  export interface AttentionPreview {
@@ -8,7 +7,7 @@ export interface AttentionPreview {
8
7
  actor: string;
9
8
  route: DirectedNotificationRoute;
10
9
  body: string;
11
- preview?: string;
10
+ metadataOnly?: boolean;
12
11
  }
13
12
  export declare function previewAttentionBody(body: string): string;
14
13
  export declare function attentionBodyIsClipped(body: string): boolean;
@@ -20,26 +19,7 @@ export declare function projectNotification(attention: AttentionPreview): {
20
19
  full: string;
21
20
  preview: string;
22
21
  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;
22
+ isFull: boolean;
43
23
  };
44
24
  /** Full human expansion never substitutes a sender-owned preview for the stored body. */
45
25
  export declare function renderAttentionFull(attention: AttentionPreview): string;
@@ -1,5 +1,3 @@
1
- import { escapeSquareTags, projectChannelNotification } from './channel-presentation.js';
2
- export { projectChannelNotification, renderSquareReminder } from './channel-presentation.js';
3
1
  import { homedir } from 'node:os';
4
2
  import { formatActivityId } from './square-core.js';
5
3
  export const ATTENTION_BODY_MAX = 200;
@@ -21,14 +19,18 @@ export function renderAttentionDescription({ actor, route }) {
21
19
  case 'bell': return `${actor} rang the bell`;
22
20
  }
23
21
  }
22
+ function escapeSquareTags(text) {
23
+ return text.replace(/<(\/?)(square-|channel-|system-reminder)/gi, '<\\$1$2');
24
+ }
24
25
  function escapeAttribute(value) {
25
26
  return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
26
27
  }
27
28
  /** The sole source adapter: transports and cards consume this projection, not delivery modes. */
28
29
  export function projectNotification(attention) {
29
- const body = attention.body.replace(/\r\n/g, '\n');
30
+ const body = attention.metadataOnly ? '(structured activity)' : attention.body.replace(/\r\n/g, '\n');
30
31
  const description = renderAttentionDescription(attention);
31
32
  const remaining = body.length - ATTENTION_BODY_MAX;
33
+ const isFull = !attention.metadataOnly && remaining <= 0;
32
34
  const preview = previewAttentionBody(body);
33
35
  const attributes = [
34
36
  `square="${escapeAttribute(displayAttentionPath(attention.squarePath))}"`,
@@ -38,19 +40,14 @@ export function projectNotification(attention) {
38
40
  ...(remaining > 0 ? [`truncated="${remaining} more chars"`] : []),
39
41
  ];
40
42
  return {
41
- text: [`<square-activity ${attributes.join(' ')}>`, `${description}.`, '', escapeSquareTags(preview), '</square-activity>'].join('\n'),
43
+ text: [`<square-activity ${attributes.join(' ')}>`, `${description}.`, '', escapeSquareTags(preview),
44
+ ...(!isFull ? ['', escapeSquareTags(`Use square_catch with location=${JSON.stringify(attention.squarePath)}, as=${JSON.stringify(attention.recipient)}, id=${JSON.stringify(formatActivityId(attention.actIndex))} for the full activity.`)] : []),
45
+ '</square-activity>'].join('\n'),
42
46
  full: ['<square-activity>', `· ${displayAttentionPath(attention.squarePath)} · ${attention.recipient} · ${formatActivityId(attention.actIndex)}`,
43
47
  `● ${description}`, '', body, '</square-activity>'].join('\n'),
44
- preview, description, clipped: remaining > 0, agora: true,
48
+ preview, description, isFull,
45
49
  };
46
50
  }
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
51
  /** Full human expansion never substitutes a sender-owned preview for the stored body. */
55
52
  export function renderAttentionFull(attention) { return projectNotification(attention).full; }
56
53
  export function renderAttentionPreview(attention) { return projectNotification(attention).text; }
@@ -1,14 +1,3 @@
1
- import type { Owner, PendingMembership } from './model.js';
2
- export declare function renderPendingAtBoundary(inbox: PendingMembership[]): string;
3
- /**
4
- * Serialize a boundary across hook processes so one stale inbox cannot render twice.
5
- *
6
- * `lookup` returns the session's memberships for this boundary. It may reuse an observation it has
7
- * verified against the current presence binding and artifact revision; the boundary still projects
8
- * presentation evidence live. Every item is claimed and revalidated before the batch sink;
9
- * after success, captured-seat commits record the winning batch, including budget omissions as
10
- * clipped (no consumption receipt), without an eligibility veto. Coverage is conservative if a
11
- * competing claim's removal lets a previously omitted body fit in the final preview.
12
- * A source that cannot verify its reuse must read the inbox again.
13
- */
14
- export declare function presentPendingAtBoundary<T>(identity: Owner, present: (context: string, delivered: PendingMembership[]) => T | Promise<T>, lookup?: (identity: Owner, env?: NodeJS.ProcessEnv, signal?: AbortSignal) => Promise<PendingMembership[]> | PendingMembership[], env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<T | undefined>;
1
+ import type { Owner } from './owner.js';
2
+ /** The sink must resolve only after the host has actually accepted the output. */
3
+ export declare function presentPendingAtBoundary<T>(owner: Owner, present: (context: string) => T | Promise<T>, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<T | undefined>;
@@ -1,115 +1,40 @@
1
- import { squareIndexPath } from './square-index.js';
2
- import { createHash } from 'node:crypto';
3
- import { withFileLock } from './file-lock.js';
4
- import { createArtifactPort } from './artifact.js';
5
- import { preparePendingPresentation } from './presentation-operations.js';
6
- import { presentPreparedBatch } from './delivery-effects.js';
7
- import { sessionInbox, projectBoundaryEligibility, pendingKey } from './inbox.js';
8
- import { projectChannelNotification, projectNotification, renderSquareReminder } from './attention-presentation.js';
1
+ import { DeliveryLedger } from './delivery-ledger.js';
2
+ import { deliveryFrameOverhead, renderDeliveryReminder } from './delivery-frame.js';
9
3
  const CONTEXT_MAX = 1200;
10
- export function renderPendingAtBoundary(inbox) {
11
- return renderBoundary(inbox).context;
12
- }
13
- function renderBoundary(inbox) {
14
- const queued = inbox.flatMap((membership) => membership.notifications.map((notification) => ({ membership, notification })));
15
- const blocks = [];
16
- const agoraBlocks = [];
17
- let remainingAgora = queued.filter(({ notification }) => notification.route !== undefined).length;
18
- const entries = [];
19
- let omitted = 0;
20
- const omission = (count) => `${count} more ${count === 1 ? 'activity' : 'activities'} not shown.`;
21
- for (const entry of queued) {
22
- const { membership, notification } = entry;
23
- // The omission line represents the rest of this batch too, but never consumes its bodies.
24
- const coverage = { membership, messageIndex: notification.messageIndex, markSeen: false };
25
- entries.push(coverage);
26
- const projected = notification.route === undefined
27
- ? projectChannelNotification({ squarePath: membership.location, recipient: membership.seat.participant,
28
- actor: notification.actor, body: notification.body, ...(notification.preview === undefined ? {} : { preview: notification.preview }) })
29
- : projectNotification({ squarePath: membership.location, actIndex: notification.actIndex ?? notification.messageIndex,
30
- recipient: membership.seat.participant, actor: notification.actor, route: notification.route,
31
- body: notification.body, ...(notification.preview === undefined ? {} : { preview: notification.preview }) });
32
- const block = projected.text;
33
- if ('agora' in projected && projected.agora) {
34
- remainingAgora -= 1;
35
- const omittedAfter = omitted + remainingAgora;
36
- const prospective = renderSquareReminder([...agoraBlocks, block, ...(omittedAfter > 0 ? [omission(omittedAfter)] : [])]);
37
- if (prospective.length > CONTEXT_MAX) {
38
- omitted += 1;
39
- continue;
40
- }
41
- agoraBlocks.push(block);
42
- }
43
- blocks.push(block);
44
- coverage.markSeen = !projected.clipped;
45
- }
46
- return {
47
- context: renderSquareReminder([...blocks, ...(omitted > 0 ? [omission(omitted)] : [])]),
48
- entries,
49
- };
50
- }
51
- async function presentPendingAtBoundaryUnlocked(identity, present, lookup = sessionInbox, env = process.env, signal) {
52
- const inbox = await lookup(identity, env, signal);
53
- if (signal?.aborted)
54
- return undefined;
55
- const pending = await projectBoundaryEligibility(inbox);
56
- if (pending.length === 0 || signal?.aborted)
57
- return undefined;
58
- const artifacts = new Map();
59
- const claims = [];
4
+ /** The sink must resolve only after the host has actually accepted the output. */
5
+ export async function presentPendingAtBoundary(owner, present, env = process.env, signal) {
6
+ const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY });
60
7
  try {
8
+ const session = await ledger.session(owner);
9
+ if (!session || session.state !== 'attached' || signal?.aborted)
10
+ return undefined;
11
+ const claim = await ledger.claimPresentation(owner, { generation: session.generation, budgetChars: CONTEXT_MAX - deliveryFrameOverhead({ tagged: true }), signal });
12
+ if (!claim)
13
+ return undefined;
14
+ let current;
61
15
  try {
62
- // Enumeration owns discovery/connections only; returned handles belong to the common runner.
63
- for (const entry of renderBoundary(pending).entries) {
64
- const { location, seat } = entry.membership;
65
- let artifact = artifacts.get(location);
66
- if (artifact === undefined) {
67
- // Keep connections alive through all signal-independent settlement/cleanup.
68
- artifact = createArtifactPort(location);
69
- artifacts.set(location, artifact);
70
- }
71
- const claim = await preparePendingPresentation({ artifact, location, participant: seat.participant,
72
- message: entry.messageIndex, seat, markSeen: entry.markSeen, signal });
73
- if (claim !== undefined)
74
- claims.push({ key: pendingKey(entry.membership, entry.messageIndex), claim });
75
- }
16
+ current = await claim.current(signal);
76
17
  }
77
18
  catch (error) {
78
- // Transfer has not happened: enumeration must abandon any custody already acquired.
79
- await Promise.all(claims.map(({ claim }) => claim.release()));
19
+ await claim.settle('released');
80
20
  throw error;
81
21
  }
82
- const report = await presentPreparedBatch(claims.map(({ claim }) => claim), eligibleClaims => {
83
- const eligible = new Set(claims.filter(({ claim }) => eligibleClaims.includes(claim)).map(({ key }) => key));
84
- const delivered = pending.map(membership => ({ ...membership,
85
- notifications: membership.notifications.filter(note => eligible.has(pendingKey(membership, note.messageIndex))),
86
- })).filter(membership => membership.notifications.length > 0);
87
- return { payload: { context: renderBoundary(delivered).context, delivered }, included: eligibleClaims };
88
- }, payload => present(payload.context, payload.delivered), { signal });
89
- if (report.observation.kind === 'threw')
90
- throw report.observation.error;
91
- for (const { result } of report.settlements)
92
- if (result.persistence.status === 'error')
93
- throw result.persistence.error;
94
- return report.observation.kind === 'returned' ? report.observation.value : undefined;
22
+ if (!current || signal?.aborted) {
23
+ await claim.settle('released');
24
+ return undefined;
25
+ }
26
+ let result;
27
+ try {
28
+ result = await present(renderDeliveryReminder(claim.cards.map(card => card.text), { tagged: true }));
29
+ }
30
+ catch (error) {
31
+ await claim.settle('unknown');
32
+ throw error;
33
+ }
34
+ await claim.settle('presented');
35
+ return result;
95
36
  }
96
37
  finally {
97
- await Promise.all([...artifacts.values()].map(artifact => artifact.close()));
38
+ await ledger.close();
98
39
  }
99
40
  }
100
- /**
101
- * Serialize a boundary across hook processes so one stale inbox cannot render twice.
102
- *
103
- * `lookup` returns the session's memberships for this boundary. It may reuse an observation it has
104
- * verified against the current presence binding and artifact revision; the boundary still projects
105
- * presentation evidence live. Every item is claimed and revalidated before the batch sink;
106
- * after success, captured-seat commits record the winning batch, including budget omissions as
107
- * clipped (no consumption receipt), without an eligibility veto. Coverage is conservative if a
108
- * competing claim's removal lets a previously omitted body fit in the final preview.
109
- * A source that cannot verify its reuse must read the inbox again.
110
- */
111
- export async function presentPendingAtBoundary(identity, present, lookup = sessionInbox, env = process.env, signal) {
112
- const indexPath = squareIndexPath(env);
113
- const ownerKey = createHash('sha256').update(JSON.stringify(identity)).digest('hex');
114
- return withFileLock(`${indexPath}.boundary-${ownerKey}.lock`, { retryMs: 10, signal }, () => presentPendingAtBoundaryUnlocked(identity, present, lookup, env, signal));
115
- }
@@ -0,0 +1 @@
1
+ export { isCardPresentation, type CardPresentation } from './plugins/paseo/shared/card-presentation.js';
@@ -0,0 +1,2 @@
1
+ // Keep the shared schema inside the standalone Paseo plugin's package boundary.
2
+ export { isCardPresentation } from './plugins/paseo/shared/card-presentation.js';
@@ -64,19 +64,13 @@ export function decideCatch(facts, requestedName, options, at) {
64
64
  }
65
65
  let consumed;
66
66
  try {
67
- // Receipts are recorded by message coordinate; the batch hydration above already resolved each selected say.
68
- let selected = new Map();
69
67
  consumed = consumeReceived({
70
68
  ...squareReceiveRead(facts),
71
- load(keys) { selected = facts.activities(keys).reduce((map, item) => map.set(item.index, item), new Map()); return selected; },
72
69
  addressed(recipient, index) {
73
70
  const head = facts.directed(recipient, { indexes: [index], kinds: ['say'] }, { edge: 'oldest', limit: 1 }).items[0];
74
71
  return head === undefined ? undefined : { key: head.index, via: head.route };
75
72
  },
76
- insertReceipt: (ref, time) => {
77
- const item = selected.get(ref.item);
78
- return item?.kind === 'say' && facts.channel.markSeen(ref.recipient, [item.messageIndex], time) > 0;
79
- },
73
+ insertReceipt: (ref, time) => facts.markSeen(ref.recipient, [ref.item], time) > 0,
80
74
  }, viewer, selection, at);
81
75
  }
82
76
  catch (error) {
@@ -1,12 +1,17 @@
1
+ import { type DeliveryHandoffRef } from './delivery-ledger.js';
1
2
  export { createClaudeWakeTransport, nativeTokens, nativePayload } from './claude-transport.js';
2
- /** Recognize the whole frame first; one inactive token vetoes it before any evidence/receipt write. */
3
- export declare function observeClaudeDelivery(sessionId: string, text: string, operation: 'guard' | 'admitted' | 'stored', env?: NodeJS.ProcessEnv): Promise<{
4
- recognized: boolean;
5
- current: boolean;
6
- }>;
7
3
  export type ClaudeBinding = {
8
- readonly location: string;
9
- readonly participant: string;
10
4
  readonly session: string;
11
- readonly generation?: string;
5
+ readonly generation: string;
12
6
  };
7
+ /** Extract complete frames and hash the exact bytes observed by the native host. */
8
+ export declare function claudeFrames(text: string): readonly {
9
+ token: string;
10
+ payloadHash: string;
11
+ }[];
12
+ /** Guard pre-effect; admit and observe only the exact captured handoff after each native effect. */
13
+ export declare function observeClaudeDelivery(sessionId: string, text: string, operation: 'guard' | 'admitted' | 'stored', env?: NodeJS.ProcessEnv, captured?: readonly DeliveryHandoffRef[]): Promise<{
14
+ recognized: boolean;
15
+ current: boolean;
16
+ refs?: readonly DeliveryHandoffRef[];
17
+ }>;
@@ -1,108 +1,55 @@
1
- import { createArtifactPort } from './artifact.js';
2
- import { presentPending } from './presentation-operations.js';
3
- import { nativePendingHead } from './delivery.js';
4
- import { decodeHarnessOwner } from './owner.js';
5
- import { indexedSquareEntries } from './square-index.js';
6
- import { parseMessageId } from './message.js';
1
+ import { DeliveryLedger } from './delivery-ledger.js';
2
+ import { harnessOwner } from './owner.js';
3
+ import { renderDeliveryReminder } from './delivery-frame.js';
4
+ import { hashPayload, nativeTokens } from './claude-transport.js';
7
5
  export { createClaudeWakeTransport, nativeTokens, nativePayload } from './claude-transport.js';
8
- import { nativeTokens, hashPayload } from './claude-transport.js';
9
- async function rowsForSession(sessionId, env, tokens) {
10
- const rows = [];
11
- const candidates = (await indexedSquareEntries({}, env)).filter(entry => {
12
- const coordinate = decodeHarnessOwner(entry.owner);
13
- return coordinate?.channel === 'claude-code' && coordinate.session === sessionId;
6
+ /** Extract complete frames and hash the exact bytes observed by the native host. */
7
+ export function claudeFrames(text) {
8
+ const prefix = renderDeliveryReminder([''], { tagged: false });
9
+ return [...text.matchAll(/(?:^|\n)(\[square-inbox:([a-zA-Z0-9-]+)\]\n[\s\S]*?\n\[\/square-inbox:\2\])(?=\n|$)/g)]
10
+ .flatMap(frame => {
11
+ const start = frame.index + frame[0].length - frame[1].length;
12
+ return text.slice(start - prefix.length, start) === prefix
13
+ ? [{ token: frame[2], payloadHash: hashPayload(prefix + frame[1]) }] : [];
14
14
  });
15
- const paths = new Set(candidates.map(candidate => candidate.location));
16
- for (const location of paths) {
17
- const artifact = createArtifactPort(location);
18
- try {
19
- const found = await artifact.read((facts) => tokens.flatMap((token) => {
20
- const evidence = facts.deliveryByToken(token);
21
- if (evidence?.kind !== 'wake' || evidence.correlation?.harness !== 'claude' || evidence.correlation.token !== token
22
- || evidence.route?.kind !== 'claude-native' || evidence.route.address.sessionId !== sessionId)
23
- return [];
24
- const seat = facts.seat(evidence.participant);
25
- return [{ artifact, evidence, location, index: parseMessageId(evidence.message),
26
- ...(seat === undefined ? {} : { seat: { participant: evidence.participant, owner: seat.owner, generation: evidence.generation } }) }];
27
- }));
28
- rows.push(...found.value);
29
- if (found.value.length === 0)
30
- await artifact.close();
31
- }
32
- catch {
33
- await artifact.close();
34
- }
35
- }
36
- return rows;
37
- }
38
- /** The token, participant, activity and captured generation must still be the exact wake. */
39
- function sameCapturedEvidence(facts, row) {
40
- const token = row.evidence.claimToken;
41
- const evidence = token === undefined ? undefined : facts.deliveryByToken(token);
42
- return token !== undefined && evidence !== undefined && row.seat !== undefined && row.seat.participant === row.evidence.participant
43
- && row.seat.generation === row.evidence.generation && evidence.kind === 'wake' && evidence.claimToken === token
44
- && evidence.participant === row.evidence.participant && evidence.message === row.evidence.message
45
- && evidence.generation === row.evidence.generation
46
- && evidence.correlation?.harness === 'claude' && evidence.correlation.token === token
47
- && evidence.correlation.payloadHash === row.evidence.correlation?.payloadHash
48
- && ['dispatching', 'unknown', 'accepted'].includes(evidence.outcome);
49
- }
50
- function capturedInput(row, now) {
51
- if (row.seat === undefined || row.index === undefined || row.evidence.route === undefined)
52
- return undefined;
53
- return { location: row.location, participant: row.evidence.participant, generation: row.seat.generation,
54
- owner: row.seat.owner, index: row.index, route: row.evidence.route, now };
55
15
  }
56
- function currentRow(facts, row, now) {
57
- const input = capturedInput(row, now);
58
- return input !== undefined && sameCapturedEvidence(facts, row) && nativePendingHead(facts, input) !== undefined;
59
- }
60
- /** Recognize the whole frame first; one inactive token vetoes it before any evidence/receipt write. */
61
- export async function observeClaudeDelivery(sessionId, text, operation, env = process.env) {
16
+ /** Guard pre-effect; admit and observe only the exact captured handoff after each native effect. */
17
+ export async function observeClaudeDelivery(sessionId, text, operation, env = process.env, captured) {
62
18
  const tokens = nativeTokens(text);
63
- if (tokens.length === 0)
19
+ if (!tokens.length)
64
20
  return { recognized: false, current: false };
65
- const rows = await rowsForSession(sessionId, env, [...new Set(tokens)]);
66
- const recognized = rows.length > 0;
67
- const frames = [...text.matchAll(/(?:^|\n)(\[square-inbox:([a-zA-Z0-9-]+)\]\n[\s\S]*?\n\[\/square-inbox:\2\])(?=\n|$)/g)];
21
+ const frames = claudeFrames(text);
22
+ const owner = harnessOwner({ channel: 'claude-code', session: sessionId });
23
+ const ledger = await DeliveryLedger.open({ location: env.SQUARE_DELIVERY });
68
24
  try {
69
- // Every recognized token is vetted before any row can write: actual bytes against the
70
- // stored correlation hash, then independent current eligibility.
71
- const contentCurrent = rows.every(row => {
72
- const token = row.evidence.claimToken, hash = row.evidence.correlation?.payloadHash;
73
- if (token === undefined || hash === undefined || row.seat === undefined || row.index === undefined
74
- || !['dispatching', 'unknown', 'accepted'].includes(row.evidence.outcome))
75
- return false;
76
- const matching = frames.filter(frame => frame[2] === token);
77
- return matching.length === tokens.filter(candidate => candidate === token).length
78
- && matching.every(frame => hashPayload(frame[1]) === hash);
79
- });
80
- if (!recognized || !contentCurrent)
25
+ const found = await Promise.all(tokens.map(token => ledger.handoff(token)));
26
+ const recognized = found.some(Boolean);
27
+ if (!recognized)
28
+ return { recognized: false, current: false };
29
+ if (frames.length !== tokens.length || new Set(tokens).size !== tokens.length || found.some(ref => !ref))
81
30
  return { recognized, current: false };
82
- const now = Date.now();
83
- for (const row of rows) {
84
- if (!(await row.artifact.read(facts => currentRow(facts, row, now))).value)
85
- return { recognized, current: false };
31
+ const refs = found;
32
+ if (refs.some((ref, i) => ref.owner !== owner || ref.routeKind !== 'claude-native' || ref.token !== frames[i].token || ref.payloadHash !== frames[i].payloadHash))
33
+ return { recognized, current: false };
34
+ if (operation === 'admitted' && (!Array.isArray(captured) || captured.length !== refs.length || refs.some((ref, i) => {
35
+ const old = captured[i];
36
+ return !old || old.token !== ref.token || old.owner !== ref.owner || old.generation !== ref.generation || old.payloadHash !== ref.payloadHash || old.routeKind !== ref.routeKind;
37
+ })))
38
+ return { recognized, current: false };
39
+ if (operation === 'guard') {
40
+ for (const ref of refs)
41
+ if (!await ledger.guardHandoff(ref))
42
+ return { recognized, current: false };
43
+ return { recognized, current: true, refs };
86
44
  }
87
- for (const row of rows) {
88
- if (operation === 'guard')
89
- continue;
90
- const seat = row.seat;
91
- const current = (facts) => currentRow(facts, row, Date.now());
92
- const admitted = await row.artifact.transactAs({ kind: 'participant', seat }, facts => current(facts) && facts.channel.completeDelivery({ seat, message: row.evidence.message,
93
- claimToken: row.evidence.claimToken, outcome: 'accepted', at: Date.now() })).catch(() => ({ value: false }));
94
- if (!admitted.value)
45
+ for (const ref of refs) {
46
+ const recorded = operation === 'admitted' ? await ledger.acceptHandoff(ref) : await ledger.observePresentation(ref);
47
+ if (!recorded)
95
48
  return { recognized, current: false };
96
- if (operation === 'stored') {
97
- await presentPending({ artifact: row.artifact, location: row.location,
98
- // Persisted coverage decides consumption; the current renderer never guesses.
99
- participant: row.evidence.participant, message: row.evidence.message, seat, observationOf: row.evidence.claimToken,
100
- markSeen: row.evidence.coverage !== 'clipped', sink: { present() { } }, current });
101
- }
102
49
  }
103
50
  return { recognized, current: true };
104
51
  }
105
52
  finally {
106
- await Promise.all([...new Set(rows.map(row => row.artifact))].map(artifact => artifact.close()));
53
+ await ledger.close();
107
54
  }
108
55
  }
@@ -1,9 +1,9 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import { promises as fs } from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { deliveryLedgerLocation } from './delivery-ledger.js';
4
5
  import { z } from 'zod/v4';
5
6
  import { withFileLock } from './file-lock.js';
6
- import { squareIndexPath } from './square-index.js';
7
7
  import { SquareError } from './model.js';
8
8
  const LEASE_MS = 10 * 60 * 1000;
9
9
  const text = z.string().max(4096).refine(value => value.trim().length > 0 && !/[\x00-\x1f\x7f]/u.test(value));
@@ -12,7 +12,7 @@ const coordinate = z.object({
12
12
  });
13
13
  const record = coordinate.extend({ token: z.string().regex(/^[a-f0-9]{64}$/u), expiresAt: z.number() });
14
14
  const unavailable = () => new SquareError('unavailable', 'Claude MCP call context unavailable');
15
- const storeRoot = (env) => path.join(path.dirname(squareIndexPath(env)), 'claude-mcp-calls');
15
+ const storeRoot = (env) => path.join(path.dirname(deliveryLedgerLocation(env)), 'claude-mcp-calls');
16
16
  const callFile = (root, id) => path.join(root, `${createHash('sha256').update(id).digest('hex')}.json`);
17
17
  async function readRecord(file) {
18
18
  try {