@astrosheep/square 0.3.93 → 0.3.94
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.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +2 -2
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/catch-decisions.d.ts +4 -6
- package/dist/catch-decisions.js +4 -4
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -25
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +26 -14
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +2 -2
- package/dist/cli/registry.js +6 -3
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +46 -54
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -11
- package/dist/decisions.js +9 -20
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +38 -38
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +1 -1
- package/dist/help.js +12 -10
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +8 -15
- package/dist/model.js +4 -7
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +6 -5
- package/dist/operation-presentation.d.ts +5 -4
- package/dist/operation-presentation.js +50 -45
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +13 -33
- package/dist/presentation-operations.js +4 -4
- package/dist/presentation.d.ts +5 -8
- package/dist/presentation.js +33 -43
- package/dist/runtime.d.ts +1 -7
- package/dist/runtime.js +0 -14
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -22
- package/dist/sqlite-square.js +138 -161
- package/dist/square-api.d.ts +15 -10
- package/dist/square-api.js +26 -22
- package/dist/square-core.d.ts +0 -5
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +56 -21
- package/dist/square-facade.d.ts +45 -87
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -70
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +2 -1
- package/dist/square-migration.js +52 -30
- package/dist/square-sqlite.d.ts +6 -2
- package/dist/square-sqlite.js +54 -28
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +22 -54
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +21 -10
- package/dist/views.js +38 -38
- package/dist/wakes.d.ts +1 -2
- package/dist/wakes.js +1 -1
- package/guides/application-facade-delivery-evidence.md +1 -1
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +142 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +33 -4
- package/guides/primitive-history-consolidation.md +1 -1
- package/guides/row-storage-design.md +8 -8
- package/guides/square-api.md +244 -88
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import type { PluginTheme } from "@getpaseo/plugin";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Square's own courtyard palette:
|
|
5
|
-
* deliberately no relative of notesoup's warm parchment or
|
|
6
|
-
* neutral carbon. Every token here is opaque and self-contained — the card and
|
|
4
|
+
* Square's own courtyard palette: Carrara marble (grey-white stone with a
|
|
5
|
+
* slate-blue vein), deliberately no relative of notesoup's warm parchment or
|
|
6
|
+
* pi-meantime's neutral carbon. Every token here is opaque and self-contained — the card and
|
|
7
7
|
* Markdown never borrow the host's surface ramp or accent, only its appearance
|
|
8
8
|
* (read from the host ink) and its accentForeground, which paints text on top
|
|
9
9
|
* of an accent fill.
|
|
10
10
|
*/
|
|
11
11
|
const DARK = {
|
|
12
|
-
surface0: "#
|
|
13
|
-
surface1: "#
|
|
14
|
-
surface2: "#
|
|
15
|
-
border: "#
|
|
16
|
-
foreground: "#
|
|
17
|
-
foregroundMuted: "#
|
|
18
|
-
accent: "#
|
|
12
|
+
surface0: "#14171b", // inset / code
|
|
13
|
+
surface1: "#1c2126", // card
|
|
14
|
+
surface2: "#14171b", // inset / code
|
|
15
|
+
border: "#2e353d",
|
|
16
|
+
foreground: "#e8edf3",
|
|
17
|
+
foregroundMuted: "#8b96a3",
|
|
18
|
+
accent: "#8ea8c3", // plaza ■ / links
|
|
19
19
|
} as const;
|
|
20
20
|
|
|
21
21
|
const LIGHT = {
|
|
22
|
-
surface0: "#
|
|
23
|
-
surface1: "#
|
|
24
|
-
surface2: "#
|
|
25
|
-
border: "#
|
|
26
|
-
foreground: "#
|
|
27
|
-
foregroundMuted: "#
|
|
28
|
-
accent: "#
|
|
22
|
+
surface0: "#e7ecf1",
|
|
23
|
+
surface1: "#f2f5f9", // cool marble white
|
|
24
|
+
surface2: "#e7ecf1",
|
|
25
|
+
border: "#d2dae3",
|
|
26
|
+
foreground: "#181d24",
|
|
27
|
+
foregroundMuted: "#67737f",
|
|
28
|
+
accent: "#4b6582",
|
|
29
29
|
} as const;
|
|
30
30
|
|
|
31
|
-
const VOICE = { dark: "#
|
|
31
|
+
const VOICE = { dark: "#8b96a3", light: "#67737f" } as const; // grey: only the ■ carries colour
|
|
32
32
|
|
|
33
33
|
export interface SquarePalette {
|
|
34
34
|
/** Complete theme for the card and its Markdown surfaces. */
|
|
@@ -28,48 +28,42 @@ export function SquareMessage({ item, theme, layout }: PluginTimelineItemProps<S
|
|
|
28
28
|
const speakers = [...new Set(names.filter((name): name is string => name !== undefined))];
|
|
29
29
|
const squareName = latest?.square ?? blocks[0].meta?.split("·")[1]?.trim() ?? "Square";
|
|
30
30
|
const bell = latest?.route === "bell" || single?.bell === true;
|
|
31
|
-
|
|
31
|
+
// Header is the same in both states; act id is always shown when we know it.
|
|
32
|
+
const actIds = [...new Set(blocks.flatMap(block => {
|
|
33
|
+
const id = parseSquareVoice(block.actor, block.meta).actId;
|
|
34
|
+
return id ? [id] : [];
|
|
35
|
+
}))];
|
|
36
|
+
const trailing = `· ${squareName}${actIds.length ? ` · ${actIds.join(", ")}` : ""}${items && items.length > 1 ? ` · ${items.length}` : ""}`;
|
|
32
37
|
const { accent, border, foreground, foregroundMuted: muted, surface1 } = palette.theme.colors;
|
|
33
38
|
const row = { flexDirection: "row" as const, alignItems: "center" as const, gap: 6 };
|
|
34
39
|
const indent = { paddingLeft: 15 };
|
|
35
40
|
|
|
36
41
|
return (
|
|
37
|
-
<View style={{ backgroundColor: surface1, borderWidth: StyleSheet.hairlineWidth, borderColor: border, borderRadius:
|
|
42
|
+
<View style={{ backgroundColor: surface1, borderWidth: StyleSheet.hairlineWidth, borderColor: border, borderRadius: 4, paddingHorizontal: 12, paddingVertical: 9 }}>
|
|
38
43
|
<Pressable
|
|
39
44
|
accessibilityRole="button"
|
|
40
45
|
accessibilityLabel={`${expanded ? "收起" : "展开"} ${squareName}: ${speakers.join(", ") || "Square"}`}
|
|
41
46
|
aria-expanded={expanded}
|
|
42
47
|
onPress={() => setExpanded(value => !value)}
|
|
43
|
-
style={({ pressed }) => ({
|
|
48
|
+
style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
|
|
44
49
|
>
|
|
45
|
-
<View style={
|
|
46
|
-
? { ...row, paddingBottom: 7, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: border }
|
|
47
|
-
: row}>
|
|
50
|
+
<View style={row}>
|
|
48
51
|
<Text style={{ color: accent, fontSize: 9 }}>■</Text>
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<>
|
|
56
|
-
<Text numberOfLines={1} style={{ color: foreground, fontSize: 13, fontWeight: "500", flex: 1 }}>
|
|
57
|
-
{speakers.join(", ") || "Square"}
|
|
58
|
-
{bell && <Text style={{ color: muted, fontWeight: "400" }}> · rang the bell</Text>}
|
|
59
|
-
</Text>
|
|
60
|
-
<Text style={{ color: muted, fontSize: 12 }}>{trailing}</Text>
|
|
61
|
-
<Icon name={expanded ? "ChevronUp" : "ChevronDown"} size={15} color={muted} />
|
|
62
|
-
</>
|
|
63
|
-
)}
|
|
52
|
+
<Text numberOfLines={1} style={{ color: foreground, fontSize: 13, fontWeight: "500", flex: 1 }}>
|
|
53
|
+
{speakers.join(", ") || "Square"}
|
|
54
|
+
{bell && <Text style={{ color: muted, fontWeight: "400" }}> · rang the bell</Text>}
|
|
55
|
+
</Text>
|
|
56
|
+
<Text style={{ color: muted, fontSize: 12 }}>{trailing}</Text>
|
|
57
|
+
<Icon name={expanded ? "ChevronUp" : "ChevronDown"} size={15} color={muted} />
|
|
64
58
|
</View>
|
|
65
59
|
{!expanded && preview !== undefined && preview !== "" && (
|
|
66
|
-
<View style={indent}>
|
|
60
|
+
<View style={{ ...indent, marginTop: 3 }}>
|
|
67
61
|
<SquareInline text={preview} theme={palette.theme} numberOfLines={2} />
|
|
68
62
|
</View>
|
|
69
63
|
)}
|
|
70
64
|
</Pressable>
|
|
71
65
|
{expanded && (
|
|
72
|
-
<ScrollView style={{ maxHeight: layout.compact ? 280 : 400 }} nestedScrollEnabled>
|
|
66
|
+
<ScrollView style={{ maxHeight: layout.compact ? 280 : 400, marginTop: 3 }} nestedScrollEnabled>
|
|
73
67
|
{multi ? (
|
|
74
68
|
<View style={{ gap: 10 }}>
|
|
75
69
|
{blocks.map((block, index) => {
|
package/plugins/pi/square-pi.js
CHANGED
|
@@ -2,12 +2,12 @@ import path from 'node:path';
|
|
|
2
2
|
import { Container, Markdown, Spacer, Text } from '@earendil-works/pi-tui';
|
|
3
3
|
import { z } from 'zod/v4';
|
|
4
4
|
|
|
5
|
-
import { Square } from '../../dist/square-api.js';
|
|
5
|
+
import { Square, harnessOwner } from '../../dist/square-api.js';
|
|
6
6
|
import { SQUARE_TOOLS, SQUARE_TOOL_INSTRUCTIONS, squareToolFailure, squareToolFailureText } from '../../dist/square-tools.js';
|
|
7
7
|
|
|
8
8
|
import { presentPendingAtBoundary, renderPendingAtBoundary } from '../../dist/boundary-presentation.js';
|
|
9
9
|
import { operationEnv } from '../../dist/participant-identity.js';
|
|
10
|
-
import {
|
|
10
|
+
import { detachSessionSeats, restoreSessionSeats } from '../../dist/session-lifecycle.js';
|
|
11
11
|
import { projectSessionSeats, observeSessionPending, pendingKey } from '../../dist/inbox.js';
|
|
12
12
|
import { previewAttentionBody, renderAttentionDescription, renderAttentionFull } from '../../dist/attention-presentation.js';
|
|
13
13
|
import { formatActivityId } from '../../dist/square-core.js';
|
|
@@ -147,7 +147,6 @@ export default function squarePiExtension(pi) {
|
|
|
147
147
|
label: `Square ${tool.name}`,
|
|
148
148
|
description: tool.description,
|
|
149
149
|
parameters: z.toJSONSchema(tool.inputSchema, { target: 'draft-7' }),
|
|
150
|
-
outputSchema: { type: 'object', additionalProperties: true },
|
|
151
150
|
exposure: 'codemode',
|
|
152
151
|
namespace: { name: 'square', description: 'A shared square where agents gather to talk, think, and act together.', instructions: SQUARE_TOOL_INSTRUCTIONS },
|
|
153
152
|
async execute(_id, args, signal, _onUpdate, ctx) {
|
|
@@ -156,17 +155,15 @@ export default function squarePiExtension(pi) {
|
|
|
156
155
|
const square = await Square.at({
|
|
157
156
|
cwd: ctx.cwd,
|
|
158
157
|
env: operationEnv('pi', sessionId, process.env),
|
|
159
|
-
body: { channel: 'pi', session: sessionId },
|
|
160
158
|
path: args.location,
|
|
161
|
-
participant: args.as,
|
|
162
159
|
}, { signal });
|
|
163
|
-
let
|
|
164
|
-
try {
|
|
160
|
+
let text;
|
|
161
|
+
try { text = await tool.run(square, args, { signal }, { owner: harnessOwner({ channel: 'pi', session: sessionId }) }); }
|
|
165
162
|
finally { await square.close(); }
|
|
166
|
-
return { content: [{ type: 'text', text
|
|
163
|
+
return { content: [{ type: 'text', text }] };
|
|
167
164
|
} catch (error) {
|
|
168
|
-
const
|
|
169
|
-
return { isError: true, content: [{ type: 'text', text: squareToolFailureText(
|
|
165
|
+
const failure = squareToolFailure(error, signal);
|
|
166
|
+
return { isError: true, content: [{ type: 'text', text: squareToolFailureText(failure, { location: args.location ?? process.env.SQUARE_LOCATION, participant: args.as }) }] };
|
|
170
167
|
}
|
|
171
168
|
},
|
|
172
169
|
});
|
|
@@ -256,12 +253,13 @@ export default function squarePiExtension(pi) {
|
|
|
256
253
|
const previous = pendingCancellations.get(id) ?? Promise.resolve();
|
|
257
254
|
let operation;
|
|
258
255
|
operation = previous.then(async () => {
|
|
259
|
-
const
|
|
256
|
+
const owner = harnessOwner({ channel: 'pi', session: id });
|
|
260
257
|
const env = sessionEnv(id);
|
|
261
|
-
const seats = await projectSessionSeats({
|
|
258
|
+
const seats = await projectSessionSeats({ owner, env });
|
|
262
259
|
for (const { location, seat } of seats) {
|
|
263
|
-
|
|
264
|
-
|
|
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 }); }
|
|
265
263
|
finally { await square.close(); }
|
|
266
264
|
}
|
|
267
265
|
}).then(() => {
|
|
@@ -307,7 +305,7 @@ export default function squarePiExtension(pi) {
|
|
|
307
305
|
const wake = async (token, signal) => {
|
|
308
306
|
const boundSessionId = sessionId;
|
|
309
307
|
const env = sessionEnv(boundSessionId);
|
|
310
|
-
const observer = await observeSessionPending({ channel: 'pi', session: boundSessionId }, env);
|
|
308
|
+
const observer = await observeSessionPending(harnessOwner({ channel: 'pi', session: boundSessionId }), env);
|
|
311
309
|
try {
|
|
312
310
|
if (signal.aborted || token !== generation) return;
|
|
313
311
|
const armDeferredRetry = () => {
|
|
@@ -361,7 +359,7 @@ export default function squarePiExtension(pi) {
|
|
|
361
359
|
let keepDeferred = false;
|
|
362
360
|
try {
|
|
363
361
|
await presentPendingAtBoundary(
|
|
364
|
-
{ channel: 'pi', session: boundSessionId },
|
|
362
|
+
harnessOwner({ channel: 'pi', session: boundSessionId }),
|
|
365
363
|
async (content) => {
|
|
366
364
|
if (signal.aborted || currentRunSignal?.aborted) throw new Error('Pi notification delivery cancelled');
|
|
367
365
|
// One card for the human: a compact heading, expandable to the full body.
|
|
@@ -428,6 +426,8 @@ export default function squarePiExtension(pi) {
|
|
|
428
426
|
if (token !== generation || cancellationFailures.has(sessionId)) return;
|
|
429
427
|
watcherAbort = new AbortController();
|
|
430
428
|
const signal = watcherAbort.signal;
|
|
429
|
+
await restoreSessionSeats(harnessOwner({ channel: 'pi', session: sessionId }), sessionEnv(sessionId), undefined, signal).catch(() => undefined);
|
|
430
|
+
if (token !== generation || signal.aborted) return;
|
|
431
431
|
watcher = wake(token, signal).catch(() => undefined);
|
|
432
432
|
});
|
|
433
433
|
|
|
@@ -502,6 +502,6 @@ export default function squarePiExtension(pi) {
|
|
|
502
502
|
currentRunSignal = undefined;
|
|
503
503
|
await stopped;
|
|
504
504
|
await waitForSessionCancellation(endedId);
|
|
505
|
-
if (endedId) await
|
|
505
|
+
if (endedId) await detachSessionSeats('pi', endedId, endedEnv).catch(() => undefined);
|
|
506
506
|
});
|
|
507
507
|
}
|
package/dist/square-backend.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { SqliteSquare } from './sqlite-square.js';
|
|
2
|
-
/** Structural resource contract extracted from the existing implementation.
|
|
3
|
-
* Backends implement the actual resource, not a second operation/policy dispatcher.
|
|
4
|
-
* Host-internal methods may explicitly refuse where their capabilities do not exist. */
|
|
5
|
-
export interface SquareBackend extends Pick<SqliteSquare, keyof SqliteSquare> {
|
|
6
|
-
}
|
|
7
|
-
export interface StdioBackendConfiguration {
|
|
8
|
-
readonly command: string;
|
|
9
|
-
readonly args?: readonly string[];
|
|
10
|
-
readonly cwd?: string;
|
|
11
|
-
readonly env?: Readonly<Record<string, string>>;
|
|
12
|
-
}
|
|
13
|
-
export type SquareBackends = Readonly<Record<string, StdioBackendConfiguration>>;
|
package/dist/square-backend.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/square-stdio.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type SquareErrorCode, type SquareErrorFacts } from './model.js';
|
|
2
|
-
import type { SquareBackends, SquareBackend } from './square-backend.js';
|
|
3
|
-
import type { ParsedSquareLocation } from './square-location.js';
|
|
4
|
-
import type { OperationControl, OperationFailureDetail, SquareAtInput, SquareBuildInput } from './square-facade.js';
|
|
5
|
-
export declare const STDIO_PROTOCOL_VERSION = 2;
|
|
6
|
-
export declare const STDIO_MAX_FRAME_BYTES: number;
|
|
7
|
-
export declare const identityVariables: (import("./participant-identity.js").HarnessSessionVariable | "CLAUDE_CODE_CHILD_SESSION")[];
|
|
8
|
-
export declare function wireIdentity(env: NodeJS.ProcessEnv): Record<string, string>;
|
|
9
|
-
export interface WireError {
|
|
10
|
-
readonly code: number;
|
|
11
|
-
readonly message: string;
|
|
12
|
-
readonly data?: {
|
|
13
|
-
readonly code?: SquareErrorCode;
|
|
14
|
-
readonly facts?: SquareErrorFacts;
|
|
15
|
-
readonly operation?: OperationFailureDetail;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export declare function encodeError(error: unknown): WireError;
|
|
19
|
-
export declare function unsupported(feature: string): never;
|
|
20
|
-
export declare function openStdioSquare(location: string, selected: Extract<ParsedSquareLocation, {
|
|
21
|
-
kind: 'stdio';
|
|
22
|
-
}>, input: SquareAtInput, backends: SquareBackends, build?: Pick<SquareBuildInput, 'markdown' | 'hardCap' | 'throttlePerMinute' | 'template' | 'force'>, control?: OperationControl): Promise<SquareBackend>;
|
package/dist/square-stdio.js
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { validateMetadataMatch, validateDeferralDuration, validateDeferralId } from './delivery-deferral.js';
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { createInterface } from 'node:readline';
|
|
6
|
-
import { SquareError } from './model.js';
|
|
7
|
-
import { harnessSessionSources } from './participant-identity.js';
|
|
8
|
-
import { requireJoinName } from './seats.js';
|
|
9
|
-
export const STDIO_PROTOCOL_VERSION = 2;
|
|
10
|
-
export const STDIO_MAX_FRAME_BYTES = 8 * 1024 * 1024;
|
|
11
|
-
export const identityVariables = [...harnessSessionSources.flatMap(source => [source.variable, ...(source.childVariable ? [source.childVariable] : [])])];
|
|
12
|
-
export function wireIdentity(env) {
|
|
13
|
-
return Object.fromEntries(identityVariables.map(key => [key, env[key] ?? '']));
|
|
14
|
-
}
|
|
15
|
-
export function encodeError(error) {
|
|
16
|
-
const e = error;
|
|
17
|
-
return { code: -32000, message: e.message ?? 'Backend request failed', data: { code: e.code, facts: e.facts, operation: e.operation } };
|
|
18
|
-
}
|
|
19
|
-
function decodeError(error) {
|
|
20
|
-
return Object.assign(error.data?.code ? new SquareError(error.data.code, error.message, error.data.facts) : new Error(error.message), error.data?.operation ? { operation: error.data.operation } : {});
|
|
21
|
-
}
|
|
22
|
-
export function unsupported(feature) {
|
|
23
|
-
throw new SquareError('invalid_args', `The stdio backend does not support ${feature}; use explicit catch/history or a local resource`);
|
|
24
|
-
}
|
|
25
|
-
/** One process per opened Square. No replay, pooling, or operation policy. */
|
|
26
|
-
class Connection {
|
|
27
|
-
nextId = 0;
|
|
28
|
-
closed = false;
|
|
29
|
-
closing;
|
|
30
|
-
pending = new Map();
|
|
31
|
-
child;
|
|
32
|
-
exited;
|
|
33
|
-
constructor(command, args, cwd, env) {
|
|
34
|
-
this.child = spawn(command, [...args], { cwd, env, shell: false, stdio: 'pipe' });
|
|
35
|
-
this.exited = new Promise(resolve => this.child.once('close', () => resolve()));
|
|
36
|
-
// Diagnostic bytes are deliberately not included in errors (they can contain credentials).
|
|
37
|
-
this.child.stderr.resume();
|
|
38
|
-
const lines = createInterface({ input: this.child.stdout, crlfDelay: Infinity });
|
|
39
|
-
let frameBytes = 0;
|
|
40
|
-
this.child.stdout.on('data', chunk => {
|
|
41
|
-
for (const byte of chunk) {
|
|
42
|
-
if (byte === 10)
|
|
43
|
-
frameBytes = 0;
|
|
44
|
-
else if (++frameBytes > STDIO_MAX_FRAME_BYTES) {
|
|
45
|
-
this.fail('Backend frame exceeds the protocol limit');
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
lines.on('line', line => {
|
|
51
|
-
try {
|
|
52
|
-
const message = JSON.parse(line);
|
|
53
|
-
if (message.jsonrpc !== '2.0')
|
|
54
|
-
throw new Error();
|
|
55
|
-
if (message.method === 'square/progress') {
|
|
56
|
-
const pending = this.pending.get(message.params?.id);
|
|
57
|
-
if (pending && message.params?.progress) {
|
|
58
|
-
try {
|
|
59
|
-
pending.control?.onProgress?.(message.params.progress);
|
|
60
|
-
}
|
|
61
|
-
catch { /* progress observers cannot corrupt framing */ }
|
|
62
|
-
}
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (!Number.isSafeInteger(message.id) || (Object.hasOwn(message, 'result') === Object.hasOwn(message, 'error')))
|
|
66
|
-
throw new Error();
|
|
67
|
-
const pending = this.pending.get(message.id);
|
|
68
|
-
if (!pending)
|
|
69
|
-
throw new Error();
|
|
70
|
-
this.pending.delete(message.id);
|
|
71
|
-
pending.dispose();
|
|
72
|
-
if (message.error)
|
|
73
|
-
pending.reject(decodeError(message.error));
|
|
74
|
-
else
|
|
75
|
-
pending.resolve(message.result);
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
this.fail('Malformed backend protocol response');
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
lines.once('close', () => this.fail('Backend stdout ended'));
|
|
82
|
-
this.child.once('error', () => this.fail('Cannot start configured backend; check command and cwd'));
|
|
83
|
-
this.child.once('exit', () => this.fail('Backend process exited'));
|
|
84
|
-
this.child.stdin.on('error', () => this.fail('Backend stdin closed'));
|
|
85
|
-
}
|
|
86
|
-
fail(message) {
|
|
87
|
-
if (this.closed)
|
|
88
|
-
return;
|
|
89
|
-
this.closed = true;
|
|
90
|
-
for (const pending of this.pending.values()) {
|
|
91
|
-
pending.dispose();
|
|
92
|
-
const error = new SquareError('unavailable', message);
|
|
93
|
-
if (pending.method === 'express')
|
|
94
|
-
Object.assign(error, { operation: { operation: 'express', expressEffect: 'unknown' } });
|
|
95
|
-
pending.reject(error);
|
|
96
|
-
}
|
|
97
|
-
this.pending.clear();
|
|
98
|
-
this.child.stdin.destroy();
|
|
99
|
-
this.child.kill();
|
|
100
|
-
const timer = setTimeout(() => this.child.kill('SIGKILL'), 500);
|
|
101
|
-
timer.unref();
|
|
102
|
-
void this.exited.then(() => clearTimeout(timer));
|
|
103
|
-
}
|
|
104
|
-
request(method, params, control) {
|
|
105
|
-
if (control?.signal?.aborted)
|
|
106
|
-
return Promise.reject(control.signal.reason ?? new Error('Operation cancelled'));
|
|
107
|
-
if (this.closed)
|
|
108
|
-
return Promise.reject(new SquareError('unavailable', 'Backend connection is closed'));
|
|
109
|
-
const id = ++this.nextId;
|
|
110
|
-
let frame;
|
|
111
|
-
try {
|
|
112
|
-
frame = JSON.stringify({ jsonrpc: '2.0', id, method, params }, (_key, value) => {
|
|
113
|
-
if (typeof value === 'number' && !Number.isFinite(value))
|
|
114
|
-
throw new Error('Non-finite number');
|
|
115
|
-
return value;
|
|
116
|
-
}) + '\n';
|
|
117
|
-
if (Buffer.byteLength(frame) > STDIO_MAX_FRAME_BYTES)
|
|
118
|
-
throw new Error('Frame too large');
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
const error = new SquareError('invalid_args', 'Backend request must be JSON-serializable with finite numbers and fit the protocol frame limit');
|
|
122
|
-
if (method === 'express')
|
|
123
|
-
Object.assign(error, { operation: { operation: 'express', expressEffect: 'unsent' } });
|
|
124
|
-
return Promise.reject(error);
|
|
125
|
-
}
|
|
126
|
-
return new Promise((resolve, reject) => {
|
|
127
|
-
const abort = () => {
|
|
128
|
-
// Wait for the response: server cleanup (including catch lease release) must finish first.
|
|
129
|
-
if (!this.closed)
|
|
130
|
-
this.child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: '$/cancelRequest', params: { id } }) + '\n');
|
|
131
|
-
};
|
|
132
|
-
this.pending.set(id, { resolve, reject, method, control, dispose: () => control?.signal?.removeEventListener('abort', abort) });
|
|
133
|
-
control?.signal?.addEventListener('abort', abort, { once: true });
|
|
134
|
-
this.child.stdin.write(frame);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
close() {
|
|
138
|
-
return this.closing ??= (async () => {
|
|
139
|
-
const timer = setTimeout(() => this.fail('Backend close timed out'), 1000);
|
|
140
|
-
try {
|
|
141
|
-
if (!this.closed)
|
|
142
|
-
await this.request('square/close', {});
|
|
143
|
-
}
|
|
144
|
-
catch { /* disconnect already rejects outstanding work */ }
|
|
145
|
-
finally {
|
|
146
|
-
clearTimeout(timer);
|
|
147
|
-
this.fail('Backend connection closed');
|
|
148
|
-
await this.exited;
|
|
149
|
-
}
|
|
150
|
-
})();
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
class StdioSquare {
|
|
154
|
-
location;
|
|
155
|
-
connection;
|
|
156
|
-
handle;
|
|
157
|
-
constructor(location, connection, handle) {
|
|
158
|
-
this.location = location;
|
|
159
|
-
this.connection = connection;
|
|
160
|
-
this.handle = handle;
|
|
161
|
-
}
|
|
162
|
-
call(method, args, control, participant, participantHandle) {
|
|
163
|
-
return this.connection.request(method, { handle: this.handle, args, ...(participant === undefined ? {} : { participant, participantHandle }) }, control);
|
|
164
|
-
}
|
|
165
|
-
participantHandle(name, reference) {
|
|
166
|
-
const call = (method, args, control) => this.call(method, args, control, name, reference);
|
|
167
|
-
return {
|
|
168
|
-
name,
|
|
169
|
-
express: ((bodyOrInput, optionsOrControl, control) => {
|
|
170
|
-
if (typeof bodyOrInput === 'object' && bodyOrInput !== null)
|
|
171
|
-
return call('express', [bodyOrInput, undefined], optionsOrControl);
|
|
172
|
-
return call('express', [bodyOrInput, optionsOrControl], control);
|
|
173
|
-
}),
|
|
174
|
-
catch: (options, control) => call('catch', [options], control),
|
|
175
|
-
listen: (target, options, control) => call('listen', [target, options], control),
|
|
176
|
-
ignore: (target, options, control) => call('ignore', [target, options], control),
|
|
177
|
-
unlisten: (target, options, control) => call('unlisten', [target, options], control),
|
|
178
|
-
listening: (options, control) => call('listening', [options], control),
|
|
179
|
-
hold: (reason, options, control) => call('hold', [reason, options], control),
|
|
180
|
-
resume: (options, control) => call('resume', [options], control),
|
|
181
|
-
done: (body, options, control) => call('done', [body, options], control),
|
|
182
|
-
deferDelivery: async (input, control) => {
|
|
183
|
-
const match = validateMetadataMatch(input?.match), durationMs = validateDeferralDuration(input?.durationMs);
|
|
184
|
-
return call('deferDelivery', [{ match, durationMs }], control);
|
|
185
|
-
},
|
|
186
|
-
renewDeliveryDeferral: async (id, durationMs, control) => call('renewDeliveryDeferral', [validateDeferralId(id), validateDeferralDuration(durationMs)], control),
|
|
187
|
-
releaseDeliveryDeferral: async (id, control) => call('releaseDeliveryDeferral', [validateDeferralId(id)], control),
|
|
188
|
-
history: (query, control) => this.history(query, control),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
async participant(name) {
|
|
192
|
-
const reference = randomUUID();
|
|
193
|
-
return this.participantHandle(await this.call('participant', [name, reference]), reference);
|
|
194
|
-
}
|
|
195
|
-
async join(...[name, options, control]) {
|
|
196
|
-
requireJoinName(name);
|
|
197
|
-
const reference = randomUUID();
|
|
198
|
-
const result = await this.call('join', [name, options, reference], control);
|
|
199
|
-
return { ...result, participant: this.participantHandle(result.participant, reference) };
|
|
200
|
-
}
|
|
201
|
-
history(...[query, control]) { return this.call('history', [query], control); }
|
|
202
|
-
status(...[options, control]) { return this.call('status', [options], control); }
|
|
203
|
-
participants(...[options, control]) { return this.call('participants', [options], control); }
|
|
204
|
-
recovery(...[name, control]) { return this.call('recovery', [name], control); }
|
|
205
|
-
snapshot() { return Promise.reject(new SquareError('invalid_args', 'stdio does not support function-valued snapshot; use status/participants/history')); }
|
|
206
|
-
stream() { unsupported('stream'); }
|
|
207
|
-
recognize() { return Promise.reject(new SquareError('invalid_args', 'stdio does not support host recognition; select a participant explicitly')); }
|
|
208
|
-
implicitJoin() { unsupported('host implicit join'); }
|
|
209
|
-
endOwnedSession() { unsupported('host session cleanup'); }
|
|
210
|
-
cancelDeliveries() { unsupported('host delivery cancellation'); }
|
|
211
|
-
close() { return this.connection.close(); }
|
|
212
|
-
}
|
|
213
|
-
export async function openStdioSquare(location, selected, input, backends, build, control) {
|
|
214
|
-
if (input.clock || input.wakeTransport)
|
|
215
|
-
unsupported('client clock or wakeTransport injection');
|
|
216
|
-
if (control?.signal?.aborted)
|
|
217
|
-
throw control.signal.reason ?? new Error('Operation cancelled');
|
|
218
|
-
const backend = Object.hasOwn(backends, selected.backend) ? backends[selected.backend] : undefined;
|
|
219
|
-
if (!backend)
|
|
220
|
-
throw new SquareError('invalid_args', `Backend ${selected.backend} is not configured; set backends or SQUARE_BACKENDS_CONFIG`);
|
|
221
|
-
const cwd = input.cwd ?? process.cwd();
|
|
222
|
-
const command = backend.command.includes('/') || backend.command.includes('\\') ? path.resolve(cwd, backend.command) : backend.command;
|
|
223
|
-
const connection = new Connection(command, backend.args ?? [], path.resolve(cwd, backend.cwd ?? '.'), { ...(input.env ?? process.env), ...backend.env });
|
|
224
|
-
try {
|
|
225
|
-
const result = await connection.request('square/open', { version: STDIO_PROTOCOL_VERSION, key: selected.key, location,
|
|
226
|
-
identity: wireIdentity(input.env ?? process.env), body: input.body, participant: input.participant, build }, control);
|
|
227
|
-
if (result.version !== STDIO_PROTOCOL_VERSION || typeof result.handle !== 'string')
|
|
228
|
-
throw new SquareError('unavailable', 'Backend protocol version mismatch');
|
|
229
|
-
return new StdioSquare(location, connection, result.handle);
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
await connection.close();
|
|
233
|
-
throw error;
|
|
234
|
-
}
|
|
235
|
-
}
|
package/dist/stdio-server.d.ts
DELETED