@astrosheep/square 0.3.25 → 0.3.27

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.25",
3
+ "version": "0.3.27",
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.25",
3
+ "version": "0.3.27",
4
4
  "description": "Shared Square activity with reliable participant attention at Codex boundaries.",
5
5
  "author": {
6
6
  "name": "Square"
@@ -1,6 +1,9 @@
1
1
  import { presentPendingAtBoundary, renderPendingAtBoundary } from '../dist/boundary-presentation.js';
2
2
  import { automaticSessionEnd, automaticSessionStart } from '../dist/automatic-session.js';
3
3
  import { waitForSessionPending } from '../dist/inbox.js';
4
+ import { lookupSessionBindings } from '../dist/registry.js';
5
+
6
+ const PI_SEND_TIMEOUT_MS = 5_000;
4
7
 
5
8
  export function pendingInbox(inbox) {
6
9
  return inbox.filter((item) => item.notifications?.length > 0);
@@ -44,8 +47,30 @@ export default function squarePiExtension(pi) {
44
47
  });
45
48
  };
46
49
 
50
+ // A transport may keep its promise pending while Pi is shutting down or
51
+ // replacing a session. Never make a lifecycle hook wait for that transport.
52
+ const stopWatcher = () => {
53
+ watcherAbort?.abort();
54
+ watcher = undefined;
55
+ watcherAbort = undefined;
56
+ };
57
+
58
+ const pause = (signal, delayMs) => new Promise((resolve) => {
59
+ const finish = () => {
60
+ signal.removeEventListener('abort', finish);
61
+ clearTimeout(timer);
62
+ resolve();
63
+ };
64
+ const timer = setTimeout(finish, delayMs);
65
+ signal.addEventListener('abort', finish, { once: true });
66
+ });
67
+
47
68
  const wake = async (piContext, token, signal) => {
48
69
  while (sessionId !== undefined && token === generation && !signal.aborted) {
70
+ if (lookupSessionBindings(sessionId).length === 0) {
71
+ await pause(signal, 1_000);
72
+ continue;
73
+ }
49
74
  const deferredRetry = retryAfterChange;
50
75
  const pending = await waitForSessionPending(sessionId, 30_000, {
51
76
  signal,
@@ -70,10 +95,21 @@ export default function squarePiExtension(pi) {
70
95
  const delivered = await presentPendingAtBoundary(
71
96
  sessionId,
72
97
  async (content) => {
73
- await pi.sendMessage(
98
+ const send = Promise.resolve(pi.sendMessage(
74
99
  { customType: 'square', content, display: true },
75
100
  { deliverAs: 'steer', triggerTurn: true },
76
- );
101
+ ));
102
+ let timer;
103
+ try {
104
+ await Promise.race([
105
+ send,
106
+ new Promise((_, reject) => {
107
+ timer = setTimeout(() => reject(new Error('Pi native injection timed out')), PI_SEND_TIMEOUT_MS);
108
+ }),
109
+ ]);
110
+ } finally {
111
+ if (timer !== undefined) clearTimeout(timer);
112
+ }
77
113
  return true;
78
114
  },
79
115
  );
@@ -91,17 +127,15 @@ export default function squarePiExtension(pi) {
91
127
 
92
128
  pi.on('session_start', async (_event, ctx) => {
93
129
  generation += 1;
94
- watcherAbort?.abort();
95
- if (watcher) await watcher;
96
- watcher = undefined;
97
- watcherAbort = undefined;
130
+ stopWatcher();
98
131
  handledPending.clear();
99
132
  retryAfterChange = false;
100
133
  sessionId = ctx.sessionManager.getSessionId();
101
134
  sessionCwd = ctx.cwd || process.cwd();
102
135
  previousSessionId = process.env.SQUARE_PI_SESSION_ID;
103
136
  process.env.SQUARE_PI_SESSION_ID = sessionId;
104
- try { joiningContext = await automaticSessionStart('pi', sessionId, sessionCwd); } catch { joiningContext = undefined; }
137
+ joiningContext = undefined;
138
+ void automaticSessionStart('pi', sessionId, sessionCwd).catch(() => undefined);
105
139
  watcherAbort = new AbortController();
106
140
  const token = generation;
107
141
  watcher = wake(ctx, token, watcherAbort.signal).catch(() => undefined);
@@ -130,13 +164,10 @@ export default function squarePiExtension(pi) {
130
164
 
131
165
  pi.on('session_shutdown', async () => {
132
166
  generation += 1;
133
- watcherAbort?.abort();
134
- if (watcher) await watcher;
135
- watcher = undefined;
136
- watcherAbort = undefined;
167
+ stopWatcher();
137
168
  for (const waiter of settledWaiters) waiter.resolve();
138
169
  settledWaiters = [];
139
- if (sessionId && sessionCwd) await automaticSessionEnd('pi', sessionId, sessionCwd);
170
+ if (sessionId && sessionCwd) void automaticSessionEnd('pi', sessionId, sessionCwd).catch(() => undefined);
140
171
  if (process.env.SQUARE_PI_SESSION_ID === sessionId) {
141
172
  if (previousSessionId === undefined) delete process.env.SQUARE_PI_SESSION_ID;
142
173
  else process.env.SQUARE_PI_SESSION_ID = previousSessionId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/square",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
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": {