@astrosheep/square 0.3.63 → 0.3.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/claude-plugin/.claude-plugin/plugin.json +3 -2
  2. package/claude-plugin/hooks/hooks.json +1 -22
  3. package/claude-plugin/hooks/register.js +119 -0
  4. package/claude-plugin/types.d.ts +7 -0
  5. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  6. package/dist/activity-feed.d.ts +1 -10
  7. package/dist/activity-feed.js +1 -27
  8. package/dist/activity.js +57 -25
  9. package/dist/artifact.d.ts +1 -1
  10. package/dist/artifact.js +24 -25
  11. package/dist/attention-presentation.js +1 -0
  12. package/dist/automatic-session.d.ts +3 -1
  13. package/dist/automatic-session.js +21 -34
  14. package/dist/boundary-presentation.js +4 -13
  15. package/dist/canonical-path.d.ts +11 -0
  16. package/dist/canonical-path.js +47 -0
  17. package/dist/catch-decisions.js +14 -10
  18. package/dist/catch-selection.d.ts +6 -0
  19. package/dist/catch-selection.js +14 -0
  20. package/dist/claude-delivery.d.ts +11 -0
  21. package/dist/claude-delivery.js +145 -0
  22. package/dist/claude-mod.d.ts +8 -0
  23. package/dist/claude-mod.js +164 -0
  24. package/dist/cli/context.d.ts +0 -1
  25. package/dist/cli/context.js +9 -21
  26. package/dist/cli/harness-command.d.ts +0 -1
  27. package/dist/cli/harness-command.js +0 -6
  28. package/dist/cli/observation-commands.d.ts +1 -1
  29. package/dist/cli/observation-commands.js +31 -58
  30. package/dist/cli/program.js +20 -18
  31. package/dist/cli/registry.js +2 -2
  32. package/dist/cli/square-commands.js +10 -6
  33. package/dist/codex-boundary-state.d.ts +2 -2
  34. package/dist/codex-boundary-state.js +4 -4
  35. package/dist/codex-hook.js +12 -15
  36. package/dist/codex-queue.d.ts +2 -2
  37. package/dist/codex-queue.js +7 -5
  38. package/dist/decisions.d.ts +2 -6
  39. package/dist/decisions.js +16 -17
  40. package/dist/delivery-health.d.ts +1 -2
  41. package/dist/delivery-health.js +4 -5
  42. package/dist/delivery-operations.d.ts +2 -5
  43. package/dist/delivery-operations.js +77 -214
  44. package/dist/delivery.d.ts +25 -47
  45. package/dist/delivery.js +14 -46
  46. package/dist/diagnostic-redaction.js +7 -9
  47. package/dist/file-changes.d.ts +0 -1
  48. package/dist/file-changes.js +2 -19
  49. package/dist/harness-claude.js +8 -6
  50. package/dist/harness-codex.d.ts +0 -2
  51. package/dist/harness-codex.js +0 -2
  52. package/dist/harness-links.js +13 -17
  53. package/dist/harness.js +3 -7
  54. package/dist/help.js +4 -3
  55. package/dist/host-ledger-file-adapter.d.ts +17 -8
  56. package/dist/host-ledger-file-adapter.js +164 -80
  57. package/dist/host-ledger.d.ts +55 -50
  58. package/dist/inbox.d.ts +0 -3
  59. package/dist/inbox.js +88 -50
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js +1 -1
  62. package/dist/list.js +1 -4
  63. package/dist/mcp.js +24 -41
  64. package/dist/mention-parse.js +3 -9
  65. package/dist/model.d.ts +11 -2
  66. package/dist/model.js +1 -1
  67. package/dist/notifications.d.ts +3 -16
  68. package/dist/notifications.js +41 -122
  69. package/dist/opencode-delivery.d.ts +14 -0
  70. package/dist/opencode-delivery.js +171 -0
  71. package/dist/opencode.d.ts +39 -18
  72. package/dist/opencode.js +145 -40
  73. package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
  74. package/dist/packages/agent-delivery/src/claude-auth.js +75 -0
  75. package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
  76. package/dist/packages/agent-delivery/src/claude-native.js +68 -0
  77. package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
  78. package/dist/packages/agent-delivery/src/claude.js +66 -0
  79. package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
  80. package/dist/packages/agent-delivery/src/deadline.js +41 -0
  81. package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
  82. package/dist/packages/agent-delivery/src/errors.js +41 -0
  83. package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
  84. package/dist/packages/agent-delivery/src/guards.js +6 -0
  85. package/dist/packages/agent-delivery/src/index.d.ts +83 -0
  86. package/dist/packages/agent-delivery/src/index.js +110 -0
  87. package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
  88. package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
  89. package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
  90. package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
  91. package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
  92. package/dist/packages/agent-delivery/src/opencode.js +127 -0
  93. package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
  94. package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
  95. package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
  96. package/dist/packages/agent-delivery/src/paseo.js +106 -0
  97. package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
  98. package/dist/packages/agent-delivery/src/pi-client.js +106 -0
  99. package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
  100. package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
  101. package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
  102. package/dist/packages/agent-delivery/src/pi-send.js +4 -0
  103. package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
  104. package/dist/packages/agent-delivery/src/pi.js +209 -0
  105. package/dist/packages/agent-delivery/src/types.d.ts +120 -0
  106. package/dist/participant-host.js +12 -13
  107. package/dist/participant-identity.d.ts +21 -0
  108. package/dist/participant-identity.js +32 -11
  109. package/dist/paseo-delivery.d.ts +2 -2
  110. package/dist/paseo-delivery.js +35 -27
  111. package/dist/paseo-timeline.js +25 -21
  112. package/dist/paseo.d.ts +2 -2
  113. package/dist/paseo.js +1 -2
  114. package/dist/ports.d.ts +7 -20
  115. package/dist/presentation-operations.js +16 -4
  116. package/dist/presentation.d.ts +19 -10
  117. package/dist/presentation.js +99 -177
  118. package/dist/registry.d.ts +15 -66
  119. package/dist/registry.js +45 -123
  120. package/dist/routes.d.ts +1 -28
  121. package/dist/routes.js +22 -104
  122. package/dist/runtime.d.ts +3 -9
  123. package/dist/runtime.js +14 -67
  124. package/dist/search.d.ts +0 -2
  125. package/dist/search.js +3 -10
  126. package/dist/square-actions.js +48 -72
  127. package/dist/square-application.js +32 -30
  128. package/dist/square-core.d.ts +3 -0
  129. package/dist/square-core.js +22 -5
  130. package/dist/square-facade.d.ts +2 -0
  131. package/dist/square-file-adapter.d.ts +2 -1
  132. package/dist/square-file-adapter.js +6 -9
  133. package/dist/square-projections.d.ts +40 -45
  134. package/dist/square-projections.js +60 -74
  135. package/dist/square-state.js +1 -4
  136. package/dist/square-storage.d.ts +5 -8
  137. package/dist/square-storage.js +13 -36
  138. package/dist/square-wiring.d.ts +2 -7
  139. package/dist/square-wiring.js +6 -24
  140. package/dist/stream.js +4 -6
  141. package/dist/time.js +2 -7
  142. package/dist/views.d.ts +6 -18
  143. package/dist/views.js +14 -21
  144. package/dist/wake-attempts.d.ts +2 -56
  145. package/dist/wake-attempts.js +15 -110
  146. package/dist/wake-eligibility.d.ts +43 -0
  147. package/dist/wake-eligibility.js +106 -0
  148. package/dist/wake-evidence.d.ts +32 -9
  149. package/dist/wake-evidence.js +29 -24
  150. package/dist/wake-sink.d.ts +13 -9
  151. package/dist/wake-sink.js +35 -54
  152. package/dist/watch.js +13 -21
  153. package/extensions/square-pi.js +27 -13
  154. package/guides/change-waits.md +1 -1
  155. package/guides/claude-native.md +101 -0
  156. package/guides/host-ledger.md +3 -1
  157. package/guides/participant.md +14 -7
  158. package/guides/pi-native-delivery.md +49 -0
  159. package/package.json +4 -3
  160. package/dist/claude-hook.d.ts +0 -10
  161. package/dist/claude-hook.js +0 -60
  162. package/dist/paseo-connection.d.ts +0 -15
  163. package/dist/paseo-connection.js +0 -141
  164. package/dist/presence.d.ts +0 -2
  165. package/dist/presence.js +0 -24
  166. package/dist/presented.d.ts +0 -2
  167. package/dist/presented.js +0 -10
  168. package/dist/state-cell.d.ts +0 -14
  169. package/dist/wake-port.d.ts +0 -23
  170. package/dist/wake-port.js +0 -27
  171. /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
@@ -1,23 +1,7 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
1
  import { isDeepStrictEqual } from 'node:util';
4
- import { createSquareFile, createSquareState, diagnoseSquareFile as diagnoseArtifactFile, loadSquare, probeSquare, observeSquareRevision, readSquareSnapshot, transactSquareSnapshot, writeSquareFile, } from './artifact.js';
2
+ import { createSquareFile, createSquareState, diagnoseSquareFile as diagnoseArtifactFile, probeSquare, observeSquareRevision, readSquareSnapshot, transactSquareSnapshot, writeSquareFile, } from './artifact.js';
3
+ import { canonicalPath } from './canonical-path.js';
5
4
  export { createSquareState };
6
- async function canonicalPath(squarePath) {
7
- const absolute = path.resolve(squarePath);
8
- try {
9
- return await fs.promises.realpath(absolute);
10
- }
11
- catch (error) {
12
- if (error.code !== 'ENOENT')
13
- throw error;
14
- const parent = await fs.promises.realpath(path.dirname(absolute)).catch(() => path.dirname(absolute));
15
- return path.join(parent, path.basename(absolute));
16
- }
17
- }
18
- export async function readSquareFile(squarePath) {
19
- return loadSquare(await canonicalPath(squarePath));
20
- }
21
5
  export async function probeSquareFile(squarePath) {
22
6
  if (!squarePath.endsWith('.square'))
23
7
  return undefined;
@@ -39,7 +23,7 @@ function cloneState(squareState) {
39
23
  }
40
24
  function assertCellOpen(closed) {
41
25
  if (closed)
42
- throw new Error('StateCell is closed');
26
+ throw new Error('Square artifact is closed');
43
27
  }
44
28
  function isThenable(value) {
45
29
  return (typeof value === 'object' || typeof value === 'function') && value !== null
@@ -48,7 +32,7 @@ function isThenable(value) {
48
32
  function discardThenable(value) {
49
33
  void Promise.resolve(value).catch(() => undefined);
50
34
  }
51
- /** In-process cell for fast behavior tests and embedded consumers. */
35
+ /** In-process artifact for fast behavior tests and embedded consumers. */
52
36
  export function createMemoryCell(initial) {
53
37
  let state = cloneState(initial);
54
38
  let version = 0;
@@ -69,11 +53,11 @@ export function createMemoryCell(initial) {
69
53
  transact(fn, signal) {
70
54
  assertCellOpen(closed);
71
55
  if (signal?.aborted)
72
- throw signal.reason ?? new Error('StateCell operation aborted');
56
+ throw signal.reason ?? new Error('Square artifact operation aborted');
73
57
  const operation = tail.then(() => {
74
58
  assertCellOpen(closed);
75
59
  if (signal?.aborted)
76
- throw signal.reason ?? new Error('StateCell operation aborted');
60
+ throw signal.reason ?? new Error('Square artifact operation aborted');
77
61
  const current = cloneState(state);
78
62
  const outcome = fn(current, version);
79
63
  if (isThenable(outcome)) {
@@ -96,27 +80,27 @@ export function createMemoryCell(initial) {
96
80
  async read(signal) {
97
81
  assertCellOpen(closed);
98
82
  if (signal?.aborted)
99
- throw signal.reason ?? new Error('StateCell operation aborted');
83
+ throw signal.reason ?? new Error('Square artifact operation aborted');
100
84
  await tail;
101
85
  assertCellOpen(closed);
102
86
  if (signal?.aborted)
103
- throw signal.reason ?? new Error('StateCell operation aborted');
87
+ throw signal.reason ?? new Error('Square artifact operation aborted');
104
88
  return { state: cloneState(state), version };
105
89
  },
106
90
  changed(sinceVersion, timeoutMs, signal) {
107
91
  assertCellOpen(closed);
108
92
  if (signal?.aborted)
109
- return Promise.reject(signal.reason ?? new Error("StateCell operation aborted"));
93
+ return Promise.reject(signal.reason ?? new Error("Square artifact operation aborted"));
110
94
  if (version > sinceVersion)
111
95
  return Promise.resolve(true);
112
96
  if (timeoutMs <= 0)
113
97
  return Promise.resolve(false);
114
98
  return new Promise((resolve, reject) => {
115
99
  if (signal?.aborted) {
116
- reject(signal.reason ?? new Error("StateCell operation aborted"));
100
+ reject(signal.reason ?? new Error("Square artifact operation aborted"));
117
101
  return;
118
102
  }
119
- const abort = () => { waiters.delete(waiter); clearTimeout(waiter.timer); signal?.removeEventListener("abort", abort); reject(signal?.reason ?? new Error("StateCell operation aborted")); };
103
+ const abort = () => { waiters.delete(waiter); clearTimeout(waiter.timer); signal?.removeEventListener("abort", abort); reject(signal?.reason ?? new Error("Square artifact operation aborted")); };
120
104
  const waiter = {
121
105
  since: sinceVersion,
122
106
  resolve,
@@ -142,7 +126,7 @@ export function createMemoryCell(initial) {
142
126
  },
143
127
  };
144
128
  }
145
- /** SQLite-backed cell. Revisions are read from the authoritative database, not file metadata. */
129
+ /** SQLite-backed artifact. Revisions are read from the authoritative database, not file metadata. */
146
130
  export function createFileCell(squarePath, externalSignal) {
147
131
  let closed = false;
148
132
  let storage;
@@ -152,9 +136,6 @@ export function createFileCell(squarePath, externalSignal) {
152
136
  storage ??= canonicalPath(squarePath);
153
137
  return storage;
154
138
  }
155
- function abortError() {
156
- return new Error('StateCell is closed');
157
- }
158
139
  /** Close wins over every operation; a per-call deadline bounds busy retries alongside it. */
159
140
  function operationSignal(signal) {
160
141
  if (signal === undefined && externalSignal === undefined)
@@ -200,12 +181,8 @@ export function createFileCell(squarePath, externalSignal) {
200
181
  if (closed)
201
182
  return;
202
183
  closed = true;
203
- cancel.abort(abortError());
184
+ cancel.abort(new Error('Square artifact is closed'));
204
185
  await tail;
205
186
  },
206
187
  };
207
188
  }
208
- /** Consumer-facing file cell factory; keeps SQLite framing behind this module. */
209
- export function openSquareCell(squarePath, signal) {
210
- return createFileCell(squarePath, signal);
211
- }
@@ -5,7 +5,7 @@ export declare class Square {
5
5
  private readonly square;
6
6
  private readonly context;
7
7
  private constructor();
8
- static at(input: SquareAtInput): Promise<Square>;
8
+ static at(input: SquareAtInput, control?: OperationControl): Promise<Square>;
9
9
  static build(input: SquareBuildInput): Promise<Square>;
10
10
  static inMemory(input: Omit<SquareBuildInput, 'path'>): Square;
11
11
  join(name: string, control?: OperationControl): Promise<Participant>;
@@ -24,15 +24,10 @@ export declare class Square {
24
24
  readonly participant?: Participant;
25
25
  }>;
26
26
  participants(): Promise<ParticipantStatus[]>;
27
- snapshot(): Promise<SquareSnapshot>;
27
+ snapshot(control?: OperationControl): Promise<SquareSnapshot>;
28
28
  history(query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
29
29
  recognize(env: NodeJS.ProcessEnv): Promise<Participant | null>;
30
30
  close(): Promise<void>;
31
31
  endOwnedSession(name: string, sessionId: string, expectedEpoch?: number): Promise<ExpressResult | null>;
32
32
  retireEndedSessionRoutes(sessionId: string): Promise<void>;
33
33
  }
34
- export declare function markBoundarySeen(squarePath: string, name: string, actIndexes: readonly number[], at?: number): Promise<void>;
35
- export declare function openParticipant(input: SquareAtInput, name: string): Promise<{
36
- readonly participant: Participant;
37
- close(): Promise<void>;
38
- }>;
@@ -1,10 +1,10 @@
1
1
  import { closeOpenSquare } from './open-square.js';
2
2
  import { buildMemorySquare, buildSquare, openSquare } from './square-file-adapter.js';
3
3
  import { catchUp, done, endOwnedSession, express, hold, ignore, implicitJoin, join, takeover, listen, listening, resume, retireEndedSessionRoutes, } from './square-actions.js';
4
- import { markBoundarySeen as recordBoundarySeen } from './presence.js';
5
- import { history, participantHistory, participants, resolveParticipant, snapshot } from './views.js';
4
+ import { history, participantHistory, participants, snapshot } from './views.js';
6
5
  import { currentParticipant } from './views.js';
7
6
  import { projectSessionBindings } from './square-projections.js';
7
+ import { sessionIdsFromEnvironment } from './participant-identity.js';
8
8
  class ParticipantHandle {
9
9
  name;
10
10
  square;
@@ -51,8 +51,8 @@ export class Square {
51
51
  this.square = square;
52
52
  this.context = { ...square, location };
53
53
  }
54
- static async at(input) {
55
- return new Square(input.path, await openSquare(input.path, input));
54
+ static async at(input, control) {
55
+ return new Square(input.path, await openSquare(input.path, input, control));
56
56
  }
57
57
  static async build(input) {
58
58
  return new Square(input.path, await buildSquare(input.path, input));
@@ -85,13 +85,12 @@ export class Square {
85
85
  : { state: joined.state, participant: new ParticipantHandle(joined.name, this.square, this.context) };
86
86
  }
87
87
  participants() { return participants(this.square); }
88
- snapshot() { return snapshot(this.square); }
88
+ snapshot(control) { return snapshot(this.square, control); }
89
89
  history(query, control) { return history(this.square, query, control); }
90
90
  async recognize(env) {
91
91
  if (this.square.hostLedger === undefined)
92
92
  return null;
93
- const sessions = [env.CLAUDE_CODE_SESSION_ID, env.CODEX_THREAD_ID, env.OPENCODE_SESSION_ID, env.PI_SESSION_ID, env.PASEO_AGENT_ID]
94
- .map((value) => value?.trim()).filter((value) => Boolean(value));
93
+ const sessions = sessionIdsFromEnvironment(env);
95
94
  if (sessions.length === 0)
96
95
  return null;
97
96
  const candidates = (await Promise.all(sessions.map((sessionId) => projectSessionBindings({
@@ -108,20 +107,3 @@ export class Square {
108
107
  endOwnedSession(name, sessionId, expectedEpoch) { return endOwnedSession(this.context, name, sessionId, expectedEpoch); }
109
108
  retireEndedSessionRoutes(sessionId) { return retireEndedSessionRoutes(this.context, sessionId); }
110
109
  }
111
- export function markBoundarySeen(squarePath, name, actIndexes, at) {
112
- return recordBoundarySeen(squarePath, name, actIndexes, at);
113
- }
114
- export async function openParticipant(input, name) {
115
- const square = await openSquare(input.path, input);
116
- try {
117
- const known = await resolveParticipant(square, name);
118
- return {
119
- participant: new ParticipantHandle(known.name, square, square),
120
- close: () => closeOpenSquare(square),
121
- };
122
- }
123
- catch (error) {
124
- await closeOpenSquare(square);
125
- throw error;
126
- }
127
- }
package/dist/stream.js CHANGED
@@ -5,10 +5,6 @@ import { quoteShell } from './presentation.js';
5
5
  import { openSquare, observeSquareChanges } from './square-file-adapter.js';
6
6
  import { closeOpenSquare } from './open-square.js';
7
7
  import { streamProjection, streamTailProjection } from './views.js';
8
- async function writeNdjson(value) {
9
- if (!process.stdout.write(`${JSON.stringify(value)}\n`))
10
- await once(process.stdout, 'drain');
11
- }
12
8
  /** Machine-readable tailing stays available; interactive terminal rendering was retired. */
13
9
  export async function cmdStreamNdjson(squarePath, recipient, start = { kind: 'tail', last: 10 }) {
14
10
  if (!fs.existsSync(squarePath)) {
@@ -31,8 +27,10 @@ export async function cmdStreamNdjson(squarePath, recipient, start = { kind: 'ta
31
27
  ? await streamProjection(square, cursor, recipient)
32
28
  : await streamTailProjection(square, initial.last, recipient);
33
29
  for (const item of projection.activities) {
34
- await writeNdjson({ seq: item.activity.index, square: squarePath, ...item.activity,
35
- ...(item.route === undefined ? {} : { route: item.route }) });
30
+ const line = { seq: item.activity.index, square: squarePath, ...item.activity,
31
+ ...(item.route === undefined ? {} : { route: item.route }) };
32
+ if (!process.stdout.write(`${JSON.stringify(line)}\n`))
33
+ await once(process.stdout, 'drain');
36
34
  }
37
35
  cursor = projection.cursor;
38
36
  initial = undefined;
package/dist/time.js CHANGED
@@ -1,12 +1,6 @@
1
1
  function pad(value, length = 2) {
2
2
  return String(value).padStart(length, '0');
3
3
  }
4
- function formatOffset(date) {
5
- const offsetMinutes = -date.getTimezoneOffset();
6
- const sign = offsetMinutes >= 0 ? '+' : '-';
7
- const abs = Math.abs(offsetMinutes);
8
- return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
9
- }
10
4
  export function formatTimestamp(at) {
11
5
  const date = new Date(at);
12
6
  const time = date.getMilliseconds() !== 0
@@ -14,10 +8,11 @@ export function formatTimestamp(at) {
14
8
  : date.getSeconds() !== 0
15
9
  ? `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
16
10
  : `${pad(date.getHours())}:${pad(date.getMinutes())}`;
11
+ const offsetMinutes = -date.getTimezoneOffset();
17
12
  return [
18
13
  `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`,
19
14
  time,
20
- formatOffset(date),
15
+ `${offsetMinutes >= 0 ? '+' : '-'}${pad(Math.floor(Math.abs(offsetMinutes) / 60))}:${pad(Math.abs(offsetMinutes) % 60)}`,
21
16
  ].join(' ');
22
17
  }
23
18
  export function formatDuration(ms) {
package/dist/views.d.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { type ActivityId } from './square-core.js';
2
2
  import { coreParticipants, coreStatus } from './decisions.js';
3
3
  import { type DeliveryModel, type PlannedNotification } from './delivery.js';
4
- import { type ActivitiesOptions, type ActivityObservation, type InboxNotification, type PublicAct, type RoomChangeAct, type SquareState, type StoredAct } from './model.js';
4
+ import { type ActivitiesOptions, type PublicAct, type SquareState, type StoredAct } from './model.js';
5
5
  import type { OpenSquare } from './open-square.js';
6
6
  import type { Activity, HistoryQuery, OperationControl, ParticipantStatus, SquareSnapshot } from './square-facade.js';
7
7
  export interface ActivityPresentation {
8
8
  readonly name: string;
9
9
  readonly roster: readonly string[];
10
10
  readonly pendingPublic: readonly PublicAct[];
11
- readonly pendingRoomChanges: readonly RoomChangeAct[];
12
11
  readonly activities: readonly StoredAct[];
13
12
  readonly state: SquareState;
14
13
  readonly participantCount: number;
@@ -53,12 +52,6 @@ export interface WatchPresentation {
53
52
  };
54
53
  readonly terminalStatus?: 'capped' | 'quorum';
55
54
  }
56
- export interface InboxProjection {
57
- readonly name: string;
58
- readonly joined: boolean;
59
- readonly notifications: readonly InboxNotification[];
60
- readonly catchLease?: import('./model.js').WatchLease;
61
- }
62
55
  export interface StreamProjection {
63
56
  readonly activities: readonly {
64
57
  readonly activity: StoredAct;
@@ -71,15 +64,18 @@ export interface PendingDeliveryProjection {
71
64
  readonly recipient: string;
72
65
  readonly notifications: readonly PlannedNotification[];
73
66
  }
67
+ /** The one StoredAct-to-public-Activity field mapping. Rejects read and actorless acts. */
68
+ export declare function toPublicActivity(stored: StoredAct): Activity;
69
+ export declare function parseRequiredActivityId(id: ActivityId): number;
74
70
  export declare function history(square: OpenSquare, query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
75
71
  export declare function participantHistory(square: OpenSquare, _name: string, query?: HistoryQuery, control?: OperationControl): Promise<Activity[]>;
76
- export declare function resolveParticipant(square: OpenSquare, name: string): Promise<{
72
+ export declare function resolveParticipant(square: OpenSquare, name: string, control?: OperationControl): Promise<{
77
73
  readonly name: string;
78
74
  readonly roster: readonly string[];
79
75
  }>;
80
76
  export declare function currentParticipant(square: OpenSquare, name: string): Promise<string | undefined>;
81
77
  export declare function participants(square: OpenSquare): Promise<ParticipantStatus[]>;
82
- export declare function snapshot(square: OpenSquare): Promise<SquareSnapshot>;
78
+ export declare function snapshot(square: OpenSquare, control?: OperationControl): Promise<SquareSnapshot>;
83
79
  export declare function activityPresentation(square: OpenSquare, name: string): Promise<ActivityPresentation>;
84
80
  export declare function entryPresentation(square: OpenSquare, name: string, lastN?: number | null): Promise<EntryPresentation>;
85
81
  export declare function historyPresentation(square: OpenSquare, options: ActivitiesOptions): Promise<HistoryPresentation>;
@@ -92,7 +88,6 @@ export declare function eventPresentation(square: OpenSquare, id: ActivityId): P
92
88
  readonly held: boolean;
93
89
  }>;
94
90
  export declare function watchPresentation(square: OpenSquare, name: string): Promise<WatchPresentation>;
95
- export declare function inboxProjection(square: OpenSquare, name: string, _sessionId?: string): Promise<InboxProjection>;
96
91
  export declare function streamProjection(square: OpenSquare | {
97
92
  readonly cell: {
98
93
  read(): Promise<{
@@ -107,11 +102,4 @@ export declare function streamTailProjection(square: OpenSquare | {
107
102
  }>;
108
103
  };
109
104
  }, last?: number, recipient?: string): Promise<StreamProjection>;
110
- export declare function notificationForAct(square: OpenSquare, actIndex: number): Promise<readonly PlannedNotification[]>;
111
105
  export declare function pendingDeliveriesFromState(state: SquareState, delivery?: DeliveryModel): readonly PendingDeliveryProjection[];
112
- export declare function pendingDeliveries(square: OpenSquare): Promise<readonly PendingDeliveryProjection[]>;
113
- export declare function notificationEvidence(square: OpenSquare, recipient: string, actIndex: number): Promise<{
114
- readonly delivered: boolean;
115
- readonly observation: ActivityObservation | undefined;
116
- }>;
117
- export declare function notificationDelivered(square: OpenSquare, recipient: string, actIndex: number): Promise<boolean>;
package/dist/views.js CHANGED
@@ -3,13 +3,14 @@ import { deliveryDelta, directedPeerSays } from './activity-feed.js';
3
3
  import { coreActivities, coreParticipants, coreStatus, resolveKnownName } from './decisions.js';
4
4
  import { deriveDeliveryModel, isActivitySeen } from './delivery.js';
5
5
  import { SquareError, nameKey } from './model.js';
6
- import { countSays, currentHold, foldedState, freshWatchLease, inSquareCount, isCurrentlyJoined, presenceAnchor, resolveRosterName, rosterNames, watchTerminalStatus } from './runtime.js';
7
- function expose(stored) {
6
+ import { countSays, currentHold, foldedState, inSquareCount, isCurrentlyJoined, presenceAnchor, resolveRosterName, rosterNames, watchTerminalStatus } from './runtime.js';
7
+ /** The one StoredAct-to-public-Activity field mapping. Rejects read and actorless acts. */
8
+ export function toPublicActivity(stored) {
8
9
  if (stored.kind === 'read' || stored.actor === undefined)
9
10
  throw new Error(`Cannot expose stored activity ${formatActivityId(stored.index)}`);
10
11
  return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor, ...(stored.kind === 'say' && stored.reach !== undefined ? { reach: stored.reach } : {}), ...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}), mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}), ...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
11
12
  }
12
- function parseRequiredActivityId(id) {
13
+ export function parseRequiredActivityId(id) {
13
14
  const index = parseActivityId(id);
14
15
  if (index === undefined)
15
16
  throw new SquareError('invalid_args', `Invalid activity id: ${id}`);
@@ -59,13 +60,13 @@ function sayNumbers(state) {
59
60
  }
60
61
  return result;
61
62
  }
62
- export async function history(square, query = {}, control) { const { state } = await square.artifact.read(control?.signal); return selectHistory(coreActivities(state, historyOptions(query)), query).map(expose); }
63
- export async function participantHistory(square, _name, query = {}, control) { const { state } = await square.artifact.read(control?.signal); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective)), effective).map(expose); }
64
- export async function resolveParticipant(square, name) { const { state } = await square.artifact.read(); return { name: resolveKnownName(state, name), roster: rosterNames(state) }; }
63
+ export async function history(square, query = {}, control) { const { state } = await square.artifact.read(control?.signal); return selectHistory(coreActivities(state, historyOptions(query)), query).map(toPublicActivity); }
64
+ export async function participantHistory(square, _name, query = {}, control) { const { state } = await square.artifact.read(control?.signal); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective)), effective).map(toPublicActivity); }
65
+ export async function resolveParticipant(square, name, control) { const { state } = await square.artifact.read(control?.signal); return { name: resolveKnownName(state, name), roster: rosterNames(state) }; }
65
66
  export async function currentParticipant(square, name) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name); return known !== undefined && isCurrentlyJoined(state.acts, known) ? known : undefined; }
66
67
  export async function participants(square) { const { state } = await square.artifact.read(); return statuses(square, state); }
67
- export async function snapshot(square) { const { state } = await square.artifact.read(); const folded = foldedState(state); return { context: [...state.preamble, ...state.warmup].join('\n'), actCount: state.acts.filter((activity) => activity.kind !== 'read').length, hardCap: state.hardCap, ...(state.throttlePerMinute === undefined ? {} : { throttlePerMinute: state.throttlePerMinute }), held: folded.hold.active && folded.hold.actor !== undefined ? { by: folded.hold.actor, ...(folded.hold.reason === undefined ? {} : { reason: folded.hold.reason }) } : null, participants: statuses(square, state), delivered(name, id) { return isActivitySeen(state, name, parseRequiredActivityId(id)); } }; }
68
- export async function activityPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const delta = deliveryDelta(state, known, delivery); const hold = currentHold(state.acts); return { name: known, roster: rosterNames(state), pendingPublic: directedPeerSays(state, delta, known, delivery), pendingRoomChanges: [], activities: state.acts, state, participantCount: inSquareCount(state), held: hold.active, ...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: countSays(state.acts, known), hardCap: state.hardCap }; }
68
+ export async function snapshot(square, control) { const { state } = await square.artifact.read(control?.signal); const folded = foldedState(state); return { context: [...state.preamble, ...state.warmup].join('\n'), actCount: state.acts.filter((activity) => activity.kind !== 'read').length, hardCap: state.hardCap, ...(state.throttlePerMinute === undefined ? {} : { throttlePerMinute: state.throttlePerMinute }), held: folded.hold.active && folded.hold.actor !== undefined ? { by: folded.hold.actor, ...(folded.hold.reason === undefined ? {} : { reason: folded.hold.reason }) } : null, participants: statuses(square, state), delivered(name, id) { return isActivitySeen(state, name, parseRequiredActivityId(id)); } }; }
69
+ export async function activityPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const delta = deliveryDelta(state, known, delivery); const hold = currentHold(state.acts); return { name: known, roster: rosterNames(state), pendingPublic: directedPeerSays(state, delta, known, delivery), activities: state.acts, state, participantCount: inSquareCount(state), held: hold.active, ...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: countSays(state.acts, known), hardCap: state.hardCap }; }
69
70
  export async function entryPresentation(square, name, lastN = 10) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name) ?? name; const publicActivities = state.acts.filter((activity) => activity.kind === 'say' || activity.kind === 'done'); return { joined: isCurrentlyJoined(state.acts, known), scene: state.warmup.join('\n').trim(), context: state.preamble.join('\n').trim(), joinContext: (state.preamble.at(-1) === '---' ? state.preamble.slice(0, -1) : state.preamble).join('\n').trim(), recentActivities: lastN === null ? publicActivities : publicActivities.slice(-lastN), state, sayNumbers: sayNumbers(state), participantCount: inSquareCount(state) }; }
70
71
  export async function historyPresentation(square, options) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return { activities: coreActivities(state, options, delivery), sayNumbers: sayNumbers(state), presenceAnchors: anchors(state, delivery), participantCount: inSquareCount(state) }; }
71
72
  export async function participantsPresentation(square) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return coreParticipants(state, square.clock(), delivery); }
@@ -74,8 +75,6 @@ export async function statusPresentation(square) { const { state } = await squar
74
75
  export async function eventPresentation(square, id) { const { state } = await square.artifact.read(); const activity = state.acts.find((candidate) => candidate.index === parseRequiredActivityId(id)); if (activity === undefined)
75
76
  throw new SquareError('invalid_args', `Unknown activity id: ${id}`); return { activity, participantCount: inSquareCount(state), held: currentHold(state.acts).active }; }
76
77
  export async function watchPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const now = square.clock(); const terminal = watchTerminalStatus(state, known); const delivery = deriveDeliveryModel(state); return { activities: state.acts, state, participantCount: inSquareCount(state), presence: { participants: coreParticipants(state, now, delivery), now }, ...(terminal === undefined ? {} : { terminalStatus: terminal }) }; }
77
- export async function inboxProjection(square, name, _sessionId) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const known = delivery.knownParticipant(name); if (known === undefined || !delivery.joinedRecipients().some((recipient) => nameKey(recipient) === nameKey(known)))
78
- return { name, joined: false, notifications: [] }; const lease = freshWatchLease(state, known, square.clock()); return { name: known, joined: true, notifications: delivery.pendingFor(known).map(({ item, route }) => ({ actIndex: item.index, actor: item.actor, at: item.at, route, body: item.body })), ...(lease === undefined ? {} : { catchLease: lease }) }; }
79
78
  const STREAM_BATCH_MAX = 100;
80
79
  function projectStreamActivities(state, activities, recipient) {
81
80
  const delivery = recipient === undefined ? undefined : deriveDeliveryModel(state);
@@ -86,12 +85,6 @@ function projectStreamActivities(state, activities, recipient) {
86
85
  return notification === undefined ? [] : [{ activity, route: notification.route }];
87
86
  });
88
87
  }
89
- function selectStreamTail(activities, last) {
90
- if (!Number.isSafeInteger(last) || last < 0 || last > STREAM_BATCH_MAX) {
91
- throw new SquareError('invalid_args', `Invalid stream tail: expected a non-negative safe integer no greater than ${STREAM_BATCH_MAX}.`);
92
- }
93
- return last === 0 ? [] : activities.slice(-last);
94
- }
95
88
  export async function streamProjection(square, cursor, recipient) {
96
89
  const artifact = 'artifact' in square ? square.artifact : square.cell;
97
90
  const { state } = await artifact.read();
@@ -106,14 +99,14 @@ export async function streamProjection(square, cursor, recipient) {
106
99
  export async function streamTailProjection(square, last = 10, recipient) {
107
100
  const artifact = 'artifact' in square ? square.artifact : square.cell;
108
101
  const { state } = await artifact.read();
102
+ const activities = projectStreamActivities(state, state.acts, recipient);
103
+ if (!Number.isSafeInteger(last) || last < 0 || last > STREAM_BATCH_MAX) {
104
+ throw new SquareError('invalid_args', `Invalid stream tail: expected a non-negative safe integer no greater than ${STREAM_BATCH_MAX}.`);
105
+ }
109
106
  return {
110
- activities: selectStreamTail(projectStreamActivities(state, state.acts, recipient), last),
107
+ activities: last === 0 ? [] : activities.slice(-last),
111
108
  cursor: state.acts.at(-1)?.index ?? -1,
112
109
  hasMore: false,
113
110
  };
114
111
  }
115
- export async function notificationForAct(square, actIndex) { const { state } = await square.artifact.read(); const activity = state.acts.find((candidate) => candidate.index === actIndex); return activity === undefined ? [] : deriveDeliveryModel(state).plan(activity); }
116
112
  export function pendingDeliveriesFromState(state, delivery = deriveDeliveryModel(state)) { return delivery.joinedRecipients().map((recipient) => ({ recipient, notifications: delivery.pendingFor(recipient) })); }
117
- export async function pendingDeliveries(square) { const { state } = await square.artifact.read(); return pendingDeliveriesFromState(state); }
118
- export async function notificationEvidence(square, recipient, actIndex) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const known = delivery.knownParticipant(recipient) ?? recipient; return { delivered: delivery.isSeen(known, actIndex), observation: state.runtime.observations?.[known]?.[formatActivityId(actIndex)] }; }
119
- export async function notificationDelivered(square, recipient, actIndex) { const { state } = await square.artifact.read(); return deriveDeliveryModel(state).isSeen(recipient, actIndex); }
@@ -1,51 +1,5 @@
1
- import { type WakeRouteKind } from './model.js';
2
- import type { WakeAttempt as ProjectedWakeAttempt } from './square-projections.js';
3
- export { hasAttemptableWakeRoute, isWakeRouteAttemptable, terminalWakeEvidence } from './square-projections.js';
4
- export type WakeOutcome = 'accepted' | 'unknown' | 'failed';
5
- export interface WakeAttention {
6
- squarePath: string;
7
- actIndex: number;
8
- recipient: string;
9
- }
10
- export interface WakeReleaseDiagnostic {
11
- readonly at: number;
12
- readonly attention: WakeAttention;
13
- readonly routeKind?: WakeRouteKind;
14
- readonly attemptN?: number;
15
- readonly session?: string;
16
- readonly signature?: string;
17
- readonly message?: string;
18
- readonly diagnostic?: unknown;
19
- }
20
- export type WakeAttempt = ProjectedWakeAttempt;
21
- export interface WakeDispatchLease {
22
- readonly leaseId: string;
23
- readonly expiresAt: number;
24
- readonly phase: 'claimed' | 'dispatching';
25
- readonly routeKind?: WakeRouteKind;
26
- readonly attemptN?: number;
27
- readonly session?: string;
28
- }
29
- export type WakeDispatchClaim = {
30
- readonly type: 'acquired';
31
- readonly leaseId: string;
32
- } | {
33
- readonly type: 'busy';
34
- } | {
35
- readonly type: 'ambiguous';
36
- readonly lease: WakeDispatchLease;
37
- };
38
- export declare function wakeAttemptsPath(env?: NodeJS.ProcessEnv): string;
39
- export declare function wakeAttentionKey(attention: WakeAttention): Promise<string>;
40
- export declare function claimWakeDispatch(attention: WakeAttention, leaseId: string, leaseMs: number, env?: NodeJS.ProcessEnv, at?: number, session?: string): Promise<WakeDispatchClaim>;
41
- export declare function transitionWakeDispatch(attention: WakeAttention, leaseId: string, phase: WakeDispatchLease['phase'], leaseMs: number, routeKind: WakeRouteKind | undefined, attemptN: number | undefined, session: string | undefined, env?: NodeJS.ProcessEnv, at?: number): Promise<boolean>;
42
- export declare function releaseWakeDispatch(attention: WakeAttention, leaseId: string, env?: NodeJS.ProcessEnv, at?: number): Promise<void>;
43
- export declare function readWakeAttempts(opts?: {
44
- attention?: WakeAttention;
45
- sessionId?: string;
46
- now?: number;
47
- env?: NodeJS.ProcessEnv;
48
- }): Promise<WakeAttempt[]>;
1
+ import type { WakeAttention } from './host-ledger.js';
2
+ import { type WakeReleaseDiagnostic } from './wake-evidence.js';
49
3
  export declare function redactWakeDiagnostic(value: unknown, env?: NodeJS.ProcessEnv): unknown;
50
4
  export declare function readWakeReleaseDiagnostics(opts?: {
51
5
  location?: string;
@@ -54,11 +8,3 @@ export declare function readWakeReleaseDiagnostics(opts?: {
54
8
  now?: number;
55
9
  env?: NodeJS.ProcessEnv;
56
10
  }): Promise<WakeReleaseDiagnostic[]>;
57
- export declare function recordWakeAttempt(attempt: Omit<WakeAttempt, 'at'> & {
58
- at?: number;
59
- }, env?: NodeJS.ProcessEnv): Promise<WakeAttempt>;
60
- export declare function recordRecoveredUnknown(attention: WakeAttention, lease: {
61
- attemptN?: number;
62
- routeKind?: WakeRouteKind;
63
- session?: string;
64
- }, env?: NodeJS.ProcessEnv, at?: number): Promise<WakeAttempt | undefined>;
@@ -1,134 +1,39 @@
1
- import os from 'node:os';
2
1
  import { hostLedgerRoot } from './host-ledger-root.js';
3
- import path from 'node:path';
4
- import { isWakeRouteKind, nameKey } from './model.js';
5
- import { canonicalSquarePath } from './registry.js';
6
- import { formatActivityId, parseActivityId } from './square-core.js';
2
+ import { nameKey } from './model.js';
3
+ import { canonicalPath } from './canonical-path.js';
4
+ import { formatActivityId } from './square-core.js';
7
5
  import { createHostLedgerPort } from './host-ledger-file-adapter.js';
8
- import { terminalWakeEvidence } from './square-projections.js';
9
6
  import { redactDiagnostic } from './diagnostic-redaction.js';
10
- export { hasAttemptableWakeRoute, isWakeRouteAttemptable, terminalWakeEvidence } from './square-projections.js';
11
- const VALID_OUTCOMES = new Set(['accepted', 'unknown', 'failed']);
12
- function ledger(env) {
13
- return createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
14
- }
15
- export function wakeAttemptsPath(env = process.env) {
16
- return env.SQUARE_WAKE_ATTEMPTS || path.join(os.homedir(), '.square', 'wake-attempts.ndjsonl');
17
- }
18
- export async function wakeAttentionKey(attention) {
19
- return JSON.stringify([await canonicalSquarePath(attention.squarePath), formatActivityId(attention.actIndex), nameKey(attention.recipient)]);
20
- }
21
- export async function claimWakeDispatch(attention, leaseId, leaseMs, env = process.env, at = Date.now(), session) {
22
- return ledger(env).claimWakeDispatch({ attention, leaseId, leaseMs, at, session });
23
- }
24
- export async function transitionWakeDispatch(attention, leaseId, phase, leaseMs, routeKind, attemptN, session, env = process.env, at = Date.now()) {
25
- return ledger(env).transitionWakeDispatch({ attention, leaseId, phase, leaseMs, routeKind, attemptN, session, at });
26
- }
27
- export async function releaseWakeDispatch(attention, leaseId, env = process.env, at = Date.now()) {
28
- await ledger(env).releaseWakeDispatch({ attention, leaseId, at });
29
- }
30
- async function readRows(env, now) {
31
- const records = await ledger(env).listWakeAttempts({ now });
32
- return records.flatMap((record) => {
33
- const actIndex = parseActivityId(record.activity);
34
- if (actIndex === undefined || !isWakeRouteKind(record.routeKind) || !VALID_OUTCOMES.has(record.outcome) || typeof record.attemptN !== 'number')
35
- return [];
36
- return [{ at: record.at ?? now, attention: { squarePath: record.location, actIndex, recipient: record.participant }, routeKind: record.routeKind, outcome: record.outcome, attemptN: record.attemptN, ...(record.signature === undefined ? {} : { signature: record.signature }), ...(record.session === undefined ? {} : { session: record.session }), ...(record.message === undefined ? {} : { message: record.message }), ...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }) }];
37
- });
38
- }
39
- export async function readWakeAttempts(opts = {}) {
40
- const now = opts.now ?? Date.now();
41
- const expected = opts.attention === undefined ? undefined : await wakeAttentionKey(opts.attention);
42
- const attempts = await readRows(opts.env ?? process.env, now);
43
- const scoped = opts.sessionId === undefined ? attempts : attempts.filter((attempt) => attempt.session === opts.sessionId);
44
- if (expected === undefined)
45
- return scoped;
46
- const keys = await Promise.all(scoped.map((attempt) => wakeAttentionKey(attempt.attention)));
47
- return scoped.filter((_, index) => keys[index] === expected);
48
- }
7
+ import { decodeWakeEvidence } from './wake-evidence.js';
49
8
  export function redactWakeDiagnostic(value, env = process.env) {
50
9
  return redactDiagnostic(value, env.PASEO_PASSWORD);
51
10
  }
52
11
  export async function readWakeReleaseDiagnostics(opts = {}) {
53
12
  const now = opts.now ?? Date.now();
54
13
  const env = opts.env ?? process.env;
55
- const rows = await ledger(env).listEvidence({
14
+ const rows = await createHostLedgerPort({ rootPath: hostLedgerRoot(env) }).listEvidence({
56
15
  kind: 'wake',
57
16
  location: opts.location ?? opts.attention?.squarePath,
58
17
  includeReleased: true,
59
18
  now,
60
19
  });
61
- const expected = opts.attention === undefined ? undefined : await wakeAttentionKey(opts.attention);
20
+ const expected = opts.attention === undefined ? undefined : JSON.stringify([await canonicalPath(opts.attention.squarePath), formatActivityId(opts.attention.actIndex), nameKey(opts.attention.recipient)]);
62
21
  const releases = [];
63
22
  for (const row of rows) {
64
- if (row.outcome !== 'released')
23
+ const decoded = decodeWakeEvidence(row, now);
24
+ if (decoded?.kind !== 'release')
65
25
  continue;
66
- const actIndex = parseActivityId(row.activity);
67
- if (actIndex === undefined || row.at === undefined)
26
+ const release = decoded.value;
27
+ if (opts.sessionId !== undefined && release.session !== opts.sessionId)
68
28
  continue;
69
- if (opts.sessionId !== undefined && row.session !== opts.sessionId)
70
- continue;
71
- const attention = { squarePath: row.location, recipient: row.participant, actIndex };
72
- if (expected !== undefined && JSON.stringify([row.location, formatActivityId(actIndex), nameKey(row.participant)]) !== expected)
29
+ const { attention } = release;
30
+ if (expected !== undefined && JSON.stringify([attention.squarePath, formatActivityId(attention.actIndex), nameKey(attention.recipient)]) !== expected)
73
31
  continue;
74
32
  releases.push({
75
- at: row.at,
76
- attention,
77
- ...(row.routeKind === undefined ? {} : { routeKind: row.routeKind }),
78
- ...(row.attemptN === undefined ? {} : { attemptN: row.attemptN }),
79
- ...(row.session === undefined ? {} : { session: row.session }),
80
- ...(row.signature === undefined ? {} : { signature: row.signature }),
81
- ...(row.message === undefined ? {} : { message: redactWakeDiagnostic(row.message, env) }),
82
- ...(row.diagnostic === undefined ? {} : { diagnostic: redactWakeDiagnostic(row.diagnostic, env) }),
33
+ ...release,
34
+ ...(release.message === undefined ? {} : { message: redactWakeDiagnostic(release.message, env) }),
35
+ ...(release.diagnostic === undefined ? {} : { diagnostic: redactWakeDiagnostic(release.diagnostic, env) }),
83
36
  });
84
37
  }
85
38
  return releases.sort((left, right) => right.at - left.at);
86
39
  }
87
- export async function recordWakeAttempt(attempt, env = process.env) {
88
- const value = { ...attempt, at: attempt.at ?? Date.now() };
89
- if (!isWakeRouteKind(value.routeKind))
90
- throw new Error('Wake attempts require a real adapter route kind.');
91
- if (value.outcome !== 'accepted' && !value.signature) {
92
- throw new Error(`${value.outcome} wake attempts require a transport signature.`);
93
- }
94
- const safe = redactWakeDiagnostic(value, env);
95
- const session = safe.session ?? safe.signature ?? `route:${safe.routeKind}`;
96
- const claim = await ledger(env).claimEvidence({ location: safe.attention.squarePath, participant: safe.attention.recipient, session, activity: formatActivityId(safe.attention.actIndex), kind: 'wake', leaseMs: 5000 });
97
- if (claim.status === 'acquired')
98
- await ledger(env).appendWakeAttempt({ location: safe.attention.squarePath, participant: safe.attention.recipient, session, activity: formatActivityId(safe.attention.actIndex), kind: 'wake', outcome: safe.outcome, routeKind: safe.routeKind, signature: safe.signature, attemptN: safe.attemptN, message: safe.message, diagnostic: safe.diagnostic, at: safe.at, claimToken: claim.claimToken });
99
- return value;
100
- }
101
- export async function recordRecoveredUnknown(attention, lease, env = process.env, at = Date.now()) {
102
- const routeKind = lease.routeKind;
103
- if (lease.attemptN === undefined || !isWakeRouteKind(routeKind))
104
- return undefined;
105
- const rows = await readRows(env, at);
106
- {
107
- const expected = await wakeAttentionKey(attention);
108
- const attempts = [];
109
- for (const attempt of rows) {
110
- if (await wakeAttentionKey(attempt.attention) !== expected)
111
- continue;
112
- if (lease.session !== undefined && attempt.session !== lease.session)
113
- continue;
114
- attempts.push(attempt);
115
- }
116
- const terminal = terminalWakeEvidence(attempts);
117
- if (terminal !== undefined)
118
- return terminal;
119
- const value = {
120
- at,
121
- attention,
122
- routeKind,
123
- outcome: 'unknown',
124
- signature: 'worker_interrupted_during_dispatch',
125
- attemptN: lease.attemptN,
126
- message: 'The notification worker ended after dispatch began; transport acceptance is unknown.',
127
- };
128
- const session = lease.session ?? value.signature;
129
- const claim = await ledger(env).claimEvidence({ location: attention.squarePath, participant: attention.recipient, session, activity: formatActivityId(attention.actIndex), kind: 'wake', leaseMs: 5000 });
130
- if (claim.status === 'acquired')
131
- await ledger(env).appendWakeAttempt({ location: attention.squarePath, participant: attention.recipient, session, activity: formatActivityId(attention.actIndex), kind: 'wake', outcome: 'unknown', routeKind, signature: value.signature, attemptN: value.attemptN, message: value.message, at, claimToken: claim.claimToken });
132
- return value;
133
- }
134
- }