@astrosheep/square 0.3.11 → 0.3.13

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 (127) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks/hooks.json +9 -0
  3. package/dist/activity-feed.d.ts +14 -0
  4. package/dist/activity-feed.js +5 -5
  5. package/dist/activity.d.ts +9 -0
  6. package/dist/activity.js +112 -96
  7. package/dist/artifact.d.ts +20 -0
  8. package/dist/artifact.js +317 -648
  9. package/dist/automatic-session.d.ts +5 -0
  10. package/dist/automatic-session.js +86 -0
  11. package/dist/boundary-presentation.d.ts +5 -0
  12. package/dist/boundary-presentation.js +6 -5
  13. package/dist/claude-hook.d.ts +9 -0
  14. package/dist/claude-hook.js +35 -3
  15. package/dist/cli/context.d.ts +35 -0
  16. package/dist/cli/context.js +26 -31
  17. package/dist/cli/harness-command.d.ts +28 -0
  18. package/dist/cli/harness-command.js +30 -15
  19. package/dist/cli/maintenance-commands.d.ts +7 -0
  20. package/dist/cli/maintenance-commands.js +12 -69
  21. package/dist/cli/meta-commands.d.ts +5 -0
  22. package/dist/cli/observation-commands.d.ts +20 -0
  23. package/dist/cli/observation-commands.js +159 -139
  24. package/dist/cli/program.d.ts +2 -0
  25. package/dist/cli/program.js +3 -12
  26. package/dist/cli/registry.d.ts +9 -0
  27. package/dist/cli/registry.js +2 -4
  28. package/dist/cli/square-commands.d.ts +34 -0
  29. package/dist/cli/square-commands.js +90 -84
  30. package/dist/cmd/notify-once.d.ts +2 -0
  31. package/dist/codex-hook.d.ts +10 -0
  32. package/dist/codex-hook.js +35 -3
  33. package/dist/decisions.d.ts +92 -0
  34. package/dist/decisions.js +93 -81
  35. package/dist/delivery-health.d.ts +21 -0
  36. package/dist/delivery-health.js +41 -34
  37. package/dist/delivery.d.ts +81 -0
  38. package/dist/delivery.js +71 -69
  39. package/dist/file-lock.d.ts +6 -0
  40. package/dist/harness-claude.d.ts +19 -0
  41. package/dist/harness-claude.js +8 -3
  42. package/dist/harness-codex.d.ts +29 -0
  43. package/dist/harness-codex.js +2 -2
  44. package/dist/harness-links.d.ts +17 -0
  45. package/dist/harness-links.js +2 -2
  46. package/dist/harness-pi.d.ts +11 -0
  47. package/dist/harness-pi.js +2 -2
  48. package/dist/harness-stage.d.ts +6 -0
  49. package/dist/harness.d.ts +20 -0
  50. package/dist/harness.js +5 -3
  51. package/dist/help.d.ts +6 -0
  52. package/dist/help.js +12 -13
  53. package/dist/identity.d.ts +13 -0
  54. package/dist/inbox.d.ts +2 -0
  55. package/dist/inbox.js +15 -19
  56. package/dist/index.d.ts +13 -0
  57. package/dist/index.js +31 -67
  58. package/dist/landing.d.ts +10 -0
  59. package/dist/landing.js +95 -0
  60. package/dist/list.d.ts +1 -0
  61. package/dist/list.js +39 -61
  62. package/dist/model.d.ts +147 -0
  63. package/dist/model.js +12 -8
  64. package/dist/notifications.d.ts +30 -0
  65. package/dist/notifications.js +83 -91
  66. package/dist/open-square.d.ts +11 -0
  67. package/dist/open-square.js +4 -0
  68. package/dist/participant-identity.d.ts +1 -0
  69. package/dist/participant-identity.js +3 -0
  70. package/dist/paseo-connection.d.ts +15 -0
  71. package/dist/paseo-delivery.d.ts +14 -0
  72. package/dist/paseo-state.d.ts +11 -0
  73. package/dist/paseo-timeline.d.ts +16 -0
  74. package/dist/presence.d.ts +3 -0
  75. package/dist/presence.js +42 -0
  76. package/dist/presentation.d.ts +91 -0
  77. package/dist/presentation.js +52 -63
  78. package/dist/presented.d.ts +11 -0
  79. package/dist/registry.d.ts +58 -0
  80. package/dist/registry.js +36 -3
  81. package/dist/routes.d.ts +42 -0
  82. package/dist/runtime.d.ts +45 -0
  83. package/dist/runtime.js +48 -55
  84. package/dist/search.d.ts +11 -0
  85. package/dist/square-core.d.ts +111 -0
  86. package/dist/square-core.js +63 -9
  87. package/dist/square-facade.d.ts +86 -0
  88. package/dist/square-facade.js +1 -0
  89. package/dist/square-file-adapter.d.ts +18 -0
  90. package/dist/square-file-adapter.js +70 -0
  91. package/dist/square-storage.d.ts +20 -0
  92. package/dist/square-storage.js +171 -0
  93. package/dist/square-wiring.d.ts +19 -0
  94. package/dist/square-wiring.js +70 -0
  95. package/dist/square.d.ts +2 -0
  96. package/dist/state-cell.d.ts +14 -0
  97. package/dist/state-cell.js +1 -0
  98. package/dist/stream.d.ts +3 -0
  99. package/dist/stream.js +19 -23
  100. package/dist/time.d.ts +6 -0
  101. package/dist/views.d.ts +95 -0
  102. package/dist/views.js +81 -0
  103. package/dist/wake-attempts.d.ts +39 -0
  104. package/dist/wake-attempts.js +8 -4
  105. package/dist/wake-evidence.d.ts +12 -0
  106. package/dist/wake-evidence.js +7 -10
  107. package/dist/wake-port.d.ts +20 -0
  108. package/dist/wake-sink.d.ts +12 -0
  109. package/dist/wakes.d.ts +40 -0
  110. package/dist/wakes.js +62 -0
  111. package/dist/watch.d.ts +2 -0
  112. package/dist/watch.js +89 -210
  113. package/extensions/square-opencode.js +27 -1
  114. package/extensions/square-pi.js +13 -0
  115. package/guides/participant.md +7 -174
  116. package/package.json +5 -4
  117. package/skills/brainstorm/SKILL.md +30 -30
  118. package/skills/square/.claude-plugin/plugin.json +1 -1
  119. package/skills/square/SKILL.md +56 -29
  120. package/skills/square/hooks/hooks.json +9 -0
  121. package/skills/square-feedback/SKILL.md +9 -9
  122. package/dist/compact.js +0 -26
  123. package/dist/doctor.js +0 -36
  124. package/dist/square-application.js +0 -262
  125. package/template.md +0 -4
  126. package/templates/architect.md +0 -4
  127. package/templates/brainstorm.md +0 -4
package/dist/decisions.js CHANGED
@@ -1,59 +1,83 @@
1
1
  import { SquareError, sameName, validateName, } from './model.js';
2
+ import { participantIdentity } from './participant-identity.js';
2
3
  import { UNREAD_BLOCK_GRACE_MS, actId, actStableIndex, foldedState, freshWatchLease, getReadState, publicActs, readCursor, resolveRosterName, rosterNames, matchesMentionTarget, THROTTLE_WINDOW_MS, } from './runtime.js';
3
4
  import { actDelta, peerPublicActs, peerRoomChanges } from './activity-feed.js';
4
- import { validate } from './square-core.js';
5
+ import { extractMentions, formatActivityId, validate } from './square-core.js';
5
6
  import { deriveDeliveryModel } from './delivery.js';
6
7
  import { compileSearchPattern } from './search.js';
7
- export function resolveKnownName(doc, name) {
8
+ export function resolveKnownName(squareState, name) {
8
9
  validateName(name);
9
- const known = resolveRosterName(doc, name);
10
+ const known = resolveRosterName(squareState, name);
10
11
  if (known === undefined) {
11
- const roster = rosterNames(doc);
12
- throw new SquareError('invalid_args', `Unknown participant "${name}". Expected one of: ${roster.join(', ')}.`);
12
+ const roster = rosterNames(squareState);
13
+ throw new SquareError('invalid_args', `Unknown participant "${participantIdentity(name)}". Expected one of: ${roster.map(participantIdentity).join(', ')}.`);
13
14
  }
14
15
  return known;
15
16
  }
16
17
  function participantState(state, name) {
17
18
  return state.participants.find((participant) => sameName(participant.name, name));
18
19
  }
19
- export function decideJoin(doc, name, now) {
20
- const knownName = resolveRosterName(doc, name);
20
+ export function decideJoin(squareState, name, now) {
21
+ validateName(name);
22
+ const knownName = resolveRosterName(squareState, name);
21
23
  const joinedName = knownName ?? name;
22
- const state = foldedState(doc);
24
+ const state = foldedState(squareState);
23
25
  const result = validate(state, { kind: 'join', actor: joinedName, at: now });
24
26
  if (!result.ok && result.reason === 'already_joined') {
25
- throw new SquareError('conflict', `A participant named "${joinedName}" is already in this square.`);
27
+ return { joinedName, addParticipant: false };
26
28
  }
27
29
  return {
28
30
  joinedName,
29
31
  addParticipant: knownName === undefined,
30
- joinAct: { kind: 'join', actor: joinedName, at: now, body: '' },
32
+ joinAct: { kind: 'join', actor: joinedName, at: now },
31
33
  };
32
34
  }
35
+ function resolveStandingName(squareState, requestedName) {
36
+ validateName(requestedName);
37
+ const name = resolveRosterName(squareState, requestedName);
38
+ if (name === undefined)
39
+ throw new SquareError('not_joined', `${participantIdentity(requestedName)} has not joined this square`);
40
+ return name;
41
+ }
42
+ function requireStanding(squareState, act) {
43
+ const result = validate(foldedState(squareState), act);
44
+ if (result.ok)
45
+ return;
46
+ if (result.reason === 'done')
47
+ throw new SquareError('already_done', `${act.actor === undefined ? 'participant' : participantIdentity(act.actor)} is already done`);
48
+ if (result.reason === 'not_joined')
49
+ throw new SquareError('not_joined', `${act.actor === undefined ? 'participant' : participantIdentity(act.actor)} has not joined this square`);
50
+ throw new Error(`Unexpected standing validation result: ${result.reason}`);
51
+ }
33
52
  const UNREAD_PREVIEW_LIMIT = 3;
34
- export function decideAct(doc, input) {
53
+ export function decideAct(squareState, input) {
35
54
  const { now, force } = input;
36
- const name = resolveKnownName(doc, input.name);
55
+ const name = resolveStandingName(squareState, input.name);
37
56
  const body = input.body;
38
57
  if (body.trim() === '')
39
58
  throw new SquareError('invalid_args', 'express body cannot be empty');
40
59
  const reach = input.reach;
41
60
  const reply = input.reply;
42
61
  if (reply !== undefined) {
43
- if (!Number.isSafeInteger(reply) || reply < 0 || reply >= doc.runtime.nextActIndex) {
44
- throw new SquareError('invalid_args', `Unknown reply activity: act_${reply}`);
62
+ if (!Number.isSafeInteger(reply) || reply < 0 || reply >= squareState.runtime.nextActIndex) {
63
+ const label = Number.isSafeInteger(reply) && reply >= 0 ? formatActivityId(reply) : String(reply);
64
+ throw new SquareError('invalid_args', `Unknown reply activity: ${label}`);
45
65
  }
46
66
  }
47
- const state = foldedState(doc);
67
+ const state = foldedState(squareState);
68
+ if (reach !== 'bell'
69
+ && extractMentions(body).length === 0) {
70
+ throw new SquareError('invalid_args', 'express requires an @mention unless using --bell');
71
+ }
48
72
  const current = participantState(state, name);
49
73
  const result = validate(state, {
50
74
  kind: 'say', actor: name, at: now, body,
51
75
  ...(reach !== undefined ? { reach } : {}),
52
76
  ...(reply !== undefined ? { reply } : {}),
53
- }, { hardCap: doc.hardCap, throttlePerMinute: doc.throttlePerMinute, throttleWindowMs: THROTTLE_WINDOW_MS });
77
+ }, { hardCap: squareState.hardCap, throttlePerMinute: squareState.throttlePerMinute, throttleWindowMs: THROTTLE_WINDOW_MS });
54
78
  if (!result.ok) {
55
79
  if (result.reason === 'done')
56
- throw new SquareError('conflict', `${name} is done; rejoin to express again`);
80
+ throw new SquareError('already_done', `${name} is already done`);
57
81
  if (result.reason === 'held')
58
82
  return { type: 'held', reason: result.hold.reason };
59
83
  if (result.reason === 'hard_cap')
@@ -63,9 +87,9 @@ export function decideAct(doc, input) {
63
87
  if (result.reason === 'bell_quota')
64
88
  return { type: 'bell_quota', nextAt: result.nextAt };
65
89
  if (result.reason === 'not_joined')
66
- throw new SquareError('conflict', `${name} has not joined this square`);
90
+ throw new SquareError('not_joined', `${name} has not joined this square`);
67
91
  }
68
- const delta = actDelta(doc.acts, readCursor(doc, name));
92
+ const delta = actDelta(squareState.acts, readCursor(squareState, name));
69
93
  const unreadPublic = peerPublicActs(delta, name);
70
94
  const unreadRoomChanges = peerRoomChanges(delta, name);
71
95
  const sayCountByActor = new Map();
@@ -97,7 +121,8 @@ export function decideAct(doc, input) {
97
121
  }))
98
122
  .sort((a, b) => a.latestActivityAgeMs - b.latestActivityAgeMs || a.name.localeCompare(b.name));
99
123
  const latestActivityAgeMs = activitySummaries[0]?.latestActivityAgeMs;
100
- const hasUnread = unreadPublic.length > 0 || unreadRoomChanges.length > 0;
124
+ const hasUnreadBlockingChange = unreadRoomChanges.some((act) => act.kind !== 'join');
125
+ const hasUnread = unreadPublic.length > 0 || hasUnreadBlockingChange;
101
126
  const hasFreshUnreadActivity = latestActivityAgeMs !== undefined && latestActivityAgeMs <= UNREAD_BLOCK_GRACE_MS;
102
127
  if (!force && hasUnread && !hasFreshUnreadActivity) {
103
128
  return { type: 'blocked', activitySummaries, unreadRoomChanges };
@@ -116,21 +141,29 @@ export function decideAct(doc, input) {
116
141
  pendingRoomChanges: unreadRoomChanges,
117
142
  };
118
143
  }
119
- export function coreDone(doc, name, body, now) {
120
- const resolvedName = resolveKnownName(doc, name);
121
- return { kind: 'done', actor: resolvedName, at: now, body: body.replace(/\r\n/g, '\n').trim() };
144
+ export function coreDone(squareState, name, body, now) {
145
+ const resolvedName = resolveStandingName(squareState, name);
146
+ const act = { kind: 'done', actor: resolvedName, at: now, body: body.replace(/\r\n/g, '\n').trim() };
147
+ requireStanding(squareState, act);
148
+ return act;
122
149
  }
123
- export function coreHold(_doc, actor, body, now) {
124
- return { kind: 'hold', actor, at: now, body: body.replace(/\r\n/g, '\n').trim() };
150
+ export function coreHold(squareState, actor, body, now) {
151
+ const resolvedName = resolveStandingName(squareState, actor);
152
+ const act = { kind: 'hold', actor: resolvedName, at: now, body: body.replace(/\r\n/g, '\n').trim() };
153
+ requireStanding(squareState, act);
154
+ return act;
125
155
  }
126
- export function coreResume(_doc, actor, now) {
127
- return { kind: 'resume', actor, at: now, body: '' };
156
+ export function coreResume(squareState, actor, now) {
157
+ const resolvedName = resolveStandingName(squareState, actor);
158
+ const act = { kind: 'resume', actor: resolvedName, at: now };
159
+ requireStanding(squareState, act);
160
+ return act;
128
161
  }
129
- function presenceFor(doc, snapshot, name, now) {
162
+ function presenceFor(squareState, snapshot, name, now) {
130
163
  if (snapshot?.done)
131
164
  return { state: 'done', lastAt: snapshot.lastActiveAt };
132
- const cursor = getReadState(doc, name);
133
- const lease = freshWatchLease(doc, name, now);
165
+ const cursor = getReadState(squareState, name);
166
+ const lease = freshWatchLease(squareState, name, now);
134
167
  if (lease !== undefined)
135
168
  return { state: 'watching', lastAt: cursor?.updatedAt ?? lease.heartbeatAt };
136
169
  const lastAt = cursor?.updatedAt ?? (snapshot?.joined ? snapshot.lastActiveAt : undefined);
@@ -138,16 +171,16 @@ function presenceFor(doc, snapshot, name, now) {
138
171
  ? { state: 'never-joined', lastAt: undefined }
139
172
  : { state: 'active', lastAt };
140
173
  }
141
- function buildParticipantStatuses(doc, now, state = foldedState(doc)) {
142
- const delivery = deriveDeliveryModel(doc);
174
+ function buildParticipantStatuses(squareState, now, state = foldedState(squareState)) {
175
+ const delivery = deriveDeliveryModel(squareState);
143
176
  return state.participants.map((snapshot) => {
144
177
  const participant = snapshot.name;
145
- const presence = presenceFor(doc, snapshot, participant, now);
178
+ const presence = presenceFor(squareState, snapshot, participant, now);
146
179
  const participantStatus = snapshot?.done ? 'done' : snapshot?.joined ? 'active' : 'not joined';
147
- const consumedThrough = readCursor(doc, participant);
180
+ const consumedThrough = readCursor(squareState, participant);
148
181
  let unreadActivityCount = 0;
149
182
  if (snapshot?.joined) {
150
- for (const act of doc.acts) {
183
+ for (const act of squareState.acts) {
151
184
  if (actStableIndex(act) <= consumedThrough || act.actor === undefined || sameName(act.actor, participant))
152
185
  continue;
153
186
  if (act.kind !== 'read')
@@ -166,43 +199,44 @@ function buildParticipantStatuses(doc, now, state = foldedState(doc)) {
166
199
  };
167
200
  });
168
201
  }
169
- export function coreStatus(doc, now) {
170
- const state = foldedState(doc);
171
- const latestAct = publicActs(doc.acts).at(-1);
202
+ export function coreStatus(squareState, now) {
203
+ const state = foldedState(squareState);
204
+ const latestAct = publicActs(squareState.acts).at(-1);
172
205
  return {
173
- hardCap: doc.hardCap,
174
- throttlePerMinute: doc.throttlePerMinute,
206
+ hardCap: squareState.hardCap,
207
+ throttlePerMinute: squareState.throttlePerMinute,
175
208
  activeCount: state.joined.length,
176
209
  doneCount: state.done.length,
177
210
  holdActive: state.hold.active,
178
211
  holdReason: state.hold.reason,
179
212
  holdActor: state.hold.actor,
180
213
  holdAt: state.hold.at,
181
- participants: buildParticipantStatuses(doc, now, state),
214
+ participants: buildParticipantStatuses(squareState, now, state),
182
215
  latestAct,
183
216
  now,
184
217
  };
185
218
  }
186
- export function coreParticipants(doc, now) {
187
- return buildParticipantStatuses(doc, now);
219
+ export function coreParticipants(squareState, now) {
220
+ return buildParticipantStatuses(squareState, now);
188
221
  }
189
- export function coreActivities(doc, opts) {
222
+ export function coreActivities(squareState, opts) {
190
223
  const participants = opts.participants ?? [];
191
- const canonicalParticipants = participants.map((participant) => resolveKnownName(doc, participant));
192
- const viewer = opts.viewer !== undefined ? resolveKnownName(doc, opts.viewer) : undefined;
193
- let acts = [...doc.acts];
194
- // --at establishes a context window first; other filters AND inside it.
195
- if (opts.atIndex != null) {
224
+ const canonicalParticipants = participants.map((participant) => resolveKnownName(squareState, participant));
225
+ const viewer = opts.viewer !== undefined ? resolveKnownName(squareState, opts.viewer) : undefined;
226
+ let acts = [...squareState.acts];
227
+ // --at establishes one or more context windows first; other filters AND inside their union.
228
+ if (opts.atIndexes !== undefined && opts.atIndexes.length > 0) {
196
229
  const before = opts.beforeContext ?? 0;
197
230
  const after = opts.afterContext ?? 0;
198
- const centerPos = acts.findIndex((act) => act.index === opts.atIndex);
199
- if (centerPos < 0)
200
- return [];
201
- acts = acts.slice(Math.max(0, centerPos - before), centerPos + after + 1);
202
- }
203
- if (opts.ids !== undefined && opts.ids.length > 0) {
204
- const wanted = new Set(opts.ids);
205
- acts = acts.filter((act) => wanted.has(act.index));
231
+ const selected = new Set();
232
+ for (const atIndex of opts.atIndexes) {
233
+ const centerPos = acts.findIndex((act) => act.index === atIndex);
234
+ if (centerPos < 0)
235
+ continue;
236
+ for (const act of acts.slice(Math.max(0, centerPos - before), centerPos + after + 1))
237
+ selected.add(act.index);
238
+ }
239
+ acts = acts.filter((act) => selected.has(act.index));
206
240
  }
207
241
  if (opts.afterIndex != null)
208
242
  acts = acts.filter((act) => act.index > opts.afterIndex);
@@ -214,13 +248,13 @@ export function coreActivities(doc, opts) {
214
248
  if (opts.after != null)
215
249
  acts = acts.filter((act) => act.at > opts.after);
216
250
  if (opts.mention != null) {
217
- const mention = resolveKnownName(doc, opts.mention);
251
+ const mention = resolveKnownName(squareState, opts.mention);
218
252
  acts = acts.filter((act) => act.kind === 'say' && (act.reach === 'bell' || matchesMentionTarget(act, mention)));
219
253
  }
220
254
  if (opts.pending) {
221
255
  if (viewer === undefined)
222
256
  return [];
223
- const pendingIndexes = new Set(deriveDeliveryModel(doc).pendingFor(viewer).map((notification) => notification.item.index));
257
+ const pendingIndexes = new Set(deriveDeliveryModel(squareState).pendingFor(viewer).map((notification) => notification.item.index));
224
258
  acts = acts.filter((act) => pendingIndexes.has(act.index));
225
259
  }
226
260
  const search = opts.grep !== undefined ? { pattern: opts.grep, fixed: false } : opts.fixed !== undefined ? { pattern: opts.fixed, fixed: true } : undefined;
@@ -243,25 +277,3 @@ export function coreActivities(doc, opts) {
243
277
  }
244
278
  return acts;
245
279
  }
246
- export function coreCompact(doc, keep) {
247
- if (doc.acts.length <= keep)
248
- return { archived: [], doc };
249
- const splitAt = doc.acts.length - keep;
250
- const archived = doc.acts.slice(0, splitAt);
251
- const retained = doc.acts.slice(splitAt);
252
- const cutoffIndex = actStableIndex(archived[archived.length - 1]);
253
- const unread = foldedState(doc).participants
254
- .filter((participant) => participant.joined && readCursor(doc, participant.name) < cutoffIndex)
255
- .map((participant) => participant.name);
256
- if (unread.length > 0) {
257
- throw new SquareError('conflict', `Refusing to compact: ${unread.join(', ')} ${unread.length === 1 ? 'has' : 'have'} not read through the activities being archived.`);
258
- }
259
- return {
260
- archived,
261
- doc: {
262
- ...doc,
263
- acts: retained,
264
- runtime: doc.runtime,
265
- },
266
- };
267
- }
@@ -0,0 +1,21 @@
1
+ import { type DirectedNotificationRoute } from './delivery.js';
2
+ import type { WakeAttempt } from './wake-attempts.js';
3
+ export type DeliveryHealthKind = 'awaiting' | 'wake-accepted' | 'wake-unknown' | 'presented-not-delivered' | 'unreachable';
4
+ export interface DeliveryHealthItem {
5
+ squarePath: string;
6
+ recipient: string;
7
+ actIndex: number;
8
+ actor: string;
9
+ at: number;
10
+ ageMs: number;
11
+ route: DirectedNotificationRoute;
12
+ kind: DeliveryHealthKind;
13
+ attempt?: WakeAttempt;
14
+ }
15
+ /** Purely classify current pending attention from the artifact and durable ledgers. */
16
+ export declare function classifyDeliveryHealth(squarePath: string, opts: {
17
+ graceMs: number;
18
+ now?: number;
19
+ env?: NodeJS.ProcessEnv;
20
+ }): Promise<DeliveryHealthItem[]>;
21
+ export declare function doctorDeliveryHealth(squarePath: string, graceMs: number, now?: number, env?: NodeJS.ProcessEnv): Promise<string[]>;
@@ -1,7 +1,10 @@
1
- import { loadSquare } from './artifact.js';
2
- import { deriveDeliveryModel, } from './delivery.js';
1
+ import { formatActivityId } from './square-core.js';
2
+ import { participantIdentity } from './presentation.js';
3
3
  import { formatDuration } from './time.js';
4
- import { joinedRecipients, wakeEvidence } from './wake-evidence.js';
4
+ import { wakeEvidence } from './wake-evidence.js';
5
+ import { openSquare } from './square-file-adapter.js';
6
+ import { closeOpenSquare } from './open-square.js';
7
+ import { pendingDeliveries } from './views.js';
5
8
  const DISPLAY_ORDER = [
6
9
  'awaiting',
7
10
  'wake-accepted',
@@ -11,43 +14,47 @@ const DISPLAY_ORDER = [
11
14
  ];
12
15
  const ACTIONABLE = new Set(['wake-unknown', 'unreachable']);
13
16
  /** Purely classify current pending attention from the artifact and durable ledgers. */
14
- export function classifyDeliveryHealth(squarePath, opts) {
17
+ export async function classifyDeliveryHealth(squarePath, opts) {
15
18
  const now = opts.now ?? Date.now();
16
19
  const env = opts.env ?? process.env;
17
- const doc = loadSquare(squarePath);
18
- const model = deriveDeliveryModel(doc);
19
- return joinedRecipients(doc).flatMap((recipient) => model.pendingFor(recipient).map((note) => {
20
- const ageMs = Math.max(0, now - note.item.at);
21
- const evidence = wakeEvidence(squarePath, note.recipient, note.item.index, now, env);
22
- const kind = evidence.presented
23
- ? 'presented-not-delivered'
24
- : evidence.terminal?.outcome === 'accepted'
25
- ? 'wake-accepted'
26
- : evidence.terminal?.outcome === 'unknown'
27
- ? 'wake-unknown'
28
- : ageMs > opts.graceMs && evidence.attemptableRoutes.length === 0
29
- ? 'unreachable'
30
- : 'awaiting';
31
- const attempt = evidence.terminal ?? evidence.attempts.at(-1);
32
- return {
33
- squarePath,
34
- recipient: note.recipient,
35
- actIndex: note.item.index,
36
- actor: note.item.actor,
37
- at: note.item.at,
38
- ageMs,
39
- route: note.route,
40
- kind,
41
- ...(attempt === undefined ? {} : { attempt }),
42
- };
43
- }));
20
+ const square = await openSquare(squarePath, { clock: () => now });
21
+ const pending = await pendingDeliveries(square).finally(() => closeOpenSquare(square));
22
+ const items = [];
23
+ for (const delivery of pending) {
24
+ for (const note of delivery.notifications) {
25
+ const ageMs = Math.max(0, now - note.item.at);
26
+ const evidence = await wakeEvidence(squarePath, note.recipient, note.item.index, now, env);
27
+ const kind = evidence.presented
28
+ ? 'presented-not-delivered'
29
+ : evidence.terminal?.outcome === 'accepted'
30
+ ? 'wake-accepted'
31
+ : evidence.terminal?.outcome === 'unknown'
32
+ ? 'wake-unknown'
33
+ : ageMs > opts.graceMs && evidence.attemptableRoutes.length === 0
34
+ ? 'unreachable'
35
+ : 'awaiting';
36
+ const attempt = evidence.terminal ?? evidence.attempts.at(-1);
37
+ items.push({
38
+ squarePath,
39
+ recipient: note.recipient,
40
+ actIndex: note.item.index,
41
+ actor: note.item.actor,
42
+ at: note.item.at,
43
+ ageMs,
44
+ route: note.route,
45
+ kind,
46
+ ...(attempt === undefined ? {} : { attempt }),
47
+ });
48
+ }
49
+ }
50
+ return items;
44
51
  }
45
52
  function formatItem(item) {
46
53
  const evidence = item.attempt?.signature === undefined ? '' : ` · ${item.attempt.signature}`;
47
- return ` · act_${item.actIndex} → @${item.recipient} from @${item.actor} · ${formatDuration(item.ageMs)}${evidence}`;
54
+ return ` · ${formatActivityId(item.actIndex)} → ${participantIdentity(item.recipient)} from ${participantIdentity(item.actor)} · ${formatDuration(item.ageMs)}${evidence}`;
48
55
  }
49
- export function doctorDeliveryHealth(squarePath, graceMs, now = Date.now(), env = process.env) {
50
- const items = classifyDeliveryHealth(squarePath, { graceMs, now, env });
56
+ export async function doctorDeliveryHealth(squarePath, graceMs, now = Date.now(), env = process.env) {
57
+ const items = await classifyDeliveryHealth(squarePath, { graceMs, now, env });
51
58
  if (items.length === 0)
52
59
  return ['✓ no pending delivery attention'];
53
60
  const out = [`· delivery attention · ${items.length} pending`];
@@ -0,0 +1,81 @@
1
+ import { type DeliveryReceipt, type DirectedNotificationRoute, type SquareState, type SquareRuntimeState, type StoredAct, type WatchLease, type WatchLeaseFilter, type WakeRouteKind, type Reach } from './model.js';
2
+ export type { DirectedNotificationRoute } from './model.js';
3
+ export type SayItem = StoredAct & {
4
+ kind: 'say';
5
+ };
6
+ export declare function notificationMessageId(squarePath: string, actIndex: number): string;
7
+ export interface PlannedNotification {
8
+ item: SayItem;
9
+ recipient: string;
10
+ route: DirectedNotificationRoute;
11
+ }
12
+ export interface WakeRequest {
13
+ squarePath: string;
14
+ actIndex: number;
15
+ recipient: string;
16
+ actor: string;
17
+ route: DirectedNotificationRoute;
18
+ }
19
+ export type WakeDispatchResult = {
20
+ outcome: 'accepted';
21
+ } | {
22
+ outcome: 'unknown';
23
+ signature: string;
24
+ message: string;
25
+ diagnostic?: unknown;
26
+ } | {
27
+ outcome: 'failed';
28
+ signature: string;
29
+ message: string;
30
+ diagnostic?: unknown;
31
+ } | {
32
+ outcome: 'cancelled';
33
+ };
34
+ export interface WakeAdapter {
35
+ readonly kind: WakeRouteKind;
36
+ dispatch(address: Readonly<Record<string, string>>, payload: string, beforeSend: () => Promise<boolean>): Promise<WakeDispatchResult>;
37
+ }
38
+ export interface RoutedNotification {
39
+ actor: string;
40
+ body: string;
41
+ route: DirectedNotificationRoute;
42
+ }
43
+ export interface CatchFilterShape {
44
+ actor: string;
45
+ body: string;
46
+ reach?: Reach;
47
+ }
48
+ export interface DeliveryModel {
49
+ plan(item: StoredAct): PlannedNotification[];
50
+ pendingFor(recipient: string): PlannedNotification[];
51
+ }
52
+ /** Delivery receipts are the only durable acknowledgement of directed attention. */
53
+ export declare function deliveryReceipt(squareState: SquareState, name: string, actOrIndex: StoredAct | number): DeliveryReceipt | undefined;
54
+ export declare function deliveryReceiptFromRuntime(runtime: SquareRuntimeState, recipient: string, actOrIndex: StoredAct | number): DeliveryReceipt | undefined;
55
+ export declare function isDeliveryDelivered(squareState: SquareState, name: string, actOrIndex: StoredAct | number): boolean;
56
+ export declare function recordDeliveredDelivery(squareState: SquareState, name: string, actOrIndex: StoredAct | number, receipt: Omit<DeliveryReceipt, 'status'>): boolean;
57
+ export declare function recordDeliveredRuntime(runtime: SquareRuntimeState, recipient: string, actOrIndex: StoredAct | number, receipt: Omit<DeliveryReceipt, 'status'>): boolean;
58
+ export declare function markDeliveredDelivery(squareState: SquareState, name: string, actOrIndex: StoredAct | number, at?: number): boolean;
59
+ /**
60
+ * Derive delivery behavior once from the decoded Square state.
61
+ * All consumers share these targets instead of reinterpreting artifact text or cursor state.
62
+ */
63
+ export declare function deriveDeliveryModel(squareState: SquareState): DeliveryModel;
64
+ export declare function planActNotifications(squareState: SquareState, item: StoredAct): PlannedNotification[];
65
+ /** Mark only the directed notifications selected by the canonical pending projection. */
66
+ export declare function markDeliveredNotifications(squareState: SquareState, recipient: string, delivered: StoredAct[], at?: number): boolean;
67
+ /** Canonical say-activity filter shared by catch selection and hook ownership. */
68
+ export declare function matchesCatchFilter(activity: CatchFilterShape, filter: WatchLeaseFilter): boolean;
69
+ /** True only when the live catch's own filters would deliver this notification. */
70
+ export declare function leaseOwnsNotification(lease: WatchLease, notification: RoutedNotification): boolean;
71
+ export type SquareRoute = Readonly<unknown>;
72
+ interface ParsedSquareRoute {
73
+ readonly v: 1;
74
+ readonly squarePath: string;
75
+ readonly name: string;
76
+ }
77
+ export declare function parseSquareRoute(value: unknown): ParsedSquareRoute;
78
+ export declare function captureRoute(input: {
79
+ cwd: string;
80
+ env?: NodeJS.ProcessEnv;
81
+ }): SquareRoute | null;