@awiki/dsh-plugin 0.3.3 → 0.3.4
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/README.md +37 -31
- package/README.zh.md +30 -27
- package/cordis.patch.yml +17 -0
- package/lib/client.js +1269 -272
- package/lib/client.js.map +1 -1
- package/lib/index.js +874 -314
- package/lib/provider.js +52 -16
- package/lib/{sdk-adapter-CDLTgHJA.mjs → sdk-adapter-BeJakxLP.mjs} +142 -4
- package/lib/typert.host.js +419 -89
- package/lib/typert.remote-client.d.ts +19 -3
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +419 -89
- package/lib/types/client/AwikiDevices.d.ts +28 -0
- package/lib/types/client/AwikiDevices.d.ts.map +1 -0
- package/lib/types/client/AwikiDevices.js +85 -0
- package/lib/types/client/AwikiDevices.js.map +1 -0
- package/lib/types/client/AwikiIdentityAccess.d.ts +6 -5
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +88 -27
- package/lib/types/client/AwikiIdentityAccess.js.map +1 -1
- package/lib/types/client/AwikiOverlay.d.ts.map +1 -1
- package/lib/types/client/AwikiOverlay.js +4 -3
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/controller.d.ts +20 -5
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +38 -7
- package/lib/types/client/controller.js.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +8 -0
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/slots.d.ts +10 -2
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/index.d.ts +51 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +675 -174
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener-state.d.ts +5 -3
- package/lib/types/listener-state.d.ts.map +1 -1
- package/lib/types/listener-state.js +28 -13
- package/lib/types/listener-state.js.map +1 -1
- package/lib/types/listener.d.ts +9 -32
- package/lib/types/listener.d.ts.map +1 -1
- package/lib/types/listener.js +11 -127
- package/lib/types/listener.js.map +1 -1
- package/lib/types/provider-api.d.ts +96 -4
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts +7 -1
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +53 -15
- package/lib/types/provider.js.map +1 -1
- package/lib/types/realtime-supervisor.d.ts +43 -0
- package/lib/types/realtime-supervisor.d.ts.map +1 -0
- package/lib/types/realtime-supervisor.js +166 -0
- package/lib/types/realtime-supervisor.js.map +1 -0
- package/lib/types/sdk-adapter.d.ts +28 -2
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +132 -3
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/types.d.ts +76 -6
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/package.json +18 -18
- package/lib/types/profile-state.d.ts +0 -11
- package/lib/types/profile-state.d.ts.map +0 -1
- package/lib/types/profile-state.js +0 -48
- package/lib/types/profile-state.js.map +0 -1
package/lib/index.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import { n as downloadedAttachment } from "./sdk-adapter-
|
|
1
|
+
import { n as downloadedAttachment } from "./sdk-adapter-BeJakxLP.mjs";
|
|
2
2
|
import "@deepseek-ai/cordis";
|
|
3
|
-
import {
|
|
3
|
+
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import {
|
|
5
|
+
import { isAbsolute, join } from "node:path";
|
|
6
6
|
import z from "@deepseek-ai/schemastery";
|
|
7
7
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
8
8
|
import { SettingsConflictError, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
9
9
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
10
10
|
import { chmod, lstat, mkdir, readFile, readdir, rename, rm, unlink, utimes, writeFile } from "node:fs/promises";
|
|
11
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
12
11
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
13
12
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
14
13
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
14
|
//#region lib/types/types.js
|
|
17
15
|
/** Client-safe AWiki service and Remote data types. */
|
|
18
16
|
/** Exact browser acknowledgement required before locally signing out. */
|
|
@@ -1450,12 +1448,13 @@ var AwikiConversationPreferenceStore = class {
|
|
|
1450
1448
|
};
|
|
1451
1449
|
//#endregion
|
|
1452
1450
|
//#region lib/types/listener-state.js
|
|
1453
|
-
const STATE_VERSION =
|
|
1451
|
+
const STATE_VERSION = 2;
|
|
1454
1452
|
const MAX_STATE_BYTES = 1048576;
|
|
1455
1453
|
const MAX_CONVERSATIONS = 1e3;
|
|
1456
|
-
function emptyState() {
|
|
1454
|
+
function emptyState(identityScopeHash) {
|
|
1457
1455
|
return {
|
|
1458
1456
|
version: STATE_VERSION,
|
|
1457
|
+
identityScopeHash,
|
|
1459
1458
|
conversations: {}
|
|
1460
1459
|
};
|
|
1461
1460
|
}
|
|
@@ -1467,8 +1466,10 @@ function boundedString(value, field) {
|
|
|
1467
1466
|
return value;
|
|
1468
1467
|
}
|
|
1469
1468
|
/** Validate and detach the complete state file before the listener trusts any route. */
|
|
1470
|
-
function parseState(value) {
|
|
1471
|
-
if (
|
|
1469
|
+
function parseState(value, identityScopeHash) {
|
|
1470
|
+
if (isRecord(value) && value.version === 1) return emptyState(identityScopeHash);
|
|
1471
|
+
if (isRecord(value) && value.version === STATE_VERSION && typeof value.identityScopeHash === "string" && value.identityScopeHash !== identityScopeHash) return emptyState(identityScopeHash);
|
|
1472
|
+
if (!isRecord(value) || value.version !== STATE_VERSION || value.identityScopeHash !== identityScopeHash || !isRecord(value.conversations)) throw new TypeError("awiki: listener state is invalid");
|
|
1472
1473
|
const entries = Object.entries(value.conversations);
|
|
1473
1474
|
if (entries.length > MAX_CONVERSATIONS) throw new TypeError("awiki: listener state is too large");
|
|
1474
1475
|
const conversations = {};
|
|
@@ -1491,6 +1492,7 @@ function parseState(value) {
|
|
|
1491
1492
|
}
|
|
1492
1493
|
return {
|
|
1493
1494
|
version: STATE_VERSION,
|
|
1495
|
+
identityScopeHash,
|
|
1494
1496
|
conversations
|
|
1495
1497
|
};
|
|
1496
1498
|
}
|
|
@@ -1501,28 +1503,31 @@ function isMissing(error) {
|
|
|
1501
1503
|
var AwikiListenerStateStore = class {
|
|
1502
1504
|
hostDirectory;
|
|
1503
1505
|
statePath;
|
|
1504
|
-
|
|
1506
|
+
identityScopeHash;
|
|
1507
|
+
constructor(stateRoot, identityScope) {
|
|
1508
|
+
if (identityScope.length === 0 || identityScope.length > 2048) throw new TypeError("awiki: listener identity scope is invalid");
|
|
1509
|
+
this.identityScopeHash = createHash("sha256").update(identityScope).digest("hex");
|
|
1505
1510
|
this.hostDirectory = join(stateRoot, ".host");
|
|
1506
1511
|
this.statePath = join(this.hostDirectory, "listener-state.json");
|
|
1507
1512
|
}
|
|
1508
|
-
/** Load the current
|
|
1513
|
+
/** Load the current identity scope or reset unscoped v1 state on first use. */
|
|
1509
1514
|
async load() {
|
|
1510
|
-
if (!await this.hasPrivateHostDirectory()) return emptyState();
|
|
1515
|
+
if (!await this.hasPrivateHostDirectory()) return emptyState(this.identityScopeHash);
|
|
1511
1516
|
try {
|
|
1512
1517
|
const metadata = await lstat(this.statePath);
|
|
1513
1518
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is invalid");
|
|
1514
1519
|
const text = await readFile(this.statePath, "utf8");
|
|
1515
1520
|
if (Buffer.byteLength(text, "utf8") > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is invalid");
|
|
1516
|
-
return parseState(JSON.parse(text));
|
|
1521
|
+
return parseState(JSON.parse(text), this.identityScopeHash);
|
|
1517
1522
|
} catch (error) {
|
|
1518
|
-
if (isMissing(error)) return emptyState();
|
|
1523
|
+
if (isMissing(error)) return emptyState(this.identityScopeHash);
|
|
1519
1524
|
if (error instanceof SyntaxError) throw new TypeError("awiki: listener state file is invalid");
|
|
1520
1525
|
throw error;
|
|
1521
1526
|
}
|
|
1522
1527
|
}
|
|
1523
1528
|
/** Replace the state atomically without ever writing message or Agent text. */
|
|
1524
1529
|
async save(state) {
|
|
1525
|
-
const snapshot = parseState(state);
|
|
1530
|
+
const snapshot = parseState(state, this.identityScopeHash);
|
|
1526
1531
|
const text = `${JSON.stringify(snapshot)}\n`;
|
|
1527
1532
|
if (Buffer.byteLength(text, "utf8") > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is too large");
|
|
1528
1533
|
await mkdir(this.hostDirectory, {
|
|
@@ -1744,11 +1749,12 @@ function incomingFromPeer(message, conversation) {
|
|
|
1744
1749
|
}
|
|
1745
1750
|
function copyState(state) {
|
|
1746
1751
|
return {
|
|
1747
|
-
version:
|
|
1752
|
+
version: 2,
|
|
1753
|
+
identityScopeHash: state.identityScopeHash,
|
|
1748
1754
|
conversations: Object.fromEntries(Object.entries(state.conversations).map(([id, route]) => [id, { ...route }]))
|
|
1749
1755
|
};
|
|
1750
1756
|
}
|
|
1751
|
-
/**
|
|
1757
|
+
/** Consume authorized Direct text only after the identity supervisor commits synchronization. */
|
|
1752
1758
|
var AwikiAgentListener = class {
|
|
1753
1759
|
awiki;
|
|
1754
1760
|
agents;
|
|
@@ -1757,34 +1763,23 @@ var AwikiAgentListener = class {
|
|
|
1757
1763
|
store;
|
|
1758
1764
|
logger;
|
|
1759
1765
|
stateReady;
|
|
1760
|
-
state
|
|
1761
|
-
version: 1,
|
|
1762
|
-
conversations: {}
|
|
1763
|
-
};
|
|
1766
|
+
state;
|
|
1764
1767
|
stateMutation = Promise.resolve();
|
|
1765
1768
|
syncMutation = Promise.resolve();
|
|
1766
1769
|
scheduledMessageIds = /* @__PURE__ */ new Set();
|
|
1767
1770
|
conversationQueues = /* @__PURE__ */ new Map();
|
|
1768
|
-
lifecycle;
|
|
1769
|
-
started;
|
|
1770
|
-
resolveStarted;
|
|
1771
|
-
rejectStarted;
|
|
1772
|
-
lifecycleGeneration = 0;
|
|
1773
|
-
streamGeneration = 0;
|
|
1774
|
-
activeRealtime;
|
|
1775
1771
|
stopped = false;
|
|
1776
|
-
termination;
|
|
1777
|
-
resolveTermination;
|
|
1778
|
-
terminationSettled = false;
|
|
1779
1772
|
constructor(awiki, agents, config, logger, store) {
|
|
1780
1773
|
this.awiki = awiki;
|
|
1781
1774
|
this.agents = agents;
|
|
1782
1775
|
this.config = config;
|
|
1783
|
-
this.termination = new Promise((resolve) => {
|
|
1784
|
-
this.resolveTermination = resolve;
|
|
1785
|
-
});
|
|
1786
1776
|
this.allowedPeers = new Set(config.allowedPeers.map((peer) => peer.startsWith("did:") ? peer : peer.toLowerCase()));
|
|
1787
|
-
this.store = store ?? new AwikiListenerStateStore(config.stateRoot);
|
|
1777
|
+
this.store = store ?? new AwikiListenerStateStore(config.stateRoot, config.identityScope);
|
|
1778
|
+
this.state = {
|
|
1779
|
+
version: 2,
|
|
1780
|
+
identityScopeHash: this.store.identityScopeHash,
|
|
1781
|
+
conversations: {}
|
|
1782
|
+
};
|
|
1788
1783
|
this.logger = logger ?? {
|
|
1789
1784
|
debug() {},
|
|
1790
1785
|
info() {},
|
|
@@ -1796,38 +1791,11 @@ var AwikiAgentListener = class {
|
|
|
1796
1791
|
this.state = state;
|
|
1797
1792
|
});
|
|
1798
1793
|
}
|
|
1799
|
-
/**
|
|
1800
|
-
|
|
1801
|
-
if (this.started !== void 0) return this.started;
|
|
1802
|
-
this.started = new Promise((resolve, reject) => {
|
|
1803
|
-
this.resolveStarted = resolve;
|
|
1804
|
-
this.rejectStarted = reject;
|
|
1805
|
-
});
|
|
1806
|
-
const generation = ++this.lifecycleGeneration;
|
|
1807
|
-
this.lifecycle = this.run(generation).then(() => {
|
|
1808
|
-
this.finishTermination({ kind: "stopped" });
|
|
1809
|
-
}, (error) => {
|
|
1810
|
-
this.rejectStarted?.(error);
|
|
1811
|
-
this.rejectStarted = void 0;
|
|
1812
|
-
this.finishTermination({
|
|
1813
|
-
kind: "failed",
|
|
1814
|
-
error
|
|
1815
|
-
});
|
|
1816
|
-
if (!this.stopped) this.logger.warn("AWiki realtime listener stopped: %s", error instanceof Error ? error.message : "unknown failure");
|
|
1817
|
-
});
|
|
1818
|
-
return this.started;
|
|
1819
|
-
}
|
|
1820
|
-
/** Resolve once with either orderly shutdown or the exact terminal lifecycle failure. */
|
|
1821
|
-
whenTerminated() {
|
|
1822
|
-
return this.termination;
|
|
1823
|
-
}
|
|
1824
|
-
/** Deterministic canonical sync plus committed-history reconciliation for tests and recovery. */
|
|
1825
|
-
async synchronizeOnce(reason) {
|
|
1794
|
+
/** Reconcile only committed history; this consumer cannot start WSS or advance sync. */
|
|
1795
|
+
async reconcileOnce() {
|
|
1826
1796
|
await this.enqueueSync(async () => {
|
|
1827
1797
|
await this.stateReady;
|
|
1828
1798
|
if (this.stopped) return;
|
|
1829
|
-
await this.awiki.syncNow(reason);
|
|
1830
|
-
if (this.stopped) return;
|
|
1831
1799
|
await this.reconcileCommittedHistory();
|
|
1832
1800
|
});
|
|
1833
1801
|
}
|
|
@@ -1837,84 +1805,12 @@ var AwikiAgentListener = class {
|
|
|
1837
1805
|
while (this.conversationQueues.size > 0) await Promise.allSettled([...this.conversationQueues.values()]);
|
|
1838
1806
|
await this.stateMutation;
|
|
1839
1807
|
}
|
|
1840
|
-
/**
|
|
1808
|
+
/** Fence late work, drain committed messages, then release listener-owned Agents. */
|
|
1841
1809
|
async dispose() {
|
|
1842
1810
|
if (this.stopped) return;
|
|
1843
1811
|
this.stopped = true;
|
|
1844
|
-
this.lifecycleGeneration += 1;
|
|
1845
|
-
this.streamGeneration += 1;
|
|
1846
|
-
const active = this.activeRealtime;
|
|
1847
|
-
this.activeRealtime = void 0;
|
|
1848
|
-
await active?.session.stop().catch(() => void 0);
|
|
1849
|
-
await this.lifecycle;
|
|
1850
|
-
this.resolveStarted?.();
|
|
1851
|
-
this.resolveStarted = void 0;
|
|
1852
|
-
this.rejectStarted = void 0;
|
|
1853
1812
|
await this.whenIdle();
|
|
1854
1813
|
await this.agents.dispose();
|
|
1855
|
-
this.finishTermination({ kind: "stopped" });
|
|
1856
|
-
}
|
|
1857
|
-
finishTermination(result) {
|
|
1858
|
-
if (this.terminationSettled) return;
|
|
1859
|
-
this.terminationSettled = true;
|
|
1860
|
-
this.resolveTermination(result);
|
|
1861
|
-
}
|
|
1862
|
-
currentLifecycle(generation) {
|
|
1863
|
-
return !this.stopped && this.lifecycleGeneration === generation;
|
|
1864
|
-
}
|
|
1865
|
-
async run(lifecycleGeneration) {
|
|
1866
|
-
await this.synchronize("session_start", lifecycleGeneration);
|
|
1867
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1868
|
-
let active = await this.openRealtime(lifecycleGeneration);
|
|
1869
|
-
if (active === void 0) return;
|
|
1870
|
-
this.resolveStarted?.();
|
|
1871
|
-
this.resolveStarted = void 0;
|
|
1872
|
-
this.rejectStarted = void 0;
|
|
1873
|
-
try {
|
|
1874
|
-
while (this.currentLifecycle(lifecycleGeneration)) {
|
|
1875
|
-
const event = await active.session.nextEvent();
|
|
1876
|
-
if (!this.currentLifecycle(lifecycleGeneration) || this.activeRealtime?.generation !== active.generation) return;
|
|
1877
|
-
if (event === null) {
|
|
1878
|
-
this.activeRealtime = void 0;
|
|
1879
|
-
this.streamGeneration += 1;
|
|
1880
|
-
await active.session.stop();
|
|
1881
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1882
|
-
await this.synchronize("websocket_reconnect", lifecycleGeneration);
|
|
1883
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1884
|
-
const replacement = await this.openRealtime(lifecycleGeneration);
|
|
1885
|
-
if (replacement === void 0) return;
|
|
1886
|
-
active = replacement;
|
|
1887
|
-
continue;
|
|
1888
|
-
}
|
|
1889
|
-
if (event.kind !== "sync_required") continue;
|
|
1890
|
-
await this.synchronize(event.cause === "reconnected" ? "websocket_reconnect" : "websocket_hint", lifecycleGeneration);
|
|
1891
|
-
}
|
|
1892
|
-
} finally {
|
|
1893
|
-
if (this.activeRealtime?.generation === active.generation) this.activeRealtime = void 0;
|
|
1894
|
-
await active.session.stop().catch(() => void 0);
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
async openRealtime(lifecycleGeneration) {
|
|
1898
|
-
const session = await this.awiki.startRealtime();
|
|
1899
|
-
if (!this.currentLifecycle(lifecycleGeneration)) {
|
|
1900
|
-
await session.stop().catch(() => void 0);
|
|
1901
|
-
return;
|
|
1902
|
-
}
|
|
1903
|
-
const active = {
|
|
1904
|
-
generation: ++this.streamGeneration,
|
|
1905
|
-
session
|
|
1906
|
-
};
|
|
1907
|
-
this.activeRealtime = active;
|
|
1908
|
-
return active;
|
|
1909
|
-
}
|
|
1910
|
-
async synchronize(reason, lifecycleGeneration) {
|
|
1911
|
-
await this.enqueueSync(async () => {
|
|
1912
|
-
await this.stateReady;
|
|
1913
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1914
|
-
await this.awiki.syncNow(reason);
|
|
1915
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1916
|
-
await this.reconcileCommittedHistory();
|
|
1917
|
-
});
|
|
1918
1814
|
}
|
|
1919
1815
|
enqueueSync(operation) {
|
|
1920
1816
|
const pending = this.syncMutation.then(operation, operation);
|
|
@@ -2081,7 +1977,8 @@ var AwikiAgentListener = class {
|
|
|
2081
1977
|
...update.lastProcessedMessageId === void 0 ? current?.lastProcessedMessageId === void 0 ? {} : { lastProcessedMessageId: current.lastProcessedMessageId } : { lastProcessedMessageId: update.lastProcessedMessageId }
|
|
2082
1978
|
};
|
|
2083
1979
|
this.state = {
|
|
2084
|
-
version:
|
|
1980
|
+
version: 2,
|
|
1981
|
+
identityScopeHash: this.state.identityScopeHash,
|
|
2085
1982
|
conversations: {
|
|
2086
1983
|
...this.state.conversations,
|
|
2087
1984
|
[conversation.id]: route
|
|
@@ -2094,39 +1991,153 @@ var AwikiAgentListener = class {
|
|
|
2094
1991
|
}
|
|
2095
1992
|
};
|
|
2096
1993
|
//#endregion
|
|
2097
|
-
//#region lib/types/
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
return
|
|
1994
|
+
//#region lib/types/realtime-supervisor.js
|
|
1995
|
+
const RETRY_BASE_DELAY_MS = 1e3;
|
|
1996
|
+
const RETRY_MAX_DELAY_MS = 3e4;
|
|
1997
|
+
function syncReason(cause) {
|
|
1998
|
+
if (cause === "session_start") return "session_start";
|
|
1999
|
+
return cause === "reconnected" ? "websocket_reconnect" : "websocket_hint";
|
|
2102
2000
|
}
|
|
2103
|
-
/**
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2001
|
+
/** Own the deployment identity's only WSS without knowing Workspace or Agent policy. */
|
|
2002
|
+
var IdentityRealtimeSupervisor = class {
|
|
2003
|
+
realtime;
|
|
2004
|
+
config;
|
|
2005
|
+
logger;
|
|
2006
|
+
lifecycle;
|
|
2007
|
+
generation = 0;
|
|
2008
|
+
activeSession;
|
|
2009
|
+
stoppedSessions = /* @__PURE__ */ new WeakSet();
|
|
2010
|
+
stopped = false;
|
|
2011
|
+
connected = false;
|
|
2012
|
+
startCount = 0;
|
|
2013
|
+
stopCount = 0;
|
|
2014
|
+
lastCommittedSyncCause;
|
|
2015
|
+
retryTimer;
|
|
2016
|
+
resolveRetry;
|
|
2017
|
+
constructor(realtime, config = {}, logger = {
|
|
2018
|
+
debug() {},
|
|
2019
|
+
info() {},
|
|
2020
|
+
warn() {},
|
|
2021
|
+
error() {},
|
|
2022
|
+
name: "awiki-realtime"
|
|
2023
|
+
}) {
|
|
2024
|
+
this.realtime = realtime;
|
|
2025
|
+
this.config = config;
|
|
2026
|
+
this.logger = logger;
|
|
2120
2027
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2028
|
+
/** Start in the background; identity activation must never await connectivity. */
|
|
2029
|
+
start() {
|
|
2030
|
+
if (this.lifecycle !== void 0 || this.stopped) return;
|
|
2031
|
+
const generation = ++this.generation;
|
|
2032
|
+
this.lifecycle = this.run(generation).catch((error) => {
|
|
2033
|
+
if (!this.stopped) this.logger.warn("AWiki realtime supervisor stopped unexpectedly: %s", error instanceof Error ? error.message : "unknown failure");
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
diagnostics() {
|
|
2037
|
+
return {
|
|
2038
|
+
connected: this.connected,
|
|
2039
|
+
activeSessionCount: this.activeSession === void 0 ? 0 : 1,
|
|
2040
|
+
startCount: this.startCount,
|
|
2041
|
+
stopCount: this.stopCount,
|
|
2042
|
+
...this.lastCommittedSyncCause === void 0 ? {} : { lastCommittedSyncCause: this.lastCommittedSyncCause }
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
/** Fence late events, wake retry sleep, stop the exact session, and join the lifecycle. */
|
|
2046
|
+
async dispose() {
|
|
2047
|
+
if (this.stopped) return;
|
|
2048
|
+
this.stopped = true;
|
|
2049
|
+
this.generation += 1;
|
|
2050
|
+
this.connected = false;
|
|
2051
|
+
this.wakeRetry();
|
|
2052
|
+
const session = this.activeSession;
|
|
2053
|
+
if (session !== void 0) await this.stopSession(session).catch(() => void 0);
|
|
2054
|
+
await this.lifecycle;
|
|
2055
|
+
}
|
|
2056
|
+
current(generation) {
|
|
2057
|
+
return !this.stopped && this.generation === generation;
|
|
2058
|
+
}
|
|
2059
|
+
async run(generation) {
|
|
2060
|
+
let cause = "session_start";
|
|
2061
|
+
let failures = 0;
|
|
2062
|
+
while (this.current(generation)) {
|
|
2063
|
+
let session;
|
|
2064
|
+
try {
|
|
2065
|
+
await this.synchronize(cause, generation);
|
|
2066
|
+
if (!this.current(generation)) return;
|
|
2067
|
+
session = await this.realtime.startRealtime();
|
|
2068
|
+
if (!this.current(generation)) {
|
|
2069
|
+
await this.stopSession(session).catch(() => void 0);
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
this.activeSession = session;
|
|
2073
|
+
this.startCount += 1;
|
|
2074
|
+
this.connected = (await session.getStatus().catch(() => ({ connected: false }))).connected;
|
|
2075
|
+
failures = 0;
|
|
2076
|
+
while (this.current(generation) && this.activeSession === session) {
|
|
2077
|
+
const event = await session.nextEvent();
|
|
2078
|
+
if (!this.current(generation) || this.activeSession !== session) return;
|
|
2079
|
+
if (event === null) {
|
|
2080
|
+
await this.stopSession(session);
|
|
2081
|
+
cause = "reconnected";
|
|
2082
|
+
break;
|
|
2083
|
+
}
|
|
2084
|
+
this.observeConnection(event);
|
|
2085
|
+
if (event.kind !== "sync_required") continue;
|
|
2086
|
+
await this.synchronize(event.cause, generation);
|
|
2087
|
+
}
|
|
2088
|
+
} catch (error) {
|
|
2089
|
+
this.connected = false;
|
|
2090
|
+
if (session !== void 0) await this.stopSession(session).catch(() => void 0);
|
|
2091
|
+
if (!this.current(generation)) return;
|
|
2092
|
+
failures += 1;
|
|
2093
|
+
this.logger.warn("AWiki realtime lifecycle failed; retrying: %s", error instanceof Error ? error.message : "unknown failure");
|
|
2094
|
+
await this.waitForRetry(failures, generation);
|
|
2095
|
+
cause = cause === "session_start" ? "session_start" : "reconnected";
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
async synchronize(cause, generation) {
|
|
2100
|
+
await this.realtime.syncNow(syncReason(cause));
|
|
2101
|
+
if (!this.current(generation)) return;
|
|
2102
|
+
this.lastCommittedSyncCause = cause;
|
|
2103
|
+
try {
|
|
2104
|
+
await this.config.onSynchronized?.(cause);
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
this.logger.warn("AWiki realtime post-sync consumer failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
observeConnection(event) {
|
|
2110
|
+
if (event.kind !== "connection_state_changed") return;
|
|
2111
|
+
this.connected = event.state === "connected";
|
|
2112
|
+
}
|
|
2113
|
+
async stopSession(session) {
|
|
2114
|
+
if (this.stoppedSessions.has(session)) return;
|
|
2115
|
+
this.stoppedSessions.add(session);
|
|
2116
|
+
if (this.activeSession === session) this.activeSession = void 0;
|
|
2117
|
+
this.connected = false;
|
|
2118
|
+
this.stopCount += 1;
|
|
2119
|
+
await session.stop();
|
|
2120
|
+
}
|
|
2121
|
+
waitForRetry(failures, generation) {
|
|
2122
|
+
if (!this.current(generation)) return Promise.resolve();
|
|
2123
|
+
const delay = Math.min(RETRY_BASE_DELAY_MS * 2 ** Math.min(failures - 1, 10), RETRY_MAX_DELAY_MS);
|
|
2124
|
+
return new Promise((resolve) => {
|
|
2125
|
+
const finish = () => {
|
|
2126
|
+
if (this.retryTimer === timer) this.retryTimer = void 0;
|
|
2127
|
+
if (this.resolveRetry === finish) this.resolveRetry = void 0;
|
|
2128
|
+
resolve();
|
|
2129
|
+
};
|
|
2130
|
+
const timer = setTimeout(finish, delay);
|
|
2131
|
+
this.retryTimer = timer;
|
|
2132
|
+
this.resolveRetry = finish;
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
wakeRetry() {
|
|
2136
|
+
const timer = this.retryTimer;
|
|
2137
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2138
|
+
this.resolveRetry?.();
|
|
2139
|
+
}
|
|
2140
|
+
};
|
|
2130
2141
|
//#endregion
|
|
2131
2142
|
//#region lib/types/index.js
|
|
2132
2143
|
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
@@ -2198,6 +2209,7 @@ const Config = z.object({
|
|
|
2198
2209
|
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
2199
2210
|
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
2200
2211
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
2212
|
+
realtimeEnabled: z.boolean().default(true),
|
|
2201
2213
|
listenerEnabled: z.boolean().default(false),
|
|
2202
2214
|
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
2203
2215
|
listenerWorkspacePath: z.string(),
|
|
@@ -2215,7 +2227,6 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
|
2215
2227
|
"forbidden",
|
|
2216
2228
|
"identity-recovery-required",
|
|
2217
2229
|
"conflict",
|
|
2218
|
-
"state-in-use",
|
|
2219
2230
|
"rate-limited",
|
|
2220
2231
|
"group-membership-required",
|
|
2221
2232
|
"group-identity-stale",
|
|
@@ -2229,10 +2240,17 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
|
2229
2240
|
"network",
|
|
2230
2241
|
"remote"
|
|
2231
2242
|
]);
|
|
2232
|
-
const
|
|
2233
|
-
const
|
|
2234
|
-
const
|
|
2235
|
-
const
|
|
2243
|
+
const DEVICE_JOIN_APPROVAL_CONFIRMATION = "APPROVE";
|
|
2244
|
+
const DEVICE_REVOKE_CONFIRMATION = "REVOKE";
|
|
2245
|
+
const DEVICE_JOIN_CHALLENGE_TTL_SECONDS = 240;
|
|
2246
|
+
const RESUMABLE_JOIN_PHASES = /* @__PURE__ */ new Set([
|
|
2247
|
+
"pending",
|
|
2248
|
+
"challenge_prepared",
|
|
2249
|
+
"response_prepared",
|
|
2250
|
+
"response_verified",
|
|
2251
|
+
"approval_prepared",
|
|
2252
|
+
"authorized"
|
|
2253
|
+
]);
|
|
2236
2254
|
const FAILURE_MESSAGES = {
|
|
2237
2255
|
"not-registered": "No AWiki identity is registered for this deployment.",
|
|
2238
2256
|
"signed-out": "This installation is signed out of AWiki.",
|
|
@@ -2245,7 +2263,6 @@ const FAILURE_MESSAGES = {
|
|
|
2245
2263
|
"forbidden": "The AWiki operation is not permitted.",
|
|
2246
2264
|
"identity-recovery-required": "The local AWiki identity must be recovered before it can be used again.",
|
|
2247
2265
|
"conflict": "The AWiki operation conflicts with current state.",
|
|
2248
|
-
"state-in-use": "AWiki data is already open in another client.",
|
|
2249
2266
|
"rate-limited": "The AWiki service rate-limited the request.",
|
|
2250
2267
|
"group-membership-required": "The active AWiki identity is not a member of this group.",
|
|
2251
2268
|
"group-identity-stale": "The AWiki group identity binding is still recovering.",
|
|
@@ -2261,6 +2278,39 @@ const FAILURE_MESSAGES = {
|
|
|
2261
2278
|
};
|
|
2262
2279
|
var ProviderUnavailableError = class extends Error {};
|
|
2263
2280
|
var SummaryProviderUnavailableError = class extends Error {};
|
|
2281
|
+
function candidateJoinPhase(value) {
|
|
2282
|
+
if (value.remoteState === "rejected") return "rejected";
|
|
2283
|
+
if (value.remoteState === "cancelled" || value.localPhase === "cancelled") return "cancelled";
|
|
2284
|
+
if (value.remoteState === "expired" || value.localPhase === "expired") return "expired";
|
|
2285
|
+
if (value.localPhase === "authorized" && value.remoteState === "consumed" && value.completed && value.identity !== void 0) return "authorized";
|
|
2286
|
+
if (value.sas !== void 0) return value.remoteState === "response_verified" && /^\d{6}$/u.test(value.sas) ? "sas-ready" : void 0;
|
|
2287
|
+
if (value.remoteState === "challenge_sent" || value.remoteState === "response_verified" || value.remoteState === "consumed" || value.localPhase !== "pending") return "verifying";
|
|
2288
|
+
return value.remoteState === "pending" ? "pending" : void 0;
|
|
2289
|
+
}
|
|
2290
|
+
function adminJoinPhase(value) {
|
|
2291
|
+
if (value.remoteState === "rejected") return "rejected";
|
|
2292
|
+
if (value.remoteState === "cancelled" || value.localPhase === "cancelled") return "cancelled";
|
|
2293
|
+
if (value.remoteState === "expired" || value.localPhase === "expired") return "expired";
|
|
2294
|
+
if (value.localPhase === "authorized" && value.remoteState === "consumed") return "authorized";
|
|
2295
|
+
if (value.sas !== void 0) return value.remoteState === "response_verified" && /^\d{6}$/u.test(value.sas) ? "sas-ready" : void 0;
|
|
2296
|
+
if (value.remoteState === "challenge_sent" || value.remoteState === "response_verified" || value.remoteState === "consumed" || value.localPhase !== "pending") return "verifying";
|
|
2297
|
+
return value.remoteState === "pending" ? "pending" : void 0;
|
|
2298
|
+
}
|
|
2299
|
+
function requestJoinPhase(value) {
|
|
2300
|
+
switch (value.state) {
|
|
2301
|
+
case "pending": return "pending";
|
|
2302
|
+
case "challenge_sent":
|
|
2303
|
+
case "response_verified": return "verifying";
|
|
2304
|
+
case "consumed": return "authorized";
|
|
2305
|
+
case "cancelled": return "cancelled";
|
|
2306
|
+
case "rejected": return "rejected";
|
|
2307
|
+
case "expired": return "expired";
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
function constantTimeSasMatches(expected, actual) {
|
|
2311
|
+
if (!/^\d{6}$/u.test(expected) || !/^\d{6}$/u.test(actual)) return false;
|
|
2312
|
+
return timingSafeEqual(Buffer.from(expected, "ascii"), Buffer.from(actual, "ascii"));
|
|
2313
|
+
}
|
|
2264
2314
|
/** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
|
|
2265
2315
|
function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
2266
2316
|
let url;
|
|
@@ -2331,14 +2381,12 @@ function listenerAllowedPeers(raw, enabled) {
|
|
|
2331
2381
|
return peers;
|
|
2332
2382
|
}
|
|
2333
2383
|
/** Resolve and validate every deployment choice before publishing the service. */
|
|
2334
|
-
function resolveConfig(
|
|
2384
|
+
function resolveConfig(config) {
|
|
2335
2385
|
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
2336
2386
|
const configuredStateRoot = config.stateRoot?.trim();
|
|
2337
2387
|
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
2338
2388
|
const dshHome = configuredDshHome === void 0 || configuredDshHome.length === 0 ? join(homedir(), ".dsh") : configuredDshHome;
|
|
2339
|
-
const
|
|
2340
|
-
const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? resolveAwikiStateRoot(ctx, dshHome) : configuredStateRoot;
|
|
2341
|
-
const legacySharedStateDetected = profileName !== void 0 && existsSync(join(dshHome, "awiki", "im-core"));
|
|
2389
|
+
const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? join(dshHome, "awiki", "im-core") : configuredStateRoot;
|
|
2342
2390
|
if (!isAbsolute(stateRoot)) throw new TypeError("awiki: stateRoot must be an absolute path");
|
|
2343
2391
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? 10485760;
|
|
2344
2392
|
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) throw new TypeError("awiki: attachmentMaxBytes must be a positive safe integer");
|
|
@@ -2347,6 +2395,8 @@ function resolveConfig(ctx, config) {
|
|
|
2347
2395
|
const pollIntervalMs = config.pollIntervalMs ?? 3e3;
|
|
2348
2396
|
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1e3 || pollIntervalMs > 6e4) throw new TypeError("awiki: pollIntervalMs must be a safe integer from 1000 through 60000");
|
|
2349
2397
|
const listenerEnabled = config.listenerEnabled ?? false;
|
|
2398
|
+
const realtimeEnabled = config.realtimeEnabled ?? true;
|
|
2399
|
+
if (listenerEnabled && !realtimeEnabled) throw new TypeError("awiki: listenerEnabled requires realtimeEnabled");
|
|
2350
2400
|
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, "workspaces", "awiki");
|
|
2351
2401
|
if (!isAbsolute(listenerWorkspacePath)) throw new TypeError("awiki: listenerWorkspacePath must be an absolute path");
|
|
2352
2402
|
const allowedPeers = listenerAllowedPeers(config.listenerAllowedPeers, listenerEnabled);
|
|
@@ -2369,10 +2419,7 @@ function resolveConfig(ctx, config) {
|
|
|
2369
2419
|
attachmentMaxBytes,
|
|
2370
2420
|
imageAttachmentCacheMaxBytes,
|
|
2371
2421
|
pollIntervalMs,
|
|
2372
|
-
|
|
2373
|
-
profileName,
|
|
2374
|
-
legacySharedStateDetected
|
|
2375
|
-
},
|
|
2422
|
+
realtimeEnabled,
|
|
2376
2423
|
listenerEnabled,
|
|
2377
2424
|
listener: {
|
|
2378
2425
|
allowedPeers,
|
|
@@ -2523,6 +2570,7 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
2523
2570
|
};
|
|
2524
2571
|
}
|
|
2525
2572
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 65536;
|
|
2573
|
+
const SERVER_INFO_RESPONSE_MAX_BYTES = 65536;
|
|
2526
2574
|
const RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES = 4096;
|
|
2527
2575
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
2528
2576
|
async function readBoundedResponseText(response, maxBytes) {
|
|
@@ -2741,6 +2789,14 @@ let AwikiService = (() => {
|
|
|
2741
2789
|
let _inspectIdentityAccess_decorators;
|
|
2742
2790
|
let _sendRegistrationOtp_decorators;
|
|
2743
2791
|
let _registerIdentity_decorators;
|
|
2792
|
+
let _beginDeviceJoin_decorators;
|
|
2793
|
+
let _getDeviceJoinStatus_decorators;
|
|
2794
|
+
let _cancelDeviceJoin_decorators;
|
|
2795
|
+
let _refreshDeviceManagement_decorators;
|
|
2796
|
+
let _startDeviceJoinVerification_decorators;
|
|
2797
|
+
let _approveDeviceJoin_decorators;
|
|
2798
|
+
let _rejectDeviceJoin_decorators;
|
|
2799
|
+
let _revokeDevice_decorators;
|
|
2744
2800
|
let _updateDisplayName_decorators;
|
|
2745
2801
|
let _getProfile_decorators;
|
|
2746
2802
|
let _updateProfile_decorators;
|
|
@@ -2786,6 +2842,14 @@ let AwikiService = (() => {
|
|
|
2786
2842
|
_inspectIdentityAccess_decorators = [Remote];
|
|
2787
2843
|
_sendRegistrationOtp_decorators = [Remote];
|
|
2788
2844
|
_registerIdentity_decorators = [Remote];
|
|
2845
|
+
_beginDeviceJoin_decorators = [Remote];
|
|
2846
|
+
_getDeviceJoinStatus_decorators = [Remote];
|
|
2847
|
+
_cancelDeviceJoin_decorators = [Remote];
|
|
2848
|
+
_refreshDeviceManagement_decorators = [Remote];
|
|
2849
|
+
_startDeviceJoinVerification_decorators = [Remote];
|
|
2850
|
+
_approveDeviceJoin_decorators = [Remote];
|
|
2851
|
+
_rejectDeviceJoin_decorators = [Remote];
|
|
2852
|
+
_revokeDevice_decorators = [Remote];
|
|
2789
2853
|
_updateDisplayName_decorators = [Remote];
|
|
2790
2854
|
_getProfile_decorators = [Remote];
|
|
2791
2855
|
_updateProfile_decorators = [Remote];
|
|
@@ -2908,6 +2972,94 @@ let AwikiService = (() => {
|
|
|
2908
2972
|
},
|
|
2909
2973
|
metadata: _metadata
|
|
2910
2974
|
}, null, _instanceExtraInitializers);
|
|
2975
|
+
__esDecorate(this, null, _beginDeviceJoin_decorators, {
|
|
2976
|
+
kind: "method",
|
|
2977
|
+
name: "beginDeviceJoin",
|
|
2978
|
+
static: false,
|
|
2979
|
+
private: false,
|
|
2980
|
+
access: {
|
|
2981
|
+
has: (obj) => "beginDeviceJoin" in obj,
|
|
2982
|
+
get: (obj) => obj.beginDeviceJoin
|
|
2983
|
+
},
|
|
2984
|
+
metadata: _metadata
|
|
2985
|
+
}, null, _instanceExtraInitializers);
|
|
2986
|
+
__esDecorate(this, null, _getDeviceJoinStatus_decorators, {
|
|
2987
|
+
kind: "method",
|
|
2988
|
+
name: "getDeviceJoinStatus",
|
|
2989
|
+
static: false,
|
|
2990
|
+
private: false,
|
|
2991
|
+
access: {
|
|
2992
|
+
has: (obj) => "getDeviceJoinStatus" in obj,
|
|
2993
|
+
get: (obj) => obj.getDeviceJoinStatus
|
|
2994
|
+
},
|
|
2995
|
+
metadata: _metadata
|
|
2996
|
+
}, null, _instanceExtraInitializers);
|
|
2997
|
+
__esDecorate(this, null, _cancelDeviceJoin_decorators, {
|
|
2998
|
+
kind: "method",
|
|
2999
|
+
name: "cancelDeviceJoin",
|
|
3000
|
+
static: false,
|
|
3001
|
+
private: false,
|
|
3002
|
+
access: {
|
|
3003
|
+
has: (obj) => "cancelDeviceJoin" in obj,
|
|
3004
|
+
get: (obj) => obj.cancelDeviceJoin
|
|
3005
|
+
},
|
|
3006
|
+
metadata: _metadata
|
|
3007
|
+
}, null, _instanceExtraInitializers);
|
|
3008
|
+
__esDecorate(this, null, _refreshDeviceManagement_decorators, {
|
|
3009
|
+
kind: "method",
|
|
3010
|
+
name: "refreshDeviceManagement",
|
|
3011
|
+
static: false,
|
|
3012
|
+
private: false,
|
|
3013
|
+
access: {
|
|
3014
|
+
has: (obj) => "refreshDeviceManagement" in obj,
|
|
3015
|
+
get: (obj) => obj.refreshDeviceManagement
|
|
3016
|
+
},
|
|
3017
|
+
metadata: _metadata
|
|
3018
|
+
}, null, _instanceExtraInitializers);
|
|
3019
|
+
__esDecorate(this, null, _startDeviceJoinVerification_decorators, {
|
|
3020
|
+
kind: "method",
|
|
3021
|
+
name: "startDeviceJoinVerification",
|
|
3022
|
+
static: false,
|
|
3023
|
+
private: false,
|
|
3024
|
+
access: {
|
|
3025
|
+
has: (obj) => "startDeviceJoinVerification" in obj,
|
|
3026
|
+
get: (obj) => obj.startDeviceJoinVerification
|
|
3027
|
+
},
|
|
3028
|
+
metadata: _metadata
|
|
3029
|
+
}, null, _instanceExtraInitializers);
|
|
3030
|
+
__esDecorate(this, null, _approveDeviceJoin_decorators, {
|
|
3031
|
+
kind: "method",
|
|
3032
|
+
name: "approveDeviceJoin",
|
|
3033
|
+
static: false,
|
|
3034
|
+
private: false,
|
|
3035
|
+
access: {
|
|
3036
|
+
has: (obj) => "approveDeviceJoin" in obj,
|
|
3037
|
+
get: (obj) => obj.approveDeviceJoin
|
|
3038
|
+
},
|
|
3039
|
+
metadata: _metadata
|
|
3040
|
+
}, null, _instanceExtraInitializers);
|
|
3041
|
+
__esDecorate(this, null, _rejectDeviceJoin_decorators, {
|
|
3042
|
+
kind: "method",
|
|
3043
|
+
name: "rejectDeviceJoin",
|
|
3044
|
+
static: false,
|
|
3045
|
+
private: false,
|
|
3046
|
+
access: {
|
|
3047
|
+
has: (obj) => "rejectDeviceJoin" in obj,
|
|
3048
|
+
get: (obj) => obj.rejectDeviceJoin
|
|
3049
|
+
},
|
|
3050
|
+
metadata: _metadata
|
|
3051
|
+
}, null, _instanceExtraInitializers);
|
|
3052
|
+
__esDecorate(this, null, _revokeDevice_decorators, {
|
|
3053
|
+
kind: "method",
|
|
3054
|
+
name: "revokeDevice",
|
|
3055
|
+
static: false,
|
|
3056
|
+
private: false,
|
|
3057
|
+
access: {
|
|
3058
|
+
has: (obj) => "revokeDevice" in obj,
|
|
3059
|
+
get: (obj) => obj.revokeDevice
|
|
3060
|
+
},
|
|
3061
|
+
metadata: _metadata
|
|
3062
|
+
}, null, _instanceExtraInitializers);
|
|
2911
3063
|
__esDecorate(this, null, _updateDisplayName_decorators, {
|
|
2912
3064
|
kind: "method",
|
|
2913
3065
|
name: "updateDisplayName",
|
|
@@ -3303,6 +3455,12 @@ let AwikiService = (() => {
|
|
|
3303
3455
|
sessionMutation = Promise.resolve();
|
|
3304
3456
|
sessionRevision = 0;
|
|
3305
3457
|
activeIdentityDid;
|
|
3458
|
+
pendingDeviceJoin;
|
|
3459
|
+
activeDeviceJoinSessionId;
|
|
3460
|
+
requestRefs = /* @__PURE__ */ new Map();
|
|
3461
|
+
requestSessions = /* @__PURE__ */ new Map();
|
|
3462
|
+
deviceRefs = /* @__PURE__ */ new Map();
|
|
3463
|
+
deviceIds = /* @__PURE__ */ new Map();
|
|
3306
3464
|
activeSummaryRequests = /* @__PURE__ */ new Set();
|
|
3307
3465
|
summaryProvider;
|
|
3308
3466
|
recoveryReconciliationTarget;
|
|
@@ -3317,7 +3475,7 @@ let AwikiService = (() => {
|
|
|
3317
3475
|
constructor(ctx, config) {
|
|
3318
3476
|
super(ctx, "awiki");
|
|
3319
3477
|
this.hostContext = ctx;
|
|
3320
|
-
this.resolved = resolveConfig(
|
|
3478
|
+
this.resolved = resolveConfig(config);
|
|
3321
3479
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
3322
3480
|
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
3323
3481
|
this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
@@ -3346,12 +3504,12 @@ let AwikiService = (() => {
|
|
|
3346
3504
|
ctx.inject(["workspaceRegistry"], (workspaceCtx) => {
|
|
3347
3505
|
this.workspaceContext = workspaceCtx;
|
|
3348
3506
|
const provider = this.provider;
|
|
3349
|
-
if (provider !== void 0) this.
|
|
3507
|
+
if (provider !== void 0) this.ensureAgentConsumer(provider);
|
|
3350
3508
|
workspaceCtx.effect(() => async () => {
|
|
3351
3509
|
if (this.workspaceContext !== workspaceCtx) return;
|
|
3352
3510
|
this.workspaceContext = void 0;
|
|
3353
3511
|
const current = this.provider;
|
|
3354
|
-
if (current !== void 0) await this.
|
|
3512
|
+
if (current !== void 0) await this.stopAgentConsumer(current);
|
|
3355
3513
|
}, "awiki: release Workspace listener composition");
|
|
3356
3514
|
});
|
|
3357
3515
|
registerAwikiTools(ctx, this);
|
|
@@ -3372,6 +3530,8 @@ let AwikiService = (() => {
|
|
|
3372
3530
|
*/
|
|
3373
3531
|
registerClientFactory(factory) {
|
|
3374
3532
|
if (this.provider !== void 0) throw new Error("awiki: a client provider is already registered");
|
|
3533
|
+
this.pendingDeviceJoin = void 0;
|
|
3534
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
3375
3535
|
const provider = {
|
|
3376
3536
|
client: factory({
|
|
3377
3537
|
userServiceUrl: this.resolved.userServiceUrl,
|
|
@@ -3385,14 +3545,28 @@ let AwikiService = (() => {
|
|
|
3385
3545
|
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
3386
3546
|
stateRoot: this.resolved.stateRoot
|
|
3387
3547
|
}),
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3548
|
+
realtimeGeneration: 0,
|
|
3549
|
+
agentConsumerGeneration: 0,
|
|
3550
|
+
localDeviceJoinRequestCountAfterSync: 0
|
|
3391
3551
|
};
|
|
3392
3552
|
this.provider = provider;
|
|
3393
|
-
this.
|
|
3553
|
+
this.ensureRealtimeSupervisor(provider);
|
|
3554
|
+
this.ensureAgentConsumer(provider);
|
|
3394
3555
|
return () => this.disposeProvider(provider);
|
|
3395
3556
|
}
|
|
3557
|
+
/** Safe same-process diagnostics for focused E2E. Never exposed through Typert Remote. */
|
|
3558
|
+
getRealtimeDiagnostics() {
|
|
3559
|
+
const provider = this.provider;
|
|
3560
|
+
return {
|
|
3561
|
+
...provider?.realtimeSupervisor?.diagnostics() ?? {
|
|
3562
|
+
connected: false,
|
|
3563
|
+
activeSessionCount: 0,
|
|
3564
|
+
startCount: 0,
|
|
3565
|
+
stopCount: 0
|
|
3566
|
+
},
|
|
3567
|
+
localDeviceJoinRequestCountAfterSync: provider?.localDeviceJoinRequestCountAfterSync ?? 0
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3396
3570
|
/** Register the optional Model Proxy recovery target without exposing an arbitrary callback or token. */
|
|
3397
3571
|
registerRecoveryReconciliationTarget(target) {
|
|
3398
3572
|
if (this.recoveryReconciliationTarget !== void 0) throw new Error("awiki: a recovery reconciliation target is already registered");
|
|
@@ -3420,18 +3594,42 @@ let AwikiService = (() => {
|
|
|
3420
3594
|
* Read settings needed by the browser presentation.
|
|
3421
3595
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
3422
3596
|
*/
|
|
3423
|
-
getConfig() {
|
|
3424
|
-
|
|
3597
|
+
async getConfig() {
|
|
3598
|
+
let handleRecoveryPhoneEnabled = false;
|
|
3599
|
+
const abort = new AbortController();
|
|
3600
|
+
const timeout = setTimeout(() => {
|
|
3601
|
+
abort.abort();
|
|
3602
|
+
}, 1e4);
|
|
3603
|
+
try {
|
|
3604
|
+
const response = await fetch(new URL("/user-service/v1/server-info", this.resolved.userServiceUrl), {
|
|
3605
|
+
method: "GET",
|
|
3606
|
+
headers: {
|
|
3607
|
+
accept: "application/json",
|
|
3608
|
+
"cache-control": "no-store"
|
|
3609
|
+
},
|
|
3610
|
+
cache: "no-store",
|
|
3611
|
+
redirect: "error",
|
|
3612
|
+
signal: abort.signal
|
|
3613
|
+
});
|
|
3614
|
+
if (response.ok) {
|
|
3615
|
+
const text = await readBoundedResponseText(response, SERVER_INFO_RESPONSE_MAX_BYTES);
|
|
3616
|
+
const value = text === void 0 ? void 0 : JSON.parse(text);
|
|
3617
|
+
if (typeof value === "object" && value !== null && value.schema_version === 1) {
|
|
3618
|
+
const methods = value.identity?.handle_recovery?.methods;
|
|
3619
|
+
handleRecoveryPhoneEnabled = Array.isArray(methods) && methods.some((method) => typeof method === "object" && method !== null && method.id === "phone" && method.enabled === true && method.verification?.required === true && method.verification?.type === "sms_otp");
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3622
|
+
} catch {} finally {
|
|
3623
|
+
clearTimeout(timeout);
|
|
3624
|
+
}
|
|
3625
|
+
return {
|
|
3425
3626
|
ok: true,
|
|
3426
3627
|
value: {
|
|
3427
3628
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
3428
3629
|
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
3429
|
-
|
|
3430
|
-
profileName: this.resolved.profileName,
|
|
3431
|
-
legacySharedStateDetected: this.resolved.legacySharedStateDetected
|
|
3432
|
-
}
|
|
3630
|
+
handleRecoveryPhoneEnabled
|
|
3433
3631
|
}
|
|
3434
|
-
}
|
|
3632
|
+
};
|
|
3435
3633
|
}
|
|
3436
3634
|
/**
|
|
3437
3635
|
* Read the deployment's identity status.
|
|
@@ -3454,6 +3652,8 @@ let AwikiService = (() => {
|
|
|
3454
3652
|
const identity = await this.run((client) => client.getIdentity(), { allowSignedOut: true });
|
|
3455
3653
|
if (!identity.ok) return identity;
|
|
3456
3654
|
this.activeIdentityDid = identity.value?.did;
|
|
3655
|
+
const provider = this.provider;
|
|
3656
|
+
if (identity.value !== null && provider !== void 0) this.ensureProviderRuntime(provider);
|
|
3457
3657
|
return identity.value === null ? {
|
|
3458
3658
|
ok: true,
|
|
3459
3659
|
value: { status: "unregistered" }
|
|
@@ -3490,7 +3690,7 @@ let AwikiService = (() => {
|
|
|
3490
3690
|
const session = { status: "signed-out" };
|
|
3491
3691
|
this.publishSession(session);
|
|
3492
3692
|
const provider = this.provider;
|
|
3493
|
-
if (provider !== void 0) await this.
|
|
3693
|
+
if (provider !== void 0) await this.stopProviderRuntime(provider);
|
|
3494
3694
|
return {
|
|
3495
3695
|
ok: true,
|
|
3496
3696
|
value: session
|
|
@@ -3523,7 +3723,7 @@ let AwikiService = (() => {
|
|
|
3523
3723
|
};
|
|
3524
3724
|
this.publishSession(session);
|
|
3525
3725
|
const provider = this.provider;
|
|
3526
|
-
if (provider !== void 0) this.
|
|
3726
|
+
if (provider !== void 0) this.ensureProviderRuntime(provider);
|
|
3527
3727
|
return {
|
|
3528
3728
|
ok: true,
|
|
3529
3729
|
value: session
|
|
@@ -3609,8 +3809,15 @@ let AwikiService = (() => {
|
|
|
3609
3809
|
};
|
|
3610
3810
|
}
|
|
3611
3811
|
}
|
|
3612
|
-
sendRegistrationOtp(request) {
|
|
3613
|
-
|
|
3812
|
+
async sendRegistrationOtp(request) {
|
|
3813
|
+
this.pendingDeviceJoin = void 0;
|
|
3814
|
+
return this.run(async (client) => {
|
|
3815
|
+
if (await this.selectDeviceJoinSession(client) !== null) throw Object.assign(/* @__PURE__ */ new Error("join already exists"), {
|
|
3816
|
+
name: "AwikiSdkError",
|
|
3817
|
+
code: "conflict"
|
|
3818
|
+
});
|
|
3819
|
+
return client.sendRegistrationOtp(request);
|
|
3820
|
+
});
|
|
3614
3821
|
}
|
|
3615
3822
|
/**
|
|
3616
3823
|
* Register and persist the deployment's only AWiki identity.
|
|
@@ -3618,9 +3825,190 @@ let AwikiService = (() => {
|
|
|
3618
3825
|
* @returns The new public identity or a closed failure.
|
|
3619
3826
|
*/
|
|
3620
3827
|
async registerIdentity(request) {
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3828
|
+
this.pendingDeviceJoin = void 0;
|
|
3829
|
+
const result = await this.run(async (client) => {
|
|
3830
|
+
if (await this.selectDeviceJoinSession(client) !== null) throw Object.assign(/* @__PURE__ */ new Error("join already exists"), {
|
|
3831
|
+
name: "AwikiSdkError",
|
|
3832
|
+
code: "conflict"
|
|
3833
|
+
});
|
|
3834
|
+
return client.registerIdentity(request);
|
|
3835
|
+
});
|
|
3836
|
+
if (!result.ok) return result;
|
|
3837
|
+
if (result.value.status === "registered") {
|
|
3838
|
+
await this.activateRegisteredIdentity(result.value.identity);
|
|
3839
|
+
return {
|
|
3840
|
+
ok: true,
|
|
3841
|
+
value: {
|
|
3842
|
+
status: "registered",
|
|
3843
|
+
identity: result.value.identity
|
|
3844
|
+
}
|
|
3845
|
+
};
|
|
3846
|
+
}
|
|
3847
|
+
this.pendingDeviceJoin = {
|
|
3848
|
+
continuationId: result.value.continuationId,
|
|
3849
|
+
fullHandle: result.value.fullHandle,
|
|
3850
|
+
mode: result.value.mode,
|
|
3851
|
+
requiresUserPresence: result.value.requiresUserPresence
|
|
3852
|
+
};
|
|
3853
|
+
return {
|
|
3854
|
+
ok: true,
|
|
3855
|
+
value: {
|
|
3856
|
+
status: "join-required",
|
|
3857
|
+
fullHandle: result.value.fullHandle,
|
|
3858
|
+
mode: result.value.mode,
|
|
3859
|
+
requiresUserPresence: result.value.requiresUserPresence
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
/** Consume the exact in-memory continuation; ordinary Join never claims rebind user presence. */
|
|
3864
|
+
async beginDeviceJoin() {
|
|
3865
|
+
const continuation = this.pendingDeviceJoin;
|
|
3866
|
+
if (continuation === void 0) return {
|
|
3867
|
+
ok: false,
|
|
3868
|
+
error: failure("conflict")
|
|
3869
|
+
};
|
|
3870
|
+
if (continuation.mode !== "ordinary" || continuation.requiresUserPresence) return {
|
|
3871
|
+
ok: false,
|
|
3872
|
+
error: failure("forbidden")
|
|
3873
|
+
};
|
|
3874
|
+
this.pendingDeviceJoin = void 0;
|
|
3875
|
+
const result = await this.run((client) => client.beginDeviceJoin({
|
|
3876
|
+
continuationId: continuation.continuationId,
|
|
3877
|
+
operationId: `dsh-device-join-${randomUUID()}`,
|
|
3878
|
+
userPresenceConfirmed: false
|
|
3879
|
+
}));
|
|
3880
|
+
if (!result.ok) return result;
|
|
3881
|
+
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
3882
|
+
return this.applyCandidateJoinProgress(result.value);
|
|
3883
|
+
}
|
|
3884
|
+
/** Restore from Core local_sessions and advance only the exact resumable Join. */
|
|
3885
|
+
async getDeviceJoinStatus() {
|
|
3886
|
+
const result = await this.run(async (client) => {
|
|
3887
|
+
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
3888
|
+
return joinSessionId === null ? null : client.getDeviceJoinStatus(joinSessionId);
|
|
3889
|
+
});
|
|
3890
|
+
if (!result.ok) return result;
|
|
3891
|
+
if (result.value === null) return {
|
|
3892
|
+
ok: true,
|
|
3893
|
+
value: null
|
|
3894
|
+
};
|
|
3895
|
+
this.activeDeviceJoinSessionId = result.value.joinSessionId;
|
|
3896
|
+
return this.applyCandidateJoinProgress(result.value);
|
|
3897
|
+
}
|
|
3898
|
+
async cancelDeviceJoin() {
|
|
3899
|
+
const result = await this.run(async (client) => {
|
|
3900
|
+
const joinSessionId = await this.selectDeviceJoinSession(client);
|
|
3901
|
+
if (joinSessionId === null) return null;
|
|
3902
|
+
return client.cancelDeviceJoin(joinSessionId);
|
|
3903
|
+
});
|
|
3904
|
+
if (!result.ok) return result;
|
|
3905
|
+
this.pendingDeviceJoin = void 0;
|
|
3906
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
3907
|
+
return {
|
|
3908
|
+
ok: true,
|
|
3909
|
+
value: { completed: true }
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3912
|
+
/** Reliable-sync and project only Host-opaque device/request references. */
|
|
3913
|
+
refreshDeviceManagement() {
|
|
3914
|
+
return this.run((client) => this.deviceManagementSnapshot(client));
|
|
3915
|
+
}
|
|
3916
|
+
async startDeviceJoinVerification(request) {
|
|
3917
|
+
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
3918
|
+
if (joinSessionId === void 0) return {
|
|
3919
|
+
ok: false,
|
|
3920
|
+
error: failure("invalid-request")
|
|
3921
|
+
};
|
|
3922
|
+
const result = await this.run(async (client) => {
|
|
3923
|
+
await this.requireDeviceManager(client);
|
|
3924
|
+
await client.syncDeviceManagement();
|
|
3925
|
+
let notice = (await client.listLocalDeviceJoinRequests()).find((value) => value.joinSessionId === joinSessionId);
|
|
3926
|
+
if (notice === void 0) throw Object.assign(/* @__PURE__ */ new Error("request not found"), {
|
|
3927
|
+
name: "AwikiSdkError",
|
|
3928
|
+
code: "not-found"
|
|
3929
|
+
});
|
|
3930
|
+
const localProgress = await this.localAdminJoinProgress(client, notice);
|
|
3931
|
+
if (localProgress !== void 0) return localProgress;
|
|
3932
|
+
if (!notice.canStartVerification) throw Object.assign(/* @__PURE__ */ new Error("request unavailable"), {
|
|
3933
|
+
name: "AwikiSdkError",
|
|
3934
|
+
code: "forbidden"
|
|
3935
|
+
});
|
|
3936
|
+
const operationId = `dsh-device-verify-${createHash("sha256").update(joinSessionId).digest("hex").slice(0, 32)}`;
|
|
3937
|
+
let started;
|
|
3938
|
+
try {
|
|
3939
|
+
started = await client.startDeviceJoinVerification({
|
|
3940
|
+
joinSessionId,
|
|
3941
|
+
operationId,
|
|
3942
|
+
challengeTtlSeconds: DEVICE_JOIN_CHALLENGE_TTL_SECONDS
|
|
3943
|
+
});
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
await client.syncDeviceManagement();
|
|
3946
|
+
notice = (await client.listLocalDeviceJoinRequests()).find((value) => value.joinSessionId === joinSessionId);
|
|
3947
|
+
const recovered = notice === void 0 ? void 0 : await this.localAdminJoinProgress(client, notice);
|
|
3948
|
+
if (recovered !== void 0) return recovered;
|
|
3949
|
+
throw error;
|
|
3950
|
+
}
|
|
3951
|
+
await client.syncDeviceManagement();
|
|
3952
|
+
notice = (await client.listLocalDeviceJoinRequests()).find((value) => value.joinSessionId === joinSessionId);
|
|
3953
|
+
if (notice === void 0) return started;
|
|
3954
|
+
return await this.localAdminJoinProgress(client, notice) ?? started;
|
|
3955
|
+
});
|
|
3956
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
3957
|
+
}
|
|
3958
|
+
async approveDeviceJoin(request) {
|
|
3959
|
+
if (request?.confirmation !== DEVICE_JOIN_APPROVAL_CONFIRMATION) return {
|
|
3960
|
+
ok: false,
|
|
3961
|
+
error: failure("invalid-request")
|
|
3962
|
+
};
|
|
3963
|
+
const joinSessionId = this.requestSessions.get(request.requestRef);
|
|
3964
|
+
if (joinSessionId === void 0 || !/^\d{6}$/u.test(request.enteredSas)) return {
|
|
3965
|
+
ok: false,
|
|
3966
|
+
error: failure("invalid-request")
|
|
3967
|
+
};
|
|
3968
|
+
const result = await this.run(async (client) => {
|
|
3969
|
+
await this.requireDeviceManager(client);
|
|
3970
|
+
const progress = await client.getLocalDeviceJoinVerificationProgress(joinSessionId);
|
|
3971
|
+
if (progress.sas === void 0 || !constantTimeSasMatches(progress.sas, request.enteredSas)) throw Object.assign(/* @__PURE__ */ new Error("sas mismatch"), {
|
|
3972
|
+
name: "AwikiSdkError",
|
|
3973
|
+
code: "forbidden"
|
|
3974
|
+
});
|
|
3975
|
+
const prompt = await client.prepareDeviceJoinApproval(joinSessionId);
|
|
3976
|
+
return client.confirmDeviceJoinApproval(prompt.approvalHandle);
|
|
3977
|
+
});
|
|
3978
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
3979
|
+
}
|
|
3980
|
+
async rejectDeviceJoin(request) {
|
|
3981
|
+
const joinSessionId = this.requestSessions.get(request?.requestRef);
|
|
3982
|
+
if (joinSessionId === void 0 || request.reason !== "user_rejected" && request.reason !== "sas_mismatch") return {
|
|
3983
|
+
ok: false,
|
|
3984
|
+
error: failure("invalid-request")
|
|
3985
|
+
};
|
|
3986
|
+
const result = await this.run(async (client) => {
|
|
3987
|
+
await this.requireDeviceManager(client);
|
|
3988
|
+
return client.rejectDeviceJoin(joinSessionId, request.reason);
|
|
3989
|
+
});
|
|
3990
|
+
return result.ok ? this.publicAdminJoinProgress(request.requestRef, result.value) : result;
|
|
3991
|
+
}
|
|
3992
|
+
async revokeDevice(request) {
|
|
3993
|
+
if (request?.confirmation !== DEVICE_REVOKE_CONFIRMATION) return {
|
|
3994
|
+
ok: false,
|
|
3995
|
+
error: failure("invalid-request")
|
|
3996
|
+
};
|
|
3997
|
+
const deviceId = this.deviceIds.get(request.deviceRef);
|
|
3998
|
+
if (deviceId === void 0) return {
|
|
3999
|
+
ok: false,
|
|
4000
|
+
error: failure("invalid-request")
|
|
4001
|
+
};
|
|
4002
|
+
return this.run(async (client) => {
|
|
4003
|
+
await this.requireDeviceManager(client);
|
|
4004
|
+
const target = (await client.getDeviceRegistry()).find((device) => device.deviceId === deviceId);
|
|
4005
|
+
if (target === void 0 || target.isCurrent) throw Object.assign(/* @__PURE__ */ new Error("device unavailable"), {
|
|
4006
|
+
name: "AwikiSdkError",
|
|
4007
|
+
code: "forbidden"
|
|
4008
|
+
});
|
|
4009
|
+
await client.revokeDevice(deviceId);
|
|
4010
|
+
return this.deviceManagementSnapshot(client);
|
|
4011
|
+
});
|
|
3624
4012
|
}
|
|
3625
4013
|
/**
|
|
3626
4014
|
* Update the deployment identity's public WNS display name.
|
|
@@ -3647,12 +4035,19 @@ let AwikiService = (() => {
|
|
|
3647
4035
|
}
|
|
3648
4036
|
/** Start durable phone recovery for one existing full Handle. */
|
|
3649
4037
|
sendRecoveryOtp(request) {
|
|
4038
|
+
this.pendingDeviceJoin = void 0;
|
|
3650
4039
|
const normalized = normalizeRecoveryOtpRequest(request);
|
|
3651
4040
|
if (normalized === void 0) return Promise.resolve({
|
|
3652
4041
|
ok: false,
|
|
3653
4042
|
error: failure("invalid-request")
|
|
3654
4043
|
});
|
|
3655
|
-
return this.run((client) =>
|
|
4044
|
+
return this.run(async (client) => {
|
|
4045
|
+
if (await this.selectDeviceJoinSession(client) !== null) throw Object.assign(/* @__PURE__ */ new Error("join already exists"), {
|
|
4046
|
+
name: "AwikiSdkError",
|
|
4047
|
+
code: "conflict"
|
|
4048
|
+
});
|
|
4049
|
+
return client.sendRecoveryOtp(normalized);
|
|
4050
|
+
}, { allowSignedOut: true });
|
|
3656
4051
|
}
|
|
3657
4052
|
/** Verify a recovery OTP and freeze its exact intent before the remote commit. */
|
|
3658
4053
|
prepareRecovery(request) {
|
|
@@ -4164,10 +4559,10 @@ let AwikiService = (() => {
|
|
|
4164
4559
|
});
|
|
4165
4560
|
return this.mutateSession(async () => {
|
|
4166
4561
|
const provider = this.provider;
|
|
4167
|
-
if (provider !== void 0) await this.
|
|
4562
|
+
if (provider !== void 0) await this.stopProviderRuntime(provider);
|
|
4168
4563
|
const result = await this.run((client) => client.clearLocalData(), { allowSignedOut: true });
|
|
4169
4564
|
if (!result.ok) {
|
|
4170
|
-
if (provider !== void 0 && this.provider === provider) this.
|
|
4565
|
+
if (provider !== void 0 && this.provider === provider) this.ensureProviderRuntime(provider);
|
|
4171
4566
|
return result;
|
|
4172
4567
|
}
|
|
4173
4568
|
try {
|
|
@@ -4177,9 +4572,10 @@ let AwikiService = (() => {
|
|
|
4177
4572
|
await this.sessionStore.signIn();
|
|
4178
4573
|
this.signedOut = false;
|
|
4179
4574
|
this.activeIdentityDid = void 0;
|
|
4575
|
+
this.pendingDeviceJoin = void 0;
|
|
4576
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
4180
4577
|
this.invalidateSummaries();
|
|
4181
4578
|
this.publishSession({ status: "unregistered" });
|
|
4182
|
-
if (provider !== void 0 && this.provider === provider) this.startListener(provider);
|
|
4183
4579
|
return result;
|
|
4184
4580
|
} catch {
|
|
4185
4581
|
return {
|
|
@@ -4213,8 +4609,7 @@ let AwikiService = (() => {
|
|
|
4213
4609
|
identity
|
|
4214
4610
|
};
|
|
4215
4611
|
this.publishSession(session);
|
|
4216
|
-
|
|
4217
|
-
await this.startListener(provider);
|
|
4612
|
+
this.ensureProviderRuntime(provider);
|
|
4218
4613
|
}
|
|
4219
4614
|
return reconciled;
|
|
4220
4615
|
} catch {
|
|
@@ -4250,22 +4645,159 @@ let AwikiService = (() => {
|
|
|
4250
4645
|
return false;
|
|
4251
4646
|
}
|
|
4252
4647
|
}
|
|
4253
|
-
/**
|
|
4648
|
+
/** Select the only resumable new-device session; Core local_sessions is the sole restart SoT. */
|
|
4649
|
+
async selectDeviceJoinSession(client) {
|
|
4650
|
+
const sessions = await client.listLocalDeviceJoinSessions();
|
|
4651
|
+
if (this.activeDeviceJoinSessionId !== void 0) {
|
|
4652
|
+
const tracked = sessions.find((value) => value.side === "new_device" && value.joinSessionId === this.activeDeviceJoinSessionId);
|
|
4653
|
+
if (tracked !== void 0) return tracked.joinSessionId;
|
|
4654
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
4655
|
+
}
|
|
4656
|
+
const resumable = sessions.filter((value) => value.side === "new_device" && RESUMABLE_JOIN_PHASES.has(value.localPhase));
|
|
4657
|
+
if (resumable.length > 1) throw Object.assign(/* @__PURE__ */ new Error("multiple local joins"), {
|
|
4658
|
+
name: "AwikiSdkError",
|
|
4659
|
+
code: "conflict"
|
|
4660
|
+
});
|
|
4661
|
+
const selected = resumable[0];
|
|
4662
|
+
this.activeDeviceJoinSessionId = selected?.joinSessionId;
|
|
4663
|
+
return selected?.joinSessionId ?? null;
|
|
4664
|
+
}
|
|
4665
|
+
async applyCandidateJoinProgress(value) {
|
|
4666
|
+
const phase = candidateJoinPhase(value);
|
|
4667
|
+
if (phase === void 0) return {
|
|
4668
|
+
ok: false,
|
|
4669
|
+
error: failure("remote")
|
|
4670
|
+
};
|
|
4671
|
+
if (phase === "cancelled" || phase === "rejected" || phase === "expired") this.activeDeviceJoinSessionId = void 0;
|
|
4672
|
+
if (phase === "authorized") {
|
|
4673
|
+
if (value.identity === void 0) return {
|
|
4674
|
+
ok: false,
|
|
4675
|
+
error: failure("remote")
|
|
4676
|
+
};
|
|
4677
|
+
await this.activateRegisteredIdentity(value.identity);
|
|
4678
|
+
}
|
|
4679
|
+
return {
|
|
4680
|
+
ok: true,
|
|
4681
|
+
value: {
|
|
4682
|
+
phase,
|
|
4683
|
+
expiresAt: value.expiresAt,
|
|
4684
|
+
...phase === "sas-ready" && value.sas !== void 0 ? { sas: value.sas } : {},
|
|
4685
|
+
completed: phase === "authorized"
|
|
4686
|
+
}
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
publicAdminJoinProgress(requestRef, value) {
|
|
4690
|
+
const phase = adminJoinPhase(value);
|
|
4691
|
+
if (phase === void 0) return {
|
|
4692
|
+
ok: false,
|
|
4693
|
+
error: failure("remote")
|
|
4694
|
+
};
|
|
4695
|
+
return {
|
|
4696
|
+
ok: true,
|
|
4697
|
+
value: {
|
|
4698
|
+
requestRef,
|
|
4699
|
+
phase,
|
|
4700
|
+
expiresAt: value.expiresAt,
|
|
4701
|
+
...phase === "sas-ready" && value.sas !== void 0 ? { sas: value.sas } : {}
|
|
4702
|
+
}
|
|
4703
|
+
};
|
|
4704
|
+
}
|
|
4705
|
+
async localAdminJoinProgress(client, notice) {
|
|
4706
|
+
const local = (await client.listLocalDeviceJoinSessions()).find((session) => session.side === "admin" && session.joinSessionId === notice.joinSessionId);
|
|
4707
|
+
if (local === void 0) return notice.claimedByCurrentDevice ? client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId) : void 0;
|
|
4708
|
+
if (local.localPhase === "challenge_prepared") return {
|
|
4709
|
+
joinSessionId: notice.joinSessionId,
|
|
4710
|
+
localPhase: local.localPhase,
|
|
4711
|
+
remoteState: notice.state,
|
|
4712
|
+
expiresAt: local.expiresAt
|
|
4713
|
+
};
|
|
4714
|
+
return client.getLocalDeviceJoinVerificationProgress(notice.joinSessionId);
|
|
4715
|
+
}
|
|
4716
|
+
async requireDeviceManager(client) {
|
|
4717
|
+
const current = await client.getCurrentDeviceSummary();
|
|
4718
|
+
if (!current.canManage || current.role !== "admin" || current.readiness !== "admin_ready") throw Object.assign(/* @__PURE__ */ new Error("device manager required"), {
|
|
4719
|
+
name: "AwikiSdkError",
|
|
4720
|
+
code: "forbidden"
|
|
4721
|
+
});
|
|
4722
|
+
}
|
|
4723
|
+
requestRef(joinSessionId) {
|
|
4724
|
+
const existing = this.requestRefs.get(joinSessionId);
|
|
4725
|
+
if (existing !== void 0) return existing;
|
|
4726
|
+
const reference = `join-${randomUUID()}`;
|
|
4727
|
+
this.requestRefs.set(joinSessionId, reference);
|
|
4728
|
+
this.requestSessions.set(reference, joinSessionId);
|
|
4729
|
+
return reference;
|
|
4730
|
+
}
|
|
4731
|
+
deviceRef(deviceId) {
|
|
4732
|
+
const existing = this.deviceRefs.get(deviceId);
|
|
4733
|
+
if (existing !== void 0) return existing;
|
|
4734
|
+
const reference = `device-${randomUUID()}`;
|
|
4735
|
+
this.deviceRefs.set(deviceId, reference);
|
|
4736
|
+
this.deviceIds.set(reference, deviceId);
|
|
4737
|
+
return reference;
|
|
4738
|
+
}
|
|
4739
|
+
async deviceManagementSnapshot(client) {
|
|
4740
|
+
await client.syncDeviceManagement();
|
|
4741
|
+
const current = await client.getCurrentDeviceSummary();
|
|
4742
|
+
if (!current.canManage || current.role !== "admin" || current.readiness !== "admin_ready") {
|
|
4743
|
+
this.requestRefs.clear();
|
|
4744
|
+
this.requestSessions.clear();
|
|
4745
|
+
this.deviceRefs.clear();
|
|
4746
|
+
this.deviceIds.clear();
|
|
4747
|
+
return {
|
|
4748
|
+
canManage: false,
|
|
4749
|
+
...current.role === void 0 ? {} : { role: current.role },
|
|
4750
|
+
readiness: current.readiness,
|
|
4751
|
+
devices: [],
|
|
4752
|
+
requests: []
|
|
4753
|
+
};
|
|
4754
|
+
}
|
|
4755
|
+
const requests = await client.listLocalDeviceJoinRequests();
|
|
4756
|
+
await Promise.all(requests.filter((request) => request.claimedByCurrentDevice).map((request) => client.getLocalDeviceJoinVerificationProgress(request.joinSessionId).catch(() => void 0)));
|
|
4757
|
+
return {
|
|
4758
|
+
canManage: true,
|
|
4759
|
+
role: "admin",
|
|
4760
|
+
readiness: "admin_ready",
|
|
4761
|
+
devices: (await client.getDeviceRegistry()).map((device) => this.publicDevice(device)),
|
|
4762
|
+
requests: requests.map((request) => ({
|
|
4763
|
+
requestRef: this.requestRef(request.joinSessionId),
|
|
4764
|
+
candidateKeyFingerprint: request.candidateKeyFingerprint,
|
|
4765
|
+
issuedAt: request.issuedAt,
|
|
4766
|
+
expiresAt: request.expiresAt,
|
|
4767
|
+
state: requestJoinPhase(request),
|
|
4768
|
+
claimedByCurrentDevice: request.claimedByCurrentDevice,
|
|
4769
|
+
canStartVerification: request.canStartVerification
|
|
4770
|
+
}))
|
|
4771
|
+
};
|
|
4772
|
+
}
|
|
4773
|
+
publicDevice(device) {
|
|
4774
|
+
return {
|
|
4775
|
+
deviceRef: this.deviceRef(device.deviceId),
|
|
4776
|
+
status: device.status,
|
|
4777
|
+
role: device.role,
|
|
4778
|
+
managementReady: device.managementReady,
|
|
4779
|
+
isCurrent: device.isCurrent
|
|
4780
|
+
};
|
|
4781
|
+
}
|
|
4782
|
+
/** Publish one newly registered identity, then start realtime only in the background. */
|
|
4254
4783
|
async activateRegisteredIdentity(identity) {
|
|
4784
|
+
this.pendingDeviceJoin = void 0;
|
|
4785
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
4255
4786
|
this.activeIdentityDid = identity.did;
|
|
4256
4787
|
this.publishSession({
|
|
4257
4788
|
status: "active",
|
|
4258
4789
|
identity
|
|
4259
4790
|
});
|
|
4260
4791
|
const provider = this.provider;
|
|
4261
|
-
if (provider !== void 0)
|
|
4262
|
-
await provider.listenerStartup;
|
|
4263
|
-
await this.startListener(provider);
|
|
4264
|
-
}
|
|
4792
|
+
if (provider !== void 0) this.ensureProviderRuntime(provider);
|
|
4265
4793
|
}
|
|
4266
4794
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
4267
4795
|
invalidateSummaries() {
|
|
4268
4796
|
this.sessionRevision += 1;
|
|
4797
|
+
this.requestRefs.clear();
|
|
4798
|
+
this.requestSessions.clear();
|
|
4799
|
+
this.deviceRefs.clear();
|
|
4800
|
+
this.deviceIds.clear();
|
|
4269
4801
|
for (const controller of this.activeSummaryRequests) controller.abort();
|
|
4270
4802
|
this.activeSummaryRequests.clear();
|
|
4271
4803
|
}
|
|
@@ -4365,120 +4897,148 @@ let AwikiService = (() => {
|
|
|
4365
4897
|
this.sessionMutation = pending.then(() => void 0, () => void 0);
|
|
4366
4898
|
return pending;
|
|
4367
4899
|
}
|
|
4368
|
-
/** Start
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
if (provider.
|
|
4372
|
-
if (
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4900
|
+
/** Start identity realtime and the optional Agent consumer without blocking identity success. */
|
|
4901
|
+
ensureProviderRuntime(provider) {
|
|
4902
|
+
const identityDid = this.activeIdentityDid;
|
|
4903
|
+
if (provider.runtimeReplacement !== void 0) return;
|
|
4904
|
+
if (identityDid !== void 0 && provider.realtimeIdentityDid !== void 0 && provider.realtimeIdentityDid !== identityDid) {
|
|
4905
|
+
this.replaceProviderRuntime(provider, identityDid);
|
|
4906
|
+
return;
|
|
4907
|
+
}
|
|
4908
|
+
this.ensureRealtimeSupervisor(provider);
|
|
4909
|
+
this.ensureAgentConsumer(provider);
|
|
4910
|
+
}
|
|
4911
|
+
replaceProviderRuntime(provider, identityDid) {
|
|
4912
|
+
if (provider.runtimeReplacement !== void 0 || this.provider !== provider) return;
|
|
4913
|
+
const observed = (async () => {
|
|
4914
|
+
await this.stopProviderRuntime(provider);
|
|
4915
|
+
if (this.provider !== provider || this.activeIdentityDid !== identityDid) return;
|
|
4916
|
+
this.ensureRealtimeSupervisor(provider);
|
|
4917
|
+
this.ensureAgentConsumer(provider);
|
|
4918
|
+
})().catch((error) => {
|
|
4919
|
+
this.ctx.logger("awiki-realtime").warn("AWiki identity realtime replacement failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
4920
|
+
}).finally(() => {
|
|
4921
|
+
if (provider.runtimeReplacement === observed) delete provider.runtimeReplacement;
|
|
4922
|
+
if (this.provider === provider) this.ensureProviderRuntime(provider);
|
|
4923
|
+
});
|
|
4924
|
+
provider.runtimeReplacement = observed;
|
|
4925
|
+
}
|
|
4926
|
+
ensureRealtimeSupervisor(provider) {
|
|
4927
|
+
if (!this.resolved.realtimeEnabled || provider.realtimeSupervisor !== void 0 || provider.realtimeStartup !== void 0 || this.provider !== provider || (provider.client.realtime ?? provider.client.listener) === void 0) return;
|
|
4928
|
+
const generation = provider.realtimeGeneration;
|
|
4929
|
+
const source = provider.client.realtime ?? provider.client.listener;
|
|
4930
|
+
if (source === void 0) return;
|
|
4931
|
+
const logger = this.ctx.logger("awiki-realtime");
|
|
4378
4932
|
const observed = (async () => {
|
|
4379
4933
|
if (await this.isSignedOut()) return;
|
|
4380
|
-
|
|
4381
|
-
if (!this.
|
|
4382
|
-
|
|
4383
|
-
const
|
|
4384
|
-
provider.
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
if (provider.listener === listener) delete provider.listener;
|
|
4389
|
-
try {
|
|
4390
|
-
await listener.dispose();
|
|
4391
|
-
} catch {
|
|
4392
|
-
provider.listenerRecoveryBlocked = true;
|
|
4393
|
-
}
|
|
4394
|
-
if (!provider.listenerRecoveryBlocked && provider.listenerRestartAttempt > 0) this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
4395
|
-
throw error;
|
|
4396
|
-
}
|
|
4397
|
-
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) {
|
|
4398
|
-
if (provider.listener === listener) delete provider.listener;
|
|
4399
|
-
try {
|
|
4400
|
-
await listener.dispose();
|
|
4401
|
-
} catch (error) {
|
|
4402
|
-
provider.listenerRecoveryBlocked = true;
|
|
4403
|
-
throw error;
|
|
4404
|
-
}
|
|
4405
|
-
return;
|
|
4406
|
-
}
|
|
4407
|
-
provider.listenerStartedAt = Date.now();
|
|
4408
|
-
listener.whenTerminated().then((result) => {
|
|
4409
|
-
if (result.kind === "failed") this.releaseFailedListener(provider, listener, workspaceContext, generation, result.error);
|
|
4410
|
-
});
|
|
4934
|
+
const identity = await provider.client.getIdentity();
|
|
4935
|
+
if (identity === null || !this.realtimeFenceMatches(provider, generation)) return;
|
|
4936
|
+
this.activeIdentityDid = identity.did;
|
|
4937
|
+
const supervisor = new IdentityRealtimeSupervisor(source, { onSynchronized: (cause) => this.onRealtimeSynchronized(provider, generation, cause) }, logger);
|
|
4938
|
+
provider.realtimeSupervisor = supervisor;
|
|
4939
|
+
provider.realtimeIdentityDid = identity.did;
|
|
4940
|
+
supervisor.start();
|
|
4941
|
+
this.ensureAgentConsumer(provider);
|
|
4411
4942
|
})().catch((error) => {
|
|
4412
|
-
logger.warn("AWiki
|
|
4943
|
+
logger.warn("AWiki realtime startup failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
4413
4944
|
}).finally(() => {
|
|
4414
|
-
if (provider.
|
|
4945
|
+
if (provider.realtimeStartup === observed) delete provider.realtimeStartup;
|
|
4415
4946
|
});
|
|
4416
|
-
provider.
|
|
4417
|
-
return observed;
|
|
4418
|
-
}
|
|
4419
|
-
async stopListener(provider) {
|
|
4420
|
-
provider.listenerGeneration += 1;
|
|
4421
|
-
provider.listenerRestartAttempt = 0;
|
|
4422
|
-
delete provider.listenerStartedAt;
|
|
4423
|
-
const timer = provider.listenerRestartTimer;
|
|
4424
|
-
if (timer !== void 0) {
|
|
4425
|
-
delete provider.listenerRestartTimer;
|
|
4426
|
-
clearTimeout(timer);
|
|
4427
|
-
}
|
|
4428
|
-
await provider.listenerStartup;
|
|
4429
|
-
await provider.listenerCleanup;
|
|
4430
|
-
const listener = provider.listener;
|
|
4431
|
-
if (listener === void 0) return;
|
|
4432
|
-
delete provider.listener;
|
|
4433
|
-
try {
|
|
4434
|
-
await listener.dispose();
|
|
4435
|
-
} catch (error) {
|
|
4436
|
-
provider.listenerRecoveryBlocked = true;
|
|
4437
|
-
throw error;
|
|
4438
|
-
}
|
|
4947
|
+
provider.realtimeStartup = observed;
|
|
4439
4948
|
}
|
|
4440
|
-
|
|
4441
|
-
return this.provider === provider &&
|
|
4949
|
+
realtimeFenceMatches(provider, generation) {
|
|
4950
|
+
return this.provider === provider && provider.realtimeGeneration === generation;
|
|
4442
4951
|
}
|
|
4443
|
-
|
|
4444
|
-
if (provider
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4952
|
+
async onRealtimeSynchronized(provider, generation, cause) {
|
|
4953
|
+
if (!this.realtimeFenceMatches(provider, generation)) return;
|
|
4954
|
+
if (cause === "system_notification" || cause === "stream_recovery") try {
|
|
4955
|
+
provider.localDeviceJoinRequestCountAfterSync = (await provider.client.listLocalDeviceJoinRequests()).length;
|
|
4956
|
+
} catch {
|
|
4957
|
+
this.ctx.logger("awiki-realtime").debug("AWiki realtime could not observe local device Join requests");
|
|
4958
|
+
}
|
|
4959
|
+
if (![
|
|
4960
|
+
"session_start",
|
|
4961
|
+
"connection_ready",
|
|
4962
|
+
"reconnected",
|
|
4963
|
+
"message",
|
|
4964
|
+
"message_update"
|
|
4965
|
+
].includes(cause)) return;
|
|
4966
|
+
this.ensureAgentConsumer(provider);
|
|
4967
|
+
await provider.agentConsumerStartup;
|
|
4968
|
+
if (!this.realtimeFenceMatches(provider, generation)) return;
|
|
4969
|
+
await provider.agentConsumer?.reconcileOnce();
|
|
4970
|
+
}
|
|
4971
|
+
ensureAgentConsumer(provider) {
|
|
4972
|
+
const workspaceContext = this.workspaceContext;
|
|
4973
|
+
const source = provider.client.agentInbox ?? provider.client.listener;
|
|
4974
|
+
const identityDid = this.activeIdentityDid;
|
|
4975
|
+
if (!this.resolved.listenerEnabled || workspaceContext === void 0 || source === void 0 || identityDid === void 0 || this.provider !== provider) return;
|
|
4976
|
+
if (provider.agentConsumer !== void 0 && provider.agentConsumerIdentityDid === identityDid) return;
|
|
4977
|
+
if (provider.agentConsumerStartup !== void 0) return;
|
|
4978
|
+
const generation = ++provider.agentConsumerGeneration;
|
|
4449
4979
|
const logger = this.ctx.logger("awiki-listener");
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4980
|
+
const observed = (async () => {
|
|
4981
|
+
await this.detachAgentConsumer(provider);
|
|
4982
|
+
if (!this.agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation)) return;
|
|
4983
|
+
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
4984
|
+
const consumer = new AwikiAgentListener(source, agents, {
|
|
4985
|
+
...this.resolved.listener,
|
|
4986
|
+
identityScope: identityDid
|
|
4987
|
+
}, logger);
|
|
4988
|
+
provider.agentConsumer = consumer;
|
|
4989
|
+
provider.agentConsumerIdentityDid = identityDid;
|
|
4990
|
+
await consumer.reconcileOnce();
|
|
4991
|
+
})().catch((error) => {
|
|
4992
|
+
logger.warn("AWiki Agent consumer startup failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
4457
4993
|
}).finally(() => {
|
|
4458
|
-
if (provider.
|
|
4459
|
-
|
|
4994
|
+
if (provider.agentConsumerStartup === observed) delete provider.agentConsumerStartup;
|
|
4995
|
+
});
|
|
4996
|
+
provider.agentConsumerStartup = observed;
|
|
4997
|
+
}
|
|
4998
|
+
agentConsumerFenceMatches(provider, workspaceContext, identityDid, generation) {
|
|
4999
|
+
return this.provider === provider && this.workspaceContext === workspaceContext && this.activeIdentityDid === identityDid && provider.agentConsumerGeneration === generation;
|
|
5000
|
+
}
|
|
5001
|
+
async detachAgentConsumer(provider) {
|
|
5002
|
+
const consumer = provider.agentConsumer;
|
|
5003
|
+
if (consumer === void 0) return;
|
|
5004
|
+
delete provider.agentConsumer;
|
|
5005
|
+
delete provider.agentConsumerIdentityDid;
|
|
5006
|
+
const observed = consumer.dispose().finally(() => {
|
|
5007
|
+
if (provider.agentConsumerCleanup === observed) delete provider.agentConsumerCleanup;
|
|
4460
5008
|
});
|
|
4461
|
-
provider.
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
provider.
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
5009
|
+
provider.agentConsumerCleanup = observed;
|
|
5010
|
+
await observed;
|
|
5011
|
+
}
|
|
5012
|
+
async stopAgentConsumer(provider) {
|
|
5013
|
+
provider.agentConsumerGeneration += 1;
|
|
5014
|
+
await provider.agentConsumerStartup;
|
|
5015
|
+
await provider.agentConsumerCleanup;
|
|
5016
|
+
await this.detachAgentConsumer(provider);
|
|
5017
|
+
}
|
|
5018
|
+
async stopRealtimeSupervisor(provider) {
|
|
5019
|
+
provider.realtimeGeneration += 1;
|
|
5020
|
+
await provider.realtimeStartup;
|
|
5021
|
+
const supervisor = provider.realtimeSupervisor;
|
|
5022
|
+
if (supervisor === void 0) return;
|
|
5023
|
+
delete provider.realtimeSupervisor;
|
|
5024
|
+
delete provider.realtimeIdentityDid;
|
|
5025
|
+
await supervisor.dispose();
|
|
5026
|
+
}
|
|
5027
|
+
async stopProviderRuntime(provider) {
|
|
5028
|
+
await Promise.all([this.stopRealtimeSupervisor(provider), this.stopAgentConsumer(provider)]);
|
|
4475
5029
|
}
|
|
4476
5030
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
4477
5031
|
disposeProvider(provider) {
|
|
4478
|
-
if (this.provider === provider)
|
|
5032
|
+
if (this.provider === provider) {
|
|
5033
|
+
this.provider = void 0;
|
|
5034
|
+
this.pendingDeviceJoin = void 0;
|
|
5035
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
5036
|
+
this.invalidateSummaries();
|
|
5037
|
+
}
|
|
4479
5038
|
provider.disposal ??= (async () => {
|
|
4480
5039
|
try {
|
|
4481
|
-
await
|
|
5040
|
+
await provider.runtimeReplacement;
|
|
5041
|
+
await this.stopProviderRuntime(provider);
|
|
4482
5042
|
} finally {
|
|
4483
5043
|
await provider.client.dispose();
|
|
4484
5044
|
}
|