@awiki/dsh-plugin 0.3.4 → 0.3.5
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 +32 -38
- package/README.zh.md +28 -31
- package/cordis.patch.yml +0 -17
- package/lib/client.js +269 -1266
- package/lib/client.js.map +1 -1
- package/lib/index.js +316 -922
- package/lib/provider.js +16 -52
- package/lib/{sdk-adapter-BeJakxLP.mjs → sdk-adapter-CDLTgHJA.mjs} +4 -142
- package/lib/typert.host.js +89 -419
- package/lib/typert.remote-client.d.ts +3 -19
- package/lib/typert.remote-client.d.ts.map +1 -1
- package/lib/typert.remote-client.js +89 -419
- package/lib/types/client/AwikiIdentityAccess.d.ts +5 -6
- package/lib/types/client/AwikiIdentityAccess.d.ts.map +1 -1
- package/lib/types/client/AwikiIdentityAccess.js +27 -88
- 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 +3 -4
- package/lib/types/client/AwikiOverlay.js.map +1 -1
- package/lib/types/client/controller.d.ts +5 -20
- package/lib/types/client/controller.d.ts.map +1 -1
- package/lib/types/client/controller.js +7 -38
- 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 +0 -8
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/client/slots.d.ts +2 -10
- package/lib/types/client/slots.d.ts.map +1 -1
- package/lib/types/index.d.ts +11 -54
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +176 -735
- package/lib/types/index.js.map +1 -1
- package/lib/types/listener-state.d.ts +3 -5
- package/lib/types/listener-state.d.ts.map +1 -1
- package/lib/types/listener-state.js +13 -28
- package/lib/types/listener-state.js.map +1 -1
- package/lib/types/listener.d.ts +32 -9
- package/lib/types/listener.d.ts.map +1 -1
- package/lib/types/listener.js +127 -11
- package/lib/types/listener.js.map +1 -1
- package/lib/types/profile-state.d.ts +11 -0
- package/lib/types/profile-state.d.ts.map +1 -0
- package/lib/types/profile-state.js +48 -0
- package/lib/types/profile-state.js.map +1 -0
- package/lib/types/provider-api.d.ts +4 -96
- package/lib/types/provider-api.d.ts.map +1 -1
- package/lib/types/provider.d.ts +1 -7
- package/lib/types/provider.d.ts.map +1 -1
- package/lib/types/provider.js +15 -53
- package/lib/types/provider.js.map +1 -1
- package/lib/types/sdk-adapter.d.ts +2 -28
- package/lib/types/sdk-adapter.d.ts.map +1 -1
- package/lib/types/sdk-adapter.js +3 -132
- package/lib/types/sdk-adapter.js.map +1 -1
- package/lib/types/types.d.ts +6 -76
- 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/client/AwikiDevices.d.ts +0 -28
- package/lib/types/client/AwikiDevices.d.ts.map +0 -1
- package/lib/types/client/AwikiDevices.js +0 -85
- package/lib/types/client/AwikiDevices.js.map +0 -1
- package/lib/types/realtime-supervisor.d.ts +0 -43
- package/lib/types/realtime-supervisor.d.ts.map +0 -1
- package/lib/types/realtime-supervisor.js +0 -166
- package/lib/types/realtime-supervisor.js.map +0 -1
package/lib/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { n as downloadedAttachment } from "./sdk-adapter-
|
|
1
|
+
import { n as downloadedAttachment } from "./sdk-adapter-CDLTgHJA.mjs";
|
|
2
2
|
import "@deepseek-ai/cordis";
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { isAbsolute, join } from "node:path";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, resolve } 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";
|
|
11
12
|
import { installModelSelection } from "@deepseek-ai/dsh-agent";
|
|
12
13
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
13
14
|
import { SessionId } from "@deepseek-ai/dsh-session";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
14
16
|
//#region lib/types/types.js
|
|
15
17
|
/** Client-safe AWiki service and Remote data types. */
|
|
16
18
|
/** Exact browser acknowledgement required before locally signing out. */
|
|
@@ -1448,13 +1450,12 @@ var AwikiConversationPreferenceStore = class {
|
|
|
1448
1450
|
};
|
|
1449
1451
|
//#endregion
|
|
1450
1452
|
//#region lib/types/listener-state.js
|
|
1451
|
-
const STATE_VERSION =
|
|
1453
|
+
const STATE_VERSION = 1;
|
|
1452
1454
|
const MAX_STATE_BYTES = 1048576;
|
|
1453
1455
|
const MAX_CONVERSATIONS = 1e3;
|
|
1454
|
-
function emptyState(
|
|
1456
|
+
function emptyState() {
|
|
1455
1457
|
return {
|
|
1456
1458
|
version: STATE_VERSION,
|
|
1457
|
-
identityScopeHash,
|
|
1458
1459
|
conversations: {}
|
|
1459
1460
|
};
|
|
1460
1461
|
}
|
|
@@ -1466,10 +1467,8 @@ function boundedString(value, field) {
|
|
|
1466
1467
|
return value;
|
|
1467
1468
|
}
|
|
1468
1469
|
/** Validate and detach the complete state file before the listener trusts any route. */
|
|
1469
|
-
function parseState(value
|
|
1470
|
-
if (isRecord(value)
|
|
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
|
+
function parseState(value) {
|
|
1471
|
+
if (!isRecord(value) || value.version !== STATE_VERSION || !isRecord(value.conversations)) throw new TypeError("awiki: listener state is invalid");
|
|
1473
1472
|
const entries = Object.entries(value.conversations);
|
|
1474
1473
|
if (entries.length > MAX_CONVERSATIONS) throw new TypeError("awiki: listener state is too large");
|
|
1475
1474
|
const conversations = {};
|
|
@@ -1492,7 +1491,6 @@ function parseState(value, identityScopeHash) {
|
|
|
1492
1491
|
}
|
|
1493
1492
|
return {
|
|
1494
1493
|
version: STATE_VERSION,
|
|
1495
|
-
identityScopeHash,
|
|
1496
1494
|
conversations
|
|
1497
1495
|
};
|
|
1498
1496
|
}
|
|
@@ -1503,31 +1501,28 @@ function isMissing(error) {
|
|
|
1503
1501
|
var AwikiListenerStateStore = class {
|
|
1504
1502
|
hostDirectory;
|
|
1505
1503
|
statePath;
|
|
1506
|
-
|
|
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");
|
|
1504
|
+
constructor(stateRoot) {
|
|
1510
1505
|
this.hostDirectory = join(stateRoot, ".host");
|
|
1511
1506
|
this.statePath = join(this.hostDirectory, "listener-state.json");
|
|
1512
1507
|
}
|
|
1513
|
-
/** Load the current
|
|
1508
|
+
/** Load the current state or return an empty v1 document on first use. */
|
|
1514
1509
|
async load() {
|
|
1515
|
-
if (!await this.hasPrivateHostDirectory()) return emptyState(
|
|
1510
|
+
if (!await this.hasPrivateHostDirectory()) return emptyState();
|
|
1516
1511
|
try {
|
|
1517
1512
|
const metadata = await lstat(this.statePath);
|
|
1518
1513
|
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is invalid");
|
|
1519
1514
|
const text = await readFile(this.statePath, "utf8");
|
|
1520
1515
|
if (Buffer.byteLength(text, "utf8") > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is invalid");
|
|
1521
|
-
return parseState(JSON.parse(text)
|
|
1516
|
+
return parseState(JSON.parse(text));
|
|
1522
1517
|
} catch (error) {
|
|
1523
|
-
if (isMissing(error)) return emptyState(
|
|
1518
|
+
if (isMissing(error)) return emptyState();
|
|
1524
1519
|
if (error instanceof SyntaxError) throw new TypeError("awiki: listener state file is invalid");
|
|
1525
1520
|
throw error;
|
|
1526
1521
|
}
|
|
1527
1522
|
}
|
|
1528
1523
|
/** Replace the state atomically without ever writing message or Agent text. */
|
|
1529
1524
|
async save(state) {
|
|
1530
|
-
const snapshot = parseState(state
|
|
1525
|
+
const snapshot = parseState(state);
|
|
1531
1526
|
const text = `${JSON.stringify(snapshot)}\n`;
|
|
1532
1527
|
if (Buffer.byteLength(text, "utf8") > MAX_STATE_BYTES) throw new TypeError("awiki: listener state file is too large");
|
|
1533
1528
|
await mkdir(this.hostDirectory, {
|
|
@@ -1749,12 +1744,11 @@ function incomingFromPeer(message, conversation) {
|
|
|
1749
1744
|
}
|
|
1750
1745
|
function copyState(state) {
|
|
1751
1746
|
return {
|
|
1752
|
-
version:
|
|
1753
|
-
identityScopeHash: state.identityScopeHash,
|
|
1747
|
+
version: 1,
|
|
1754
1748
|
conversations: Object.fromEntries(Object.entries(state.conversations).map(([id, route]) => [id, { ...route }]))
|
|
1755
1749
|
};
|
|
1756
1750
|
}
|
|
1757
|
-
/**
|
|
1751
|
+
/** Reconcile authorized Direct text from Core history whenever realtime schedules synchronization. */
|
|
1758
1752
|
var AwikiAgentListener = class {
|
|
1759
1753
|
awiki;
|
|
1760
1754
|
agents;
|
|
@@ -1763,23 +1757,34 @@ var AwikiAgentListener = class {
|
|
|
1763
1757
|
store;
|
|
1764
1758
|
logger;
|
|
1765
1759
|
stateReady;
|
|
1766
|
-
state
|
|
1760
|
+
state = {
|
|
1761
|
+
version: 1,
|
|
1762
|
+
conversations: {}
|
|
1763
|
+
};
|
|
1767
1764
|
stateMutation = Promise.resolve();
|
|
1768
1765
|
syncMutation = Promise.resolve();
|
|
1769
1766
|
scheduledMessageIds = /* @__PURE__ */ new Set();
|
|
1770
1767
|
conversationQueues = /* @__PURE__ */ new Map();
|
|
1768
|
+
lifecycle;
|
|
1769
|
+
started;
|
|
1770
|
+
resolveStarted;
|
|
1771
|
+
rejectStarted;
|
|
1772
|
+
lifecycleGeneration = 0;
|
|
1773
|
+
streamGeneration = 0;
|
|
1774
|
+
activeRealtime;
|
|
1771
1775
|
stopped = false;
|
|
1776
|
+
termination;
|
|
1777
|
+
resolveTermination;
|
|
1778
|
+
terminationSettled = false;
|
|
1772
1779
|
constructor(awiki, agents, config, logger, store) {
|
|
1773
1780
|
this.awiki = awiki;
|
|
1774
1781
|
this.agents = agents;
|
|
1775
1782
|
this.config = config;
|
|
1783
|
+
this.termination = new Promise((resolve) => {
|
|
1784
|
+
this.resolveTermination = resolve;
|
|
1785
|
+
});
|
|
1776
1786
|
this.allowedPeers = new Set(config.allowedPeers.map((peer) => peer.startsWith("did:") ? peer : peer.toLowerCase()));
|
|
1777
|
-
this.store = store ?? new AwikiListenerStateStore(config.stateRoot
|
|
1778
|
-
this.state = {
|
|
1779
|
-
version: 2,
|
|
1780
|
-
identityScopeHash: this.store.identityScopeHash,
|
|
1781
|
-
conversations: {}
|
|
1782
|
-
};
|
|
1787
|
+
this.store = store ?? new AwikiListenerStateStore(config.stateRoot);
|
|
1783
1788
|
this.logger = logger ?? {
|
|
1784
1789
|
debug() {},
|
|
1785
1790
|
info() {},
|
|
@@ -1791,11 +1796,38 @@ var AwikiAgentListener = class {
|
|
|
1791
1796
|
this.state = state;
|
|
1792
1797
|
});
|
|
1793
1798
|
}
|
|
1794
|
-
/**
|
|
1795
|
-
|
|
1799
|
+
/** Start canonical startup sync followed by the single Core-owned realtime stream. */
|
|
1800
|
+
start() {
|
|
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) {
|
|
1796
1826
|
await this.enqueueSync(async () => {
|
|
1797
1827
|
await this.stateReady;
|
|
1798
1828
|
if (this.stopped) return;
|
|
1829
|
+
await this.awiki.syncNow(reason);
|
|
1830
|
+
if (this.stopped) return;
|
|
1799
1831
|
await this.reconcileCommittedHistory();
|
|
1800
1832
|
});
|
|
1801
1833
|
}
|
|
@@ -1805,12 +1837,84 @@ var AwikiAgentListener = class {
|
|
|
1805
1837
|
while (this.conversationQueues.size > 0) await Promise.allSettled([...this.conversationQueues.values()]);
|
|
1806
1838
|
await this.stateMutation;
|
|
1807
1839
|
}
|
|
1808
|
-
/**
|
|
1840
|
+
/** Stop realtime first, fence late events, drain messages, then release listener-owned Agents. */
|
|
1809
1841
|
async dispose() {
|
|
1810
1842
|
if (this.stopped) return;
|
|
1811
1843
|
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;
|
|
1812
1853
|
await this.whenIdle();
|
|
1813
1854
|
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
|
+
});
|
|
1814
1918
|
}
|
|
1815
1919
|
enqueueSync(operation) {
|
|
1816
1920
|
const pending = this.syncMutation.then(operation, operation);
|
|
@@ -1977,8 +2081,7 @@ var AwikiAgentListener = class {
|
|
|
1977
2081
|
...update.lastProcessedMessageId === void 0 ? current?.lastProcessedMessageId === void 0 ? {} : { lastProcessedMessageId: current.lastProcessedMessageId } : { lastProcessedMessageId: update.lastProcessedMessageId }
|
|
1978
2082
|
};
|
|
1979
2083
|
this.state = {
|
|
1980
|
-
version:
|
|
1981
|
-
identityScopeHash: this.state.identityScopeHash,
|
|
2084
|
+
version: 1,
|
|
1982
2085
|
conversations: {
|
|
1983
2086
|
...this.state.conversations,
|
|
1984
2087
|
[conversation.id]: route
|
|
@@ -1991,153 +2094,39 @@ var AwikiAgentListener = class {
|
|
|
1991
2094
|
}
|
|
1992
2095
|
};
|
|
1993
2096
|
//#endregion
|
|
1994
|
-
//#region lib/types/
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
return cause === "reconnected" ? "websocket_reconnect" : "websocket_hint";
|
|
2097
|
+
//#region lib/types/profile-state.js
|
|
2098
|
+
/** Match the profile-name boundary enforced by the DSH launcher. */
|
|
2099
|
+
function assertProfileName(value, source) {
|
|
2100
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("/") || value.includes("\\") || value === "." || value === ".." || value === "node_modules") throw new TypeError(`awiki: ${source} supplied an invalid profile name`);
|
|
2101
|
+
return value;
|
|
2000
2102
|
}
|
|
2001
|
-
/**
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
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?.();
|
|
2103
|
+
/**
|
|
2104
|
+
* Resolve the active DSH profile without guessing it from argv or process type.
|
|
2105
|
+
* Desktop's generation-scoped service is authoritative. Ordinary DSH has no
|
|
2106
|
+
* corresponding service, so its Loader root is accepted only when it is the
|
|
2107
|
+
* exact `$DSH_HOME/profiles/<name>` directory.
|
|
2108
|
+
*/
|
|
2109
|
+
function resolveAwikiProfileName(ctx, dshHome) {
|
|
2110
|
+
const desktopProfiles = ctx.get("desktopProfiles");
|
|
2111
|
+
if (desktopProfiles !== void 0) return assertProfileName(desktopProfiles.current?.name, "desktopProfiles.current");
|
|
2112
|
+
let profileDir;
|
|
2113
|
+
try {
|
|
2114
|
+
if (ctx.baseUrl === void 0) return void 0;
|
|
2115
|
+
const baseUrl = new URL(ctx.baseUrl);
|
|
2116
|
+
if (baseUrl.protocol !== "file:") return void 0;
|
|
2117
|
+
profileDir = resolve(fileURLToPath(baseUrl));
|
|
2118
|
+
} catch {
|
|
2119
|
+
return;
|
|
2139
2120
|
}
|
|
2140
|
-
|
|
2121
|
+
const profilesDir = resolve(dshHome, "profiles");
|
|
2122
|
+
if (dirname(profileDir) !== profilesDir) return void 0;
|
|
2123
|
+
return assertProfileName(basename(profileDir), "Loader profile directory");
|
|
2124
|
+
}
|
|
2125
|
+
/** Resolve the profile-local default while preserving the legacy fallback. */
|
|
2126
|
+
function resolveAwikiStateRoot(ctx, dshHome) {
|
|
2127
|
+
const profileName = resolveAwikiProfileName(ctx, dshHome);
|
|
2128
|
+
return profileName === void 0 ? join(dshHome, "awiki", "im-core") : join(dshHome, "awiki", profileName, "im-core");
|
|
2129
|
+
}
|
|
2141
2130
|
//#endregion
|
|
2142
2131
|
//#region lib/types/index.js
|
|
2143
2132
|
/** Unified AWiki identity, messaging, attachment, Remote, and model-tool service. */
|
|
@@ -2209,7 +2198,6 @@ const Config = z.object({
|
|
|
2209
2198
|
attachmentMaxBytes: z.number().default(DEFAULT_ATTACHMENT_MAX_BYTES),
|
|
2210
2199
|
imageAttachmentCacheMaxBytes: z.number().default(DEFAULT_IMAGE_ATTACHMENT_CACHE_MAX_BYTES),
|
|
2211
2200
|
pollIntervalMs: z.number().default(DEFAULT_POLL_INTERVAL_MS),
|
|
2212
|
-
realtimeEnabled: z.boolean().default(true),
|
|
2213
2201
|
listenerEnabled: z.boolean().default(false),
|
|
2214
2202
|
listenerAllowedPeers: z.array(z.string()).default([]),
|
|
2215
2203
|
listenerWorkspacePath: z.string(),
|
|
@@ -2227,6 +2215,7 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
|
2227
2215
|
"forbidden",
|
|
2228
2216
|
"identity-recovery-required",
|
|
2229
2217
|
"conflict",
|
|
2218
|
+
"state-in-use",
|
|
2230
2219
|
"rate-limited",
|
|
2231
2220
|
"group-membership-required",
|
|
2232
2221
|
"group-identity-stale",
|
|
@@ -2240,17 +2229,10 @@ const FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
|
2240
2229
|
"network",
|
|
2241
2230
|
"remote"
|
|
2242
2231
|
]);
|
|
2243
|
-
const
|
|
2244
|
-
const
|
|
2245
|
-
const
|
|
2246
|
-
const
|
|
2247
|
-
"pending",
|
|
2248
|
-
"challenge_prepared",
|
|
2249
|
-
"response_prepared",
|
|
2250
|
-
"response_verified",
|
|
2251
|
-
"approval_prepared",
|
|
2252
|
-
"authorized"
|
|
2253
|
-
]);
|
|
2232
|
+
const LISTENER_RESTART_BASE_DELAY_MS = 1e3;
|
|
2233
|
+
const LISTENER_RESTART_MAX_DELAY_MS = 3e4;
|
|
2234
|
+
const LISTENER_RESTART_MAX_ATTEMPT = 6;
|
|
2235
|
+
const LISTENER_STABLE_RESET_MS = 6e4;
|
|
2254
2236
|
const FAILURE_MESSAGES = {
|
|
2255
2237
|
"not-registered": "No AWiki identity is registered for this deployment.",
|
|
2256
2238
|
"signed-out": "This installation is signed out of AWiki.",
|
|
@@ -2263,6 +2245,7 @@ const FAILURE_MESSAGES = {
|
|
|
2263
2245
|
"forbidden": "The AWiki operation is not permitted.",
|
|
2264
2246
|
"identity-recovery-required": "The local AWiki identity must be recovered before it can be used again.",
|
|
2265
2247
|
"conflict": "The AWiki operation conflicts with current state.",
|
|
2248
|
+
"state-in-use": "AWiki data is already open in another client.",
|
|
2266
2249
|
"rate-limited": "The AWiki service rate-limited the request.",
|
|
2267
2250
|
"group-membership-required": "The active AWiki identity is not a member of this group.",
|
|
2268
2251
|
"group-identity-stale": "The AWiki group identity binding is still recovering.",
|
|
@@ -2278,39 +2261,6 @@ const FAILURE_MESSAGES = {
|
|
|
2278
2261
|
};
|
|
2279
2262
|
var ProviderUnavailableError = class extends Error {};
|
|
2280
2263
|
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
|
-
}
|
|
2314
2264
|
/** Validate and preserve one SDK service URL without accepting insecure remote HTTP. */
|
|
2315
2265
|
function serviceUrl(field, raw, allowInsecureLoopbackForTesting) {
|
|
2316
2266
|
let url;
|
|
@@ -2332,22 +2282,6 @@ function recoveryReconciliationEndpoint(target, allowInsecureLoopbackForTesting)
|
|
|
2332
2282
|
if (parsed.search !== "") throw new TypeError("awiki: recovery reconciliation target must not contain a query");
|
|
2333
2283
|
return new URL("/api/identity-recovery", parsed).toString();
|
|
2334
2284
|
}
|
|
2335
|
-
/** Accept only the closed Model Proxy success response; no ledger identifier may cross back. */
|
|
2336
|
-
async function acceptsRecoveryReconciliation(response) {
|
|
2337
|
-
if (!response.ok) return false;
|
|
2338
|
-
let value;
|
|
2339
|
-
try {
|
|
2340
|
-
const text = await readBoundedResponseText(response, RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES);
|
|
2341
|
-
if (text === void 0) return false;
|
|
2342
|
-
value = JSON.parse(text);
|
|
2343
|
-
} catch {
|
|
2344
|
-
return false;
|
|
2345
|
-
}
|
|
2346
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2347
|
-
const result = value;
|
|
2348
|
-
const keys = Object.keys(result).sort();
|
|
2349
|
-
return keys.length === 2 && keys[0] === "idempotent" && keys[1] === "restored" && result.restored === true && typeof result.idempotent === "boolean";
|
|
2350
|
-
}
|
|
2351
2285
|
/** Validate a provider domain without inferring it from an API endpoint. */
|
|
2352
2286
|
function serviceDomain(raw, field = "userServiceDomain") {
|
|
2353
2287
|
return normalizeAwikiDomain(raw, field);
|
|
@@ -2381,12 +2315,14 @@ function listenerAllowedPeers(raw, enabled) {
|
|
|
2381
2315
|
return peers;
|
|
2382
2316
|
}
|
|
2383
2317
|
/** Resolve and validate every deployment choice before publishing the service. */
|
|
2384
|
-
function resolveConfig(config) {
|
|
2318
|
+
function resolveConfig(ctx, config) {
|
|
2385
2319
|
const allowInsecureLoopbackForTesting = config.allowInsecureLoopbackForTesting ?? false;
|
|
2386
2320
|
const configuredStateRoot = config.stateRoot?.trim();
|
|
2387
2321
|
const configuredDshHome = process.env.DSH_HOME?.trim();
|
|
2388
2322
|
const dshHome = configuredDshHome === void 0 || configuredDshHome.length === 0 ? join(homedir(), ".dsh") : configuredDshHome;
|
|
2389
|
-
const
|
|
2323
|
+
const profileName = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? resolveAwikiProfileName(ctx, dshHome) : void 0;
|
|
2324
|
+
const stateRoot = configuredStateRoot === void 0 || configuredStateRoot.length === 0 ? resolveAwikiStateRoot(ctx, dshHome) : configuredStateRoot;
|
|
2325
|
+
const legacySharedStateDetected = profileName !== void 0 && existsSync(join(dshHome, "awiki", "im-core"));
|
|
2390
2326
|
if (!isAbsolute(stateRoot)) throw new TypeError("awiki: stateRoot must be an absolute path");
|
|
2391
2327
|
const attachmentMaxBytes = config.attachmentMaxBytes ?? 10485760;
|
|
2392
2328
|
if (!Number.isSafeInteger(attachmentMaxBytes) || attachmentMaxBytes < 1) throw new TypeError("awiki: attachmentMaxBytes must be a positive safe integer");
|
|
@@ -2395,8 +2331,6 @@ function resolveConfig(config) {
|
|
|
2395
2331
|
const pollIntervalMs = config.pollIntervalMs ?? 3e3;
|
|
2396
2332
|
if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 1e3 || pollIntervalMs > 6e4) throw new TypeError("awiki: pollIntervalMs must be a safe integer from 1000 through 60000");
|
|
2397
2333
|
const listenerEnabled = config.listenerEnabled ?? false;
|
|
2398
|
-
const realtimeEnabled = config.realtimeEnabled ?? true;
|
|
2399
|
-
if (listenerEnabled && !realtimeEnabled) throw new TypeError("awiki: listenerEnabled requires realtimeEnabled");
|
|
2400
2334
|
const listenerWorkspacePath = config.listenerWorkspacePath?.trim() || join(dshHome, "workspaces", "awiki");
|
|
2401
2335
|
if (!isAbsolute(listenerWorkspacePath)) throw new TypeError("awiki: listenerWorkspacePath must be an absolute path");
|
|
2402
2336
|
const allowedPeers = listenerAllowedPeers(config.listenerAllowedPeers, listenerEnabled);
|
|
@@ -2419,7 +2353,10 @@ function resolveConfig(config) {
|
|
|
2419
2353
|
attachmentMaxBytes,
|
|
2420
2354
|
imageAttachmentCacheMaxBytes,
|
|
2421
2355
|
pollIntervalMs,
|
|
2422
|
-
|
|
2356
|
+
...profileName === void 0 ? {} : {
|
|
2357
|
+
profileName,
|
|
2358
|
+
legacySharedStateDetected
|
|
2359
|
+
},
|
|
2423
2360
|
listenerEnabled,
|
|
2424
2361
|
listener: {
|
|
2425
2362
|
allowedPeers,
|
|
@@ -2570,8 +2507,6 @@ function normalizeRecoveryPrepareRequest(request) {
|
|
|
2570
2507
|
};
|
|
2571
2508
|
}
|
|
2572
2509
|
const IDENTITY_ACCESS_RESPONSE_MAX_BYTES = 65536;
|
|
2573
|
-
const SERVER_INFO_RESPONSE_MAX_BYTES = 65536;
|
|
2574
|
-
const RECOVERY_RECONCILIATION_RESPONSE_MAX_BYTES = 4096;
|
|
2575
2510
|
/** Read one untrusted discovery response without buffering beyond the fixed Host limit. */
|
|
2576
2511
|
async function readBoundedResponseText(response, maxBytes) {
|
|
2577
2512
|
const declaredLength = response.headers.get("content-length");
|
|
@@ -2789,14 +2724,6 @@ let AwikiService = (() => {
|
|
|
2789
2724
|
let _inspectIdentityAccess_decorators;
|
|
2790
2725
|
let _sendRegistrationOtp_decorators;
|
|
2791
2726
|
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;
|
|
2800
2727
|
let _updateDisplayName_decorators;
|
|
2801
2728
|
let _getProfile_decorators;
|
|
2802
2729
|
let _updateProfile_decorators;
|
|
@@ -2842,14 +2769,6 @@ let AwikiService = (() => {
|
|
|
2842
2769
|
_inspectIdentityAccess_decorators = [Remote];
|
|
2843
2770
|
_sendRegistrationOtp_decorators = [Remote];
|
|
2844
2771
|
_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];
|
|
2853
2772
|
_updateDisplayName_decorators = [Remote];
|
|
2854
2773
|
_getProfile_decorators = [Remote];
|
|
2855
2774
|
_updateProfile_decorators = [Remote];
|
|
@@ -2972,94 +2891,6 @@ let AwikiService = (() => {
|
|
|
2972
2891
|
},
|
|
2973
2892
|
metadata: _metadata
|
|
2974
2893
|
}, 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);
|
|
3063
2894
|
__esDecorate(this, null, _updateDisplayName_decorators, {
|
|
3064
2895
|
kind: "method",
|
|
3065
2896
|
name: "updateDisplayName",
|
|
@@ -3455,12 +3286,6 @@ let AwikiService = (() => {
|
|
|
3455
3286
|
sessionMutation = Promise.resolve();
|
|
3456
3287
|
sessionRevision = 0;
|
|
3457
3288
|
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();
|
|
3464
3289
|
activeSummaryRequests = /* @__PURE__ */ new Set();
|
|
3465
3290
|
summaryProvider;
|
|
3466
3291
|
recoveryReconciliationTarget;
|
|
@@ -3475,7 +3300,7 @@ let AwikiService = (() => {
|
|
|
3475
3300
|
constructor(ctx, config) {
|
|
3476
3301
|
super(ctx, "awiki");
|
|
3477
3302
|
this.hostContext = ctx;
|
|
3478
|
-
this.resolved = resolveConfig(config);
|
|
3303
|
+
this.resolved = resolveConfig(ctx, config);
|
|
3479
3304
|
this.externalHttpAuth = createAwikiExternalHttpAuth(() => this.acquireExternalHttpAuthSession());
|
|
3480
3305
|
this.sessionStore = new AwikiSessionStore(this.resolved.stateRoot);
|
|
3481
3306
|
this.imageAttachmentCache = new AwikiImageAttachmentCache(this.resolved.stateRoot, this.resolved.attachmentMaxBytes, this.resolved.imageAttachmentCacheMaxBytes);
|
|
@@ -3504,12 +3329,12 @@ let AwikiService = (() => {
|
|
|
3504
3329
|
ctx.inject(["workspaceRegistry"], (workspaceCtx) => {
|
|
3505
3330
|
this.workspaceContext = workspaceCtx;
|
|
3506
3331
|
const provider = this.provider;
|
|
3507
|
-
if (provider !== void 0) this.
|
|
3332
|
+
if (provider !== void 0) this.startListener(provider);
|
|
3508
3333
|
workspaceCtx.effect(() => async () => {
|
|
3509
3334
|
if (this.workspaceContext !== workspaceCtx) return;
|
|
3510
3335
|
this.workspaceContext = void 0;
|
|
3511
3336
|
const current = this.provider;
|
|
3512
|
-
if (current !== void 0) await this.
|
|
3337
|
+
if (current !== void 0) await this.stopListener(current);
|
|
3513
3338
|
}, "awiki: release Workspace listener composition");
|
|
3514
3339
|
});
|
|
3515
3340
|
registerAwikiTools(ctx, this);
|
|
@@ -3530,8 +3355,6 @@ let AwikiService = (() => {
|
|
|
3530
3355
|
*/
|
|
3531
3356
|
registerClientFactory(factory) {
|
|
3532
3357
|
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;
|
|
3535
3358
|
const provider = {
|
|
3536
3359
|
client: factory({
|
|
3537
3360
|
userServiceUrl: this.resolved.userServiceUrl,
|
|
@@ -3545,28 +3368,14 @@ let AwikiService = (() => {
|
|
|
3545
3368
|
allowInsecureLoopbackForTesting: this.resolved.allowInsecureLoopbackForTesting,
|
|
3546
3369
|
stateRoot: this.resolved.stateRoot
|
|
3547
3370
|
}),
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3371
|
+
listenerRestartAttempt: 0,
|
|
3372
|
+
listenerGeneration: 0,
|
|
3373
|
+
listenerRecoveryBlocked: false
|
|
3551
3374
|
};
|
|
3552
3375
|
this.provider = provider;
|
|
3553
|
-
this.
|
|
3554
|
-
this.ensureAgentConsumer(provider);
|
|
3376
|
+
this.startListener(provider);
|
|
3555
3377
|
return () => this.disposeProvider(provider);
|
|
3556
3378
|
}
|
|
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
|
-
}
|
|
3570
3379
|
/** Register the optional Model Proxy recovery target without exposing an arbitrary callback or token. */
|
|
3571
3380
|
registerRecoveryReconciliationTarget(target) {
|
|
3572
3381
|
if (this.recoveryReconciliationTarget !== void 0) throw new Error("awiki: a recovery reconciliation target is already registered");
|
|
@@ -3594,42 +3403,18 @@ let AwikiService = (() => {
|
|
|
3594
3403
|
* Read settings needed by the browser presentation.
|
|
3595
3404
|
* @returns Browser-safe polling configuration without SDK endpoints or state paths.
|
|
3596
3405
|
*/
|
|
3597
|
-
|
|
3598
|
-
|
|
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 {
|
|
3406
|
+
getConfig() {
|
|
3407
|
+
return Promise.resolve({
|
|
3626
3408
|
ok: true,
|
|
3627
3409
|
value: {
|
|
3628
3410
|
pollIntervalMs: this.resolved.pollIntervalMs,
|
|
3629
3411
|
attachmentMaxBytes: this.resolved.attachmentMaxBytes,
|
|
3630
|
-
|
|
3412
|
+
...this.resolved.profileName === void 0 ? {} : {
|
|
3413
|
+
profileName: this.resolved.profileName,
|
|
3414
|
+
legacySharedStateDetected: this.resolved.legacySharedStateDetected
|
|
3415
|
+
}
|
|
3631
3416
|
}
|
|
3632
|
-
};
|
|
3417
|
+
});
|
|
3633
3418
|
}
|
|
3634
3419
|
/**
|
|
3635
3420
|
* Read the deployment's identity status.
|
|
@@ -3652,8 +3437,6 @@ let AwikiService = (() => {
|
|
|
3652
3437
|
const identity = await this.run((client) => client.getIdentity(), { allowSignedOut: true });
|
|
3653
3438
|
if (!identity.ok) return identity;
|
|
3654
3439
|
this.activeIdentityDid = identity.value?.did;
|
|
3655
|
-
const provider = this.provider;
|
|
3656
|
-
if (identity.value !== null && provider !== void 0) this.ensureProviderRuntime(provider);
|
|
3657
3440
|
return identity.value === null ? {
|
|
3658
3441
|
ok: true,
|
|
3659
3442
|
value: { status: "unregistered" }
|
|
@@ -3690,7 +3473,7 @@ let AwikiService = (() => {
|
|
|
3690
3473
|
const session = { status: "signed-out" };
|
|
3691
3474
|
this.publishSession(session);
|
|
3692
3475
|
const provider = this.provider;
|
|
3693
|
-
if (provider !== void 0) await this.
|
|
3476
|
+
if (provider !== void 0) await this.stopListener(provider);
|
|
3694
3477
|
return {
|
|
3695
3478
|
ok: true,
|
|
3696
3479
|
value: session
|
|
@@ -3723,7 +3506,7 @@ let AwikiService = (() => {
|
|
|
3723
3506
|
};
|
|
3724
3507
|
this.publishSession(session);
|
|
3725
3508
|
const provider = this.provider;
|
|
3726
|
-
if (provider !== void 0) this.
|
|
3509
|
+
if (provider !== void 0) this.startListener(provider);
|
|
3727
3510
|
return {
|
|
3728
3511
|
ok: true,
|
|
3729
3512
|
value: session
|
|
@@ -3809,15 +3592,8 @@ let AwikiService = (() => {
|
|
|
3809
3592
|
};
|
|
3810
3593
|
}
|
|
3811
3594
|
}
|
|
3812
|
-
|
|
3813
|
-
this.
|
|
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
|
-
});
|
|
3595
|
+
sendRegistrationOtp(request) {
|
|
3596
|
+
return this.run((client) => client.sendRegistrationOtp(request));
|
|
3821
3597
|
}
|
|
3822
3598
|
/**
|
|
3823
3599
|
* Register and persist the deployment's only AWiki identity.
|
|
@@ -3825,190 +3601,9 @@ let AwikiService = (() => {
|
|
|
3825
3601
|
* @returns The new public identity or a closed failure.
|
|
3826
3602
|
*/
|
|
3827
3603
|
async registerIdentity(request) {
|
|
3828
|
-
this.
|
|
3829
|
-
|
|
3830
|
-
|
|
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
|
-
});
|
|
3604
|
+
const result = await this.run((client) => client.registerIdentity(request));
|
|
3605
|
+
if (result.ok) await this.activateRegisteredIdentity(result.value);
|
|
3606
|
+
return result;
|
|
4012
3607
|
}
|
|
4013
3608
|
/**
|
|
4014
3609
|
* Update the deployment identity's public WNS display name.
|
|
@@ -4035,19 +3630,12 @@ let AwikiService = (() => {
|
|
|
4035
3630
|
}
|
|
4036
3631
|
/** Start durable phone recovery for one existing full Handle. */
|
|
4037
3632
|
sendRecoveryOtp(request) {
|
|
4038
|
-
this.pendingDeviceJoin = void 0;
|
|
4039
3633
|
const normalized = normalizeRecoveryOtpRequest(request);
|
|
4040
3634
|
if (normalized === void 0) return Promise.resolve({
|
|
4041
3635
|
ok: false,
|
|
4042
3636
|
error: failure("invalid-request")
|
|
4043
3637
|
});
|
|
4044
|
-
return this.run(
|
|
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 });
|
|
3638
|
+
return this.run((client) => client.sendRecoveryOtp(normalized), { allowSignedOut: true });
|
|
4051
3639
|
}
|
|
4052
3640
|
/** Verify a recovery OTP and freeze its exact intent before the remote commit. */
|
|
4053
3641
|
prepareRecovery(request) {
|
|
@@ -4559,10 +4147,10 @@ let AwikiService = (() => {
|
|
|
4559
4147
|
});
|
|
4560
4148
|
return this.mutateSession(async () => {
|
|
4561
4149
|
const provider = this.provider;
|
|
4562
|
-
if (provider !== void 0) await this.
|
|
4150
|
+
if (provider !== void 0) await this.stopListener(provider);
|
|
4563
4151
|
const result = await this.run((client) => client.clearLocalData(), { allowSignedOut: true });
|
|
4564
4152
|
if (!result.ok) {
|
|
4565
|
-
if (provider !== void 0 && this.provider === provider) this.
|
|
4153
|
+
if (provider !== void 0 && this.provider === provider) this.startListener(provider);
|
|
4566
4154
|
return result;
|
|
4567
4155
|
}
|
|
4568
4156
|
try {
|
|
@@ -4572,10 +4160,9 @@ let AwikiService = (() => {
|
|
|
4572
4160
|
await this.sessionStore.signIn();
|
|
4573
4161
|
this.signedOut = false;
|
|
4574
4162
|
this.activeIdentityDid = void 0;
|
|
4575
|
-
this.pendingDeviceJoin = void 0;
|
|
4576
|
-
this.activeDeviceJoinSessionId = void 0;
|
|
4577
4163
|
this.invalidateSummaries();
|
|
4578
4164
|
this.publishSession({ status: "unregistered" });
|
|
4165
|
+
if (provider !== void 0 && this.provider === provider) this.startListener(provider);
|
|
4579
4166
|
return result;
|
|
4580
4167
|
} catch {
|
|
4581
4168
|
return {
|
|
@@ -4585,7 +4172,7 @@ let AwikiService = (() => {
|
|
|
4585
4172
|
}
|
|
4586
4173
|
});
|
|
4587
4174
|
}
|
|
4588
|
-
/** Re-enter
|
|
4175
|
+
/** Re-enter after Core applies the recovered identity without blocking on optional account restoration. */
|
|
4589
4176
|
async applyRecoveredSession(progress) {
|
|
4590
4177
|
if (progress.phase !== "applied") return true;
|
|
4591
4178
|
return this.mutateSession(async () => {
|
|
@@ -4601,7 +4188,6 @@ let AwikiService = (() => {
|
|
|
4601
4188
|
this.activeIdentityDid = identity.did;
|
|
4602
4189
|
this.invalidateSummaries();
|
|
4603
4190
|
}
|
|
4604
|
-
const reconciled = await this.reconcileRecoveredIdentity(provider, progress.operationId);
|
|
4605
4191
|
if (this.provider !== provider) return false;
|
|
4606
4192
|
if (!alreadyActive) {
|
|
4607
4193
|
const session = {
|
|
@@ -4609,195 +4195,31 @@ let AwikiService = (() => {
|
|
|
4609
4195
|
identity
|
|
4610
4196
|
};
|
|
4611
4197
|
this.publishSession(session);
|
|
4612
|
-
|
|
4198
|
+
await provider.listenerStartup;
|
|
4199
|
+
await this.startListener(provider);
|
|
4613
4200
|
}
|
|
4614
|
-
return
|
|
4201
|
+
return true;
|
|
4615
4202
|
} catch {
|
|
4616
4203
|
return false;
|
|
4617
4204
|
}
|
|
4618
4205
|
});
|
|
4619
4206
|
}
|
|
4620
|
-
/**
|
|
4621
|
-
async reconcileRecoveredIdentity(provider, operationId) {
|
|
4622
|
-
const logger = this.ctx.logger("awiki-recovery");
|
|
4623
|
-
let mailboxRestored = false;
|
|
4624
|
-
try {
|
|
4625
|
-
await provider.client.getMailAccount();
|
|
4626
|
-
mailboxRestored = true;
|
|
4627
|
-
} catch {
|
|
4628
|
-
logger.warn("awiki: recovered mailbox reconciliation is pending");
|
|
4629
|
-
}
|
|
4630
|
-
const target = this.recoveryReconciliationTarget;
|
|
4631
|
-
if (target === void 0) return mailboxRestored;
|
|
4632
|
-
try {
|
|
4633
|
-
const authority = await provider.client.issueRecoveryAttestation({ operationId });
|
|
4634
|
-
if (!await acceptsRecoveryReconciliation(await this.externalHttpAuth.dispatch(new Request(target.endpoint, {
|
|
4635
|
-
method: "POST",
|
|
4636
|
-
headers: { "content-type": "application/json" },
|
|
4637
|
-
body: JSON.stringify({ attestation: authority.attestation })
|
|
4638
|
-
}), (request) => fetch(request)))) {
|
|
4639
|
-
logger.warn("awiki: recovered model account reconciliation is pending");
|
|
4640
|
-
return false;
|
|
4641
|
-
}
|
|
4642
|
-
return mailboxRestored;
|
|
4643
|
-
} catch {
|
|
4644
|
-
logger.warn("awiki: recovered model account reconciliation is pending");
|
|
4645
|
-
return false;
|
|
4646
|
-
}
|
|
4647
|
-
}
|
|
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. */
|
|
4207
|
+
/** Publish one newly registered identity and start its listener through the existing session path. */
|
|
4783
4208
|
async activateRegisteredIdentity(identity) {
|
|
4784
|
-
this.pendingDeviceJoin = void 0;
|
|
4785
|
-
this.activeDeviceJoinSessionId = void 0;
|
|
4786
4209
|
this.activeIdentityDid = identity.did;
|
|
4787
4210
|
this.publishSession({
|
|
4788
4211
|
status: "active",
|
|
4789
4212
|
identity
|
|
4790
4213
|
});
|
|
4791
4214
|
const provider = this.provider;
|
|
4792
|
-
if (provider !== void 0)
|
|
4215
|
+
if (provider !== void 0) {
|
|
4216
|
+
await provider.listenerStartup;
|
|
4217
|
+
await this.startListener(provider);
|
|
4218
|
+
}
|
|
4793
4219
|
}
|
|
4794
4220
|
/** Invalidate cached session work and cancel every model request still owned by the old session. */
|
|
4795
4221
|
invalidateSummaries() {
|
|
4796
4222
|
this.sessionRevision += 1;
|
|
4797
|
-
this.requestRefs.clear();
|
|
4798
|
-
this.requestSessions.clear();
|
|
4799
|
-
this.deviceRefs.clear();
|
|
4800
|
-
this.deviceIds.clear();
|
|
4801
4223
|
for (const controller of this.activeSummaryRequests) controller.abort();
|
|
4802
4224
|
this.activeSummaryRequests.clear();
|
|
4803
4225
|
}
|
|
@@ -4897,148 +4319,120 @@ let AwikiService = (() => {
|
|
|
4897
4319
|
this.sessionMutation = pending.then(() => void 0, () => void 0);
|
|
4898
4320
|
return pending;
|
|
4899
4321
|
}
|
|
4900
|
-
/** Start
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
if (provider.
|
|
4904
|
-
if (
|
|
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");
|
|
4932
|
-
const observed = (async () => {
|
|
4933
|
-
if (await this.isSignedOut()) return;
|
|
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);
|
|
4942
|
-
})().catch((error) => {
|
|
4943
|
-
logger.warn("AWiki realtime startup failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
4944
|
-
}).finally(() => {
|
|
4945
|
-
if (provider.realtimeStartup === observed) delete provider.realtimeStartup;
|
|
4946
|
-
});
|
|
4947
|
-
provider.realtimeStartup = observed;
|
|
4948
|
-
}
|
|
4949
|
-
realtimeFenceMatches(provider, generation) {
|
|
4950
|
-
return this.provider === provider && provider.realtimeGeneration === generation;
|
|
4951
|
-
}
|
|
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) {
|
|
4322
|
+
/** Start one exact identity-bound listener, atomically releasing a failed startup. */
|
|
4323
|
+
startListener(provider) {
|
|
4324
|
+
if (!this.resolved.listenerEnabled || provider.listener !== void 0 || provider.listenerRecoveryBlocked || this.provider !== provider) return Promise.resolve();
|
|
4325
|
+
if (provider.listenerStartup !== void 0) return provider.listenerStartup;
|
|
4326
|
+
if (provider.listenerCleanup !== void 0 || provider.listenerRestartTimer !== void 0) return Promise.resolve();
|
|
4972
4327
|
const workspaceContext = this.workspaceContext;
|
|
4973
|
-
const source = provider.client.
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
if (provider.agentConsumer !== void 0 && provider.agentConsumerIdentityDid === identityDid) return;
|
|
4977
|
-
if (provider.agentConsumerStartup !== void 0) return;
|
|
4978
|
-
const generation = ++provider.agentConsumerGeneration;
|
|
4328
|
+
const source = provider.client.listener;
|
|
4329
|
+
if (workspaceContext === void 0 || source === void 0) return Promise.resolve();
|
|
4330
|
+
const generation = provider.listenerGeneration;
|
|
4979
4331
|
const logger = this.ctx.logger("awiki-listener");
|
|
4980
4332
|
const observed = (async () => {
|
|
4981
|
-
await this.
|
|
4982
|
-
if (
|
|
4333
|
+
if (await this.isSignedOut()) return;
|
|
4334
|
+
if (await provider.client.getIdentity() === null) return;
|
|
4335
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) return;
|
|
4983
4336
|
const agents = new DshAwikiListenerAgentRuntime(workspaceContext, this.resolved.listener.workspacePath);
|
|
4984
|
-
const
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4337
|
+
const listener = new AwikiAgentListener(source, agents, this.resolved.listener, logger);
|
|
4338
|
+
provider.listener = listener;
|
|
4339
|
+
try {
|
|
4340
|
+
await listener.start();
|
|
4341
|
+
} catch (error) {
|
|
4342
|
+
if (provider.listener === listener) delete provider.listener;
|
|
4343
|
+
try {
|
|
4344
|
+
await listener.dispose();
|
|
4345
|
+
} catch {
|
|
4346
|
+
provider.listenerRecoveryBlocked = true;
|
|
4347
|
+
}
|
|
4348
|
+
if (!provider.listenerRecoveryBlocked && provider.listenerRestartAttempt > 0) this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
4349
|
+
throw error;
|
|
4350
|
+
}
|
|
4351
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) {
|
|
4352
|
+
if (provider.listener === listener) delete provider.listener;
|
|
4353
|
+
try {
|
|
4354
|
+
await listener.dispose();
|
|
4355
|
+
} catch (error) {
|
|
4356
|
+
provider.listenerRecoveryBlocked = true;
|
|
4357
|
+
throw error;
|
|
4358
|
+
}
|
|
4359
|
+
return;
|
|
4360
|
+
}
|
|
4361
|
+
provider.listenerStartedAt = Date.now();
|
|
4362
|
+
listener.whenTerminated().then((result) => {
|
|
4363
|
+
if (result.kind === "failed") this.releaseFailedListener(provider, listener, workspaceContext, generation, result.error);
|
|
4364
|
+
});
|
|
4991
4365
|
})().catch((error) => {
|
|
4992
|
-
logger.warn("AWiki
|
|
4366
|
+
logger.warn("AWiki listener startup failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
4993
4367
|
}).finally(() => {
|
|
4994
|
-
if (provider.
|
|
4368
|
+
if (provider.listenerStartup === observed) delete provider.listenerStartup;
|
|
4995
4369
|
});
|
|
4996
|
-
provider.
|
|
4370
|
+
provider.listenerStartup = observed;
|
|
4371
|
+
return observed;
|
|
4372
|
+
}
|
|
4373
|
+
async stopListener(provider) {
|
|
4374
|
+
provider.listenerGeneration += 1;
|
|
4375
|
+
provider.listenerRestartAttempt = 0;
|
|
4376
|
+
delete provider.listenerStartedAt;
|
|
4377
|
+
const timer = provider.listenerRestartTimer;
|
|
4378
|
+
if (timer !== void 0) {
|
|
4379
|
+
delete provider.listenerRestartTimer;
|
|
4380
|
+
clearTimeout(timer);
|
|
4381
|
+
}
|
|
4382
|
+
await provider.listenerStartup;
|
|
4383
|
+
await provider.listenerCleanup;
|
|
4384
|
+
const listener = provider.listener;
|
|
4385
|
+
if (listener === void 0) return;
|
|
4386
|
+
delete provider.listener;
|
|
4387
|
+
try {
|
|
4388
|
+
await listener.dispose();
|
|
4389
|
+
} catch (error) {
|
|
4390
|
+
provider.listenerRecoveryBlocked = true;
|
|
4391
|
+
throw error;
|
|
4392
|
+
}
|
|
4997
4393
|
}
|
|
4998
|
-
|
|
4999
|
-
return this.provider === provider && this.workspaceContext === workspaceContext &&
|
|
4394
|
+
listenerFenceMatches(provider, workspaceContext, generation) {
|
|
4395
|
+
return this.provider === provider && this.workspaceContext === workspaceContext && provider.listenerGeneration === generation;
|
|
5000
4396
|
}
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
delete provider.
|
|
5006
|
-
|
|
5007
|
-
|
|
4397
|
+
releaseFailedListener(provider, listener, workspaceContext, generation, error) {
|
|
4398
|
+
if (provider.listener !== listener) return;
|
|
4399
|
+
delete provider.listener;
|
|
4400
|
+
const startedAt = provider.listenerStartedAt;
|
|
4401
|
+
delete provider.listenerStartedAt;
|
|
4402
|
+
if (startedAt !== void 0 && Date.now() - startedAt >= LISTENER_STABLE_RESET_MS) provider.listenerRestartAttempt = 0;
|
|
4403
|
+
const logger = this.ctx.logger("awiki-listener");
|
|
4404
|
+
logger.warn("AWiki listener lifecycle failed: %s", error instanceof Error ? error.message : "unknown failure");
|
|
4405
|
+
let disposed = false;
|
|
4406
|
+
const observed = listener.dispose().then(() => {
|
|
4407
|
+
disposed = true;
|
|
4408
|
+
}, (cleanupError) => {
|
|
4409
|
+
provider.listenerRecoveryBlocked = true;
|
|
4410
|
+
logger.warn("AWiki listener cleanup failed: %s", cleanupError instanceof Error ? cleanupError.message : "unknown failure");
|
|
4411
|
+
}).finally(() => {
|
|
4412
|
+
if (provider.listenerCleanup === observed) delete provider.listenerCleanup;
|
|
4413
|
+
if (disposed) this.scheduleListenerRestart(provider, workspaceContext, generation);
|
|
5008
4414
|
});
|
|
5009
|
-
provider.
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
provider.
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
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)]);
|
|
4415
|
+
provider.listenerCleanup = observed;
|
|
4416
|
+
}
|
|
4417
|
+
scheduleListenerRestart(provider, workspaceContext, generation) {
|
|
4418
|
+
if (provider.listenerRestartTimer !== void 0 || !this.listenerFenceMatches(provider, workspaceContext, generation)) return;
|
|
4419
|
+
provider.listenerRestartAttempt = Math.min(provider.listenerRestartAttempt + 1, LISTENER_RESTART_MAX_ATTEMPT);
|
|
4420
|
+
const exponent = provider.listenerRestartAttempt - 1;
|
|
4421
|
+
const delay = Math.min(LISTENER_RESTART_BASE_DELAY_MS * 2 ** exponent, LISTENER_RESTART_MAX_DELAY_MS);
|
|
4422
|
+
const timer = setTimeout(() => {
|
|
4423
|
+
if (provider.listenerRestartTimer !== timer) return;
|
|
4424
|
+
delete provider.listenerRestartTimer;
|
|
4425
|
+
if (!this.listenerFenceMatches(provider, workspaceContext, generation)) return;
|
|
4426
|
+
this.startListener(provider);
|
|
4427
|
+
}, delay);
|
|
4428
|
+
provider.listenerRestartTimer = timer;
|
|
5029
4429
|
}
|
|
5030
4430
|
/** Clear one exact provider slot before joining its one shared disposal. */
|
|
5031
4431
|
disposeProvider(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
|
-
}
|
|
4432
|
+
if (this.provider === provider) this.provider = void 0;
|
|
5038
4433
|
provider.disposal ??= (async () => {
|
|
5039
4434
|
try {
|
|
5040
|
-
await provider
|
|
5041
|
-
await this.stopProviderRuntime(provider);
|
|
4435
|
+
await this.stopListener(provider);
|
|
5042
4436
|
} finally {
|
|
5043
4437
|
await provider.client.dispose();
|
|
5044
4438
|
}
|