@astrosheep/square 0.4.0 → 0.4.1
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/dist/artifact.d.ts +1 -3
- package/dist/artifact.js +12 -36
- package/dist/attention-presentation.d.ts +2 -22
- package/dist/attention-presentation.js +9 -12
- package/dist/boundary-presentation.d.ts +3 -14
- package/dist/boundary-presentation.js +29 -104
- package/dist/card-presentation.d.ts +1 -0
- package/dist/card-presentation.js +2 -0
- package/dist/catch-decisions.js +1 -7
- package/dist/claude-delivery.d.ts +13 -8
- package/dist/claude-delivery.js +40 -93
- package/dist/claude-mcp-context.js +2 -2
- package/dist/claude-mod.js +28 -140
- package/dist/claude-transport.d.ts +3 -3
- package/dist/claude-transport.js +20 -44
- package/dist/cli/context.js +7 -9
- package/dist/cli/observation-commands.d.ts +2 -0
- package/dist/cli/observation-commands.js +12 -2
- package/dist/cli/registry.js +3 -1
- package/dist/codex-address.d.ts +2 -2
- package/dist/codex-boundary-state.d.ts +1 -5
- package/dist/codex-boundary-state.js +0 -4
- package/dist/codex-hook.d.ts +7 -10
- package/dist/codex-hook.js +69 -33
- package/dist/codex-queue.d.ts +10 -11
- package/dist/codex-queue.js +32 -32
- package/dist/codex-watch.d.ts +2 -0
- package/dist/codex-watch.js +100 -0
- package/dist/decisions.js +2 -2
- package/dist/delivery-deferral.d.ts +8 -17
- package/dist/delivery-deferral.js +0 -28
- package/dist/delivery-frame.d.ts +7 -0
- package/dist/delivery-frame.js +11 -0
- package/dist/delivery-ledger-sqlite.d.ts +97 -0
- package/dist/delivery-ledger-sqlite.js +496 -0
- package/dist/delivery-ledger.d.ts +196 -0
- package/dist/delivery-ledger.js +268 -0
- package/dist/delivery-lifetime.d.ts +6 -0
- package/dist/delivery-lifetime.js +45 -0
- package/dist/harness-links.js +61 -7
- package/dist/harness.js +12 -17
- package/dist/help.js +2 -0
- package/dist/http-delivery.d.ts +5 -9
- package/dist/http-delivery.js +20 -77
- package/dist/inbox.d.ts +6 -49
- package/dist/inbox.js +24 -371
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -1
- package/dist/mcp.js +1 -1
- package/dist/model.d.ts +7 -47
- package/dist/model.js +1 -7
- package/dist/notifications.d.ts +24 -20
- package/dist/notifications.js +39 -108
- package/dist/operation-control.d.ts +4 -0
- package/dist/operation-control.js +20 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +2 -0
- package/dist/packages/agent-delivery/src/claude-native.js +5 -0
- package/dist/packages/agent-delivery/src/codex-boundary.d.ts +11 -6
- package/dist/packages/agent-delivery/src/codex-boundary.js +31 -40
- package/dist/packages/agent-delivery/src/codex-hook.d.ts +17 -12
- package/dist/packages/agent-delivery/src/codex-hook.js +26 -39
- package/dist/packages/agent-delivery/src/codex.d.ts +3 -0
- package/dist/packages/agent-delivery/src/codex.js +41 -4
- package/dist/packages/agent-delivery/src/index.d.ts +3 -1
- package/dist/packages/agent-delivery/src/index.js +1 -1
- package/dist/packages/agent-delivery/src/paseo.js +11 -2
- package/dist/packages/agent-delivery/src/types.d.ts +1 -0
- package/dist/paseo-delivery.d.ts +5 -5
- package/dist/paseo-delivery.js +19 -97
- package/dist/paseo.d.ts +1 -1
- package/dist/paseo.js +1 -1
- package/dist/plugins/opencode/index.d.ts +5 -3
- package/dist/plugins/opencode/index.js +99 -21
- package/dist/plugins/opencode-shared/delivery.d.ts +23 -0
- package/dist/plugins/opencode-shared/delivery.js +109 -0
- package/dist/plugins/opencode-v1/index.d.ts +28 -0
- package/dist/plugins/opencode-v1/index.js +41 -0
- package/dist/plugins/opencode-v1/runtime.d.ts +56 -0
- package/dist/plugins/opencode-v1/runtime.js +152 -0
- package/dist/plugins/opencode-v1/transport.d.ts +13 -0
- package/dist/plugins/opencode-v1/transport.js +50 -0
- package/dist/plugins/paseo/shared/card-presentation.d.ts +10 -0
- package/dist/plugins/paseo/shared/card-presentation.js +7 -0
- package/dist/ports.d.ts +31 -90
- package/dist/session-lifecycle.d.ts +8 -7
- package/dist/session-lifecycle.js +40 -42
- package/dist/sqlite-square.d.ts +14 -23
- package/dist/sqlite-square.js +132 -139
- package/dist/square-api.js +4 -7
- package/dist/square-core.d.ts +0 -1
- package/dist/square-domain.d.ts +5 -17
- package/dist/square-domain.js +104 -221
- package/dist/square-facade.d.ts +18 -15
- package/dist/square-post.d.ts +53 -0
- package/dist/square-post.js +4 -0
- package/dist/square-posting.d.ts +13 -0
- package/dist/square-posting.js +174 -0
- package/dist/square-producer-sqlite.d.ts +11 -0
- package/dist/square-producer-sqlite.js +210 -0
- package/dist/square-sqlite.d.ts +15 -25
- package/dist/square-sqlite.js +150 -226
- package/dist/views.d.ts +2 -2
- package/dist/views.js +0 -1
- package/dist/wake-sink.d.ts +4 -11
- package/dist/wake-sink.js +8 -32
- package/dist/wake-transports.d.ts +4 -7
- package/dist/wake-transports.js +8 -39
- package/examples/symmetric-domains.mjs +29 -249
- package/guides/change-waits.md +13 -53
- package/guides/claude-native.md +19 -175
- package/guides/codex-delivery.md +55 -80
- package/guides/delivery-primitives.md +48 -207
- package/guides/operation-cancellation.md +2 -5
- package/guides/participant-ownership.md +25 -89
- package/guides/pi-native-delivery.md +19 -56
- package/guides/pi-tools.md +6 -8
- package/guides/square-api.md +113 -615
- package/package.json +8 -8
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/hooks/hooks.json +1 -1
- package/plugins/claude/{hooks/register.js → mods/square.js} +4 -7
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/codex/hooks/hooks.json +11 -23
- package/plugins/opencode/index.ts +91 -23
- package/plugins/opencode-shared/delivery.ts +91 -0
- package/plugins/opencode-v1/index.ts +36 -0
- package/plugins/opencode-v1/runtime.ts +134 -0
- package/plugins/opencode-v1/transport.ts +41 -0
- package/plugins/paseo/OVERVIEW.md +5 -5
- package/plugins/paseo/README.md +7 -6
- package/plugins/paseo/client/markdown.tsx +5 -5
- package/plugins/paseo/client/square-message.tsx +28 -24
- package/plugins/paseo/client/transform.ts +8 -5
- package/plugins/paseo/shared/card-presentation.ts +16 -0
- package/plugins/paseo/shared/message.ts +6 -11
- package/plugins/pi/square-pi.js +156 -417
- package/dist/channel-artifact.d.ts +0 -10
- package/dist/channel-artifact.js +0 -55
- package/dist/channel-delivery.d.ts +0 -11
- package/dist/channel-delivery.js +0 -51
- package/dist/channel-domain.d.ts +0 -3
- package/dist/channel-domain.js +0 -83
- package/dist/channel-facade.d.ts +0 -76
- package/dist/channel-facade.js +0 -1
- package/dist/channel-internal.d.ts +0 -126
- package/dist/channel-internal.js +0 -440
- package/dist/channel-metadata.d.ts +0 -11
- package/dist/channel-metadata.js +0 -1
- package/dist/channel-ports.d.ts +0 -241
- package/dist/channel-ports.js +0 -1
- package/dist/channel-presentation.d.ts +0 -17
- package/dist/channel-presentation.js +0 -22
- package/dist/channel-sqlite-schema.d.ts +0 -13
- package/dist/channel-sqlite-schema.js +0 -81
- package/dist/channel-transport.d.ts +0 -62
- package/dist/channel-transport.js +0 -1
- package/dist/channel.d.ts +0 -84
- package/dist/channel.js +0 -60
- package/dist/claude-delivery-status.d.ts +0 -13
- package/dist/claude-delivery-status.js +0 -46
- package/dist/cmd/notify-once.d.ts +0 -2
- package/dist/cmd/notify-once.js +0 -29
- package/dist/delivery-attempts.d.ts +0 -52
- package/dist/delivery-attempts.js +0 -73
- package/dist/delivery-effects.d.ts +0 -139
- package/dist/delivery-effects.js +0 -163
- package/dist/delivery-health.d.ts +0 -51
- package/dist/delivery-health.js +0 -173
- package/dist/delivery-operations.d.ts +0 -22
- package/dist/delivery-operations.js +0 -198
- package/dist/delivery-primitives.d.ts +0 -5
- package/dist/delivery-primitives.js +0 -3
- package/dist/delivery.d.ts +0 -56
- package/dist/delivery.js +0 -80
- package/dist/plugins/opencode/delivery.d.ts +0 -19
- package/dist/plugins/opencode/delivery.js +0 -223
- package/dist/presentation-operations.d.ts +0 -5
- package/dist/presentation-operations.js +0 -86
- package/dist/sqlite-channel.d.ts +0 -15
- package/dist/sqlite-channel.js +0 -600
- package/dist/square-index.d.ts +0 -16
- package/dist/square-index.js +0 -106
- package/dist/wake-preview.d.ts +0 -7
- package/dist/wake-preview.js +0 -15
- package/plugins/opencode/delivery.ts +0 -198
- /package/dist/{channel-values.d.ts → square-values.d.ts} +0 -0
- /package/dist/{channel-values.js → square-values.js} +0 -0
package/plugins/pi/square-pi.js
CHANGED
|
@@ -1,147 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Container, Markdown, Spacer, Text } from '@earendil-works/pi-tui';
|
|
1
|
+
import { Markdown } from '@earendil-works/pi-tui';
|
|
3
2
|
import { z } from 'zod/v4';
|
|
4
3
|
|
|
5
|
-
import { Square
|
|
4
|
+
import { Square } from '../../dist/square-api.js';
|
|
5
|
+
import { harnessOwner } from '../../dist/owner.js';
|
|
6
|
+
import { DeliveryLedger } from '../../dist/delivery-ledger.js';
|
|
7
|
+
import { deliveryFrameOverhead, renderDeliveryReminder } from '../../dist/delivery-frame.js';
|
|
8
|
+
import { isCardPresentation } from '../../dist/card-presentation.js';
|
|
6
9
|
import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, createSquareToolRunner, squareToolsEnabled } from '../../dist/square-tools.js';
|
|
7
|
-
|
|
8
|
-
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../../dist/boundary-presentation.js';
|
|
9
10
|
import { operationEnv } from '../../dist/participant-identity.js';
|
|
10
|
-
import {
|
|
11
|
-
import { projectSessionSeats, observeSessionPending, pendingKey } from '../../dist/inbox.js';
|
|
12
|
-
import { projectMessageNotification } from '../../dist/attention-presentation.js';
|
|
13
|
-
import { formatActivityId } from '../../dist/square-core.js';
|
|
14
|
-
import { formatMessageId } from '../../dist/message.js';
|
|
11
|
+
import { attachDeliverySession, detachDeliverySession } from '../../dist/session-lifecycle.js';
|
|
15
12
|
import { createPiReceiver, sendPiMessage } from '../../dist/packages/agent-delivery/src/pi.js';
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
constructor() {
|
|
19
|
-
super('Pi discarded the queued Square notification');
|
|
20
|
-
this.name = 'PiDeliveryDroppedError';
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function pendingInbox(inbox) {
|
|
25
|
-
return inbox.filter((item) => item.notifications?.length > 0);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function inboxKeys(inbox) {
|
|
29
|
-
return pendingInbox(inbox).flatMap((item) => item.notifications.map((note) =>
|
|
30
|
-
pendingKey(item, note.messageIndex)
|
|
31
|
-
));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function renderPiInbox(inbox) {
|
|
35
|
-
return renderPendingAtBoundary(pendingInbox(inbox));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function framePiMessage(content) {
|
|
39
|
-
return `\n${content.replace(/^\n+/, '').replace(/\n+$/, '')}\n`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Renderer-only card facts for Pi and Paseo; Pi never sends `details` to the model. */
|
|
43
|
-
export function pendingCardDetails(pending) {
|
|
44
|
-
const items = pending.flatMap((membership) => membership.notifications.map((note) => {
|
|
45
|
-
const projected = projectMessageNotification({ squarePath: membership.location, recipient: membership.seat.participant,
|
|
46
|
-
actor: note.actor, body: note.body, ...(note.preview === undefined ? {} : { preview: note.preview }),
|
|
47
|
-
...(note.actIndex === undefined || note.route === undefined ? {} : { agora: { actIndex: note.actIndex, route: note.route } }) });
|
|
48
|
-
return ({
|
|
49
|
-
square: path.basename(membership.location, '.square'),
|
|
50
|
-
actor: note.actor,
|
|
51
|
-
recipient: membership.seat.participant,
|
|
52
|
-
route: note.route,
|
|
53
|
-
act: note.actIndex === undefined ? formatMessageId(note.messageIndex) : formatActivityId(note.actIndex),
|
|
54
|
-
preview: projected.preview,
|
|
55
|
-
description: projected.description,
|
|
56
|
-
body: note.body,
|
|
57
|
-
}); }));
|
|
58
|
-
if (items.length === 0) return undefined;
|
|
59
|
-
const fullText = pending.flatMap((membership) => membership.notifications.map((note) => projectMessageNotification({
|
|
60
|
-
squarePath: membership.location, recipient: membership.seat.participant, actor: note.actor, body: note.body,
|
|
61
|
-
...(note.preview === undefined ? {} : { preview: note.preview }),
|
|
62
|
-
...(note.actIndex === undefined || note.route === undefined ? {} : { agora: { actIndex: note.actIndex, route: note.route } }),
|
|
63
|
-
}).full)).join('\n\n');
|
|
64
|
-
return { items, fullText };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Conservative terminal width: anything outside ASCII counts as two columns, so a
|
|
68
|
-
// truncated line never exceeds the viewport (Pi stops the TUI when a line overflows).
|
|
69
|
-
const columnsOf = (char) => (char.charCodeAt(0) < 0x80 ? 1 : 2);
|
|
70
|
-
const widthOf = (text) => Array.from(text).reduce((sum, char) => sum + columnsOf(char), 0);
|
|
14
|
+
const BUDGET_CHARS = 12_000;
|
|
71
15
|
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
let used = 1; // the ellipsis
|
|
75
|
-
let out = '';
|
|
76
|
-
for (const char of text) {
|
|
77
|
-
if (used + columnsOf(char) > columns) break;
|
|
78
|
-
used += columnsOf(char);
|
|
79
|
-
out += char;
|
|
80
|
-
}
|
|
81
|
-
return `${out}…`;
|
|
16
|
+
function displayCard(card) {
|
|
17
|
+
return isCardPresentation(card.details) ? card.details : { text: card.text };
|
|
82
18
|
}
|
|
83
19
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return {
|
|
20
|
+
export function renderSquareCard(message, { outputPad = 1 }, theme) {
|
|
21
|
+
if (typeof message.content !== 'string') return undefined;
|
|
22
|
+
const color = (name) => (text) => theme.fg?.(name, text) ?? text;
|
|
23
|
+
const style = (name) => (text) => theme[name]?.(text) ?? text;
|
|
24
|
+
return new Markdown(message.content, outputPad, 0, {
|
|
90
25
|
heading: color('mdHeading'), link: color('mdLink'), linkUrl: color('mdLinkUrl'), code: color('mdCode'),
|
|
91
26
|
codeBlock: color('mdCodeBlock'), codeBlockBorder: color('mdCodeBlockBorder'), quote: color('mdQuote'),
|
|
92
27
|
quoteBorder: color('mdQuoteBorder'), hr: color('mdHr'), listBullet: color('mdListBullet'),
|
|
93
|
-
bold: style(
|
|
94
|
-
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** One styled line that never exceeds the width; truncated lines stay unstyled so escapes are never cut. */
|
|
99
|
-
function fitLine(parts, width) {
|
|
100
|
-
const plain = parts.map(([text]) => text).join('');
|
|
101
|
-
return widthOf(plain) <= width ? parts.map(([text, paint]) => paint(text)).join('') : fitColumns(plain, width);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Collapsed: who reached you and where, then the first line of what they said.
|
|
106
|
-
* Expanded: each activity as a heading line and its Markdown body; the model's
|
|
107
|
-
* framing and continuation hints are not for human eyes.
|
|
108
|
-
*/
|
|
109
|
-
export function renderSquareCard(message, { expanded, outputPad = 1 }, theme) {
|
|
110
|
-
const items = message.details?.items;
|
|
111
|
-
if (!Array.isArray(items) || items.length === 0 || items.some((item) => typeof item.body !== 'string')) return undefined;
|
|
112
|
-
const pad = ' '.repeat(outputPad);
|
|
113
|
-
const dim = (text) => fg(theme, 'dim', text);
|
|
114
|
-
if (!expanded) {
|
|
115
|
-
const [first] = items;
|
|
116
|
-
const more = items.length > 1 ? ` +${items.length - 1}` : '';
|
|
117
|
-
// A glance, not a render: drop the commonest inline markers so the line reads as speech.
|
|
118
|
-
const opening = (first.body.split('\n').find((line) => line.trim() !== '') ?? '')
|
|
119
|
-
.trim().replace(/^(#{1,6}|>|[-*+])\s+/, '').replace(/\*\*|__|`/g, '');
|
|
120
|
-
return {
|
|
121
|
-
invalidate() {},
|
|
122
|
-
render(width) {
|
|
123
|
-
const room = Math.max(1, width - outputPad * 2 - 2);
|
|
124
|
-
return [
|
|
125
|
-
`${pad}${fg(theme, 'customMessageLabel', '■')} ${fitLine([[first.description, (t) => t], [` · ${first.square} · ${first.recipient}${more}`, dim]], room)}`,
|
|
126
|
-
...(opening ? [`${pad} ${fitLine([[opening, dim]], room)}`] : []),
|
|
127
|
-
];
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
const card = new Container();
|
|
132
|
-
for (const [index, item] of items.entries()) {
|
|
133
|
-
if (index > 0) card.addChild(new Spacer(1));
|
|
134
|
-
card.addChild({
|
|
135
|
-
invalidate() {},
|
|
136
|
-
render(width) {
|
|
137
|
-
const room = Math.max(1, width - outputPad * 2 - 2);
|
|
138
|
-
return [`${pad}${fg(theme, 'customMessageLabel', '●')} ${fitLine([[item.description, (t) => t], [` ${item.square} · ${item.recipient} · ${item.act}`, dim]], room)}`];
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
card.addChild(new Spacer(1));
|
|
142
|
-
card.addChild(new Markdown(item.body, outputPad + 2, 0, markdownTheme(theme)));
|
|
143
|
-
}
|
|
144
|
-
return card;
|
|
28
|
+
bold: style('bold'), italic: style('italic'), underline: style('underline'), strikethrough: style('strikethrough'),
|
|
29
|
+
});
|
|
145
30
|
}
|
|
146
31
|
|
|
147
32
|
export default function squarePiExtension(pi) {
|
|
@@ -170,339 +55,193 @@ export default function squarePiExtension(pi) {
|
|
|
170
55
|
}
|
|
171
56
|
pi.registerMessageRenderer?.('square', renderSquareCard);
|
|
172
57
|
pi.registerFlag('agent-delivery-socket', { type: 'string', description: 'Explicit private local delivery socket' });
|
|
173
|
-
createPiReceiver(pi, {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let
|
|
58
|
+
createPiReceiver(pi, { get endpoint() { return pi.getFlag('agent-delivery-socket'); } });
|
|
59
|
+
|
|
60
|
+
let session;
|
|
61
|
+
let epoch = 0;
|
|
177
62
|
let watcher;
|
|
178
63
|
let watcherAbort;
|
|
179
|
-
let
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
let retryAfterChange = false;
|
|
184
|
-
let retryWait;
|
|
64
|
+
let cancellation = Promise.resolve();
|
|
65
|
+
let cancellationFailed = false;
|
|
66
|
+
let cancelled = false;
|
|
67
|
+
let detachRunAbort;
|
|
185
68
|
let turnIndex = 0;
|
|
186
69
|
let activeTurn;
|
|
187
|
-
|
|
188
|
-
let detachRunAbort;
|
|
189
|
-
let cancelledRun = false;
|
|
190
|
-
let pendingCancellations = new Map();
|
|
191
|
-
const cancellationFailures = new Set();
|
|
192
|
-
|
|
193
|
-
const cancelRunDelivery = () => {
|
|
194
|
-
if (cancelledRun) return;
|
|
195
|
-
cancelledRun = true;
|
|
196
|
-
for (const key of observedPending) handledPending.add(key);
|
|
197
|
-
persistSessionCancellation(sessionId);
|
|
198
|
-
generation += 1;
|
|
199
|
-
stopWatcher();
|
|
200
|
-
failAcks(new Error('Pi notification delivery cancelled'));
|
|
201
|
-
retryAfterChange = false;
|
|
202
|
-
retryWait = undefined;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const failAcks = (error) => {
|
|
206
|
-
const acks = [...landingAcks.values()].flatMap((waiters) => [...waiters]);
|
|
207
|
-
for (const ack of acks) ack.settle(error);
|
|
208
|
-
};
|
|
70
|
+
const landings = new Map();
|
|
209
71
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const waitForLanding = (content, signal, kind) => {
|
|
218
|
-
let ack;
|
|
72
|
+
function finishLandings(error) {
|
|
73
|
+
for (const landing of [...landings.values()]) landing.finish(error);
|
|
74
|
+
}
|
|
75
|
+
function waitForLanding(content, signal) {
|
|
76
|
+
let landing;
|
|
219
77
|
const promise = new Promise((resolve, reject) => {
|
|
220
|
-
const abort = () =>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (ack.settled) return;
|
|
227
|
-
ack.settled = true;
|
|
78
|
+
const abort = () => landing.finish(signal.reason ?? new Error('Pi delivery aborted'));
|
|
79
|
+
landing = {
|
|
80
|
+
sentAfterTurn: turnIndex, emptyTurnWindows: 0,
|
|
81
|
+
finish(error) {
|
|
82
|
+
if (landings.get(content) !== landing) return;
|
|
83
|
+
landings.delete(content);
|
|
228
84
|
signal.removeEventListener('abort', abort);
|
|
229
|
-
|
|
230
|
-
if (acks?.delete(ack) && acks.size === 0) landingAcks.delete(content);
|
|
231
|
-
if (error === undefined) resolve();
|
|
232
|
-
else reject(error);
|
|
85
|
+
if (error) reject(error); else resolve();
|
|
233
86
|
},
|
|
234
87
|
};
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
acks = new Set();
|
|
238
|
-
landingAcks.set(content, acks);
|
|
239
|
-
}
|
|
240
|
-
acks.add(ack);
|
|
241
|
-
if (signal.aborted) abort();
|
|
242
|
-
else signal.addEventListener('abort', abort, { once: true });
|
|
243
|
-
});
|
|
244
|
-
return { promise, ack };
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// Each operation carries its own session coordinate. Never rewrite the process environment:
|
|
248
|
-
// Pi owns PI_SESSION_ID and several SDK sessions may coexist in one process.
|
|
249
|
-
const sessionEnv = (id) => ({ ...process.env, ...(id === undefined ? {} : { PI_SESSION_ID: id }) });
|
|
250
|
-
|
|
251
|
-
const persistSessionCancellation = (id) => {
|
|
252
|
-
if (id === undefined) return Promise.resolve();
|
|
253
|
-
const previous = pendingCancellations.get(id) ?? Promise.resolve();
|
|
254
|
-
let operation;
|
|
255
|
-
operation = previous.then(async () => {
|
|
256
|
-
const owner = harnessOwner({ channel: 'pi', session: id });
|
|
257
|
-
const env = sessionEnv(id);
|
|
258
|
-
const seats = await projectSessionSeats({ owner, env });
|
|
259
|
-
for (const { location, seat } of seats) {
|
|
260
|
-
if (seat.attachment !== 'attached') continue;
|
|
261
|
-
const square = await Square.at({ path: location, env });
|
|
262
|
-
try { await square.cancelDeliveries(seat.participant, { owner, generation: seat.generation }); }
|
|
263
|
-
finally { await square.close(); }
|
|
264
|
-
}
|
|
265
|
-
}).then(() => {
|
|
266
|
-
cancellationFailures.delete(id);
|
|
267
|
-
}, () => {
|
|
268
|
-
cancellationFailures.add(id);
|
|
269
|
-
}).finally(() => {
|
|
270
|
-
if (pendingCancellations.get(id) === operation) pendingCancellations.delete(id);
|
|
88
|
+
landings.set(content, landing);
|
|
89
|
+
if (signal.aborted) abort(); else signal.addEventListener('abort', abort, { once: true });
|
|
271
90
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const waitForSessionCancellation = async (id) => {
|
|
277
|
-
if (id === undefined) return true;
|
|
278
|
-
while (true) {
|
|
279
|
-
const pending = pendingCancellations.get(id);
|
|
280
|
-
if (pending !== undefined) await pending;
|
|
281
|
-
if (pendingCancellations.get(id) !== pending) continue;
|
|
282
|
-
return !cancellationFailures.has(id);
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
// Aborting releases native landing waits; retain cleanup until the observer
|
|
287
|
-
// has closed, including when cancellation precedes a lifecycle hook.
|
|
288
|
-
const stopWatcher = () => {
|
|
91
|
+
return { promise, landing };
|
|
92
|
+
}
|
|
93
|
+
function stopWatcher() {
|
|
289
94
|
watcherAbort?.abort();
|
|
290
95
|
watcherAbort = undefined;
|
|
291
96
|
return watcher;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
const observer = await observeSessionPending(harnessOwner({ channel: 'pi', session: boundSessionId }), env);
|
|
97
|
+
}
|
|
98
|
+
function cancelRun() {
|
|
99
|
+
if (cancelled) return;
|
|
100
|
+
cancelled = true;
|
|
101
|
+
const captured = session;
|
|
102
|
+
epoch += 1;
|
|
103
|
+
stopWatcher();
|
|
104
|
+
finishLandings(new Error('Pi notification delivery cancelled'));
|
|
105
|
+
if (captured) cancellation = cancellation.then(async () => {
|
|
106
|
+
if (!await captured.ledger.cancel({ owner: captured.owner, generation: captured.generation })) throw new Error('Pi cancellation fence lost');
|
|
107
|
+
cancellationFailed = false;
|
|
108
|
+
}).catch(() => { cancellationFailed = true; });
|
|
109
|
+
}
|
|
110
|
+
async function watch(captured, token, signal) {
|
|
111
|
+
const { ledger, owner, generation } = captured;
|
|
112
|
+
const observer = await ledger.observe(owner);
|
|
309
113
|
try {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
let resolveArmed;
|
|
314
|
-
const armed = new Promise((resolve) => { resolveArmed = resolve; });
|
|
315
|
-
const abort = () => controller.abort();
|
|
316
|
-
signal.addEventListener('abort', abort, { once: true });
|
|
317
|
-
const pending = observer.wait(Infinity, {
|
|
318
|
-
signal: controller.signal,
|
|
319
|
-
excludeKeys: handledPending,
|
|
320
|
-
skipImmediate: true,
|
|
321
|
-
onChangeArmed: resolveArmed,
|
|
322
|
-
}).catch(() => {
|
|
323
|
-
resolveArmed(false);
|
|
324
|
-
return [];
|
|
325
|
-
}).finally(() => signal.removeEventListener('abort', abort));
|
|
326
|
-
return { armed, pending, cancel: () => controller.abort() };
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
while (sessionId !== undefined && token === generation && !signal.aborted) {
|
|
330
|
-
const deferredRetry = retryAfterChange;
|
|
331
|
-
let pending;
|
|
114
|
+
while (!signal.aborted && token === epoch) {
|
|
115
|
+
const edge = observer.generation;
|
|
116
|
+
let claim;
|
|
332
117
|
try {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
await pause(signal, 1_000);
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
if (sessionId === undefined || token !== generation || signal.aborted) return;
|
|
341
|
-
if (pending.length === 0) {
|
|
342
|
-
if (deferredRetry) {
|
|
343
|
-
retryAfterChange = false;
|
|
344
|
-
retryWait = undefined;
|
|
118
|
+
claim = await ledger.claimPresentation(owner, { generation, budgetChars: BUDGET_CHARS - deliveryFrameOverhead({ tagged: true }), signal });
|
|
119
|
+
if (!claim) {
|
|
120
|
+
await observer.hinted(edge, 2_000, signal);
|
|
121
|
+
continue;
|
|
345
122
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
retryAfterChange = false;
|
|
349
|
-
retryWait = undefined;
|
|
350
|
-
|
|
351
|
-
const keys = inboxKeys(pending);
|
|
352
|
-
for (const key of keys) observedPending.add(key);
|
|
353
|
-
const deferred = armDeferredRetry();
|
|
354
|
-
const retryArmed = await deferred.armed;
|
|
355
|
-
if (!retryArmed) {
|
|
356
|
-
deferred.cancel();
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
let keepDeferred = false;
|
|
360
|
-
try {
|
|
361
|
-
await presentPendingAtBoundary(
|
|
362
|
-
harnessOwner({ channel: 'pi', session: boundSessionId }),
|
|
363
|
-
async (content, delivered) => {
|
|
364
|
-
if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
|
|
365
|
-
// One card for the human: a compact heading, expandable to the full body.
|
|
366
|
-
// Model context stays bounded; renderer-only details carry the complete text.
|
|
367
|
-
const messageContent = framePiMessage(content);
|
|
368
|
-
const landing = waitForLanding(messageContent, signal, 'steer');
|
|
369
|
-
try {
|
|
370
|
-
sendPiMessage(pi,
|
|
371
|
-
{ customType: 'square', content: messageContent, display: true, details: pendingCardDetails(delivered) },
|
|
372
|
-
{ deliverAs: 'steer', triggerTurn: true },
|
|
373
|
-
);
|
|
374
|
-
} catch (error) {
|
|
375
|
-
landing.ack.settle(error);
|
|
376
|
-
}
|
|
377
|
-
return landing.promise;
|
|
378
|
-
},
|
|
379
|
-
async (_id, _env, boundarySignal) => {
|
|
380
|
-
// Recheck committed revisions and current bindings before reusing observed content.
|
|
381
|
-
const inbox = await observer.read({ signal: boundarySignal, excludeKeys: handledPending });
|
|
382
|
-
if (signal.aborted || token !== generation) return [];
|
|
383
|
-
for (const key of inboxKeys(inbox)) observedPending.add(key);
|
|
384
|
-
pending = inbox;
|
|
385
|
-
return inbox;
|
|
386
|
-
},
|
|
387
|
-
env,
|
|
388
|
-
signal,
|
|
389
|
-
);
|
|
390
|
-
if (signal.aborted || token !== generation) return;
|
|
391
|
-
// Only committed evidence suppresses successful delivery. A competing wake may
|
|
392
|
-
// have won the claim and can still fail before sending, making this item eligible again.
|
|
393
|
-
} catch (error) {
|
|
394
|
-
if (signal.aborted || token !== generation) return;
|
|
395
|
-
if (error instanceof PiDeliveryDroppedError) {
|
|
396
|
-
deferred.cancel();
|
|
123
|
+
if (signal.aborted || token !== epoch || !await claim.current(signal) || signal.aborted || token !== epoch) {
|
|
124
|
+
await claim.settle('released');
|
|
397
125
|
continue;
|
|
398
126
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
127
|
+
const items = claim.cards.map(displayCard);
|
|
128
|
+
if (signal.aborted || token !== epoch || !await claim.current(signal) || signal.aborted || token !== epoch) {
|
|
129
|
+
await claim.settle('released');
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const content = renderDeliveryReminder(claim.cards.map(card => card.text), { tagged: true });
|
|
133
|
+
const { promise, landing } = waitForLanding(content, signal);
|
|
134
|
+
try {
|
|
135
|
+
sendPiMessage(pi, { customType: 'square', content, display: true, details: { items } }, { deliverAs: 'steer', triggerTurn: true });
|
|
136
|
+
} catch (error) { landing.finish(error); }
|
|
137
|
+
await promise;
|
|
138
|
+
await claim.settle('presented');
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (claim) await claim.settle(error?.dropped ? 'failed' : 'unknown').catch(() => undefined);
|
|
141
|
+
if (signal.aborted || token !== epoch) return;
|
|
142
|
+
// Avoid re-injecting a failed batch until another ledger edge or reconciliation.
|
|
143
|
+
await observer.hinted(observer.generation, 2_000, signal).catch(() => undefined);
|
|
405
144
|
}
|
|
406
145
|
}
|
|
407
146
|
} finally { observer.close(); }
|
|
408
|
-
}
|
|
147
|
+
}
|
|
148
|
+
function startWatcher(captured, token) {
|
|
149
|
+
watcherAbort = new AbortController();
|
|
150
|
+
watcher = watch(captured, token, watcherAbort.signal).catch(() => undefined);
|
|
151
|
+
}
|
|
409
152
|
|
|
410
153
|
pi.on('session_start', async (_event, ctx) => {
|
|
411
154
|
detachRunAbort?.();
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
155
|
+
detachRunAbort = undefined;
|
|
156
|
+
cancelled = false;
|
|
157
|
+
const token = ++epoch;
|
|
158
|
+
finishLandings(new Error('Pi session replaced'));
|
|
415
159
|
const stopped = stopWatcher();
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
retryAfterChange = false;
|
|
419
|
-
retryWait = undefined;
|
|
420
|
-
turnIndex = 0;
|
|
421
|
-
activeTurn = undefined;
|
|
422
|
-
currentRunSignal = undefined;
|
|
423
|
-
sessionId = ctx.sessionManager.getSessionId();
|
|
424
|
-
const token = generation;
|
|
160
|
+
const previous = session;
|
|
161
|
+
session = undefined;
|
|
425
162
|
await stopped;
|
|
426
|
-
await
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (token !==
|
|
432
|
-
|
|
163
|
+
await cancellation;
|
|
164
|
+
if (previous) {
|
|
165
|
+
await detachDeliverySession(previous.owner, previous.generation).catch(() => undefined);
|
|
166
|
+
await previous.ledger.close();
|
|
167
|
+
}
|
|
168
|
+
if (token !== epoch || cancellationFailed) return;
|
|
169
|
+
const owner = harnessOwner({ channel: 'pi', session: ctx.sessionManager.getSessionId() });
|
|
170
|
+
const attached = await attachDeliverySession(owner);
|
|
171
|
+
if (!attached) return;
|
|
172
|
+
if (token !== epoch) {
|
|
173
|
+
await detachDeliverySession(owner, attached.generation);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
let ledger;
|
|
177
|
+
try {
|
|
178
|
+
ledger = await DeliveryLedger.open();
|
|
179
|
+
if (token !== epoch) {
|
|
180
|
+
await detachDeliverySession(owner, attached.generation);
|
|
181
|
+
await ledger.close();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
session = { ledger, owner, generation: attached.generation };
|
|
185
|
+
turnIndex = 0;
|
|
186
|
+
activeTurn = undefined;
|
|
187
|
+
startWatcher(session, token);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
await detachDeliverySession(owner, attached.generation).catch(() => undefined);
|
|
190
|
+
await ledger?.close();
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
433
193
|
});
|
|
434
194
|
|
|
435
195
|
pi.on('agent_start', async (_event, ctx) => {
|
|
436
196
|
detachRunAbort?.();
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (runSignal.aborted) cancelRunDelivery();
|
|
442
|
-
else runSignal.addEventListener('abort', cancelRunDelivery, { once: true });
|
|
197
|
+
if (ctx?.mode !== 'tui' || !ctx.signal) return;
|
|
198
|
+
const signal = ctx.signal;
|
|
199
|
+
detachRunAbort = () => signal.removeEventListener('abort', cancelRun);
|
|
200
|
+
if (signal.aborted) cancelRun(); else signal.addEventListener('abort', cancelRun, { once: true });
|
|
443
201
|
});
|
|
444
|
-
|
|
445
202
|
pi.on('message_end', async (event) => {
|
|
446
|
-
|
|
447
|
-
|
|
203
|
+
const message = event.message;
|
|
204
|
+
if (activeTurn && (message?.role === 'user' || message?.role === 'custom')) activeTurn.hasInput = true;
|
|
205
|
+
if (message?.role === 'custom' && message.customType === 'square' && typeof message.content === 'string') {
|
|
206
|
+
landings.get(message.content)?.finish();
|
|
448
207
|
}
|
|
449
|
-
acknowledgeLanding(event.message);
|
|
450
208
|
});
|
|
451
|
-
|
|
452
|
-
pi.on('turn_start', async () => {
|
|
453
|
-
turnIndex += 1;
|
|
454
|
-
activeTurn = { index: turnIndex, hasInput: false, signal: currentRunSignal };
|
|
455
|
-
});
|
|
456
|
-
|
|
209
|
+
pi.on('turn_start', async () => { activeTurn = { index: ++turnIndex, hasInput: false }; });
|
|
457
210
|
pi.on('turn_end', async (event, ctx) => {
|
|
458
211
|
const turn = activeTurn;
|
|
459
212
|
activeTurn = undefined;
|
|
460
|
-
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
for (const ack of acks) {
|
|
467
|
-
if (ack.kind !== 'steer' || turn.index <= ack.sentAfterTurn) continue;
|
|
468
|
-
if (turn.hasInput) {
|
|
469
|
-
ack.emptyTurnWindows = 0;
|
|
470
|
-
} else {
|
|
471
|
-
ack.emptyTurnWindows += 1;
|
|
472
|
-
if (ack.emptyTurnWindows >= 2) ack.settle(new PiDeliveryDroppedError());
|
|
473
|
-
}
|
|
213
|
+
if (event.message?.stopReason === 'aborted' && ctx?.mode === 'tui') { cancelRun(); return; }
|
|
214
|
+
if (!turn) return;
|
|
215
|
+
for (const landing of [...landings.values()]) {
|
|
216
|
+
if (turn.index <= landing.sentAfterTurn) continue;
|
|
217
|
+
if (turn.hasInput) landing.emptyTurnWindows = 0;
|
|
218
|
+
else if (++landing.emptyTurnWindows >= 2) landing.finish(Object.assign(new Error('Pi discarded queued notification'), { dropped: true }));
|
|
474
219
|
}
|
|
475
220
|
});
|
|
476
|
-
|
|
477
221
|
pi.on('agent_settled', async () => {
|
|
478
222
|
detachRunAbort?.();
|
|
479
223
|
detachRunAbort = undefined;
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const token = generation;
|
|
224
|
+
if (!cancelled || !session) return;
|
|
225
|
+
const token = epoch;
|
|
483
226
|
await stopWatcher();
|
|
484
|
-
await
|
|
485
|
-
if (token !==
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
watcher = wake(token, watcherAbort.signal).catch(() => undefined);
|
|
227
|
+
await cancellation;
|
|
228
|
+
if (token !== epoch || !session || cancellationFailed) return;
|
|
229
|
+
cancelled = false;
|
|
230
|
+
startWatcher(session, token);
|
|
489
231
|
});
|
|
490
|
-
|
|
491
232
|
pi.on('session_shutdown', async () => {
|
|
492
233
|
detachRunAbort?.();
|
|
493
234
|
detachRunAbort = undefined;
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
failAcks(new Error('Pi session ended'));
|
|
235
|
+
++epoch;
|
|
236
|
+
finishLandings(new Error('Pi session ended'));
|
|
497
237
|
const stopped = stopWatcher();
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
sessionId = undefined;
|
|
501
|
-
retryWait = undefined;
|
|
502
|
-
activeTurn = undefined;
|
|
503
|
-
currentRunSignal = undefined;
|
|
238
|
+
const ended = session;
|
|
239
|
+
session = undefined;
|
|
504
240
|
await stopped;
|
|
505
|
-
await
|
|
506
|
-
if (
|
|
241
|
+
await cancellation;
|
|
242
|
+
if (ended) {
|
|
243
|
+
await detachDeliverySession(ended.owner, ended.generation).catch(() => undefined);
|
|
244
|
+
await ended.ledger.close();
|
|
245
|
+
}
|
|
507
246
|
});
|
|
508
247
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type StoreTransaction } from './artifact-store.js';
|
|
2
|
-
import type { ChannelArtifact, ChannelRead, ChannelWrite } from './channel-ports.js';
|
|
3
|
-
import type { WakeTransportPort, WakeProjection } from './channel-transport.js';
|
|
4
|
-
export declare function channelReads(channel: ChannelRead, guard: StoreTransaction['guard']): ChannelRead;
|
|
5
|
-
export declare function channelWrites(channel: ChannelWrite, guard: StoreTransaction['guard']): ChannelWrite;
|
|
6
|
-
/** Native Channel factories use only lower SQL and the shared physical-store lifetime. */
|
|
7
|
-
export declare function openChannelArtifact(location: string, signal?: AbortSignal): ChannelArtifact;
|
|
8
|
-
export declare function createMemoryChannelArtifact(description?: string): ChannelArtifact;
|
|
9
|
-
export declare function createChannelArtifactFile(location: string, description: string, force: boolean, signal?: AbortSignal): Promise<void>;
|
|
10
|
-
export declare function channelCourier(artifact: ChannelArtifact, transport: WakeTransportPort, location: string, clock: () => number, preparePresentation?: WakeProjection): (message?: number) => Promise<void>;
|