@astrosheep/square 0.4.0 → 0.4.2

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 (196) 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-boundary-state.d.ts +20 -0
  11. package/dist/claude-boundary-state.js +84 -0
  12. package/dist/claude-hook.d.ts +2 -0
  13. package/dist/claude-hook.js +231 -0
  14. package/dist/claude-mcp-context.d.ts +1 -1
  15. package/dist/claude-mcp-context.js +12 -3
  16. package/dist/cli/context.js +7 -9
  17. package/dist/cli/observation-commands.d.ts +4 -1
  18. package/dist/cli/observation-commands.js +37 -4
  19. package/dist/cli/registry.js +5 -2
  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-claude.js +3 -4
  41. package/dist/harness-links.js +63 -7
  42. package/dist/harness.js +12 -17
  43. package/dist/help.js +4 -1
  44. package/dist/http-delivery.d.ts +5 -9
  45. package/dist/http-delivery.js +20 -77
  46. package/dist/inbox.d.ts +6 -49
  47. package/dist/inbox.js +24 -371
  48. package/dist/index.d.ts +6 -3
  49. package/dist/index.js +4 -1
  50. package/dist/mcp.js +2 -3
  51. package/dist/model.d.ts +7 -47
  52. package/dist/model.js +1 -7
  53. package/dist/notifications.d.ts +24 -20
  54. package/dist/notifications.js +39 -108
  55. package/dist/operation-control.d.ts +4 -0
  56. package/dist/operation-control.js +20 -0
  57. package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
  58. package/dist/packages/agent-delivery/src/claude-native.js +5 -0
  59. package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
  60. package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
  61. package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
  62. package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
  63. package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
  64. package/dist/packages/agent-delivery/src/codex.js +41 -4
  65. package/dist/packages/agent-delivery/src/index.d.ts +3 -1
  66. package/dist/packages/agent-delivery/src/index.js +1 -1
  67. package/dist/packages/agent-delivery/src/paseo.js +11 -2
  68. package/dist/packages/agent-delivery/src/types.d.ts +1 -0
  69. package/dist/paseo-delivery.d.ts +5 -5
  70. package/dist/paseo-delivery.js +19 -97
  71. package/dist/paseo.d.ts +1 -1
  72. package/dist/paseo.js +1 -1
  73. package/dist/plugins/opencode/index.d.ts +5 -3
  74. package/dist/plugins/opencode/index.js +99 -21
  75. package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
  76. package/dist/plugins/opencode-shared/delivery.js +109 -0
  77. package/dist/plugins/opencode-v1/index.d.ts +28 -0
  78. package/dist/plugins/opencode-v1/index.js +41 -0
  79. package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
  80. package/dist/plugins/opencode-v1/runtime.js +152 -0
  81. package/dist/plugins/opencode-v1/transport.d.ts +13 -0
  82. package/dist/plugins/opencode-v1/transport.js +50 -0
  83. package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
  84. package/dist/plugins/paseo/shared/card-presentation.js +7 -0
  85. package/dist/ports.d.ts +31 -90
  86. package/dist/session-lifecycle.d.ts +8 -7
  87. package/dist/session-lifecycle.js +40 -42
  88. package/dist/sqlite-square.d.ts +14 -23
  89. package/dist/sqlite-square.js +132 -139
  90. package/dist/square-api.js +4 -7
  91. package/dist/square-core.d.ts +0 -1
  92. package/dist/square-domain.d.ts +5 -17
  93. package/dist/square-domain.js +104 -221
  94. package/dist/square-facade.d.ts +18 -15
  95. package/dist/square-post.d.ts +53 -0
  96. package/dist/square-post.js +4 -0
  97. package/dist/square-posting.d.ts +13 -0
  98. package/dist/square-posting.js +174 -0
  99. package/dist/square-producer-sqlite.d.ts +11 -0
  100. package/dist/square-producer-sqlite.js +210 -0
  101. package/dist/square-sqlite.d.ts +15 -25
  102. package/dist/square-sqlite.js +150 -226
  103. package/dist/views.d.ts +2 -2
  104. package/dist/views.js +0 -1
  105. package/dist/wake-sink.d.ts +4 -11
  106. package/dist/wake-sink.js +8 -32
  107. package/dist/wake-transports.d.ts +4 -7
  108. package/dist/wake-transports.js +7 -39
  109. package/examples/symmetric-domains.mjs +29 -249
  110. package/guides/change-waits.md +13 -53
  111. package/guides/claude-native.md +7 -171
  112. package/guides/codex-delivery.md +55 -80
  113. package/guides/delivery-primitives.md +48 -207
  114. package/guides/mcp.md +1 -1
  115. package/guides/operation-cancellation.md +2 -5
  116. package/guides/participant-ownership.md +25 -89
  117. package/guides/pi-native-delivery.md +19 -56
  118. package/guides/pi-tools.md +6 -8
  119. package/guides/square-api.md +113 -615
  120. package/package.json +8 -8
  121. package/plugins/claude/.claude-plugin/plugin.json +2 -3
  122. package/plugins/claude/hooks/hooks.json +11 -1
  123. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  124. package/plugins/codex/hooks/hooks.json +11 -23
  125. package/plugins/opencode/index.ts +91 -23
  126. package/plugins/opencode-shared/delivery.ts +91 -0
  127. package/plugins/opencode-v1/index.ts +36 -0
  128. package/plugins/opencode-v1/runtime.ts +134 -0
  129. package/plugins/opencode-v1/transport.ts +41 -0
  130. package/plugins/paseo/OVERVIEW.md +5 -5
  131. package/plugins/paseo/README.md +7 -6
  132. package/plugins/paseo/client/markdown.tsx +5 -5
  133. package/plugins/paseo/client/square-message.tsx +28 -24
  134. package/plugins/paseo/client/transform.ts +8 -5
  135. package/plugins/paseo/shared/card-presentation.ts +16 -0
  136. package/plugins/paseo/shared/message.ts +6 -11
  137. package/plugins/pi/square-pi.js +156 -417
  138. package/dist/channel-artifact.d.ts +0 -10
  139. package/dist/channel-artifact.js +0 -55
  140. package/dist/channel-delivery.d.ts +0 -11
  141. package/dist/channel-delivery.js +0 -51
  142. package/dist/channel-domain.d.ts +0 -3
  143. package/dist/channel-domain.js +0 -83
  144. package/dist/channel-facade.d.ts +0 -76
  145. package/dist/channel-facade.js +0 -1
  146. package/dist/channel-internal.d.ts +0 -126
  147. package/dist/channel-internal.js +0 -440
  148. package/dist/channel-metadata.d.ts +0 -11
  149. package/dist/channel-metadata.js +0 -1
  150. package/dist/channel-ports.d.ts +0 -241
  151. package/dist/channel-ports.js +0 -1
  152. package/dist/channel-presentation.d.ts +0 -17
  153. package/dist/channel-presentation.js +0 -22
  154. package/dist/channel-sqlite-schema.d.ts +0 -13
  155. package/dist/channel-sqlite-schema.js +0 -81
  156. package/dist/channel-transport.d.ts +0 -62
  157. package/dist/channel-transport.js +0 -1
  158. package/dist/channel.d.ts +0 -84
  159. package/dist/channel.js +0 -60
  160. package/dist/claude-delivery-status.d.ts +0 -13
  161. package/dist/claude-delivery-status.js +0 -46
  162. package/dist/claude-delivery.d.ts +0 -12
  163. package/dist/claude-delivery.js +0 -108
  164. package/dist/claude-mod.d.ts +0 -8
  165. package/dist/claude-mod.js +0 -172
  166. package/dist/claude-transport.d.ts +0 -5
  167. package/dist/claude-transport.js +0 -58
  168. package/dist/cmd/notify-once.d.ts +0 -2
  169. package/dist/cmd/notify-once.js +0 -29
  170. package/dist/delivery-attempts.d.ts +0 -52
  171. package/dist/delivery-attempts.js +0 -73
  172. package/dist/delivery-effects.d.ts +0 -139
  173. package/dist/delivery-effects.js +0 -163
  174. package/dist/delivery-health.d.ts +0 -51
  175. package/dist/delivery-health.js +0 -173
  176. package/dist/delivery-operations.d.ts +0 -22
  177. package/dist/delivery-operations.js +0 -198
  178. package/dist/delivery-primitives.d.ts +0 -5
  179. package/dist/delivery-primitives.js +0 -3
  180. package/dist/delivery.d.ts +0 -56
  181. package/dist/delivery.js +0 -80
  182. package/dist/plugins/opencode/delivery.d.ts +0 -19
  183. package/dist/plugins/opencode/delivery.js +0 -223
  184. package/dist/presentation-operations.d.ts +0 -5
  185. package/dist/presentation-operations.js +0 -86
  186. package/dist/sqlite-channel.d.ts +0 -15
  187. package/dist/sqlite-channel.js +0 -600
  188. package/dist/square-index.d.ts +0 -16
  189. package/dist/square-index.js +0 -106
  190. package/dist/wake-preview.d.ts +0 -7
  191. package/dist/wake-preview.js +0 -15
  192. package/plugins/claude/hooks/register.js +0 -132
  193. package/plugins/claude/types.d.ts +0 -7
  194. package/plugins/opencode/delivery.ts +0 -198
  195. /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
  196. /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) {
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod/v4';
2
+ declare const stateSchema: z.ZodObject<{
3
+ generation: z.ZodString;
4
+ promptId: z.ZodOptional<z.ZodString>;
5
+ stopEpoch: z.ZodString;
6
+ consumed: z.ZodBoolean;
7
+ ended: z.ZodBoolean;
8
+ }, z.core.$strip>;
9
+ export type ClaudeBoundary = z.infer<typeof stateSchema>;
10
+ /** Receiver operational facts only: no cards, claims, or payload copies. */
11
+ export declare function claudeBoundaryStore(session: string, env: NodeJS.ProcessEnv): {
12
+ start(attach: () => Promise<string | false>): Promise<void>;
13
+ end(): Promise<ClaudeBoundary | undefined>;
14
+ prompt(promptId: string | undefined): Promise<void>;
15
+ capture(promptId: string): Promise<ClaudeBoundary | undefined>;
16
+ current(captured: ClaudeBoundary): Promise<boolean>;
17
+ consume(captured: ClaudeBoundary): Promise<void>;
18
+ watch<T>(signal: AbortSignal, run: () => Promise<T>): Promise<T>;
19
+ };
20
+ export {};
@@ -0,0 +1,84 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { z } from 'zod/v4';
5
+ import { deliveryLedgerLocation } from './delivery-ledger.js';
6
+ import { withFileLock } from './file-lock.js';
7
+ const stateSchema = z.object({
8
+ generation: z.string().min(1), promptId: z.string().min(1).optional(),
9
+ stopEpoch: z.string().min(1), consumed: z.boolean(), ended: z.boolean(),
10
+ });
11
+ /** Receiver operational facts only: no cards, claims, or payload copies. */
12
+ export function claudeBoundaryStore(session, env) {
13
+ const root = path.join(path.dirname(deliveryLedgerLocation(env)), 'claude-hooks');
14
+ const key = createHash('sha256').update(session).digest('hex');
15
+ const file = path.join(root, `${key}.json`);
16
+ async function mutate(change) {
17
+ await fs.mkdir(root, { recursive: true, mode: 0o700 });
18
+ await fs.chmod(root, 0o700);
19
+ return withFileLock(path.join(root, `${key}.lock`), { retryMs: 10, signal: AbortSignal.timeout(2500) }, async () => {
20
+ let state;
21
+ try {
22
+ state = stateSchema.parse(JSON.parse(await fs.readFile(file, 'utf8')));
23
+ }
24
+ catch (error) {
25
+ if (error.code !== 'ENOENT')
26
+ throw error;
27
+ }
28
+ return change(state, async (next) => {
29
+ const temporary = `${file}.${randomUUID()}.tmp`;
30
+ try {
31
+ await fs.writeFile(temporary, JSON.stringify(next), { mode: 0o600, flag: 'wx' });
32
+ await fs.rename(temporary, file);
33
+ }
34
+ finally {
35
+ await fs.rm(temporary, { force: true });
36
+ }
37
+ });
38
+ });
39
+ }
40
+ const matches = (state, captured) => !!state && !state.ended && !state.consumed
41
+ && state.generation === captured.generation && state.promptId === captured.promptId && state.stopEpoch === captured.stopEpoch;
42
+ return {
43
+ async start(attach) {
44
+ await mutate(async (state, save) => {
45
+ if (state)
46
+ await save({ ...state, promptId: undefined, ended: true, stopEpoch: randomUUID() });
47
+ const generation = await attach();
48
+ if (generation)
49
+ await save({ generation, stopEpoch: randomUUID(), consumed: false, ended: false });
50
+ });
51
+ },
52
+ async end() {
53
+ return mutate(async (state, save) => {
54
+ if (state)
55
+ await save({ ...state, ended: true, stopEpoch: randomUUID() });
56
+ return state;
57
+ });
58
+ },
59
+ async prompt(promptId) {
60
+ await mutate(async (state, save) => {
61
+ if (state && !state.ended)
62
+ await save({ ...state, promptId, stopEpoch: randomUUID(), consumed: false });
63
+ });
64
+ },
65
+ capture(promptId) {
66
+ return mutate(async (state) => state && !state.ended && !state.consumed && state.promptId === promptId ? state : undefined);
67
+ },
68
+ current(captured) { return mutate(async (state) => matches(state, captured)); },
69
+ async consume(captured) {
70
+ await mutate(async (state, save) => {
71
+ if (matches(state, captured))
72
+ await save({ ...state, consumed: true });
73
+ });
74
+ },
75
+ async watch(signal, run) {
76
+ await fs.mkdir(root, { recursive: true, mode: 0o700 });
77
+ const lock = path.join(root, `${key}.watch.lock`);
78
+ const handle = await fs.open(lock, 'a', 0o600);
79
+ await handle.close();
80
+ // A newer Stop waits through bounded shutdown; it must not skip a busy lock.
81
+ return withFileLock(lock, { retryMs: 10, signal }, run);
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runClaudeHook(text: string, env?: NodeJS.ProcessEnv, sink?: (value: string) => void | Promise<void>): Promise<string>;
2
+ export declare function runClaudeWatch(text: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal, output?: (frame: string) => Promise<void>): Promise<string>;