@awiki/dsh-plugin 0.3.2 → 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 +36 -22
- package/README.zh.md +29 -19
- package/cordis.patch.yml +17 -0
- package/lib/client.js +1273 -216
- package/lib/client.js.map +1 -1
- package/lib/index.js +874 -266
- package/lib/provider.js +52 -16
- package/lib/{sdk-adapter-C0gdpg1u.mjs → sdk-adapter-BeJakxLP.mjs} +141 -3
- package/lib/typert.host.js +376 -44
- 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 +376 -44
- 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 -3
- 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 -3
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +38 -3
- 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 +50 -9
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +672 -157
- 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 +131 -2
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/types.d.ts +75 -1
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/package.json +5 -3
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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 { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import { isAbsolute, join } from "node:path";
|
|
5
6
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -7,7 +8,6 @@ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
|
7
8
|
import { SettingsConflictError, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
8
9
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
9
10
|
import { chmod, lstat, mkdir, readFile, readdir, rename, rm, unlink, utimes, writeFile } from "node:fs/promises";
|
|
10
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
11
11
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
12
12
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
13
13
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
@@ -1448,12 +1448,13 @@ var AwikiConversationPreferenceStore = class {
|
|
|
1448
1448
|
};
|
|
1449
1449
|
//#endregion
|
|
1450
1450
|
//#region lib/types/listener-state.js
|
|
1451
|
-
const STATE_VERSION =
|
|
1451
|
+
const STATE_VERSION = 2;
|
|
1452
1452
|
const MAX_STATE_BYTES = 1048576;
|
|
1453
1453
|
const MAX_CONVERSATIONS = 1e3;
|
|
1454
|
-
function emptyState() {
|
|
1454
|
+
function emptyState(identityScopeHash) {
|
|
1455
1455
|
return {
|
|
1456
1456
|
version: STATE_VERSION,
|
|
1457
|
+
identityScopeHash,
|
|
1457
1458
|
conversations: {}
|
|
1458
1459
|
};
|
|
1459
1460
|
}
|
|
@@ -1465,8 +1466,10 @@ function boundedString(value, field) {
|
|
|
1465
1466
|
return value;
|
|
1466
1467
|
}
|
|
1467
1468
|
/** Validate and detach the complete state file before the listener trusts any route. */
|
|
1468
|
-
function parseState(value) {
|
|
1469
|
-
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");
|
|
1470
1473
|
const entries = Object.entries(value.conversations);
|
|
1471
1474
|
if (entries.length > MAX_CONVERSATIONS) throw new TypeError("awiki: listener state is too large");
|
|
1472
1475
|
const conversations = {};
|
|
@@ -1489,6 +1492,7 @@ function parseState(value) {
|
|
|
1489
1492
|
}
|
|
1490
1493
|
return {
|
|
1491
1494
|
version: STATE_VERSION,
|
|
1495
|
+
identityScopeHash,
|
|
1492
1496
|
conversations
|
|
1493
1497
|
};
|
|
1494
1498
|
}
|
|
@@ -1499,28 +1503,31 @@ function isMissing(error) {
|
|
|
1499
1503
|
var AwikiListenerStateStore = class {
|
|
1500
1504
|
hostDirectory;
|
|
1501
1505
|
statePath;
|
|
1502
|
-
|
|
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");
|
|
1503
1510
|
this.hostDirectory = join(stateRoot, ".host");
|
|
1504
1511
|
this.statePath = join(this.hostDirectory, "listener-state.json");
|
|
1505
1512
|
}
|
|
1506
|
-
/** Load the current
|
|
1513
|
+
/** Load the current identity scope or reset unscoped v1 state on first use. */
|
|
1507
1514
|
async load() {
|
|
1508
|
-
if (!await this.hasPrivateHostDirectory()) return emptyState();
|
|
1515
|
+
if (!await this.hasPrivateHostDirectory()) return emptyState(this.identityScopeHash);
|
|
1509
1516
|
try {
|
|
1510
1517
|
const metadata = await lstat(this.statePath);
|
|
1511
1518
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is invalid");
|
|
1512
1519
|
const text = await readFile(this.statePath, "utf8");
|
|
1513
1520
|
if (Buffer.byteLength(text, "utf8") > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is invalid");
|
|
1514
|
-
return parseState(JSON.parse(text));
|
|
1521
|
+
return parseState(JSON.parse(text), this.identityScopeHash);
|
|
1515
1522
|
} catch (error) {
|
|
1516
|
-
if (isMissing(error)) return emptyState();
|
|
1523
|
+
if (isMissing(error)) return emptyState(this.identityScopeHash);
|
|
1517
1524
|
if (error instanceof SyntaxError) throw new TypeError("awiki: listener state file is invalid");
|
|
1518
1525
|
throw error;
|
|
1519
1526
|
}
|
|
1520
1527
|
}
|
|
1521
1528
|
/** Replace the state atomically without ever writing message or Agent text. */
|
|
1522
1529
|
async save(state) {
|
|
1523
|
-
const snapshot = parseState(state);
|
|
1530
|
+
const snapshot = parseState(state, this.identityScopeHash);
|
|
1524
1531
|
const text = `${JSON.stringify(snapshot)}\n`;
|
|
1525
1532
|
if (Buffer.byteLength(text, "utf8") > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is too large");
|
|
1526
1533
|
await mkdir(this.hostDirectory, {
|
|
@@ -1742,11 +1749,12 @@ function incomingFromPeer(message, conversation) {
|
|
|
1742
1749
|
}
|
|
1743
1750
|
function copyState(state) {
|
|
1744
1751
|
return {
|
|
1745
|
-
version:
|
|
1752
|
+
version: 2,
|
|
1753
|
+
identityScopeHash: state.identityScopeHash,
|
|
1746
1754
|
conversations: Object.fromEntries(Object.entries(state.conversations).map(([id, route]) => [id, { ...route }]))
|
|
1747
1755
|
};
|
|
1748
1756
|
}
|
|
1749
|
-
/**
|
|
1757
|
+
/** Consume authorized Direct text only after the identity supervisor commits synchronization. */
|
|
1750
1758
|
var AwikiAgentListener = class {
|
|
1751
1759
|
awiki;
|
|
1752
1760
|
agents;
|
|
@@ -1755,34 +1763,23 @@ var AwikiAgentListener = class {
|
|
|
1755
1763
|
store;
|
|
1756
1764
|
logger;
|
|
1757
1765
|
stateReady;
|
|
1758
|
-
state
|
|
1759
|
-
version: 1,
|
|
1760
|
-
conversations: {}
|
|
1761
|
-
};
|
|
1766
|
+
state;
|
|
1762
1767
|
stateMutation = Promise.resolve();
|
|
1763
1768
|
syncMutation = Promise.resolve();
|
|
1764
1769
|
scheduledMessageIds = /* @__PURE__ */ new Set();
|
|
1765
1770
|
conversationQueues = /* @__PURE__ */ new Map();
|
|
1766
|
-
lifecycle;
|
|
1767
|
-
started;
|
|
1768
|
-
resolveStarted;
|
|
1769
|
-
rejectStarted;
|
|
1770
|
-
lifecycleGeneration = 0;
|
|
1771
|
-
streamGeneration = 0;
|
|
1772
|
-
activeRealtime;
|
|
1773
1771
|
stopped = false;
|
|
1774
|
-
termination;
|
|
1775
|
-
resolveTermination;
|
|
1776
|
-
terminationSettled = false;
|
|
1777
1772
|
constructor(awiki, agents, config, logger, store) {
|
|
1778
1773
|
this.awiki = awiki;
|
|
1779
1774
|
this.agents = agents;
|
|
1780
1775
|
this.config = config;
|
|
1781
|
-
this.termination = new Promise((resolve) => {
|
|
1782
|
-
this.resolveTermination = resolve;
|
|
1783
|
-
});
|
|
1784
1776
|
this.allowedPeers = new Set(config.allowedPeers.map((peer) => peer.startsWith("did:") ? peer : peer.toLowerCase()));
|
|
1785
|
-
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
|
+
};
|
|
1786
1783
|
this.logger = logger ?? {
|
|
1787
1784
|
debug() {},
|
|
1788
1785
|
info() {},
|
|
@@ -1794,38 +1791,11 @@ var AwikiAgentListener = class {
|
|
|
1794
1791
|
this.state = state;
|
|
1795
1792
|
});
|
|
1796
1793
|
}
|
|
1797
|
-
/**
|
|
1798
|
-
|
|
1799
|
-
if (this.started !== void 0) return this.started;
|
|
1800
|
-
this.started = new Promise((resolve, reject) => {
|
|
1801
|
-
this.resolveStarted = resolve;
|
|
1802
|
-
this.rejectStarted = reject;
|
|
1803
|
-
});
|
|
1804
|
-
const generation = ++this.lifecycleGeneration;
|
|
1805
|
-
this.lifecycle = this.run(generation).then(() => {
|
|
1806
|
-
this.finishTermination({ kind: "stopped" });
|
|
1807
|
-
}, (error) => {
|
|
1808
|
-
this.rejectStarted?.(error);
|
|
1809
|
-
this.rejectStarted = void 0;
|
|
1810
|
-
this.finishTermination({
|
|
1811
|
-
kind: "failed",
|
|
1812
|
-
error
|
|
1813
|
-
});
|
|
1814
|
-
if (!this.stopped) this.logger.warn("AWiki realtime listener stopped: %s", error instanceof Error ? error.message : "unknown failure");
|
|
1815
|
-
});
|
|
1816
|
-
return this.started;
|
|
1817
|
-
}
|
|
1818
|
-
/** Resolve once with either orderly shutdown or the exact terminal lifecycle failure. */
|
|
1819
|
-
whenTerminated() {
|
|
1820
|
-
return this.termination;
|
|
1821
|
-
}
|
|
1822
|
-
/** Deterministic canonical sync plus committed-history reconciliation for tests and recovery. */
|
|
1823
|
-
async synchronizeOnce(reason) {
|
|
1794
|
+
/** Reconcile only committed history; this consumer cannot start WSS or advance sync. */
|
|
1795
|
+
async reconcileOnce() {
|
|
1824
1796
|
await this.enqueueSync(async () => {
|
|
1825
1797
|
await this.stateReady;
|
|
1826
1798
|
if (this.stopped) return;
|
|
1827
|
-
await this.awiki.syncNow(reason);
|
|
1828
|
-
if (this.stopped) return;
|
|
1829
1799
|
await this.reconcileCommittedHistory();
|
|
1830
1800
|
});
|
|
1831
1801
|
}
|
|
@@ -1835,84 +1805,12 @@ var AwikiAgentListener = class {
|
|
|
1835
1805
|
while (this.conversationQueues.size > 0) await Promise.allSettled([...this.conversationQueues.values()]);
|
|
1836
1806
|
await this.stateMutation;
|
|
1837
1807
|
}
|
|
1838
|
-
/**
|
|
1808
|
+
/** Fence late work, drain committed messages, then release listener-owned Agents. */
|
|
1839
1809
|
async dispose() {
|
|
1840
1810
|
if (this.stopped) return;
|
|
1841
1811
|
this.stopped = true;
|
|
1842
|
-
this.lifecycleGeneration += 1;
|
|
1843
|
-
this.streamGeneration += 1;
|
|
1844
|
-
const active = this.activeRealtime;
|
|
1845
|
-
this.activeRealtime = void 0;
|
|
1846
|
-
await active?.session.stop().catch(() => void 0);
|
|
1847
|
-
await this.lifecycle;
|
|
1848
|
-
this.resolveStarted?.();
|
|
1849
|
-
this.resolveStarted = void 0;
|
|
1850
|
-
this.rejectStarted = void 0;
|
|
1851
1812
|
await this.whenIdle();
|
|
1852
1813
|
await this.agents.dispose();
|
|
1853
|
-
this.finishTermination({ kind: "stopped" });
|
|
1854
|
-
}
|
|
1855
|
-
finishTermination(result) {
|
|
1856
|
-
if (this.terminationSettled) return;
|
|
1857
|
-
this.terminationSettled = true;
|
|
1858
|
-
this.resolveTermination(result);
|
|
1859
|
-
}
|
|
1860
|
-
currentLifecycle(generation) {
|
|
1861
|
-
return !this.stopped && this.lifecycleGeneration === generation;
|
|
1862
|
-
}
|
|
1863
|
-
async run(lifecycleGeneration) {
|
|
1864
|
-
await this.synchronize("session_start", lifecycleGeneration);
|
|
1865
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1866
|
-
let active = await this.openRealtime(lifecycleGeneration);
|
|
1867
|
-
if (active === void 0) return;
|
|
1868
|
-
this.resolveStarted?.();
|
|
1869
|
-
this.resolveStarted = void 0;
|
|
1870
|
-
this.rejectStarted = void 0;
|
|
1871
|
-
try {
|
|
1872
|
-
while (this.currentLifecycle(lifecycleGeneration)) {
|
|
1873
|
-
const event = await active.session.nextEvent();
|
|
1874
|
-
if (!this.currentLifecycle(lifecycleGeneration) || this.activeRealtime?.generation !== active.generation) return;
|
|
1875
|
-
if (event === null) {
|
|
1876
|
-
this.activeRealtime = void 0;
|
|
1877
|
-
this.streamGeneration += 1;
|
|
1878
|
-
await active.session.stop();
|
|
1879
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1880
|
-
await this.synchronize("websocket_reconnect", lifecycleGeneration);
|
|
1881
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1882
|
-
const replacement = await this.openRealtime(lifecycleGeneration);
|
|
1883
|
-
if (replacement === void 0) return;
|
|
1884
|
-
active = replacement;
|
|
1885
|
-
continue;
|
|
1886
|
-
}
|
|
1887
|
-
if (event.kind !== "sync_required") continue;
|
|
1888
|
-
await this.synchronize(event.cause === "reconnected" ? "websocket_reconnect" : "websocket_hint", lifecycleGeneration);
|
|
1889
|
-
}
|
|
1890
|
-
} finally {
|
|
1891
|
-
if (this.activeRealtime?.generation === active.generation) this.activeRealtime = void 0;
|
|
1892
|
-
await active.session.stop().catch(() => void 0);
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
async openRealtime(lifecycleGeneration) {
|
|
1896
|
-
const session = await this.awiki.startRealtime();
|
|
1897
|
-
if (!this.currentLifecycle(lifecycleGeneration)) {
|
|
1898
|
-
await session.stop().catch(() => void 0);
|
|
1899
|
-
return;
|
|
1900
|
-
}
|
|
1901
|
-
const active = {
|
|
1902
|
-
generation: ++this.streamGeneration,
|
|
1903
|
-
session
|
|
1904
|
-
};
|
|
1905
|
-
this.activeRealtime = active;
|
|
1906
|
-
return active;
|
|
1907
|
-
}
|
|
1908
|
-
async synchronize(reason, lifecycleGeneration) {
|
|
1909
|
-
await this.enqueueSync(async () => {
|
|
1910
|
-
await this.stateReady;
|
|
1911
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1912
|
-
await this.awiki.syncNow(reason);
|
|
1913
|
-
if (!this.currentLifecycle(lifecycleGeneration)) return;
|
|
1914
|
-
await this.reconcileCommittedHistory();
|
|
1915
|
-
});
|
|
1916
1814
|
}
|
|
1917
1815
|
enqueueSync(operation) {
|
|
1918
1816
|
const pending = this.syncMutation.then(operation, operation);
|
|
@@ -2079,7 +1977,8 @@ var AwikiAgentListener = class {
|
|
|
2079
1977
|
...update.lastProcessedMessageId === void 0 ? current?.lastProcessedMessageId === void 0 ? {} : { lastProcessedMessageId: current.lastProcessedMessageId } : { lastProcessedMessageId: update.lastProcessedMessageId }
|
|
2080
1978
|
};
|
|
2081
1979
|
this.state = {
|
|
2082
|
-
version:
|
|
1980
|
+
version: 2,
|
|
1981
|
+
identityScopeHash: this.state.identityScopeHash,
|
|
2083
1982
|
conversations: {
|
|
2084
1983
|
...this.state.conversations,
|
|
2085
1984
|
[conversation.id]: route
|
|
@@ -2092,6 +1991,154 @@ var AwikiAgentListener = class {
|
|
|
2092
1991
|
}
|
|
2093
1992
|
};
|
|
2094
1993
|
//#endregion
|
|
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";
|
|
2000
|
+
}
|
|
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;
|
|
2027
|
+
}
|
|
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
|
+
};
|
|
2141
|
+
//#endregion
|
|
2095
2142
|
//#region lib/types/index.js
|
|
2096
2143
|
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
2097
2144
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
@@ -2162,6 +2209,7 @@ const Config = z.object({
|
|
|
2162
2209
|
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
2163
2210
|
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
2164
2211
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
2212
|
+
realtimeEnabled: z.boolean().default(true),
|
|
2165
2213
|
listenerEnabled: z.boolean().default(false),
|
|
2166
2214
|
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
2167
2215
|
listenerWorkspacePath: z.string(),
|
|
@@ -2192,10 +2240,17 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
|
2192
2240
|
"network",
|
|
2193
2241
|
"remote"
|
|
2194
2242
|
]);
|
|
2195
|
-
const
|
|
2196
|
-
const
|
|
2197
|
-
const
|
|
2198
|
-
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
|
+
]);
|
|
2199
2254
|
const FAILURE_MESSAGES = {
|
|
2200
2255
|
"not-registered": "No AWiki identity is registered for this deployment.",
|
|
2201
2256
|
"signed-out": "This installation is signed out of AWiki.",
|
|
@@ -2223,6 +2278,39 @@ const FAILURE_MESSAGES = {
|
|
|
2223
2278
|
};
|
|
2224
2279
|
var ProviderUnavailableError = class extends Error {};
|
|
2225
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
|
+
}
|
|
2226
2314
|
/** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
|
|
2227
2315
|
function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
2228
2316
|
let url;
|
|
@@ -2307,6 +2395,8 @@ function resolveConfig(config) {
|
|
|
2307
2395
|
const pollIntervalMs = config.pollIntervalMs ?? 3e3;
|
|
2308
2396
|
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1e3 || pollIntervalMs > 6e4) throw new TypeError("awiki: pollIntervalMs must be a safe integer from 1000 through 60000");
|
|
2309
2397
|
const listenerEnabled = config.listenerEnabled ?? false;
|
|
2398
|
+
const realtimeEnabled = config.realtimeEnabled ?? true;
|
|
2399
|
+
if (listenerEnabled && !realtimeEnabled) throw new TypeError("awiki: listenerEnabled requires realtimeEnabled");
|
|
2310
2400
|
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, "workspaces", "awiki");
|
|
2311
2401
|
if (!isAbsolute(listenerWorkspacePath)) throw new TypeError("awiki: listenerWorkspacePath must be an absolute path");
|
|
2312
2402
|
const allowedPeers = listenerAllowedPeers(config.listenerAllowedPeers, listenerEnabled);
|
|
@@ -2329,6 +2419,7 @@ function resolveConfig(config) {
|
|
|
2329
2419
|
attachmentMaxBytes,
|
|
2330
2420
|
imageAttachmentCacheMaxBytes,
|
|
2331
2421
|
pollIntervalMs,
|
|
2422
|
+
realtimeEnabled,
|
|
2332
2423
|
listenerEnabled,
|
|
2333
2424
|
listener: {
|
|
2334
2425
|
allowedPeers,
|
|
@@ -2479,6 +2570,7 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
2479
2570
|
};
|
|
2480
2571
|
}
|
|
2481
2572
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 65536;
|
|
2573
|
+
const SERVER_INFO_RESPONSE_MAX_BYTES = 65536;
|
|
2482
2574
|
const RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES = 4096;
|
|
2483
2575
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
2484
2576
|
async function readBoundedResponseText(response, maxBytes) {
|
|
@@ -2697,6 +2789,14 @@ let AwikiService = (() => {
|
|
|
2697
2789
|
let _inspectIdentityAccess_decorators;
|
|
2698
2790
|
let _sendRegistrationOtp_decorators;
|
|
2699
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;
|
|
2700
2800
|
let _updateDisplayName_decorators;
|
|
2701
2801
|
let _getProfile_decorators;
|
|
2702
2802
|
let _updateProfile_decorators;
|
|
@@ -2742,6 +2842,14 @@ let AwikiService = (() => {
|
|
|
2742
2842
|
_inspectIdentityAccess_decorators = [Remote];
|
|
2743
2843
|
_sendRegistrationOtp_decorators = [Remote];
|
|
2744
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];
|
|
2745
2853
|
_updateDisplayName_decorators = [Remote];
|
|
2746
2854
|
_getProfile_decorators = [Remote];
|
|
2747
2855
|
_updateProfile_decorators = [Remote];
|
|
@@ -2864,6 +2972,94 @@ let AwikiService = (() => {
|
|
|
2864
2972
|
},
|
|
2865
2973
|
metadata: _metadata
|
|
2866
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);
|
|
2867
3063
|
__esDecorate(this, null, _updateDisplayName_decorators, {
|
|
2868
3064
|
kind: "method",
|
|
2869
3065
|
name: "updateDisplayName",
|
|
@@ -3259,6 +3455,12 @@ let AwikiService = (() => {
|
|
|
3259
3455
|
sessionMutation = Promise.resolve();
|
|
3260
3456
|
sessionRevision = 0;
|
|
3261
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();
|
|
3262
3464
|
activeSummaryRequests = /* @__PURE__ */ new Set();
|
|
3263
3465
|
summaryProvider;
|
|
3264
3466
|
recoveryReconciliationTarget;
|
|
@@ -3302,12 +3504,12 @@ let AwikiService = (() => {
|
|
|
3302
3504
|
ctx.inject(["workspaceRegistry"], (workspaceCtx) => {
|
|
3303
3505
|
this.workspaceContext = workspaceCtx;
|
|
3304
3506
|
const provider = this.provider;
|
|
3305
|
-
if (provider !== void 0) this.
|
|
3507
|
+
if (provider !== void 0) this.ensureAgentConsumer(provider);
|
|
3306
3508
|
workspaceCtx.effect(() => async () => {
|
|
3307
3509
|
if (this.workspaceContext !== workspaceCtx) return;
|
|
3308
3510
|
this.workspaceContext = void 0;
|
|
3309
3511
|
const current = this.provider;
|
|
3310
|
-
if (current !== void 0) await this.
|
|
3512
|
+
if (current !== void 0) await this.stopAgentConsumer(current);
|
|
3311
3513
|
}, "awiki: release Workspace listener composition");
|
|
3312
3514
|
});
|
|
3313
3515
|
registerAwikiTools(ctx, this);
|
|
@@ -3328,6 +3530,8 @@ let AwikiService = (() => {
|
|
|
3328
3530
|
*/
|
|
3329
3531
|
registerClientFactory(factory) {
|
|
3330
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;
|
|
3331
3535
|
const provider = {
|
|
3332
3536
|
client: factory({
|
|
3333
3537
|
userServiceUrl: this.resolved.userServiceUrl,
|
|
@@ -3341,14 +3545,28 @@ let AwikiService = (() => {
|
|
|
3341
3545
|
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
3342
3546
|
stateRoot: this.resolved.stateRoot
|
|
3343
3547
|
}),
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3548
|
+
realtimeGeneration: 0,
|
|
3549
|
+
agentConsumerGeneration: 0,
|
|
3550
|
+
localDeviceJoinRequestCountAfterSync: 0
|
|
3347
3551
|
};
|
|
3348
3552
|
this.provider = provider;
|
|
3349
|
-
this.
|
|
3553
|
+
this.ensureRealtimeSupervisor(provider);
|
|
3554
|
+
this.ensureAgentConsumer(provider);
|
|
3350
3555
|
return () => this.disposeProvider(provider);
|
|
3351
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
|
+
}
|
|
3352
3570
|
/** Register the optional Model Proxy recovery target without exposing an arbitrary callback or token. */
|
|
3353
3571
|
registerRecoveryReconciliationTarget(target) {
|
|
3354
3572
|
if (this.recoveryReconciliationTarget !== void 0) throw new Error("awiki: a recovery reconciliation target is already registered");
|
|
@@ -3376,14 +3594,42 @@ let AwikiService = (() => {
|
|
|
3376
3594
|
* Read settings needed by the browser presentation.
|
|
3377
3595
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
3378
3596
|
*/
|
|
3379
|
-
getConfig() {
|
|
3380
|
-
|
|
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 {
|
|
3381
3626
|
ok: true,
|
|
3382
3627
|
value: {
|
|
3383
3628
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
3384
|
-
attachmentMaxBytes: this.resolved.attachmentMaxBytes
|
|
3629
|
+
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
3630
|
+
handleRecoveryPhoneEnabled
|
|
3385
3631
|
}
|
|
3386
|
-
}
|
|
3632
|
+
};
|
|
3387
3633
|
}
|
|
3388
3634
|
/**
|
|
3389
3635
|
* Read the deployment's identity status.
|
|
@@ -3406,6 +3652,8 @@ let AwikiService = (() => {
|
|
|
3406
3652
|
const identity = await this.run((client) => client.getIdentity(), { allowSignedOut: true });
|
|
3407
3653
|
if (!identity.ok) return identity;
|
|
3408
3654
|
this.activeIdentityDid = identity.value?.did;
|
|
3655
|
+
const provider = this.provider;
|
|
3656
|
+
if (identity.value !== null && provider !== void 0) this.ensureProviderRuntime(provider);
|
|
3409
3657
|
return identity.value === null ? {
|
|
3410
3658
|
ok: true,
|
|
3411
3659
|
value: { status: "unregistered" }
|
|
@@ -3442,7 +3690,7 @@ let AwikiService = (() => {
|
|
|
3442
3690
|
const session = { status: "signed-out" };
|
|
3443
3691
|
this.publishSession(session);
|
|
3444
3692
|
const provider = this.provider;
|
|
3445
|
-
if (provider !== void 0) await this.
|
|
3693
|
+
if (provider !== void 0) await this.stopProviderRuntime(provider);
|
|
3446
3694
|
return {
|
|
3447
3695
|
ok: true,
|
|
3448
3696
|
value: session
|
|
@@ -3475,7 +3723,7 @@ let AwikiService = (() => {
|
|
|
3475
3723
|
};
|
|
3476
3724
|
this.publishSession(session);
|
|
3477
3725
|
const provider = this.provider;
|
|
3478
|
-
if (provider !== void 0) this.
|
|
3726
|
+
if (provider !== void 0) this.ensureProviderRuntime(provider);
|
|
3479
3727
|
return {
|
|
3480
3728
|
ok: true,
|
|
3481
3729
|
value: session
|
|
@@ -3561,8 +3809,15 @@ let AwikiService = (() => {
|
|
|
3561
3809
|
};
|
|
3562
3810
|
}
|
|
3563
3811
|
}
|
|
3564
|
-
sendRegistrationOtp(request) {
|
|
3565
|
-
|
|
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
|
+
});
|
|
3566
3821
|
}
|
|
3567
3822
|
/**
|
|
3568
3823
|
* Register and persist the deployment's only AWiki identity.
|
|
@@ -3570,9 +3825,190 @@ let AwikiService = (() => {
|
|
|
3570
3825
|
* @returns The new public identity or a closed failure.
|
|
3571
3826
|
*/
|
|
3572
3827
|
async registerIdentity(request) {
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
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
|
+
});
|
|
3576
4012
|
}
|
|
3577
4013
|
/**
|
|
3578
4014
|
* Update the deployment identity's public WNS display name.
|
|
@@ -3599,12 +4035,19 @@ let AwikiService = (() => {
|
|
|
3599
4035
|
}
|
|
3600
4036
|
/** Start durable phone recovery for one existing full Handle. */
|
|
3601
4037
|
sendRecoveryOtp(request) {
|
|
4038
|
+
this.pendingDeviceJoin = void 0;
|
|
3602
4039
|
const normalized = normalizeRecoveryOtpRequest(request);
|
|
3603
4040
|
if (normalized === void 0) return Promise.resolve({
|
|
3604
4041
|
ok: false,
|
|
3605
4042
|
error: failure("invalid-request")
|
|
3606
4043
|
});
|
|
3607
|
-
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 });
|
|
3608
4051
|
}
|
|
3609
4052
|
/** Verify a recovery OTP and freeze its exact intent before the remote commit. */
|
|
3610
4053
|
prepareRecovery(request) {
|
|
@@ -4116,10 +4559,10 @@ let AwikiService = (() => {
|
|
|
4116
4559
|
});
|
|
4117
4560
|
return this.mutateSession(async () => {
|
|
4118
4561
|
const provider = this.provider;
|
|
4119
|
-
if (provider !== void 0) await this.
|
|
4562
|
+
if (provider !== void 0) await this.stopProviderRuntime(provider);
|
|
4120
4563
|
const result = await this.run((client) => client.clearLocalData(), { allowSignedOut: true });
|
|
4121
4564
|
if (!result.ok) {
|
|
4122
|
-
if (provider !== void 0 && this.provider === provider) this.
|
|
4565
|
+
if (provider !== void 0 && this.provider === provider) this.ensureProviderRuntime(provider);
|
|
4123
4566
|
return result;
|
|
4124
4567
|
}
|
|
4125
4568
|
try {
|
|
@@ -4129,9 +4572,10 @@ let AwikiService = (() => {
|
|
|
4129
4572
|
await this.sessionStore.signIn();
|
|
4130
4573
|
this.signedOut = false;
|
|
4131
4574
|
this.activeIdentityDid = void 0;
|
|
4575
|
+
this.pendingDeviceJoin = void 0;
|
|
4576
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
4132
4577
|
this.invalidateSummaries();
|
|
4133
4578
|
this.publishSession({ status: "unregistered" });
|
|
4134
|
-
if (provider !== void 0 && this.provider === provider) this.startListener(provider);
|
|
4135
4579
|
return result;
|
|
4136
4580
|
} catch {
|
|
4137
4581
|
return {
|
|
@@ -4165,8 +4609,7 @@ let AwikiService = (() => {
|
|
|
4165
4609
|
identity
|
|
4166
4610
|
};
|
|
4167
4611
|
this.publishSession(session);
|
|
4168
|
-
|
|
4169
|
-
await this.startListener(provider);
|
|
4612
|
+
this.ensureProviderRuntime(provider);
|
|
4170
4613
|
}
|
|
4171
4614
|
return reconciled;
|
|
4172
4615
|
} catch {
|
|
@@ -4202,22 +4645,159 @@ let AwikiService = (() => {
|
|
|
4202
4645
|
return false;
|
|
4203
4646
|
}
|
|
4204
4647
|
}
|
|
4205
|
-
/**
|
|
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. */
|
|
4206
4783
|
async activateRegisteredIdentity(identity) {
|
|
4784
|
+
this.pendingDeviceJoin = void 0;
|
|
4785
|
+
this.activeDeviceJoinSessionId = void 0;
|
|
4207
4786
|
this.activeIdentityDid = identity.did;
|
|
4208
4787
|
this.publishSession({
|
|
4209
4788
|
status: "active",
|
|
4210
4789
|
identity
|
|
4211
4790
|
});
|
|
4212
4791
|
const provider = this.provider;
|
|
4213
|
-
if (provider !== void 0)
|
|
4214
|
-
await provider.listenerStartup;
|
|
4215
|
-
await this.startListener(provider);
|
|
4216
|
-
}
|
|
4792
|
+
if (provider !== void 0) this.ensureProviderRuntime(provider);
|
|
4217
4793
|
}
|
|
4218
4794
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
4219
4795
|
invalidateSummaries() {
|
|
4220
4796
|
this.sessionRevision += 1;
|
|
4797
|
+
this.requestRefs.clear();
|
|
4798
|
+
this.requestSessions.clear();
|
|
4799
|
+
this.deviceRefs.clear();
|
|
4800
|
+
this.deviceIds.clear();
|
|
4221
4801
|
for (const controller of this.activeSummaryRequests) controller.abort();
|
|
4222
4802
|
this.activeSummaryRequests.clear();
|
|
4223
4803
|
}
|
|
@@ -4317,120 +4897,148 @@ let AwikiService = (() => {
|
|
|
4317
4897
|
this.sessionMutation = pending.then(() => void 0, () => void 0);
|
|
4318
4898
|
return pending;
|
|
4319
4899
|
}
|
|
4320
|
-
/** Start
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
if (provider.
|
|
4324
|
-
if (
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
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");
|
|
4330
4932
|
const observed = (async () => {
|
|
4331
4933
|
if (await this.isSignedOut()) return;
|
|
4332
|
-
|
|
4333
|
-
if (!this.
|
|
4334
|
-
|
|
4335
|
-
const
|
|
4336
|
-
provider.
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
if (provider.listener === listener) delete provider.listener;
|
|
4341
|
-
try {
|
|
4342
|
-
await listener.dispose();
|
|
4343
|
-
} catch {
|
|
4344
|
-
provider.listenerRecoveryBlocked = true;
|
|
4345
|
-
}
|
|
4346
|
-
if (!provider.listenerRecoveryBlocked && provider.listenerRestartAttempt > 0) this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
4347
|
-
throw error;
|
|
4348
|
-
}
|
|
4349
|
-
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) {
|
|
4350
|
-
if (provider.listener === listener) delete provider.listener;
|
|
4351
|
-
try {
|
|
4352
|
-
await listener.dispose();
|
|
4353
|
-
} catch (error) {
|
|
4354
|
-
provider.listenerRecoveryBlocked = true;
|
|
4355
|
-
throw error;
|
|
4356
|
-
}
|
|
4357
|
-
return;
|
|
4358
|
-
}
|
|
4359
|
-
provider.listenerStartedAt = Date.now();
|
|
4360
|
-
listener.whenTerminated().then((result) => {
|
|
4361
|
-
if (result.kind === "failed") this.releaseFailedListener(provider, listener, workspaceContext, generation, result.error);
|
|
4362
|
-
});
|
|
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);
|
|
4363
4942
|
})().catch((error) => {
|
|
4364
|
-
logger.warn("AWiki
|
|
4943
|
+
logger.warn("AWiki realtime startup failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
4365
4944
|
}).finally(() => {
|
|
4366
|
-
if (provider.
|
|
4945
|
+
if (provider.realtimeStartup === observed) delete provider.realtimeStartup;
|
|
4367
4946
|
});
|
|
4368
|
-
provider.
|
|
4369
|
-
return observed;
|
|
4370
|
-
}
|
|
4371
|
-
async stopListener(provider) {
|
|
4372
|
-
provider.listenerGeneration += 1;
|
|
4373
|
-
provider.listenerRestartAttempt = 0;
|
|
4374
|
-
delete provider.listenerStartedAt;
|
|
4375
|
-
const timer = provider.listenerRestartTimer;
|
|
4376
|
-
if (timer !== void 0) {
|
|
4377
|
-
delete provider.listenerRestartTimer;
|
|
4378
|
-
clearTimeout(timer);
|
|
4379
|
-
}
|
|
4380
|
-
await provider.listenerStartup;
|
|
4381
|
-
await provider.listenerCleanup;
|
|
4382
|
-
const listener = provider.listener;
|
|
4383
|
-
if (listener === void 0) return;
|
|
4384
|
-
delete provider.listener;
|
|
4385
|
-
try {
|
|
4386
|
-
await listener.dispose();
|
|
4387
|
-
} catch (error) {
|
|
4388
|
-
provider.listenerRecoveryBlocked = true;
|
|
4389
|
-
throw error;
|
|
4390
|
-
}
|
|
4947
|
+
provider.realtimeStartup = observed;
|
|
4391
4948
|
}
|
|
4392
|
-
|
|
4393
|
-
return this.provider === provider &&
|
|
4949
|
+
realtimeFenceMatches(provider, generation) {
|
|
4950
|
+
return this.provider === provider && provider.realtimeGeneration === generation;
|
|
4394
4951
|
}
|
|
4395
|
-
|
|
4396
|
-
if (provider
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
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;
|
|
4401
4979
|
const logger = this.ctx.logger("awiki-listener");
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
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");
|
|
4409
4993
|
}).finally(() => {
|
|
4410
|
-
if (provider.
|
|
4411
|
-
|
|
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;
|
|
4412
5008
|
});
|
|
4413
|
-
provider.
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
provider.
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
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)]);
|
|
4427
5029
|
}
|
|
4428
5030
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
4429
5031
|
disposeProvider(provider) {
|
|
4430
|
-
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
|
+
}
|
|
4431
5038
|
provider.disposal ??= (async () => {
|
|
4432
5039
|
try {
|
|
4433
|
-
await
|
|
5040
|
+
await provider.runtimeReplacement;
|
|
5041
|
+
await this.stopProviderRuntime(provider);
|
|
4434
5042
|
} finally {
|
|
4435
5043
|
await provider.client.dispose();
|
|
4436
5044
|
}
|