@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.
- package/dist/attention-presentation.d.ts +2 -0
- package/dist/attention-presentation.js +11 -2
- package/dist/boundary-presentation.d.ts +8 -1
- package/dist/boundary-presentation.js +8 -1
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/express-presentation.js +1 -2
- package/dist/cli/ledger-command.js +6 -2
- package/dist/cli/meta-commands.js +1 -1
- package/dist/decisions.js +6 -21
- package/dist/help.js +4 -4
- package/dist/host-ledger-migration.d.ts +1 -0
- package/dist/host-ledger-migration.js +62 -21
- package/dist/host-ledger-sqlite.d.ts +4 -1
- package/dist/host-ledger-sqlite.js +36 -9
- package/dist/inbox.d.ts +2 -0
- package/dist/inbox.js +64 -22
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.js +51 -26
- package/dist/model.d.ts +2 -3
- package/dist/model.js +5 -2
- package/dist/open-square.d.ts +12 -2
- package/dist/open-square.js +42 -4
- package/dist/participant-host.js +6 -2
- package/dist/ports.d.ts +10 -0
- package/dist/presentation.js +1 -1
- package/dist/square-actions.d.ts +3 -1
- package/dist/square-actions.js +10 -5
- package/dist/square-application.d.ts +4 -0
- package/dist/square-application.js +42 -28
- package/dist/square-facade.d.ts +26 -22
- package/dist/views.d.ts +3 -1
- package/dist/views.js +37 -29
- package/guides/application-demand-optimization.md +50 -0
- package/guides/catch-cold-path-optimization.md +56 -0
- package/guides/catch-lock-investigation.md +81 -0
- package/guides/host-ledger.md +2 -2
- package/guides/mcp-connection-lifetime.md +22 -0
- package/guides/pi-boundary-pending-reuse.md +13 -0
- package/package.json +2 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +1 -1
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/paseo/README.md +10 -5
- package/plugins/paseo/client/markdown.tsx +221 -0
- package/plugins/paseo/client/square-message.tsx +64 -94
- package/plugins/paseo/client/transform.ts +4 -1
- package/plugins/paseo/shared/message.ts +6 -4
- package/plugins/pi/square-pi.js +26 -16
- 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 },
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
/**
|
|
24
|
-
|
|
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
|
|
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
|
}
|
package/dist/open-square.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/open-square.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/participant-host.js
CHANGED
|
@@ -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
|
|
15
|
-
|
|
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. */
|
package/dist/presentation.js
CHANGED
|
@@ -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'
|
|
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) {
|
package/dist/square-actions.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/square-actions.js
CHANGED
|
@@ -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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
86
|
-
return
|
|
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', '
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
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
|
|
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);
|