@astrosheep/square 0.3.69 → 0.3.71
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 +4 -5
- package/dist/activity-feed.js +14 -10
- package/dist/artifact.d.ts +26 -33
- package/dist/artifact.js +346 -317
- package/dist/attention-presentation.js +2 -9
- package/dist/automatic-session.js +6 -6
- package/dist/boundary-presentation.js +3 -3
- package/dist/catch-decisions.d.ts +19 -12
- package/dist/catch-decisions.js +32 -68
- package/dist/claude-delivery.js +7 -7
- package/dist/claude-mod.js +18 -7
- package/dist/cli/catch-presentation.d.ts +7 -0
- package/dist/cli/catch-presentation.js +55 -0
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/context.js +5 -23
- package/dist/cli/express-presentation.d.ts +10 -0
- package/dist/cli/express-presentation.js +196 -0
- package/dist/cli/maintenance-commands.d.ts +4 -0
- package/dist/cli/maintenance-commands.js +29 -6
- package/dist/cli/observation-commands.d.ts +7 -5
- package/dist/cli/observation-commands.js +275 -262
- package/dist/cli/program.d.ts +1 -1
- package/dist/cli/program.js +43 -56
- package/dist/cli/registry.js +2 -1
- package/dist/cli/square-commands.d.ts +3 -9
- package/dist/cli/square-commands.js +134 -255
- package/dist/codex-queue.js +4 -1
- package/dist/decisions.d.ts +26 -74
- package/dist/decisions.js +190 -335
- package/dist/delivery-health.js +13 -22
- package/dist/delivery-operations.d.ts +0 -11
- package/dist/delivery-operations.js +44 -33
- package/dist/delivery.d.ts +33 -32
- package/dist/delivery.js +70 -81
- package/dist/harness-claude.js +1 -1
- package/dist/harness-codex.js +1 -1
- package/dist/harness-pi.js +1 -1
- package/dist/help.js +6 -1
- package/dist/inbox.d.ts +27 -0
- package/dist/inbox.js +55 -18
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -1
- package/dist/list.d.ts +7 -1
- package/dist/list.js +49 -122
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +7 -37
- package/dist/model.d.ts +4 -44
- package/dist/model.js +1 -3
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.js +13 -7
- package/dist/open-square.d.ts +16 -2
- package/dist/open-square.js +40 -1
- package/dist/perception-projection.d.ts +6 -15
- package/dist/perception-projection.js +6 -26
- package/dist/{opencode-delivery.d.ts → plugins/opencode/delivery.d.ts} +1 -1
- package/dist/{opencode-delivery.js → plugins/opencode/delivery.js} +18 -18
- package/dist/{opencode.d.ts → plugins/opencode/index.d.ts} +2 -2
- package/dist/{opencode.js → plugins/opencode/index.js} +9 -9
- package/dist/ports.d.ts +156 -11
- package/dist/presentation-operations.d.ts +1 -1
- package/dist/presentation-operations.js +35 -44
- package/dist/presentation.d.ts +12 -19
- package/dist/presentation.js +33 -54
- package/dist/routes.d.ts +7 -14
- package/dist/routes.js +33 -62
- package/dist/runtime.d.ts +12 -39
- package/dist/runtime.js +33 -164
- package/dist/square-actions.d.ts +11 -21
- package/dist/square-actions.js +171 -271
- package/dist/square-application.d.ts +31 -55
- package/dist/square-application.js +637 -141
- package/dist/square-core.d.ts +3 -94
- package/dist/square-core.js +8 -353
- package/dist/square-domain.d.ts +24 -0
- package/dist/square-domain.js +77 -0
- package/dist/square-facade.d.ts +360 -9
- package/dist/square-migration.d.ts +24 -0
- package/dist/square-migration.js +211 -0
- package/dist/square-projections.d.ts +2 -42
- package/dist/square-projections.js +1 -37
- package/dist/square-sqlite.d.ts +59 -0
- package/dist/square-sqlite.js +702 -0
- package/dist/stream.d.ts +3 -10
- package/dist/stream.js +81 -52
- package/dist/views.d.ts +27 -95
- package/dist/views.js +217 -87
- package/dist/wake-eligibility.d.ts +15 -5
- package/dist/wake-eligibility.js +32 -16
- package/dist/wakes.d.ts +3 -3
- package/dist/wakes.js +31 -27
- package/guides/application-facade-delivery-evidence.md +200 -0
- package/guides/application-facade-design.md +95 -0
- package/guides/change-waits.md +4 -4
- package/guides/migration.md +77 -0
- package/guides/pi-native-delivery.md +1 -1
- package/guides/primitive-catch-consolidation.md +85 -0
- package/guides/primitive-history-consolidation.md +76 -0
- package/guides/primitive-status-consolidation.md +87 -0
- package/guides/primitive-storage-consolidation.md +128 -0
- package/guides/row-runtime-delivery-evidence.md +160 -0
- package/guides/row-storage-design.md +567 -0
- package/package.json +5 -7
- package/{claude-plugin → plugins/claude}/.claude-plugin/plugin.json +1 -1
- package/{codex-plugin → plugins/codex}/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/delivery.ts +149 -0
- package/plugins/opencode/index.ts +136 -0
- package/plugins/paseo/OVERVIEW.md +14 -0
- package/plugins/paseo/README.md +54 -0
- package/plugins/paseo/client/square-message.tsx +106 -0
- package/plugins/paseo/client/transform.ts +24 -0
- package/plugins/paseo/index.client.tsx +19 -0
- package/plugins/paseo/package.json +19 -0
- package/plugins/paseo/paseo-plugin.json +7 -0
- package/plugins/paseo/shared/message.ts +29 -0
- package/{extensions → plugins/pi}/square-pi.js +8 -5
- package/dist/activity.d.ts +0 -14
- package/dist/activity.js +0 -248
- package/dist/square-file-adapter.d.ts +0 -26
- package/dist/square-file-adapter.js +0 -85
- package/dist/square-state.d.ts +0 -6
- package/dist/square-state.js +0 -138
- package/dist/square-storage.d.ts +0 -14
- package/dist/square-storage.js +0 -188
- package/dist/square-wiring.d.ts +0 -33
- package/dist/square-wiring.js +0 -109
- package/dist/watch.d.ts +0 -8
- package/dist/watch.js +0 -252
- /package/{claude-plugin → plugins/claude}/hooks/hooks.json +0 -0
- /package/{claude-plugin → plugins/claude}/hooks/register.js +0 -0
- /package/{claude-plugin → plugins/claude}/skills/square/SKILL.md +0 -0
- /package/{claude-plugin → plugins/claude}/types.d.ts +0 -0
- /package/{codex-plugin → plugins/codex}/hooks/hooks.json +0 -0
package/dist/views.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { formatActivityId, parseActivityId
|
|
2
|
-
import {
|
|
3
|
-
import { coreActivities, coreParticipants, coreStatus, resolveKnownName } from './decisions.js';
|
|
4
|
-
import { deriveDeliveryModel, isActivitySeen } from './delivery.js';
|
|
1
|
+
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
2
|
+
import { compareParticipantActivity, coreParticipants, resolveKnownName, unreadActivitySummaries } from './decisions.js';
|
|
5
3
|
import { SquareError, nameKey } from './model.js';
|
|
6
|
-
import {
|
|
4
|
+
import { inSquareCount, readCursor, rosterNames } from './runtime.js';
|
|
5
|
+
import { perceiveActivity } from './perception-projection.js';
|
|
6
|
+
import { compileSearchPattern } from './search.js';
|
|
7
|
+
const PAGE_SIZE = 100;
|
|
8
|
+
export const HISTORY_DEFAULT_LIMIT = 10;
|
|
9
|
+
export const HISTORY_MAX_LIMIT = 100;
|
|
10
|
+
const STATUS_HERE_WINDOW_MS = 30 * 60_000;
|
|
11
|
+
const PRESENCE_WINDOW_MS = 8 * 60 * 60_000;
|
|
12
|
+
export const NON_READ = ['join', 'done', 'say', 'hold', 'resume', 'listen', 'ignore'];
|
|
13
|
+
const ARCHIVE = new Set(['join', 'done', 'say', 'hold', 'resume']);
|
|
7
14
|
/** The one StoredAct-to-public-Activity field mapping. Rejects read and actorless acts. */
|
|
8
15
|
export function toPublicActivity(stored) {
|
|
9
16
|
if (stored.kind === 'read' || stored.actor === undefined)
|
|
10
17
|
throw new Error(`Cannot expose stored activity ${formatActivityId(stored.index)}`);
|
|
11
|
-
return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor,
|
|
18
|
+
return { id: formatActivityId(stored.index), at: stored.at, kind: stored.kind, actor: stored.actor,
|
|
19
|
+
...(stored.kind === 'say' && stored.reach !== undefined ? { reach: stored.reach } : {}),
|
|
20
|
+
...('body' in stored && stored.body !== undefined ? { body: stored.body } : {}),
|
|
21
|
+
mentions: stored.kind === 'say' ? stored.mentions ?? [] : [], ...('target' in stored ? { target: stored.target } : {}),
|
|
22
|
+
...(stored.kind === 'say' && stored.reply !== undefined ? { reply: formatActivityId(stored.reply) } : {}) };
|
|
12
23
|
}
|
|
13
24
|
export function parseRequiredActivityId(id) {
|
|
14
25
|
const index = parseActivityId(id);
|
|
@@ -16,97 +27,216 @@ export function parseRequiredActivityId(id) {
|
|
|
16
27
|
throw new SquareError('invalid_args', `Invalid activity id: ${id}`);
|
|
17
28
|
return index;
|
|
18
29
|
}
|
|
19
|
-
|
|
30
|
+
/** Compile the public query directly to typed selectors; only search hydrates unselected bodies. */
|
|
31
|
+
export function selectHistory(facts, query, limit, kinds) {
|
|
20
32
|
if (query.before !== undefined && query.after !== undefined)
|
|
21
33
|
throw new SquareError('invalid_args', 'History cannot combine before and after cursors');
|
|
22
34
|
const afterIndex = query.after === undefined ? undefined : parseRequiredActivityId(query.after);
|
|
23
35
|
const beforeIndex = query.before === undefined ? undefined : parseRequiredActivityId(query.before);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
const actors = (query.from ?? []).map((name) => resolveKnownName(facts, name));
|
|
37
|
+
const recipient = query.mention == null ? undefined : resolveKnownName(facts, query.mention);
|
|
38
|
+
const pattern = query.grep ?? query.fixed;
|
|
39
|
+
const search = pattern === undefined || pattern === '' ? undefined : compileSearchPattern(pattern, query.grep === undefined);
|
|
40
|
+
if (limit !== null && (!Number.isSafeInteger(limit) || limit < 1))
|
|
41
|
+
throw new SquareError('invalid_args', 'History limit must be a positive integer');
|
|
42
|
+
const filter = {
|
|
43
|
+
...(actors.length === 0 ? {} : { actors }), afterIndex, beforeIndex, afterAt: query.since,
|
|
44
|
+
...(query.at?.length ? { indexes: facts.contextIndexes(query.at.map(parseRequiredActivityId), query.contextBefore ?? 0, query.contextAfter ?? 0) } : {}),
|
|
45
|
+
kinds: search === undefined ? kinds : ['say', 'done'],
|
|
46
|
+
};
|
|
47
|
+
let selected, totalMatches, publicBefore, publicAfter;
|
|
48
|
+
if (recipient === undefined && search === undefined && limit !== null && limit <= PAGE_SIZE) {
|
|
49
|
+
selected = facts.activityHeads(filter, { edge: afterIndex === undefined ? 'newest' : 'oldest', limit }).items;
|
|
50
|
+
const publicHeads = selected.filter((head) => ARCHIVE.has(head.kind));
|
|
51
|
+
const publicFilter = { ...filter, kinds: [...ARCHIVE] };
|
|
52
|
+
totalMatches = facts.activityCount(filter);
|
|
53
|
+
publicBefore = publicHeads.length > 0 && facts.activityCount({ ...publicFilter, beforeIndex: publicHeads[0].index }) > 0;
|
|
54
|
+
publicAfter = publicHeads.length > 0 && facts.activityCount({ ...publicFilter, afterIndex: publicHeads.at(-1).index }) > 0;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const page = [];
|
|
58
|
+
totalMatches = 0;
|
|
59
|
+
let firstPublic = Infinity, lastPublic = -1, cursor = afterIndex ?? -1;
|
|
60
|
+
while (true) {
|
|
61
|
+
const source = recipient === undefined
|
|
62
|
+
? facts.activityHeads({ ...filter, afterIndex: cursor }, { edge: 'oldest', limit: PAGE_SIZE })
|
|
63
|
+
: facts.directed(recipient, { afterIndex: cursor, beforeIndex }, { edge: 'oldest', limit: PAGE_SIZE });
|
|
64
|
+
const heads = recipient === undefined ? source.items : source.items.length === 0 ? []
|
|
65
|
+
: facts.activityHeads({ ...filter, indexes: source.items.map((head) => head.index).filter((index) => filter.indexes === undefined || filter.indexes.includes(index)) }, { edge: 'oldest', limit: PAGE_SIZE }).items;
|
|
66
|
+
const matches = search === undefined ? undefined : new Set(facts.activities(heads.map((head) => head.index)).filter((act) => [formatActivityId(act.index), act.actor ?? '', 'body' in act ? act.body ?? '' : ''].some((field) => search.test(field))).map((act) => act.index));
|
|
67
|
+
for (const head of heads) {
|
|
68
|
+
if (matches !== undefined && !matches.has(head.index))
|
|
69
|
+
continue;
|
|
70
|
+
totalMatches++;
|
|
71
|
+
if (ARCHIVE.has(head.kind)) {
|
|
72
|
+
firstPublic = Math.min(firstPublic, head.index);
|
|
73
|
+
lastPublic = head.index;
|
|
74
|
+
}
|
|
75
|
+
if (limit === null || afterIndex === undefined || page.length < limit)
|
|
76
|
+
page.push(head);
|
|
77
|
+
if (limit !== null && page.length > limit)
|
|
78
|
+
page.shift();
|
|
79
|
+
}
|
|
80
|
+
if (!source.hasMore)
|
|
81
|
+
break;
|
|
82
|
+
cursor = source.items.at(-1).index;
|
|
83
|
+
}
|
|
84
|
+
selected = page;
|
|
85
|
+
const publicHeads = selected.filter((head) => ARCHIVE.has(head.kind));
|
|
86
|
+
publicBefore = publicHeads.length > 0 && firstPublic < publicHeads[0].index;
|
|
87
|
+
publicAfter = publicHeads.length > 0 && lastPublic > publicHeads.at(-1).index;
|
|
32
88
|
}
|
|
33
|
-
|
|
89
|
+
const activities = [...facts.activities(selected.map((head) => head.index))];
|
|
90
|
+
if (query.order === 'desc')
|
|
91
|
+
activities.reverse();
|
|
92
|
+
const visibleActivities = activities.filter((activity) => ARCHIVE.has(activity.kind));
|
|
93
|
+
const direction = afterIndex === undefined ? 'before' : 'after';
|
|
94
|
+
const cursor = visibleActivities.length === 0 ? undefined : direction === 'before'
|
|
95
|
+
? Math.min(...visibleActivities.map((activity) => activity.index)) : Math.max(...visibleActivities.map((activity) => activity.index));
|
|
96
|
+
const continuation = limit === null || cursor === undefined || !(direction === 'before' ? publicBefore : publicAfter)
|
|
97
|
+
? undefined : { direction, cursor: formatActivityId(cursor), limit };
|
|
98
|
+
return { activities, visibleActivities, totalMatches, ...(continuation === undefined ? {} : { continuation }) };
|
|
34
99
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
return coreStatus(state, square.clock(), delivery).participants.filter((participant) => participant.state !== 'not joined').map((participant) => {
|
|
38
|
-
const cursor = delivery.cursorFor(participant.name);
|
|
39
|
-
return { name: participant.name, state: participant.state === 'done' ? 'done' : 'joined', consumedThrough: cursor < 0 ? null : formatActivityId(cursor), watching: participant.presence === 'watching', listening: participant.listening };
|
|
40
|
-
});
|
|
100
|
+
export function publicActivities(activities) {
|
|
101
|
+
return activities.flatMap((activity) => activity.kind === 'read' || activity.actor === undefined ? [] : [toPublicActivity(activity)]);
|
|
41
102
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
for (const activity of state.acts)
|
|
56
|
-
if (activity.kind === 'say') {
|
|
57
|
-
const next = (counts.get(nameKey(activity.actor)) ?? 0) + 1;
|
|
58
|
-
counts.set(nameKey(activity.actor), next);
|
|
59
|
-
result[activity.index] = next;
|
|
60
|
-
}
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
export async function history(square, query = {}, control) { const { state } = await square.artifact.read(control?.signal); return selectHistory(coreActivities(state, historyOptions(query)), query).map(toPublicActivity); }
|
|
64
|
-
export async function participantHistory(square, _name, query = {}, control) { const { state } = await square.artifact.read(control?.signal); const effective = query.limit !== undefined ? query : { ...query, limit: 10 }; return selectHistory(coreActivities(state, historyOptions(effective)), effective).map(toPublicActivity); }
|
|
65
|
-
export async function resolveParticipant(square, name, control) { const { state } = await square.artifact.read(control?.signal); return { name: resolveKnownName(state, name), roster: rosterNames(state) }; }
|
|
66
|
-
export async function currentParticipant(square, name) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name); return known !== undefined && isCurrentlyJoined(state.acts, known) ? known : undefined; }
|
|
67
|
-
export async function participants(square) { const { state } = await square.artifact.read(); return statuses(square, state); }
|
|
68
|
-
export async function snapshot(square, control) { const { state } = await square.artifact.read(control?.signal); const folded = foldedState(state); return { context: [...state.preamble, ...state.warmup].join('\n'), actCount: state.acts.filter((activity) => activity.kind !== 'read').length, hardCap: state.hardCap, ...(state.throttlePerMinute === undefined ? {} : { throttlePerMinute: state.throttlePerMinute }), held: folded.hold.active && folded.hold.actor !== undefined ? { by: folded.hold.actor, ...(folded.hold.reason === undefined ? {} : { reason: folded.hold.reason }) } : null, participants: statuses(square, state), delivered(name, id) { return isActivitySeen(state, name, parseRequiredActivityId(id)); } }; }
|
|
69
|
-
export async function activityPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const delivery = deriveDeliveryModel(state); const delta = deliveryDelta(state, known, delivery); const hold = currentHold(state.acts); return { name: known, roster: rosterNames(state), pendingPublic: directedPeerSays(state, delta, known, delivery), activities: state.acts, state, participantCount: inSquareCount(state), held: hold.active, ...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: countSays(state.acts, known), hardCap: state.hardCap }; }
|
|
70
|
-
export async function entryPresentation(square, name, lastN = 10) { const { state } = await square.artifact.read(); const known = resolveRosterName(state, name) ?? name; const publicActivities = state.acts.filter((activity) => activity.kind === 'say' || activity.kind === 'done'); return { joined: isCurrentlyJoined(state.acts, known), scene: state.warmup.join('\n').trim(), context: state.preamble.join('\n').trim(), joinContext: (state.preamble.at(-1) === '---' ? state.preamble.slice(0, -1) : state.preamble).join('\n').trim(), recentActivities: lastN === null ? publicActivities : publicActivities.slice(-lastN), state, sayNumbers: sayNumbers(state), participantCount: inSquareCount(state) }; }
|
|
71
|
-
export async function historyPresentation(square, options) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return { activities: coreActivities(state, options, delivery), sayNumbers: sayNumbers(state), presenceAnchors: anchors(state, delivery), participantCount: inSquareCount(state) }; }
|
|
72
|
-
export async function participantsPresentation(square) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); return coreParticipants(state, square.clock(), delivery); }
|
|
73
|
-
export async function listPresentation(square) { const { state } = await square.artifact.read(); return { context: state.preamble, participants: coreParticipants(state, square.clock()).filter((participant) => participant.state === 'active').map((participant) => participant.name), activities: state.acts.filter((activity) => activity.kind === 'say').length }; }
|
|
74
|
-
export async function statusPresentation(square) { const { state } = await square.artifact.read(); const delivery = deriveDeliveryModel(state); const status = coreStatus(state, square.clock(), delivery); return { state, status, ...(status.latestAct?.kind === 'say' ? { latestActNumber: countSays(state.acts, status.latestAct.actor) } : {}) }; }
|
|
75
|
-
export async function eventPresentation(square, id) { const { state } = await square.artifact.read(); const activity = state.acts.find((candidate) => candidate.index === parseRequiredActivityId(id)); if (activity === undefined)
|
|
76
|
-
throw new SquareError('invalid_args', `Unknown activity id: ${id}`); return { activity, participantCount: inSquareCount(state), held: currentHold(state.acts).active }; }
|
|
77
|
-
export async function watchPresentation(square, name) { const { state } = await square.artifact.read(); const known = resolveKnownName(state, name); const now = square.clock(); const terminal = watchTerminalStatus(state, known); const delivery = deriveDeliveryModel(state); return { activities: state.acts, state, participantCount: inSquareCount(state), presence: { participants: coreParticipants(state, now, delivery), now }, ...(terminal === undefined ? {} : { terminalStatus: terminal }) }; }
|
|
78
|
-
const STREAM_BATCH_MAX = 100;
|
|
79
|
-
function projectStreamActivities(state, activities, recipient) {
|
|
80
|
-
const delivery = recipient === undefined ? undefined : deriveDeliveryModel(state);
|
|
81
|
-
return activities.flatMap((activity) => {
|
|
82
|
-
if (delivery === undefined || recipient === undefined)
|
|
83
|
-
return [{ activity }];
|
|
84
|
-
const notification = delivery.plan(activity).find((candidate) => nameKey(candidate.recipient) === nameKey(recipient));
|
|
85
|
-
return notification === undefined ? [] : [{ activity, route: notification.route }];
|
|
103
|
+
export async function history(square, query = {}, control) {
|
|
104
|
+
return (await square.artifact.read((facts) => publicActivities(selectHistory(facts, query, query.limit ?? null, NON_READ).activities), control?.signal)).value;
|
|
105
|
+
}
|
|
106
|
+
export async function resolveParticipant(square, name, control) {
|
|
107
|
+
return (await square.artifact.read((facts) => ({ name: resolveKnownName(facts, name) }), control?.signal)).value;
|
|
108
|
+
}
|
|
109
|
+
export async function currentParticipant(square, name, control) {
|
|
110
|
+
return (await square.artifact.read((facts) => { const p = facts.participant(name); return p?.joined ? p.name : undefined; }, control?.signal)).value;
|
|
111
|
+
}
|
|
112
|
+
function statuses(facts, now) {
|
|
113
|
+
return coreParticipants(facts, now).filter((p) => p.state !== 'not joined').map((p) => {
|
|
114
|
+
const cursor = readCursor(facts, p.name);
|
|
115
|
+
return { name: p.name, state: p.state === 'done' ? 'done' : 'joined', consumedThrough: cursor < 0 ? null : formatActivityId(cursor), watching: p.presence === 'watching', listening: p.listening };
|
|
86
116
|
});
|
|
87
117
|
}
|
|
88
|
-
export async function
|
|
89
|
-
|
|
90
|
-
const { state } = await artifact.read();
|
|
91
|
-
const pending = state.acts.filter((activity) => activity.index > cursor);
|
|
92
|
-
const batch = pending.slice(0, STREAM_BATCH_MAX);
|
|
93
|
-
return {
|
|
94
|
-
activities: projectStreamActivities(state, batch, recipient),
|
|
95
|
-
cursor: batch.at(-1)?.index ?? cursor,
|
|
96
|
-
hasMore: pending.length > batch.length,
|
|
97
|
-
};
|
|
118
|
+
export async function participants(square) {
|
|
119
|
+
return (await square.artifact.read((facts) => statuses(facts, square.clock()))).value;
|
|
98
120
|
}
|
|
99
|
-
export async function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
121
|
+
export async function snapshot(square, control) {
|
|
122
|
+
return (await square.artifact.read((facts) => {
|
|
123
|
+
const header = facts.header(), hold = facts.hold();
|
|
124
|
+
const names = new Map(facts.roster().map((p) => [nameKey(p.name), p.name]));
|
|
125
|
+
const receipts = new Set(facts.receiptAssignments().map((r) => JSON.stringify([r.owner, r.index])));
|
|
126
|
+
return { context: [...header.preamble, ...header.warmup].join('\n'), actCount: facts.activityCount({ kinds: NON_READ }), hardCap: header.hardCap,
|
|
127
|
+
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
128
|
+
held: hold.active && hold.actor !== undefined ? { by: hold.actor, ...(hold.reason === undefined ? {} : { reason: hold.reason }) } : null,
|
|
129
|
+
participants: statuses(facts, square.clock()), delivered(name, id) { return receipts.has(JSON.stringify([names.get(nameKey(name)) ?? name, parseRequiredActivityId(id)])); } };
|
|
130
|
+
}, control?.signal)).value;
|
|
131
|
+
}
|
|
132
|
+
export async function recoveryObservation(square, name, control) {
|
|
133
|
+
return (await square.artifact.read((facts) => {
|
|
134
|
+
const known = resolveKnownName(facts, name), hold = facts.hold();
|
|
135
|
+
return { name: known, roster: rosterNames(facts), activitySummaries: unreadActivitySummaries(facts, known, square.clock()), participantCount: inSquareCount(facts), held: hold.active,
|
|
136
|
+
...(hold.reason === undefined ? {} : { holdReason: hold.reason }), ownActivityCount: facts.participant(known).sayCount, hardCap: facts.header().hardCap };
|
|
137
|
+
}, control?.signal)).value;
|
|
138
|
+
}
|
|
139
|
+
export async function entryPresentation(square, name, lastN = HISTORY_DEFAULT_LIMIT, control) {
|
|
140
|
+
return (await square.artifact.read((facts) => {
|
|
141
|
+
const header = facts.header(), p = facts.participant(name);
|
|
142
|
+
const recentActivities = selectHistory(facts, {}, lastN, ['say', 'done']).activities;
|
|
143
|
+
return { joined: p?.joined ?? false, joinedBefore: facts.activityCount({ kinds: ['join'], actors: [name] }) > 1,
|
|
144
|
+
scene: header.warmup.join('\n').trim(), context: header.preamble.join('\n').trim(),
|
|
145
|
+
joinContext: (header.preamble.at(-1) === '---' ? header.preamble.slice(0, -1) : header.preamble).join('\n').trim(), recentActivities,
|
|
146
|
+
perceptions: Object.fromEntries(recentActivities.map((a) => [a.index, perceiveActivity(facts, a, name)])),
|
|
147
|
+
sayNumbers: facts.sayOrdinals(recentActivities.map((a) => a.index)), participantCount: inSquareCount(facts), held: facts.hold().active };
|
|
148
|
+
}, control?.signal)).value;
|
|
149
|
+
}
|
|
150
|
+
function classifyPresence(participants, now) {
|
|
151
|
+
const here = participants.filter((participant) => participant.state === 'active' && (participant.presence === 'watching'
|
|
152
|
+
|| (participant.lastActiveAt !== undefined && now - participant.lastActiveAt <= STATUS_HERE_WINDOW_MS)));
|
|
153
|
+
const hereNames = new Set(here.map((participant) => participant.name));
|
|
154
|
+
return { orderedHereNames: [...here.filter((participant) => participant.presence === 'watching'), ...here.filter((participant) => participant.presence !== 'watching')].map((participant) => participant.name),
|
|
155
|
+
lingeringCount: participants.filter((participant) => participant.state === 'active' && !hereNames.has(participant.name)).length,
|
|
156
|
+
classificationByName: Object.fromEntries(participants.map((participant) => [participant.name, participant.state === 'done' ? 'done'
|
|
157
|
+
: participant.state === 'not joined' ? 'not-joined' : hereNames.has(participant.name) ? 'here' : 'lingering'])) };
|
|
158
|
+
}
|
|
159
|
+
export function queryParticipants(facts, context) {
|
|
160
|
+
const participants = coreParticipants(facts, context.now);
|
|
161
|
+
return { data: { participants }, projection: { ...context, ...classifyPresence(participants, context.now),
|
|
162
|
+
standingCount: inSquareCount(facts), held: facts.hold().active } };
|
|
163
|
+
}
|
|
164
|
+
export async function listPresentation(square, control) {
|
|
165
|
+
return (await square.artifact.read((facts) => {
|
|
166
|
+
const participants = facts.roster().filter((p) => p.joined).map((p) => {
|
|
167
|
+
const receiptAt = facts.receiptFacts(p.name, p.lastJoinIndex ?? -1).latestAt;
|
|
168
|
+
return { name: p.name, lastActiveAt: receiptAt === undefined ? p.lastActiveAt : Math.max(p.lastActiveAt ?? -Infinity, receiptAt) };
|
|
169
|
+
}).sort(compareParticipantActivity).map((p) => p.name);
|
|
170
|
+
return { context: facts.header().preamble, participants, activities: facts.activityCount({ kinds: ['say'] }) };
|
|
171
|
+
}, control?.signal)).value;
|
|
172
|
+
}
|
|
173
|
+
export function queryStatus(facts, context, viewer = '') {
|
|
174
|
+
const { data: { participants }, projection } = queryParticipants(facts, context);
|
|
175
|
+
const header = facts.header(), hold = facts.hold();
|
|
176
|
+
const latest = facts.activityHeads({ kinds: ['say', 'done'] }, { edge: 'newest', limit: 1 }).items[0];
|
|
177
|
+
const latestAct = latest === undefined ? undefined : facts.activity(latest.index);
|
|
178
|
+
const afterIndex = latestAct?.index ?? -1;
|
|
179
|
+
const joins = facts.activities(facts.activityHeads({ kinds: ['join'], afterIndex }, { edge: 'newest', limit: 3 }).items.map((h) => h.index)).map((activity) => ({ activity,
|
|
180
|
+
rejoined: facts.activityCount({ kinds: ['done'], actors: [activity.actor], actorMatch: 'exact', beforeIndex: activity.index }) > 0 }));
|
|
181
|
+
const canonicalViewer = facts.participant(viewer)?.name ?? viewer;
|
|
106
182
|
return {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
183
|
+
data: {
|
|
184
|
+
hardCap: header.hardCap,
|
|
185
|
+
...(header.throttlePerMinute === undefined ? {} : { throttlePerMinute: header.throttlePerMinute }),
|
|
186
|
+
activeCount: projection.standingCount, doneCount: participants.filter((p) => p.state === 'done').length,
|
|
187
|
+
hold: hold.active ? {
|
|
188
|
+
...(hold.actor === undefined ? {} : { by: hold.actor }),
|
|
189
|
+
...(hold.reason === undefined ? {} : { reason: hold.reason }),
|
|
190
|
+
...(hold.at === undefined ? {} : { at: hold.at }),
|
|
191
|
+
} : null,
|
|
192
|
+
participants: participants.filter((p) => p.state !== 'not joined'), now: context.now,
|
|
193
|
+
...(latestAct === undefined || latestAct.actor === undefined || latestAct.kind === 'read' ? {} : { latestActivity: toPublicActivity(latestAct) }),
|
|
194
|
+
},
|
|
195
|
+
projection: {
|
|
196
|
+
...projection, knownParticipantCount: participants.length, viewer: canonicalViewer,
|
|
197
|
+
...(canonicalViewer === '' ? {} : { participant: canonicalViewer }),
|
|
198
|
+
joins, joinCount: facts.activityCount({ kinds: ['join'], afterIndex }),
|
|
199
|
+
attentionNames: participants.filter((p) => canonicalViewer === '' || nameKey(p.name) === nameKey(canonicalViewer)).map((p) => p.name),
|
|
200
|
+
...(latestAct === undefined ? {} : { latestPerception: perceiveActivity(facts, latestAct, canonicalViewer) }),
|
|
201
|
+
...(latestAct?.kind === 'say' ? { latestActNumber: facts.sayOrdinals([latestAct.index])[latestAct.index] } : {}),
|
|
202
|
+
},
|
|
110
203
|
};
|
|
111
204
|
}
|
|
112
|
-
export function
|
|
205
|
+
export async function eventPresentation(square, id, control) {
|
|
206
|
+
return (await square.artifact.read((facts) => {
|
|
207
|
+
const activity = facts.activity(parseRequiredActivityId(id));
|
|
208
|
+
if (activity === undefined)
|
|
209
|
+
throw new SquareError('invalid_args', `Unknown activity id: ${id}`);
|
|
210
|
+
return { activity, participantCount: inSquareCount(facts), held: facts.hold().active };
|
|
211
|
+
}, control?.signal)).value;
|
|
212
|
+
}
|
|
213
|
+
export async function catchPresence(square, control) {
|
|
214
|
+
return (await square.artifact.read((facts) => {
|
|
215
|
+
const now = square.clock();
|
|
216
|
+
const participants = coreParticipants(facts, now).filter((participant) => participant.state === 'done' || participant.presence === 'watching'
|
|
217
|
+
|| (participant.lastActiveAt !== undefined && now - participant.lastActiveAt <= PRESENCE_WINDOW_MS));
|
|
218
|
+
return { participants, now };
|
|
219
|
+
}, control?.signal)).value;
|
|
220
|
+
}
|
|
221
|
+
export async function streamProjection(square, cursor, recipient, control) {
|
|
222
|
+
return (await square.artifact.read((facts) => {
|
|
223
|
+
const page = facts.activityHeads({ afterIndex: cursor }, { edge: 'oldest', limit: PAGE_SIZE });
|
|
224
|
+
const heads = page.items.flatMap((head) => {
|
|
225
|
+
const route = recipient === undefined ? undefined : facts.directedAt(recipient, head.index)?.route;
|
|
226
|
+
return recipient !== undefined && route === undefined ? [] : [{ index: head.index, route }];
|
|
227
|
+
});
|
|
228
|
+
return { activities: facts.activities(heads.map((h) => h.index)).map((activity) => {
|
|
229
|
+
const route = heads.find((h) => h.index === activity.index).route;
|
|
230
|
+
return { activity, ...(route === undefined ? {} : { route }) };
|
|
231
|
+
}), cursor: page.items.at(-1)?.index ?? cursor, hasMore: page.hasMore };
|
|
232
|
+
}, control?.signal)).value;
|
|
233
|
+
}
|
|
234
|
+
export async function streamTailProjection(square, last = HISTORY_DEFAULT_LIMIT, recipient, control) {
|
|
235
|
+
return (await square.artifact.read((facts) => {
|
|
236
|
+
if (!Number.isSafeInteger(last) || last < 0 || last > PAGE_SIZE)
|
|
237
|
+
throw new SquareError('invalid_args', `Invalid stream tail: expected a non-negative safe integer no greater than ${PAGE_SIZE}.`);
|
|
238
|
+
const heads = last === 0 ? [] : recipient === undefined ? facts.activityHeads({}, { edge: 'newest', limit: last }).items : facts.directed(recipient, {}, { edge: 'newest', limit: last }).items;
|
|
239
|
+
return { activities: facts.activities(heads.map((h) => h.index)).map((activity) => ({ activity,
|
|
240
|
+
...(recipient === undefined ? {} : { route: facts.directedAt(recipient, activity.index).route }) })), cursor: facts.header().lastActIndex, hasMore: false };
|
|
241
|
+
}, control?.signal)).value;
|
|
242
|
+
}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { deriveDeliveryModel } from './delivery.js';
|
|
1
|
+
import { type WakeRoute } from './model.js';
|
|
3
2
|
import type { EvidenceRecord, PresenceRecord } from './host-ledger.js';
|
|
4
|
-
import type { PresentationEvidenceProjection, WakeRequest } from './ports.js';
|
|
3
|
+
import type { DirectedHead, HostLedgerPort, PresentationEvidenceProjection, RouteFact, WakeRequest } from './ports.js';
|
|
5
4
|
import { type WakeAttempt } from './wake-evidence.js';
|
|
6
5
|
export interface WakeSnapshot {
|
|
7
|
-
readonly
|
|
6
|
+
readonly routes: readonly RouteFact[];
|
|
7
|
+
readonly pending: readonly DirectedHead[];
|
|
8
|
+
readonly seen?: readonly {
|
|
9
|
+
readonly recipient: string;
|
|
10
|
+
readonly index: number;
|
|
11
|
+
}[];
|
|
8
12
|
/** Canonicalized by the caller; every identity in this snapshot is square-local. */
|
|
9
13
|
readonly location: string;
|
|
10
14
|
readonly owners: readonly PresenceRecord[];
|
|
15
|
+
readonly bindings?: readonly PresenceRecord[];
|
|
11
16
|
readonly wakeRecords: readonly EvidenceRecord[];
|
|
12
17
|
readonly presentations: readonly PresentationEvidenceProjection[];
|
|
13
18
|
readonly now: number;
|
|
14
|
-
readonly delivery?: ReturnType<typeof deriveDeliveryModel>;
|
|
15
19
|
}
|
|
16
20
|
export interface OwnedWakeRoute {
|
|
17
21
|
readonly route: WakeRoute;
|
|
@@ -40,4 +44,10 @@ export interface WakeEligibility {
|
|
|
40
44
|
}
|
|
41
45
|
/** One strict ownership join, latest selection, and request fence over primary evidence only. */
|
|
42
46
|
export declare function projectWakeEligibility(input: WakeSnapshot): WakeEligibility;
|
|
47
|
+
/** Host observation happens only after detached artifact facts have left their transaction. */
|
|
48
|
+
export declare function projectWakeEvidence(input: Pick<WakeSnapshot, 'routes' | 'pending' | 'seen' | 'location' | 'now' | 'bindings'> & {
|
|
49
|
+
readonly hostLedger: HostLedgerPort;
|
|
50
|
+
readonly owners?: readonly PresenceRecord[];
|
|
51
|
+
}): Promise<WakeEligibility>;
|
|
52
|
+
export declare function sameBinding(captured: PresenceRecord, current: PresenceRecord): boolean;
|
|
43
53
|
export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
|
package/dist/wake-eligibility.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { nameKey } from './model.js';
|
|
2
|
-
import { deriveDeliveryModel } from './delivery.js';
|
|
3
2
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
4
3
|
import { presenceEpoch } from './registry.js';
|
|
4
|
+
import { canonicalPath } from './canonical-path.js';
|
|
5
5
|
import { decodeWakeEvidence } from './wake-evidence.js';
|
|
6
6
|
function identity(...parts) { return JSON.stringify(parts); }
|
|
7
7
|
/** One strict ownership join, latest selection, and request fence over primary evidence only. */
|
|
8
8
|
export function projectWakeEligibility(input) {
|
|
9
|
-
const delivery = input.delivery ?? deriveDeliveryModel(input.state);
|
|
10
9
|
const ownersBySession = new Map();
|
|
11
10
|
for (const owner of input.owners) {
|
|
12
11
|
if (owner.location !== input.location)
|
|
@@ -20,12 +19,14 @@ export function projectWakeEligibility(input) {
|
|
|
20
19
|
: ownersBySession.get(identity(nameKey(route.participant), route.sessionId))?.find((row) => route.epoch === undefined || presenceEpoch(row) === route.epoch);
|
|
21
20
|
const ownedRoutes = [];
|
|
22
21
|
const latest = new Map();
|
|
23
|
-
for (const
|
|
24
|
-
const participant = nameKey(
|
|
25
|
-
const owner = ownerFor(
|
|
26
|
-
|
|
22
|
+
for (const fact of input.routes) {
|
|
23
|
+
const participant = nameKey(fact.participant);
|
|
24
|
+
const owner = ownerFor(fact);
|
|
25
|
+
// Malformed legacy epoch cannot acquire an owner by coercion or enter a transport request.
|
|
26
|
+
if (owner === undefined || (fact.epoch !== undefined && typeof fact.epoch !== 'number'))
|
|
27
27
|
continue;
|
|
28
|
-
|
|
28
|
+
const { extra, epoch, ...route } = fact;
|
|
29
|
+
ownedRoutes.push({ route: { ...route, ...(epoch === undefined ? {} : { epoch }) }, owner });
|
|
29
30
|
latest.set(participant, Math.max(latest.get(participant) ?? Number.NEGATIVE_INFINITY, route.updatedAt));
|
|
30
31
|
}
|
|
31
32
|
const selectedByRecipient = new Map();
|
|
@@ -37,11 +38,8 @@ export function projectWakeEligibility(input) {
|
|
|
37
38
|
rows.push(owned);
|
|
38
39
|
selectedByRecipient.set(key, rows);
|
|
39
40
|
}
|
|
40
|
-
const pending = new Set();
|
|
41
|
-
|
|
42
|
-
for (const notification of delivery.pendingFor(recipient))
|
|
43
|
-
pending.add(identity(nameKey(recipient), notification.item.index));
|
|
44
|
-
}
|
|
41
|
+
const pending = new Set(input.pending.map((head) => identity(nameKey(head.recipient), head.index)));
|
|
42
|
+
const seen = new Set(input.seen?.map((receipt) => identity(nameKey(receipt.recipient), receipt.index)));
|
|
45
43
|
const attemptsByAttention = new Map();
|
|
46
44
|
for (const record of input.wakeRecords) {
|
|
47
45
|
if (record.location !== input.location)
|
|
@@ -63,6 +61,8 @@ export function projectWakeEligibility(input) {
|
|
|
63
61
|
}
|
|
64
62
|
const isPresented = (route, actIndex) => presentedSessions.has(identity(nameKey(route.participant), formatActivityId(actIndex), route.sessionId));
|
|
65
63
|
const isCancelled = (owner, actIndex) => actIndex <= (owner.cancelledThrough ?? -1);
|
|
64
|
+
const bindingCurrent = (owner) => input.bindings === undefined
|
|
65
|
+
|| input.bindings.some((captured) => sameBinding(captured, owner));
|
|
66
66
|
return {
|
|
67
67
|
evidence(recipient, actIndex) {
|
|
68
68
|
const key = nameKey(recipient);
|
|
@@ -73,10 +73,10 @@ export function projectWakeEligibility(input) {
|
|
|
73
73
|
const routes = (selectedByRecipient.get(key) ?? []).filter(({ owner }) => !isCancelled(owner, actIndex));
|
|
74
74
|
const presented = routes.length > 0 && routes.every(({ route }) => isPresented(route, actIndex));
|
|
75
75
|
return {
|
|
76
|
-
delivered:
|
|
76
|
+
delivered: seen.has(identity(key, actIndex)), presented, attempts,
|
|
77
77
|
...(terminal === undefined ? {} : { terminal }),
|
|
78
78
|
attemptableRoutes: terminal === undefined && pending.has(identity(key, actIndex))
|
|
79
|
-
? routes.filter(({ route }) => !isPresented(route, actIndex)).map(({ route }) => route) : [],
|
|
79
|
+
? routes.filter(({ route, owner }) => bindingCurrent(owner) && !isPresented(route, actIndex)).map(({ route }) => route) : [],
|
|
80
80
|
};
|
|
81
81
|
},
|
|
82
82
|
currentness(request) {
|
|
@@ -87,11 +87,11 @@ export function projectWakeEligibility(input) {
|
|
|
87
87
|
const activityPending = actIndex !== undefined && request.location === input.location
|
|
88
88
|
&& pending.has(identity(nameKey(request.participant), actIndex));
|
|
89
89
|
const owner = sameLocation && sameRecipient ? ownerFor(route) : undefined;
|
|
90
|
-
const sessionBound = owner !== undefined;
|
|
90
|
+
const sessionBound = owner !== undefined && bindingCurrent(owner);
|
|
91
91
|
const matchesRequest = (published) => published.location === route.location
|
|
92
92
|
&& nameKey(published.participant) === nameKey(route.participant) && published.sessionId === route.sessionId
|
|
93
93
|
&& published.kind === route.kind && published.epoch === route.epoch && Object.keys(published.address).length === Object.keys(route.address).length && Object.keys(published.address).every((key) => published.address[key] === route.address[key]);
|
|
94
|
-
const routePublished = sameLocation && sameRecipient &&
|
|
94
|
+
const routePublished = sameLocation && sameRecipient && input.routes.some(matchesRequest);
|
|
95
95
|
const selectedOwner = sameLocation && sameRecipient
|
|
96
96
|
&& (selectedByRecipient.get(nameKey(route.participant)) ?? []).some(({ route: published }) => matchesRequest(published));
|
|
97
97
|
const cancelled = actIndex !== undefined && owner !== undefined && isCancelled(owner, actIndex);
|
|
@@ -102,6 +102,22 @@ export function projectWakeEligibility(input) {
|
|
|
102
102
|
},
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
/** Host observation happens only after detached artifact facts have left their transaction. */
|
|
106
|
+
export async function projectWakeEvidence(input) {
|
|
107
|
+
const location = await canonicalPath(input.location);
|
|
108
|
+
const owners = input.owners ?? await input.hostLedger.listPresence({ location, now: input.now });
|
|
109
|
+
const records = await input.hostLedger.listEvidence({ location, now: input.now });
|
|
110
|
+
const presentations = records.filter((row) => row.kind === 'presentation').map((row) => ({
|
|
111
|
+
location: row.location, participant: row.participant, sessionId: row.session, activity: row.activity, outcome: row.outcome,
|
|
112
|
+
...(row.at === undefined ? {} : { at: row.at }),
|
|
113
|
+
}));
|
|
114
|
+
return projectWakeEligibility({ ...input, location, owners, wakeRecords: records.filter((row) => row.kind === 'wake'), presentations });
|
|
115
|
+
}
|
|
116
|
+
export function sameBinding(captured, current) {
|
|
117
|
+
return captured.location === current.location && nameKey(captured.participant) === nameKey(current.participant)
|
|
118
|
+
&& captured.session === current.session && captured.channel === current.channel && captured.epoch === current.epoch
|
|
119
|
+
&& (captured.cancelledThrough ?? -1) === (current.cancelledThrough ?? -1);
|
|
120
|
+
}
|
|
105
121
|
export function wakeIsEligible(evidence) {
|
|
106
122
|
return !evidence.delivered && !evidence.presented && evidence.terminal === undefined && evidence.attemptableRoutes.length > 0;
|
|
107
123
|
}
|
package/dist/wakes.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type WatchLeasePulse = {
|
|
|
21
21
|
readonly type: 'sleep';
|
|
22
22
|
readonly heartbeatAt?: number;
|
|
23
23
|
};
|
|
24
|
-
export declare function acquireWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions): Promise<WatchLeaseStart>;
|
|
25
|
-
export declare function pulseWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions, heartbeatDue: boolean): Promise<WatchLeasePulse>;
|
|
24
|
+
export declare function acquireWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions, signal?: AbortSignal): Promise<WatchLeaseStart>;
|
|
25
|
+
export declare function pulseWatchLease(square: OpenSquare, name: string, leaseId: string, options: WatchOptions, heartbeatDue: boolean, signal?: AbortSignal): Promise<WatchLeasePulse>;
|
|
26
26
|
export declare function releaseWatchLease(square: OpenSquare, name: string, leaseId: string | undefined): Promise<void>;
|
|
27
|
-
export declare function ownsWatchLease(square: OpenSquare, name: string, leaseId: string): Promise<boolean>;
|
|
27
|
+
export declare function ownsWatchLease(square: OpenSquare, name: string, leaseId: string, signal?: AbortSignal): Promise<boolean>;
|
package/dist/wakes.js
CHANGED
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
import { resolveKnownName } from './decisions.js';
|
|
2
|
-
import { WATCH_STALE_MS,
|
|
3
|
-
function
|
|
4
|
-
|
|
2
|
+
import { WATCH_STALE_MS, freshWatchLease, watchTerminalStatus } from './runtime.js';
|
|
3
|
+
function newLease(leaseId, at, options) {
|
|
4
|
+
const filter = { ...(options.participants === undefined ? {} : { participants: [...options.participants] }), ...(options.mention === undefined ? {} : { mention: options.mention }) };
|
|
5
|
+
return { leaseId, heartbeatAt: at, expiresAt: at + WATCH_STALE_MS, ...(Object.keys(filter).length === 0 ? {} : { filter }) };
|
|
5
6
|
}
|
|
6
|
-
export async function acquireWatchLease(square, name, leaseId, options) {
|
|
7
|
+
export async function acquireWatchLease(square, name, leaseId, options, signal) {
|
|
7
8
|
const at = square.clock();
|
|
8
|
-
|
|
9
|
-
const known = resolveKnownName(
|
|
10
|
-
const existing = freshWatchLease(
|
|
9
|
+
const { value } = await square.artifact.transact((facts) => {
|
|
10
|
+
const known = resolveKnownName(facts, name);
|
|
11
|
+
const existing = freshWatchLease(facts, known, at);
|
|
11
12
|
if (existing !== undefined && !options.replace)
|
|
12
|
-
return {
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
});
|
|
13
|
+
return { type: 'active', lease: existing };
|
|
14
|
+
facts.putLease(known, newLease(leaseId, at, options));
|
|
15
|
+
return { type: 'started', leaseId, replaced: existing !== undefined, heartbeatAt: at };
|
|
16
|
+
}, signal);
|
|
17
|
+
return value;
|
|
16
18
|
}
|
|
17
|
-
export async function pulseWatchLease(square, name, leaseId, options, heartbeatDue) {
|
|
19
|
+
export async function pulseWatchLease(square, name, leaseId, options, heartbeatDue, signal) {
|
|
18
20
|
const at = square.clock();
|
|
19
|
-
|
|
20
|
-
const known = resolveKnownName(
|
|
21
|
-
const lease = freshWatchLease(
|
|
21
|
+
const { value } = await square.artifact.transact((facts) => {
|
|
22
|
+
const known = resolveKnownName(facts, name);
|
|
23
|
+
const lease = freshWatchLease(facts, known, at);
|
|
22
24
|
if (lease?.leaseId !== leaseId)
|
|
23
|
-
return {
|
|
24
|
-
const held =
|
|
25
|
-
const terminal = held ? undefined : watchTerminalStatus(
|
|
25
|
+
return { type: 'replaced' };
|
|
26
|
+
const held = facts.hold().active;
|
|
27
|
+
const terminal = held ? undefined : watchTerminalStatus(facts, known);
|
|
26
28
|
if (terminal !== undefined)
|
|
27
|
-
return {
|
|
29
|
+
return { type: 'terminal', status: terminal };
|
|
28
30
|
const type = held ? 'held' : 'sleep';
|
|
29
31
|
if (!heartbeatDue)
|
|
30
|
-
return {
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
});
|
|
32
|
+
return { type };
|
|
33
|
+
facts.putLease(known, newLease(leaseId, at, options));
|
|
34
|
+
return { type, heartbeatAt: at };
|
|
35
|
+
}, signal);
|
|
36
|
+
return value;
|
|
34
37
|
}
|
|
35
38
|
export async function releaseWatchLease(square, name, leaseId) {
|
|
36
|
-
if (leaseId
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
if (leaseId !== undefined)
|
|
40
|
+
await square.artifact.transact((facts) => facts.removeLease(resolveKnownName(facts, name), leaseId));
|
|
41
|
+
}
|
|
42
|
+
export async function ownsWatchLease(square, name, leaseId, signal) {
|
|
43
|
+
return (await square.artifact.read((facts) => freshWatchLease(facts, resolveKnownName(facts, name), square.clock())?.leaseId === leaseId, signal)).value;
|
|
39
44
|
}
|
|
40
|
-
export async function ownsWatchLease(square, name, leaseId) { const { state } = await square.artifact.read(); return freshWatchLease(state, resolveKnownName(state, name), square.clock())?.leaseId === leaseId; }
|