@astrosheep/square 0.4.4 → 0.4.5
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/artifact-store.d.ts +1 -1
- package/dist/artifact-store.js +32 -15
- package/dist/artifact.d.ts +1 -4
- package/dist/artifact.js +0 -28
- package/dist/attention-presentation.js +1 -1
- package/dist/boundary-presentation.js +2 -2
- package/dist/claude-boundary-state.d.ts +6 -6
- package/dist/claude-boundary-state.js +13 -13
- package/dist/claude-hook.js +25 -24
- package/dist/claude-mcp-context.d.ts +2 -2
- package/dist/claude-mcp-context.js +6 -6
- package/dist/cli/context.js +3 -6
- package/dist/cli/json.js +1 -1
- package/dist/cli/mcp-server-command.js +3 -3
- package/dist/cli/observation-commands.d.ts +2 -2
- package/dist/cli/observation-commands.js +23 -51
- package/dist/cli/perceive-presentation.d.ts +3 -0
- package/dist/cli/{catch-presentation.js → perceive-presentation.js} +3 -3
- package/dist/cli/registry.js +2 -2
- package/dist/codex-address.d.ts +3 -0
- package/dist/codex-address.js +7 -0
- package/dist/codex-hook.d.ts +1 -1
- package/dist/codex-hook.js +11 -8
- package/dist/codex-queue.d.ts +3 -2
- package/dist/codex-queue.js +8 -11
- package/dist/codex-watch.d.ts +6 -1
- package/dist/codex-watch.js +27 -29
- package/dist/delivery-ledger-sqlite.d.ts +25 -20
- package/dist/delivery-ledger-sqlite.js +76 -61
- package/dist/delivery-ledger.d.ts +24 -19
- package/dist/delivery-ledger.js +150 -81
- package/dist/delivery-lifetime.d.ts +2 -2
- package/dist/delivery-lifetime.js +6 -18
- package/dist/help.js +12 -12
- package/dist/hints.d.ts +2 -2
- package/dist/hints.js +7 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.d.ts +2 -2
- package/dist/list.js +4 -3
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +8 -32
- package/dist/neutral.d.ts +5 -5
- package/dist/notifications.js +12 -6
- package/dist/operation-presentation.d.ts +8 -3
- package/dist/operation-presentation.js +50 -12
- package/dist/packages/agent-delivery/src/file-lock.js +4 -1
- package/dist/perceive-decisions.d.ts +28 -0
- package/dist/{catch-decisions.js → perceive-decisions.js} +24 -24
- package/dist/{catch-selection.d.ts → perceive-selection.d.ts} +2 -2
- package/dist/{catch-selection.js → perceive-selection.js} +3 -3
- package/dist/plugins/opencode/index.js +4 -4
- package/dist/plugins/opencode-shared/delivery.d.ts +6 -6
- package/dist/plugins/opencode-shared/delivery.js +27 -21
- package/dist/plugins/opencode-v1/runtime.js +3 -3
- package/dist/ports.d.ts +0 -4
- package/dist/presentation.d.ts +1 -1
- package/dist/presentation.js +13 -13
- package/dist/session-lifecycle.d.ts +2 -2
- package/dist/session-lifecycle.js +11 -11
- package/dist/sqlite-square.d.ts +2 -4
- package/dist/sqlite-square.js +47 -54
- package/dist/sqlite-transaction.d.ts +2 -0
- package/dist/sqlite-transaction.js +25 -3
- package/dist/square-api.d.ts +1 -1
- package/dist/square-api.js +1 -1
- package/dist/square-domain.js +1 -1
- package/dist/square-facade.d.ts +15 -13
- package/dist/square-location.js +1 -1
- package/dist/square-posting.d.ts +5 -5
- package/dist/square-posting.js +39 -38
- package/dist/square-tools.d.ts +15 -4
- package/dist/square-tools.js +35 -17
- package/dist/views.d.ts +7 -3
- package/dist/views.js +6 -3
- package/guides/architect.md +2 -2
- package/guides/change-waits.md +1 -1
- package/guides/codex-delivery.md +40 -35
- package/guides/delivery-primitives.md +22 -7
- package/guides/mcp.md +4 -2
- package/guides/operation-cancellation.md +11 -3
- package/guides/pi-native-delivery.md +1 -1
- package/guides/pi-tools.md +11 -6
- package/guides/receive-primitive.md +3 -3
- package/guides/square-api.md +15 -8
- package/package.json +2 -2
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +10 -10
- package/plugins/codex/.codex-plugin/plugin.json +2 -2
- package/plugins/codex/hooks/hooks.json +3 -0
- package/plugins/opencode/index.ts +4 -4
- package/plugins/opencode-shared/delivery.ts +27 -21
- package/plugins/opencode-v1/runtime.ts +3 -3
- package/plugins/pi/square-pi.js +18 -12
- package/skills/square/SKILL.md +10 -10
- package/dist/catch-decisions.d.ts +0 -28
- package/dist/cli/catch-presentation.d.ts +0 -3
- package/guides/mcp-connection-lifetime.md +0 -22
|
@@ -5,12 +5,13 @@ import { harnessOwner, decodeHarnessOwner } from '../../owner.js';
|
|
|
5
5
|
import { attachDeliverySessionRoutes, detachDeliverySession, markDeliverySessionDeleted } from '../../session-lifecycle.js';
|
|
6
6
|
const routeKind = 'opencode-server';
|
|
7
7
|
const ownerFor = (sessionId) => harnessOwner({ channel: 'opencode', session: sessionId });
|
|
8
|
-
const open = (env) => DeliveryLedger.open(
|
|
8
|
+
const open = (env, signal) => DeliveryLedger.open({ location: env.SQUARE_DELIVERY, signal });
|
|
9
9
|
const payloadHash = (payload) => createHash('sha256').update(payload).digest('hex');
|
|
10
|
-
export async function listOpenCodeSessions(env) {
|
|
11
|
-
|
|
10
|
+
export async function listOpenCodeSessions(env, signal) {
|
|
11
|
+
signal ??= AbortSignal.timeout(5000);
|
|
12
|
+
const ledger = await open(env, signal);
|
|
12
13
|
try {
|
|
13
|
-
return (await ledger.sessions()).flatMap(session => {
|
|
14
|
+
return (await ledger.sessions(signal)).flatMap(session => {
|
|
14
15
|
const coordinate = decodeHarnessOwner(session.owner);
|
|
15
16
|
return session.state !== 'deleted' && coordinate?.channel === 'opencode' ? [coordinate.session] : [];
|
|
16
17
|
});
|
|
@@ -20,8 +21,9 @@ export async function listOpenCodeSessions(env) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
/** The file observer is a hint; callers must always reread the ledger after an edge. */
|
|
23
|
-
export async function observeOpenCodeSessions(env) {
|
|
24
|
-
|
|
24
|
+
export async function observeOpenCodeSessions(env, signal) {
|
|
25
|
+
signal ??= AbortSignal.timeout(5000);
|
|
26
|
+
const ledger = await open(env, signal);
|
|
25
27
|
const observer = await ledger.observe(ownerFor('discovery'));
|
|
26
28
|
return { observer, close: async () => { observer.close(); await ledger.close(); } };
|
|
27
29
|
}
|
|
@@ -29,25 +31,29 @@ export async function publishOpenCodeAddress(sessionId, env, signal) {
|
|
|
29
31
|
const session = await attachDeliverySessionRoutes(ownerFor(sessionId), env, [{ kind: routeKind, address: { sessionId } }], signal);
|
|
30
32
|
return session ? session.generation : undefined;
|
|
31
33
|
}
|
|
32
|
-
export async function currentOpenCodeGeneration(sessionId, env) {
|
|
33
|
-
|
|
34
|
+
export async function currentOpenCodeGeneration(sessionId, env, signal) {
|
|
35
|
+
signal ??= AbortSignal.timeout(5000);
|
|
36
|
+
const ledger = await open(env, signal);
|
|
34
37
|
try {
|
|
35
|
-
return (await ledger.session(ownerFor(sessionId)))?.generation;
|
|
38
|
+
return (await ledger.session(ownerFor(sessionId), signal))?.generation;
|
|
36
39
|
}
|
|
37
40
|
finally {
|
|
38
41
|
await ledger.close();
|
|
39
42
|
}
|
|
40
43
|
}
|
|
41
|
-
export async function detachOpenCodeSession(sessionId, generation, env) {
|
|
42
|
-
|
|
44
|
+
export async function detachOpenCodeSession(sessionId, generation, env, signal) {
|
|
45
|
+
signal ??= AbortSignal.timeout(5000);
|
|
46
|
+
await detachDeliverySession(ownerFor(sessionId), generation, env, signal);
|
|
43
47
|
}
|
|
44
|
-
export async function endOpenCodeSession(sessionId, generation, env) {
|
|
45
|
-
|
|
48
|
+
export async function endOpenCodeSession(sessionId, generation, env, signal) {
|
|
49
|
+
signal ??= AbortSignal.timeout(5000);
|
|
50
|
+
await markDeliverySessionDeleted(ownerFor(sessionId), generation, env, signal);
|
|
46
51
|
}
|
|
47
|
-
export async function cancelOpenCodeDeliveries(sessionId, generation, env) {
|
|
48
|
-
|
|
52
|
+
export async function cancelOpenCodeDeliveries(sessionId, generation, env, signal) {
|
|
53
|
+
signal ??= AbortSignal.timeout(5000);
|
|
54
|
+
const ledger = await open(env, signal);
|
|
49
55
|
try {
|
|
50
|
-
return await ledger.cancel({ owner: ownerFor(sessionId), generation });
|
|
56
|
+
return await ledger.cancel({ owner: ownerFor(sessionId), generation }, signal);
|
|
51
57
|
}
|
|
52
58
|
finally {
|
|
53
59
|
await ledger.close();
|
|
@@ -66,15 +72,15 @@ export async function observeOpenCodeContext(sessionId, generation, messages, en
|
|
|
66
72
|
}));
|
|
67
73
|
if (!observed.size || signal.aborted)
|
|
68
74
|
return;
|
|
69
|
-
const ledger = await open(env);
|
|
75
|
+
const ledger = await open(env, signal);
|
|
70
76
|
try {
|
|
71
|
-
for (const attempt of await ledger.handoffs(ownerFor(sessionId), { generation })) {
|
|
77
|
+
for (const attempt of await ledger.handoffs(ownerFor(sessionId), { generation }, signal)) {
|
|
72
78
|
if (signal.aborted)
|
|
73
79
|
return;
|
|
74
80
|
const { correlation } = attempt;
|
|
75
81
|
if (attempt.routeKind === routeKind && correlation.token === attempt.token && correlation.messageId !== undefined
|
|
76
82
|
&& correlation.payloadHash !== undefined && observed.get(correlation.messageId) === correlation.payloadHash)
|
|
77
|
-
await ledger.observePresentation({ token: attempt.token, owner: ownerFor(sessionId), generation, routeKind, payloadHash: correlation.payloadHash }, { requireCurrent: true });
|
|
83
|
+
await ledger.observePresentation({ token: attempt.token, owner: ownerFor(sessionId), generation, routeKind, payloadHash: correlation.payloadHash }, { requireCurrent: true }, signal);
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
finally {
|
|
@@ -83,13 +89,13 @@ export async function observeOpenCodeContext(sessionId, generation, messages, en
|
|
|
83
89
|
}
|
|
84
90
|
export async function receiveOpenCodePending(sessionId, generation, transport, env, signal) {
|
|
85
91
|
const owner = ownerFor(sessionId);
|
|
86
|
-
const ledger = await open(env);
|
|
92
|
+
const ledger = await open(env, signal);
|
|
87
93
|
const lifetime = retainDeliveryLedger(ledger);
|
|
88
94
|
const observer = await ledger.observe(owner);
|
|
89
95
|
try {
|
|
90
96
|
while (!signal.aborted) {
|
|
91
97
|
const baseline = observer.generation;
|
|
92
|
-
for (const card of await ledger.pending(owner, { generation })) {
|
|
98
|
+
for (const card of await ledger.pending(owner, { generation }, signal)) {
|
|
93
99
|
if (signal.aborted)
|
|
94
100
|
return;
|
|
95
101
|
lifetime.track(await ledger.dispatchHandoff(owner, card.key, transport, { generation, timeoutMs: 5000, signal }));
|
|
@@ -4,8 +4,8 @@ import { legacyTransport } from './transport.js';
|
|
|
4
4
|
export async function startReceiver(ctx) {
|
|
5
5
|
const env = { ...process.env };
|
|
6
6
|
const directory = await canonicalPath(ctx.directory);
|
|
7
|
-
const discovery = await observeOpenCodeSessions(env);
|
|
8
7
|
const lifetime = new AbortController();
|
|
8
|
+
const discovery = await observeOpenCodeSessions(env, lifetime.signal);
|
|
9
9
|
const receivers = new Map();
|
|
10
10
|
const terminal = new Set();
|
|
11
11
|
const interrupting = new Set();
|
|
@@ -28,7 +28,7 @@ export async function startReceiver(ctx) {
|
|
|
28
28
|
&& await canonicalPath(info.directory) === directory;
|
|
29
29
|
};
|
|
30
30
|
const reconcile = async (active) => {
|
|
31
|
-
const candidates = new Set(await listOpenCodeSessions(env));
|
|
31
|
+
const candidates = new Set(await listOpenCodeSessions(env, lifetime.signal));
|
|
32
32
|
if (active)
|
|
33
33
|
candidates.add(active);
|
|
34
34
|
for (const id of candidates) {
|
|
@@ -40,7 +40,7 @@ export async function startReceiver(ctx) {
|
|
|
40
40
|
const receiver = receivers.get(id);
|
|
41
41
|
stop(id);
|
|
42
42
|
if (receiver)
|
|
43
|
-
await detachOpenCodeSession(id, receiver.generation, env);
|
|
43
|
+
await detachOpenCodeSession(id, receiver.generation, env, lifetime.signal);
|
|
44
44
|
continue;
|
|
45
45
|
}
|
|
46
46
|
const existing = receivers.get(id);
|
package/dist/ports.d.ts
CHANGED
|
@@ -153,7 +153,3 @@ export interface SquareArtifactPort {
|
|
|
153
153
|
changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
|
|
154
154
|
close(): Promise<void>;
|
|
155
155
|
}
|
|
156
|
-
export interface SquareResourceOwner {
|
|
157
|
-
artifact(location: string): SquareArtifactPort;
|
|
158
|
-
close(): Promise<void>;
|
|
159
|
-
}
|
package/dist/presentation.d.ts
CHANGED
package/dist/presentation.js
CHANGED
|
@@ -82,7 +82,7 @@ export function renderPresenceLines(recent, now, limit = 5) {
|
|
|
82
82
|
? (p.lastActiveAt === undefined ? 'stepped out of the square' : `stepped out of the square · ${formatRelativeTime(p.lastActiveAt, now)}`)
|
|
83
83
|
: p.presence === 'away' ? 'is away'
|
|
84
84
|
: p.presence === 'watching'
|
|
85
|
-
? (p.lastActiveAt === undefined ? 'is nearby ·
|
|
85
|
+
? (p.lastActiveAt === undefined ? 'is nearby · perceiving' : `is nearby · perceiving · ${formatRelativeTime(p.lastActiveAt, now)}`)
|
|
86
86
|
: p.lastActiveAt === undefined ? 'is nearby · quiet'
|
|
87
87
|
: p.activityCount > 0 ? `is nearby · ${formatRelativeTime(p.lastActiveAt, now)}` : `is nearby · quiet · ${formatRelativeTime(p.lastActiveAt, now)}`;
|
|
88
88
|
return ` ${glyph === '○' ? style('dim', glyph) : glyph} ${participantIdentity(p.name)}${style('dim', ` ${text}`)}`;
|
|
@@ -237,7 +237,7 @@ function renderUnreadSummary(activitySummaries) {
|
|
|
237
237
|
];
|
|
238
238
|
}
|
|
239
239
|
export function renderActivityBlocked(opts) {
|
|
240
|
-
const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)}
|
|
240
|
+
const readNowCommand = `${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)} perceive`;
|
|
241
241
|
return withPathOutput(opts.squarePath, [
|
|
242
242
|
`${style('blocked', '✕')} your activity hasn't landed — someone spoke while you were away`,
|
|
243
243
|
...renderUnreadSummary(opts.activitySummaries),
|
|
@@ -339,20 +339,20 @@ export function renderActivityLimit(opts) {
|
|
|
339
339
|
}
|
|
340
340
|
export function renderWatchAlreadyActive(opts) {
|
|
341
341
|
return [
|
|
342
|
-
`${style('blocked', '✕')} you are already
|
|
343
|
-
` · an active
|
|
344
|
-
' · --replace lets a new
|
|
345
|
-
`${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)}
|
|
342
|
+
`${style('blocked', '✕')} you are already perceiving`,
|
|
343
|
+
` · an active perceive is already running for ${participantIdentity(opts.name)}`,
|
|
344
|
+
' · --replace lets a new perceive take over',
|
|
345
|
+
`${participantCommandPrefix(opts.squarePath, opts.name, opts.owner)} perceive --idle 30m --replace`,
|
|
346
346
|
].join('\n');
|
|
347
347
|
}
|
|
348
348
|
export function renderWatchForceTakeover(_opts) {
|
|
349
|
-
return '✓ your new
|
|
349
|
+
return '✓ your new perceive takes over';
|
|
350
350
|
}
|
|
351
351
|
export function renderWatchReplaceMissing(_opts) {
|
|
352
|
-
return '· no
|
|
352
|
+
return '· no perceive stood here — yours takes the spot';
|
|
353
353
|
}
|
|
354
354
|
export function renderWatchReplaced(_opts) {
|
|
355
|
-
return '✕ a newer
|
|
355
|
+
return '✕ a newer perceive took over';
|
|
356
356
|
}
|
|
357
357
|
export function renderWatchStatus(opts) {
|
|
358
358
|
const others = opts.presence === undefined
|
|
@@ -365,18 +365,18 @@ export function renderWatchStatus(opts) {
|
|
|
365
365
|
case 'idle':
|
|
366
366
|
case 'drained': {
|
|
367
367
|
const prefix = participantCommandPrefix(opts.squarePath, opts.name, opts.owner);
|
|
368
|
-
// Quiet graduates with the idle length; an empty
|
|
368
|
+
// Quiet graduates with the idle length; an empty perceive has no idle budget to report.
|
|
369
369
|
const idleMs = opts.idleMs;
|
|
370
370
|
const quiet = idleMs !== undefined && idleMs >= 60 * 60 * 1000
|
|
371
371
|
? style('dim', `○ dust lies thick — ${formatDuration(idleMs)} of quiet`)
|
|
372
372
|
: idleMs !== undefined && idleMs >= 60 * 1000
|
|
373
373
|
? style('dim', `○ dust settles on the flagstones — ${formatDuration(idleMs)} of quiet`)
|
|
374
374
|
: style('dim', '○ only footsteps in the square — nothing new for you');
|
|
375
|
-
// The quiet report never repeats the
|
|
376
|
-
const hints = [`${prefix}
|
|
375
|
+
// The quiet report never repeats the perceive just run.
|
|
376
|
+
const hints = [`${prefix} perceive --idle 30m`];
|
|
377
377
|
return [
|
|
378
378
|
quiet,
|
|
379
|
-
...(opts.
|
|
379
|
+
...(opts.showPerceiveHint === false ? [] : hints),
|
|
380
380
|
...presenceLines,
|
|
381
381
|
].join('\n');
|
|
382
382
|
}
|
|
@@ -4,5 +4,5 @@ import type { Owner } from './owner.js';
|
|
|
4
4
|
export declare function attachDeliverySession(owner: Owner, env?: NodeJS.ProcessEnv, routes?: readonly DeliveryRoute[], signal?: AbortSignal): Promise<DeliverySession | false>;
|
|
5
5
|
/** Replace the supplied route kinds while preserving every other registered route. */
|
|
6
6
|
export declare function attachDeliverySessionRoutes(owner: Owner, env: NodeJS.ProcessEnv | undefined, routes: readonly DeliveryRoute[], signal?: AbortSignal): Promise<DeliverySession | false>;
|
|
7
|
-
export declare function detachDeliverySession(owner: Owner, generation: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
8
|
-
export declare function markDeliverySessionDeleted(owner: Owner, generation: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
7
|
+
export declare function detachDeliverySession(owner: Owner, generation: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<boolean>;
|
|
8
|
+
export declare function markDeliverySessionDeleted(owner: Owner, generation: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<boolean>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DeliveryLedger, deliveryLedgerLocation } from './delivery-ledger.js';
|
|
2
|
-
function openLedger(env) {
|
|
3
|
-
return DeliveryLedger.open({ location: deliveryLedgerLocation(env) });
|
|
2
|
+
function openLedger(env, signal) {
|
|
3
|
+
return DeliveryLedger.open({ location: deliveryLedgerLocation(env), signal });
|
|
4
4
|
}
|
|
5
5
|
/** Rotate the captured session generation; omitted routes retain its registrations. */
|
|
6
6
|
export async function attachDeliverySession(owner, env = process.env, routes, signal) {
|
|
@@ -13,31 +13,31 @@ export async function attachDeliverySessionRoutes(owner, env = process.env, rout
|
|
|
13
13
|
async function attach(owner, env, routes, merge, signal) {
|
|
14
14
|
if (signal?.aborted)
|
|
15
15
|
return false;
|
|
16
|
-
const ledger = await openLedger(env);
|
|
16
|
+
const ledger = await openLedger(env, signal);
|
|
17
17
|
try {
|
|
18
|
-
const previous = await ledger.session(owner);
|
|
18
|
+
const previous = await ledger.session(owner, signal);
|
|
19
19
|
if (signal?.aborted)
|
|
20
20
|
return false;
|
|
21
21
|
const selected = merge && routes ? [...(previous?.routes ?? []).filter(old => !routes.some(route => route.kind === old.kind)), ...routes] : routes ?? previous?.routes ?? [];
|
|
22
|
-
return await ledger.attach({ owner, expectedGeneration: previous?.generation ?? null, routes: selected });
|
|
22
|
+
return await ledger.attach({ owner, expectedGeneration: previous?.generation ?? null, routes: selected }, signal);
|
|
23
23
|
}
|
|
24
24
|
finally {
|
|
25
25
|
await ledger.close();
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
export async function detachDeliverySession(owner, generation, env = process.env) {
|
|
29
|
-
const ledger = await openLedger(env);
|
|
28
|
+
export async function detachDeliverySession(owner, generation, env = process.env, signal) {
|
|
29
|
+
const ledger = await openLedger(env, signal);
|
|
30
30
|
try {
|
|
31
|
-
return await ledger.detach({ owner, generation });
|
|
31
|
+
return await ledger.detach({ owner, generation }, signal);
|
|
32
32
|
}
|
|
33
33
|
finally {
|
|
34
34
|
await ledger.close();
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
export async function markDeliverySessionDeleted(owner, generation, env = process.env) {
|
|
38
|
-
const ledger = await openLedger(env);
|
|
37
|
+
export async function markDeliverySessionDeleted(owner, generation, env = process.env, signal) {
|
|
38
|
+
const ledger = await openLedger(env, signal);
|
|
39
39
|
try {
|
|
40
|
-
return await ledger.markDeleted({ owner, generation });
|
|
40
|
+
return await ledger.markDeleted({ owner, generation }, signal);
|
|
41
41
|
}
|
|
42
42
|
finally {
|
|
43
43
|
await ledger.close();
|
package/dist/sqlite-square.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { type Owner } from './model.js';
|
|
2
2
|
import type { HistoryQuery, HistoryResult, JoinOptions, JoinResult, ObservationOptions, OperationControl, Participant, SquareAtInput, SquareBuildInput, SquareLocation, StatusOptions, StatusResult, ParticipantsResult, RecoveryResult, DiscoveryOptions, InboxOptions, InboxResult, StreamOptions, StreamRecord, Activity, PerceivedActivity, SendInput, SendResult } from './square-facade.js';
|
|
3
3
|
export type * from './square-facade.js';
|
|
4
|
-
export { createSquareResourceOwner } from './artifact.js';
|
|
5
4
|
export { squareClock } from './operation-control.js';
|
|
6
5
|
export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
7
|
-
export {
|
|
6
|
+
export { PERCEIVE_DEFAULT_LIMIT, PERCEIVE_MAX_LIMIT } from './perceive-decisions.js';
|
|
8
7
|
export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
9
8
|
export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
|
|
10
9
|
/** One concrete local resource, including its entry-resolved dependencies and lifetime. */
|
|
@@ -13,14 +12,13 @@ export declare class SqliteSquare {
|
|
|
13
12
|
private readonly artifact;
|
|
14
13
|
private readonly clock;
|
|
15
14
|
private readonly env;
|
|
16
|
-
private readonly closesArtifact;
|
|
17
15
|
private readonly deliveryLocation;
|
|
18
16
|
private constructor();
|
|
19
17
|
private reconcile;
|
|
20
18
|
private flush;
|
|
21
19
|
static at(input: SquareAtInput, control?: OperationControl): Promise<SqliteSquare>;
|
|
22
20
|
static build(input: SquareBuildInput, control?: OperationControl): Promise<SqliteSquare>;
|
|
23
|
-
static inMemory(input: Omit<SquareBuildInput, 'path'
|
|
21
|
+
static inMemory(input: Omit<SquareBuildInput, 'path'>): SqliteSquare;
|
|
24
22
|
/** Join establishes membership and local producer custody in one transaction. */
|
|
25
23
|
private joinSeat;
|
|
26
24
|
private requiredSeat;
|
package/dist/sqlite-square.js
CHANGED
|
@@ -10,11 +10,11 @@ import { validateMetadataMatch } from './delivery-deferral.js';
|
|
|
10
10
|
import { flushSquarePosts, reconcileSquareDelivery, readSquareSessions, ensureInitialDeliverySession } from './square-posting.js';
|
|
11
11
|
import { attentionChange, coreDone, coreListening, decideAct, decideJoin, resolveKnownName, validateDoneBody } from './decisions.js';
|
|
12
12
|
import { WATCH_HEARTBEAT_MS, inSquareCount, presenceAnchor } from './runtime.js';
|
|
13
|
-
import {
|
|
13
|
+
import { perceiveLimit, decidePerceive, observePerceiveSelection, selectPerceivePage } from './perceive-decisions.js';
|
|
14
14
|
import { expressionContext } from './activity-feed.js';
|
|
15
15
|
import { expressHint } from './hints.js';
|
|
16
16
|
import { asSeatRef, participantAuthority, requireJoinName, requireOwner } from './seats.js';
|
|
17
|
-
import { recoveryObservation, entryPresentation, queryStatus, queryParticipants,
|
|
17
|
+
import { recoveryObservation, entryPresentation, queryStatus, queryParticipants, perceivePresence, parseRequiredActivityId, toPublicActivity, toPerceivedActivity, publicActivities, selectHistory, NON_READ, HISTORY_MAX_LIMIT, HISTORY_DEFAULT_LIMIT } from './views.js';
|
|
18
18
|
import { sessionInboxCounts } from './inbox.js';
|
|
19
19
|
import { participantIdentity } from './participant-identity.js';
|
|
20
20
|
import { joinRecoveryCommand } from './presentation.js';
|
|
@@ -23,10 +23,9 @@ import { createArtifactPort, createSquare, diagnoseSquareFile, prepareSquareBuil
|
|
|
23
23
|
import { activityQueryFilter } from './activity-query.js';
|
|
24
24
|
import { discoverSquares } from './list.js';
|
|
25
25
|
import { streamSquare } from './stream.js';
|
|
26
|
-
export { createSquareResourceOwner } from './artifact.js';
|
|
27
26
|
export { squareClock } from './operation-control.js';
|
|
28
27
|
export { formatActivityId, parseActivityId, MAX_IDENTITY_SET_SIZE } from './square-core.js';
|
|
29
|
-
export {
|
|
28
|
+
export { PERCEIVE_DEFAULT_LIMIT, PERCEIVE_MAX_LIMIT } from './perceive-decisions.js';
|
|
30
29
|
export { HISTORY_DEFAULT_LIMIT, HISTORY_MAX_LIMIT } from './views.js';
|
|
31
30
|
export { DEFAULT_LIST_DEPTH, MAX_LIST_DEPTH, DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT } from './list.js';
|
|
32
31
|
function rejectUnsentActivity(operation) {
|
|
@@ -45,32 +44,27 @@ export class SqliteSquare {
|
|
|
45
44
|
artifact;
|
|
46
45
|
clock;
|
|
47
46
|
env;
|
|
48
|
-
closesArtifact;
|
|
49
47
|
deliveryLocation;
|
|
50
|
-
constructor(location, artifact, clock, env,
|
|
48
|
+
constructor(location, artifact, clock, env, deliveryLocation = location) {
|
|
51
49
|
this.location = location;
|
|
52
50
|
this.artifact = artifact;
|
|
53
51
|
this.clock = clock;
|
|
54
52
|
this.env = env;
|
|
55
|
-
this.closesArtifact = closesArtifact;
|
|
56
53
|
this.deliveryLocation = deliveryLocation;
|
|
57
54
|
}
|
|
58
|
-
reconcile() { return reconcileSquareDelivery(this.artifact, this.deliveryLocation, this.env, this.clock); }
|
|
55
|
+
reconcile(signal) { return reconcileSquareDelivery(this.artifact, this.deliveryLocation, this.env, this.clock, signal); }
|
|
59
56
|
// Posting is retryable after commit; a delivery failure cannot make a landed write unsent.
|
|
60
|
-
async flush() { await flushSquarePosts(this.artifact, this.deliveryLocation, this.env, this.clock).catch(() => undefined); }
|
|
57
|
+
async flush(signal) { await flushSquarePosts(this.artifact, this.deliveryLocation, this.env, this.clock, signal).catch(() => undefined); }
|
|
61
58
|
static async at(input, control) {
|
|
62
59
|
const env = { ...(input.env ?? process.env) }, location = squareLocation({ ...input, env });
|
|
63
60
|
const clock = squareClock(env, input.clock);
|
|
64
|
-
|
|
65
|
-
const borrowed = input.resources?.artifact(location);
|
|
66
|
-
const artifact = borrowed ?? createArtifactPort(location, undefined, control?.signal);
|
|
61
|
+
const artifact = createArtifactPort(location, undefined, control?.signal);
|
|
67
62
|
try {
|
|
68
63
|
await artifact.read(facts => facts.header(), control?.signal);
|
|
69
|
-
return new SqliteSquare(location, artifact, clock, env,
|
|
64
|
+
return new SqliteSquare(location, artifact, clock, env, await canonicalPath(location));
|
|
70
65
|
}
|
|
71
66
|
catch (error) {
|
|
72
|
-
|
|
73
|
-
await artifact.close();
|
|
67
|
+
await artifact.close();
|
|
74
68
|
if (error instanceof InternalSquareError && error.code === 'not_found')
|
|
75
69
|
throw new SquareError('unavailable', `Square is unavailable at ${location}`);
|
|
76
70
|
throw error;
|
|
@@ -156,7 +150,7 @@ export class SqliteSquare {
|
|
|
156
150
|
async withHostPresence(select, control) {
|
|
157
151
|
const seats = (await this.artifact.read(facts => facts.roster().filter(person => person.joined)
|
|
158
152
|
.flatMap(person => { const seat = facts.seat(person.name); return seat === undefined ? [] : [seat]; }), control?.signal)).value;
|
|
159
|
-
const sessions = await readSquareSessions(seats, this.env);
|
|
153
|
+
const sessions = await readSquareSessions(seats, this.env, control?.signal);
|
|
160
154
|
const captured = new Map(seats.map(seat => [nameKey(seat.participant), seat]));
|
|
161
155
|
return (await this.artifact.read(facts => {
|
|
162
156
|
const seat = (name) => {
|
|
@@ -197,14 +191,14 @@ export class SqliteSquare {
|
|
|
197
191
|
let landedId;
|
|
198
192
|
try {
|
|
199
193
|
checkControl(control);
|
|
200
|
-
await square.reconcile();
|
|
194
|
+
await square.reconcile(control?.signal);
|
|
201
195
|
name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
202
196
|
const seat = this.seat;
|
|
203
197
|
const context = !options.observe ? undefined : (await square.artifact.read(facts => expressionContext(facts, name), control?.signal)).value;
|
|
204
198
|
while (true) {
|
|
205
199
|
checkControl(control);
|
|
206
200
|
try {
|
|
207
|
-
await square.reconcile();
|
|
201
|
+
await square.reconcile(control?.signal);
|
|
208
202
|
effect = 'unknown';
|
|
209
203
|
const now = square.clock();
|
|
210
204
|
const reply = rejectUnsentActivity(() => {
|
|
@@ -240,7 +234,7 @@ export class SqliteSquare {
|
|
|
240
234
|
}, control?.signal)).value;
|
|
241
235
|
landedId = formatActivityId(committed.stored.index);
|
|
242
236
|
effect = 'landed';
|
|
243
|
-
await square.flush();
|
|
237
|
+
await square.flush(control?.signal);
|
|
244
238
|
const { stored, standingCount, ownActivityCount, hardCap, ...metadata } = committed;
|
|
245
239
|
const hint = expressHint(ownActivityCount);
|
|
246
240
|
return { activity: toPublicActivity(stored), ...metadata,
|
|
@@ -267,7 +261,7 @@ export class SqliteSquare {
|
|
|
267
261
|
async listener(verb, target, options, control) {
|
|
268
262
|
const square = this.owner;
|
|
269
263
|
checkControl(control);
|
|
270
|
-
await square.reconcile();
|
|
264
|
+
await square.reconcile(control?.signal);
|
|
271
265
|
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
272
266
|
const seat = this.seat, now = square.clock();
|
|
273
267
|
const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
@@ -278,7 +272,7 @@ export class SqliteSquare {
|
|
|
278
272
|
target: activity?.target ?? target,
|
|
279
273
|
} }) };
|
|
280
274
|
}, control?.signal)).value;
|
|
281
|
-
await square.flush();
|
|
275
|
+
await square.flush(control?.signal);
|
|
282
276
|
return result;
|
|
283
277
|
}
|
|
284
278
|
listen(target, options = {}, control) { return this.listener('listen', target, options, control); }
|
|
@@ -292,27 +286,27 @@ export class SqliteSquare {
|
|
|
292
286
|
}, control?.signal)).value;
|
|
293
287
|
}
|
|
294
288
|
/** Receipt selection rechecks the lease inside the writer, not at its last heartbeat. */
|
|
295
|
-
async
|
|
296
|
-
await this.owner.reconcile();
|
|
289
|
+
async perceivePage(name, seat, options, control, leaseId) {
|
|
290
|
+
await this.owner.reconcile(control?.signal);
|
|
297
291
|
return (await this.owner.artifact.transactAs(participantAuthority(seat), facts => {
|
|
298
292
|
if (leaseId !== undefined && facts.lease(name)?.leaseId !== leaseId)
|
|
299
293
|
return null;
|
|
300
|
-
return
|
|
294
|
+
return decidePerceive(facts, name, options, this.owner.clock());
|
|
301
295
|
}, control?.signal)).value;
|
|
302
296
|
}
|
|
303
|
-
async
|
|
297
|
+
async perceive(options = {}, control) {
|
|
304
298
|
const square = this.owner;
|
|
305
299
|
let name = this.name, subject = 'caller';
|
|
306
300
|
let leaseId, seat;
|
|
307
301
|
let selection = options;
|
|
308
302
|
const idle = options.idle ?? 0;
|
|
309
|
-
const complete = async (
|
|
310
|
-
const view =
|
|
303
|
+
const complete = async (perceived, reason = 'drained') => {
|
|
304
|
+
const view = perceived ?? (await square.artifact.read(facts => observePerceiveSelection(facts, name, [], {
|
|
311
305
|
unseenOnly: true, senders: selection.from, ...(selection.mention === true ? { attentionOnly: true } : {}),
|
|
312
306
|
}, square.clock()))).value;
|
|
313
307
|
if (reason === 'drained')
|
|
314
308
|
reason = view.terminalStatus ?? reason;
|
|
315
|
-
await square.flush();
|
|
309
|
+
await square.flush(control?.signal);
|
|
316
310
|
if (leaseId !== undefined) {
|
|
317
311
|
await releaseWatchLease(square.artifact, seat, leaseId).catch(() => undefined);
|
|
318
312
|
leaseId = undefined;
|
|
@@ -321,7 +315,7 @@ export class SqliteSquare {
|
|
|
321
315
|
if (options.observe) {
|
|
322
316
|
try {
|
|
323
317
|
const observed = await square.withHostPresence((facts, available) => {
|
|
324
|
-
const data =
|
|
318
|
+
const data = perceivePresence(facts, square.clock());
|
|
325
319
|
return { presence: { ...data, participants: data.participants.map(person => ({ ...person, deliveryAvailable: available(person.name) })) },
|
|
326
320
|
deliveryAvailable: available(view.participant) };
|
|
327
321
|
}, control);
|
|
@@ -340,15 +334,15 @@ export class SqliteSquare {
|
|
|
340
334
|
};
|
|
341
335
|
try {
|
|
342
336
|
checkControl(control);
|
|
343
|
-
await square.reconcile();
|
|
337
|
+
await square.reconcile(control?.signal);
|
|
344
338
|
validateName(name);
|
|
345
339
|
if (!Number.isFinite(idle) || idle < 0)
|
|
346
|
-
throw new SquareError('invalid_args', '
|
|
340
|
+
throw new SquareError('invalid_args', 'Perceive idle duration must be a non-negative number');
|
|
347
341
|
if (options.id !== undefined && ['idle', 'from', 'mention', 'limit', 'replace'].some(key => Object.hasOwn(options, key)))
|
|
348
|
-
throw new SquareError('invalid_args', '
|
|
342
|
+
throw new SquareError('invalid_args', 'Perceive id cannot be combined with idle, from, mention, limit or replace');
|
|
349
343
|
if (Object.hasOwn(options, 'replace') && idle === 0)
|
|
350
|
-
throw new SquareError('invalid_args', '
|
|
351
|
-
|
|
344
|
+
throw new SquareError('invalid_args', 'Perceive replacement requires a positive idle duration');
|
|
345
|
+
perceiveLimit(options.limit);
|
|
352
346
|
const probe = (await square.artifact.read(facts => {
|
|
353
347
|
name = resolveKnownName(facts, this.name);
|
|
354
348
|
seat = this.seat;
|
|
@@ -359,11 +353,11 @@ export class SqliteSquare {
|
|
|
359
353
|
subject = 'caller';
|
|
360
354
|
if (idle !== 0 || options.id !== undefined)
|
|
361
355
|
return undefined;
|
|
362
|
-
const page =
|
|
363
|
-
return page.indexes.length === 0 ?
|
|
356
|
+
const page = selectPerceivePage(facts, name, selection);
|
|
357
|
+
return page.indexes.length === 0 ? observePerceiveSelection(facts, page.participant, [], page.filter, square.clock()) : undefined;
|
|
364
358
|
}, control?.signal)).value;
|
|
365
359
|
if (idle === 0) {
|
|
366
|
-
const data = probe ?? (await this.
|
|
360
|
+
const data = probe ?? (await this.perceivePage(name, seat, selection, control));
|
|
367
361
|
return await complete(data);
|
|
368
362
|
}
|
|
369
363
|
const watch = {
|
|
@@ -374,7 +368,7 @@ export class SqliteSquare {
|
|
|
374
368
|
if (start.type === 'active')
|
|
375
369
|
return await complete(undefined, 'already-active');
|
|
376
370
|
leaseId = start.leaseId;
|
|
377
|
-
control?.onProgress?.({ kind: '
|
|
371
|
+
control?.onProgress?.({ kind: 'perceive-started', participant: name, idleMs: idle, replacementRequested: options.replace ?? false, replaced: start.replaced });
|
|
378
372
|
const quietSince = square.clock();
|
|
379
373
|
let nextHeartbeat = start.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
380
374
|
while (true) {
|
|
@@ -388,11 +382,11 @@ export class SqliteSquare {
|
|
|
388
382
|
return await complete(undefined, pulse.status);
|
|
389
383
|
if (pulse.heartbeatAt !== undefined)
|
|
390
384
|
nextHeartbeat = pulse.heartbeatAt + WATCH_HEARTBEAT_MS;
|
|
391
|
-
const
|
|
392
|
-
if (
|
|
385
|
+
const perceived = await this.perceivePage(name, seat, selection, control, leaseId);
|
|
386
|
+
if (perceived === null)
|
|
393
387
|
return await complete(undefined, 'replaced');
|
|
394
|
-
if (
|
|
395
|
-
return await complete(
|
|
388
|
+
if (perceived.activities.length > 0)
|
|
389
|
+
return await complete(perceived);
|
|
396
390
|
if (square.clock() - quietSince >= idle) {
|
|
397
391
|
const owns = await ownsWatchLease(square.artifact, seat, square.clock(), leaseId, control?.signal);
|
|
398
392
|
return await complete(undefined, owns ? 'idle' : 'replaced');
|
|
@@ -402,7 +396,7 @@ export class SqliteSquare {
|
|
|
402
396
|
}
|
|
403
397
|
}
|
|
404
398
|
catch (error) {
|
|
405
|
-
throw failure(error, { operation: '
|
|
399
|
+
throw failure(error, { operation: 'perceive', subject, participant: name });
|
|
406
400
|
}
|
|
407
401
|
finally {
|
|
408
402
|
// Cancellation releases only this captured generation and this lease.
|
|
@@ -459,7 +453,7 @@ export class SqliteSquare {
|
|
|
459
453
|
validateDoneBody(body);
|
|
460
454
|
checkControl(control);
|
|
461
455
|
const square = this.owner;
|
|
462
|
-
await square.reconcile();
|
|
456
|
+
await square.reconcile(control?.signal);
|
|
463
457
|
const name = (await square.artifact.read(facts => resolveKnownName(facts, this.name), control?.signal)).value;
|
|
464
458
|
const seat = this.seat, now = square.clock();
|
|
465
459
|
const result = (await square.artifact.transactAs(participantAuthority(seat), facts => {
|
|
@@ -467,15 +461,15 @@ export class SqliteSquare {
|
|
|
467
461
|
return { activity: toPublicActivity(stored), ...(!options.observe ? {} : { observation: square.observation(inSquareCount(facts), name, now),
|
|
468
462
|
}) };
|
|
469
463
|
}, control?.signal)).value;
|
|
470
|
-
await square.flush();
|
|
464
|
+
await square.flush(control?.signal);
|
|
471
465
|
return result;
|
|
472
466
|
}
|
|
473
467
|
};
|
|
474
468
|
async send(input, control) {
|
|
475
469
|
checkControl(control);
|
|
476
|
-
await this.reconcile();
|
|
470
|
+
await this.reconcile(control?.signal);
|
|
477
471
|
const result = (await this.artifact.transactAs({ kind: 'send', actor: input.from }, facts => facts.insertNotice(input, this.clock()), control?.signal)).value;
|
|
478
|
-
await this.flush();
|
|
472
|
+
await this.flush(control?.signal);
|
|
479
473
|
return result;
|
|
480
474
|
}
|
|
481
475
|
async participant(name, options) {
|
|
@@ -489,11 +483,11 @@ export class SqliteSquare {
|
|
|
489
483
|
const last = options.last ?? HISTORY_DEFAULT_LIMIT;
|
|
490
484
|
if (!Number.isSafeInteger(last) || last < 1 || last > HISTORY_MAX_LIMIT)
|
|
491
485
|
throw new SquareError('invalid_args', 'Join history limit must be between 1 and 100');
|
|
492
|
-
if (!await ensureInitialDeliverySession(requireOwner(options.owner), this.env, options.wakeAddress))
|
|
486
|
+
if (!await ensureInitialDeliverySession(requireOwner(options.owner), this.env, options.wakeAddress, control?.signal))
|
|
493
487
|
throw new SquareError('already_joined', `${participantIdentity(name)} is away; resume the host session first`, { reason: 'needs_seat' });
|
|
494
|
-
await this.reconcile();
|
|
488
|
+
await this.reconcile(control?.signal);
|
|
495
489
|
const committed = await this.joinSeat(name, options, control);
|
|
496
|
-
await this.flush();
|
|
490
|
+
await this.flush(control?.signal);
|
|
497
491
|
const participant = this.handle(committed.seat);
|
|
498
492
|
const result = { participant, activity: committed.activity, kind: committed.kind,
|
|
499
493
|
...(committed.observation === undefined ? {} : { observation: committed.observation }) };
|
|
@@ -579,14 +573,13 @@ export class SqliteSquare {
|
|
|
579
573
|
.filter(seat => facts.participant(seat.participant)?.joined && seat.attachment === 'attached')
|
|
580
574
|
.map(seat => ({ name: seat.participant, generation: seat.generation })))).value;
|
|
581
575
|
}
|
|
582
|
-
close() { return this.
|
|
576
|
+
close() { return this.artifact.close(); }
|
|
583
577
|
}
|
|
584
578
|
export function diagnoseSquare(input, control) { checkControl(control); return diagnoseSquareFile(squareLocation(input)); }
|
|
585
579
|
export function discover(input, options = {}, control) {
|
|
586
|
-
if (input.path !== undefined
|
|
580
|
+
if (input.path !== undefined)
|
|
587
581
|
squareLocation(input);
|
|
588
|
-
|
|
589
|
-
return discoverSquares(input.cwd ?? process.cwd(), options, control, { clock: squareClock(env, input.clock) });
|
|
582
|
+
return discoverSquares(input.cwd ?? process.cwd(), options, control);
|
|
590
583
|
}
|
|
591
584
|
export async function inbox(input, owner, options = {}, control) {
|
|
592
585
|
checkControl(control);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
/** Yield rather than blocking the process while another connection owns SQLite. */
|
|
3
3
|
export declare function retrySqliteBusy<T>(operation: () => T, signal?: AbortSignal, retryMs?: number | (() => number)): Promise<T>;
|
|
4
|
+
/** Cancel a queued caller promptly without releasing its place in the connection's serial chain. */
|
|
5
|
+
export declare function awaitSqliteQueue<T>(result: Promise<T>, signal: AbortSignal | undefined, started: () => boolean): Promise<T>;
|
|
4
6
|
/** Acquisition may retry; COMMIT retries retain the callback's result and transaction. */
|
|
5
7
|
export declare function inSqliteTransaction<T, V>(db: DatabaseSync, write: boolean, validate: () => V, change: (validated: V) => T, signal?: AbortSignal): Promise<T>;
|