@astrosheep/square 0.3.61 → 0.3.62
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/skills/square/SKILL.md +5 -7
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/dist/activity.d.ts +6 -2
- package/dist/activity.js +168 -133
- package/dist/artifact.d.ts +4 -0
- package/dist/artifact.js +43 -14
- package/dist/attention-presentation.d.ts +1 -0
- package/dist/attention-presentation.js +8 -6
- package/dist/automatic-session.js +14 -24
- package/dist/boundary-presentation.d.ts +1 -2
- package/dist/boundary-presentation.js +4 -19
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +2 -18
- package/dist/cli/harness-command.js +1 -1
- package/dist/cli/mcp-server-command.d.ts +2 -0
- package/dist/cli/mcp-server-command.js +13 -0
- package/dist/cli/observation-commands.js +47 -19
- package/dist/cli/program.js +5 -2
- package/dist/cli/registry.js +2 -0
- package/dist/cli/square-commands.d.ts +1 -1
- package/dist/cli/square-commands.js +93 -125
- package/dist/decisions.d.ts +2 -0
- package/dist/decisions.js +44 -3
- package/dist/delivery-operations.d.ts +1 -2
- package/dist/delivery-operations.js +1 -4
- package/dist/file-changes.d.ts +12 -0
- package/dist/file-changes.js +235 -0
- package/dist/help.js +4 -8
- package/dist/host-ledger-file-adapter.d.ts +15 -13
- package/dist/host-ledger-file-adapter.js +131 -79
- package/dist/host-ledger-root.d.ts +2 -0
- package/dist/host-ledger-root.js +9 -0
- package/dist/host-ledger.d.ts +4 -24
- package/dist/inbox.d.ts +10 -5
- package/dist/inbox.js +152 -67
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +122 -0
- package/dist/mention-parse.d.ts +8 -0
- package/dist/mention-parse.js +107 -0
- package/dist/model.d.ts +5 -0
- package/dist/model.js +4 -1
- package/dist/notifications.d.ts +1 -0
- package/dist/notifications.js +25 -20
- package/dist/participant-host.js +2 -2
- package/dist/ports.d.ts +2 -9
- package/dist/presentation.d.ts +12 -1
- package/dist/presentation.js +42 -12
- package/dist/presented.js +2 -1
- package/dist/registry.d.ts +8 -4
- package/dist/registry.js +41 -74
- package/dist/routes.d.ts +19 -2
- package/dist/routes.js +48 -5
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +2 -0
- package/dist/square-actions.d.ts +26 -13
- package/dist/square-actions.js +200 -84
- package/dist/square-application.d.ts +58 -0
- package/dist/square-application.js +183 -0
- package/dist/square-facade.d.ts +24 -9
- package/dist/square-file-adapter.d.ts +5 -12
- package/dist/square-file-adapter.js +12 -64
- package/dist/square-projections.d.ts +3 -2
- package/dist/square-projections.js +12 -3
- package/dist/square-storage.js +27 -34
- package/dist/square-wiring.d.ts +12 -7
- package/dist/square-wiring.js +36 -32
- package/dist/state-cell.d.ts +1 -1
- package/dist/stream.js +30 -26
- package/dist/views.d.ts +3 -3
- package/dist/views.js +4 -4
- package/dist/wake-attempts.js +2 -1
- package/dist/wake-evidence.js +2 -8
- package/dist/wakes.d.ts +1 -0
- package/dist/wakes.js +5 -5
- package/dist/watch.d.ts +6 -1
- package/dist/watch.js +20 -18
- package/extensions/square-pi.js +100 -112
- package/guides/application-boundary.md +32 -0
- package/guides/change-waits.md +28 -0
- package/guides/host-ledger.md +9 -0
- package/guides/mcp.md +9 -0
- package/guides/operation-cancellation.md +17 -0
- package/package.json +8 -2
- package/skills/square/SKILL.md +5 -7
package/dist/notifications.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare function hasDeliveredNotification(squarePath: string, name: strin
|
|
|
12
12
|
export declare function hasAttentionNotification(squarePath: string, name: string, ref: number | ActivityId, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
13
13
|
export declare function waitForDeliveredNotification(squarePath: string, name: string, ref: number | ActivityId, opts?: {
|
|
14
14
|
timeoutMs?: number;
|
|
15
|
+
signal?: AbortSignal;
|
|
15
16
|
}): Promise<boolean>;
|
|
16
17
|
interface ProcessNotificationOptions {
|
|
17
18
|
adapters?: WakeAdapter[];
|
package/dist/notifications.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { planActNotifications, } from './delivery.js';
|
|
5
5
|
import { SquareError } from './model.js';
|
|
6
|
-
import {
|
|
6
|
+
import { matchesMentionTarget } from './runtime.js';
|
|
7
7
|
import { formatActivityId, parseActivityId } from './square-core.js';
|
|
8
8
|
import { displayAttentionPath } from './attention-presentation.js';
|
|
9
|
-
import { openSquare } from './square-file-adapter.js';
|
|
9
|
+
import { openSquare, observeSquareChanges } from './square-file-adapter.js';
|
|
10
10
|
import { closeOpenSquare } from './open-square.js';
|
|
11
11
|
import { notificationDelivered, resolveParticipant } from './views.js';
|
|
12
12
|
import { deliverPending, observeSquare, sweepPending, sweepPendingFromState } from './delivery-operations.js';
|
|
@@ -63,8 +63,8 @@ export async function hasAttentionNotification(squarePath, name, ref, env = proc
|
|
|
63
63
|
const index = notificationIndex(ref);
|
|
64
64
|
if (await notificationDelivered(square, recipient, index))
|
|
65
65
|
return true;
|
|
66
|
-
const root =
|
|
67
|
-
const hostLedger = createHostLedgerPort({
|
|
66
|
+
const root = hostLedgerRoot(env);
|
|
67
|
+
const hostLedger = createHostLedgerPort({ rootPath: root });
|
|
68
68
|
return (await projectPresentationEvidence({ hostLedger, location: squarePath, participant: recipient, activity: formatActivityId(index), now: Date.now() })).some((row) => row.outcome === 'presented');
|
|
69
69
|
}
|
|
70
70
|
finally {
|
|
@@ -73,12 +73,20 @@ export async function hasAttentionNotification(squarePath, name, ref, env = proc
|
|
|
73
73
|
}
|
|
74
74
|
export async function waitForDeliveredNotification(squarePath, name, ref, opts = {}) {
|
|
75
75
|
const deadline = Date.now() + (opts.timeoutMs ?? 30000);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
const observer = await observeSquareChanges(squarePath);
|
|
77
|
+
try {
|
|
78
|
+
while (Date.now() <= deadline) {
|
|
79
|
+
const baseline = await observer.read(opts.signal);
|
|
80
|
+
if (await hasDeliveredNotification(squarePath, name, ref))
|
|
81
|
+
return true;
|
|
82
|
+
if (!await observer.changed(baseline, Math.max(0, deadline - Date.now()), opts.signal))
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
observer.close();
|
|
80
89
|
}
|
|
81
|
-
return false;
|
|
82
90
|
}
|
|
83
91
|
export async function defaultWakeAdapters() {
|
|
84
92
|
const adapters = [];
|
|
@@ -188,10 +196,9 @@ export function createWakeTransport(adapters, hostLedger, clock) {
|
|
|
188
196
|
export async function processActNotificationsOnce(squarePath, actIndex, opts = {}) {
|
|
189
197
|
const env = opts.env ?? process.env;
|
|
190
198
|
const now = opts.now ?? Date.now;
|
|
191
|
-
const ledgerRoot =
|
|
199
|
+
const ledgerRoot = hostLedgerRoot(env);
|
|
192
200
|
const hostLedger = createHostLedgerPort({
|
|
193
|
-
|
|
194
|
-
localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? ledgerRoot,
|
|
201
|
+
rootPath: ledgerRoot,
|
|
195
202
|
});
|
|
196
203
|
const square = await openSquare(squarePath, { clock: now, hostLedger, env });
|
|
197
204
|
try {
|
|
@@ -213,11 +220,11 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
|
|
|
213
220
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
214
221
|
if (remainingMs() === 0 || signal?.aborted)
|
|
215
222
|
return;
|
|
216
|
-
const root =
|
|
217
|
-
const hostLedger = createHostLedgerPort({
|
|
223
|
+
const root = hostLedgerRoot(env);
|
|
224
|
+
const hostLedger = createHostLedgerPort({ rootPath: root });
|
|
218
225
|
let indexed = [];
|
|
219
226
|
try {
|
|
220
|
-
indexed = await hostLedger.listPresence({
|
|
227
|
+
indexed = await hostLedger.listPresence({ now: Date.now() });
|
|
221
228
|
}
|
|
222
229
|
catch { /* capability is best effort */ }
|
|
223
230
|
const paths = new Set(indexed.map((binding) => binding.location));
|
|
@@ -237,7 +244,6 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
|
|
|
237
244
|
try {
|
|
238
245
|
const square = await openSquare(squarePath, { hostLedger, env, signal });
|
|
239
246
|
try {
|
|
240
|
-
await hostLedger.reconcileBinding({ artifact: square.artifact, scopes: ['user'], now: Date.now() }).catch(() => undefined);
|
|
241
247
|
if (remainingMs() === 0 || signal?.aborted)
|
|
242
248
|
break;
|
|
243
249
|
const limit = Number.parseInt(env.SQUARE_NOTIFY_SWEEP_LIMIT ?? '8', 10);
|
|
@@ -262,7 +268,7 @@ export async function sweepPrivilegedPending(cwd, env = process.env, suppliedAda
|
|
|
262
268
|
}
|
|
263
269
|
/** Select sweep candidates from one frozen snapshot and one delivery replay. */
|
|
264
270
|
export async function pendingNotificationSweepFromState(squarePath, state, now, env, limit, deriveDelivery) {
|
|
265
|
-
const ledger = createHostLedgerPort({
|
|
271
|
+
const ledger = createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
|
|
266
272
|
return sweepPendingFromState({ state, hostLedger: ledger, location: squarePath, now, graceMs: wakeGraceMs(env), limit, deriveDelivery });
|
|
267
273
|
}
|
|
268
274
|
/** Select old pending attention at a bounded action boundary for an explicit executor. */
|
|
@@ -272,8 +278,7 @@ export async function sweepPendingNotifications(squarePath, opts = {}) {
|
|
|
272
278
|
return [];
|
|
273
279
|
const now = opts.now ?? Date.now();
|
|
274
280
|
const limit = opts.limit ?? 8;
|
|
275
|
-
const
|
|
276
|
-
const hostLedger = createHostLedgerPort({ userPath: env.SQUARE_HOST_LEDGER_USER ?? ledgerRoot, localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? ledgerRoot });
|
|
281
|
+
const hostLedger = createHostLedgerPort({ rootPath: hostLedgerRoot(env) });
|
|
277
282
|
const square = await openSquare(squarePath, { clock: () => now, hostLedger, env });
|
|
278
283
|
let selected;
|
|
279
284
|
try {
|
package/dist/participant-host.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function ensureLocalPresence(context, participant, epoch) {
|
|
|
53
53
|
const now = context.clock();
|
|
54
54
|
// Semantic publish: an unexpired presence row already stands when it carries the same owner epoch.
|
|
55
55
|
try {
|
|
56
|
-
const existing = await context.hostLedger.listPresence({ location: context.location, participant, session: identity.session,
|
|
56
|
+
const existing = await context.hostLedger.listPresence({ location: context.location, participant, session: identity.session, now });
|
|
57
57
|
if (existing.some((row) => row.channel === identity.channel
|
|
58
58
|
&& now - (row.updatedAt ?? 0) < ROUTE_FRESH_MS
|
|
59
59
|
&& (ownerEpoch === undefined || row.epoch === ownerEpoch)))
|
|
@@ -68,7 +68,7 @@ export async function ensureLocalPresence(context, participant, epoch) {
|
|
|
68
68
|
// Presence rows are host-ledger wall-time evidence; the square clock belongs to artifact activities.
|
|
69
69
|
updatedAt: Date.now(),
|
|
70
70
|
...(ownerEpoch === undefined || ownerEpoch <= 0 ? {} : { epoch: ownerEpoch }),
|
|
71
|
-
}
|
|
71
|
+
}).catch((error) => ({
|
|
72
72
|
status: 'degraded',
|
|
73
73
|
record: { location: context.location, participant, session: identity.session, channel: identity.channel },
|
|
74
74
|
error,
|
package/dist/ports.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { InboxNotification, SquareState, StoredAct, WakeRoute, WatchLease } from './model.js';
|
|
2
2
|
import type { PlannedNotification } from './delivery.js';
|
|
3
|
-
export type { ClaimResult, EvidenceClaim, EvidenceRelease, EvidenceGc, EvidenceLookup, EvidenceRecord, HostLedgerPort,
|
|
3
|
+
export type { ClaimResult, EvidenceClaim, EvidenceRelease, EvidenceGc, EvidenceLookup, EvidenceRecord, HostLedgerPort, PresenceKey, PresenceLookup, PresenceRecord, PresenceClaimResult, PresenceChannel, PresenceResult, } from './host-ledger.js';
|
|
4
4
|
import type { HostLedgerPort } from './host-ledger.js';
|
|
5
5
|
/** Atomic artifact access. Never exposes framing, compression, locks, or storage schema. */
|
|
6
6
|
export interface SquareArtifactPort {
|
|
@@ -12,7 +12,7 @@ export interface SquareArtifactPort {
|
|
|
12
12
|
state?: SquareState;
|
|
13
13
|
result: R;
|
|
14
14
|
}, signal?: AbortSignal): Promise<R>;
|
|
15
|
-
changed(sinceVersion: number, timeoutMs: number): Promise<boolean>;
|
|
15
|
+
changed(sinceVersion: number, timeoutMs: number, signal?: AbortSignal): Promise<boolean>;
|
|
16
16
|
close(): Promise<void>;
|
|
17
17
|
}
|
|
18
18
|
/** Capability-neutral wake transport. Unused by this Contract's activity operations. */
|
|
@@ -101,13 +101,6 @@ export interface ObserveSquareInput {
|
|
|
101
101
|
readonly location?: string;
|
|
102
102
|
readonly now?: number;
|
|
103
103
|
}
|
|
104
|
-
export interface ReconcileBindingInput {
|
|
105
|
-
readonly artifact: SquareArtifactPort;
|
|
106
|
-
readonly hostLedger: HostLedgerPort;
|
|
107
|
-
readonly location?: string;
|
|
108
|
-
readonly scopes?: readonly import('./host-ledger.js').HostLedgerScope[];
|
|
109
|
-
readonly now?: number;
|
|
110
|
-
}
|
|
111
104
|
export interface DeliveryResult {
|
|
112
105
|
readonly attempted: number;
|
|
113
106
|
readonly accepted: number;
|
package/dist/presentation.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ interface ActivityLimitOptions extends ParticipantOutputOptions {
|
|
|
21
21
|
draftPath?: string;
|
|
22
22
|
}
|
|
23
23
|
interface ActivityBlockedOptions extends ParticipantOutputOptions {
|
|
24
|
+
retryCommand: string;
|
|
24
25
|
forceCommand: string;
|
|
25
26
|
activitySummaries: UnreadActivitySummary[];
|
|
26
27
|
unreadRoomChanges: RoomChangeAct[];
|
|
@@ -29,10 +30,12 @@ interface ActivityBlockedOptions extends ParticipantOutputOptions {
|
|
|
29
30
|
interface ExpressWaitingOptions {
|
|
30
31
|
reason: 'throttled' | 'held';
|
|
31
32
|
delayMs?: number;
|
|
33
|
+
holder?: string;
|
|
34
|
+
holdReason?: string;
|
|
32
35
|
}
|
|
33
36
|
interface ExpressNoWaitOptions extends ParticipantOutputOptions {
|
|
34
37
|
reason: 'throttled' | 'held';
|
|
35
|
-
|
|
38
|
+
retryCommand: string;
|
|
36
39
|
delayMs?: number;
|
|
37
40
|
holdReason?: string;
|
|
38
41
|
draftPath?: string;
|
|
@@ -58,6 +61,8 @@ export declare function commandPrefix(squarePath: string): string;
|
|
|
58
61
|
export declare function participantCommandPrefix(squarePath: string, name: string): string;
|
|
59
62
|
/** Blocked actions end with a full copy-pasteable recovery command. */
|
|
60
63
|
export declare function joinRecoveryCommand(squarePath: string, name: string): string;
|
|
64
|
+
/** Taking a name is an explicit recovery choice, never an automatic retry. */
|
|
65
|
+
export declare function takeoverRecoveryLines(squarePath: string, name: string): string[];
|
|
61
66
|
export declare function participantsRecoveryCommand(squarePath: string): string;
|
|
62
67
|
export declare function renderPresenceLines(participants: ParticipantStatus[], now: number, limit?: number): string[];
|
|
63
68
|
export declare function expressHintLine(ownActivityCount: number): string | undefined;
|
|
@@ -85,6 +90,12 @@ export declare function renderAmbientEvent(event: StoredAct, viewer: string, opt
|
|
|
85
90
|
}): string;
|
|
86
91
|
export declare function renderPendingFeed(history: StoredAct[], publicItems: PublicAct[], roomChanges: RoomChangeAct[], viewer: string | undefined, squareState: SquareState): string;
|
|
87
92
|
export declare function renderActivityBlocked(opts: ActivityBlockedOptions): string;
|
|
93
|
+
/** An indeterminate or already-committed send only points to inspection, never resend. */
|
|
94
|
+
export declare function renderActivityUncertain(opts: ParticipantOutputOptions & {
|
|
95
|
+
draftPath: string;
|
|
96
|
+
landedId?: string;
|
|
97
|
+
detail?: string;
|
|
98
|
+
}): string;
|
|
88
99
|
export declare function renderExpressWaiting(opts: ExpressWaitingOptions): string;
|
|
89
100
|
export declare function renderExpressNoWait(opts: ExpressNoWaitOptions): string;
|
|
90
101
|
export declare function renderPublicTail(squareState: SquareState, events: StoredAct[], lastN: number | null | undefined, now?: number, viewer?: string): string;
|
package/dist/presentation.js
CHANGED
|
@@ -5,7 +5,7 @@ import { participantIdentity } from './participant-identity.js';
|
|
|
5
5
|
export { participantIdentity } from './participant-identity.js';
|
|
6
6
|
import { audienceIncludes, audienceOf, MAX_IDENTITY_SET_SIZE, replayLandedAudiences } from './square-core.js';
|
|
7
7
|
import { perceiveActivity } from './delivery.js';
|
|
8
|
-
import { actId, presenceAnchor, publicActs, rosterNames, sayNumberFor } from './runtime.js';
|
|
8
|
+
import { actId, presenceAnchor, publicActs, rosterNames, sayNumberFor, HELD_WAIT_BUDGET_MS } from './runtime.js';
|
|
9
9
|
import { formatDuration, formatRelativeTime, formatTimestamp } from './time.js';
|
|
10
10
|
import { compareParticipantActivity } from './decisions.js';
|
|
11
11
|
import { grepSnippet } from './search.js';
|
|
@@ -55,6 +55,13 @@ export function participantCommandPrefix(squarePath, name) {
|
|
|
55
55
|
export function joinRecoveryCommand(squarePath, name) {
|
|
56
56
|
return `${participantCommandPrefix(squarePath, name)} join`;
|
|
57
57
|
}
|
|
58
|
+
/** Taking a name is an explicit recovery choice, never an automatic retry. */
|
|
59
|
+
export function takeoverRecoveryLines(squarePath, name) {
|
|
60
|
+
return [
|
|
61
|
+
'If this is your name to reclaim, --kick banishes the one standing here so you can step in:',
|
|
62
|
+
`${participantCommandPrefix(squarePath, name)} join --kick`,
|
|
63
|
+
];
|
|
64
|
+
}
|
|
58
65
|
export function participantsRecoveryCommand(squarePath) {
|
|
59
66
|
return `${commandPrefix(squarePath)} participants`;
|
|
60
67
|
}
|
|
@@ -281,19 +288,42 @@ export function renderActivityBlocked(opts) {
|
|
|
281
288
|
...renderUnreadSummary({ activitySummaries: opts.activitySummaries, roomChanges: opts.unreadRoomChanges, viewer: opts.name }),
|
|
282
289
|
...draftSavedLines(opts.draftPath),
|
|
283
290
|
`${readNowCommand}`,
|
|
284
|
-
' take it in, then
|
|
285
|
-
|
|
286
|
-
'
|
|
291
|
+
' · take it in, then retry:',
|
|
292
|
+
withDraftInput(opts.retryCommand, opts.draftPath),
|
|
293
|
+
' · optional: express over unread activity with --force:',
|
|
294
|
+
withDraftInput(opts.forceCommand, opts.draftPath),
|
|
287
295
|
].join('\n'), { participantCount: opts.participantCount, held: opts.held });
|
|
288
296
|
}
|
|
297
|
+
/** An indeterminate or already-committed send only points to inspection, never resend. */
|
|
298
|
+
export function renderActivityUncertain(opts) {
|
|
299
|
+
return formatRefusal(opts.squarePath, [
|
|
300
|
+
opts.landedId === undefined
|
|
301
|
+
? '✕ your activity may have landed — check history before sending again'
|
|
302
|
+
: `✕ your activity landed · ${opts.landedId} — the confirmation could not finish`,
|
|
303
|
+
...(opts.detail === undefined ? [] : [` · ${truncateExternalDiagnostic(opts.detail)}`]),
|
|
304
|
+
...draftSavedLines(opts.draftPath),
|
|
305
|
+
' · the draft is a saved copy; sending it again could repeat your activity',
|
|
306
|
+
`${commandPrefix(opts.squarePath)} history${opts.landedId === undefined ? ' --limit 10' : ` --at ${opts.landedId}`} --no-truncate`,
|
|
307
|
+
], opts);
|
|
308
|
+
}
|
|
289
309
|
export function renderExpressWaiting(opts) {
|
|
290
310
|
if (opts.reason === 'throttled') {
|
|
291
|
-
return [
|
|
311
|
+
return [
|
|
312
|
+
'✕ the square is packed',
|
|
313
|
+
` · your activity is waiting · next opening in ${formatDuration(opts.delayMs)}`,
|
|
314
|
+
' · --no-wait saves a draft and returns now',
|
|
315
|
+
].join('\n');
|
|
292
316
|
}
|
|
293
|
-
|
|
317
|
+
const holder = opts.holder === undefined ? 'a hand is raised' : `${participantIdentity(opts.holder)} raised a hand`;
|
|
318
|
+
return [
|
|
319
|
+
`✕ your activity doesn't land — ${holder}`,
|
|
320
|
+
...(opts.holdReason === undefined ? [] : [` · ${opts.holdReason}`]),
|
|
321
|
+
` · your activity is waiting — after ${formatDuration(HELD_WAIT_BUDGET_MS)} it saves a draft and stops`,
|
|
322
|
+
' · --no-wait saves a draft and returns now',
|
|
323
|
+
].join('\n');
|
|
294
324
|
}
|
|
295
325
|
export function renderExpressNoWait(opts) {
|
|
296
|
-
const retryCommand = opts.
|
|
326
|
+
const retryCommand = opts.retryCommand;
|
|
297
327
|
const lines = opts.reason === 'throttled'
|
|
298
328
|
? [
|
|
299
329
|
`${style('blocked', '✕')} the square is packed`,
|
|
@@ -411,12 +441,12 @@ export function renderGrepActivitiesView(visible, totalMatches, noTruncate, squa
|
|
|
411
441
|
}
|
|
412
442
|
function renderActivityLimitBody(opts) {
|
|
413
443
|
const countText = opts.count !== undefined && opts.hardCap !== undefined ? ` (${opts.count}/${opts.hardCap})` : '';
|
|
414
|
-
const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} done
|
|
444
|
+
const doneCommand = `${participantCommandPrefix(opts.squarePath, opts.name)} done`;
|
|
415
445
|
return [
|
|
416
446
|
`${style('blocked', '✕')} your activity doesn't land — the cap is reached${countText}`,
|
|
417
447
|
...draftSavedLines(opts.draftPath),
|
|
418
|
-
|
|
419
|
-
|
|
448
|
+
' · your draft stays unsent; done only steps out',
|
|
449
|
+
doneCommand,
|
|
420
450
|
].join('\n');
|
|
421
451
|
}
|
|
422
452
|
export function renderActivityLimit(opts) {
|
|
@@ -462,9 +492,9 @@ export function renderWatchStatus(opts) {
|
|
|
462
492
|
].join('\n');
|
|
463
493
|
}
|
|
464
494
|
case 'quorum':
|
|
465
|
-
return [`${style('release', '✓')} everyone else has left — the square is yours alone`, `${participantCommandPrefix(opts.squarePath, opts.name)} done
|
|
495
|
+
return [`${style('release', '✓')} everyone else has left — the square is yours alone`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
|
|
466
496
|
case 'capped':
|
|
467
|
-
return [`${style('blocked', '✕')} nothing left in you — the cap is reached`, `${participantCommandPrefix(opts.squarePath, opts.name)} done
|
|
497
|
+
return [`${style('blocked', '✕')} nothing left in you — the cap is reached`, `${participantCommandPrefix(opts.squarePath, opts.name)} done`].join('\n');
|
|
468
498
|
}
|
|
469
499
|
}
|
|
470
500
|
function renderRoomChanges(changes) {
|
package/dist/presented.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { canonicalSquarePath } from './registry.js';
|
|
4
5
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
5
6
|
import { formatActivityId } from './square-core.js';
|
|
6
7
|
import { projectPresentationEvidence } from './square-projections.js';
|
|
7
8
|
export function presentedPath(env = process.env) { return env.SQUARE_PRESENTED || path.join(os.homedir(), '.square', 'presented.ndjsonl'); }
|
|
8
|
-
function evidence(env = process.env) { const file = presentedPath(env); return createHostLedgerPort({
|
|
9
|
+
function evidence(env = process.env) { const file = presentedPath(env); return createHostLedgerPort({ rootPath: hostLedgerRoot(env) }); }
|
|
9
10
|
export async function hasPresentedForOwner(sessionId, squarePath, name, actIndex, env = process.env, now = Date.now()) { const resolved = await canonicalSquarePath(squarePath); return (await projectPresentationEvidence({ hostLedger: evidence(env), now, location: resolved, participant: name, sessionId, activity: formatActivityId(actIndex) })).some((row) => row.outcome === 'presented'); }
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Machine-local participant discovery cache. */
|
|
2
2
|
import { type StoredAct } from './model.js';
|
|
3
|
-
import type { PresenceRecord } from './host-ledger.js';
|
|
3
|
+
import type { HostLedgerPort, PresenceRecord } from './host-ledger.js';
|
|
4
4
|
export type SessionChannel = 'claude-code' | 'codex' | 'opencode' | 'pi' | 'paseo' | 'unknown';
|
|
5
5
|
export interface RegistryBinding {
|
|
6
6
|
sessionId: string;
|
|
@@ -26,7 +26,7 @@ export declare function registryPath(env?: NodeJS.ProcessEnv): string;
|
|
|
26
26
|
export declare function canonicalSquarePath(squarePath: string): Promise<string>;
|
|
27
27
|
export declare function presenceEpoch(record: PresenceWithEpoch | undefined): number;
|
|
28
28
|
/** Runs `fn` inside the ownership claim critical section, under the same lock as claims and finalize. */
|
|
29
|
-
export declare function withOwnershipClaimLock<T>(env: NodeJS.ProcessEnv, fn: () => Promise<T
|
|
29
|
+
export declare function withOwnershipClaimLock<T>(env: NodeJS.ProcessEnv, fn: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
30
30
|
export declare function recordJoin(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): Promise<void>;
|
|
31
31
|
export declare function recordDone(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): Promise<void>;
|
|
32
32
|
export declare function readActiveBindings(now?: number, env?: NodeJS.ProcessEnv): Promise<RegistryBinding[]>;
|
|
@@ -45,6 +45,7 @@ export type CurrentParticipantBinding = Readonly<{
|
|
|
45
45
|
}>;
|
|
46
46
|
export type OwnershipClaim = {
|
|
47
47
|
readonly status: 'acquired' | 'owned';
|
|
48
|
+
readonly record: PresenceWithEpoch;
|
|
48
49
|
readonly sessionId: string;
|
|
49
50
|
readonly epoch: number;
|
|
50
51
|
} | undefined;
|
|
@@ -63,7 +64,8 @@ export type TakeoverRunResult<T> = {
|
|
|
63
64
|
};
|
|
64
65
|
export declare function readParticipantOwner(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<RegistryBinding | undefined>;
|
|
65
66
|
export declare function sessionOwnsParticipant(squarePath: string, name: string, sessionId: string, env?: NodeJS.ProcessEnv, expectedEpoch?: number): Promise<boolean>;
|
|
66
|
-
export declare function
|
|
67
|
+
export declare function releaseSessionParticipantClaim(squarePath: string, name: string, env: NodeJS.ProcessEnv, claim: OwnershipClaim): Promise<void>;
|
|
68
|
+
export declare function claimSessionParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv, signal?: AbortSignal): Promise<OwnershipClaim>;
|
|
67
69
|
/**
|
|
68
70
|
* Fenced takeover: the ownership claim and the lifecycle run as one critical section under the
|
|
69
71
|
* presence-claim lock, so no second takeover can interleave between claim and commit. The claim
|
|
@@ -76,7 +78,7 @@ export declare function claimSessionParticipant(squarePath: string, name: string
|
|
|
76
78
|
export declare function claimSessionTakeover<T>(squarePath: string, name: string, env: NodeJS.ProcessEnv | undefined, opts: {
|
|
77
79
|
readonly expectedEpoch?: number;
|
|
78
80
|
readonly expectedSession?: string;
|
|
79
|
-
} | undefined, lifecycle: (claim: TakeoverClaimToken) => Promise<T
|
|
81
|
+
} | undefined, lifecycle: (claim: TakeoverClaimToken) => Promise<T>, signal?: AbortSignal): Promise<TakeoverRunResult<T>>;
|
|
80
82
|
export declare function releaseSessionParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
81
83
|
export declare function bindCurrentParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<CurrentParticipantBinding>;
|
|
82
84
|
export declare function unbindCurrentParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
@@ -97,4 +99,6 @@ export declare function recordLocalJoin(name: string, squarePath: string, env?:
|
|
|
97
99
|
export declare function recordLocalDone(name: string, squarePath: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
98
100
|
export declare function recordSessionJoin(sessionId: string, name: string, squarePath: string, channel: SessionChannel, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
99
101
|
export declare function recordSessionDone(sessionId: string, name: string, squarePath: string, channel: SessionChannel, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
102
|
+
/** Bind the host ledger to the caller's captured environment. */
|
|
103
|
+
export declare function hostLedgerForEnv(env?: NodeJS.ProcessEnv): HostLedgerPort;
|
|
100
104
|
export {};
|
package/dist/registry.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Machine-local participant discovery cache. */
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
3
|
import { withFileLock } from './file-lock.js';
|
|
5
4
|
import { sameName, SquareError } from './model.js';
|
|
6
5
|
import { isCurrentlyJoined } from './runtime.js';
|
|
7
6
|
import { squareAssignedParticipantName as computeSquareAssignedParticipantName } from './participant-identity.js';
|
|
8
7
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
8
|
+
import { hostLedgerRoot } from './host-ledger-root.js';
|
|
9
9
|
const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
10
10
|
const PRESENCE_CLAIM_LOCK = { retryMs: 10 };
|
|
11
11
|
const LOCAL_SESSION_SOURCES = [
|
|
@@ -14,23 +14,15 @@ const LOCAL_SESSION_SOURCES = [
|
|
|
14
14
|
{ variable: 'OPENCODE_SESSION_ID', channel: 'opencode' },
|
|
15
15
|
{ variable: 'PI_SESSION_ID', channel: 'pi' },
|
|
16
16
|
];
|
|
17
|
-
export function registryPath(env = process.env) { return env.SQUARE_REGISTRY || path.join(
|
|
17
|
+
export function registryPath(env = process.env) { return env.SQUARE_REGISTRY || path.join(hostLedgerRoot(env), '..', 'sessions.ndjsonl'); }
|
|
18
18
|
export async function canonicalSquarePath(squarePath) { const absolute = path.resolve(squarePath); try {
|
|
19
19
|
return await (await import('node:fs/promises')).realpath(absolute);
|
|
20
20
|
}
|
|
21
21
|
catch {
|
|
22
22
|
return absolute;
|
|
23
23
|
} }
|
|
24
|
-
function ledgerRoot(env) {
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
userPath: env.SQUARE_HOST_LEDGER_USER ?? (env.SQUARE_REGISTRY ? root : path.join(homedir(), '.square', 'host-ledger')),
|
|
28
|
-
localPath: env.SQUARE_HOST_LEDGER_LOCAL ?? (env.SQUARE_REGISTRY ? root : path.join(process.cwd(), '.square', 'host-ledger')),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
function ledger(env, writableScope = 'local') {
|
|
32
|
-
return createHostLedgerPort({ ...ledgerRoot(env), writableScope });
|
|
33
|
-
}
|
|
24
|
+
function ledgerRoot(env) { return hostLedgerRoot(env); }
|
|
25
|
+
function ledger(env) { return createHostLedgerPort({ rootPath: ledgerRoot(env) }); }
|
|
34
26
|
export function presenceEpoch(record) {
|
|
35
27
|
return typeof record?.epoch === 'number' && Number.isSafeInteger(record.epoch) && record.epoch > 0 ? record.epoch : 0;
|
|
36
28
|
}
|
|
@@ -47,22 +39,21 @@ function toBinding(record) {
|
|
|
47
39
|
};
|
|
48
40
|
}
|
|
49
41
|
function presenceClaimLockPath(env) {
|
|
50
|
-
return path.join(ledgerRoot(env)
|
|
42
|
+
return path.join(ledgerRoot(env), 'presence-claim.lock');
|
|
51
43
|
}
|
|
52
44
|
/** Runs `fn` inside the ownership claim critical section, under the same lock as claims and finalize. */
|
|
53
|
-
export async function withOwnershipClaimLock(env, fn) {
|
|
54
|
-
return withFileLock(presenceClaimLockPath(env), PRESENCE_CLAIM_LOCK, fn);
|
|
45
|
+
export async function withOwnershipClaimLock(env, fn, signal) {
|
|
46
|
+
return withFileLock(presenceClaimLockPath(env), { ...PRESENCE_CLAIM_LOCK, signal }, fn);
|
|
55
47
|
}
|
|
56
|
-
async function activeBindings(now, env) { return (await ledger(env).listPresence({ now
|
|
57
|
-
async function writePresence(sessionId, name, squarePath, options, done
|
|
58
|
-
return; const env = options.env ?? process.env; const channel = options.channel ?? 'unknown'; const port = ledger(env
|
|
48
|
+
async function activeBindings(now, env) { return (await ledger(env).listPresence({ now })).map(toBinding).sort((a, b) => b.updatedAt - a.updatedAt); }
|
|
49
|
+
async function writePresence(sessionId, name, squarePath, options, done) { if (!sessionId || !name || !squarePath)
|
|
50
|
+
return; const env = options.env ?? process.env; const channel = options.channel ?? 'unknown'; const port = ledger(env); const location = await canonicalSquarePath(squarePath); if (done)
|
|
59
51
|
await port.removePresence({ location, participant: name, session: sessionId, channel });
|
|
60
52
|
else
|
|
61
53
|
await port.ensurePresence({ location, participant: name, session: sessionId, channel, updatedAt: options.at ?? Date.now() }); }
|
|
62
54
|
export function recordJoin(sessionId, name, squarePath, options = {}) { return writePresence(sessionId, name, squarePath, options, false); }
|
|
63
55
|
export async function recordDone(sessionId, name, squarePath, options = {}) {
|
|
64
56
|
await writePresence(sessionId, name, squarePath, options, true);
|
|
65
|
-
await writePresence(sessionId, name, squarePath, options, true, 'user');
|
|
66
57
|
}
|
|
67
58
|
export async function readActiveBindings(now = Date.now(), env = process.env) { try {
|
|
68
59
|
return await activeBindings(now, env);
|
|
@@ -87,10 +78,18 @@ export async function sessionOwnsParticipant(squarePath, name, sessionId, env =
|
|
|
87
78
|
return false;
|
|
88
79
|
return expectedEpoch === undefined || owner.epoch === expectedEpoch;
|
|
89
80
|
}
|
|
90
|
-
export async function
|
|
81
|
+
export async function releaseSessionParticipantClaim(squarePath, name, env, claim) {
|
|
82
|
+
if (claim?.status !== 'acquired')
|
|
83
|
+
return;
|
|
84
|
+
const port = ledger(env);
|
|
85
|
+
await port.removePresenceIfUnchanged(claim.record);
|
|
86
|
+
}
|
|
87
|
+
export async function claimSessionParticipant(squarePath, name, env = process.env, signal) {
|
|
91
88
|
const identity = localSessionIdentities(env)[0];
|
|
92
89
|
if (identity === undefined)
|
|
93
90
|
return undefined;
|
|
91
|
+
if (signal?.aborted)
|
|
92
|
+
throw signal.reason ?? new Error('Operation aborted');
|
|
94
93
|
const location = await canonicalSquarePath(squarePath);
|
|
95
94
|
const result = await ledger(env).claimPresence({
|
|
96
95
|
location,
|
|
@@ -99,13 +98,13 @@ export async function claimSessionParticipant(squarePath, name, env = process.en
|
|
|
99
98
|
channel: identity.channel,
|
|
100
99
|
updatedAt: Date.now(),
|
|
101
100
|
epoch: 1,
|
|
102
|
-
},
|
|
101
|
+
}, signal);
|
|
103
102
|
if (result.status === 'busy')
|
|
104
103
|
throw new SquareError('already_joined', `✕ ${name} already stands here — another session holds the name`);
|
|
105
104
|
if (result.status === 'degraded')
|
|
106
105
|
throw result.error;
|
|
107
|
-
const
|
|
108
|
-
return { status: result.status, sessionId:
|
|
106
|
+
const record = result.record;
|
|
107
|
+
return { status: result.status, record, sessionId: record.session, epoch: presenceEpoch(record) };
|
|
109
108
|
}
|
|
110
109
|
/**
|
|
111
110
|
* Fenced takeover: the ownership claim and the lifecycle run as one critical section under the
|
|
@@ -116,18 +115,15 @@ export async function claimSessionParticipant(squarePath, name, env = process.en
|
|
|
116
115
|
* carrying the claim token (session + channel + claim epoch) are withdrawn and the captured
|
|
117
116
|
* standing rows are restored exactly.
|
|
118
117
|
*/
|
|
119
|
-
export async function claimSessionTakeover(squarePath, name, env = process.env, opts = {}, lifecycle) {
|
|
118
|
+
export async function claimSessionTakeover(squarePath, name, env = process.env, opts = {}, lifecycle, signal) {
|
|
120
119
|
const identity = localSessionIdentities(env)[0];
|
|
121
120
|
if (identity === undefined)
|
|
122
121
|
throw new SquareError('invalid_args', 'No local session identity for takeover');
|
|
123
122
|
const location = await canonicalSquarePath(squarePath);
|
|
124
|
-
return withFileLock(presenceClaimLockPath(env), PRESENCE_CLAIM_LOCK, async () => {
|
|
123
|
+
return withFileLock(presenceClaimLockPath(env), { ...PRESENCE_CLAIM_LOCK, signal }, async () => {
|
|
125
124
|
const port = ledger(env);
|
|
126
|
-
const standing = {
|
|
127
|
-
|
|
128
|
-
local: await port.listPresence({ location, participant: name, scopes: ['local'] }),
|
|
129
|
-
};
|
|
130
|
-
const owner = [...standing.user, ...standing.local]
|
|
125
|
+
const standing = await port.listPresence({ location, participant: name });
|
|
126
|
+
const owner = standing
|
|
131
127
|
.map((row) => toBinding(row))
|
|
132
128
|
.sort((left, right) => right.epoch - left.epoch || right.updatedAt - left.updatedAt)[0];
|
|
133
129
|
const currentEpoch = owner?.epoch ?? 0;
|
|
@@ -147,50 +143,21 @@ export async function claimSessionTakeover(squarePath, name, env = process.env,
|
|
|
147
143
|
// token (session + channel + epoch) — whatever the lifecycle itself ensured — then restore
|
|
148
144
|
// the captured standing rows so the old owner survives byte-consistently. Foreign rows and
|
|
149
145
|
// later owners are never touched.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
participant:
|
|
154
|
-
session: identity.sessionId,
|
|
155
|
-
scopes: [scope],
|
|
156
|
-
});
|
|
157
|
-
for (const row of tokenRows) {
|
|
158
|
-
if (row.channel !== identity.channel || presenceEpoch(row) !== epoch)
|
|
159
|
-
continue;
|
|
160
|
-
await ledger(env, scope).removePresence({
|
|
161
|
-
location: row.location,
|
|
162
|
-
participant: row.participant,
|
|
163
|
-
session: row.session,
|
|
164
|
-
channel: row.channel,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
for (const row of standing[scope]) {
|
|
168
|
-
await ledger(env, scope).ensurePresence(row, scope);
|
|
169
|
-
}
|
|
146
|
+
const tokenRows = await ledger(env).listPresence({ location, participant: name, session: identity.sessionId });
|
|
147
|
+
for (const row of tokenRows) {
|
|
148
|
+
if (row.channel === identity.channel && presenceEpoch(row) === epoch)
|
|
149
|
+
await ledger(env).removePresence({ location: row.location, participant: row.participant, session: row.session, channel: row.channel });
|
|
170
150
|
}
|
|
151
|
+
for (const row of standing)
|
|
152
|
+
await ledger(env).ensurePresence(row);
|
|
171
153
|
throw error;
|
|
172
154
|
}
|
|
173
155
|
// Finalize: the lifecycle committed. Replace the standing owner with the new owner's row at
|
|
174
|
-
// the claim epoch
|
|
156
|
+
// the claim epoch where the owner was visible, so exactly one current owner
|
|
175
157
|
// remains — also when the takeover is a self-takeover (same session as the standing owner).
|
|
176
|
-
for (const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
location: row.location,
|
|
180
|
-
participant: row.participant,
|
|
181
|
-
session: row.session,
|
|
182
|
-
channel: row.channel,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
await ledger(env, scope).ensurePresence({
|
|
186
|
-
location,
|
|
187
|
-
participant: name,
|
|
188
|
-
session: identity.sessionId,
|
|
189
|
-
channel: identity.channel,
|
|
190
|
-
updatedAt: Date.now(),
|
|
191
|
-
epoch,
|
|
192
|
-
}, scope);
|
|
193
|
-
}
|
|
158
|
+
for (const row of standing)
|
|
159
|
+
await ledger(env).removePresence({ location: row.location, participant: row.participant, session: row.session, channel: row.channel });
|
|
160
|
+
await ledger(env).ensurePresence({ location, participant: name, session: identity.sessionId, channel: identity.channel, updatedAt: Date.now(), epoch });
|
|
194
161
|
return { status: 'acquired', sessionId: identity.sessionId, epoch, result };
|
|
195
162
|
});
|
|
196
163
|
}
|
|
@@ -226,9 +193,9 @@ export async function recordLocalJoin(name, squarePath, env = process.env) { con
|
|
|
226
193
|
} }
|
|
227
194
|
export async function recordLocalDone(name, squarePath, env = process.env) { const at = Date.now(); const identities = new Set(localSessionIdentities(env).map((identity) => identity.sessionId)); const current = (await lookupParticipant(squarePath, name, at, env)).filter((binding) => identities.has(binding.sessionId)); for (const binding of current)
|
|
228
195
|
await recordDone(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env }); }
|
|
229
|
-
export async function recordSessionJoin(sessionId, name, squarePath, channel, env = process.env) { const at = Date.now(); const current = (await lookupParticipant(squarePath, name, at, env)).filter((binding) => binding.sessionId === sessionId); for (const binding of current)
|
|
230
|
-
await recordDone(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env });
|
|
231
|
-
await writePresence(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env }, true, 'user');
|
|
232
|
-
} await recordJoin(sessionId, name, squarePath, { channel, at, env }); await writePresence(sessionId, name, squarePath, { channel, at, env }, false, 'user'); return sessionId; }
|
|
196
|
+
export async function recordSessionJoin(sessionId, name, squarePath, channel, env = process.env) { const at = Date.now(); const current = (await lookupParticipant(squarePath, name, at, env)).filter((binding) => binding.sessionId === sessionId); for (const binding of current)
|
|
197
|
+
await recordDone(binding.sessionId, binding.name, binding.squarePath, { channel: binding.channel, at, env }); await recordJoin(sessionId, name, squarePath, { channel, at, env }); return sessionId; }
|
|
233
198
|
export async function recordSessionDone(sessionId, name, squarePath, channel, env = process.env) { const canonicalPath = await canonicalSquarePath(squarePath); const binding = (await lookupSessionBindings(sessionId, Date.now(), env)).find((item) => item.squarePath === canonicalPath && sameName(item.name, name) && item.channel === channel); if (binding === undefined)
|
|
234
|
-
return false; const options = { channel, at: Date.now(), env }; await recordDone(sessionId, binding.name, binding.squarePath, options);
|
|
199
|
+
return false; const options = { channel, at: Date.now(), env }; await recordDone(sessionId, binding.name, binding.squarePath, options); return true; }
|
|
200
|
+
/** Bind the host ledger to the caller's captured environment. */
|
|
201
|
+
export function hostLedgerForEnv(env = process.env) { return ledger(env); }
|
package/dist/routes.d.ts
CHANGED
|
@@ -36,7 +36,22 @@ export type RouteEpoch = {
|
|
|
36
36
|
readonly epoch?: number;
|
|
37
37
|
};
|
|
38
38
|
export declare function applyWakeRouteToState(state: import('./model.js').SquareState, route: Omit<WakeRoute, 'updatedAt'> & RouteEpoch, at?: number): void;
|
|
39
|
-
export declare function dropSessionWakeRoutesFromState(state: import('./model.js').SquareState, location: string, sessionId: string): void;
|
|
39
|
+
export declare function dropSessionWakeRoutesFromState(state: import('./model.js').SquareState, location: string, sessionId: string, expectedEpoch?: number): void;
|
|
40
|
+
/**
|
|
41
|
+
* Drop one ended session's routes at a location, atomically with the lifecycle decide.
|
|
42
|
+
*
|
|
43
|
+
* A route survives when the ending transaction did not commit `done` and this session
|
|
44
|
+
* still holds the sole live presence for its participant — an in-flight same-session
|
|
45
|
+
* rejoin. `liveParticipants === undefined` means the ledger could not be read, so
|
|
46
|
+
* nothing is dropped on suspicion. An explicit epoch fence additionally keeps routes
|
|
47
|
+
* published by a later generation.
|
|
48
|
+
*/
|
|
49
|
+
export declare function dropEndedSessionWakeRoutesFromState(state: import('./model.js').SquareState, location: string, sessionId: string, opts: {
|
|
50
|
+
readonly expectedEpoch?: number;
|
|
51
|
+
readonly liveParticipants?: ReadonlySet<string>;
|
|
52
|
+
readonly force: boolean;
|
|
53
|
+
readonly participant?: string;
|
|
54
|
+
}): void;
|
|
40
55
|
export declare function dropParticipantWakeRoutesFromState(state: import('./model.js').SquareState, location: string, participant: string): void;
|
|
41
56
|
export declare function sessionOwnsParticipantRoutes(state: import('./model.js').SquareState, location: string, participant: string, sessionId: string): boolean;
|
|
42
57
|
export declare function sessionCanEndParticipant(state: import('./model.js').SquareState, location: string, participant: string, sessionId: string, currentSessionId?: string): boolean;
|
|
@@ -52,5 +67,7 @@ export declare function retireWakeRoute(route: WakeRoute, opts?: {
|
|
|
52
67
|
export declare function retireWakeRouteFromArtifact(artifact: import('./ports.js').SquareArtifactPort, route: Pick<WakeRoute, 'location' | 'participant' | 'sessionId'>, opts?: {
|
|
53
68
|
readonly expectedEpoch?: number;
|
|
54
69
|
}): Promise<void>;
|
|
55
|
-
export declare function retireWakeRoutesForSessionFromArtifact(artifact: import('./ports.js').SquareArtifactPort, route: Pick<WakeRoute, 'location' | 'sessionId'
|
|
70
|
+
export declare function retireWakeRoutesForSessionFromArtifact(artifact: import('./ports.js').SquareArtifactPort, route: Pick<WakeRoute, 'location' | 'sessionId'>, opts?: {
|
|
71
|
+
readonly expectedEpoch?: number;
|
|
72
|
+
}): Promise<void>;
|
|
56
73
|
export declare function canonicalRouteLocation(location: string): Promise<string>;
|