@astrosheep/square 0.3.42 → 0.3.43
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/attention-presentation.d.ts +1 -1
- package/dist/attention-presentation.js +1 -1
- package/dist/automatic-session.js +38 -23
- package/dist/boundary-presentation.js +1 -1
- package/dist/codex-boundary-state.js +2 -2
- package/dist/delivery-operations.d.ts +2 -2
- package/dist/delivery-operations.js +144 -45
- package/dist/file-lock.d.ts +5 -1
- package/dist/file-lock.js +16 -91
- package/dist/host-ledger-file-adapter.d.ts +6 -2
- package/dist/host-ledger-file-adapter.js +42 -23
- package/dist/host-ledger.d.ts +13 -3
- package/dist/notifications.js +32 -3
- package/dist/participant-identity.js +1 -1
- package/dist/ports.d.ts +5 -2
- package/dist/presentation-operations.js +8 -6
- package/dist/registry.d.ts +40 -2
- package/dist/registry.js +142 -5
- package/dist/routes.d.ts +15 -3
- package/dist/routes.js +72 -12
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +0 -1
- package/dist/square-actions.d.ts +7 -4
- package/dist/square-actions.js +240 -40
- package/dist/square-facade.d.ts +4 -0
- package/dist/square-projections.d.ts +7 -0
- package/dist/square-projections.js +17 -4
- package/dist/square-storage.js +59 -13
- package/dist/square-wiring.d.ts +2 -1
- package/dist/square-wiring.js +3 -2
- package/dist/state-cell.d.ts +4 -1
- package/dist/wake-attempts.js +8 -2
- package/extensions/square-pi.js +7 -0
- package/package.json +1 -1
package/dist/file-lock.js
CHANGED
|
@@ -1,95 +1,25 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
2
|
import { promises as fs } from 'node:fs';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return 'unknown';
|
|
16
|
-
}
|
|
17
|
-
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
18
|
-
return 'unknown';
|
|
19
|
-
try {
|
|
20
|
-
process.kill(pid, 0);
|
|
21
|
-
return 'alive';
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
return error.code === 'ESRCH' ? 'dead' : 'alive';
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async function createLegacyLock(lockPath) {
|
|
28
|
-
let fd;
|
|
29
|
-
try {
|
|
30
|
-
fd = await fs.open(lockPath, 'wx', 0o600);
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
if (error.code === 'EEXIST')
|
|
34
|
-
return undefined;
|
|
35
|
-
throw error;
|
|
36
|
-
}
|
|
37
|
-
const token = `${process.pid}\n${Date.now()}\n${randomUUID()}\n`;
|
|
38
|
-
try {
|
|
39
|
-
await fd.writeFile(token, 'utf8');
|
|
40
|
-
return token;
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
await fs.unlink(lockPath).catch(() => undefined);
|
|
44
|
-
throw error;
|
|
45
|
-
}
|
|
46
|
-
finally {
|
|
47
|
-
await fd.close();
|
|
5
|
+
export class FileLockError extends Error {
|
|
6
|
+
code = 'not_a_database';
|
|
7
|
+
constructor(lockPath, cause) {
|
|
8
|
+
super(`File lock is not a database: ${lockPath}`);
|
|
9
|
+
this.name = 'FileLockError';
|
|
10
|
+
if (cause !== undefined)
|
|
11
|
+
this.cause = cause;
|
|
48
12
|
}
|
|
49
13
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const stale = Date.now() - (await fs.stat(lockPath)).mtimeMs > staleMs;
|
|
54
|
-
const owner = await ownerState(lockPath);
|
|
55
|
-
if (owner === 'alive' || (owner !== 'dead' && !stale))
|
|
56
|
-
return false;
|
|
57
|
-
await fs.rename(lockPath, quarantinePath);
|
|
58
|
-
await fs.unlink(quarantinePath).catch(() => undefined);
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
await fs.unlink(quarantinePath).catch(() => undefined);
|
|
63
|
-
return error.code === 'ENOENT';
|
|
64
|
-
}
|
|
14
|
+
function isBusy(error) {
|
|
15
|
+
return error instanceof Error && /database is locked|SQLITE_BUSY/i.test(error.message);
|
|
65
16
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (await fs.readFile(lockPath, 'utf8') === token)
|
|
69
|
-
await fs.unlink(lockPath);
|
|
70
|
-
}
|
|
71
|
-
catch { }
|
|
17
|
+
function isNotADatabase(error) {
|
|
18
|
+
return error instanceof Error && /not a database|file is not a database/i.test(error.message);
|
|
72
19
|
}
|
|
73
|
-
async function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (options.signal?.aborted)
|
|
77
|
-
throw options.signal.reason ?? new Error('File lock acquisition aborted');
|
|
78
|
-
token = await createLegacyLock(lockPath);
|
|
79
|
-
if (token !== undefined)
|
|
80
|
-
break;
|
|
81
|
-
if (await reclaimLegacyLock(lockPath, options.staleMs))
|
|
82
|
-
continue;
|
|
83
|
-
await sleep(options.retryMs, undefined, { signal: options.signal });
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
return await fn();
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
await releaseLegacyLock(lockPath, token);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async function withSqliteLock(lockPath, options, fn) {
|
|
20
|
+
export async function withFileLock(lockPath, options, fn) {
|
|
21
|
+
if (options.createParent !== false)
|
|
22
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
93
23
|
while (true) {
|
|
94
24
|
if (options.signal?.aborted)
|
|
95
25
|
throw options.signal.reason ?? new Error('File lock acquisition aborted');
|
|
@@ -109,9 +39,8 @@ async function withSqliteLock(lockPath, options, fn) {
|
|
|
109
39
|
await sleep(options.retryMs, undefined, { signal: options.signal });
|
|
110
40
|
continue;
|
|
111
41
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
}
|
|
42
|
+
if (isNotADatabase(error))
|
|
43
|
+
throw new FileLockError(lockPath, error);
|
|
115
44
|
throw error;
|
|
116
45
|
}
|
|
117
46
|
try {
|
|
@@ -131,7 +60,3 @@ async function withSqliteLock(lockPath, options, fn) {
|
|
|
131
60
|
}
|
|
132
61
|
}
|
|
133
62
|
}
|
|
134
|
-
export async function withFileLock(lockPath, options, fn) {
|
|
135
|
-
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
136
|
-
return withSqliteLock(lockPath, options, fn);
|
|
137
|
-
}
|
|
@@ -24,10 +24,14 @@ export declare class FileHostLedgerPort implements HostLedgerPort {
|
|
|
24
24
|
transitionWakeDispatch(i: WakeDispatchTransitionInput): Promise<boolean>;
|
|
25
25
|
releaseWakeDispatch(i: WakeDispatchReleaseInput): Promise<void>;
|
|
26
26
|
listWakeAttempts(i?: WakeAttemptLookup): Promise<readonly EvidenceRecord[]>;
|
|
27
|
-
appendWakeAttempt(i: EvidenceRecord
|
|
27
|
+
appendWakeAttempt(i: EvidenceRecord & {
|
|
28
|
+
readonly claimToken: string;
|
|
29
|
+
}): Promise<void>;
|
|
28
30
|
claimEvidence(i: EvidenceClaim): Promise<ClaimResult>;
|
|
29
31
|
releaseEvidence(i: EvidenceRelease): Promise<void>;
|
|
30
|
-
appendEvidence(i: EvidenceRecord
|
|
32
|
+
appendEvidence(i: EvidenceRecord & {
|
|
33
|
+
readonly claimToken: string;
|
|
34
|
+
}): Promise<void>;
|
|
31
35
|
listEvidence(i?: EvidenceLookup): Promise<readonly EvidenceRecord[]>;
|
|
32
36
|
gcEvidence(i: EvidenceGc): Promise<void>;
|
|
33
37
|
reconcileBinding(i?: ReconcileBindingInput): Promise<ReconcileBindingResult>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
2
3
|
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { withFileLock } from './file-lock.js';
|
|
5
6
|
import { nameKey } from './model.js';
|
|
6
7
|
import { isCurrentlyJoined } from './runtime.js';
|
|
7
|
-
const LOCK = { retryMs: 10
|
|
8
|
+
const LOCK = { retryMs: 10 };
|
|
8
9
|
const RETENTION = 7 * 86400000;
|
|
9
10
|
async function canon(value) { const absolute = path.resolve(value); try {
|
|
10
11
|
return await fs.realpath(absolute);
|
|
@@ -12,11 +13,26 @@ async function canon(value) { const absolute = path.resolve(value); try {
|
|
|
12
13
|
catch {
|
|
13
14
|
return absolute;
|
|
14
15
|
} }
|
|
16
|
+
function canonRoot(value) { let current = path.resolve(value), suffix = []; for (;;) {
|
|
17
|
+
try {
|
|
18
|
+
return path.join(fsSync.realpathSync.native(current), ...suffix.reverse());
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error.code !== 'ENOENT')
|
|
22
|
+
return path.resolve(value);
|
|
23
|
+
const parent = path.dirname(current);
|
|
24
|
+
if (parent === current)
|
|
25
|
+
return path.resolve(value);
|
|
26
|
+
suffix.push(path.basename(current));
|
|
27
|
+
current = parent;
|
|
28
|
+
}
|
|
29
|
+
} }
|
|
15
30
|
async function read(file, now, includeFuture = false) { try {
|
|
16
31
|
return (await fs.readFile(file, 'utf8')).split('\n').flatMap(line => { try {
|
|
17
32
|
const row = JSON.parse(line);
|
|
18
33
|
const at = row.at ?? row.updatedAt;
|
|
19
|
-
|
|
34
|
+
const durableTerminal = row.kind === 'wake' && row.outcome === 'accepted';
|
|
35
|
+
return row.v === 1 && typeof at === 'number' && (durableTerminal || at >= now - RETENTION) && (includeFuture || at <= now) ? [row] : [];
|
|
20
36
|
}
|
|
21
37
|
catch {
|
|
22
38
|
return [];
|
|
@@ -28,7 +44,7 @@ catch (e) {
|
|
|
28
44
|
throw e;
|
|
29
45
|
} }
|
|
30
46
|
async function write(file, rows) { await fs.mkdir(path.dirname(file), { recursive: true }); const tmp = `${file}.${process.pid}.${Date.now()}.tmp`; await fs.writeFile(tmp, rows.length ? rows.map(row => JSON.stringify(row)).join('\n') + '\n' : '', { mode: 0o600 }); await fs.rename(tmp, file); }
|
|
31
|
-
function k(v) { return JSON.stringify([v.location, nameKey(v.participant), v.session, v.channel, v.activity, v.kind]); }
|
|
47
|
+
function k(v) { return JSON.stringify([v.location, nameKey(v.participant), v.session, v.channel, v.activity, v.kind, v.scope ?? null]); }
|
|
32
48
|
function evidenceKey(v) { return JSON.stringify([v.location, nameKey(v.participant), v.session, v.activity, v.kind, v.attemptN ?? null]); }
|
|
33
49
|
async function readClaims(file, now) { try {
|
|
34
50
|
return (await fs.readFile(file, 'utf8')).split('\n').flatMap(line => { try {
|
|
@@ -60,12 +76,12 @@ export class FileHostLedgerPort {
|
|
|
60
76
|
readable;
|
|
61
77
|
claims;
|
|
62
78
|
clock;
|
|
63
|
-
constructor(o = {}) { const testRoot = process.env.SQUARE_REGISTRY ? path.dirname(process.env.SQUARE_REGISTRY) : undefined; this.user = o.userPath ?? (testRoot ?? path.join(os.homedir(), '.square', 'host-ledger')); this.local = o.localPath ?? (testRoot ?? path.join(process.cwd(), '.square', 'host-ledger')); this.writable = o.writableScope ?? 'local'; this.readable = o.readableScopes ?? ['user', 'local']; this.claims = o.claimsPath ?? path.join(this.user, 'wake-claims.ndjsonl'); this.clock = o.now ?? Date.now; }
|
|
79
|
+
constructor(o = {}) { const testRoot = process.env.SQUARE_REGISTRY ? path.dirname(process.env.SQUARE_REGISTRY) : undefined; this.user = canonRoot(o.userPath ?? (testRoot ?? path.join(os.homedir(), '.square', 'host-ledger'))); this.local = canonRoot(o.localPath ?? (testRoot ?? path.join(process.cwd(), '.square', 'host-ledger'))); this.writable = o.writableScope ?? 'local'; this.readable = o.readableScopes ?? ['user', 'local']; this.claims = canonRoot(o.claimsPath ?? path.join(this.user, 'wake-claims.ndjsonl')); this.clock = o.now ?? Date.now; }
|
|
64
80
|
file(s, f) { return path.join(s === 'user' ? this.user : this.local, `${f}.ndjsonl`); }
|
|
65
|
-
async claimPresence(i, scope = this.writable) { const location = await canon(i.location), r = { ...i, location, ...(scope === 'local' ? { route: undefined } : {}), updatedAt: i.updatedAt ?? this.clock(), v: 1 }, lock = path.join(this.user, 'presence-claim.lock'); try {
|
|
81
|
+
async claimPresence(i, scope = this.writable) { const location = await canon(i.location), r = { ...i, location, scope, ...(scope === 'local' ? { route: undefined } : {}), updatedAt: i.updatedAt ?? this.clock(), v: 1 }, lock = path.join(this.user, 'presence-claim.lock'); try {
|
|
66
82
|
return await withFileLock(lock, LOCK, async () => { const candidates = []; for (const s of ['user', 'local']) {
|
|
67
83
|
for (const row of await read(this.file(s, 'presence'), this.clock(), true)) {
|
|
68
|
-
if (row.location !== location || nameKey(row.participant) !== nameKey(r.participant))
|
|
84
|
+
if ((row.scope ?? s) !== s || row.location !== location || nameKey(row.participant) !== nameKey(r.participant))
|
|
69
85
|
continue;
|
|
70
86
|
candidates.push(s === 'user' ? row : { ...row, route: undefined });
|
|
71
87
|
}
|
|
@@ -73,37 +89,37 @@ export class FileHostLedgerPort {
|
|
|
73
89
|
if (existing.session === r.session)
|
|
74
90
|
return { status: 'owned', record: existing };
|
|
75
91
|
return { status: 'busy', record: existing };
|
|
76
|
-
} const f = this.file(scope, 'presence'); await withFileLock(f + '.lock', LOCK, async () => write(f, [...(await read(f, this.clock(), true)).filter(x => k(x) !== k(r)), r])); return { status: 'acquired', record: r }; });
|
|
92
|
+
} const f = this.file(scope, 'presence'); await withFileLock(f + '.lock', LOCK, async () => write(f, [...(await read(f, this.clock(), true)).filter(x => k({ ...x, scope: x.scope ?? scope }) !== k(r)), r])); return { status: 'acquired', record: r }; });
|
|
77
93
|
}
|
|
78
94
|
catch (error) {
|
|
79
95
|
return { status: 'degraded', record: r, error };
|
|
80
96
|
} }
|
|
81
|
-
async ensurePresence(i, scope = this.writable) { const r = { ...i, location: await canon(i.location), ...(scope === 'local' ? { route: undefined } : {}), updatedAt: i.updatedAt ?? this.clock(), v: 1 }; const f = this.file(scope, 'presence'); try {
|
|
82
|
-
await withFileLock(f + '.lock', LOCK, async () => write(f, [...(await read(f, this.clock(), true)).filter(x => k(x) !== k(r)), r]));
|
|
97
|
+
async ensurePresence(i, scope = this.writable) { const r = { ...i, location: await canon(i.location), scope, ...(scope === 'local' ? { route: undefined } : {}), updatedAt: i.updatedAt ?? this.clock(), v: 1 }; const f = this.file(scope, 'presence'); try {
|
|
98
|
+
await withFileLock(f + '.lock', LOCK, async () => write(f, [...(await read(f, this.clock(), true)).filter(x => k({ ...x, scope: x.scope ?? scope }) !== k(r)), r]));
|
|
83
99
|
return { status: 'ensured', record: r };
|
|
84
100
|
}
|
|
85
101
|
catch (error) {
|
|
86
102
|
return { status: 'degraded', record: r, error };
|
|
87
103
|
} }
|
|
88
|
-
async removePresence(i) { const r = { ...i, location: await canon(i.location) }, f = this.file(this.writable, 'presence'); await withFileLock(f + '.lock', LOCK, async () => write(f, (await read(f, this.clock(), true)).filter(x => k(x) !== k(r)))); }
|
|
104
|
+
async removePresence(i) { const r = { ...i, location: await canon(i.location), scope: this.writable }, f = this.file(this.writable, 'presence'), shared = this.user === this.local; await withFileLock(f + '.lock', LOCK, async () => write(f, (await read(f, this.clock(), true)).filter(x => { const rowScope = x.scope ?? this.writable; return shared ? k({ ...x, scope: rowScope }) !== k({ ...r, scope: rowScope }) : k({ ...x, scope: rowScope }) !== k(r); }))); }
|
|
89
105
|
async listPresence(i = {}) { const loc = i.location === undefined ? undefined : await canon(i.location), m = new Map(); for (const s of i.scopes ?? this.readable)
|
|
90
106
|
for (const r of await read(this.file(s, 'presence'), i.now ?? this.clock())) {
|
|
91
|
-
if (loc && r.location !== loc || i.participant && nameKey(r.participant) !== nameKey(i.participant) || i.session && r.session !== i.session)
|
|
107
|
+
if ((r.scope ?? s) !== s || loc && r.location !== loc || i.participant && nameKey(r.participant) !== nameKey(i.participant) || i.session && r.session !== i.session)
|
|
92
108
|
continue;
|
|
93
|
-
const x = s === 'user' ? r : { ...r, route: undefined }, key = k(x), old = m.get(key);
|
|
109
|
+
const x = s === 'user' ? r : { ...r, route: undefined }, key = k({ ...x, scope: undefined }), old = m.get(key);
|
|
94
110
|
if (old?.scope === 'user' && s === 'local')
|
|
95
111
|
continue;
|
|
96
112
|
if (!old || s === 'user' || x.updatedAt >= old.record.updatedAt)
|
|
97
113
|
m.set(key, { record: x, scope: s });
|
|
98
|
-
} return [...m.values()].map(({ record }) => record); }
|
|
99
|
-
async claimWakeDispatch(i) { const location = await canon(i.attention.squarePath), key = JSON.stringify([location, nameKey(i.attention.recipient), i.attention.actIndex,
|
|
114
|
+
} return [...m.values()].map(({ record }) => { const { scope: _scope, ...publicRecord } = record; return publicRecord; }); }
|
|
115
|
+
async claimWakeDispatch(i) { const location = await canon(i.attention.squarePath), key = JSON.stringify([location, nameKey(i.attention.recipient), i.attention.actIndex, 'wake']), at = i.at ?? this.clock(); return withFileLock(`${this.claims}.lock`, LOCK, async () => { const rows = await readClaims(this.claims, at), existing = rows.find((row) => row.attention_key === key); if (existing?.phase === 'dispatching') {
|
|
100
116
|
const alive = processAlive(existing.ownerPid);
|
|
101
117
|
if (existing.expiresAt <= at || alive === false)
|
|
102
118
|
return { type: 'ambiguous', lease: existing };
|
|
103
119
|
return { type: 'busy' };
|
|
104
120
|
} if (existing !== undefined && existing.expiresAt > at)
|
|
105
121
|
return { type: 'busy' }; const next = { v: 1, ts: at, attention_key: key, leaseId: i.leaseId, expiresAt: at + i.leaseMs, phase: 'claimed', ownerPid: process.pid, ...(i.session === undefined ? {} : { session: i.session }) }; await writeClaims(this.claims, [...rows.filter((row) => row.attention_key !== key), next]); return { type: 'acquired', leaseId: i.leaseId }; }); }
|
|
106
|
-
async transitionWakeDispatch(i) { const location = await canon(i.attention.squarePath), key = JSON.stringify([location, nameKey(i.attention.recipient), i.attention.actIndex,
|
|
122
|
+
async transitionWakeDispatch(i) { const location = await canon(i.attention.squarePath), key = JSON.stringify([location, nameKey(i.attention.recipient), i.attention.actIndex, 'wake']), at = i.at ?? this.clock(); return withFileLock(`${this.claims}.lock`, LOCK, async () => { const rows = await readClaims(this.claims, at), current = rows.find((row) => { if (row.leaseId !== i.leaseId)
|
|
107
123
|
return false; try {
|
|
108
124
|
const parts = JSON.parse(row.attention_key);
|
|
109
125
|
return parts[0] === location && parts[1] === nameKey(i.attention.recipient) && parts[2] === i.attention.actIndex;
|
|
@@ -112,22 +128,25 @@ export class FileHostLedgerPort {
|
|
|
112
128
|
return false;
|
|
113
129
|
} }); if (current === undefined)
|
|
114
130
|
return false; const next = { v: 1, ts: at, attention_key: key, leaseId: i.leaseId, expiresAt: at + i.leaseMs, phase: i.phase, ownerPid: current.ownerPid, ...(i.routeKind === undefined ? {} : { routeKind: i.routeKind }), ...(i.attemptN === undefined ? {} : { attemptN: i.attemptN }), ...(i.session === undefined ? {} : { session: i.session }) }; await writeClaims(this.claims, [...rows.filter((row) => row !== current), next]); return true; }); }
|
|
115
|
-
async releaseWakeDispatch(i) { const location = await canon(i.attention.squarePath), key = JSON.stringify([location, nameKey(i.attention.recipient), i.attention.actIndex,
|
|
131
|
+
async releaseWakeDispatch(i) { const location = await canon(i.attention.squarePath), key = JSON.stringify([location, nameKey(i.attention.recipient), i.attention.actIndex, 'wake']), at = i.at ?? this.clock(); await withFileLock(`${this.claims}.lock`, LOCK, async () => { const rows = await readClaims(this.claims, at); await writeClaims(this.claims, rows.filter((row) => row.attention_key !== key || row.leaseId !== i.leaseId)); }); }
|
|
116
132
|
async listWakeAttempts(i = {}) { const attention = i.attention; return this.listEvidence({ kind: 'wake', ...(attention === undefined ? {} : { location: await canon(attention.squarePath), participant: attention.recipient, activity: `act/${attention.actIndex}` }), ...(i.session === undefined ? {} : { session: i.session }), now: i.now }); }
|
|
117
133
|
async appendWakeAttempt(i) { if (i.kind !== 'wake')
|
|
118
|
-
throw new Error('Wake attempt rows require wake evidence kind');
|
|
119
|
-
async claimEvidence(i) { const f = this.file('user', 'evidence'), at = i.now ?? this.clock(), { leaseMs, ...claim } = i, r = { ...claim, location: await canon(i.location), outcome: 'dispatching', at, expiresAt: at + leaseMs, v: 1 }; try {
|
|
134
|
+
throw new Error('Wake attempt rows require wake evidence kind'); await this.appendEvidence(i); }
|
|
135
|
+
async claimEvidence(i) { const f = this.file('user', 'evidence'), at = i.now ?? this.clock(), { leaseMs, claimToken: requestedToken, ...claim } = i, r = { ...claim, location: await canon(i.location), outcome: 'dispatching', at, expiresAt: at + leaseMs, v: 1 }; try {
|
|
120
136
|
return await withFileLock(f + '.lock', LOCK, async () => { const all = await read(f, at, true), matching = all.filter(x => x.kind === r.kind && x.location === r.location && nameKey(x.participant) === nameKey(r.participant) && x.activity === r.activity && x.session === r.session), old = matching.findLast(x => x.outcome === 'dispatching'); if (old !== undefined && typeof old.expiresAt === 'number' && old.expiresAt > at)
|
|
121
137
|
return { status: 'busy', record: old }; const delivered = (r.kind === 'wake' && matching.some(x => x.outcome === 'accepted')) || (r.kind === 'presentation' && matching.some(x => x.outcome === 'presented')); if (delivered)
|
|
122
|
-
return { status: 'delivered', record: matching.findLast(x => x.outcome === 'accepted' || x.outcome === 'presented') }; await write(f, [...all.filter(x => !(x.kind === r.kind && x.location === r.location && nameKey(x.participant) === nameKey(r.participant) && x.activity === r.activity && x.session === r.session && x.outcome === 'dispatching')),
|
|
138
|
+
return { status: 'delivered', record: matching.findLast(x => x.outcome === 'accepted' || x.outcome === 'presented') }; const index = Number(r.activity.slice(4)), attentionKey = JSON.stringify([r.location, nameKey(r.participant), index, r.session ?? null, 'wake']), dispatchToken = r.kind === 'wake' && typeof requestedToken === 'string' && Number.isInteger(index) && (await readClaims(this.claims, at)).some((row) => row.leaseId === requestedToken && row.attention_key === attentionKey) ? requestedToken : undefined, claimToken = dispatchToken ?? `${process.pid}-${at}-${Math.random().toString(36).slice(2)}`, row = { ...r, claimToken }; await write(f, [...all.filter(x => !(x.kind === r.kind && x.location === r.location && nameKey(x.participant) === nameKey(r.participant) && x.activity === r.activity && x.session === r.session && (x.outcome === 'dispatching' || x.outcome === 'released'))), row]); return { status: 'acquired', claimToken }; });
|
|
123
139
|
}
|
|
124
140
|
catch (error) {
|
|
125
141
|
return { status: 'degraded', error };
|
|
126
142
|
} }
|
|
127
|
-
async releaseEvidence(i) { const f = this.file('user', 'evidence'), location = await canon(i.location), at = i.now ?? this.clock(); await withFileLock(f + '.lock', LOCK, async () =>
|
|
128
|
-
|
|
129
|
-
async
|
|
130
|
-
|
|
143
|
+
async releaseEvidence(i) { const f = this.file('user', 'evidence'), location = await canon(i.location), at = i.now ?? this.clock(); await withFileLock(f + '.lock', LOCK, async () => { const all = await read(f, at, true), same = (x) => x.kind === i.kind && x.location === location && nameKey(x.participant) === nameKey(i.participant) && x.activity === i.activity && x.session === i.session, current = all.findLast(x => same(x) && x.outcome === 'dispatching'); if (current === undefined || current.claimToken !== i.claimToken)
|
|
144
|
+
return; const tombstone = { ...current, outcome: 'released', at, expiresAt: undefined }; delete tombstone.expiresAt; await write(f, [...all.filter(x => x !== current), tombstone]); }); }
|
|
145
|
+
async appendEvidence(i) { const f = this.file('user', 'evidence'), r = { ...i, location: await canon(i.location), at: i.at ?? this.clock(), v: 1 }; const token = i.claimToken; if (typeof token !== 'string' || token.length === 0)
|
|
146
|
+
return; await withFileLock(f + '.lock', LOCK, async () => { const all = await read(f, r.at, true); const same = (x) => x.kind === r.kind && x.location === r.location && nameKey(x.participant) === nameKey(r.participant) && x.activity === r.activity && x.session === r.session; const current = all.findLast(x => same(x) && x.outcome === 'dispatching'); if (current === undefined || current.claimToken !== token)
|
|
147
|
+
return; const terminalKey = (x) => same(x) && x.attemptN === r.attemptN && x.outcome !== 'dispatching' && x.outcome !== 'released'; await write(f, [...all.filter(x => x !== current && !terminalKey(x)), r]); }); }
|
|
148
|
+
async listEvidence(i = {}) { const loc = i.location === undefined ? undefined : await canon(i.location); return (await read(this.file('user', 'evidence'), i.now ?? this.clock())).filter(r => r.outcome !== 'released' && (!loc || r.location === loc) && (!i.participant || nameKey(r.participant) === nameKey(i.participant)) && (!i.session || r.session === i.session) && (!i.activity || r.activity === i.activity) && (!i.kind || r.kind === i.kind)); }
|
|
149
|
+
async gcEvidence(i) { const f = this.file('user', 'evidence'), pending = new Set(i.pendingWakeActivities ?? []); await withFileLock(f + '.lock', LOCK, async () => write(f, (await read(f, this.clock(), true)).filter(r => r.at >= i.before || r.kind === 'wake' && r.outcome === 'accepted' && pending.has(r.activity)))); }
|
|
131
150
|
async reconcileBinding(i = {}) { let b = []; try {
|
|
132
151
|
b = [...await this.listPresence({ scopes: i.scopes, now: i.now })];
|
|
133
152
|
if (i.artifact) {
|
package/dist/host-ledger.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ export interface EvidenceRecord {
|
|
|
53
53
|
readonly attemptN?: number;
|
|
54
54
|
readonly message?: string;
|
|
55
55
|
readonly diagnostic?: unknown;
|
|
56
|
+
readonly claimToken?: string;
|
|
56
57
|
}
|
|
57
58
|
export interface EvidenceClaim {
|
|
58
59
|
readonly location: string;
|
|
@@ -61,9 +62,12 @@ export interface EvidenceClaim {
|
|
|
61
62
|
readonly activity: string;
|
|
62
63
|
readonly kind: EvidenceRecord['kind'];
|
|
63
64
|
readonly leaseMs: number;
|
|
65
|
+
readonly claimToken?: string;
|
|
64
66
|
readonly now?: number;
|
|
65
67
|
}
|
|
66
|
-
export type EvidenceRelease = Omit<EvidenceClaim, 'leaseMs'
|
|
68
|
+
export type EvidenceRelease = Omit<EvidenceClaim, 'leaseMs'> & {
|
|
69
|
+
readonly claimToken: string;
|
|
70
|
+
};
|
|
67
71
|
export interface EvidenceLookup {
|
|
68
72
|
readonly location?: string;
|
|
69
73
|
readonly participant?: string;
|
|
@@ -74,6 +78,7 @@ export interface EvidenceLookup {
|
|
|
74
78
|
}
|
|
75
79
|
export interface EvidenceGc {
|
|
76
80
|
readonly before: number;
|
|
81
|
+
readonly pendingWakeActivities?: readonly string[];
|
|
77
82
|
}
|
|
78
83
|
export interface WakeAttention {
|
|
79
84
|
readonly squarePath: string;
|
|
@@ -127,6 +132,7 @@ export interface WakeAttemptLookup {
|
|
|
127
132
|
}
|
|
128
133
|
export type ClaimResult = {
|
|
129
134
|
readonly status: 'acquired';
|
|
135
|
+
readonly claimToken: string;
|
|
130
136
|
} | {
|
|
131
137
|
readonly status: 'busy' | 'delivered';
|
|
132
138
|
readonly record: EvidenceRecord;
|
|
@@ -160,10 +166,14 @@ export interface HostLedgerPort {
|
|
|
160
166
|
listPresence(input: PresenceLookup): Promise<readonly PresenceRecord[]>;
|
|
161
167
|
claimEvidence(input: EvidenceClaim): Promise<ClaimResult>;
|
|
162
168
|
releaseEvidence(input: EvidenceRelease): Promise<void>;
|
|
163
|
-
appendEvidence(input: EvidenceRecord
|
|
169
|
+
appendEvidence(input: EvidenceRecord & {
|
|
170
|
+
readonly claimToken: string;
|
|
171
|
+
}): Promise<void>;
|
|
164
172
|
listEvidence(input: EvidenceLookup): Promise<readonly EvidenceRecord[]>;
|
|
165
173
|
listWakeAttempts(input?: WakeAttemptLookup): Promise<readonly EvidenceRecord[]>;
|
|
166
|
-
appendWakeAttempt(input: EvidenceRecord
|
|
174
|
+
appendWakeAttempt(input: EvidenceRecord & {
|
|
175
|
+
readonly claimToken: string;
|
|
176
|
+
}): Promise<void>;
|
|
167
177
|
claimWakeDispatch(input: WakeDispatchClaimInput): Promise<WakeDispatchClaim>;
|
|
168
178
|
transitionWakeDispatch(input: WakeDispatchTransitionInput): Promise<boolean>;
|
|
169
179
|
releaseWakeDispatch(input: WakeDispatchReleaseInput): Promise<void>;
|
package/dist/notifications.js
CHANGED
|
@@ -9,7 +9,8 @@ import { displayAttentionPath } from './attention-presentation.js';
|
|
|
9
9
|
import { openSquare } from './square-file-adapter.js';
|
|
10
10
|
import { closeOpenSquare } from './open-square.js';
|
|
11
11
|
import { notificationDelivered, resolveParticipant } from './views.js';
|
|
12
|
-
import { deliverPending, sweepPending, sweepPendingFromState } from './delivery-operations.js';
|
|
12
|
+
import { deliverPending, observeSquare, sweepPending, sweepPendingFromState } from './delivery-operations.js';
|
|
13
|
+
import { nameKey } from './model.js';
|
|
13
14
|
import { projectPresentationEvidence } from './square-projections.js';
|
|
14
15
|
import { createHostLedgerPort } from './host-ledger-file-adapter.js';
|
|
15
16
|
export { planActNotifications, matchesMentionTarget };
|
|
@@ -101,6 +102,34 @@ export async function createDefaultWakeTransport(hostLedger, clock, env = proces
|
|
|
101
102
|
const adapters = await defaultWakeAdapters();
|
|
102
103
|
return createWakeTransport(env.SQUARE_DISABLE_PASEO_WAKE === '1' ? adapters.filter((adapter) => adapter.kind !== 'paseo') : adapters, hostLedger, clock);
|
|
103
104
|
}
|
|
105
|
+
async function wakeRequestIsCurrent(request, hostLedger, now) {
|
|
106
|
+
const activity = parseActivityId(request.activity);
|
|
107
|
+
if (activity === undefined)
|
|
108
|
+
return false;
|
|
109
|
+
let square;
|
|
110
|
+
try {
|
|
111
|
+
square = await openSquare(request.location, { hostLedger });
|
|
112
|
+
const current = await observeSquare({ artifact: square.artifact, hostLedger, location: request.location, now });
|
|
113
|
+
const pending = current.pending.some((entry) => nameKey(entry.recipient) === nameKey(request.participant)
|
|
114
|
+
&& entry.notifications.some((notification) => notification.item.index === activity));
|
|
115
|
+
const bound = current.bindings.some((binding) => nameKey(binding.participant) === nameKey(request.route.participant)
|
|
116
|
+
&& binding.sessionId === request.route.sessionId
|
|
117
|
+
&& binding.location === request.route.location);
|
|
118
|
+
const published = (current.state.routes ?? []).some((route) => route.location === request.route.location
|
|
119
|
+
&& nameKey(route.participant) === nameKey(request.route.participant)
|
|
120
|
+
&& route.sessionId === request.route.sessionId
|
|
121
|
+
&& route.kind === request.route.kind
|
|
122
|
+
&& JSON.stringify(route.address) === JSON.stringify(request.route.address));
|
|
123
|
+
return pending && bound && published;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
if (square !== undefined)
|
|
130
|
+
await closeOpenSquare(square);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
104
133
|
export function createWakeTransport(adapters, hostLedger, clock) {
|
|
105
134
|
return {
|
|
106
135
|
probe: async (route) => {
|
|
@@ -117,12 +146,12 @@ export function createWakeTransport(adapters, hostLedger, clock) {
|
|
|
117
146
|
return { outcome: 'not-capable', diagnostic: error instanceof Error ? error.message : String(error) };
|
|
118
147
|
}
|
|
119
148
|
},
|
|
120
|
-
attempt: async (request, timeoutMs) => {
|
|
149
|
+
attempt: async (request, timeoutMs, beforeSend) => {
|
|
121
150
|
const adapter = adapters.find((candidate) => candidate.kind === request.route.kind);
|
|
122
151
|
if (adapter === undefined)
|
|
123
152
|
return { outcome: 'not-capable', diagnostic: `no adapter for ${request.route.kind}` };
|
|
124
153
|
try {
|
|
125
|
-
const result = await adapter.dispatch(request.route.address, renderWakePayload(request), async () => true, timeoutMs);
|
|
154
|
+
const result = await adapter.dispatch(request.route.address, renderWakePayload(request), async () => (await (beforeSend ?? (async () => true))()) && await wakeRequestIsCurrent(request, hostLedger, clock()), timeoutMs);
|
|
126
155
|
if (result.outcome === 'accepted')
|
|
127
156
|
return { outcome: 'accepted' };
|
|
128
157
|
if (result.outcome === 'failed')
|
|
@@ -7,7 +7,7 @@ const nativeSessions = [
|
|
|
7
7
|
{ variable: 'CLAUDE_CODE_SESSION_ID', provider: 'claude' },
|
|
8
8
|
{ variable: 'CODEX_THREAD_ID', provider: 'codex' },
|
|
9
9
|
{ variable: 'OPENCODE_SESSION_ID', provider: 'opencode' },
|
|
10
|
-
{ variable: '
|
|
10
|
+
{ variable: 'SQUARE_PI_SESSION_ID', provider: 'pi' },
|
|
11
11
|
];
|
|
12
12
|
export function automaticParticipant(provider, sessionId, env) {
|
|
13
13
|
const configured = env.SQUARE_PARTICIPANT_NAME?.trim();
|
package/dist/ports.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ export interface SquareArtifactPort {
|
|
|
11
11
|
transact<R>(fn: (state: SquareState, version: number) => {
|
|
12
12
|
state?: SquareState;
|
|
13
13
|
result: R;
|
|
14
|
-
}
|
|
14
|
+
} | Promise<{
|
|
15
|
+
state?: SquareState;
|
|
16
|
+
result: R;
|
|
17
|
+
}>): Promise<R>;
|
|
15
18
|
changed(sinceVersion: number, timeoutMs: number): Promise<boolean>;
|
|
16
19
|
close(): Promise<void>;
|
|
17
20
|
}
|
|
@@ -22,7 +25,7 @@ export interface WakeTransportPort {
|
|
|
22
25
|
readonly outcome: 'not-capable';
|
|
23
26
|
readonly diagnostic?: string;
|
|
24
27
|
}>;
|
|
25
|
-
attempt(request: WakeRequest, timeoutMs: number): Promise<WakeOutcome>;
|
|
28
|
+
attempt(request: WakeRequest, timeoutMs: number, beforeSend?: () => Promise<boolean>): Promise<WakeOutcome>;
|
|
26
29
|
/** Optional route retirement supplied by the concrete executor adapter. */
|
|
27
30
|
invalidate?(request: WakeRequest): Promise<void>;
|
|
28
31
|
}
|
|
@@ -17,27 +17,29 @@ export async function presentPending(input) {
|
|
|
17
17
|
const item = before.state.acts.find((activity) => activity.index === index);
|
|
18
18
|
if (item === undefined || delivery.isSeen(input.participant, index) || !delivery.pendingFor(input.participant).some((notification) => notification.item.index === index))
|
|
19
19
|
return { presented: false };
|
|
20
|
+
let claimToken;
|
|
20
21
|
if (input.hostLedger !== undefined && input.session !== undefined) {
|
|
21
|
-
const claim = await input.hostLedger.claimEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', leaseMs: input.timeoutMs ?? 5000
|
|
22
|
+
const claim = await input.hostLedger.claimEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', leaseMs: input.timeoutMs ?? 5000 });
|
|
22
23
|
if (claim.status !== 'acquired')
|
|
23
24
|
return { presented: false };
|
|
25
|
+
claimToken = claim.claimToken;
|
|
24
26
|
let current;
|
|
25
27
|
try {
|
|
26
28
|
current = await input.artifact.read();
|
|
27
29
|
}
|
|
28
30
|
catch {
|
|
29
|
-
await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation',
|
|
31
|
+
await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken }).catch(() => undefined);
|
|
30
32
|
return { presented: false };
|
|
31
33
|
}
|
|
32
34
|
if (deriveDeliveryModel(current.state).isSeen(input.participant, index)) {
|
|
33
|
-
await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation',
|
|
35
|
+
await input.hostLedger.releaseEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', claimToken }).catch(() => undefined);
|
|
34
36
|
return { presented: false };
|
|
35
37
|
}
|
|
36
38
|
try {
|
|
37
39
|
await input.sink.present(item);
|
|
38
40
|
}
|
|
39
41
|
catch (error) {
|
|
40
|
-
await input.hostLedger.appendEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', outcome: 'failed', message: error instanceof Error ? error.message : String(error),
|
|
42
|
+
await input.hostLedger.appendEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', outcome: 'failed', message: error instanceof Error ? error.message : String(error), claimToken });
|
|
41
43
|
throw error;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
@@ -45,7 +47,7 @@ export async function presentPending(input) {
|
|
|
45
47
|
await input.sink.present(item);
|
|
46
48
|
if (input.markSeen !== false)
|
|
47
49
|
await input.artifact.transact((state) => { const changed = recordObservation(state, input.participant, index, 'seen', input.now ?? Date.now()); return changed ? { state, result: undefined } : { result: undefined }; });
|
|
48
|
-
if (input.hostLedger !== undefined && input.session !== undefined)
|
|
49
|
-
await input.hostLedger.appendEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', outcome: input.markSeen === false ? 'clipped' : 'presented', ...(input.markSeen === false ? { message: 'presentation clipped' } : {}),
|
|
50
|
+
if (input.hostLedger !== undefined && input.session !== undefined && claimToken !== undefined)
|
|
51
|
+
await input.hostLedger.appendEvidence({ location: input.location, participant: input.participant, session: input.session, activity: formatActivityId(index), kind: 'presentation', outcome: input.markSeen === false ? 'clipped' : 'presented', ...(input.markSeen === false ? { message: 'presentation clipped' } : {}), claimToken });
|
|
50
52
|
return { presented: true, activity: item };
|
|
51
53
|
}
|
package/dist/registry.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface RegistryBinding {
|
|
|
10
10
|
child: boolean;
|
|
11
11
|
route?: PresenceRecord['route'];
|
|
12
12
|
updatedAt: number;
|
|
13
|
+
epoch: number;
|
|
13
14
|
}
|
|
14
15
|
export interface RegistryWriteOptions {
|
|
15
16
|
channel?: SessionChannel;
|
|
@@ -18,8 +19,14 @@ export interface RegistryWriteOptions {
|
|
|
18
19
|
at?: number;
|
|
19
20
|
env?: NodeJS.ProcessEnv;
|
|
20
21
|
}
|
|
22
|
+
type PresenceWithEpoch = PresenceRecord & {
|
|
23
|
+
readonly epoch?: number;
|
|
24
|
+
};
|
|
21
25
|
export declare function registryPath(env?: NodeJS.ProcessEnv): string;
|
|
22
26
|
export declare function canonicalSquarePath(squarePath: string): Promise<string>;
|
|
27
|
+
export declare function presenceEpoch(record: PresenceWithEpoch | undefined): number;
|
|
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>): Promise<T>;
|
|
23
30
|
export declare function recordJoin(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): Promise<void>;
|
|
24
31
|
export declare function recordDone(sessionId: string, name: string, squarePath: string, options?: RegistryWriteOptions): Promise<void>;
|
|
25
32
|
export declare function readActiveBindings(now?: number, env?: NodeJS.ProcessEnv): Promise<RegistryBinding[]>;
|
|
@@ -36,10 +43,40 @@ export type CurrentParticipantBinding = Readonly<{
|
|
|
36
43
|
created: boolean;
|
|
37
44
|
sessionId: string;
|
|
38
45
|
}>;
|
|
39
|
-
export
|
|
46
|
+
export type OwnershipClaim = {
|
|
40
47
|
readonly status: 'acquired' | 'owned';
|
|
41
48
|
readonly sessionId: string;
|
|
42
|
-
|
|
49
|
+
readonly epoch: number;
|
|
50
|
+
} | undefined;
|
|
51
|
+
export type TakeoverClaimToken = {
|
|
52
|
+
readonly sessionId: string;
|
|
53
|
+
readonly epoch: number;
|
|
54
|
+
};
|
|
55
|
+
export type TakeoverRunResult<T> = {
|
|
56
|
+
readonly status: 'acquired';
|
|
57
|
+
readonly sessionId: string;
|
|
58
|
+
readonly epoch: number;
|
|
59
|
+
readonly result: T;
|
|
60
|
+
} | {
|
|
61
|
+
readonly status: 'busy';
|
|
62
|
+
readonly epoch: number;
|
|
63
|
+
};
|
|
64
|
+
export declare function readParticipantOwner(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<RegistryBinding | undefined>;
|
|
65
|
+
export declare function sessionOwnsParticipant(squarePath: string, name: string, sessionId: string, env?: NodeJS.ProcessEnv, expectedEpoch?: number): Promise<boolean>;
|
|
66
|
+
export declare function claimSessionParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<OwnershipClaim>;
|
|
67
|
+
/**
|
|
68
|
+
* Fenced takeover: the ownership claim and the lifecycle run as one critical section under the
|
|
69
|
+
* presence-claim lock, so no second takeover can interleave between claim and commit. The claim
|
|
70
|
+
* persists nothing until the lifecycle commits: standing owner rows stay authoritative through
|
|
71
|
+
* the lifecycle (a self-takeover can therefore never clobber its own standing row). On success
|
|
72
|
+
* the standing rows are replaced by the new owner's row at the claim epoch; on refusal, rows
|
|
73
|
+
* carrying the claim token (session + channel + claim epoch) are withdrawn and the captured
|
|
74
|
+
* standing rows are restored exactly.
|
|
75
|
+
*/
|
|
76
|
+
export declare function claimSessionTakeover<T>(squarePath: string, name: string, env: NodeJS.ProcessEnv | undefined, opts: {
|
|
77
|
+
readonly expectedEpoch?: number;
|
|
78
|
+
readonly expectedSession?: string;
|
|
79
|
+
} | undefined, lifecycle: (claim: TakeoverClaimToken) => Promise<T>): Promise<TakeoverRunResult<T>>;
|
|
43
80
|
export declare function releaseSessionParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
44
81
|
export declare function bindCurrentParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<CurrentParticipantBinding>;
|
|
45
82
|
export declare function unbindCurrentParticipant(squarePath: string, name: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
@@ -60,3 +97,4 @@ export declare function recordLocalJoin(name: string, squarePath: string, env?:
|
|
|
60
97
|
export declare function recordLocalDone(name: string, squarePath: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
61
98
|
export declare function recordSessionJoin(sessionId: string, name: string, squarePath: string, channel: SessionChannel, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
62
99
|
export declare function recordSessionDone(sessionId: string, name: string, squarePath: string, channel: SessionChannel, env?: NodeJS.ProcessEnv): Promise<boolean>;
|
|
100
|
+
export {};
|