@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.
Files changed (116) hide show
  1. package/dist/activity-feed.d.ts +3 -5
  2. package/dist/activity-feed.js +16 -14
  3. package/dist/artifact.js +2 -2
  4. package/dist/boundary-presentation.d.ts +2 -2
  5. package/dist/catch-decisions.d.ts +4 -6
  6. package/dist/catch-decisions.js +4 -4
  7. package/dist/claude-delivery-status.js +10 -4
  8. package/dist/claude-delivery.js +19 -11
  9. package/dist/claude-mod.js +24 -53
  10. package/dist/cli/context.d.ts +9 -4
  11. package/dist/cli/context.js +30 -25
  12. package/dist/cli/express-presentation.d.ts +3 -1
  13. package/dist/cli/express-presentation.js +26 -14
  14. package/dist/cli/json.d.ts +3 -0
  15. package/dist/cli/json.js +14 -0
  16. package/dist/cli/mcp-server-command.js +2 -2
  17. package/dist/cli/observation-commands.d.ts +1 -3
  18. package/dist/cli/observation-commands.js +43 -56
  19. package/dist/cli/program.js +2 -2
  20. package/dist/cli/registry.js +6 -3
  21. package/dist/cli/square-commands.d.ts +1 -1
  22. package/dist/cli/square-commands.js +46 -54
  23. package/dist/codex-hook.d.ts +3 -2
  24. package/dist/codex-hook.js +13 -4
  25. package/dist/codex-queue.js +4 -4
  26. package/dist/decisions.d.ts +5 -11
  27. package/dist/decisions.js +9 -20
  28. package/dist/delivery-deferral.js +2 -2
  29. package/dist/delivery-health.d.ts +0 -1
  30. package/dist/delivery-health.js +1 -3
  31. package/dist/delivery-operations.d.ts +2 -2
  32. package/dist/delivery-operations.js +38 -38
  33. package/dist/delivery.d.ts +1 -1
  34. package/dist/delivery.js +1 -1
  35. package/dist/help.js +12 -10
  36. package/dist/inbox.d.ts +9 -22
  37. package/dist/inbox.js +23 -27
  38. package/dist/index.d.ts +1 -3
  39. package/dist/index.js +1 -1
  40. package/dist/mcp.d.ts +3 -0
  41. package/dist/mcp.js +23 -24
  42. package/dist/model.d.ts +8 -15
  43. package/dist/model.js +4 -7
  44. package/dist/notifications.d.ts +1 -1
  45. package/dist/notifications.js +6 -5
  46. package/dist/operation-presentation.d.ts +5 -4
  47. package/dist/operation-presentation.js +50 -45
  48. package/dist/owner.d.ts +11 -0
  49. package/dist/owner.js +32 -0
  50. package/dist/paseo-delivery.d.ts +4 -1
  51. package/dist/paseo-delivery.js +7 -12
  52. package/dist/paseo-state.d.ts +2 -2
  53. package/dist/paseo-state.js +6 -4
  54. package/dist/plugins/opencode/delivery.js +27 -15
  55. package/dist/ports.d.ts +13 -33
  56. package/dist/presentation-operations.js +4 -4
  57. package/dist/presentation.d.ts +5 -8
  58. package/dist/presentation.js +33 -43
  59. package/dist/runtime.d.ts +1 -7
  60. package/dist/runtime.js +0 -14
  61. package/dist/seats.d.ts +2 -3
  62. package/dist/seats.js +7 -15
  63. package/dist/session-lifecycle.d.ts +6 -2
  64. package/dist/session-lifecycle.js +39 -11
  65. package/dist/sqlite-square.d.ts +36 -22
  66. package/dist/sqlite-square.js +138 -161
  67. package/dist/square-api.d.ts +15 -10
  68. package/dist/square-api.js +26 -22
  69. package/dist/square-core.d.ts +0 -5
  70. package/dist/square-core.js +0 -9
  71. package/dist/square-domain.d.ts +4 -2
  72. package/dist/square-domain.js +56 -21
  73. package/dist/square-facade.d.ts +45 -87
  74. package/dist/square-index.d.ts +5 -13
  75. package/dist/square-index.js +13 -38
  76. package/dist/square-location.d.ts +1 -14
  77. package/dist/square-location.js +4 -70
  78. package/dist/square-migration-host-ledger.d.ts +2 -2
  79. package/dist/square-migration-host-ledger.js +6 -5
  80. package/dist/square-migration.d.ts +2 -1
  81. package/dist/square-migration.js +52 -30
  82. package/dist/square-sqlite.d.ts +6 -2
  83. package/dist/square-sqlite.js +54 -28
  84. package/dist/square-tools.d.ts +7 -10
  85. package/dist/square-tools.js +22 -54
  86. package/dist/stream.js +4 -5
  87. package/dist/views.d.ts +21 -10
  88. package/dist/views.js +38 -38
  89. package/dist/wakes.d.ts +1 -2
  90. package/dist/wakes.js +1 -1
  91. package/guides/application-facade-delivery-evidence.md +1 -1
  92. package/guides/claude-native.md +18 -9
  93. package/guides/codex-delivery.md +17 -6
  94. package/guides/mcp-connection-lifetime.md +1 -1
  95. package/guides/mcp.md +3 -3
  96. package/guides/migration.md +142 -151
  97. package/guides/operation-cancellation.md +1 -1
  98. package/guides/participant-ownership.md +78 -47
  99. package/guides/pi-tools.md +33 -4
  100. package/guides/primitive-history-consolidation.md +1 -1
  101. package/guides/row-storage-design.md +8 -8
  102. package/guides/square-api.md +244 -88
  103. package/package.json +1 -1
  104. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  105. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  106. package/plugins/opencode/delivery.ts +27 -17
  107. package/plugins/paseo/client/palette.ts +18 -18
  108. package/plugins/paseo/client/square-message.tsx +17 -23
  109. package/plugins/pi/square-pi.js +17 -17
  110. package/dist/square-backend.d.ts +0 -13
  111. package/dist/square-backend.js +0 -1
  112. package/dist/square-stdio.d.ts +0 -22
  113. package/dist/square-stdio.js +0 -235
  114. package/dist/stdio-server.d.ts +0 -3
  115. package/dist/stdio-server.js +0 -212
  116. 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: cool pewter slate and a cold blue voice,
5
- * deliberately no relative of notesoup's warm parchment or pi-meantime's
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: "#121519", // inset / code
13
- surface1: "#171b20", // card
14
- surface2: "#121519", // inset / code
15
- border: "#262d36",
16
- foreground: "#e6ebf2",
17
- foregroundMuted: "#7d8999",
18
- accent: "#60a5fa", // plaza ■ / links
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: "#e8edf3",
23
- surface1: "#f3f6fa", // pewter slate, not notesoup's warm parchment
24
- surface2: "#e8edf3",
25
- border: "#d5dee8",
26
- foreground: "#141b24",
27
- foregroundMuted: "#64748b",
28
- accent: "#2563eb",
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: "#93c5fd", light: "#3b82f6" } as const;
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
- const trailing = `· ${squareName}${expanded && single?.actId ? ` · ${single.actId}` : !expanded && items && items.length > 1 ? ` · ${items.length}` : ""}`;
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: 8, paddingHorizontal: 12, paddingVertical: 9, gap: expanded ? 8 : 3 }}>
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 }) => ({ gap: 3, opacity: pressed ? 0.7 : 1 })}
48
+ style={({ pressed }) => ({ opacity: pressed ? 0.7 : 1 })}
44
49
  >
45
- <View style={expanded && multi
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
- {expanded && multi ? (
50
- <>
51
- <Text numberOfLines={1} style={{ color: foreground, fontSize: 12.5, fontWeight: "500", flex: 1 }}>{squareName}</Text>
52
- <Icon name="ChevronUp" size={15} color={muted} />
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) => {
@@ -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 { endSessionMemberships } from '../../dist/session-lifecycle.js';
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 data;
164
- try { data = await tool.run(square, args, { signal, observe: true }); }
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: tool.render(data, args) ?? '' }], details: data, structuredContent: data };
163
+ return { content: [{ type: 'text', text }] };
167
164
  } catch (error) {
168
- const data = { error: squareToolFailure(error, signal) };
169
- return { isError: true, content: [{ type: 'text', text: squareToolFailureText(data.error, { location: args.location ?? process.env.SQUARE_LOCATION, participant: args.as }) }], details: data, structuredContent: data };
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 body = { channel: 'pi', session: id };
256
+ const owner = harnessOwner({ channel: 'pi', session: id });
260
257
  const env = sessionEnv(id);
261
- const seats = await projectSessionSeats({ body, env });
258
+ const seats = await projectSessionSeats({ owner, env });
262
259
  for (const { location, seat } of seats) {
263
- const square = await Square.at({ path: location, env, body, participant: seat.participant });
264
- try { await square.cancelDeliveries(seat.participant, seat.generation); }
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 endSessionMemberships('pi', endedId, endedEnv).catch(() => undefined);
505
+ if (endedId) await detachSessionSeats('pi', endedId, endedEnv).catch(() => undefined);
506
506
  });
507
507
  }
@@ -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>>;
@@ -1 +0,0 @@
1
- export {};
@@ -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>;
@@ -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
- }
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- /** Serve the existing resource API. This adapter contains no activity policy. */
3
- export declare function runSquareStdioServer(root: string): Promise<void>;