@astrosheep/square 0.3.48 → 0.3.49

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.48",
3
+ "version": "0.3.49",
4
4
  "description": "Native Claude Code turn-boundary delivery for Square participants",
5
5
  "author": {
6
6
  "name": "Square"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "square",
3
- "version": "0.3.48",
3
+ "version": "0.3.49",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -17,9 +17,11 @@ export async function codexHookResponse(input, lookup = sessionInbox, env = proc
17
17
  if (hookEventName === undefined)
18
18
  return undefined;
19
19
  await recordCodexBoundary(input.session_id, hookEventName === 'Stop' ? 'Stop' : 'non-stop', env);
20
- const response = await presentPendingAtBoundary(input.session_id, (context) => hookEventName === 'Stop'
21
- ? { systemMessage: context }
22
- : { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context } }, lookup, env);
20
+ if (hookEventName === 'Stop') {
21
+ await sweepPrivilegedPending(typeof input.cwd === 'string' ? input.cwd : process.cwd(), env, deliveryAdapters, sweepDeadline).catch(() => undefined);
22
+ return undefined;
23
+ }
24
+ const response = await presentPendingAtBoundary(input.session_id, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: context } }), lookup, env);
23
25
  await sweepPrivilegedPending(typeof input.cwd === 'string' ? input.cwd : process.cwd(), env, deliveryAdapters, sweepDeadline).catch(() => undefined);
24
26
  return response;
25
27
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Square } from './square-wiring.js';
2
2
  export { SquareError } from './model.js';
3
3
  export { bindCurrentParticipant, squareAssignedParticipantName, unbindCurrentParticipant } from './registry.js';
4
+ export { createDefaultWakeTransport } from './notifications.js';
4
5
  export { createHostLedgerPort, FileHostLedgerPort } from './host-ledger-file-adapter.js';
5
6
  export type { HostLedgerPort, PresenceRecord, EvidenceRecord } from './host-ledger.js';
6
7
  export type { PresentationSinkPort } from './ports.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { Square } from './square-wiring.js';
2
2
  export { SquareError } from './model.js';
3
3
  export { bindCurrentParticipant, squareAssignedParticipantName, unbindCurrentParticipant } from './registry.js';
4
+ export { createDefaultWakeTransport } from './notifications.js';
4
5
  export { createHostLedgerPort, FileHostLedgerPort } from './host-ledger-file-adapter.js';
@@ -1,6 +1,6 @@
1
1
  import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
2
2
  import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
3
- import { waitForSessionPending } from '../dist/inbox.js';
3
+ import { sessionInbox, waitForSessionPending } from '../dist/inbox.js';
4
4
  import { projectSessionBindings } from '../dist/square-projections.js';
5
5
  import { createHostLedgerPort } from '../dist/host-ledger-file-adapter.js';
6
6
 
@@ -37,34 +37,24 @@ export default function squarePiExtension(pi) {
37
37
  let watcherAbort;
38
38
  let generation = 0;
39
39
  const handledPending = new Set();
40
+ const observedPending = new Set();
40
41
  const landingAcks = new Map();
41
42
  let retryAfterChange = false;
42
43
  let retryWait;
43
44
  let turnIndex = 0;
44
45
  let activeTurn;
45
46
  let currentRunSignal;
46
- const deferredSteers = new Set();
47
+ let detachRunAbort;
48
+ let cancelledRun = false;
47
49
 
48
- const waitForAgentSettled = (signal) => new Promise((resolve, reject) => {
49
- const deferred = {
50
- release() {
51
- deferredSteers.delete(deferred);
52
- signal.removeEventListener('abort', abort);
53
- resolve();
54
- },
55
- };
56
- const abort = () => {
57
- deferredSteers.delete(deferred);
58
- signal.removeEventListener('abort', abort);
59
- reject(signal.reason || new Error('Pi native injection aborted'));
60
- };
61
- deferredSteers.add(deferred);
62
- if (signal.aborted) abort();
63
- else signal.addEventListener('abort', abort, { once: true });
64
- });
65
-
66
- const releaseDeferredSteers = () => {
67
- for (const deferred of [...deferredSteers]) deferred.release();
50
+ const cancelRunDelivery = () => {
51
+ cancelledRun = true;
52
+ for (const key of observedPending) handledPending.add(key);
53
+ generation += 1;
54
+ stopWatcher();
55
+ failAcks(new Error('Pi notification delivery cancelled'));
56
+ retryAfterChange = false;
57
+ retryWait = undefined;
68
58
  };
69
59
 
70
60
  const failAcks = (error) => {
@@ -87,7 +77,6 @@ export default function squarePiExtension(pi) {
87
77
  kind,
88
78
  sentAfterTurn: turnIndex,
89
79
  emptyTurnWindows: 0,
90
- dropAfterSettled: false,
91
80
  settle(error) {
92
81
  if (ack.settled) return;
93
82
  ack.settled = true;
@@ -180,6 +169,7 @@ export default function squarePiExtension(pi) {
180
169
  retryWait = undefined;
181
170
 
182
171
  const keys = inboxKeys(pending);
172
+ for (const key of keys) observedPending.add(key);
183
173
  const deferred = armDeferredRetry();
184
174
  const retryArmed = await deferred.armed;
185
175
  if (!retryArmed) {
@@ -191,7 +181,7 @@ export default function squarePiExtension(pi) {
191
181
  await presentPendingAtBoundary(
192
182
  sessionId,
193
183
  async (content) => {
194
- if (currentRunSignal?.aborted) await waitForAgentSettled(signal);
184
+ if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
195
185
  const landing = waitForLanding(content, signal, 'steer');
196
186
  try {
197
187
  Promise.resolve(pi.sendMessage(
@@ -203,12 +193,23 @@ export default function squarePiExtension(pi) {
203
193
  }
204
194
  return landing.promise;
205
195
  },
206
- undefined,
196
+ async (id, env) => {
197
+ const inbox = await sessionInbox(id, env);
198
+ if (signal.aborted || token !== generation) return [];
199
+ for (const key of inboxKeys(inbox)) observedPending.add(key);
200
+ return inbox.map((membership) => ({
201
+ ...membership,
202
+ notifications: membership.notifications.filter((note) => !handledPending.has(
203
+ `${membership.squarePath}\u0000${membership.name.toLocaleLowerCase()}\u0000${note.actIndex}`,
204
+ )),
205
+ }));
206
+ },
207
207
  undefined,
208
208
  signal,
209
209
  );
210
210
  for (const key of keys) handledPending.add(key);
211
211
  } catch (error) {
212
+ if (signal.aborted || token !== generation) return;
212
213
  if (error instanceof PiDeliveryDroppedError) {
213
214
  deferred.cancel();
214
215
  continue;
@@ -224,10 +225,13 @@ export default function squarePiExtension(pi) {
224
225
  };
225
226
 
226
227
  pi.on('session_start', async (_event, ctx) => {
228
+ detachRunAbort?.();
229
+ cancelledRun = false;
227
230
  generation += 1;
228
231
  failAcks(new Error('Pi session replaced'));
229
232
  stopWatcher();
230
233
  handledPending.clear();
234
+ observedPending.clear();
231
235
  retryAfterChange = false;
232
236
  retryWait = undefined;
233
237
  turnIndex = 0;
@@ -258,7 +262,13 @@ export default function squarePiExtension(pi) {
258
262
  });
259
263
 
260
264
  pi.on('agent_start', async (_event, ctx) => {
265
+ detachRunAbort?.();
261
266
  currentRunSignal = ctx?.getSignal?.();
267
+ if (ctx?.mode !== 'tui' || currentRunSignal === undefined) return;
268
+ const runSignal = currentRunSignal;
269
+ detachRunAbort = () => runSignal.removeEventListener('abort', cancelRunDelivery);
270
+ if (runSignal.aborted) cancelRunDelivery();
271
+ else runSignal.addEventListener('abort', cancelRunDelivery, { once: true });
262
272
  });
263
273
 
264
274
  pi.on('message_end', async (event) => {
@@ -277,15 +287,13 @@ export default function squarePiExtension(pi) {
277
287
  const turn = activeTurn;
278
288
  activeTurn = undefined;
279
289
  const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
280
- if (event.message?.stopReason === 'aborted' && ctx?.mode === 'tui' && turn?.signal !== undefined) {
281
- for (const ack of acks) {
282
- if (ack.kind === 'steer') ack.dropAfterSettled = true;
283
- }
290
+ if (event.message?.stopReason === 'aborted' && ctx?.mode === 'tui') {
291
+ if (!cancelledRun) cancelRunDelivery();
284
292
  return;
285
293
  }
286
294
  if (turn === undefined) return;
287
295
  for (const ack of acks) {
288
- if (ack.kind !== 'steer' || ack.dropAfterSettled || turn.index <= ack.sentAfterTurn) continue;
296
+ if (ack.kind !== 'steer' || turn.index <= ack.sentAfterTurn) continue;
289
297
  if (turn.hasInput) {
290
298
  ack.emptyTurnWindows = 0;
291
299
  } else {
@@ -296,15 +304,25 @@ export default function squarePiExtension(pi) {
296
304
  });
297
305
 
298
306
  pi.on('agent_settled', async () => {
307
+ detachRunAbort?.();
308
+ detachRunAbort = undefined;
299
309
  currentRunSignal = undefined;
300
- const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
301
- for (const ack of acks) {
302
- if (ack.dropAfterSettled) ack.settle(new PiDeliveryDroppedError());
303
- }
304
- releaseDeferredSteers();
310
+ if (!cancelledRun || sessionId === undefined) return;
311
+ const token = generation;
312
+ // Cancel the entire pending batch, including entries not yet sent to Pi.
313
+ // This is local suppression, never evidence that the model saw the activity.
314
+ const cancelled = await sessionInbox(sessionId);
315
+ if (token !== generation || sessionId === undefined) return;
316
+ for (const key of inboxKeys(cancelled)) handledPending.add(key);
317
+ cancelledRun = false;
318
+ watcherAbort = new AbortController();
319
+ watcher = wake(token, watcherAbort.signal).catch(() => undefined);
305
320
  });
306
321
 
307
322
  pi.on('session_shutdown', async () => {
323
+ detachRunAbort?.();
324
+ detachRunAbort = undefined;
325
+ cancelledRun = false;
308
326
  generation += 1;
309
327
  failAcks(new Error('Pi session ended'));
310
328
  stopWatcher();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.48",
3
+ "version": "0.3.49",
4
4
  "description": "A shared public square where agents join, catch activity, express, and step out when done.",
5
5
  "type": "module",
6
6
  "bin": {