@astrosheep/square 0.3.22 → 0.3.24
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 +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/automatic-session.js +1 -10
- package/dist/cli/observation-commands.js +4 -3
- package/dist/notifications.js +12 -4
- package/dist/presentation.d.ts +1 -0
- package/dist/presentation.js +6 -5
- package/dist/presented.d.ts +8 -0
- package/dist/presented.js +9 -0
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +1 -1
- package/dist/square-storage.js +18 -9
- package/dist/views.d.ts +1 -0
- package/dist/views.js +6 -1
- package/dist/wake-evidence.d.ts +7 -0
- package/dist/wake-evidence.js +78 -24
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@ import { closeOpenSquare } from './open-square.js';
|
|
|
5
5
|
import { Square } from './square-wiring.js';
|
|
6
6
|
import { entryPresentation } from './views.js';
|
|
7
7
|
import { canonicalSquarePath, lookupSessionBindings, recordSessionDone, recordSessionJoin } from './registry.js';
|
|
8
|
-
import { participantIdentity, renderAmbientEvent } from './presentation.js';
|
|
9
8
|
import { automaticParticipant } from './participant-identity.js';
|
|
10
9
|
export { automaticParticipant } from './participant-identity.js';
|
|
11
10
|
const providerEnv = {
|
|
@@ -39,15 +38,7 @@ export async function automaticSessionStart(provider, sessionId, cwd, env = proc
|
|
|
39
38
|
return undefined;
|
|
40
39
|
const channel = provider === 'claude' ? 'claude-code' : provider;
|
|
41
40
|
recordSessionJoin(sessionId, name, squarePath, channel, { ...env, [providerEnv[provider]]: sessionId });
|
|
42
|
-
|
|
43
|
-
const after = await entryPresentation(afterSquare, name, 10).finally(() => closeOpenSquare(afterSquare));
|
|
44
|
-
const activity = after.recentActivities.map((event) => renderAmbientEvent(event, name, {
|
|
45
|
-
now: Date.now(),
|
|
46
|
-
preview: 200,
|
|
47
|
-
actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
|
|
48
|
-
squareState: after.state,
|
|
49
|
-
})).filter(Boolean).join('\n\n');
|
|
50
|
-
return [`You joined the public square as ${participantIdentity(name)}.`, after.scene, after.context ? `context\n${after.context}` : '', activity ? `recent activity\n${activity}` : ''].filter(Boolean).join('\n\n');
|
|
41
|
+
return undefined;
|
|
51
42
|
}
|
|
52
43
|
finally {
|
|
53
44
|
await square.close();
|
|
@@ -4,7 +4,7 @@ import { sessionInbox } from '../inbox.js';
|
|
|
4
4
|
import { sweepPendingNotifications } from '../notifications.js';
|
|
5
5
|
import { cmdListSquares } from '../list.js';
|
|
6
6
|
import { parseActivityId, sameName } from '../model.js';
|
|
7
|
-
import { commandPrefix, participantCommandPrefix, participantIdentity, renderGrepActivitiesView, renderEventCli, renderAmbientEvent, withPathOutput, } from '../presentation.js';
|
|
7
|
+
import { commandPrefix, participantCommandPrefix, participantIdentity, renderGrepActivitiesView, renderEventCli, renderAmbientEvent, renderPresenceAnchor, withPathOutput, } from '../presentation.js';
|
|
8
8
|
import { actId, nowMs } from '../runtime.js';
|
|
9
9
|
import { cmdStream, cmdStreamNdjson } from '../stream.js';
|
|
10
10
|
import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from '../time.js';
|
|
@@ -293,8 +293,9 @@ function renderHistoryProjection(projection, visible, full, squarePath, viewer,
|
|
|
293
293
|
: renderAmbientEvent(activity, viewer, options);
|
|
294
294
|
if (rendered !== '')
|
|
295
295
|
chunks.push(rendered);
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
const participants = projection.presenceAnchors[activity.index];
|
|
297
|
+
if (participants !== undefined)
|
|
298
|
+
chunks.push(renderPresenceAnchor(participants));
|
|
298
299
|
}
|
|
299
300
|
if (chunks.length === 0)
|
|
300
301
|
return 'latest\n ○ no public activity in this view';
|
package/dist/notifications.js
CHANGED
|
@@ -14,10 +14,10 @@ import { retireWakeRoute } from './routes.js';
|
|
|
14
14
|
import { openSquare } from './square-file-adapter.js';
|
|
15
15
|
import { markNotificationNotified } from './square-wiring.js';
|
|
16
16
|
import { closeOpenSquare } from './open-square.js';
|
|
17
|
-
import { entryPresentation, notificationDelivered, notificationForAct,
|
|
17
|
+
import { entryPresentation, notificationDelivered, notificationForAct, pendingDeliveriesFromState, resolveParticipant } from './views.js';
|
|
18
18
|
import { claimNotificationLease, releaseNotificationLease, transitionNotificationLease } from './wakes.js';
|
|
19
19
|
import { nextWakeAttemptNumber, recordRecoveredUnknown, recordWakeAttempt, } from './wake-attempts.js';
|
|
20
|
-
import { wakeEvidence, wakeIsEligible } from './wake-evidence.js';
|
|
20
|
+
import { wakeEvidence, wakeEvidenceProjectionFromState, wakeIsEligible } from './wake-evidence.js';
|
|
21
21
|
import { WakePort } from './wake-port.js';
|
|
22
22
|
const NOTIFY_LEASE_MS = 5 * 60 * 1000;
|
|
23
23
|
export { planActNotifications, matchesMentionTarget };
|
|
@@ -264,13 +264,21 @@ export async function sweepPendingNotifications(squarePath, opts = {}) {
|
|
|
264
264
|
const now = opts.now ?? Date.now();
|
|
265
265
|
const limit = opts.limit ?? 8;
|
|
266
266
|
const square = await openSquare(squarePath, { clock: () => now });
|
|
267
|
-
|
|
267
|
+
let state;
|
|
268
|
+
try {
|
|
269
|
+
({ state } = await entryPresentation(square, ''));
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
await closeOpenSquare(square);
|
|
273
|
+
}
|
|
274
|
+
const pending = pendingDeliveriesFromState(state);
|
|
275
|
+
const evidence = wakeEvidenceProjectionFromState(squarePath, state, now, env);
|
|
268
276
|
const indexes = new Set();
|
|
269
277
|
for (const delivery of pending) {
|
|
270
278
|
for (const note of delivery.notifications) {
|
|
271
279
|
if (now - note.item.at <= wakeGraceMs(env))
|
|
272
280
|
continue;
|
|
273
|
-
if (!wakeIsEligible(
|
|
281
|
+
if (!wakeIsEligible(evidence.evidence(delivery.recipient, note.item.index)))
|
|
274
282
|
continue;
|
|
275
283
|
indexes.add(note.item.index);
|
|
276
284
|
}
|
package/dist/presentation.d.ts
CHANGED
|
@@ -75,6 +75,7 @@ export declare function renderActivityBlocked(opts: ActivityBlockedOptions): str
|
|
|
75
75
|
export declare function renderExpressWaiting(opts: ExpressWaitingOptions): string;
|
|
76
76
|
export declare function renderExpressNoWait(opts: ExpressNoWaitOptions): string;
|
|
77
77
|
export declare function renderPublicTail(squareState: SquareState, events: StoredAct[], lastN: number | null | undefined, now?: number, viewer?: string): string;
|
|
78
|
+
export declare function renderPresenceAnchor(names: readonly string[]): string;
|
|
78
79
|
export declare function renderActivitiesView(squareState: SquareState, visible: StoredAct[], lastN: number | null | undefined, full: boolean | undefined, squarePath: string, viewer?: string, mode?: 'ambient' | 'archive'): string;
|
|
79
80
|
export declare function renderGrepActivitiesView(visible: StoredAct[], totalMatches: number, full: boolean | undefined, squarePath: string, pattern: string, fixed?: boolean): string;
|
|
80
81
|
export declare function renderActivityLimit(opts: ActivityLimitOptions): string;
|
package/dist/presentation.js
CHANGED
|
@@ -291,8 +291,9 @@ function lastPresenceAnchor(squareState, name) {
|
|
|
291
291
|
}
|
|
292
292
|
return -1;
|
|
293
293
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
294
|
+
export function renderPresenceAnchor(names) {
|
|
295
|
+
const participants = names.map((name) => participantIdentity(name)).join(', ');
|
|
296
|
+
return names.length === 1 ? `→ ${participants} was here` : `→ ${participants} were here`;
|
|
296
297
|
}
|
|
297
298
|
export function renderActivitiesView(squareState, visible, lastN, full, squarePath, viewer = '', mode = 'ambient') {
|
|
298
299
|
const publicVisible = visible.filter((act) => act.kind === 'say' || act.kind === 'done');
|
|
@@ -315,9 +316,9 @@ export function renderActivitiesView(squareState, visible, lastN, full, squarePa
|
|
|
315
316
|
: renderAmbientEvent(act, viewer, { ...opts, squareState });
|
|
316
317
|
if (rendered !== '')
|
|
317
318
|
chunks.push(rendered);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
const participants = markers.get(act.index);
|
|
320
|
+
if (participants !== undefined)
|
|
321
|
+
chunks.push(renderPresenceAnchor(participants));
|
|
321
322
|
}
|
|
322
323
|
if (chunks.length === 0)
|
|
323
324
|
return 'latest\n ○ no public activity in this view';
|
package/dist/presented.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { type InboxMembership } from './model.js';
|
|
2
|
+
export interface PresentedAttention {
|
|
3
|
+
ownerId: string;
|
|
4
|
+
squarePath: string;
|
|
5
|
+
name: string;
|
|
6
|
+
actIndex: number;
|
|
7
|
+
}
|
|
2
8
|
export declare function presentedPath(env?: NodeJS.ProcessEnv): string;
|
|
9
|
+
/** Read the current presentation facts once for a derived evidence projection. */
|
|
10
|
+
export declare function readPresentedAttentions(env?: NodeJS.ProcessEnv, now?: number): PresentedAttention[];
|
|
3
11
|
export declare function hasPresentedForOwner(ownerId: string, squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, now?: number): boolean;
|
|
4
12
|
/** True when any current participant owner has already received this attention. */
|
|
5
13
|
export declare function hasPresentedAttention(squarePath: string, name: string, actIndex: number, env?: NodeJS.ProcessEnv, now?: number): boolean;
|
package/dist/presented.js
CHANGED
|
@@ -48,6 +48,15 @@ function readRows(filePath, now = Date.now()) {
|
|
|
48
48
|
}
|
|
49
49
|
return rows;
|
|
50
50
|
}
|
|
51
|
+
/** Read the current presentation facts once for a derived evidence projection. */
|
|
52
|
+
export function readPresentedAttentions(env = process.env, now = Date.now()) {
|
|
53
|
+
return readRows(presentedPath(env), now).map((row) => ({
|
|
54
|
+
ownerId: row.owner_id,
|
|
55
|
+
squarePath: canonicalSquarePath(row.square_path),
|
|
56
|
+
name: row.name,
|
|
57
|
+
actIndex: row.act_index,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
51
60
|
function writeRows(filePath, rows) {
|
|
52
61
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
53
62
|
const temp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
package/dist/registry.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export declare function registryPath(): string;
|
|
|
28
28
|
export declare function canonicalSquarePath(squarePath: string): string;
|
|
29
29
|
export declare function recordJoin(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): void;
|
|
30
30
|
export declare function recordDone(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): void;
|
|
31
|
+
export declare function readActiveBindings(now?: number): RegistryBinding[];
|
|
31
32
|
export declare function lookupSessionBindings(sessionId: string, now?: number): RegistryBinding[];
|
|
32
33
|
export declare function lookupSession(sessionId: string, now?: number): Array<{
|
|
33
34
|
name: string;
|
package/dist/registry.js
CHANGED
|
@@ -205,7 +205,7 @@ export function recordJoin(sessionId, name, squarePath, options = {}) {
|
|
|
205
205
|
export function recordDone(sessionId, name, squarePath, options = {}) {
|
|
206
206
|
writeLifecycle('done', sessionId, name, squarePath, options);
|
|
207
207
|
}
|
|
208
|
-
function readActiveBindings(now = Date.now()) {
|
|
208
|
+
export function readActiveBindings(now = Date.now()) {
|
|
209
209
|
try {
|
|
210
210
|
return foldRegistry(fs.readFileSync(registryPath(), 'utf8'), now);
|
|
211
211
|
}
|
package/dist/square-storage.js
CHANGED
|
@@ -116,25 +116,36 @@ export function createFileCell(squarePath) {
|
|
|
116
116
|
let closed = false;
|
|
117
117
|
let version = 0;
|
|
118
118
|
let fingerprint = fileFingerprint(squarePath);
|
|
119
|
+
let cached;
|
|
119
120
|
function observe() {
|
|
120
121
|
const next = fileFingerprint(squarePath);
|
|
121
|
-
if (next
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
if (next !== fingerprint) {
|
|
123
|
+
fingerprint = next;
|
|
124
|
+
cached = undefined;
|
|
125
|
+
version += 1;
|
|
126
|
+
}
|
|
127
|
+
return fingerprint;
|
|
128
|
+
}
|
|
129
|
+
function currentState() {
|
|
130
|
+
const observed = observe();
|
|
131
|
+
if (cached?.fingerprint === observed)
|
|
132
|
+
return cloneState(cached.state);
|
|
133
|
+
const decoded = readSquareFile(squarePath);
|
|
134
|
+
cached = { fingerprint: observed, state: cloneState(decoded) };
|
|
135
|
+
return cloneState(cached.state);
|
|
125
136
|
}
|
|
126
137
|
return {
|
|
127
138
|
async transact(fn) {
|
|
128
139
|
assertCellOpen(closed);
|
|
129
140
|
return withFileLock(`${squarePath}.lock`, { retryMs: LOCK_RETRY_MS, staleMs: LOCK_STALE_MS }, () => {
|
|
130
141
|
assertCellOpen(closed);
|
|
131
|
-
|
|
132
|
-
const current = readSquareFile(squarePath);
|
|
142
|
+
const current = currentState();
|
|
133
143
|
const working = cloneState(current);
|
|
134
144
|
const outcome = fn(working, version);
|
|
135
145
|
if (outcome.state !== undefined) {
|
|
136
146
|
writeSquareSnapshot(squarePath, outcome.state);
|
|
137
147
|
fingerprint = fileFingerprint(squarePath);
|
|
148
|
+
cached = { fingerprint, state: cloneState(outcome.state) };
|
|
138
149
|
version += 1;
|
|
139
150
|
}
|
|
140
151
|
return outcome.result;
|
|
@@ -142,9 +153,7 @@ export function createFileCell(squarePath) {
|
|
|
142
153
|
},
|
|
143
154
|
async read() {
|
|
144
155
|
assertCellOpen(closed);
|
|
145
|
-
|
|
146
|
-
const state = readSquareFile(squarePath);
|
|
147
|
-
return { state: cloneState(state), version };
|
|
156
|
+
return { state: currentState(), version };
|
|
148
157
|
},
|
|
149
158
|
async changed(sinceVersion, timeoutMs) {
|
|
150
159
|
assertCellOpen(closed);
|
package/dist/views.d.ts
CHANGED
|
@@ -96,6 +96,7 @@ export declare function watchPresentation(square: OpenSquare, name: string): Pro
|
|
|
96
96
|
export declare function inboxProjection(square: OpenSquare, name: string, ownerId: string): Promise<InboxProjection>;
|
|
97
97
|
export declare function streamProjection(square: OpenSquare, cursor: number, recipient?: string): Promise<StreamProjection>;
|
|
98
98
|
export declare function notificationForAct(square: OpenSquare, actIndex: number): Promise<readonly PlannedNotification[]>;
|
|
99
|
+
export declare function pendingDeliveriesFromState(state: SquareState): readonly PendingDeliveryProjection[];
|
|
99
100
|
export declare function pendingDeliveries(square: OpenSquare): Promise<readonly PendingDeliveryProjection[]>;
|
|
100
101
|
export declare function notificationEvidence(square: OpenSquare, recipient: string, actIndex: number): Promise<{
|
|
101
102
|
readonly delivered: boolean;
|
package/dist/views.js
CHANGED
|
@@ -78,6 +78,11 @@ export async function inboxProjection(square, name, ownerId) { const { state } =
|
|
|
78
78
|
export async function streamProjection(square, cursor, recipient) { const { state } = await square.cell.read(); return { activities: state.acts.filter((activity) => activity.index > cursor).flatMap((activity) => { if (recipient === undefined)
|
|
79
79
|
return [{ activity }]; const notification = planActNotifications(state, activity).find((candidate) => nameKey(candidate.recipient) === nameKey(recipient)); return notification === undefined ? [] : [{ activity, route: notification.route }]; }), cursor: Math.max(cursor, ...state.acts.map((activity) => activity.index)) }; }
|
|
80
80
|
export async function notificationForAct(square, actIndex) { const { state } = await square.cell.read(); const activity = state.acts.find((candidate) => candidate.index === actIndex); return activity === undefined ? [] : planActNotifications(state, activity); }
|
|
81
|
-
export
|
|
81
|
+
export function pendingDeliveriesFromState(state) {
|
|
82
|
+
return [...new Set(state.acts.filter((activity) => activity.kind === 'join').map((activity) => activity.actor))]
|
|
83
|
+
.filter((name) => isCurrentlyJoined(state.acts, name))
|
|
84
|
+
.map((recipient) => ({ recipient, notifications: deriveDeliveryModel(state).pendingFor(recipient) }));
|
|
85
|
+
}
|
|
86
|
+
export async function pendingDeliveries(square) { const { state } = await square.cell.read(); return pendingDeliveriesFromState(state); }
|
|
82
87
|
export async function notificationEvidence(square, recipient, actIndex) { const { state } = await square.cell.read(); return { delivered: isActivitySeen(state, recipient, actIndex), observation: observationFor(state, recipient, actIndex) }; }
|
|
83
88
|
export async function notificationDelivered(square, recipient, actIndex) { const { state } = await square.cell.read(); return isActivitySeen(state, recipient, actIndex); }
|
package/dist/wake-evidence.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type WakeRoute } from './model.js';
|
|
2
|
+
import { type SquareState } from './model.js';
|
|
2
3
|
import { type WakeAttempt } from './wake-attempts.js';
|
|
3
4
|
export interface WakeEvidence {
|
|
4
5
|
delivered: boolean;
|
|
@@ -8,6 +9,12 @@ export interface WakeEvidence {
|
|
|
8
9
|
terminal?: WakeAttempt;
|
|
9
10
|
attemptableRoutes: WakeRoute[];
|
|
10
11
|
}
|
|
12
|
+
export interface WakeEvidenceProjection {
|
|
13
|
+
evidence(recipient: string, actIndex: number): WakeEvidence;
|
|
14
|
+
}
|
|
15
|
+
/** Capture the primary wake facts once and derive any number of eligibility decisions from them. */
|
|
16
|
+
export declare function wakeEvidenceProjection(squarePath: string, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidenceProjection>;
|
|
17
|
+
export declare function wakeEvidenceProjectionFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv): WakeEvidenceProjection;
|
|
11
18
|
/** Project every wake decision from the same primary evidence. */
|
|
12
19
|
export declare function wakeEvidence(squarePath: string, recipient: string, actIndex: number, now: number, env: NodeJS.ProcessEnv): Promise<WakeEvidence>;
|
|
13
20
|
export declare function wakeIsEligible(evidence: WakeEvidence): boolean;
|
package/dist/wake-evidence.js
CHANGED
|
@@ -1,38 +1,92 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { isActivitySeen } from './delivery.js';
|
|
2
|
+
import { nameKey } from './model.js';
|
|
3
|
+
import { readPresentedAttentions } from './presented.js';
|
|
4
|
+
import { canonicalSquarePath, readActiveBindings } from './registry.js';
|
|
3
5
|
import { readWakeRoutes } from './routes.js';
|
|
4
6
|
import { isWakeRouteAttemptable, readWakeAttempts, terminalWakeEvidence, } from './wake-attempts.js';
|
|
5
7
|
import { openSquare } from './square-file-adapter.js';
|
|
6
8
|
import { closeOpenSquare } from './open-square.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
import { observationFor } from './runtime.js';
|
|
10
|
+
import { entryPresentation } from './views.js';
|
|
11
|
+
function attentionKey(squarePath, recipient, actIndex) {
|
|
12
|
+
return JSON.stringify([canonicalSquarePath(squarePath), nameKey(recipient), actIndex]);
|
|
13
|
+
}
|
|
14
|
+
function projectionFromState(squarePath, state, now, env) {
|
|
15
|
+
const canonicalPath = canonicalSquarePath(squarePath);
|
|
16
|
+
const owners = new Map();
|
|
17
|
+
for (const binding of readActiveBindings(now)) {
|
|
18
|
+
if (binding.squarePath !== canonicalPath)
|
|
19
|
+
continue;
|
|
20
|
+
const key = nameKey(binding.name);
|
|
21
|
+
const recipientOwners = owners.get(key) ?? new Set();
|
|
22
|
+
recipientOwners.add(binding.ownerId);
|
|
23
|
+
owners.set(key, recipientOwners);
|
|
24
|
+
}
|
|
25
|
+
const routesByOwner = new Map();
|
|
26
|
+
for (const route of readWakeRoutes({ freshOnly: true, now, env })) {
|
|
27
|
+
const routes = routesByOwner.get(route.ownerId) ?? [];
|
|
28
|
+
routes.push(route);
|
|
29
|
+
routesByOwner.set(route.ownerId, routes);
|
|
30
|
+
}
|
|
31
|
+
const attemptsByAttention = new Map();
|
|
32
|
+
for (const attempt of readWakeAttempts({ env, now })) {
|
|
33
|
+
const key = attentionKey(attempt.attention.squarePath, attempt.attention.recipient, attempt.attention.actIndex);
|
|
34
|
+
const attempts = attemptsByAttention.get(key) ?? [];
|
|
35
|
+
attempts.push(attempt);
|
|
36
|
+
attemptsByAttention.set(key, attempts);
|
|
37
|
+
}
|
|
38
|
+
const presentedByAttention = new Map();
|
|
39
|
+
for (const presented of readPresentedAttentions(env, now)) {
|
|
40
|
+
const key = attentionKey(presented.squarePath, presented.name, presented.actIndex);
|
|
41
|
+
const presentedOwners = presentedByAttention.get(key) ?? new Set();
|
|
42
|
+
presentedOwners.add(presented.ownerId);
|
|
43
|
+
presentedByAttention.set(key, presentedOwners);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
evidence(recipient, actIndex) {
|
|
47
|
+
const recipientOwners = owners.get(nameKey(recipient)) ?? new Set();
|
|
48
|
+
const key = attentionKey(squarePath, recipient, actIndex);
|
|
49
|
+
const attempts = attemptsByAttention.get(key) ?? [];
|
|
50
|
+
const terminal = terminalWakeEvidence(attempts);
|
|
51
|
+
const routes = [...recipientOwners].flatMap((ownerId) => routesByOwner.get(ownerId) ?? []);
|
|
52
|
+
const presented = [...(presentedByAttention.get(key) ?? [])]
|
|
53
|
+
.some((ownerId) => recipientOwners.has(ownerId));
|
|
54
|
+
return {
|
|
55
|
+
delivered: isActivitySeen(state, recipient, actIndex),
|
|
56
|
+
notified: (() => {
|
|
57
|
+
const observation = observationFor(state, recipient, actIndex);
|
|
58
|
+
return observation?.state === 'notified'
|
|
59
|
+
&& observation.ownerId !== undefined
|
|
60
|
+
&& recipientOwners.has(observation.ownerId);
|
|
61
|
+
})(),
|
|
62
|
+
presented,
|
|
63
|
+
attempts,
|
|
64
|
+
...(terminal === undefined ? {} : { terminal }),
|
|
65
|
+
attemptableRoutes: terminal === undefined
|
|
66
|
+
? routes.filter((route) => isWakeRouteAttemptable(route, attempts))
|
|
67
|
+
: [],
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Capture the primary wake facts once and derive any number of eligibility decisions from them. */
|
|
73
|
+
export async function wakeEvidenceProjection(squarePath, now, env) {
|
|
10
74
|
const square = await openSquare(squarePath, { clock: () => now });
|
|
11
75
|
try {
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
const notified = observation?.state === 'notified'
|
|
15
|
-
&& observation.ownerId !== undefined
|
|
16
|
-
&& owners.has(observation.ownerId);
|
|
17
|
-
const attempts = readWakeAttempts({ attention: { squarePath, recipient, actIndex }, env, now });
|
|
18
|
-
const terminal = terminalWakeEvidence(attempts);
|
|
19
|
-
const routes = readWakeRoutes({ freshOnly: true, now, env })
|
|
20
|
-
.filter((route) => owners.has(route.ownerId));
|
|
21
|
-
return {
|
|
22
|
-
delivered,
|
|
23
|
-
notified,
|
|
24
|
-
presented: hasPresentedAttention(squarePath, recipient, actIndex, env, now),
|
|
25
|
-
attempts,
|
|
26
|
-
...(terminal === undefined ? {} : { terminal }),
|
|
27
|
-
attemptableRoutes: terminal === undefined
|
|
28
|
-
? routes.filter((route) => isWakeRouteAttemptable(route, attempts))
|
|
29
|
-
: [],
|
|
30
|
-
};
|
|
76
|
+
const { state } = await entryPresentation(square, '');
|
|
77
|
+
return projectionFromState(squarePath, state, now, env);
|
|
31
78
|
}
|
|
32
79
|
finally {
|
|
33
80
|
await closeOpenSquare(square);
|
|
34
81
|
}
|
|
35
82
|
}
|
|
83
|
+
export function wakeEvidenceProjectionFromState(squarePath, state, now, env) {
|
|
84
|
+
return projectionFromState(squarePath, state, now, env);
|
|
85
|
+
}
|
|
86
|
+
/** Project every wake decision from the same primary evidence. */
|
|
87
|
+
export async function wakeEvidence(squarePath, recipient, actIndex, now, env) {
|
|
88
|
+
return (await wakeEvidenceProjection(squarePath, now, env)).evidence(recipient, actIndex);
|
|
89
|
+
}
|
|
36
90
|
export function wakeIsEligible(evidence) {
|
|
37
91
|
return !evidence.delivered
|
|
38
92
|
&& !evidence.notified
|