@astrosheep/square 0.3.47 → 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.
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/boundary-presentation.js +7 -4
- package/dist/codex-hook.js +5 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/extensions/square-pi.js +189 -116
- package/package.json +1 -1
|
@@ -30,6 +30,9 @@ export function pendingAtBoundary(inbox) {
|
|
|
30
30
|
export function renderPendingAtBoundary(inbox) {
|
|
31
31
|
return renderBoundary(inbox).context;
|
|
32
32
|
}
|
|
33
|
+
function frameBoundary(lines) {
|
|
34
|
+
return `\n${lines.join('\n')}\n`;
|
|
35
|
+
}
|
|
33
36
|
function renderBoundary(inbox) {
|
|
34
37
|
const count = pendingCount(inbox);
|
|
35
38
|
const noun = count === 1 ? 'notification' : 'notifications';
|
|
@@ -52,7 +55,7 @@ function renderBoundary(inbox) {
|
|
|
52
55
|
}),
|
|
53
56
|
].join('\n');
|
|
54
57
|
const omittedAfter = omitted + queued.length - index - 1;
|
|
55
|
-
const prospective = [
|
|
58
|
+
const prospective = frameBoundary([
|
|
56
59
|
header,
|
|
57
60
|
...blocks,
|
|
58
61
|
block,
|
|
@@ -60,7 +63,7 @@ function renderBoundary(inbox) {
|
|
|
60
63
|
? [`… ${omittedAfter} ${omittedAfter === 1 ? 'notification' : 'notifications'} omitted.`]
|
|
61
64
|
: []),
|
|
62
65
|
...footer,
|
|
63
|
-
]
|
|
66
|
+
]);
|
|
64
67
|
if (prospective.length > CONTEXT_MAX) {
|
|
65
68
|
omitted += 1;
|
|
66
69
|
continue;
|
|
@@ -73,14 +76,14 @@ function renderBoundary(inbox) {
|
|
|
73
76
|
});
|
|
74
77
|
}
|
|
75
78
|
return {
|
|
76
|
-
context: [
|
|
79
|
+
context: frameBoundary([
|
|
77
80
|
header,
|
|
78
81
|
...blocks,
|
|
79
82
|
...(omitted > 0
|
|
80
83
|
? [`… ${omitted} ${omitted === 1 ? 'notification' : 'notifications'} omitted.`]
|
|
81
84
|
: []),
|
|
82
85
|
...footer,
|
|
83
|
-
]
|
|
86
|
+
]),
|
|
84
87
|
complete,
|
|
85
88
|
};
|
|
86
89
|
}
|
package/dist/codex-hook.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
?
|
|
22
|
-
|
|
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';
|
package/extensions/square-pi.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
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
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
class PiDeliveryDroppedError extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super('Pi discarded the queued Square notification');
|
|
10
|
+
this.name = 'PiDeliveryDroppedError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
9
13
|
|
|
10
14
|
function sessionBindings(sessionId) {
|
|
11
15
|
return projectSessionBindings({ hostLedger: createHostLedgerPort(), sessionId });
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
function piBoundaryTimeoutMs() {
|
|
15
|
-
const configured = Number.parseInt(process.env.SQUARE_PI_BOUNDARY_TIMEOUT_MS || '', 10);
|
|
16
|
-
return Number.isFinite(configured) && configured > 0 ? configured : DEFAULT_PI_BOUNDARY_TIMEOUT_MS;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
18
|
export function pendingInbox(inbox) {
|
|
20
19
|
return inbox.filter((item) => item.notifications?.length > 0);
|
|
21
20
|
}
|
|
@@ -33,67 +32,76 @@ export function renderPiInbox(inbox) {
|
|
|
33
32
|
export default function squarePiExtension(pi) {
|
|
34
33
|
let sessionId;
|
|
35
34
|
let sessionCwd;
|
|
36
|
-
let joiningContext;
|
|
37
35
|
let previousSessionId;
|
|
38
36
|
let watcher;
|
|
39
37
|
let watcherAbort;
|
|
40
38
|
let generation = 0;
|
|
41
|
-
let presenting = false;
|
|
42
|
-
let settledSerial = 0;
|
|
43
|
-
let settledWaiters = [];
|
|
44
39
|
const handledPending = new Set();
|
|
40
|
+
const observedPending = new Set();
|
|
41
|
+
const landingAcks = new Map();
|
|
45
42
|
let retryAfterChange = false;
|
|
46
43
|
let retryWait;
|
|
47
|
-
|
|
44
|
+
let turnIndex = 0;
|
|
45
|
+
let activeTurn;
|
|
46
|
+
let currentRunSignal;
|
|
47
|
+
let detachRunAbort;
|
|
48
|
+
let cancelledRun = false;
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const pending = present((context) => {
|
|
64
|
-
// A lookup may finish after the deadline or after session replacement.
|
|
65
|
-
// Refuse before rendering so that late work cannot acknowledge anything.
|
|
66
|
-
controller.signal.throwIfAborted();
|
|
67
|
-
return deliver(context);
|
|
68
|
-
}, controller.signal);
|
|
69
|
-
boundaryPending = pending;
|
|
70
|
-
void pending?.finally(() => {
|
|
71
|
-
if (boundaryPending === pending) boundaryPending = undefined;
|
|
72
|
-
}).catch(() => undefined);
|
|
73
|
-
return await Promise.race([pending, expired]);
|
|
74
|
-
} finally {
|
|
75
|
-
clearTimeout(timer);
|
|
76
|
-
boundaryControllers.delete(controller);
|
|
77
|
-
}
|
|
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;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const failAcks = (error) => {
|
|
61
|
+
const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
|
|
62
|
+
for (const ack of acks) ack.settle(error);
|
|
78
63
|
};
|
|
79
64
|
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
65
|
+
const acknowledgeLanding = (message) => {
|
|
66
|
+
if (message?.role !== 'custom' || message.customType !== 'square' || typeof message.content !== 'string') return;
|
|
67
|
+
const acks = landingAcks.get(message.content);
|
|
68
|
+
if (acks === undefined) return;
|
|
69
|
+
for (const ack of [...acks]) ack.settle();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const waitForLanding = (content, signal, kind) => {
|
|
73
|
+
let ack;
|
|
74
|
+
const promise = new Promise((resolve, reject) => {
|
|
75
|
+
const abort = () => ack.settle(signal.reason || new Error('Pi native injection aborted'));
|
|
76
|
+
ack = {
|
|
77
|
+
kind,
|
|
78
|
+
sentAfterTurn: turnIndex,
|
|
79
|
+
emptyTurnWindows: 0,
|
|
80
|
+
settle(error) {
|
|
81
|
+
if (ack.settled) return;
|
|
82
|
+
ack.settled = true;
|
|
83
|
+
signal.removeEventListener('abort', abort);
|
|
84
|
+
const acks = landingAcks.get(content);
|
|
85
|
+
if (acks?.delete(ack) && acks.size === 0) landingAcks.delete(content);
|
|
86
|
+
if (error === undefined) resolve();
|
|
87
|
+
else reject(error);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
let acks = landingAcks.get(content);
|
|
91
|
+
if (acks === undefined) {
|
|
92
|
+
acks = new Set();
|
|
93
|
+
landingAcks.set(content, acks);
|
|
94
|
+
}
|
|
95
|
+
acks.add(ack);
|
|
96
|
+
if (signal.aborted) abort();
|
|
97
|
+
else signal.addEventListener('abort', abort, { once: true });
|
|
90
98
|
});
|
|
99
|
+
return { promise, ack };
|
|
91
100
|
};
|
|
92
101
|
|
|
93
102
|
// A transport may keep its promise pending while Pi is shutting down or
|
|
94
103
|
// replacing a session. Never make a lifecycle hook wait for that transport.
|
|
95
104
|
const stopWatcher = () => {
|
|
96
|
-
for (const controller of boundaryControllers) controller.abort(new Error('Pi session ended'));
|
|
97
105
|
watcherAbort?.abort();
|
|
98
106
|
watcher = undefined;
|
|
99
107
|
watcherAbort = undefined;
|
|
@@ -109,7 +117,7 @@ export default function squarePiExtension(pi) {
|
|
|
109
117
|
signal.addEventListener('abort', finish, { once: true });
|
|
110
118
|
});
|
|
111
119
|
|
|
112
|
-
const wake = async (
|
|
120
|
+
const wake = async (token, signal) => {
|
|
113
121
|
const armDeferredRetry = () => {
|
|
114
122
|
const controller = new AbortController();
|
|
115
123
|
let resolveArmed;
|
|
@@ -129,14 +137,26 @@ export default function squarePiExtension(pi) {
|
|
|
129
137
|
};
|
|
130
138
|
|
|
131
139
|
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
132
|
-
|
|
140
|
+
try {
|
|
141
|
+
if ((await sessionBindings(sessionId)).length === 0) {
|
|
142
|
+
await pause(signal, 1_000);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
133
146
|
await pause(signal, 1_000);
|
|
134
147
|
continue;
|
|
135
148
|
}
|
|
149
|
+
|
|
136
150
|
const deferredRetry = retryAfterChange;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
151
|
+
let pending;
|
|
152
|
+
try {
|
|
153
|
+
pending = deferredRetry && retryWait !== undefined
|
|
154
|
+
? await retryWait
|
|
155
|
+
: await waitForSessionPending(sessionId, 30_000, { signal, excludeKeys: handledPending });
|
|
156
|
+
} catch {
|
|
157
|
+
await pause(signal, 1_000);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
140
160
|
if (sessionId === undefined || token !== generation || signal.aborted) return;
|
|
141
161
|
if (pending.length === 0) {
|
|
142
162
|
if (deferredRetry) {
|
|
@@ -147,112 +167,165 @@ export default function squarePiExtension(pi) {
|
|
|
147
167
|
}
|
|
148
168
|
retryAfterChange = false;
|
|
149
169
|
retryWait = undefined;
|
|
150
|
-
|
|
151
|
-
const serial = settledSerial;
|
|
152
|
-
await waitForSettled(serial, signal);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (presenting) continue;
|
|
170
|
+
|
|
156
171
|
const keys = inboxKeys(pending);
|
|
157
|
-
|
|
172
|
+
for (const key of keys) observedPending.add(key);
|
|
158
173
|
const deferred = armDeferredRetry();
|
|
159
174
|
const retryArmed = await deferred.armed;
|
|
160
175
|
if (!retryArmed) {
|
|
161
176
|
deferred.cancel();
|
|
162
|
-
presenting = false;
|
|
163
177
|
continue;
|
|
164
178
|
}
|
|
165
179
|
let keepDeferred = false;
|
|
166
180
|
try {
|
|
167
|
-
|
|
181
|
+
await presentPendingAtBoundary(
|
|
168
182
|
sessionId,
|
|
169
183
|
async (content) => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
{ deliverAs: 'steer', triggerTurn: true },
|
|
173
|
-
));
|
|
174
|
-
let timer;
|
|
184
|
+
if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
|
|
185
|
+
const landing = waitForLanding(content, signal, 'steer');
|
|
175
186
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
signal.addEventListener('abort', () => reject(signal.reason || new Error('Pi native injection aborted')), { once: true });
|
|
184
|
-
}),
|
|
185
|
-
new Promise((_, reject) => {
|
|
186
|
-
timer = setTimeout(() => reject(new Error('Pi native injection timed out')), PI_SEND_TIMEOUT_MS);
|
|
187
|
-
}),
|
|
188
|
-
]);
|
|
189
|
-
} finally {
|
|
190
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
187
|
+
Promise.resolve(pi.sendMessage(
|
|
188
|
+
{ customType: 'square', content, display: true },
|
|
189
|
+
{ deliverAs: 'steer', triggerTurn: true },
|
|
190
|
+
)).catch((error) => landing.ack.settle(error));
|
|
191
|
+
} catch (error) {
|
|
192
|
+
landing.ack.settle(error);
|
|
191
193
|
}
|
|
192
|
-
return
|
|
194
|
+
return landing.promise;
|
|
195
|
+
},
|
|
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
|
+
}));
|
|
193
206
|
},
|
|
194
|
-
undefined,
|
|
195
207
|
undefined,
|
|
196
208
|
signal,
|
|
197
209
|
);
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
for (const key of keys) handledPending.add(key);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (signal.aborted || token !== generation) return;
|
|
213
|
+
if (error instanceof PiDeliveryDroppedError) {
|
|
214
|
+
deferred.cancel();
|
|
215
|
+
continue;
|
|
200
216
|
}
|
|
201
|
-
} catch {
|
|
202
217
|
// The next state edge is already being observed before native injection starts.
|
|
203
218
|
retryAfterChange = true;
|
|
204
219
|
retryWait = deferred.pending;
|
|
205
220
|
keepDeferred = true;
|
|
206
221
|
} finally {
|
|
207
222
|
if (!keepDeferred) deferred.cancel();
|
|
208
|
-
presenting = false;
|
|
209
223
|
}
|
|
210
224
|
}
|
|
211
225
|
};
|
|
212
226
|
|
|
213
227
|
pi.on('session_start', async (_event, ctx) => {
|
|
228
|
+
detachRunAbort?.();
|
|
229
|
+
cancelledRun = false;
|
|
214
230
|
generation += 1;
|
|
231
|
+
failAcks(new Error('Pi session replaced'));
|
|
215
232
|
stopWatcher();
|
|
216
233
|
handledPending.clear();
|
|
234
|
+
observedPending.clear();
|
|
217
235
|
retryAfterChange = false;
|
|
218
236
|
retryWait = undefined;
|
|
237
|
+
turnIndex = 0;
|
|
238
|
+
activeTurn = undefined;
|
|
239
|
+
currentRunSignal = undefined;
|
|
219
240
|
sessionId = ctx.sessionManager.getSessionId();
|
|
220
241
|
sessionCwd = ctx.cwd || process.cwd();
|
|
221
242
|
previousSessionId = process.env.SQUARE_PI_SESSION_ID;
|
|
222
243
|
process.env.SQUARE_PI_SESSION_ID = sessionId;
|
|
223
|
-
joiningContext = undefined;
|
|
224
|
-
void automaticSessionStart('pi', sessionId, sessionCwd).catch(() => undefined);
|
|
225
|
-
watcherAbort = new AbortController();
|
|
226
244
|
const token = generation;
|
|
227
|
-
|
|
245
|
+
watcherAbort = new AbortController();
|
|
246
|
+
const signal = watcherAbort.signal;
|
|
247
|
+
void automaticSessionStart('pi', sessionId, sessionCwd).then((context) => {
|
|
248
|
+
if (context === undefined || sessionId === undefined || token !== generation) return;
|
|
249
|
+
const landing = waitForLanding(context, signal, 'nextTurn');
|
|
250
|
+
try {
|
|
251
|
+
Promise.resolve(pi.sendMessage(
|
|
252
|
+
{ customType: 'square', content: context, display: true },
|
|
253
|
+
{ deliverAs: 'nextTurn' },
|
|
254
|
+
)).catch((error) => landing.ack.settle(error));
|
|
255
|
+
} catch {
|
|
256
|
+
// Joining context is advisory; an unavailable Pi transport does not block startup.
|
|
257
|
+
landing.ack.settle(new Error('Pi joining-context injection failed'));
|
|
258
|
+
}
|
|
259
|
+
void landing.promise.catch(() => undefined);
|
|
260
|
+
}).catch(() => undefined);
|
|
261
|
+
watcher = wake(token, signal).catch(() => undefined);
|
|
228
262
|
});
|
|
229
263
|
|
|
230
|
-
pi.on('
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
264
|
+
pi.on('agent_start', async (_event, ctx) => {
|
|
265
|
+
detachRunAbort?.();
|
|
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 });
|
|
235
272
|
});
|
|
236
273
|
|
|
237
|
-
pi.on('
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
274
|
+
pi.on('message_end', async (event) => {
|
|
275
|
+
if (activeTurn !== undefined && (event.message?.role === 'user' || event.message?.role === 'custom')) {
|
|
276
|
+
activeTurn.hasInput = true;
|
|
277
|
+
}
|
|
278
|
+
acknowledgeLanding(event.message);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
pi.on('turn_start', async () => {
|
|
282
|
+
turnIndex += 1;
|
|
283
|
+
activeTurn = { index: turnIndex, hasInput: false, signal: currentRunSignal };
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
pi.on('turn_end', async (event, ctx) => {
|
|
287
|
+
const turn = activeTurn;
|
|
288
|
+
activeTurn = undefined;
|
|
289
|
+
const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
|
|
290
|
+
if (event.message?.stopReason === 'aborted' && ctx?.mode === 'tui') {
|
|
291
|
+
if (!cancelledRun) cancelRunDelivery();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (turn === undefined) return;
|
|
295
|
+
for (const ack of acks) {
|
|
296
|
+
if (ack.kind !== 'steer' || turn.index <= ack.sentAfterTurn) continue;
|
|
297
|
+
if (turn.hasInput) {
|
|
298
|
+
ack.emptyTurnWindows = 0;
|
|
299
|
+
} else {
|
|
300
|
+
ack.emptyTurnWindows += 1;
|
|
301
|
+
if (ack.emptyTurnWindows >= 2) ack.settle(new PiDeliveryDroppedError());
|
|
243
302
|
}
|
|
244
|
-
if (presenting) return undefined;
|
|
245
|
-
return await presentAtBoundary((context) => ({ message: { customType: 'square', content: context, display: true } }));
|
|
246
|
-
} catch {
|
|
247
|
-
return undefined;
|
|
248
303
|
}
|
|
249
304
|
});
|
|
250
305
|
|
|
306
|
+
pi.on('agent_settled', async () => {
|
|
307
|
+
detachRunAbort?.();
|
|
308
|
+
detachRunAbort = undefined;
|
|
309
|
+
currentRunSignal = undefined;
|
|
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);
|
|
320
|
+
});
|
|
321
|
+
|
|
251
322
|
pi.on('session_shutdown', async () => {
|
|
323
|
+
detachRunAbort?.();
|
|
324
|
+
detachRunAbort = undefined;
|
|
325
|
+
cancelledRun = false;
|
|
252
326
|
generation += 1;
|
|
327
|
+
failAcks(new Error('Pi session ended'));
|
|
253
328
|
stopWatcher();
|
|
254
|
-
for (const waiter of settledWaiters) waiter.resolve();
|
|
255
|
-
settledWaiters = [];
|
|
256
329
|
if (sessionId && sessionCwd) void automaticSessionEnd('pi', sessionId, sessionCwd).catch(() => undefined);
|
|
257
330
|
if (process.env.SQUARE_PI_SESSION_ID === sessionId) {
|
|
258
331
|
if (previousSessionId === undefined) delete process.env.SQUARE_PI_SESSION_ID;
|
|
@@ -260,8 +333,8 @@ export default function squarePiExtension(pi) {
|
|
|
260
333
|
}
|
|
261
334
|
sessionId = undefined;
|
|
262
335
|
sessionCwd = undefined;
|
|
263
|
-
joiningContext = undefined;
|
|
264
|
-
presenting = false;
|
|
265
336
|
retryWait = undefined;
|
|
337
|
+
activeTurn = undefined;
|
|
338
|
+
currentRunSignal = undefined;
|
|
266
339
|
});
|
|
267
340
|
}
|