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