@astrosheep/square 0.3.93 → 0.3.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activity-feed.d.ts +3 -5
- package/dist/activity-feed.js +16 -14
- package/dist/artifact.js +3 -3
- package/dist/attention.d.ts +19 -0
- package/dist/attention.js +30 -0
- package/dist/boundary-presentation.d.ts +2 -2
- package/dist/boundary-presentation.js +33 -40
- package/dist/catch-decisions.d.ts +5 -8
- package/dist/catch-decisions.js +45 -25
- package/dist/claude-delivery-status.js +10 -4
- package/dist/claude-delivery.js +19 -11
- package/dist/claude-mod.js +24 -53
- package/dist/cli/context.d.ts +9 -4
- package/dist/cli/context.js +30 -27
- package/dist/cli/express-presentation.d.ts +3 -1
- package/dist/cli/express-presentation.js +28 -16
- package/dist/cli/json.d.ts +3 -0
- package/dist/cli/json.js +14 -0
- package/dist/cli/mcp-server-command.js +2 -2
- package/dist/cli/observation-commands.d.ts +1 -3
- package/dist/cli/observation-commands.js +43 -56
- package/dist/cli/program.js +3 -3
- package/dist/cli/registry.js +7 -6
- package/dist/cli/square-commands.d.ts +0 -2
- package/dist/cli/square-commands.js +30 -44
- package/dist/codex-hook.d.ts +3 -2
- package/dist/codex-hook.js +13 -4
- package/dist/codex-queue.js +4 -4
- package/dist/decisions.d.ts +5 -16
- package/dist/decisions.js +9 -33
- package/dist/delivery-attempts.d.ts +52 -0
- package/dist/delivery-attempts.js +73 -0
- package/dist/delivery-deferral.js +2 -2
- package/dist/delivery-effects.d.ts +139 -0
- package/dist/delivery-effects.js +163 -0
- package/dist/delivery-health.d.ts +0 -1
- package/dist/delivery-health.js +1 -3
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +76 -101
- package/dist/delivery-primitives.d.ts +5 -0
- package/dist/delivery-primitives.js +3 -0
- package/dist/delivery.d.ts +1 -1
- package/dist/delivery.js +7 -6
- package/dist/help.js +14 -14
- package/dist/http-delivery.d.ts +16 -0
- package/dist/http-delivery.js +101 -0
- package/dist/inbox.d.ts +9 -22
- package/dist/inbox.js +23 -27
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.js +23 -24
- package/dist/model.d.ts +12 -21
- package/dist/model.js +5 -8
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +8 -5
- package/dist/operation-presentation.d.ts +6 -6
- package/dist/operation-presentation.js +55 -58
- package/dist/owner.d.ts +11 -0
- package/dist/owner.js +32 -0
- package/dist/paseo-delivery.d.ts +4 -1
- package/dist/paseo-delivery.js +7 -12
- package/dist/paseo-state.d.ts +2 -2
- package/dist/paseo-state.js +6 -4
- package/dist/plugins/opencode/delivery.js +27 -15
- package/dist/ports.d.ts +18 -47
- package/dist/presentation-operations.d.ts +14 -19
- package/dist/presentation-operations.js +51 -61
- package/dist/presentation.d.ts +7 -17
- package/dist/presentation.js +46 -91
- package/dist/receive.d.ts +66 -0
- package/dist/receive.js +54 -0
- package/dist/runtime.d.ts +1 -8
- package/dist/runtime.js +2 -17
- package/dist/seats.d.ts +2 -3
- package/dist/seats.js +7 -15
- package/dist/session-lifecycle.d.ts +6 -2
- package/dist/session-lifecycle.js +39 -11
- package/dist/sqlite-square.d.ts +36 -23
- package/dist/sqlite-square.js +162 -211
- package/dist/square-api.d.ts +16 -11
- package/dist/square-api.js +27 -23
- package/dist/square-core.d.ts +0 -14
- package/dist/square-core.js +0 -9
- package/dist/square-domain.d.ts +4 -2
- package/dist/square-domain.js +65 -52
- package/dist/square-facade.d.ts +47 -105
- package/dist/square-index.d.ts +5 -13
- package/dist/square-index.js +13 -38
- package/dist/square-legacy-framing.d.ts +2 -0
- package/dist/square-legacy-framing.js +8 -0
- package/dist/square-location.d.ts +1 -14
- package/dist/square-location.js +4 -70
- package/dist/square-migration-host-ledger.d.ts +2 -2
- package/dist/square-migration-host-ledger.js +6 -5
- package/dist/square-migration.d.ts +4 -1
- package/dist/square-migration.js +93 -38
- package/dist/square-receive.d.ts +6 -0
- package/dist/square-receive.js +18 -0
- package/dist/square-sqlite.d.ts +9 -3
- package/dist/square-sqlite.js +139 -114
- package/dist/square-tools.d.ts +7 -10
- package/dist/square-tools.js +21 -61
- package/dist/stream.js +4 -5
- package/dist/views.d.ts +23 -13
- package/dist/views.js +47 -53
- package/dist/wakes.d.ts +1 -5
- package/dist/wakes.js +4 -6
- package/examples/symmetric-domains.mjs +250 -0
- package/guides/application-facade-delivery-evidence.md +6 -6
- package/guides/attention-primitive.md +32 -0
- package/guides/change-waits.md +2 -2
- package/guides/claude-native.md +18 -9
- package/guides/codex-delivery.md +17 -6
- package/guides/delivery-primitives.md +180 -0
- package/guides/mcp-connection-lifetime.md +1 -1
- package/guides/mcp.md +3 -3
- package/guides/migration.md +148 -151
- package/guides/operation-cancellation.md +1 -1
- package/guides/participant-ownership.md +78 -47
- package/guides/pi-tools.md +34 -5
- package/guides/primitive-catch-consolidation.md +5 -5
- package/guides/primitive-history-consolidation.md +2 -2
- package/guides/primitive-status-consolidation.md +3 -3
- package/guides/primitive-storage-consolidation.md +1 -1
- package/guides/receive-primitive.md +90 -0
- package/guides/row-runtime-delivery-evidence.md +1 -1
- package/guides/row-storage-design.md +22 -28
- package/guides/square-api.md +314 -91
- package/guides/symmetric-domains.md +92 -0
- package/package.json +14 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/skills/square/SKILL.md +2 -6
- package/plugins/codex/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +27 -17
- package/plugins/paseo/client/palette.ts +18 -18
- package/plugins/paseo/client/square-message.tsx +17 -23
- package/plugins/pi/square-pi.js +17 -17
- package/skills/square/SKILL.md +2 -6
- package/dist/square-backend.d.ts +0 -13
- package/dist/square-backend.js +0 -1
- package/dist/square-stdio.d.ts +0 -22
- package/dist/square-stdio.js +0 -235
- package/dist/stdio-server.d.ts +0 -3
- package/dist/stdio-server.js +0 -212
- package/guides/backends.md +0 -193
package/dist/paseo-delivery.js
CHANGED
|
@@ -17,28 +17,30 @@ function diagnostic(phase, address, code) {
|
|
|
17
17
|
}
|
|
18
18
|
export class PaseoWakeTransport {
|
|
19
19
|
opts;
|
|
20
|
+
discovering;
|
|
20
21
|
constructor(opts = {}) {
|
|
21
22
|
this.opts = opts;
|
|
22
23
|
}
|
|
24
|
+
/** Coalesce overlapping inventory reads only; a later send must rediscover current status. */
|
|
25
|
+
discover(timeoutMs) {
|
|
26
|
+
return this.discovering ??= Promise.resolve().then(() => (this.opts.discover ?? discoverPaseoAgents)(timeoutMs))
|
|
27
|
+
.finally(() => { this.discovering = undefined; });
|
|
28
|
+
}
|
|
23
29
|
async attempt(request, timeoutMs, beforeSend) {
|
|
24
30
|
const address = request.route.address;
|
|
25
31
|
const deadline = Date.now() + timeoutMs;
|
|
26
32
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
27
33
|
const budgetUnavailable = () => ({
|
|
28
34
|
outcome: 'failed',
|
|
29
|
-
unavailable: true,
|
|
30
35
|
signature: 'dispatch_budget_exhausted',
|
|
31
36
|
message: 'The wake dispatch budget elapsed before Paseo accepted the wake.',
|
|
32
|
-
retainRoute: true,
|
|
33
37
|
});
|
|
34
38
|
const agentId = address.agentId?.trim();
|
|
35
39
|
if (!agentId) {
|
|
36
40
|
return {
|
|
37
41
|
outcome: 'failed',
|
|
38
|
-
unavailable: true,
|
|
39
42
|
signature: 'invalid_address',
|
|
40
43
|
message: 'Paseo route has no agent id.',
|
|
41
|
-
routeStale: true,
|
|
42
44
|
diagnostic: diagnostic('selection', address, 'invalid_address'),
|
|
43
45
|
};
|
|
44
46
|
}
|
|
@@ -49,30 +51,25 @@ export class PaseoWakeTransport {
|
|
|
49
51
|
let remaining = remainingMs();
|
|
50
52
|
if (remaining === 0)
|
|
51
53
|
return budgetUnavailable();
|
|
52
|
-
const discovery =
|
|
54
|
+
const discovery = await this.discover(remaining);
|
|
53
55
|
if (discovery.error && discovery.agents.length === 0) {
|
|
54
56
|
return {
|
|
55
57
|
outcome: 'failed',
|
|
56
|
-
unavailable: true,
|
|
57
58
|
// A credential or authorization refusal is proven; anything else may be transient.
|
|
58
59
|
signature: /password|auth|unauthori[sz]ed/i.test(discovery.error) ? 'discovery_rejected'
|
|
59
60
|
: /DAEMON_NOT_RUNNING|ECONNREFUSED|ENOENT|not found.*executable|ETIMEDOUT|timed out|timeout/i.test(discovery.error)
|
|
60
61
|
? 'discovery_transient' : 'discovery_rejected',
|
|
61
62
|
message: `Paseo unavailable: ${discovery.error}`,
|
|
62
63
|
diagnostic: diagnostic('discovery', address, 'unavailable'),
|
|
63
|
-
retainRoute: true,
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
const agent = discovery.agents.find((candidate) => candidate.id === agentId);
|
|
67
67
|
if (agent === undefined || agent.status !== 'idle') {
|
|
68
68
|
return {
|
|
69
69
|
outcome: 'failed',
|
|
70
|
-
unavailable: true,
|
|
71
70
|
signature: agent === undefined ? 'address_not_found' : 'agent_not_idle',
|
|
72
71
|
message: agent === undefined ? 'The registered Paseo agent was not found.' : 'The registered Paseo agent is not idle.',
|
|
73
72
|
diagnostic: diagnostic('selection', address, agent === undefined ? 'not_found' : 'not_idle'),
|
|
74
|
-
...(agent === undefined ? {} : { retainRoute: true }),
|
|
75
|
-
...(agent === undefined ? { routeStale: true } : {}),
|
|
76
73
|
};
|
|
77
74
|
}
|
|
78
75
|
remaining = remainingMs();
|
|
@@ -81,11 +78,9 @@ export class PaseoWakeTransport {
|
|
|
81
78
|
if (!(await (this.opts.waitForBoundary ?? waitForPaseoWakeBoundary)(agent, remaining))) {
|
|
82
79
|
return {
|
|
83
80
|
outcome: 'failed',
|
|
84
|
-
unavailable: true,
|
|
85
81
|
signature: 'boundary_unavailable',
|
|
86
82
|
message: 'Paseo did not reach the current tool boundary before the wake timeout.',
|
|
87
83
|
diagnostic: diagnostic('boundary', address, 'unavailable'),
|
|
88
|
-
retainRoute: true,
|
|
89
84
|
};
|
|
90
85
|
}
|
|
91
86
|
if (!(await beforeSend()))
|
package/dist/paseo-state.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export interface PaseoAgent {
|
|
|
7
7
|
export declare function discoverPaseoAgents(timeoutMs?: number, opts?: {
|
|
8
8
|
args?: string[];
|
|
9
9
|
bin?: string;
|
|
10
|
-
}): {
|
|
10
|
+
}): Promise<{
|
|
11
11
|
agents: PaseoAgent[];
|
|
12
12
|
error?: string;
|
|
13
|
-
}
|
|
13
|
+
}>;
|
|
14
14
|
export declare function waitForPaseoWakeBoundary(agent: Pick<PaseoAgent, 'id' | 'status'>, timeoutMs?: number): Promise<boolean>;
|
package/dist/paseo-state.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
2
3
|
import { waitForPaseoToolBoundary } from './paseo-timeline.js';
|
|
3
|
-
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
export async function discoverPaseoAgents(timeoutMs = 5000, opts = {}) {
|
|
4
6
|
try {
|
|
5
|
-
const raw =
|
|
7
|
+
const { stdout: raw } = await execFileAsync(opts.bin ?? process.env.SQUARE_PASEO_BIN ?? 'paseo', [...(opts.args ?? []), 'ls', '--global', '--json'], {
|
|
6
8
|
encoding: 'utf8',
|
|
7
9
|
timeout: timeoutMs,
|
|
8
|
-
|
|
10
|
+
maxBuffer: 1024 * 1024,
|
|
9
11
|
});
|
|
10
12
|
const parsed = JSON.parse(raw);
|
|
11
13
|
const agents = Array.isArray(parsed) ? parsed : parsed?.agents;
|
|
@@ -3,39 +3,48 @@ import { createArtifactPort } from '../../artifact.js';
|
|
|
3
3
|
import { deliverPending } from '../../delivery-operations.js';
|
|
4
4
|
import { nativePendingPreview, observeSessionPending, projectSessionSeats } from '../../inbox.js';
|
|
5
5
|
import { automaticWakeEligible, presentationBlocksWake } from '../../delivery.js';
|
|
6
|
-
import {
|
|
6
|
+
import { harnessOwner, decodeHarnessOwner } from '../../owner.js';
|
|
7
7
|
import { formatActivityId, parseActivityId } from '../../square-core.js';
|
|
8
8
|
import { indexedSquareEntries } from '../../square-index.js';
|
|
9
9
|
import { asSeatRef } from '../../seats.js';
|
|
10
|
+
import { restoreSessionSeats } from '../../session-lifecycle.js';
|
|
10
11
|
import { presentPending } from '../../presentation-operations.js';
|
|
11
12
|
import { createNativeInputId, sendNativeText } from '../../packages/agent-delivery/src/opencode-native.js';
|
|
12
13
|
const routeKind = 'opencode-server';
|
|
13
|
-
const
|
|
14
|
+
const ownerFor = (sessionId) => harnessOwner({ channel: 'opencode', session: sessionId });
|
|
14
15
|
const payloadHash = (payload) => createHash('sha256').update(payload).digest('hex');
|
|
15
|
-
function preview(facts, location,
|
|
16
|
+
function preview(facts, location, captured, index) {
|
|
17
|
+
const seat = facts.seat(captured.participant);
|
|
18
|
+
if (seat === undefined || seat.attachment !== 'attached' || seat.generation !== captured.generation || seat.owner !== captured.owner)
|
|
19
|
+
return undefined;
|
|
20
|
+
const wake = seat.wakeAddress;
|
|
21
|
+
if (wake?.kind !== routeKind)
|
|
22
|
+
return undefined;
|
|
16
23
|
return nativePendingPreview(facts, {
|
|
17
|
-
location, routeKind,
|
|
18
|
-
actIndex: index, address:
|
|
24
|
+
location, routeKind, generation: seat.generation, participant: seat.participant,
|
|
25
|
+
actIndex: index, address: wake.address, now: Date.now(),
|
|
19
26
|
});
|
|
20
27
|
}
|
|
21
28
|
export async function indexedOpenCodeSessions(env) {
|
|
22
|
-
return [...new Set((await indexedSquareEntries({
|
|
29
|
+
return [...new Set((await indexedSquareEntries({}, env)).flatMap((entry) => {
|
|
30
|
+
const coordinate = decodeHarnessOwner(entry.owner);
|
|
31
|
+
return coordinate?.channel === 'opencode' ? [coordinate.session] : [];
|
|
32
|
+
}))];
|
|
23
33
|
}
|
|
24
34
|
export async function discoverOpenCodeSeats(sessionId, env, signal) {
|
|
25
|
-
return projectSessionSeats({
|
|
35
|
+
return projectSessionSeats({ owner: ownerFor(sessionId), env }, signal);
|
|
26
36
|
}
|
|
27
37
|
function sameSeat(left, right) {
|
|
28
|
-
return left !== undefined && left.generation === right.generation &&
|
|
38
|
+
return left !== undefined && left.attachment === 'attached' && left.generation === right.generation && left.owner === right.owner;
|
|
29
39
|
}
|
|
30
40
|
function createOpenCodeWakeTransport(target, lifetime) {
|
|
31
41
|
return {
|
|
32
42
|
probe: async (route) => !lifetime.aborted && route.kind === routeKind
|
|
33
|
-
&& route.
|
|
43
|
+
&& route.address.sessionId === target.sessionId,
|
|
34
44
|
async attempt(request, timeoutMs, beforeSend) {
|
|
35
45
|
const index = parseActivityId(request.activity);
|
|
36
46
|
const artifact = request.artifact;
|
|
37
47
|
if (artifact === undefined || request.claimToken === undefined || index === undefined
|
|
38
|
-
|| !sameBody(request.seat.body, bodyFor(target.sessionId))
|
|
39
48
|
|| request.route.kind !== routeKind || request.route.address.sessionId !== target.sessionId) {
|
|
40
49
|
return { outcome: 'not-capable', diagnostic: 'OpenCode attention has no matching artifact claim or session.' };
|
|
41
50
|
}
|
|
@@ -61,7 +70,7 @@ function createOpenCodeWakeTransport(target, lifetime) {
|
|
|
61
70
|
if (result.state === 'accepted')
|
|
62
71
|
return { outcome: 'accepted' };
|
|
63
72
|
if (result.state === 'unavailable')
|
|
64
|
-
return { outcome: 'failed',
|
|
73
|
+
return { outcome: 'failed', signature: result.code };
|
|
65
74
|
return { outcome: 'unknown', signature: result.code };
|
|
66
75
|
},
|
|
67
76
|
};
|
|
@@ -126,7 +135,7 @@ export async function observeOpenCodeContext(sessionId, messages, env, signal) {
|
|
|
126
135
|
}
|
|
127
136
|
/** The native receiver owns no claims outside the artifact; HOME is used only for discovery. */
|
|
128
137
|
export async function receiveOpenCodePending(target, env, signal) {
|
|
129
|
-
const observer = await observeSessionPending(
|
|
138
|
+
const observer = await observeSessionPending(ownerFor(target.sessionId), env);
|
|
130
139
|
const transport = createOpenCodeWakeTransport(target, signal);
|
|
131
140
|
let waiting;
|
|
132
141
|
try {
|
|
@@ -181,7 +190,8 @@ export async function receiveOpenCodePending(target, env, signal) {
|
|
|
181
190
|
}
|
|
182
191
|
/** Publish the native address into current artifact seats; the legacy route projection is not read. */
|
|
183
192
|
export async function publishOpenCodeAddress(sessionId, env, signal) {
|
|
184
|
-
|
|
193
|
+
await restoreSessionSeats(ownerFor(sessionId), env, { kind: routeKind, address: { sessionId } }, signal);
|
|
194
|
+
const seats = (await discoverOpenCodeSeats(sessionId, env, signal)).filter(({ seat }) => seat.attachment === 'attached');
|
|
185
195
|
for (const { location, seat } of seats) {
|
|
186
196
|
if (signal.aborted)
|
|
187
197
|
return false;
|
|
@@ -199,6 +209,8 @@ export async function cancelOpenCodeDeliveries(sessionId, env, signal) {
|
|
|
199
209
|
for (const { location, seat } of await discoverOpenCodeSeats(sessionId, env, signal)) {
|
|
200
210
|
if (signal.aborted)
|
|
201
211
|
return;
|
|
212
|
+
if (seat.attachment !== 'attached')
|
|
213
|
+
continue;
|
|
202
214
|
const artifact = createArtifactPort(location, undefined, signal);
|
|
203
215
|
try {
|
|
204
216
|
const { value: through } = await artifact.read((facts) => facts.header().lastActIndex, signal);
|
|
@@ -215,9 +227,9 @@ export async function endOpenCodeSession(sessionId, env, signal) {
|
|
|
215
227
|
if (signal.aborted)
|
|
216
228
|
return;
|
|
217
229
|
const { Square } = await import('../../square-api.js');
|
|
218
|
-
const square = await Square.at({ path: location, env
|
|
230
|
+
const square = await Square.at({ path: location, env });
|
|
219
231
|
try {
|
|
220
|
-
await square.
|
|
232
|
+
await square.endHeld(seat.participant, { owner: seat.owner, generation: seat.generation });
|
|
221
233
|
}
|
|
222
234
|
finally {
|
|
223
235
|
await square.close();
|
package/dist/ports.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Act, ActivityId,
|
|
1
|
+
import type { Act, ActivityId, Owner, HardCap, Seat, StoredAct, WakeAddress, WakeRoute, WatchLease } from './model.js';
|
|
2
|
+
import type { AttemptOutcome } from './delivery-attempts.js';
|
|
2
3
|
import type { MetadataMatch, DeliveryDeferralFact } from './delivery-deferral.js';
|
|
3
4
|
export type ActivityIndex = number;
|
|
4
5
|
export interface SquareHeader {
|
|
@@ -35,7 +36,7 @@ export interface ActivityHead {
|
|
|
35
36
|
readonly index: ActivityIndex;
|
|
36
37
|
readonly at: number;
|
|
37
38
|
readonly kind: StoredAct['kind'];
|
|
38
|
-
readonly actor
|
|
39
|
+
readonly actor: string;
|
|
39
40
|
readonly reach?: 'bell';
|
|
40
41
|
readonly reply?: ActivityIndex;
|
|
41
42
|
}
|
|
@@ -47,7 +48,6 @@ export interface ActivityFilter {
|
|
|
47
48
|
readonly kinds?: readonly StoredAct['kind'][];
|
|
48
49
|
readonly actors?: readonly string[];
|
|
49
50
|
readonly actorMatch?: 'identity' | 'exact';
|
|
50
|
-
readonly actorRequired?: true;
|
|
51
51
|
readonly afterAt?: number;
|
|
52
52
|
}
|
|
53
53
|
export interface IndexPage {
|
|
@@ -71,12 +71,9 @@ export interface DirectedHead {
|
|
|
71
71
|
readonly at: number;
|
|
72
72
|
readonly actor: string;
|
|
73
73
|
readonly recipient: string;
|
|
74
|
-
readonly route: '
|
|
75
|
-
}
|
|
76
|
-
export interface LandedRecipient {
|
|
77
|
-
readonly recipient: string;
|
|
78
|
-
readonly route: DirectedHead['route'];
|
|
74
|
+
readonly route: import('./attention.js').AudienceRoute;
|
|
79
75
|
}
|
|
76
|
+
export type LandedRecipient = import('./attention.js').AudienceRecipient;
|
|
80
77
|
export interface ReceiptFacts {
|
|
81
78
|
readonly latestAt?: number;
|
|
82
79
|
readonly latestActorActivityIndex?: number;
|
|
@@ -85,22 +82,22 @@ export interface ReceiptAssignment {
|
|
|
85
82
|
readonly owner: string;
|
|
86
83
|
readonly index: ActivityIndex;
|
|
87
84
|
}
|
|
88
|
-
export type
|
|
89
|
-
readonly [key: string]: JsonValue;
|
|
90
|
-
};
|
|
91
|
-
export type SeatRef = Omit<Seat, 'wakeAddress'>;
|
|
85
|
+
export type SeatRef = Pick<Seat, 'participant' | 'owner' | 'generation'>;
|
|
92
86
|
export type ArtifactWriteAuthority = {
|
|
93
87
|
readonly kind: 'participant';
|
|
94
88
|
readonly seat: SeatRef;
|
|
89
|
+
} | {
|
|
90
|
+
readonly kind: 'host-cleanup';
|
|
91
|
+
readonly seat: SeatRef;
|
|
95
92
|
} | {
|
|
96
93
|
readonly kind: 'join';
|
|
97
94
|
readonly participant: string;
|
|
98
|
-
readonly
|
|
95
|
+
readonly owner: Owner;
|
|
99
96
|
readonly wakeAddress?: WakeAddress;
|
|
100
97
|
} | {
|
|
101
98
|
readonly kind: 'takeover';
|
|
102
99
|
readonly participant: string;
|
|
103
|
-
readonly
|
|
100
|
+
readonly owner: Owner;
|
|
104
101
|
readonly expectedGeneration: ActivityId;
|
|
105
102
|
readonly wakeAddress?: WakeAddress;
|
|
106
103
|
};
|
|
@@ -109,7 +106,7 @@ export interface DeliveryEvidence {
|
|
|
109
106
|
readonly generation: ActivityId;
|
|
110
107
|
readonly activity: ActivityId;
|
|
111
108
|
readonly kind: 'wake' | 'presentation';
|
|
112
|
-
readonly outcome:
|
|
109
|
+
readonly outcome: AttemptOutcome;
|
|
113
110
|
readonly attemptN: number;
|
|
114
111
|
readonly at: number;
|
|
115
112
|
readonly expiresAt?: number;
|
|
@@ -168,9 +165,7 @@ export interface DeliveryCompletion {
|
|
|
168
165
|
/** Domain facts exist only during a synchronous artifact callback. Values are detached. */
|
|
169
166
|
export interface SquareRead {
|
|
170
167
|
seat(requestedName: string): Seat | undefined;
|
|
171
|
-
|
|
172
|
-
/** Latest completed coordinates only; never participant write authority. */
|
|
173
|
-
completedSeatsForBody(body: Body): readonly Seat[];
|
|
168
|
+
seatsForOwner(owner: Owner): readonly Seat[];
|
|
174
169
|
deliveryEvidence(participant: string, generation: ActivityId, activity?: ActivityId): readonly DeliveryEvidence[];
|
|
175
170
|
deliveryByToken(claimToken: string): DeliveryEvidence | undefined;
|
|
176
171
|
cancelledThrough(participant: string, generation: ActivityId): number | undefined;
|
|
@@ -180,12 +175,6 @@ export interface SquareRead {
|
|
|
180
175
|
roster(): readonly ParticipantFact[];
|
|
181
176
|
attention(listener: string): readonly AttentionFact[];
|
|
182
177
|
incomingAttention(sender: string): readonly AttentionFact[];
|
|
183
|
-
hold(): {
|
|
184
|
-
readonly active: boolean;
|
|
185
|
-
readonly at?: number;
|
|
186
|
-
readonly reason?: string;
|
|
187
|
-
readonly actor?: string;
|
|
188
|
-
};
|
|
189
178
|
activity(index: ActivityIndex): StoredAct | undefined;
|
|
190
179
|
activityHeads(filter: ActivityFilter, page: IndexPage): FactPage<ActivityHead>;
|
|
191
180
|
activities(indexes: readonly ActivityIndex[]): readonly StoredAct[];
|
|
@@ -219,6 +208,10 @@ export interface SquareWrite extends SquareRead {
|
|
|
219
208
|
readonly at: number;
|
|
220
209
|
}): StoredAct;
|
|
221
210
|
updateWakeAddress(seat: SeatRef, wakeAddress?: WakeAddress): boolean;
|
|
211
|
+
/** Join authority only; preserves membership and its establishing generation. */
|
|
212
|
+
reattachSeat(seat: SeatRef, wakeAddress?: WakeAddress): boolean;
|
|
213
|
+
/** Host-cleanup authority only; stale/already-detached coordinates are a no-op. */
|
|
214
|
+
detachSeat(seat: SeatRef): boolean;
|
|
222
215
|
cancelThrough(seat: SeatRef, through: ActivityIndex): boolean;
|
|
223
216
|
claimDelivery(input: DeliveryClaimInput): DeliveryClaimResult;
|
|
224
217
|
prepareDelivery(input: DeliveryPreparation): boolean;
|
|
@@ -245,7 +238,7 @@ export interface SquareCommitResult<R> extends SquareReadResult<R> {
|
|
|
245
238
|
/** Atomic artifact access; no framing, SQL or lazy transaction capability escapes. */
|
|
246
239
|
export interface SquareArtifactPort {
|
|
247
240
|
read<R>(select: (facts: SquareRead) => R, signal?: AbortSignal): Promise<SquareReadResult<R>>;
|
|
248
|
-
/** Participant/domain writes must carry an explicit
|
|
241
|
+
/** Participant/domain writes must carry an explicit owner; no owner-less mutation path exists. */
|
|
249
242
|
transactAs<R>(authority: ArtifactWriteAuthority, change: (facts: SquareWrite) => R, signal?: AbortSignal): Promise<SquareCommitResult<R>>;
|
|
250
243
|
/** Bounded cleanup/evidence settlement on this same connection after custody was acquired.
|
|
251
244
|
* Independent of request/lifetime cancellation; explicit close and authority fences still apply. */
|
|
@@ -295,28 +288,14 @@ export interface WakeRequest {
|
|
|
295
288
|
export interface PresentationSinkPort {
|
|
296
289
|
present(activity: StoredAct): void | Promise<void>;
|
|
297
290
|
}
|
|
298
|
-
/** Read-only presentation evidence projection for diagnostics and migration callers. */
|
|
299
|
-
export interface PresentationEvidenceProjection {
|
|
300
|
-
readonly location: string;
|
|
301
|
-
readonly participant: string;
|
|
302
|
-
readonly sessionId: string;
|
|
303
|
-
readonly activity: string;
|
|
304
|
-
readonly outcome: string;
|
|
305
|
-
readonly at?: number;
|
|
306
|
-
}
|
|
307
291
|
export type WakeOutcome = {
|
|
308
292
|
readonly outcome: 'accepted';
|
|
309
293
|
readonly signature?: string;
|
|
310
|
-
readonly attemptN?: number;
|
|
311
294
|
} | {
|
|
312
295
|
readonly outcome: 'failed';
|
|
313
296
|
readonly message?: string;
|
|
314
297
|
readonly signature?: string;
|
|
315
298
|
readonly diagnostic?: unknown;
|
|
316
|
-
readonly attemptN?: number;
|
|
317
|
-
readonly unavailable?: boolean;
|
|
318
|
-
readonly retainRoute?: boolean;
|
|
319
|
-
readonly routeStale?: boolean;
|
|
320
299
|
} | {
|
|
321
300
|
readonly outcome: 'not-capable';
|
|
322
301
|
readonly diagnostic?: string;
|
|
@@ -325,15 +304,7 @@ export type WakeOutcome = {
|
|
|
325
304
|
readonly signature?: string;
|
|
326
305
|
readonly message?: string;
|
|
327
306
|
readonly diagnostic?: unknown;
|
|
328
|
-
readonly attemptN?: number;
|
|
329
307
|
};
|
|
330
|
-
export interface DeliveryResult {
|
|
331
|
-
readonly attempted: number;
|
|
332
|
-
readonly accepted: number;
|
|
333
|
-
readonly failed: number;
|
|
334
|
-
readonly unknown: number;
|
|
335
|
-
readonly notCapable: number;
|
|
336
|
-
}
|
|
337
308
|
export interface DeliverPendingInput {
|
|
338
309
|
readonly artifact: SquareArtifactPort;
|
|
339
310
|
readonly transport: WakeTransportPort;
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import type { PresentationResult, PresentPendingInput } from './ports.js';
|
|
2
2
|
/** Prepare custody before any external effect. A batch prepares every included item first. */
|
|
3
|
-
export declare function preparePendingPresentation(input: Omit<PresentPendingInput, 'sink'>): Promise<{
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
eligible(): Promise<boolean>;
|
|
18
|
-
release(outcome?: "released" | "failed", message?: string): Promise<void>;
|
|
19
|
-
recordPresented(): Promise<PresentationResult>;
|
|
20
|
-
} | undefined>;
|
|
21
|
-
/** Eligibility precedes the sink; only captured ownership fences its truthful completion. */
|
|
3
|
+
export declare function preparePendingPresentation(input: Omit<PresentPendingInput, 'sink'>): Promise<import("./delivery-effects.js").PreparedPresentation<{
|
|
4
|
+
kind: "say";
|
|
5
|
+
actor: import("./square-core.js").Participant;
|
|
6
|
+
at?: number;
|
|
7
|
+
body?: string;
|
|
8
|
+
metadata?: import("./model.js").ActivityMetadata;
|
|
9
|
+
mentions?: readonly import("./square-core.js").Participant[];
|
|
10
|
+
reach?: import("./square-core.js").Reach;
|
|
11
|
+
reply?: number;
|
|
12
|
+
} & {
|
|
13
|
+
index: number;
|
|
14
|
+
at: number;
|
|
15
|
+
}> | undefined>;
|
|
16
|
+
/** Square sinks explicitly classify rejection as failed; the public effect core defaults to unknown. */
|
|
22
17
|
export declare function presentPending(input: PresentPendingInput): Promise<PresentationResult>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
3
|
-
import { isSquareError
|
|
3
|
+
import { isSquareError } from './model.js';
|
|
4
4
|
import { pendingHead, sameJoin } from './delivery.js';
|
|
5
5
|
import { deliveryDeferralExpiry } from './delivery-deferral.js';
|
|
6
6
|
import { asSeatRef } from './seats.js';
|
|
7
|
+
import { preparePresentation, presentPrepared } from './delivery-effects.js';
|
|
7
8
|
/** Prepare custody before any external effect. A batch prepares every included item first. */
|
|
8
9
|
export async function preparePendingPresentation(input) {
|
|
9
10
|
const index = typeof input.activity === 'number' ? input.activity : parseActivityId(input.activity);
|
|
@@ -12,8 +13,8 @@ export async function preparePendingPresentation(input) {
|
|
|
12
13
|
const initial = await input.artifact.read((facts) => {
|
|
13
14
|
const participant = facts.participant(input.participant);
|
|
14
15
|
const seat = facts.seat(input.participant);
|
|
15
|
-
if (!participant?.joined || seat === undefined || input.generation !== undefined && input.generation !== seat.generation
|
|
16
|
-
|| input.seat !== undefined && (input.seat.generation !== seat.generation ||
|
|
16
|
+
if (!participant?.joined || seat === undefined || seat.attachment !== 'attached' || input.generation !== undefined && input.generation !== seat.generation
|
|
17
|
+
|| input.seat !== undefined && (input.seat.generation !== seat.generation || input.seat.owner !== seat.owner)
|
|
17
18
|
|| input.current?.(facts) === false || !pendingHead(facts, participant.name, index)
|
|
18
19
|
|| deliveryDeferralExpiry(facts, seat, index, input.now ?? Date.now()) !== undefined)
|
|
19
20
|
return undefined;
|
|
@@ -26,71 +27,60 @@ export async function preparePendingPresentation(input) {
|
|
|
26
27
|
const ref = asSeatRef(seat);
|
|
27
28
|
const owns = (facts) => {
|
|
28
29
|
const current = facts.seat(seat.participant);
|
|
29
|
-
return current !== undefined &&
|
|
30
|
+
return current !== undefined && current.attachment === 'attached' && current.owner === seat.owner && current.generation === seat.generation
|
|
30
31
|
&& sameJoin(facts, participant);
|
|
31
32
|
};
|
|
32
33
|
const claimToken = randomUUID();
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
return preparePresentation({
|
|
35
|
+
payload: item, coverage: input.markSeen === false ? 'clipped' : 'full',
|
|
36
|
+
port: {
|
|
37
|
+
async claim(signal) {
|
|
38
|
+
const claimed = await input.artifact.claimDelivery({ seat: ref, activity: formatActivityId(index), kind: 'presentation',
|
|
39
|
+
at: input.now ?? Date.now(), leaseMs: input.timeoutMs ?? 5000, claimToken }, signal).catch(() => ({ status: 'stale' }));
|
|
40
|
+
return claimed.status === 'acquired' ? { token: claimToken, ordinal: claimed.attemptN } : undefined;
|
|
41
|
+
},
|
|
42
|
+
async current(signal) {
|
|
43
|
+
return (await input.artifact.read(facts => owns(facts) && input.current?.(facts) !== false
|
|
44
|
+
&& pendingHead(facts, participant.name, index) !== undefined
|
|
45
|
+
&& deliveryDeferralExpiry(facts, seat, index, input.now ?? Date.now()) === undefined, signal ?? input.signal)).value;
|
|
46
|
+
},
|
|
47
|
+
async settle(claim, completion) {
|
|
48
|
+
const success = completion.outcome === 'presented' || completion.outcome === 'clipped';
|
|
49
|
+
const { value } = await input.artifact.settleAs({ kind: 'participant', seat: ref }, facts => {
|
|
50
|
+
// Captured ownership fences truth; post-effect policy/cancellation cannot veto it.
|
|
51
|
+
if (!owns(facts))
|
|
52
|
+
return false;
|
|
53
|
+
if (!facts.completeDelivery({ seat: ref, activity: formatActivityId(index), claimToken: claim.token,
|
|
54
|
+
outcome: completion.outcome, at: success ? input.now ?? Date.now() : Date.now(),
|
|
55
|
+
...(completion.reason === 'threw' ? { message: completion.error instanceof Error ? completion.error.message : String(completion.error) } : {}),
|
|
56
|
+
}))
|
|
57
|
+
return false;
|
|
58
|
+
if (completion.outcome === 'presented')
|
|
59
|
+
facts.markSeen(participant.name, [index], input.now ?? Date.now());
|
|
60
|
+
return true;
|
|
61
|
+
}).catch(error => {
|
|
62
|
+
if (!success || isSquareError(error) && error.code === 'already_joined')
|
|
63
|
+
return { value: false };
|
|
64
|
+
throw error;
|
|
65
|
+
});
|
|
66
|
+
return value;
|
|
67
|
+
},
|
|
53
68
|
},
|
|
54
|
-
|
|
55
|
-
// Success is an observed external fact, not another eligibility decision. Neither
|
|
56
|
-
// post-effect cancellation nor a new deferral can erase it and authorize replay.
|
|
57
|
-
effectObserved = true;
|
|
58
|
-
const outcome = input.markSeen === false ? 'clipped' : 'presented';
|
|
59
|
-
const { value: committed } = await input.artifact.settleAs({ kind: 'participant', seat: ref }, facts => {
|
|
60
|
-
if (!owns(facts))
|
|
61
|
-
return false;
|
|
62
|
-
if (!facts.completeDelivery({ seat: ref, activity: formatActivityId(index), claimToken, outcome, at: input.now ?? Date.now() }))
|
|
63
|
-
return false;
|
|
64
|
-
if (input.markSeen !== false)
|
|
65
|
-
facts.markSeen(participant.name, [index], input.now ?? Date.now());
|
|
66
|
-
return true;
|
|
67
|
-
}).catch(error => {
|
|
68
|
-
if (isSquareError(error) && error.code === 'already_joined')
|
|
69
|
-
return { value: false };
|
|
70
|
-
throw error;
|
|
71
|
-
});
|
|
72
|
-
return committed ? { presented: true, activity: item } : { presented: false };
|
|
73
|
-
},
|
|
74
|
-
};
|
|
69
|
+
}, { signal: input.signal });
|
|
75
70
|
}
|
|
76
|
-
/**
|
|
71
|
+
/** Square sinks explicitly classify rejection as failed; the public effect core defaults to unknown. */
|
|
77
72
|
export async function presentPending(input) {
|
|
78
73
|
const prepared = await preparePendingPresentation(input);
|
|
79
74
|
if (prepared === undefined)
|
|
80
75
|
return { presented: false };
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
return await prepared.recordPresented();
|
|
92
|
-
}
|
|
93
|
-
finally {
|
|
94
|
-
await prepared.release();
|
|
95
|
-
}
|
|
76
|
+
const report = await presentPrepared(prepared, activity => input.sink.present(activity), {
|
|
77
|
+
signal: input.signal, classifyError: () => 'failed',
|
|
78
|
+
});
|
|
79
|
+
if (report.observation.kind === 'threw')
|
|
80
|
+
throw report.observation.error;
|
|
81
|
+
const result = report.settlements[0].result;
|
|
82
|
+
if (result.persistence.status === 'error')
|
|
83
|
+
throw result.persistence.error;
|
|
84
|
+
return report.observation.kind === 'returned' && result.persistence.status === 'recorded'
|
|
85
|
+
? { presented: true, activity: prepared.payload } : { presented: false };
|
|
96
86
|
}
|
package/dist/presentation.d.ts
CHANGED
|
@@ -2,18 +2,17 @@ import { type StoredAct, type RoomChangeAct } from './model.js';
|
|
|
2
2
|
export { participantIdentity } from './participant-identity.js';
|
|
3
3
|
import { type Perception } from './square-core.js';
|
|
4
4
|
import type { Activity, UnreadActivitySummary, ParticipantSummary } from './square-facade.js';
|
|
5
|
-
export type WatchStatus = '
|
|
5
|
+
export type WatchStatus = 'idle' | 'drained' | 'quorum' | 'capped';
|
|
6
6
|
interface HeaderOptions {
|
|
7
7
|
participantCount?: number;
|
|
8
|
-
held?: boolean;
|
|
9
8
|
/** Refusal bodies go to stderr, which stays literal even when it is a TTY. */
|
|
10
9
|
plain?: boolean;
|
|
11
10
|
}
|
|
12
11
|
interface ParticipantOutputOptions {
|
|
12
|
+
owner?: string;
|
|
13
13
|
squarePath: string;
|
|
14
14
|
name: string;
|
|
15
15
|
participantCount?: number;
|
|
16
|
-
held?: boolean;
|
|
17
16
|
}
|
|
18
17
|
interface ActivityLimitOptions extends ParticipantOutputOptions {
|
|
19
18
|
count?: number;
|
|
@@ -26,17 +25,13 @@ interface ActivityBlockedOptions extends ParticipantOutputOptions {
|
|
|
26
25
|
draftPath?: string;
|
|
27
26
|
}
|
|
28
27
|
interface ExpressWaitingOptions {
|
|
29
|
-
reason: 'throttled'
|
|
28
|
+
reason: 'throttled';
|
|
30
29
|
delayMs?: number;
|
|
31
|
-
holder?: string;
|
|
32
|
-
holdReason?: string;
|
|
33
30
|
}
|
|
34
31
|
interface ExpressNoWaitOptions extends ParticipantOutputOptions {
|
|
35
|
-
reason: 'throttled'
|
|
32
|
+
reason: 'throttled';
|
|
36
33
|
retryCommand: string;
|
|
37
34
|
delayMs?: number;
|
|
38
|
-
holder?: string;
|
|
39
|
-
holdReason?: string;
|
|
40
35
|
draftPath?: string;
|
|
41
36
|
}
|
|
42
37
|
interface WatchStatusOptions extends ParticipantOutputOptions {
|
|
@@ -55,16 +50,15 @@ export declare function withPathOutput(squarePath: string, body?: string, opts?:
|
|
|
55
50
|
/** The same header/blank/body layout as withPathOutput, for refusal bodies written to stderr. */
|
|
56
51
|
export declare function formatRefusal(squarePath: string, bodyLines: string[], opts?: {
|
|
57
52
|
participantCount?: number;
|
|
58
|
-
held?: boolean;
|
|
59
53
|
}): string;
|
|
60
54
|
export declare function refusalText(error: unknown): string;
|
|
61
55
|
export declare function quoteShell(value: string): string;
|
|
62
56
|
export declare function commandPrefix(squarePath: string): string;
|
|
63
|
-
export declare function participantCommandPrefix(squarePath: string, name: string): string;
|
|
57
|
+
export declare function participantCommandPrefix(squarePath: string, name: string, owner?: string): string;
|
|
64
58
|
/** Blocked actions end with a full copy-pasteable recovery command. */
|
|
65
|
-
export declare function joinRecoveryCommand(squarePath: string, name: string): string;
|
|
59
|
+
export declare function joinRecoveryCommand(squarePath: string, name: string, owner?: string): string;
|
|
66
60
|
/** Taking a name is an explicit recovery choice, never an automatic retry. */
|
|
67
|
-
export declare function takeoverRecoveryLines(squarePath: string, name: string): string[];
|
|
61
|
+
export declare function takeoverRecoveryLines(squarePath: string, name: string, owner?: string): string[];
|
|
68
62
|
export declare function participantsRecoveryCommand(squarePath: string): string;
|
|
69
63
|
export declare function renderPresenceLines(recent: readonly ParticipantSummary[], now: number, limit?: number): string[];
|
|
70
64
|
export declare function truncateChars(body: string, maxChars: number): {
|
|
@@ -81,14 +75,12 @@ interface RenderedEvent {
|
|
|
81
75
|
export declare function renderEventCli(event: StoredAct | Activity, opts?: {
|
|
82
76
|
now?: number;
|
|
83
77
|
preview?: number;
|
|
84
|
-
actNumber?: number;
|
|
85
78
|
mention?: string;
|
|
86
79
|
}): string;
|
|
87
80
|
export declare function renderAmbientEventFacts(event: StoredAct | Activity, viewer: string, opts?: {
|
|
88
81
|
now?: number;
|
|
89
82
|
preview?: number;
|
|
90
83
|
presencePreview?: number;
|
|
91
|
-
actNumber?: number;
|
|
92
84
|
mention?: string;
|
|
93
85
|
perception?: Perception;
|
|
94
86
|
}): RenderedEvent;
|
|
@@ -96,11 +88,9 @@ export declare function renderAmbientEvent(event: StoredAct | Activity, viewer:
|
|
|
96
88
|
now?: number;
|
|
97
89
|
preview?: number;
|
|
98
90
|
presencePreview?: number;
|
|
99
|
-
actNumber?: number;
|
|
100
91
|
mention?: string;
|
|
101
92
|
perception?: Perception;
|
|
102
93
|
}): string;
|
|
103
|
-
export declare function renderPendingFeed(publicItems: readonly StoredAct[], sayNumbers: Readonly<Record<number, number>>, perceptions: Readonly<Record<number, Perception>>, viewer: string): string;
|
|
104
94
|
export declare function renderActivityBlocked(opts: ActivityBlockedOptions): string;
|
|
105
95
|
/** An indeterminate or already-committed send only points to inspection, never resend. */
|
|
106
96
|
export declare function renderActivityUncertain(opts: ParticipantOutputOptions & {
|