@astrosheep/square 0.3.29 → 0.3.31
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/claude-plugin/hooks/hooks.json +0 -3
- package/claude-plugin/skills/square/SKILL.md +8 -7
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.js +7 -3
- package/dist/artifact.js +7 -50
- package/dist/automatic-session.js +31 -14
- package/dist/boundary-presentation.d.ts +1 -1
- package/dist/boundary-presentation.js +58 -14
- package/dist/catch-decisions.d.ts +17 -0
- package/dist/catch-decisions.js +53 -0
- package/dist/claude-hook.d.ts +1 -1
- package/dist/cli/context.js +10 -2
- package/dist/cli/observation-commands.d.ts +5 -1
- package/dist/cli/observation-commands.js +68 -33
- package/dist/cli/square-commands.js +17 -10
- package/dist/codex-hook.d.ts +1 -1
- package/dist/decisions.js +2 -2
- package/dist/delivery-health.d.ts +1 -1
- package/dist/delivery-operations.d.ts +25 -0
- package/dist/delivery-operations.js +124 -0
- package/dist/help.js +1 -1
- package/dist/host-ledger-file-adapter.d.ts +34 -0
- package/dist/host-ledger-file-adapter.js +165 -0
- package/dist/host-ledger.d.ts +160 -0
- package/dist/host-ledger.js +1 -0
- package/dist/inbox.d.ts +2 -2
- package/dist/inbox.js +22 -14
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/landing.d.ts +18 -14
- package/dist/landing.js +29 -113
- package/dist/model.d.ts +6 -17
- package/dist/notifications.d.ts +6 -10
- package/dist/notifications.js +71 -192
- package/dist/open-square.d.ts +4 -4
- package/dist/open-square.js +1 -1
- package/dist/ports.d.ts +127 -0
- package/dist/ports.js +1 -0
- package/dist/presence.d.ts +1 -2
- package/dist/presence.js +13 -46
- package/dist/presentation-operations.d.ts +3 -0
- package/dist/presentation-operations.js +51 -0
- package/dist/presentation.d.ts +2 -2
- package/dist/presentation.js +11 -7
- package/dist/presented.d.ts +1 -12
- package/dist/presented.js +6 -75
- package/dist/registry.d.ts +10 -10
- package/dist/registry.js +48 -113
- package/dist/routes.d.ts +6 -33
- package/dist/routes.js +6 -170
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +3 -4
- package/dist/square-actions.d.ts +32 -0
- package/dist/square-actions.js +167 -0
- package/dist/square-facade.d.ts +7 -7
- package/dist/square-file-adapter.d.ts +4 -3
- package/dist/square-file-adapter.js +22 -5
- package/dist/square-projections.d.ts +68 -0
- package/dist/square-projections.js +87 -0
- package/dist/square-storage.d.ts +2 -2
- package/dist/square-storage.js +14 -9
- package/dist/square-wiring.d.ts +3 -3
- package/dist/square-wiring.js +44 -29
- package/dist/views.d.ts +8 -2
- package/dist/views.js +28 -24
- package/dist/wake-attempts.d.ts +29 -22
- package/dist/wake-attempts.js +36 -119
- package/dist/wake-evidence.d.ts +6 -18
- package/dist/wake-evidence.js +17 -80
- package/dist/wakes.d.ts +2 -16
- package/dist/wakes.js +7 -27
- package/dist/watch.js +2 -3
- package/extensions/square-pi.js +7 -2
- package/package.json +1 -1
- package/skills/brainstorm/SKILL.md +2 -2
- package/skills/square/SKILL.md +8 -7
package/dist/model.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ export declare const WAKE_ROUTE_KINDS: readonly ["opencode-server", "codex-queue
|
|
|
5
5
|
export type WakeRouteKind = typeof WAKE_ROUTE_KINDS[number];
|
|
6
6
|
export declare function isWakeRouteKind(value: unknown): value is WakeRouteKind;
|
|
7
7
|
export interface WakeRoute {
|
|
8
|
-
|
|
8
|
+
location: string;
|
|
9
|
+
participant: string;
|
|
9
10
|
sessionId: string;
|
|
11
|
+
channel: string;
|
|
10
12
|
kind: WakeRouteKind;
|
|
11
13
|
address: Record<string, string>;
|
|
12
14
|
updatedAt: number;
|
|
@@ -40,11 +42,10 @@ export type StoredAct = Act & {
|
|
|
40
42
|
at: number;
|
|
41
43
|
};
|
|
42
44
|
export type StoredActHead = StoredAct extends infer T ? T extends StoredAct ? Omit<T, 'body' | 'through' | 'index'> : never : never;
|
|
43
|
-
export type ObservationState = '
|
|
45
|
+
export type ObservationState = 'seen';
|
|
44
46
|
export interface ActivityObservation {
|
|
45
47
|
state: ObservationState;
|
|
46
48
|
at: number;
|
|
47
|
-
ownerId?: string;
|
|
48
49
|
}
|
|
49
50
|
export interface WatchLeaseFilter {
|
|
50
51
|
participants?: string[];
|
|
@@ -52,8 +53,6 @@ export interface WatchLeaseFilter {
|
|
|
52
53
|
}
|
|
53
54
|
export interface WatchLease {
|
|
54
55
|
leaseId: string;
|
|
55
|
-
/** Machine-local owner that may claim automatic delivery for this lease. */
|
|
56
|
-
ownerId?: string;
|
|
57
56
|
heartbeatAt: number;
|
|
58
57
|
expiresAt: number;
|
|
59
58
|
filter?: WatchLeaseFilter;
|
|
@@ -69,22 +68,13 @@ export interface InboxNotification {
|
|
|
69
68
|
export interface InboxMembership {
|
|
70
69
|
name: string;
|
|
71
70
|
squarePath: string;
|
|
72
|
-
ownerId?: string;
|
|
73
71
|
notifications: InboxNotification[];
|
|
74
72
|
catchLease?: WatchLease;
|
|
75
73
|
}
|
|
76
|
-
export interface NotifyLease {
|
|
77
|
-
leaseId: string;
|
|
78
|
-
expiresAt: number;
|
|
79
|
-
phase: 'claimed' | 'dispatching';
|
|
80
|
-
attemptN?: number;
|
|
81
|
-
routeKind?: WakeRouteKind;
|
|
82
|
-
}
|
|
83
74
|
export interface SquareRuntimeState {
|
|
84
75
|
nextActIndex: number;
|
|
85
76
|
observations: Record<string, Record<string, ActivityObservation>>;
|
|
86
77
|
leases: Record<string, WatchLease>;
|
|
87
|
-
notifyLeases: Record<string, NotifyLease>;
|
|
88
78
|
}
|
|
89
79
|
export interface SquareState {
|
|
90
80
|
hardCap: HardCap;
|
|
@@ -97,12 +87,12 @@ export interface SquareState {
|
|
|
97
87
|
export interface ActivitiesOptions {
|
|
98
88
|
lastN?: number | null;
|
|
99
89
|
participants?: string[];
|
|
100
|
-
/** Exclusive upper bound on act.at (ms). */
|
|
101
|
-
before?: number;
|
|
102
90
|
/** Exclusive lower bound on act.at (ms). Alias of --since. */
|
|
103
91
|
after?: number;
|
|
104
92
|
/** Exclusive lower bound on stable act index (--after act/<index>). */
|
|
105
93
|
afterIndex?: number;
|
|
94
|
+
/** Exclusive upper bound on stable act index (--before act/<index>). */
|
|
95
|
+
beforeIndex?: number;
|
|
106
96
|
/** Center act indexes for context windows (--at). */
|
|
107
97
|
atIndexes?: number[];
|
|
108
98
|
beforeContext?: number;
|
|
@@ -111,7 +101,6 @@ export interface ActivitiesOptions {
|
|
|
111
101
|
/** Undelivered mention/bell items for viewer only (read-only; requires viewer). */
|
|
112
102
|
pending?: boolean;
|
|
113
103
|
viewer?: string;
|
|
114
|
-
full?: boolean;
|
|
115
104
|
grep?: string;
|
|
116
105
|
fixed?: string;
|
|
117
106
|
order?: 'asc' | 'desc';
|
package/dist/notifications.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { planActNotifications, type
|
|
1
|
+
import { planActNotifications, type WakeAdapter } from './delivery.js';
|
|
2
2
|
import { type SquareState } from './model.js';
|
|
3
3
|
import { matchesMentionTarget } from './runtime.js';
|
|
4
4
|
import { type ActivityId } from './square-core.js';
|
|
5
|
-
import type { WakeNotifier } from './square-facade.js';
|
|
6
5
|
export type { PlannedNotification } from './delivery.js';
|
|
7
6
|
export { planActNotifications, matchesMentionTarget };
|
|
8
7
|
export { notificationMessageId } from './delivery.js';
|
|
@@ -17,17 +16,14 @@ interface ProcessNotificationOptions {
|
|
|
17
16
|
env?: NodeJS.ProcessEnv;
|
|
18
17
|
now?: () => number;
|
|
19
18
|
}
|
|
20
|
-
export declare function processActNotificationsOnce(squarePath: string, actIndex: number, opts?: ProcessNotificationOptions): Promise<
|
|
21
|
-
interface
|
|
22
|
-
launchWorker?: (workerPath: string, args: string[]) => void;
|
|
19
|
+
export declare function processActNotificationsOnce(squarePath: string, actIndex: number, opts?: ProcessNotificationOptions): Promise<import("./ports.js").DeliveryResult>;
|
|
20
|
+
export interface SweepPendingNotificationsOptions {
|
|
23
21
|
env?: NodeJS.ProcessEnv;
|
|
24
|
-
}
|
|
25
|
-
export declare function wakeNotifierForSquare(squarePath: string, env?: NodeJS.ProcessEnv): WakeNotifier;
|
|
26
|
-
export interface SweepPendingNotificationsOptions extends WorkerLaunchOptions {
|
|
27
22
|
now?: number;
|
|
28
23
|
limit?: number;
|
|
24
|
+
dispatchCandidate?: (actIndex: number) => void | Promise<void>;
|
|
29
25
|
}
|
|
30
26
|
/** Select sweep candidates from one frozen snapshot and one delivery replay. */
|
|
31
|
-
export declare function pendingNotificationSweepFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, limit: number, deriveDelivery?: (snapshot: SquareState) =>
|
|
32
|
-
/**
|
|
27
|
+
export declare function pendingNotificationSweepFromState(squarePath: string, state: SquareState, now: number, env: NodeJS.ProcessEnv, limit: number, deriveDelivery?: (snapshot: import('./model.js').SquareState) => ReturnType<typeof import('./delivery.js').deriveDeliveryModel>): Promise<number[]>;
|
|
28
|
+
/** Select old pending attention at a bounded action boundary for an explicit executor. */
|
|
33
29
|
export declare function sweepPendingNotifications(squarePath: string, opts?: SweepPendingNotificationsOptions): Promise<number[]>;
|
package/dist/notifications.js
CHANGED
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
1
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { sessionInbox } from './inbox.js';
|
|
7
|
-
import { hasPresentedAttention } from './presented.js';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { planActNotifications, } from './delivery.js';
|
|
8
4
|
import { SquareError } from './model.js';
|
|
9
5
|
import { SLEEP_MS, matchesMentionTarget } from './runtime.js';
|
|
10
|
-
import { parseActivityId } from './square-core.js';
|
|
11
|
-
import { displayAttentionPath
|
|
12
|
-
import { lookupParticipant } from './registry.js';
|
|
13
|
-
import { retireWakeRoute } from './routes.js';
|
|
6
|
+
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
7
|
+
import { displayAttentionPath } from './attention-presentation.js';
|
|
14
8
|
import { openSquare } from './square-file-adapter.js';
|
|
15
|
-
import { markNotificationNotified } from './square-wiring.js';
|
|
16
9
|
import { closeOpenSquare } from './open-square.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { WakePort } from './wake-port.js';
|
|
22
|
-
const NOTIFY_LEASE_MS = 5 * 60 * 1000;
|
|
10
|
+
import { notificationDelivered, resolveParticipant } from './views.js';
|
|
11
|
+
import { deliverPending, sweepPending, sweepPendingFromState } from './delivery-operations.js';
|
|
12
|
+
import { projectPresentationEvidence } from './square-projections.js';
|
|
13
|
+
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
23
14
|
export { planActNotifications, matchesMentionTarget };
|
|
24
15
|
export { notificationMessageId } from './delivery.js';
|
|
25
16
|
export function wakeGraceMs(env = process.env) {
|
|
@@ -36,48 +27,14 @@ function wakeLabel(kind) {
|
|
|
36
27
|
return 'codex-queue';
|
|
37
28
|
return kind;
|
|
38
29
|
}
|
|
39
|
-
function renderWakePayload(request
|
|
30
|
+
function renderWakePayload(request) {
|
|
40
31
|
return [
|
|
41
|
-
`<system-reminder source="square" wake="${wakeLabel(kind)}">`,
|
|
42
|
-
`square: ${displayAttentionPath(request.
|
|
43
|
-
|
|
44
|
-
squarePath: request.squarePath,
|
|
45
|
-
actIndex: request.actIndex,
|
|
46
|
-
recipient: request.recipient,
|
|
47
|
-
actor: request.actor,
|
|
48
|
-
route: request.route,
|
|
49
|
-
body,
|
|
50
|
-
compact: true,
|
|
51
|
-
}),
|
|
32
|
+
`<system-reminder source="square" wake="${wakeLabel(request.route.kind)}">`,
|
|
33
|
+
`square: ${displayAttentionPath(request.location)}`,
|
|
34
|
+
`attention: ${request.activity} for ${request.participant}`,
|
|
52
35
|
'</system-reminder>',
|
|
53
36
|
].join('\n');
|
|
54
37
|
}
|
|
55
|
-
async function waitForCatch(route, request, body) {
|
|
56
|
-
const binding = (await lookupParticipant(request.squarePath, request.recipient))
|
|
57
|
-
.find((item) => item.ownerId === route.ownerId);
|
|
58
|
-
const activeCatch = binding && (await sessionInbox(binding.sessionId))
|
|
59
|
-
.find((item) => item.name === request.recipient)?.catchLease;
|
|
60
|
-
if (!activeCatch || !leaseOwnsNotification(activeCatch, {
|
|
61
|
-
actor: request.actor,
|
|
62
|
-
body,
|
|
63
|
-
route: request.route,
|
|
64
|
-
recipient: request.recipient,
|
|
65
|
-
}))
|
|
66
|
-
return false;
|
|
67
|
-
const deadline = Date.now() + 180_000;
|
|
68
|
-
while (Date.now() < deadline) {
|
|
69
|
-
if (await hasDeliveredNotification(request.squarePath, request.recipient, request.actIndex))
|
|
70
|
-
return true;
|
|
71
|
-
const currentBinding = (await lookupParticipant(request.squarePath, request.recipient))
|
|
72
|
-
.find((item) => item.ownerId === route.ownerId);
|
|
73
|
-
const lease = currentBinding && (await sessionInbox(currentBinding.sessionId))
|
|
74
|
-
.find((item) => item.name === request.recipient)?.catchLease;
|
|
75
|
-
if (!lease || lease.expiresAt <= Date.now())
|
|
76
|
-
return false;
|
|
77
|
-
await sleep(Math.min(250, lease.expiresAt - Date.now()));
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
38
|
function notificationIndex(ref) {
|
|
82
39
|
if (typeof ref === 'number')
|
|
83
40
|
return ref;
|
|
@@ -101,7 +58,11 @@ export async function hasAttentionNotification(squarePath, name, ref, env = proc
|
|
|
101
58
|
try {
|
|
102
59
|
const recipient = (await resolveParticipant(square, name)).name;
|
|
103
60
|
const index = notificationIndex(ref);
|
|
104
|
-
|
|
61
|
+
if (await notificationDelivered(square, recipient, index))
|
|
62
|
+
return true;
|
|
63
|
+
const root = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
|
|
64
|
+
const hostLedger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? root, localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? root, readableScopes: ['user'], writableScope: 'user' });
|
|
65
|
+
return (await projectPresentationEvidence({ hostLedger, location: squarePath, participant: recipient, activity: formatActivityId(index), now: Date.now() })).some((row) => row.outcome === 'presented');
|
|
105
66
|
}
|
|
106
67
|
finally {
|
|
107
68
|
await closeOpenSquare(square);
|
|
@@ -134,164 +95,82 @@ async function defaultWakeAdapters() {
|
|
|
134
95
|
}
|
|
135
96
|
return adapters;
|
|
136
97
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
98
|
+
function createWakeTransport(adapters, hostLedger, clock) {
|
|
99
|
+
return {
|
|
100
|
+
attempt: async (request, _timeoutMs) => {
|
|
101
|
+
const adapter = adapters.find((candidate) => candidate.kind === request.route.kind);
|
|
102
|
+
if (adapter === undefined)
|
|
103
|
+
return { outcome: 'failed', message: 'wake adapter unavailable' };
|
|
104
|
+
try {
|
|
105
|
+
const result = await adapter.dispatch(request.route.address, renderWakePayload(request), async () => true);
|
|
106
|
+
if (result.outcome === 'accepted')
|
|
107
|
+
return { outcome: 'accepted' };
|
|
108
|
+
if (result.outcome === 'failed')
|
|
109
|
+
return { outcome: 'failed', message: result.message };
|
|
110
|
+
if (result.outcome === 'unavailable')
|
|
111
|
+
return { outcome: 'failed', message: result.message, unavailable: true };
|
|
112
|
+
if (result.outcome === 'unknown')
|
|
113
|
+
return { outcome: 'unknown', diagnostic: result.message };
|
|
114
|
+
return { outcome: 'unknown', diagnostic: 'wake dispatch cancelled' };
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return { outcome: 'unknown', diagnostic: error instanceof Error ? error.message : String(error) };
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
invalidate: async (request) => {
|
|
121
|
+
await hostLedger.ensurePresence({
|
|
122
|
+
location: request.route.location,
|
|
123
|
+
participant: request.route.participant,
|
|
124
|
+
session: request.route.sessionId,
|
|
125
|
+
channel: request.route.channel,
|
|
126
|
+
updatedAt: clock(),
|
|
127
|
+
}, 'user');
|
|
128
|
+
},
|
|
129
|
+
};
|
|
146
130
|
}
|
|
147
|
-
async function
|
|
131
|
+
export async function processActNotificationsOnce(squarePath, actIndex, opts = {}) {
|
|
148
132
|
const env = opts.env ?? process.env;
|
|
149
133
|
const now = opts.now ?? Date.now;
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
155
|
-
const
|
|
156
|
-
if (!wakeIsEligible(await wakeEvidence(squarePath, notification.recipient, notification.item.index, initialAt, env)))
|
|
157
|
-
return;
|
|
158
|
-
const square = await openSquare(squarePath, { clock: now });
|
|
159
|
-
const claim = await claimNotifyLease(square, notification.recipient, notification.item.index);
|
|
160
|
-
if (claim.type === 'busy' || claim.type === 'delivered') {
|
|
161
|
-
await closeOpenSquare(square);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
if (claim.type === 'ambiguous') {
|
|
165
|
-
const recovered = await recordRecoveredUnknown(attention, claim.lease, env);
|
|
166
|
-
if (recovered !== undefined) {
|
|
167
|
-
await releaseNotifyLease(square, notification.recipient, notification.item.index, claim.lease.leaseId);
|
|
168
|
-
}
|
|
169
|
-
await closeOpenSquare(square);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const { leaseId } = claim;
|
|
173
|
-
let releaseLease = true;
|
|
134
|
+
const ledgerRoot = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
|
|
135
|
+
const hostLedger = createHostLedgerPort({
|
|
136
|
+
userPath: env.SQUARE_HOST_LEDGER_USER ?? ledgerRoot,
|
|
137
|
+
localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? ledgerRoot,
|
|
138
|
+
});
|
|
139
|
+
const square = await openSquare(squarePath, { clock: now, hostLedger, env });
|
|
174
140
|
try {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
return;
|
|
179
|
-
const port = new WakePort(opts.adapters ?? await defaultWakeAdapters());
|
|
180
|
-
const request = {
|
|
181
|
-
squarePath,
|
|
182
|
-
actIndex: notification.item.index,
|
|
183
|
-
recipient: notification.recipient,
|
|
184
|
-
actor: notification.item.actor,
|
|
185
|
-
route: notification.route,
|
|
186
|
-
};
|
|
187
|
-
await port.dispatch(evidence.attemptableRoutes, (route) => renderWakePayload(request, notification.item.body, route.kind), {
|
|
188
|
-
nextAttemptN: async () => nextWakeAttemptNumber(attention, { env, now: now() }),
|
|
189
|
-
beforeSend: async (route, attemptN) => {
|
|
190
|
-
if (await waitForCatch(route, request, notification.item.body))
|
|
191
|
-
return false;
|
|
192
|
-
const currentAt = now();
|
|
193
|
-
if (!(await entryPresentation(square, notification.recipient)).joined)
|
|
194
|
-
return false;
|
|
195
|
-
const current = await wakeEvidence(squarePath, notification.recipient, notification.item.index, currentAt, env);
|
|
196
|
-
if (!wakeIsEligible(current))
|
|
197
|
-
return false;
|
|
198
|
-
if (!current.attemptableRoutes.some((candidate) => candidate.ownerId === route.ownerId && candidate.kind === route.kind && candidate.sessionId === route.sessionId))
|
|
199
|
-
return false;
|
|
200
|
-
const dispatching = await transitionNotifyLease(square, notification.recipient, notification.item.index, leaseId, 'dispatching', route.kind, attemptN);
|
|
201
|
-
if (dispatching)
|
|
202
|
-
releaseLease = false;
|
|
203
|
-
return dispatching;
|
|
204
|
-
},
|
|
205
|
-
record: async (route, attemptN, outcome) => {
|
|
206
|
-
if (outcome.outcome === 'failed') {
|
|
207
|
-
await transitionNotifyLease(square, notification.recipient, notification.item.index, leaseId, 'claimed');
|
|
208
|
-
releaseLease = true;
|
|
209
|
-
}
|
|
210
|
-
await recordWakeAttempt({
|
|
211
|
-
attention,
|
|
212
|
-
routeKind: route.kind,
|
|
213
|
-
outcome: outcome.outcome,
|
|
214
|
-
attemptN,
|
|
215
|
-
at: now(),
|
|
216
|
-
...('signature' in outcome ? { signature: outcome.signature } : {}),
|
|
217
|
-
...('message' in outcome ? { message: outcome.message } : {}),
|
|
218
|
-
...('diagnostic' in outcome && outcome.diagnostic !== undefined ? { diagnostic: outcome.diagnostic } : {}),
|
|
219
|
-
}, env);
|
|
220
|
-
if (outcome.outcome === 'accepted') {
|
|
221
|
-
await markNotificationNotified(square, notification.recipient, notification.item.index, route.ownerId, now());
|
|
222
|
-
}
|
|
223
|
-
if (outcome.outcome !== 'failed')
|
|
224
|
-
releaseLease = true;
|
|
225
|
-
},
|
|
226
|
-
invalidate: async (route) => {
|
|
227
|
-
await retireWakeRoute(route, { env, at: now() });
|
|
228
|
-
},
|
|
229
|
-
});
|
|
141
|
+
const adapters = opts.adapters ?? await defaultWakeAdapters();
|
|
142
|
+
const transport = createWakeTransport(adapters, hostLedger, now);
|
|
143
|
+
return await deliverPending({ artifact: square.artifact, hostLedger, transport, location: squarePath, activity: actIndex, timeoutMs: Number(env.SQUARE_NOTIFY_DELIVERY_WAIT_MS ?? 5000), now: now() });
|
|
230
144
|
}
|
|
231
145
|
finally {
|
|
232
|
-
if (releaseLease) {
|
|
233
|
-
await releaseNotifyLease(square, notification.recipient, notification.item.index, leaseId);
|
|
234
|
-
}
|
|
235
146
|
await closeOpenSquare(square);
|
|
236
147
|
}
|
|
237
148
|
}
|
|
238
|
-
export async function processActNotificationsOnce(squarePath, actIndex, opts = {}) {
|
|
239
|
-
const square = await openSquare(squarePath);
|
|
240
|
-
const notifications = await notificationForAct(square, actIndex).finally(() => closeOpenSquare(square));
|
|
241
|
-
await Promise.all(notifications.map((notification) => processNotification(squarePath, notification, opts)));
|
|
242
|
-
}
|
|
243
|
-
function launchWorker(workerPath, args) {
|
|
244
|
-
const child = spawn(process.execPath, [workerPath, ...args], { detached: true, stdio: 'ignore', env: process.env });
|
|
245
|
-
child.unref();
|
|
246
|
-
}
|
|
247
|
-
export function wakeNotifierForSquare(squarePath, env = process.env) {
|
|
248
|
-
return {
|
|
249
|
-
wake(recipients, activity) {
|
|
250
|
-
if (env.SQUARE_DISABLE_PASEO_WAKE === '1' || recipients.length === 0)
|
|
251
|
-
return;
|
|
252
|
-
const actIndex = parseActivityId(activity.id);
|
|
253
|
-
if (actIndex === undefined)
|
|
254
|
-
return;
|
|
255
|
-
launchWorker(fileURLToPath(new URL('./cmd/notify-once.js', import.meta.url)), ['--location', squarePath, '--act-index', String(actIndex)]);
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
149
|
/** Select sweep candidates from one frozen snapshot and one delivery replay. */
|
|
260
|
-
export async function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
const evidence = await wakeEvidenceProjectionFromState(squarePath, state, now, env, delivery);
|
|
264
|
-
const indexes = new Set();
|
|
265
|
-
for (const recipient of pending) {
|
|
266
|
-
for (const note of recipient.notifications) {
|
|
267
|
-
if (now - note.item.at <= wakeGraceMs(env))
|
|
268
|
-
continue;
|
|
269
|
-
if (!wakeIsEligible(evidence.evidence(recipient.recipient, note.item.index)))
|
|
270
|
-
continue;
|
|
271
|
-
indexes.add(note.item.index);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return [...indexes].sort((left, right) => left - right).slice(0, Math.max(0, limit));
|
|
150
|
+
export async function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery) {
|
|
151
|
+
const ledger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER, writableScope: 'user', readableScopes: ['user'] });
|
|
152
|
+
return sweepPendingFromState({ state, hostLedger: ledger, location: squarePath, now, graceMs: wakeGraceMs(env), limit, deriveDelivery });
|
|
275
153
|
}
|
|
276
|
-
/**
|
|
154
|
+
/** Select old pending attention at a bounded action boundary for an explicit executor. */
|
|
277
155
|
export async function sweepPendingNotifications(squarePath, opts = {}) {
|
|
278
156
|
const env = opts.env ?? process.env;
|
|
279
157
|
if (env.SQUARE_DISABLE_PASEO_WAKE === '1')
|
|
280
158
|
return [];
|
|
281
159
|
const now = opts.now ?? Date.now();
|
|
282
160
|
const limit = opts.limit ?? 8;
|
|
283
|
-
const
|
|
284
|
-
|
|
161
|
+
const ledgerRoot = env.SQUARE_REGISTRY === undefined ? undefined : path.dirname(env.SQUARE_REGISTRY);
|
|
162
|
+
const hostLedger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? ledgerRoot, localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? ledgerRoot });
|
|
163
|
+
const square = await openSquare(squarePath, { clock: () => now, hostLedger, env });
|
|
164
|
+
let selected;
|
|
285
165
|
try {
|
|
286
|
-
({
|
|
166
|
+
selected = await sweepPending({ artifact: square.artifact, hostLedger, location: squarePath, now, graceMs: wakeGraceMs(env), limit });
|
|
287
167
|
}
|
|
288
168
|
finally {
|
|
289
169
|
await closeOpenSquare(square);
|
|
290
170
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
(opts.launchWorker ?? launchWorker)(workerPath, ['--location', squarePath, '--act-index', String(actIndex)]);
|
|
171
|
+
if (opts.dispatchCandidate !== undefined) {
|
|
172
|
+
for (const actIndex of selected)
|
|
173
|
+
await opts.dispatchCandidate(actIndex);
|
|
295
174
|
}
|
|
296
175
|
return selected;
|
|
297
176
|
}
|
package/dist/open-square.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { WakeNotifier } from './square-facade.js';
|
|
1
|
+
import type { HostLedgerPort, SquareArtifactPort } from './ports.js';
|
|
3
2
|
/** Private binding assembled by storage and consumed by the four concerns. */
|
|
4
3
|
export interface OpenSquare {
|
|
5
|
-
readonly
|
|
4
|
+
readonly artifact: SquareArtifactPort;
|
|
6
5
|
readonly clock: () => number;
|
|
7
|
-
readonly notifier?: WakeNotifier;
|
|
8
6
|
readonly location: string;
|
|
7
|
+
readonly hostLedger?: HostLedgerPort;
|
|
8
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
9
9
|
}
|
|
10
10
|
/** Package-private lifecycle boundary for bound squares. */
|
|
11
11
|
export declare function closeOpenSquare(square: OpenSquare): Promise<void>;
|
package/dist/open-square.js
CHANGED
package/dist/ports.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { InboxNotification, SquareState, StoredAct, WakeRoute, WatchLease } from './model.js';
|
|
2
|
+
import type { PlannedNotification } from './delivery.js';
|
|
3
|
+
export type { ClaimResult, EvidenceClaim, EvidenceRelease, EvidenceGc, EvidenceLookup, EvidenceRecord, HostLedgerPort, HostLedgerScope, PresenceKey, PresenceLookup, PresenceRecord, PresenceChannel, PresenceResult, ReconcileBindingInput as HostLedgerReconcileBindingInput, ReconcileBindingResult, } from './host-ledger.js';
|
|
4
|
+
import type { HostLedgerPort } from './host-ledger.js';
|
|
5
|
+
/** Atomic artifact access. Never exposes framing, compression, locks, or storage schema. */
|
|
6
|
+
export interface SquareArtifactPort {
|
|
7
|
+
read(): Promise<{
|
|
8
|
+
state: SquareState;
|
|
9
|
+
version: number;
|
|
10
|
+
}>;
|
|
11
|
+
transact<R>(fn: (state: SquareState, version: number) => {
|
|
12
|
+
state?: SquareState;
|
|
13
|
+
result: R;
|
|
14
|
+
}): Promise<R>;
|
|
15
|
+
changed(sinceVersion: number, timeoutMs: number): Promise<boolean>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** Capability-neutral wake transport. Unused by this Contract's activity operations. */
|
|
19
|
+
export interface WakeTransportPort {
|
|
20
|
+
attempt(request: WakeRequest, timeoutMs: number): Promise<WakeOutcome>;
|
|
21
|
+
/** Optional route retirement supplied by the concrete executor adapter. */
|
|
22
|
+
invalidate?(request: WakeRequest): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface WakeRequest {
|
|
25
|
+
readonly location: string;
|
|
26
|
+
readonly participant: string;
|
|
27
|
+
readonly activity: string;
|
|
28
|
+
readonly route: WakeRoute;
|
|
29
|
+
}
|
|
30
|
+
/** Executor-neutral presentation capability; intentionally separate from wake transport. */
|
|
31
|
+
export interface PresentationSinkPort {
|
|
32
|
+
present(activity: StoredAct): void | Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/** Stable projection of a host binding; storage rows never cross this boundary. */
|
|
35
|
+
export interface SessionBindingProjection {
|
|
36
|
+
readonly location: string;
|
|
37
|
+
readonly participant: string;
|
|
38
|
+
readonly sessionId: string;
|
|
39
|
+
readonly channel: import('./host-ledger.js').PresenceChannel;
|
|
40
|
+
readonly route?: WakeRoute;
|
|
41
|
+
readonly updatedAt: number;
|
|
42
|
+
}
|
|
43
|
+
/** Pending boundary presentation projected for one bound session. */
|
|
44
|
+
export interface PresentationProjection {
|
|
45
|
+
readonly binding: SessionBindingProjection;
|
|
46
|
+
readonly joined: boolean;
|
|
47
|
+
readonly notifications: readonly InboxNotification[];
|
|
48
|
+
readonly catchLease?: WatchLease;
|
|
49
|
+
}
|
|
50
|
+
/** Read-only presentation evidence projection for diagnostics and migration callers. */
|
|
51
|
+
export interface PresentationEvidenceProjection {
|
|
52
|
+
readonly location: string;
|
|
53
|
+
readonly participant: string;
|
|
54
|
+
readonly sessionId: string;
|
|
55
|
+
readonly activity: string;
|
|
56
|
+
readonly outcome: string;
|
|
57
|
+
readonly at?: number;
|
|
58
|
+
}
|
|
59
|
+
export type WakeOutcome = {
|
|
60
|
+
readonly outcome: 'accepted';
|
|
61
|
+
readonly signature?: string;
|
|
62
|
+
readonly attemptN?: number;
|
|
63
|
+
} | {
|
|
64
|
+
readonly outcome: 'failed';
|
|
65
|
+
readonly message?: string;
|
|
66
|
+
readonly attemptN?: number;
|
|
67
|
+
readonly unavailable?: boolean;
|
|
68
|
+
} | {
|
|
69
|
+
readonly outcome: 'unknown';
|
|
70
|
+
readonly diagnostic?: string;
|
|
71
|
+
readonly attemptN?: number;
|
|
72
|
+
};
|
|
73
|
+
export interface SquareObservation {
|
|
74
|
+
readonly location?: string;
|
|
75
|
+
readonly version: number;
|
|
76
|
+
readonly state: SquareState;
|
|
77
|
+
readonly pending: readonly {
|
|
78
|
+
readonly recipient: string;
|
|
79
|
+
readonly notifications: readonly PlannedNotification[];
|
|
80
|
+
}[];
|
|
81
|
+
readonly bindings: readonly SessionBindingProjection[];
|
|
82
|
+
}
|
|
83
|
+
export interface ObserveSquareInput {
|
|
84
|
+
readonly artifact: SquareArtifactPort;
|
|
85
|
+
readonly hostLedger?: HostLedgerPort;
|
|
86
|
+
readonly location?: string;
|
|
87
|
+
readonly now?: number;
|
|
88
|
+
}
|
|
89
|
+
export interface ReconcileBindingInput {
|
|
90
|
+
readonly artifact: SquareArtifactPort;
|
|
91
|
+
readonly hostLedger: HostLedgerPort;
|
|
92
|
+
readonly location?: string;
|
|
93
|
+
readonly scopes?: readonly import('./host-ledger.js').HostLedgerScope[];
|
|
94
|
+
readonly now?: number;
|
|
95
|
+
}
|
|
96
|
+
export interface DeliveryResult {
|
|
97
|
+
readonly attempted: number;
|
|
98
|
+
readonly accepted: number;
|
|
99
|
+
readonly failed: number;
|
|
100
|
+
readonly unknown: number;
|
|
101
|
+
}
|
|
102
|
+
export interface DeliverPendingInput {
|
|
103
|
+
readonly artifact: SquareArtifactPort;
|
|
104
|
+
readonly hostLedger: HostLedgerPort;
|
|
105
|
+
readonly transport: WakeTransportPort;
|
|
106
|
+
readonly location: string;
|
|
107
|
+
readonly timeoutMs?: number;
|
|
108
|
+
readonly now?: number;
|
|
109
|
+
readonly activity?: string | number;
|
|
110
|
+
}
|
|
111
|
+
export interface PresentationResult {
|
|
112
|
+
readonly presented: boolean;
|
|
113
|
+
readonly activity?: StoredAct;
|
|
114
|
+
}
|
|
115
|
+
export interface PresentPendingInput {
|
|
116
|
+
readonly artifact: SquareArtifactPort;
|
|
117
|
+
readonly location: string;
|
|
118
|
+
readonly participant: string;
|
|
119
|
+
readonly activity: string | number;
|
|
120
|
+
readonly sink: PresentationSinkPort;
|
|
121
|
+
readonly hostLedger?: HostLedgerPort;
|
|
122
|
+
readonly session?: string;
|
|
123
|
+
readonly now?: number;
|
|
124
|
+
readonly timeoutMs?: number;
|
|
125
|
+
/** Boundary previews that were clipped remain pending for a later full presentation. */
|
|
126
|
+
readonly markSeen?: boolean;
|
|
127
|
+
}
|
package/dist/ports.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/presence.d.ts
CHANGED
|
@@ -3,5 +3,4 @@ import type { OpenSquare } from './open-square.js';
|
|
|
3
3
|
import type { CatchOptions, CatchResult } from './square-facade.js';
|
|
4
4
|
export declare function catchUp(square: OpenSquare, name: string, options?: CatchOptions, deriveDelivery?: (state: import('./model.js').SquareState) => DeliveryModel): Promise<CatchResult>;
|
|
5
5
|
/** Commit seen only for complete, actually rendered boundary bodies. */
|
|
6
|
-
export declare function markBoundarySeen(squarePath: string, name: string,
|
|
7
|
-
export declare function markNotificationNotified(square: OpenSquare, name: string, actIndex: number, ownerId: string | undefined, at?: number): Promise<void>;
|
|
6
|
+
export declare function markBoundarySeen(squarePath: string, name: string, actIndexes: readonly number[], at?: number): Promise<void>;
|