@astrosheep/square 0.3.63 → 0.3.65
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/claude-plugin/.claude-plugin/plugin.json +3 -2
- package/claude-plugin/hooks/hooks.json +1 -22
- package/claude-plugin/hooks/register.js +119 -0
- package/claude-plugin/types.d.ts +7 -0
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity-feed.d.ts +1 -10
- package/dist/activity-feed.js +1 -27
- package/dist/activity.js +57 -25
- package/dist/artifact.d.ts +1 -1
- package/dist/artifact.js +24 -25
- package/dist/attention-presentation.js +1 -0
- package/dist/automatic-session.d.ts +3 -1
- package/dist/automatic-session.js +21 -34
- package/dist/boundary-presentation.js +4 -13
- package/dist/canonical-path.d.ts +11 -0
- package/dist/canonical-path.js +47 -0
- package/dist/catch-decisions.js +14 -10
- package/dist/catch-selection.d.ts +6 -0
- package/dist/catch-selection.js +14 -0
- package/dist/claude-delivery.d.ts +11 -0
- package/dist/claude-delivery.js +145 -0
- package/dist/claude-mod.d.ts +8 -0
- package/dist/claude-mod.js +164 -0
- package/dist/cli/context.d.ts +0 -1
- package/dist/cli/context.js +9 -21
- package/dist/cli/harness-command.d.ts +0 -1
- package/dist/cli/harness-command.js +0 -6
- package/dist/cli/observation-commands.d.ts +1 -1
- package/dist/cli/observation-commands.js +31 -58
- package/dist/cli/program.js +20 -18
- package/dist/cli/registry.js +2 -2
- package/dist/cli/square-commands.js +10 -6
- package/dist/codex-boundary-state.d.ts +2 -2
- package/dist/codex-boundary-state.js +4 -4
- package/dist/codex-hook.js +12 -15
- package/dist/codex-queue.d.ts +2 -2
- package/dist/codex-queue.js +7 -5
- package/dist/decisions.d.ts +2 -6
- package/dist/decisions.js +16 -17
- package/dist/delivery-health.d.ts +1 -2
- package/dist/delivery-health.js +4 -5
- package/dist/delivery-operations.d.ts +2 -5
- package/dist/delivery-operations.js +77 -214
- package/dist/delivery.d.ts +25 -47
- package/dist/delivery.js +14 -46
- package/dist/diagnostic-redaction.js +7 -9
- package/dist/file-changes.d.ts +0 -1
- package/dist/file-changes.js +2 -19
- package/dist/harness-claude.js +8 -6
- package/dist/harness-codex.d.ts +0 -2
- package/dist/harness-codex.js +0 -2
- package/dist/harness-links.js +13 -17
- package/dist/harness.js +3 -7
- package/dist/help.js +4 -3
- package/dist/host-ledger-file-adapter.d.ts +17 -8
- package/dist/host-ledger-file-adapter.js +164 -80
- package/dist/host-ledger.d.ts +55 -50
- package/dist/inbox.d.ts +0 -3
- package/dist/inbox.js +88 -50
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/list.js +1 -4
- package/dist/mcp.js +24 -41
- package/dist/mention-parse.js +3 -9
- package/dist/model.d.ts +11 -2
- package/dist/model.js +1 -1
- package/dist/notifications.d.ts +3 -16
- package/dist/notifications.js +41 -122
- package/dist/opencode-delivery.d.ts +14 -0
- package/dist/opencode-delivery.js +171 -0
- package/dist/opencode.d.ts +39 -18
- package/dist/opencode.js +145 -40
- package/dist/packages/agent-delivery/src/claude-auth.d.ts +13 -0
- package/dist/packages/agent-delivery/src/claude-auth.js +75 -0
- package/dist/packages/agent-delivery/src/claude-native.d.ts +28 -0
- package/dist/packages/agent-delivery/src/claude-native.js +68 -0
- package/dist/packages/agent-delivery/src/claude.d.ts +14 -0
- package/dist/packages/agent-delivery/src/claude.js +66 -0
- package/dist/packages/agent-delivery/src/deadline.d.ts +11 -0
- package/dist/packages/agent-delivery/src/deadline.js +41 -0
- package/dist/packages/agent-delivery/src/errors.d.ts +16 -0
- package/dist/packages/agent-delivery/src/errors.js +41 -0
- package/dist/packages/agent-delivery/src/guards.d.ts +6 -0
- package/dist/packages/agent-delivery/src/guards.js +6 -0
- package/dist/packages/agent-delivery/src/index.d.ts +83 -0
- package/dist/packages/agent-delivery/src/index.js +110 -0
- package/dist/packages/agent-delivery/src/local-endpoint.d.ts +26 -0
- package/dist/packages/agent-delivery/src/local-endpoint.js +114 -0
- package/dist/packages/agent-delivery/src/opencode-native.d.ts +33 -0
- package/dist/packages/agent-delivery/src/opencode-native.js +75 -0
- package/dist/packages/agent-delivery/src/opencode.d.ts +14 -0
- package/dist/packages/agent-delivery/src/opencode.js +127 -0
- package/dist/packages/agent-delivery/src/paseo-native.d.ts +58 -0
- package/dist/packages/agent-delivery/src/paseo-native.js +122 -0
- package/dist/packages/agent-delivery/src/paseo.d.ts +19 -0
- package/dist/packages/agent-delivery/src/paseo.js +106 -0
- package/dist/packages/agent-delivery/src/pi-client.d.ts +10 -0
- package/dist/packages/agent-delivery/src/pi-client.js +106 -0
- package/dist/packages/agent-delivery/src/pi-protocol.d.ts +15 -0
- package/dist/packages/agent-delivery/src/pi-protocol.js +50 -0
- package/dist/packages/agent-delivery/src/pi-send.d.ts +23 -0
- package/dist/packages/agent-delivery/src/pi-send.js +4 -0
- package/dist/packages/agent-delivery/src/pi.d.ts +25 -0
- package/dist/packages/agent-delivery/src/pi.js +209 -0
- package/dist/packages/agent-delivery/src/types.d.ts +120 -0
- package/dist/participant-host.js +12 -13
- package/dist/participant-identity.d.ts +21 -0
- package/dist/participant-identity.js +32 -11
- package/dist/paseo-delivery.d.ts +2 -2
- package/dist/paseo-delivery.js +35 -27
- package/dist/paseo-timeline.js +25 -21
- package/dist/paseo.d.ts +2 -2
- package/dist/paseo.js +1 -2
- package/dist/ports.d.ts +7 -20
- package/dist/presentation-operations.js +16 -4
- package/dist/presentation.d.ts +19 -10
- package/dist/presentation.js +99 -177
- package/dist/registry.d.ts +15 -66
- package/dist/registry.js +45 -123
- package/dist/routes.d.ts +1 -28
- package/dist/routes.js +22 -104
- package/dist/runtime.d.ts +3 -9
- package/dist/runtime.js +14 -67
- package/dist/search.d.ts +0 -2
- package/dist/search.js +3 -10
- package/dist/square-actions.js +48 -72
- package/dist/square-application.js +32 -30
- package/dist/square-core.d.ts +3 -0
- package/dist/square-core.js +22 -5
- package/dist/square-facade.d.ts +2 -0
- package/dist/square-file-adapter.d.ts +2 -1
- package/dist/square-file-adapter.js +6 -9
- package/dist/square-projections.d.ts +40 -45
- package/dist/square-projections.js +60 -74
- package/dist/square-state.js +1 -4
- package/dist/square-storage.d.ts +5 -8
- package/dist/square-storage.js +13 -36
- package/dist/square-wiring.d.ts +2 -7
- package/dist/square-wiring.js +6 -24
- package/dist/stream.js +4 -6
- package/dist/time.js +2 -7
- package/dist/views.d.ts +6 -18
- package/dist/views.js +14 -21
- package/dist/wake-attempts.d.ts +2 -56
- package/dist/wake-attempts.js +15 -110
- package/dist/wake-eligibility.d.ts +43 -0
- package/dist/wake-eligibility.js +106 -0
- package/dist/wake-evidence.d.ts +32 -9
- package/dist/wake-evidence.js +29 -24
- package/dist/wake-sink.d.ts +13 -9
- package/dist/wake-sink.js +35 -54
- package/dist/watch.js +13 -21
- package/extensions/square-pi.js +27 -13
- package/guides/change-waits.md +1 -1
- package/guides/claude-native.md +101 -0
- package/guides/host-ledger.md +3 -1
- package/guides/participant.md +14 -7
- package/guides/pi-native-delivery.md +49 -0
- package/package.json +4 -3
- package/dist/claude-hook.d.ts +0 -10
- package/dist/claude-hook.js +0 -60
- package/dist/paseo-connection.d.ts +0 -15
- package/dist/paseo-connection.js +0 -141
- package/dist/presence.d.ts +0 -2
- package/dist/presence.js +0 -24
- package/dist/presented.d.ts +0 -2
- package/dist/presented.js +0 -10
- package/dist/state-cell.d.ts +0 -14
- package/dist/wake-port.d.ts +0 -23
- package/dist/wake-port.js +0 -27
- /package/dist/{state-cell.js → packages/agent-delivery/src/types.js} +0 -0
|
@@ -3,22 +3,23 @@ import { setTimeout as sleep } from 'node:timers/promises';
|
|
|
3
3
|
import { SquareError } from './model.js';
|
|
4
4
|
import { validateDoneBody } from './decisions.js';
|
|
5
5
|
import { currentHold, HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
6
|
-
import { Square
|
|
6
|
+
import { Square } from './square-wiring.js';
|
|
7
7
|
import { openSquare } from './square-file-adapter.js';
|
|
8
8
|
import { closeOpenSquare } from './open-square.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { catchUp, express, ignore, listen, listening } from './square-actions.js';
|
|
10
|
+
import { participantHistory, resolveParticipant, statusPresentation, participantsPresentation } from './views.js';
|
|
11
|
+
import { hostLedgerForEnv, localParticipantName, localSessionIdentities, presenceEpoch, readParticipantOwner } from './registry.js';
|
|
11
12
|
function requireLocation(context) {
|
|
12
13
|
const selected = context.squarePath ?? context.env.SQUARE_LOCATION;
|
|
13
14
|
if (selected === undefined || selected.trim() === '')
|
|
14
15
|
throw new SquareError('invalid_args', 'Square application context needs a square location');
|
|
15
16
|
return path.resolve(context.cwd, selected);
|
|
16
17
|
}
|
|
17
|
-
async function requireParticipant(context, squarePath, env = context.env) {
|
|
18
|
+
async function requireParticipant(context, squarePath, hostLedger, env = context.env) {
|
|
18
19
|
const selected = context.participant ?? env.SQUARE_PARTICIPANT_NAME;
|
|
19
20
|
if (selected?.trim())
|
|
20
21
|
return selected;
|
|
21
|
-
const discovered = await localParticipantName(squarePath, env).catch(() => undefined);
|
|
22
|
+
const discovered = await localParticipantName(squarePath, hostLedger, env).catch(() => undefined);
|
|
22
23
|
if (discovered !== undefined)
|
|
23
24
|
return discovered;
|
|
24
25
|
throw new SquareError('invalid_args', 'Square application context needs one unambiguous participant name');
|
|
@@ -31,25 +32,26 @@ export function createSquareApplication(context) {
|
|
|
31
32
|
const env = { ...context.env };
|
|
32
33
|
const location = () => requireLocation({ ...context, env });
|
|
33
34
|
const hostLedger = context.hostLedger ?? hostLedgerForEnv(env);
|
|
34
|
-
const open = () => Square.at({ path: location(), clock: context.clock, env, hostLedger, wakeTransport: context.wakeTransport });
|
|
35
|
+
const open = (control) => Square.at({ path: location(), clock: context.clock, env, hostLedger, wakeTransport: context.wakeTransport }, control);
|
|
35
36
|
async function existing(operation, control) {
|
|
36
37
|
checkControl(control);
|
|
37
38
|
const squarePath = location();
|
|
38
|
-
const participantName = await requireParticipant(context, squarePath, env);
|
|
39
|
-
const
|
|
39
|
+
const participantName = await requireParticipant(context, squarePath, hostLedger, env);
|
|
40
|
+
const square = await openSquare(squarePath, { clock: context.clock, env, hostLedger, wakeTransport: context.wakeTransport }, control);
|
|
40
41
|
try {
|
|
42
|
+
const { name } = await resolveParticipant(square, participantName, control);
|
|
41
43
|
checkControl(control);
|
|
42
|
-
return await operation(
|
|
44
|
+
return await operation(square, name);
|
|
43
45
|
}
|
|
44
46
|
finally {
|
|
45
|
-
await
|
|
47
|
+
await closeOpenSquare(square);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
async function joined(operation, control) {
|
|
49
51
|
checkControl(control);
|
|
50
|
-
const square = await open();
|
|
52
|
+
const square = await open(control);
|
|
51
53
|
try {
|
|
52
|
-
return await operation(await square.join(await requireParticipant(context, square.location, env), control));
|
|
54
|
+
return await operation(await square.join(await requireParticipant(context, square.location, hostLedger, env), control));
|
|
53
55
|
}
|
|
54
56
|
finally {
|
|
55
57
|
await square.close();
|
|
@@ -59,14 +61,14 @@ export function createSquareApplication(context) {
|
|
|
59
61
|
async join(options = {}, control = {}) {
|
|
60
62
|
checkControl(control);
|
|
61
63
|
const squarePath = location();
|
|
62
|
-
const square = await open();
|
|
64
|
+
const square = await open(control);
|
|
63
65
|
try {
|
|
64
|
-
const participantName = await requireParticipant(context, squarePath, env);
|
|
65
|
-
const before = await square.snapshot();
|
|
66
|
+
const participantName = await requireParticipant(context, squarePath, hostLedger, env);
|
|
67
|
+
const before = await square.snapshot(control);
|
|
66
68
|
const standing = before.participants.some((item) => item.name.toLocaleLowerCase() === participantName.toLocaleLowerCase() && item.state === 'joined');
|
|
67
69
|
const identity = localSessionIdentities(env)[0];
|
|
68
|
-
const owner = standing ? await readParticipantOwner(squarePath, participantName,
|
|
69
|
-
const reconnect = standing && identity !== undefined && owner?.
|
|
70
|
+
const owner = standing ? await readParticipantOwner(squarePath, participantName, hostLedger).catch(() => undefined) : undefined;
|
|
71
|
+
const reconnect = standing && identity !== undefined && owner?.session === identity.sessionId;
|
|
70
72
|
if (standing && owner !== undefined && identity !== undefined && !options.takeover && !reconnect) {
|
|
71
73
|
throw new SquareError('already_joined', `✕ ${participantName} already stands here — another session holds the name`, { pending: before.participants.length });
|
|
72
74
|
}
|
|
@@ -88,7 +90,7 @@ export function createSquareApplication(context) {
|
|
|
88
90
|
while (true) {
|
|
89
91
|
checkControl(control);
|
|
90
92
|
try {
|
|
91
|
-
const result = await existing((
|
|
93
|
+
const result = await existing((square, name) => express(square, name, body, options, control), control);
|
|
92
94
|
return { ...result, waited };
|
|
93
95
|
}
|
|
94
96
|
catch (error) {
|
|
@@ -111,7 +113,7 @@ export function createSquareApplication(context) {
|
|
|
111
113
|
}
|
|
112
114
|
else {
|
|
113
115
|
const waitStarted = Date.now();
|
|
114
|
-
const square = await openSquare(location(), { clock: context.clock, env, hostLedger,
|
|
116
|
+
const square = await openSquare(location(), { clock: context.clock, env, hostLedger }, control);
|
|
115
117
|
try {
|
|
116
118
|
const snapshot = await square.artifact.read(control.signal);
|
|
117
119
|
if (currentHold(snapshot.state.acts).active)
|
|
@@ -125,12 +127,12 @@ export function createSquareApplication(context) {
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
},
|
|
128
|
-
catch(options, control) { return existing((
|
|
130
|
+
catch(options, control) { return existing((square, name) => catchUp(square, name, options, undefined, control), control); },
|
|
129
131
|
async history(query, control) {
|
|
130
132
|
checkControl(control);
|
|
131
133
|
if (context.participant !== undefined || env.SQUARE_PARTICIPANT_NAME !== undefined)
|
|
132
|
-
return existing((
|
|
133
|
-
const square = await open();
|
|
134
|
+
return existing((square, name) => participantHistory(square, name, query, control), control);
|
|
135
|
+
const square = await open(control);
|
|
134
136
|
try {
|
|
135
137
|
checkControl(control);
|
|
136
138
|
return await square.history(query, control);
|
|
@@ -139,21 +141,21 @@ export function createSquareApplication(context) {
|
|
|
139
141
|
await square.close();
|
|
140
142
|
}
|
|
141
143
|
},
|
|
142
|
-
listen(target, control) { return existing((
|
|
143
|
-
ignore(target, control) { return existing((
|
|
144
|
-
listening(control) { return existing((
|
|
144
|
+
listen(target, control) { return existing((square, name) => listen(square, name, target, control), control); },
|
|
145
|
+
ignore(target, control) { return existing((square, name) => ignore(square, name, target, control), control); },
|
|
146
|
+
listening(control) { return existing((square, name) => listening(square, name, control), control); },
|
|
145
147
|
hold(reason, control) { return joined((participant) => participant.hold(reason, control), control); },
|
|
146
148
|
resume(control) { return joined((participant) => participant.resume(control), control); },
|
|
147
149
|
async done(body, control) {
|
|
148
150
|
validateDoneBody(body);
|
|
149
151
|
const squarePath = location();
|
|
150
|
-
const participantName = await requireParticipant(context, squarePath, env);
|
|
152
|
+
const participantName = await requireParticipant(context, squarePath, hostLedger, env);
|
|
151
153
|
const identity = localSessionIdentities(env)[0];
|
|
152
|
-
const owner = identity === undefined ? undefined : await readParticipantOwner(squarePath, participantName,
|
|
153
|
-
if (identity !== undefined && owner?.
|
|
154
|
-
const square = await open();
|
|
154
|
+
const owner = identity === undefined ? undefined : await readParticipantOwner(squarePath, participantName, hostLedger).catch(() => undefined);
|
|
155
|
+
if (identity !== undefined && owner?.session === identity.sessionId && presenceEpoch(owner) > 0) {
|
|
156
|
+
const square = await open(control);
|
|
155
157
|
try {
|
|
156
|
-
return await square.doneOwnedSession(participantName, body ?? '', owner
|
|
158
|
+
return await square.doneOwnedSession(participantName, body ?? '', presenceEpoch(owner), control);
|
|
157
159
|
}
|
|
158
160
|
finally {
|
|
159
161
|
await square.close();
|
package/dist/square-core.d.ts
CHANGED
|
@@ -139,8 +139,11 @@ export declare function audienceIncludes(audience: Audience, name: string): bool
|
|
|
139
139
|
export declare function resolveAudience(audience: Audience, candidateNames: readonly string[]): string[];
|
|
140
140
|
export declare function activeListeners(state: FoldedSquareState, sender: string): string[];
|
|
141
141
|
export declare function isIgnored(state: FoldedSquareState, listener: string, sender: string): boolean;
|
|
142
|
+
/** The author of every landed activity, keyed by index; a reply audience derives from this. */
|
|
143
|
+
export declare function activityAuthors(acts: readonly Act[]): Map<number, Participant>;
|
|
142
144
|
export declare function listeningTo(state: FoldedSquareState, listener: string): string[];
|
|
143
145
|
export declare function isListening(state: FoldedSquareState, listener: string, sender: string): boolean;
|
|
146
|
+
export declare function actorOf(act: Act): Participant | undefined;
|
|
144
147
|
export declare function fold(acts: readonly Act[]): FoldedSquareState;
|
|
145
148
|
/** Replay the activity stream once to fix every say's audience at landing. */
|
|
146
149
|
export declare function replayLandedAudiences(acts: readonly Act[]): LandedAudienceReplay;
|
package/dist/square-core.js
CHANGED
|
@@ -60,12 +60,28 @@ export function activeListeners(state, sender) {
|
|
|
60
60
|
export function isIgnored(state, listener, sender) {
|
|
61
61
|
return (state.ignored.get(nameKey(listener)) ?? []).some((target) => sameName(target, sender));
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
/** The author of every landed activity, keyed by index; a reply audience derives from this. */
|
|
64
|
+
export function activityAuthors(acts) {
|
|
65
|
+
const authors = new Map();
|
|
66
|
+
for (const act of acts) {
|
|
67
|
+
const actor = actorOf(act);
|
|
68
|
+
if (actor === undefined || !('index' in act) || typeof act.index !== 'number')
|
|
69
|
+
continue;
|
|
70
|
+
authors.set(act.index, actor);
|
|
71
|
+
}
|
|
72
|
+
return authors;
|
|
73
|
+
}
|
|
74
|
+
function recipientsAtLanding(before, say, authors) {
|
|
64
75
|
const audience = audienceOf(say);
|
|
65
|
-
const
|
|
76
|
+
const standing = before.participants.filter((participant) => participant.joined).map((participant) => participant.name);
|
|
77
|
+
const mentionTargets = resolveAudience(audience, standing);
|
|
78
|
+
// A reply is directed attention in its own right: it reaches the author of the
|
|
79
|
+
// replied-to activity under the same standing and ignore rules as a mention.
|
|
80
|
+
const replyAuthor = say.reply === undefined ? undefined : authors.get(say.reply);
|
|
81
|
+
const replyTargets = replyAuthor === undefined ? [] : resolveAudience({ kind: 'mentions', names: [replyAuthor] }, standing);
|
|
66
82
|
const listeners = activeListeners(before, say.actor);
|
|
67
83
|
const recipients = [];
|
|
68
|
-
for (const name of [...mentionTargets, ...listeners]) {
|
|
84
|
+
for (const name of [...mentionTargets, ...replyTargets, ...listeners]) {
|
|
69
85
|
if (sameName(name, say.actor) || recipients.some((existing) => sameName(existing, name)))
|
|
70
86
|
continue;
|
|
71
87
|
if (audience.kind !== 'bell' && isIgnored(before, name, say.actor))
|
|
@@ -80,7 +96,7 @@ export function listeningTo(state, listener) {
|
|
|
80
96
|
export function isListening(state, listener, sender) {
|
|
81
97
|
return listeningTo(state, listener).some((target) => sameName(target, sender));
|
|
82
98
|
}
|
|
83
|
-
function actorOf(act) {
|
|
99
|
+
export function actorOf(act) {
|
|
84
100
|
if ('actor' in act && typeof act.actor === 'string')
|
|
85
101
|
return act.actor;
|
|
86
102
|
return undefined;
|
|
@@ -238,9 +254,10 @@ export function replayLandedAudiences(acts) {
|
|
|
238
254
|
const byActivity = new Map();
|
|
239
255
|
const byIndex = new Map();
|
|
240
256
|
const lastJoinByKey = new Map();
|
|
257
|
+
const authors = activityAuthors(acts);
|
|
241
258
|
for (const act of acts) {
|
|
242
259
|
if (act.kind === 'say') {
|
|
243
|
-
const recipients = recipientsAtLanding(accumulator.state, act);
|
|
260
|
+
const recipients = recipientsAtLanding(accumulator.state, act, authors);
|
|
244
261
|
byActivity.set(act, recipients);
|
|
245
262
|
const index = 'index' in act ? act.index : undefined;
|
|
246
263
|
if (typeof index === 'number')
|
package/dist/square-facade.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface ExpressOptions {
|
|
|
24
24
|
export interface ExpressResult {
|
|
25
25
|
readonly activity: Activity;
|
|
26
26
|
readonly delivery?: DeliveryResult;
|
|
27
|
+
/** The replied-to author was not standing when the reply landed, so no notification reached them. */
|
|
28
|
+
readonly replyAuthorNotHere?: string;
|
|
27
29
|
}
|
|
28
30
|
export interface ListenerChangeResult {
|
|
29
31
|
readonly activity: Activity | null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { observeSquareRevision as observeSquareChanges } from './artifact.js';
|
|
2
2
|
import { type BuildOptions, type HardCap } from './model.js';
|
|
3
3
|
import type { OpenSquare } from './open-square.js';
|
|
4
|
+
import type { OperationControl } from './square-facade.js';
|
|
4
5
|
import type { HostLedgerPort, SquareArtifactPort } from './ports.js';
|
|
5
6
|
/** File-owned artifact creation for the CLI and path-backed public facade. */
|
|
6
7
|
export declare function createSquare(squarePath: string, options: BuildOptions & {
|
|
@@ -17,7 +18,7 @@ export interface SquareBuildOptions {
|
|
|
17
18
|
/** One caller deadline (e.g. a native hook budget) propagated into artifact busy retries. */
|
|
18
19
|
signal?: AbortSignal;
|
|
19
20
|
}
|
|
20
|
-
export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env' | 'signal'
|
|
21
|
+
export declare function openSquare(squarePath: string, options?: Pick<SquareBuildOptions, 'clock' | 'hostLedger' | 'wakeTransport' | 'env' | 'signal'>, control?: OperationControl): Promise<OpenSquare>;
|
|
21
22
|
export declare function probeSquare(squarePath: string): Promise<OpenSquare | undefined>;
|
|
22
23
|
export declare function buildSquare(squarePath: string, options: SquareBuildOptions): Promise<OpenSquare>;
|
|
23
24
|
export declare function buildMemorySquare(options: SquareBuildOptions): OpenSquare;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { observeSquareRevision as observeSquareChanges } from './artifact.js';
|
|
2
2
|
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
3
|
-
import { createSquareState, createSquareSnapshot, probeSquareFile, writeSquareSnapshot,
|
|
3
|
+
import { createSquareState, createSquareSnapshot, probeSquareFile, writeSquareSnapshot, createFileCell, createMemoryCell, } from './square-storage.js';
|
|
4
4
|
import { InternalSquareError, SquareError, } from './model.js';
|
|
5
5
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
6
6
|
/** File-owned artifact creation for the CLI and path-backed public facade. */
|
|
@@ -24,12 +24,12 @@ function validateBuildOptions(options) {
|
|
|
24
24
|
throw new SquareError('invalid_args', 'throttlePerMinute must be a positive integer');
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
export async function openSquare(squarePath, options = {}) {
|
|
27
|
+
export async function openSquare(squarePath, options = {}, control) {
|
|
28
28
|
const env = options.env ?? process.env;
|
|
29
29
|
const ledgerRoot = hostLedgerRoot(env);
|
|
30
30
|
const artifact = openSquareArtifact(squarePath, options.signal);
|
|
31
31
|
try {
|
|
32
|
-
await artifact.read();
|
|
32
|
+
await artifact.read(control?.signal);
|
|
33
33
|
return {
|
|
34
34
|
artifact,
|
|
35
35
|
clock: options.clock ?? Date.now,
|
|
@@ -51,7 +51,7 @@ export async function openSquare(squarePath, options = {}) {
|
|
|
51
51
|
}
|
|
52
52
|
export async function probeSquare(squarePath) {
|
|
53
53
|
const state = await probeSquareFile(squarePath);
|
|
54
|
-
return state === undefined ? undefined : { artifact:
|
|
54
|
+
return state === undefined ? undefined : { artifact: createMemoryCell(state), clock: Date.now, location: squarePath, hostLedger: createHostLedgerPort({ rootPath: hostLedgerRoot(process.env) }) };
|
|
55
55
|
}
|
|
56
56
|
export async function buildSquare(squarePath, options) {
|
|
57
57
|
validateBuildOptions(options);
|
|
@@ -77,12 +77,9 @@ export function buildMemorySquare(options) {
|
|
|
77
77
|
hardCap: options.hardCap ?? null,
|
|
78
78
|
...(options.throttlePerMinute === undefined ? {} : { throttlePerMinute: options.throttlePerMinute }),
|
|
79
79
|
}, options.markdown);
|
|
80
|
-
return { artifact:
|
|
80
|
+
return { artifact: createMemoryCell(squareState), clock: options.clock ?? Date.now, location: 'memory', hostLedger: options.hostLedger, wakeTransport: options.wakeTransport };
|
|
81
81
|
}
|
|
82
82
|
/** A projection reads and validates its snapshot once; no preliminary duplicate read. */
|
|
83
83
|
export function openSquareArtifact(squarePath, signal) {
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
function memoryArtifact(cell) {
|
|
87
|
-
return { read: (signal) => cell.read(signal), transact: (fn, signal) => cell.transact(fn, signal), changed: (since, timeout, signal) => cell.changed(since, timeout, signal), close: () => cell.close() };
|
|
84
|
+
return createFileCell(squarePath, signal);
|
|
88
85
|
}
|
|
@@ -1,28 +1,56 @@
|
|
|
1
1
|
import { type InboxMembership, type SquareState } from './model.js';
|
|
2
2
|
import type { HostLedgerPort, SquareArtifactPort, PresentationEvidenceProjection, PresentationProjection, SessionBindingProjection } from './ports.js';
|
|
3
3
|
import { deriveDeliveryModel } from './delivery.js';
|
|
4
|
-
import
|
|
4
|
+
import { type WakeEligibility } from './wake-eligibility.js';
|
|
5
5
|
/** A fresh blocking catch owns only the notifications admitted by its filter. */
|
|
6
6
|
export declare function pendingAtBoundary(inbox: InboxMembership[]): InboxMembership[];
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* One eligibility projection for a freshly read session inbox: a fresh catch lease owns what it
|
|
9
|
+
* admitted, and a delivered (or clipped) boundary already raised awareness for its activity, so
|
|
10
|
+
* neither belongs at the next boundary. Ordering, session scoping, suppression outcomes and
|
|
11
|
+
* ledger errors stay exactly as the boundary callers read them.
|
|
12
|
+
*/
|
|
13
|
+
export declare function projectBoundaryEligibility(input: {
|
|
8
14
|
readonly hostLedger: HostLedgerPort;
|
|
9
15
|
readonly sessionId: string;
|
|
16
|
+
readonly inbox: readonly InboxMembership[];
|
|
17
|
+
}): Promise<InboxMembership[]>;
|
|
18
|
+
export declare function projectSessionBindings(input: {
|
|
19
|
+
readonly hostLedger: HostLedgerPort;
|
|
20
|
+
readonly sessionId?: string;
|
|
10
21
|
readonly location?: string;
|
|
11
22
|
readonly now?: number;
|
|
12
23
|
}): Promise<readonly SessionBindingProjection[]>;
|
|
13
|
-
export declare function sessionIdsFromEnvironment(env?: NodeJS.ProcessEnv): readonly string[];
|
|
14
|
-
export declare function projectLocalParticipantBinding(input: {
|
|
15
|
-
readonly hostLedger: HostLedgerPort;
|
|
16
|
-
readonly location: string;
|
|
17
|
-
readonly participant: string;
|
|
18
|
-
readonly sessionIds: readonly string[];
|
|
19
|
-
readonly now?: number;
|
|
20
|
-
}): Promise<SessionBindingProjection | undefined>;
|
|
21
24
|
export declare function projectPresentation(input: {
|
|
22
25
|
readonly artifact: SquareArtifactPort;
|
|
23
26
|
readonly binding: SessionBindingProjection;
|
|
24
27
|
readonly now?: number;
|
|
25
28
|
}): Promise<PresentationProjection>;
|
|
29
|
+
export interface NativePendingPreview {
|
|
30
|
+
readonly payload: string;
|
|
31
|
+
readonly clipped: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** The one native pending preview. `epoch` matches exactly, and each harness pins its own address entry. */
|
|
34
|
+
export type NativePendingPreviewInput = {
|
|
35
|
+
readonly location: string;
|
|
36
|
+
readonly sessionId: string;
|
|
37
|
+
readonly participant: string;
|
|
38
|
+
readonly actIndex: number;
|
|
39
|
+
readonly epoch?: number;
|
|
40
|
+
readonly cancelledThrough?: number;
|
|
41
|
+
readonly now: number;
|
|
42
|
+
} & ({
|
|
43
|
+
readonly routeKind: 'claude-native';
|
|
44
|
+
readonly address: {
|
|
45
|
+
readonly endpoint: string;
|
|
46
|
+
};
|
|
47
|
+
} | {
|
|
48
|
+
readonly routeKind: 'opencode-server';
|
|
49
|
+
readonly address: {
|
|
50
|
+
readonly sessionId: string;
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
export declare function nativePendingPreview(state: SquareState, input: NativePendingPreviewInput): NativePendingPreview | undefined;
|
|
26
54
|
export declare function projectPresentationEvidence(input: {
|
|
27
55
|
readonly hostLedger: HostLedgerPort;
|
|
28
56
|
readonly location?: string;
|
|
@@ -33,44 +61,11 @@ export declare function projectPresentationEvidence(input: {
|
|
|
33
61
|
}): Promise<readonly PresentationEvidenceProjection[]>;
|
|
34
62
|
/** Successful boundary output, including a clipped preview, suppresses awareness wake. */
|
|
35
63
|
export declare function presentationSuppressesWake(evidence: readonly Pick<PresentationEvidenceProjection, 'outcome'>[]): boolean;
|
|
36
|
-
/**
|
|
37
|
-
export declare function currentSessionBindings<T extends {
|
|
38
|
-
readonly participant: string;
|
|
39
|
-
readonly updatedAt?: number;
|
|
40
|
-
}>(bindings: readonly T[]): T[];
|
|
41
|
-
export interface WakeAttempt {
|
|
42
|
-
readonly at: number;
|
|
43
|
-
readonly attention: {
|
|
44
|
-
readonly squarePath: string;
|
|
45
|
-
readonly actIndex: number;
|
|
46
|
-
readonly recipient: string;
|
|
47
|
-
};
|
|
48
|
-
readonly routeKind: WakeRouteKind;
|
|
49
|
-
readonly outcome: 'accepted' | 'unknown' | 'failed';
|
|
50
|
-
readonly signature?: string;
|
|
51
|
-
readonly attemptN: number;
|
|
52
|
-
readonly session?: string;
|
|
53
|
-
readonly message?: string;
|
|
54
|
-
readonly diagnostic?: unknown;
|
|
55
|
-
}
|
|
56
|
-
export declare function terminalWakeEvidence(attempts: readonly WakeAttempt[]): WakeAttempt | undefined;
|
|
57
|
-
export declare function isWakeRouteAttemptable(route: Pick<WakeRoute, 'kind' | 'updatedAt'>, attempts: readonly WakeAttempt[]): boolean;
|
|
58
|
-
export declare function hasAttemptableWakeRoute(routes: readonly Pick<WakeRoute, 'kind' | 'updatedAt'>[], attempts: readonly WakeAttempt[]): boolean;
|
|
59
|
-
export interface WakeEvidence {
|
|
60
|
-
readonly delivered: boolean;
|
|
61
|
-
readonly presented: boolean;
|
|
62
|
-
readonly attempts: readonly WakeAttempt[];
|
|
63
|
-
readonly terminal?: WakeAttempt;
|
|
64
|
-
readonly attemptableRoutes: readonly WakeRoute[];
|
|
65
|
-
}
|
|
66
|
-
export interface WakeEvidenceProjection {
|
|
67
|
-
evidence(recipient: string, actIndex: number): WakeEvidence;
|
|
68
|
-
}
|
|
64
|
+
/** Read primary evidence once; wake behavior belongs to the pure eligibility projection. */
|
|
69
65
|
export declare function projectWakeEvidenceFromState(input: {
|
|
70
66
|
readonly location: string;
|
|
71
67
|
readonly state: SquareState;
|
|
72
68
|
readonly hostLedger: HostLedgerPort;
|
|
73
69
|
readonly now: number;
|
|
74
70
|
readonly delivery?: ReturnType<typeof deriveDeliveryModel>;
|
|
75
|
-
}): Promise<
|
|
76
|
-
export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
|
|
71
|
+
}): Promise<WakeEligibility>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { formatActivityId
|
|
1
|
+
import { formatActivityId } from './square-core.js';
|
|
2
2
|
import { nameKey } from './model.js';
|
|
3
3
|
import { deriveDeliveryModel, leaseOwnsNotification } from './delivery.js';
|
|
4
4
|
import { freshWatchLease } from './runtime.js';
|
|
5
|
-
import {
|
|
5
|
+
import { attentionBodyIsClipped, renderAttentionPreview } from './attention-presentation.js';
|
|
6
|
+
import { canonicalPath } from './canonical-path.js';
|
|
7
|
+
import { projectWakeEligibility } from './wake-eligibility.js';
|
|
6
8
|
/** A fresh blocking catch owns only the notifications admitted by its filter. */
|
|
7
9
|
export function pendingAtBoundary(inbox) {
|
|
8
10
|
return inbox.map((membership) => {
|
|
@@ -12,37 +14,48 @@ export function pendingAtBoundary(inbox) {
|
|
|
12
14
|
return { ...membership, notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, { ...notification, recipient: membership.name })) };
|
|
13
15
|
}).filter((membership) => membership.notifications.length > 0);
|
|
14
16
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* One eligibility projection for a freshly read session inbox: a fresh catch lease owns what it
|
|
19
|
+
* admitted, and a delivered (or clipped) boundary already raised awareness for its activity, so
|
|
20
|
+
* neither belongs at the next boundary. Ordering, session scoping, suppression outcomes and
|
|
21
|
+
* ledger errors stay exactly as the boundary callers read them.
|
|
22
|
+
*/
|
|
23
|
+
export async function projectBoundaryEligibility(input) {
|
|
24
|
+
const memberships = pendingAtBoundary([...input.inbox]);
|
|
25
|
+
if (memberships.length === 0)
|
|
26
|
+
return [];
|
|
27
|
+
const evidence = await projectPresentationEvidence({ hostLedger: input.hostLedger, sessionId: input.sessionId });
|
|
28
|
+
const presented = new Map();
|
|
29
|
+
for (const row of evidence) {
|
|
30
|
+
if (row.outcome !== 'presented' && row.outcome !== 'clipped')
|
|
31
|
+
continue;
|
|
32
|
+
const key = JSON.stringify([row.location, nameKey(row.participant)]);
|
|
33
|
+
let activities = presented.get(key);
|
|
34
|
+
if (activities === undefined)
|
|
35
|
+
presented.set(key, activities = new Set());
|
|
36
|
+
activities.add(row.activity);
|
|
37
|
+
}
|
|
38
|
+
const pending = [];
|
|
39
|
+
for (const membership of memberships) {
|
|
40
|
+
const key = JSON.stringify([await canonicalPath(membership.squarePath), nameKey(membership.name)]);
|
|
41
|
+
const activities = presented.get(key);
|
|
42
|
+
const notifications = membership.notifications.filter((notification) => !activities?.has(formatActivityId(notification.actIndex)));
|
|
43
|
+
if (notifications.length > 0)
|
|
44
|
+
pending.push({ ...membership, notifications });
|
|
45
|
+
}
|
|
46
|
+
return pending;
|
|
47
|
+
}
|
|
48
|
+
export async function projectSessionBindings(input) {
|
|
49
|
+
const rows = await input.hostLedger.listPresence({ location: input.location, session: input.sessionId, now: input.now });
|
|
50
|
+
return rows.map((record) => ({
|
|
17
51
|
location: record.location,
|
|
18
52
|
participant: record.participant,
|
|
19
53
|
sessionId: record.session,
|
|
20
54
|
channel: record.channel,
|
|
21
|
-
...(record.
|
|
22
|
-
|
|
23
|
-
location: record.location,
|
|
24
|
-
participant: record.participant,
|
|
25
|
-
sessionId: record.session,
|
|
26
|
-
channel: record.channel,
|
|
27
|
-
kind: record.route.kind,
|
|
28
|
-
address: { ...record.route.address },
|
|
29
|
-
updatedAt: record.updatedAt ?? 0,
|
|
30
|
-
},
|
|
31
|
-
}),
|
|
55
|
+
...(record.epoch === undefined ? {} : { epoch: record.epoch }),
|
|
56
|
+
...(record.cancelledThrough === undefined ? {} : { cancelledThrough: record.cancelledThrough }),
|
|
32
57
|
updatedAt: record.updatedAt ?? 0,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
export async function projectSessionBindings(input) {
|
|
36
|
-
const rows = await input.hostLedger.listPresence({ location: input.location, session: input.sessionId, now: input.now });
|
|
37
|
-
return rows.map(bindingProjection);
|
|
38
|
-
}
|
|
39
|
-
export function sessionIdsFromEnvironment(env = process.env) {
|
|
40
|
-
return [env.CLAUDE_CODE_SESSION_ID, env.CODEX_THREAD_ID, env.OPENCODE_SESSION_ID, env.PI_SESSION_ID, env.PASEO_AGENT_ID]
|
|
41
|
-
.map((value) => value?.trim()).filter((value) => Boolean(value));
|
|
42
|
-
}
|
|
43
|
-
export async function projectLocalParticipantBinding(input) {
|
|
44
|
-
const bindings = (await Promise.all(input.sessionIds.map((sessionId) => projectSessionBindings({ hostLedger: input.hostLedger, location: input.location, sessionId, now: input.now })))).flat();
|
|
45
|
-
return bindings.find((binding) => binding.participant.toLocaleLowerCase() === input.participant.toLocaleLowerCase());
|
|
58
|
+
}));
|
|
46
59
|
}
|
|
47
60
|
export async function projectPresentation(input) {
|
|
48
61
|
const { state } = await input.artifact.read();
|
|
@@ -55,6 +68,18 @@ export async function projectPresentation(input) {
|
|
|
55
68
|
const lease = freshWatchLease(state, known, input.now ?? Date.now());
|
|
56
69
|
return { binding, joined: true, notifications, ...(lease === undefined ? {} : { catchLease: lease }) };
|
|
57
70
|
}
|
|
71
|
+
export function nativePendingPreview(state, input) {
|
|
72
|
+
const route = state.routes?.find((candidate) => candidate.kind === input.routeKind && candidate.sessionId === input.sessionId
|
|
73
|
+
&& candidate.epoch === input.epoch && nameKey(candidate.participant) === nameKey(input.participant)
|
|
74
|
+
&& (input.routeKind === 'claude-native' ? candidate.address.endpoint === input.address.endpoint : candidate.address.sessionId === input.address.sessionId));
|
|
75
|
+
if (!route || input.actIndex <= (input.cancelledThrough ?? -1))
|
|
76
|
+
return undefined;
|
|
77
|
+
const notification = deriveDeliveryModel(state).pendingFor(input.participant).find((entry) => entry.item.index === input.actIndex);
|
|
78
|
+
const lease = freshWatchLease(state, input.participant, input.now);
|
|
79
|
+
if (!notification || (lease !== undefined && leaseOwnsNotification(lease, { ...notification.item, recipient: input.participant, route: notification.route })))
|
|
80
|
+
return undefined;
|
|
81
|
+
return { payload: renderAttentionPreview({ squarePath: input.location, recipient: input.participant, actIndex: input.actIndex, actor: notification.item.actor, route: notification.route, body: notification.item.body }), clipped: attentionBodyIsClipped(notification.item.body) };
|
|
82
|
+
}
|
|
58
83
|
export async function projectPresentationEvidence(input) {
|
|
59
84
|
const rows = await input.hostLedger.listEvidence({ kind: 'presentation', location: input.location, participant: input.participant, session: input.sessionId, activity: input.activity, now: input.now });
|
|
60
85
|
return rows.map((row) => ({ location: row.location, participant: row.participant, sessionId: row.session, activity: row.activity, outcome: row.outcome, ...(row.at === undefined ? {} : { at: row.at }) }));
|
|
@@ -63,52 +88,13 @@ export async function projectPresentationEvidence(input) {
|
|
|
63
88
|
export function presentationSuppressesWake(evidence) {
|
|
64
89
|
return evidence.some((row) => row.outcome === 'clipped' || row.outcome === 'presented');
|
|
65
90
|
}
|
|
66
|
-
/**
|
|
67
|
-
export function currentSessionBindings(bindings) {
|
|
68
|
-
const latest = new Map();
|
|
69
|
-
for (const binding of bindings) {
|
|
70
|
-
const participant = nameKey(binding.participant);
|
|
71
|
-
latest.set(participant, Math.max(latest.get(participant) ?? Number.NEGATIVE_INFINITY, binding.updatedAt ?? 0));
|
|
72
|
-
}
|
|
73
|
-
return bindings.filter((binding) => (binding.updatedAt ?? 0) === latest.get(nameKey(binding.participant)));
|
|
74
|
-
}
|
|
75
|
-
export function terminalWakeEvidence(attempts) { return attempts.findLast((attempt) => attempt.outcome === 'accepted'); }
|
|
76
|
-
export function isWakeRouteAttemptable(route, attempts) {
|
|
77
|
-
if (terminalWakeEvidence(attempts) !== undefined)
|
|
78
|
-
return false;
|
|
79
|
-
if (attempts.some((attempt) => attempt.routeKind === route.kind && attempt.outcome === 'unknown'))
|
|
80
|
-
return false;
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
export function hasAttemptableWakeRoute(routes, attempts) { return routes.some((route) => isWakeRouteAttemptable(route, attempts)); }
|
|
91
|
+
/** Read primary evidence once; wake behavior belongs to the pure eligibility projection. */
|
|
84
92
|
export async function projectWakeEvidenceFromState(input) {
|
|
85
|
-
const canonicalLocation = await
|
|
93
|
+
const canonicalLocation = await canonicalPath(input.location);
|
|
86
94
|
const delivery = input.delivery ?? deriveDeliveryModel(input.state);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
for (const record of wakeRecords) {
|
|
93
|
-
const actIndex = parseActivityId(record.activity);
|
|
94
|
-
if (actIndex === undefined || record.routeKind === undefined || typeof record.attemptN !== 'number')
|
|
95
|
-
continue;
|
|
96
|
-
const attempt = { attention: { squarePath: record.location, recipient: record.participant, actIndex }, outcome: record.outcome, at: record.at ?? input.now, routeKind: record.routeKind, attemptN: record.attemptN, ...(record.signature === undefined ? {} : { signature: record.signature }), ...(record.session === undefined ? {} : { session: record.session }), ...(record.message === undefined ? {} : { message: record.message }), ...(record.diagnostic === undefined ? {} : { diagnostic: record.diagnostic }) };
|
|
97
|
-
const key = JSON.stringify([nameKey(record.participant), actIndex, record.session]);
|
|
98
|
-
const existing = attemptsByBinding.get(key) ?? [];
|
|
99
|
-
existing.push(attempt);
|
|
100
|
-
attemptsByBinding.set(key, existing);
|
|
101
|
-
}
|
|
102
|
-
const presentedRows = await projectPresentationEvidence({ hostLedger: input.hostLedger, location: canonicalLocation, now: input.now });
|
|
103
|
-
return {
|
|
104
|
-
evidence(recipient, actIndex) {
|
|
105
|
-
const recipientBindings = currentSessionBindings(bindings.filter((binding) => nameKey(binding.participant) === nameKey(recipient)));
|
|
106
|
-
const routes = recipientBindings.flatMap((binding) => binding.route === undefined ? [] : [binding.route]);
|
|
107
|
-
const attempts = recipientBindings.flatMap((binding) => attemptsByBinding.get(JSON.stringify([nameKey(recipient), actIndex, binding.sessionId])) ?? []);
|
|
108
|
-
const terminal = terminalWakeEvidence(attempts);
|
|
109
|
-
const presented = presentedRows.some((row) => row.activity === formatActivityId(actIndex) && row.participant.toLocaleLowerCase() === recipient.toLocaleLowerCase() && recipientBindings.some((binding) => binding.sessionId === row.sessionId) && presentationSuppressesWake([row]));
|
|
110
|
-
return { delivered: delivery.isSeen(recipient, actIndex), presented, attempts, ...(terminal === undefined ? {} : { terminal }), attemptableRoutes: terminal === undefined ? routes.filter((route) => isWakeRouteAttemptable(route, attempts)) : [] };
|
|
111
|
-
},
|
|
112
|
-
};
|
|
95
|
+
const owners = await input.hostLedger.listPresence({ location: canonicalLocation, now: input.now });
|
|
96
|
+
const evidence = await input.hostLedger.listEvidence({ location: canonicalLocation, now: input.now });
|
|
97
|
+
const wakeRecords = evidence.filter((row) => row.kind === 'wake');
|
|
98
|
+
const presentations = evidence.filter((row) => row.kind === 'presentation').map((row) => ({ location: row.location, participant: row.participant, sessionId: row.session, activity: row.activity, outcome: row.outcome, ...(row.at === undefined ? {} : { at: row.at }) }));
|
|
99
|
+
return projectWakeEligibility({ state: input.state, location: canonicalLocation, owners, wakeRecords, presentations, now: input.now, delivery });
|
|
113
100
|
}
|
|
114
|
-
export function wakeIsEligible(evidence) { return !evidence.delivered && !evidence.presented && evidence.terminal === undefined && evidence.attemptableRoutes.length > 0; }
|
package/dist/square-state.js
CHANGED
|
@@ -25,9 +25,6 @@ function isNonblankString(value) {
|
|
|
25
25
|
function isStringArray(value) {
|
|
26
26
|
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
27
27
|
}
|
|
28
|
-
function validateObservation(value) {
|
|
29
|
-
return isObject(value) && hasExactKeys(value, ['state', 'at']) && value.state === 'seen' && isFiniteNumber(value.at);
|
|
30
|
-
}
|
|
31
28
|
function validateWatchLease(value) {
|
|
32
29
|
if (!isObject(value) || !hasExactKeys(value, ['leaseId', 'heartbeatAt', 'expiresAt'], ['filter'])
|
|
33
30
|
|| !isNonblankString(value.leaseId) || !isFiniteNumber(value.heartbeatAt)
|
|
@@ -46,7 +43,7 @@ function validateRuntime(value) {
|
|
|
46
43
|
return isObject(value) && hasExactKeys(value, ['nextActIndex', 'observations', 'leases'])
|
|
47
44
|
&& isNonNegativeInteger(value.nextActIndex)
|
|
48
45
|
&& validateRecord(value.observations, (candidate) => isObject(candidate)
|
|
49
|
-
&& Object.entries(candidate).every(([id, observation]) => parseActivityId(id) !== undefined &&
|
|
46
|
+
&& Object.entries(candidate).every(([id, observation]) => parseActivityId(id) !== undefined && isObject(observation) && hasExactKeys(observation, ['state', 'at']) && observation.state === 'seen' && isFiniteNumber(observation.at)))
|
|
50
47
|
&& validateRecord(value.leases, validateWatchLease);
|
|
51
48
|
}
|
|
52
49
|
function validateAssignedRuntimeReferences(runtime) {
|
package/dist/square-storage.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { createSquareState, diagnoseSquareFile as diagnoseArtifactFile } from './artifact.js';
|
|
2
2
|
import type { SquareState } from './model.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { SquareArtifactPort } from './ports.js';
|
|
4
4
|
export { createSquareState };
|
|
5
|
-
export declare function readSquareFile(squarePath: string): Promise<SquareState>;
|
|
6
5
|
export declare function probeSquareFile(squarePath: string): Promise<SquareState | undefined>;
|
|
7
6
|
export declare function diagnoseSquareFile(squarePath: string): ReturnType<typeof diagnoseArtifactFile>;
|
|
8
7
|
/** Test-helper snapshot replacement backed by one SQLite artifact. */
|
|
9
8
|
export declare function writeSquareSnapshot(squarePath: string, squareState: SquareState): Promise<void>;
|
|
10
9
|
/** Race-safe non-force creation for the file adapter. */
|
|
11
10
|
export declare function createSquareSnapshot(squarePath: string, squareState: SquareState): Promise<boolean>;
|
|
12
|
-
/** In-process
|
|
13
|
-
export declare function createMemoryCell(initial: SquareState):
|
|
14
|
-
/** SQLite-backed
|
|
15
|
-
export declare function createFileCell(squarePath: string, externalSignal?: AbortSignal):
|
|
16
|
-
/** Consumer-facing file cell factory; keeps SQLite framing behind this module. */
|
|
17
|
-
export declare function openSquareCell(squarePath: string, signal?: AbortSignal): StateCell;
|
|
11
|
+
/** In-process artifact for fast behavior tests and embedded consumers. */
|
|
12
|
+
export declare function createMemoryCell(initial: SquareState): SquareArtifactPort;
|
|
13
|
+
/** SQLite-backed artifact. Revisions are read from the authoritative database, not file metadata. */
|
|
14
|
+
export declare function createFileCell(squarePath: string, externalSignal?: AbortSignal): SquareArtifactPort;
|