@astrosheep/square 0.3.73 → 0.3.75

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 (49) hide show
  1. package/dist/attention-presentation.d.ts +2 -0
  2. package/dist/attention-presentation.js +11 -2
  3. package/dist/boundary-presentation.d.ts +8 -1
  4. package/dist/boundary-presentation.js +8 -1
  5. package/dist/cli/context.d.ts +1 -1
  6. package/dist/cli/express-presentation.js +1 -2
  7. package/dist/cli/ledger-command.js +6 -2
  8. package/dist/cli/meta-commands.js +1 -1
  9. package/dist/decisions.js +6 -21
  10. package/dist/help.js +4 -4
  11. package/dist/host-ledger-migration.d.ts +1 -0
  12. package/dist/host-ledger-migration.js +62 -21
  13. package/dist/host-ledger-sqlite.d.ts +4 -1
  14. package/dist/host-ledger-sqlite.js +36 -9
  15. package/dist/inbox.d.ts +2 -0
  16. package/dist/inbox.js +64 -22
  17. package/dist/mcp.d.ts +2 -1
  18. package/dist/mcp.js +51 -26
  19. package/dist/model.d.ts +2 -3
  20. package/dist/model.js +5 -2
  21. package/dist/open-square.d.ts +12 -2
  22. package/dist/open-square.js +42 -4
  23. package/dist/participant-host.js +6 -2
  24. package/dist/ports.d.ts +10 -0
  25. package/dist/presentation.js +1 -1
  26. package/dist/square-actions.d.ts +3 -1
  27. package/dist/square-actions.js +10 -5
  28. package/dist/square-application.d.ts +4 -0
  29. package/dist/square-application.js +42 -28
  30. package/dist/square-facade.d.ts +26 -22
  31. package/dist/views.d.ts +3 -1
  32. package/dist/views.js +37 -29
  33. package/guides/application-demand-optimization.md +50 -0
  34. package/guides/catch-cold-path-optimization.md +56 -0
  35. package/guides/catch-lock-investigation.md +81 -0
  36. package/guides/host-ledger.md +2 -2
  37. package/guides/mcp-connection-lifetime.md +22 -0
  38. package/guides/pi-boundary-pending-reuse.md +13 -0
  39. package/package.json +2 -1
  40. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  41. package/plugins/claude/skills/square/SKILL.md +1 -1
  42. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  43. package/plugins/paseo/README.md +10 -5
  44. package/plugins/paseo/client/markdown.tsx +221 -0
  45. package/plugins/paseo/client/square-message.tsx +64 -94
  46. package/plugins/paseo/client/transform.ts +4 -1
  47. package/plugins/paseo/shared/message.ts +6 -4
  48. package/plugins/pi/square-pi.js +26 -16
  49. package/skills/square/SKILL.md +1 -1
package/dist/mcp.js CHANGED
@@ -4,7 +4,7 @@ import { z } from 'zod/v4';
4
4
  import { SquareError, isSquareError } from './model.js';
5
5
  import { operationEnv } from './automatic-session.js';
6
6
  import { SQUARE_IDENTITY } from './identity.js';
7
- import { createSquareApplication } from './square-application.js';
7
+ import { createSquareApplication, createSquareResourceOwner } from './square-application.js';
8
8
  const activityId = z.string().regex(/^act\/(0|[1-9][0-9]*)$/);
9
9
  const stringArray = z.array(z.string());
10
10
  const participantName = z.string().optional().describe("Participant name, same as --as. Defaults to the server's configured participant.");
@@ -44,37 +44,62 @@ function operation(app, name, args, control) {
44
44
  }
45
45
  export function createSquareMcpServer(options) {
46
46
  const baseEnv = { ...options.env };
47
+ const resources = createSquareResourceOwner();
48
+ // One explicit lifetime for this server: every tool call borrows the same connection.
49
+ const inFlight = new Set();
47
50
  const server = new McpServer({ name: 'square', version: SQUARE_IDENTITY.packageVersion }, {
48
51
  instructions: 'The catch tool consumes activities. Use history with act/<index> cursors for read-only archive access.',
49
52
  });
50
53
  for (const tool of TOOL_DEFINITIONS) {
51
- server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, async (args, extra) => {
52
- try {
53
- const { as: participant, ...operationArgs } = args;
54
- const threadId = extra._meta?.threadId;
55
- const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
56
- const env = typeof threadId === 'string' && threadId.trim()
57
- ? operationEnv('codex', threadId, baseEnv)
58
- : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
59
- ? operationEnv('opencode', opencodeSessionId, baseEnv)
60
- : baseEnv;
61
- const app = createSquareApplication({ ...options, env, participant: participant ?? options.participant });
62
- const outcome = await operation(app, tool.name, operationArgs, { signal: extra.signal });
63
- const structuredContent = outcome.data;
64
- return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent };
65
- }
66
- catch (error) {
67
- const normalized = extra.signal.aborted
68
- ? { code: 'cancelled', message: 'The MCP request was cancelled' }
69
- : isSquareError(error)
70
- ? { code: error.code, message: error.message, ...(error.facts === undefined ? {} : { facts: error.facts }) }
71
- : { code: 'internal', message: error instanceof Error ? error.message : String(error) };
72
- const operation = error instanceof Error ? error.operation : undefined;
73
- const failure = operation === undefined ? normalized : { ...normalized, operation };
74
- return { isError: true, content: [{ type: 'text', text: JSON.stringify(failure) }], structuredContent: { error: failure } };
75
- }
54
+ server.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema }, (args, extra) => {
55
+ const call = (async () => {
56
+ try {
57
+ const { as: participant, ...operationArgs } = args;
58
+ const threadId = extra._meta?.threadId;
59
+ const opencodeSessionId = extra._meta?.['ai.opencode/sessionID'];
60
+ const env = typeof threadId === 'string' && threadId.trim()
61
+ ? operationEnv('codex', threadId, baseEnv)
62
+ : typeof opencodeSessionId === 'string' && opencodeSessionId.trim()
63
+ ? operationEnv('opencode', opencodeSessionId, baseEnv)
64
+ : baseEnv;
65
+ const app = createSquareApplication({ ...options, env, participant: participant ?? options.participant, resources }, { presentation: false });
66
+ const outcome = await operation(app, tool.name, operationArgs, { signal: extra.signal });
67
+ const structuredContent = outcome.data;
68
+ return { content: [{ type: 'text', text: JSON.stringify(structuredContent) }], structuredContent };
69
+ }
70
+ catch (error) {
71
+ const normalized = extra.signal.aborted
72
+ ? { code: 'cancelled', message: 'The MCP request was cancelled' }
73
+ : isSquareError(error)
74
+ ? { code: error.code, message: error.message, ...(error.facts === undefined ? {} : { facts: error.facts }) }
75
+ : { code: 'internal', message: error instanceof Error ? error.message : String(error) };
76
+ const operation = error instanceof Error ? error.operation : undefined;
77
+ const failure = operation === undefined ? normalized : { ...normalized, operation };
78
+ return { isError: true, content: [{ type: 'text', text: JSON.stringify(failure) }], structuredContent: { error: failure } };
79
+ }
80
+ })();
81
+ inFlight.add(call);
82
+ return call.finally(() => { inFlight.delete(call); });
76
83
  });
77
84
  }
85
+ const close = server.close.bind(server);
86
+ // One idempotent shutdown for both close paths: an explicit server.close() and a
87
+ // transport-initiated disconnect, where the SDK protocol fires onclose by itself.
88
+ let draining;
89
+ const drain = () => draining ??= (async () => {
90
+ // The SDK aborts in-flight request signals before onclose/close returns; wait for
91
+ // those handlers to release the shared connection (their cleanup writes included).
92
+ await Promise.allSettled([...inFlight]);
93
+ await resources.close();
94
+ })();
95
+ server.close = async () => { await close(); await drain(); };
96
+ const priorOnclose = server.server.onclose;
97
+ server.server.onclose = () => {
98
+ // onclose is synchronous, so the disconnect path starts the drain without awaiting it;
99
+ // the handler's own catch keeps an unexpected close failure from crashing the process.
100
+ void drain().catch(() => undefined);
101
+ priorOnclose?.();
102
+ };
78
103
  return server;
79
104
  }
80
105
  export async function runSquareMcpStdio(options) {
package/dist/model.d.ts CHANGED
@@ -20,9 +20,8 @@ export type InternalSquareErrorCode = SquareErrorCode | 'not_found' | 'cap_reach
20
20
  export interface SquareErrorFacts {
21
21
  /** Express admission rejected before any activity was committed. */
22
22
  activityUnsent?: true;
23
- /** Why a name could not be resolved: never in the roster, no longer standing in it, a bell
24
- * colliding with a mention, an @name that matches nobody, or an activity catch cannot address. */
25
- reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'unmatched_mention' | 'not_addressed' | 'migration_required' | 'explicit_reach_required' | 'invalid_reply' | 'invalid_done_body';
23
+ /** Refusal reason, including absent participants, conflicting reach, and inaccessible activities. */
24
+ reason?: 'never_joined' | 'not_standing' | 'bell_mention_conflict' | 'not_addressed' | 'migration_required' | 'explicit_reach_required' | 'invalid_reply' | 'invalid_done_body';
26
25
  /** The activity id a `catch --id` refusal named. */
27
26
  activityId?: string;
28
27
  /** Requested recipient spellings, without parsing presentation copy. */
package/dist/model.js CHANGED
@@ -37,11 +37,14 @@ export function nameKey(name) {
37
37
  export function sameName(a, b) {
38
38
  return nameKey(a) === nameKey(b);
39
39
  }
40
+ const TEXT_NAME_GRAPHEME_SOURCE = String.raw `[[\p{L}\p{N}\p{M}_\x2D]--[\p{Variation_Selector}]]`;
40
41
  /** One name grapheme: Unicode letters, digits, marks, hyphens, or underscores (no isolated variation selectors), or a complete RGI emoji. Shared by name validation and body @mention scanning. */
41
- export const NAME_GRAPHEME_SOURCE = String.raw `[[\p{L}\p{N}\p{M}_\x2D]--[\p{Variation_Selector}]]|\p{RGI_Emoji}`;
42
+ export const NAME_GRAPHEME_SOURCE = String.raw `${TEXT_NAME_GRAPHEME_SOURCE}|\p{RGI_Emoji}`;
43
+ // Ordinary Unicode names need not trigger the full RGI emoji matcher’s cold compilation.
44
+ const TEXT_NAME_PATTERN = new RegExp(`^(?:${TEXT_NAME_GRAPHEME_SOURCE})+(?:/(?:${TEXT_NAME_GRAPHEME_SOURCE})+)*$`, 'v');
42
45
  const NAME_PATTERN = new RegExp(`^(?:${NAME_GRAPHEME_SOURCE})+(?:/(?:${NAME_GRAPHEME_SOURCE})+)*$`, 'v');
43
46
  export function validateName(name) {
44
- if (!name || !NAME_PATTERN.test(name)) {
47
+ if (!name || (!TEXT_NAME_PATTERN.test(name) && !NAME_PATTERN.test(name))) {
45
48
  throw new SquareError('invalid_name', 'Invalid name: names must contain non-empty slash-separated segments using Unicode letters, digits, marks, hyphens, underscores, or complete RGI emoji graphemes.');
46
49
  }
47
50
  }
@@ -1,4 +1,4 @@
1
- import type { HostLedgerPort, SquareArtifactPort, WakeTransportPort } from './ports.js';
1
+ import type { HostLedgerPort, SquareArtifactPort, SquareResourceOwner, WakeTransportPort } from './ports.js';
2
2
  import type { OperationControl } from './square-facade.js';
3
3
  /** Host-bound resource: artifact, captured environment, clock, ledger and transport. */
4
4
  export interface OpenSquare {
@@ -8,6 +8,8 @@ export interface OpenSquare {
8
8
  readonly hostLedger?: HostLedgerPort;
9
9
  readonly wakeTransport?: WakeTransportPort;
10
10
  readonly env?: NodeJS.ProcessEnv;
11
+ /** False when a long-lived owner lent the connection; closing this view leaves it open. */
12
+ readonly ownsArtifact: boolean;
11
13
  }
12
14
  export interface SquareBuildOptions {
13
15
  markdown: string;
@@ -20,7 +22,15 @@ export interface SquareBuildOptions {
20
22
  /** One caller deadline propagated into artifact busy retries. */
21
23
  signal?: AbortSignal;
22
24
  }
23
- export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env' | 'signal'>, control?: OperationControl): Promise<OpenSquare>;
25
+ export interface SquareOpenOptions extends Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env' | 'signal'> {
26
+ /** A caller-owned connection to reuse; this view never closes it. */
27
+ readonly artifact?: SquareArtifactPort;
28
+ }
29
+ export declare function openSquare(squarePath: string, options?: SquareOpenOptions, control?: OperationControl): Promise<OpenSquare>;
24
30
  export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
25
31
  export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
26
32
  export declare function closeOpenSquare(square: OpenSquare): Promise<void>;
33
+ /** Explicit-lifetime holder of one reused artifact connection. The first request
34
+ * binds the location; a request for any other location is refused instead of
35
+ * silently sharing the wrong square. The owner closes the connection once. */
36
+ export declare function createSquareResourceOwner(): SquareResourceOwner;
@@ -1,20 +1,25 @@
1
+ import path from 'node:path';
1
2
  import { createArtifactPort, createSquare, prepareSquareBuild } from './artifact.js';
2
3
  import { InternalSquareError, SquareError } from './model.js';
3
4
  import { hostLedgerRoot } from './host-ledger-root.js';
4
5
  import { createHostLedgerPort } from './host-ledger-file-adapter.js';
5
6
  export async function openSquare(squarePath, options = {}, control) {
6
7
  const env = { ...(options.env ?? process.env) };
7
- const artifact = createArtifactPort(squarePath, undefined, options.signal);
8
+ const borrowed = options.artifact;
9
+ // A borrowed connection was opened by its owner without a request signal; this
10
+ // request may only abort its own read, never the shared connection's lifetime.
11
+ const artifact = borrowed ?? createArtifactPort(squarePath, undefined, options.signal);
8
12
  try {
9
13
  await artifact.read(facts => facts.header(), control?.signal);
10
14
  return {
11
- artifact, clock: options.clock ?? Date.now, location: squarePath, env,
15
+ artifact, clock: options.clock ?? Date.now, location: squarePath, env, ownsArtifact: borrowed === undefined,
12
16
  hostLedger: options.hostLedger ?? createHostLedgerPort({ rootPath: hostLedgerRoot(env) }),
13
17
  wakeTransport: options.wakeTransport,
14
18
  };
15
19
  }
16
20
  catch (error) {
17
- await artifact.close();
21
+ if (borrowed === undefined)
22
+ await artifact.close();
18
23
  if (error instanceof InternalSquareError && error.code === 'not_found')
19
24
  throw new SquareError('unavailable', `Square is unavailable at ${squarePath}`);
20
25
  throw error;
@@ -36,8 +41,41 @@ export function buildMemorySquare(options) {
36
41
  artifact: createArtifactPort(':memory:', prepareSquareBuild(options, options.markdown)),
37
42
  clock: options.clock ?? Date.now, location: 'memory',
38
43
  hostLedger: options.hostLedger, wakeTransport: options.wakeTransport,
44
+ ownsArtifact: true,
39
45
  };
40
46
  }
41
47
  export function closeOpenSquare(square) {
42
- return square.artifact.close();
48
+ // A borrowed connection belongs to its owner; only owned views close it here.
49
+ return square.ownsArtifact === false ? Promise.resolve() : square.artifact.close();
50
+ }
51
+ /** Explicit-lifetime holder of one reused artifact connection. The first request
52
+ * binds the location; a request for any other location is refused instead of
53
+ * silently sharing the wrong square. The owner closes the connection once. */
54
+ export function createSquareResourceOwner() {
55
+ let held;
56
+ let closed = false;
57
+ return {
58
+ artifact(location) {
59
+ if (closed)
60
+ throw new SquareError('unavailable', 'Square resource owner is closed');
61
+ const key = path.resolve(location);
62
+ if (held === undefined) {
63
+ // No external signal: a request must never become the shared connection's lifetime.
64
+ held = { location: key, artifact: createArtifactPort(key) };
65
+ }
66
+ if (held.location !== key) {
67
+ throw new SquareError('unavailable', `Square resource owner already holds ${held.location}; it cannot also open ${key}`);
68
+ }
69
+ return held.artifact;
70
+ },
71
+ async close() {
72
+ if (closed)
73
+ return;
74
+ closed = true;
75
+ const artifact = held?.artifact;
76
+ held = undefined;
77
+ if (artifact !== undefined)
78
+ await artifact.close();
79
+ },
80
+ };
43
81
  }
@@ -11,8 +11,12 @@ export async function identityRouteDraft(context, participant) {
11
11
  return undefined;
12
12
  const identity = processIdentity(context.env ?? process.env);
13
13
  const provider = identity.channel === 'claude-code' ? 'claude' : identity.channel === 'opencode' ? 'opencode' : identity.channel === 'pi' ? 'pi' : identity.channel === 'paseo' ? 'paseo' : 'codex';
14
- const capabilities = context.hostLedger === undefined ? { canUse: () => false } : await defaultWakeRouteCapabilities(context.hostLedger);
15
- return resolvePrimaryWakeRoute({ location: context.location, participant, sessionId: identity.session, provider }, context.env ?? process.env, capabilities);
14
+ const boundary = { location: context.location, participant, sessionId: identity.session, provider };
15
+ const env = context.env ?? process.env;
16
+ // No adapter can add a route when the host has no candidate in the first place.
17
+ if (context.hostLedger === undefined || resolvePrimaryWakeRoute(boundary, env, { canUse: () => true }) === undefined)
18
+ return undefined;
19
+ return resolvePrimaryWakeRoute(boundary, env, await defaultWakeRouteCapabilities(context.hostLedger));
16
20
  }
17
21
  /** One postcommit host observation; presence and route publication fail independently. */
18
22
  export async function publishCommittedIdentity(context, participant, epoch) {
package/dist/ports.d.ts CHANGED
@@ -165,6 +165,16 @@ export interface SquareArtifactPort {
165
165
  changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
166
166
  close(): Promise<void>;
167
167
  }
168
+ /** Explicit lifetime for an artifact connection reused by a long-lived caller.
169
+ * Only the connection is shared: every open still derives its own environment,
170
+ * clock, ledger and transport, and no request signal becomes a connection lifetime.
171
+ * The owner holds one location and refuses a second rather than sharing the wrong
172
+ * square; the caller lends the connection and never closes it. */
173
+ export interface SquareResourceOwner {
174
+ /** The shared connection for a location. Never close it; the owner does. */
175
+ artifact(location: string): SquareArtifactPort;
176
+ close(): Promise<void>;
177
+ }
168
178
  /** Capability-neutral wake transport. Unused by this Contract's activity operations. */
169
179
  export interface WakeTransportPort {
170
180
  /** Capability check that performs no external wake and writes no evidence. */
@@ -39,7 +39,7 @@ export function formatRefusal(squarePath, bodyLines, opts = {}) {
39
39
  }
40
40
  export function refusalText(error) {
41
41
  const text = error instanceof Error ? error.message : String(error);
42
- const marked = isSquareError(error) && ['never_joined', 'not_standing', 'unmatched_mention'].includes(error.facts?.reason ?? '');
42
+ const marked = isSquareError(error) && ['never_joined', 'not_standing'].includes(error.facts?.reason ?? '');
43
43
  return marked ? text.split('\n').map((line) => `✕ ${line}`).join('\n') : text;
44
44
  }
45
45
  export function quoteShell(value) {
@@ -8,7 +8,9 @@ export interface OperationContext extends HostContext {
8
8
  export type { OwnershipFenceOptions };
9
9
  export declare function catchUp(square: OperationContext, name: string, options?: CatchOptions, control?: OperationControl): Promise<CatchSelection>;
10
10
  export declare function catchUp(square: OperationContext, name: string, options: CatchOptions, control: OperationControl | undefined, leaseId: string): Promise<CatchSelection | null>;
11
- export declare function join(square: OperationContext, name: string, control?: OperationControl): Promise<{
11
+ export declare function join(square: OperationContext, name: string, control?: OperationControl, admission?: {
12
+ readonly requireKnown?: boolean;
13
+ }): Promise<{
12
14
  readonly name: string;
13
15
  readonly activity: Activity | null;
14
16
  readonly participantCount: number;
@@ -33,14 +33,19 @@ export async function catchUp(square, name, options = {}, control, leaseId) {
33
33
  await publishCommittedIdentity(square, selected.participant);
34
34
  return selected;
35
35
  }
36
- export async function join(square, name, control) {
36
+ export async function join(square, name, control, admission = {}) {
37
37
  throwIfAborted(control);
38
38
  validateName(name);
39
39
  const hostLedger = square.hostLedger;
40
- const preview = (await square.artifact.read((facts) => ({
41
- ...decideJoin(facts, name, square.clock()),
42
- participantCount: facts.roster().filter((p) => p.joined || p.done).length,
43
- }), control?.signal)).value;
40
+ const preview = (await square.artifact.read((facts) => {
41
+ // Express may reenter a known participant, but must never create a new name.
42
+ const selected = admission.requireKnown ? resolveKnownName(facts, name) : name;
43
+ return {
44
+ ...decideJoin(facts, selected, square.clock()),
45
+ participantCount: facts.roster().filter((p) => p.joined || p.done).length,
46
+ };
47
+ }, control?.signal)).value;
48
+ name = preview.joinedName;
44
49
  if (preview.joinAct === undefined) {
45
50
  if (hostLedger === undefined)
46
51
  return { name: preview.joinedName, activity: null, participantCount: preview.participantCount, reconnected: false };
@@ -1,5 +1,6 @@
1
1
  import type { Activity, ExpressResult, HistoryQuery, OperationControl, Participant, ParticipantStatus, SquareApplication, SquareApplicationContext, SquareAtInput, SquareBuildInput, SquareSnapshot } from './square-facade.js';
2
2
  export type * from './square-facade.js';
3
+ export { createSquareResourceOwner } from './open-square.js';
3
4
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
4
5
  export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
5
6
  export { HELD_WAIT_BUDGET_MS } from './runtime.js';
@@ -32,3 +33,6 @@ export declare class Square {
32
33
  retireEndedSessionRoutes(sessionId: string): Promise<void>;
33
34
  }
34
35
  export declare function createSquareApplication(context: SquareApplicationContext): SquareApplication;
36
+ export declare function createSquareApplication(context: SquareApplicationContext, demand: {
37
+ readonly presentation: false;
38
+ }): SquareApplication<false>;
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import crypto from 'node:crypto';
3
3
  import { setTimeout as sleep } from 'node:timers/promises';
4
4
  import { SquareError, isSquareError, nameKey, validateName } from './model.js';
5
- import { validateDoneBody } from './decisions.js';
5
+ import { resolveKnownName, validateDoneBody } from './decisions.js';
6
6
  import { HELD_WAIT_BUDGET_MS, WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
7
7
  import { catchLimit, observeCatchSelection } from './catch-decisions.js';
8
8
  import { directedSuffix } from './activity-feed.js';
@@ -19,6 +19,7 @@ import { createSquare, diagnoseSquareFile } from './artifact.js';
19
19
  import { migrateSquareFile } from './square-migration.js';
20
20
  import { discoverSquares, DEFAULT_LIST_DEPTH, DEFAULT_LIST_LIMIT } from './list.js';
21
21
  import { streamSquare } from './stream.js';
22
+ export { createSquareResourceOwner } from './open-square.js';
22
23
  export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
23
24
  export { CATCH_DEFAULT_LIMIT, CATCH_MAX_LIMIT } from './catch-decisions.js';
24
25
  export { HELD_WAIT_BUDGET_MS } from './runtime.js';
@@ -82,8 +83,8 @@ async function joinOwned(square, requested, options = {}, control) {
82
83
  async function prepareExpression(square, requested, control) {
83
84
  try {
84
85
  checkControl(control);
85
- const name = (await resolveParticipant(square, requested, control)).name;
86
- return await joinOwned(square, name, {}, control);
86
+ const result = await join(square, requested, control, { requireKnown: true });
87
+ return { participant: result.name, activity: result.activity };
87
88
  }
88
89
  catch (error) {
89
90
  throw failure(error, { operation: 'express', subject: 'caller', participant: requested, expressEffect: 'unsent' });
@@ -133,18 +134,18 @@ async function landExpression(square, name, body, options, transport, control) {
133
134
  }
134
135
  catch (error) {
135
136
  const recipient = isSquareError(error)
136
- && ['not_standing', 'unmatched_mention', 'bell_mention_conflict'].includes(error.facts?.reason ?? '');
137
+ && ['not_standing', 'bell_mention_conflict'].includes(error.facts?.reason ?? '');
137
138
  throw failure(error, {
138
139
  operation: 'express', participant: name, subject: recipient ? 'recipient' : 'caller', expressEffect: effect,
139
140
  });
140
141
  }
141
142
  }
142
- async function expressOwned(square, requested, body, options, transport, control) {
143
+ async function expressOwned(square, requested, body, options, transport, presentation, control) {
143
144
  const entry = await prepareExpression(square, requested, control);
144
145
  const name = entry.participant;
145
146
  let result;
146
147
  try {
147
- const feed = (await square.artifact.read((facts) => {
148
+ const feed = !presentation ? undefined : (await square.artifact.read((facts) => {
148
149
  const pendingPublic = directedSuffix(facts, name);
149
150
  return {
150
151
  pendingPublic,
@@ -161,7 +162,7 @@ async function expressOwned(square, requested, body, options, transport, control
161
162
  activity: result.activity, waited: result.waited,
162
163
  ...(result.replyAuthorNotHere === undefined ? {} : { replyAuthorNotHere: result.replyAuthorNotHere }),
163
164
  },
164
- projection: {
165
+ projection: feed === undefined ? undefined : {
165
166
  ...fresh, ...feed, participant: name,
166
167
  location: square.location, now: square.clock(), automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(square.env),
167
168
  ...(entry.activity === null ? {} : { reentry: entry.activity }),
@@ -217,9 +218,11 @@ async function catchOwned(square, requested, options = {}, control) {
217
218
  if (Object.hasOwn(options, 'replace') && idle === 0)
218
219
  throw new SquareError('invalid_args', 'Catch replacement requires a positive idle duration');
219
220
  catchLimit(options.limit);
220
- name = (await resolveParticipant(square, requested, control)).name;
221
- subject = 'filter';
222
- const from = options.from === undefined ? undefined : await Promise.all(options.from.map(async (target) => (await resolveParticipant(square, target, control)).name));
221
+ const from = (await square.artifact.read((facts) => {
222
+ name = resolveKnownName(facts, requested);
223
+ subject = 'filter';
224
+ return options.from?.map((target) => resolveKnownName(facts, target));
225
+ }, control?.signal)).value;
223
226
  selection = { ...options, ...(from === undefined ? {} : { from }) };
224
227
  subject = 'caller';
225
228
  if (idle === 0) {
@@ -360,12 +363,14 @@ export class Square {
360
363
  endOwnedSession(name, sessionId, expectedEpoch) { return endOwnedSession(this.square, name, sessionId, expectedEpoch); }
361
364
  retireEndedSessionRoutes(sessionId) { return retireEndedSessionRoutes(this.square, sessionId); }
362
365
  }
363
- export function createSquareApplication(context) {
366
+ export function createSquareApplication(context, demand = { presentation: true }) {
367
+ const presentation = demand.presentation;
364
368
  const env = { ...context.env };
365
369
  const clock = clockFor(env, context.clock);
366
370
  const hostLedger = context.hostLedger ?? hostLedgerForEnv(env);
367
371
  const openOptions = { env, clock, hostLedger, wakeTransport: context.wakeTransport };
368
372
  const getTransport = defaultTransport(openOptions);
373
+ const resources = context.resources;
369
374
  function location() {
370
375
  const selected = context.squarePath ?? env.SQUARE_LOCATION;
371
376
  if (selected === undefined || selected.trim() === '')
@@ -385,7 +390,9 @@ export function createSquareApplication(context) {
385
390
  }
386
391
  async function scoped(operation, control) {
387
392
  checkControl(control);
388
- const square = await openSquare(location(), openOptions, control);
393
+ const selected = location();
394
+ const artifact = resources?.artifact(selected);
395
+ const square = await openSquare(selected, { ...openOptions, artifact }, control);
389
396
  try {
390
397
  return await operation(square);
391
398
  }
@@ -416,7 +423,7 @@ export function createSquareApplication(context) {
416
423
  const result = await (verb === 'listen' ? listen : ignore)(square, name, target, control);
417
424
  return {
418
425
  data: result,
419
- projection: {
426
+ projection: !presentation ? undefined : {
420
427
  ...await contextProjection(square, name), participant: name,
421
428
  requestedTarget: target, target: result.activity?.target ?? target,
422
429
  },
@@ -442,6 +449,8 @@ export function createSquareApplication(context) {
442
449
  else {
443
450
  result = await resume(square, name, control);
444
451
  }
452
+ if (!presentation)
453
+ return { data: { activity: result.activity }, projection: undefined };
445
454
  const view = await eventPresentation(square, result.activity.id);
446
455
  return {
447
456
  data: { activity: result.activity },
@@ -461,7 +470,7 @@ export function createSquareApplication(context) {
461
470
  }, input.markdown, control?.signal);
462
471
  return {
463
472
  data: { location: squarePath, ...configuration },
464
- projection: { location: squarePath, standingCount: 0, held: false, now,
473
+ projection: !presentation ? undefined : { location: squarePath, standingCount: 0, held: false, now,
465
474
  automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(env) },
466
475
  };
467
476
  },
@@ -472,6 +481,8 @@ export function createSquareApplication(context) {
472
481
  throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
473
482
  }
474
483
  const data = await joinOwned(square, name, options, control);
484
+ if (!presentation)
485
+ return { data, projection: undefined };
475
486
  try {
476
487
  const view = await entryPresentation(square, data.participant, last);
477
488
  return {
@@ -485,19 +496,19 @@ export function createSquareApplication(context) {
485
496
  }, control);
486
497
  },
487
498
  express(body, options = {}, control) {
488
- return selected('express', (square, name) => expressOwned(square, name, body, options, getTransport, control), control);
499
+ return selected('express', (square, name) => expressOwned(square, name, body, options, getTransport, presentation, control), control);
489
500
  },
490
501
  catch(options, control) {
491
502
  return selected('catch', async (square, name) => {
492
503
  const result = await catchOwned(square, name, options, control);
493
- const presence = await catchPresence(square, control).catch(() => undefined);
504
+ const presence = !presentation ? undefined : await catchPresence(square, control).catch(() => undefined);
494
505
  const view = result.selection;
495
506
  const stop = result.stop;
496
507
  const status = stop === 'already-active' || stop === 'replaced' || stop === 'capped' || stop === 'quorum'
497
508
  ? stop : undefined;
498
509
  return {
499
510
  data: { ...caughtResult(result), ...(status === undefined ? {} : { status }) },
500
- projection: {
511
+ projection: !presentation ? undefined : {
501
512
  ...observedContext(square, view.standingCount, view.held, view.participant, view.now),
502
513
  participant: view.participant, sayNumbers: view.sayNumbers,
503
514
  ownActivityCount: view.ownActivityCount, hardCap: view.hardCap, stop,
@@ -525,12 +536,15 @@ export function createSquareApplication(context) {
525
536
  const limit = query.limit ?? (query.at?.length ? null : HISTORY_DEFAULT_LIMIT);
526
537
  const kinds = query.at?.length || query.contextBefore !== undefined || query.contextAfter !== undefined || query.since !== undefined ? undefined : NON_READ;
527
538
  return (await square.artifact.read((facts) => {
528
- const selected = selectHistory(facts, query, limit, kinds);
539
+ const selected = selectHistory(facts, query, limit, kinds, presentation);
529
540
  if (limit === null && selected.totalMatches > HISTORY_MAX_LIMIT) {
530
541
  throw failure(new SquareError('invalid_args', `History context exceeds ${HISTORY_MAX_LIMIT} activities`), {
531
542
  operation: 'history', historyBounds: { maxLimit: HISTORY_MAX_LIMIT, totalMatches: selected.totalMatches },
532
543
  });
533
544
  }
545
+ const data = { activities: publicActivities(selected.activities) };
546
+ if (!presentation)
547
+ return { data, projection: undefined };
534
548
  const indexes = selected.activities.map((activity) => activity.index);
535
549
  const presenceAnchors = {};
536
550
  for (const participant of facts.roster()) {
@@ -539,7 +553,7 @@ export function createSquareApplication(context) {
539
553
  (presenceAnchors[anchor] ??= []).push(participant.name);
540
554
  }
541
555
  return {
542
- data: { activities: publicActivities(selected.activities) },
556
+ data,
543
557
  projection: {
544
558
  ...observedContext(square, inSquareCount(facts), facts.hold().active), ...selected,
545
559
  sayNumbers: facts.sayOrdinals(indexes), presenceAnchors,
@@ -554,7 +568,7 @@ export function createSquareApplication(context) {
554
568
  return selected('other', async (square, requested) => {
555
569
  const name = (await resolveParticipant(square, requested, control)).name;
556
570
  const targets = await listening(square, name, control);
557
- return { data: { listening: targets }, projection: await contextProjection(square, name, control) };
571
+ return { data: { listening: targets }, projection: !presentation ? undefined : await contextProjection(square, name, control) };
558
572
  }, control);
559
573
  },
560
574
  hold: (reason, control) => lifecycle('hold', reason, control),
@@ -562,15 +576,15 @@ export function createSquareApplication(context) {
562
576
  done: (body, control) => lifecycle('done', body, control),
563
577
  async status(viewer, control) {
564
578
  checkControl(control);
565
- const selectedViewer = viewer ?? await actor().catch(() => '');
579
+ const selectedViewer = !presentation ? '' : viewer ?? await actor().catch(() => '');
566
580
  return scoped(async (square) => (await square.artifact.read((facts) => queryStatus(facts, {
567
581
  location: square.location, now: square.clock(), automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(square.env),
568
- }, selectedViewer), control?.signal)).value, control);
582
+ }, selectedViewer, presentation), control?.signal)).value, control);
569
583
  },
570
584
  participants(control) {
571
585
  return scoped(async (square) => (await square.artifact.read((facts) => queryParticipants(facts, {
572
586
  location: square.location, now: square.clock(), automaticDeliveryAvailable: hasAutomaticDeliveryIdentity(square.env),
573
- }), control?.signal)).value, control);
587
+ }, presentation), control?.signal)).value, control);
574
588
  },
575
589
  async diagnose(control) {
576
590
  checkControl(control);
@@ -578,7 +592,7 @@ export function createSquareApplication(context) {
578
592
  const data = await diagnoseSquareFile(squarePath);
579
593
  return {
580
594
  data,
581
- projection: { location: squarePath, now: clock(),
595
+ projection: !presentation ? undefined : { location: squarePath, now: clock(),
582
596
  ...(data.standingCount === undefined ? {} : { standingCount: data.standingCount }) },
583
597
  };
584
598
  },
@@ -586,14 +600,14 @@ export function createSquareApplication(context) {
586
600
  checkControl(control);
587
601
  const squarePath = location();
588
602
  const data = await migrateSquareFile(squarePath, control?.signal);
589
- return { data, projection: { location: squarePath, now: clock(), standingCount: data.standing } };
603
+ return { data, projection: !presentation ? undefined : { location: squarePath, now: clock(), standingCount: data.standing } };
590
604
  },
591
605
  async discover(options, control) {
592
606
  checkControl(control);
593
607
  const data = await discoverSquares(context.cwd, options, control, openOptions);
594
608
  return {
595
609
  data,
596
- projection: { cwd: context.cwd, now: clock(),
610
+ projection: !presentation ? undefined : { cwd: context.cwd, now: clock(),
597
611
  depth: options.depth ?? DEFAULT_LIST_DEPTH, limit: options.limit ?? DEFAULT_LIST_LIMIT },
598
612
  };
599
613
  },
@@ -633,7 +647,7 @@ export function createSquareApplication(context) {
633
647
  const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
634
648
  return compare(a.squarePath, b.squarePath) || compare(nameKey(a.name), nameKey(b.name)) || compare(a.name, b.name);
635
649
  });
636
- return { data: { rows: rows.slice(0, limit), total: rows.length }, projection: { sessionId, now: clock(), limit } };
650
+ return { data: { rows: rows.slice(0, limit), total: rows.length }, projection: !presentation ? undefined : { sessionId, now: clock(), limit } };
637
651
  },
638
652
  async recovery(control) {
639
653
  let projection = { now: clock() };
@@ -659,7 +673,7 @@ export function createSquareApplication(context) {
659
673
  }, control);
660
674
  }
661
675
  catch { /* unavailable recovery observations never replace the original error */ }
662
- return { data: projection, projection };
676
+ return { data: projection, projection: presentation ? projection : undefined };
663
677
  },
664
678
  stream(options, control) {
665
679
  checkControl(control);