@senguoyun/dsh-arkme 0.1.74 → 0.1.75
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/docs/desktop-screenshot.md +75 -28
- package/lib/bundle-runtime.js +1 -1
- package/lib/client.js +2511 -1405
- package/lib/client.js.map +1 -1
- package/lib/extension-profile-restart-helper.js +5 -5
- package/lib/harness-model-client.js +86 -4
- package/lib/harness-native-selection-client.js +62 -0
- package/lib/harness-session-client.js +109 -6
- package/lib/harness-sidebar-client.js +2055 -6
- package/lib/index.js +4495 -1501
- package/lib/{install-store-bnyQDtYt.js → install-store-CAljxSo2.js} +2 -2
- package/lib/persistent-extension.js +1 -1
- package/lib/{plugin-updater-helper-Ny1szQA0.js → plugin-updater-helper-BWIKYugE.js} +5 -5
- package/lib/{plugin-updater-helper-D43MAnTY.js → plugin-updater-helper-DVcpMGBt.js} +1 -1
- package/lib/plugin-updater-helper.js +1 -1
- package/lib/{profile-bundle-policy-DLXDyohJ.js → profile-bundle-policy-Cwi2iuHi.js} +3 -3
- package/lib/sdk.js +62 -0
- package/lib/types/client/ArkmeArkoSurface.d.ts.map +1 -1
- package/lib/types/client/ArkmeComposerScreenshotButton.d.ts.map +1 -1
- package/lib/types/client/ArkmeScreenshotCropDialog.d.ts +2 -2
- package/lib/types/client/ArkmeScreenshotCropDialog.d.ts.map +1 -1
- package/lib/types/client/ArkmeScreenshotEditor.d.ts +15 -0
- package/lib/types/client/ArkmeScreenshotEditor.d.ts.map +1 -0
- package/lib/types/client/ArkmeScreenshotShortcutSetting.d.ts +2 -0
- package/lib/types/client/ArkmeScreenshotShortcutSetting.d.ts.map +1 -0
- package/lib/types/client/ArkmeScreenshotTooltip.d.ts +8 -0
- package/lib/types/client/ArkmeScreenshotTooltip.d.ts.map +1 -0
- package/lib/types/client/ArkmeScreenshotWindow.d.ts +2 -0
- package/lib/types/client/ArkmeScreenshotWindow.d.ts.map +1 -0
- package/lib/types/client/ArkmeSettingsSurface.d.ts.map +1 -1
- package/lib/types/client/ArkoModelMenu.d.ts +10 -0
- package/lib/types/client/ArkoModelMenu.d.ts.map +1 -0
- package/lib/types/client/DeepSeekHarnessSurface.d.ts +4 -2
- package/lib/types/client/DeepSeekHarnessSurface.d.ts.map +1 -1
- package/lib/types/client/arko-model-cache.d.ts +21 -0
- package/lib/types/client/arko-model-cache.d.ts.map +1 -0
- package/lib/types/client/harness-account-sessions.d.ts +68 -0
- package/lib/types/client/harness-account-sessions.d.ts.map +1 -0
- package/lib/types/client/harness-model-client.d.ts.map +1 -1
- package/lib/types/client/harness-native-navigation.d.ts +12 -0
- package/lib/types/client/harness-native-navigation.d.ts.map +1 -0
- package/lib/types/client/harness-session-client.d.ts +17 -1
- package/lib/types/client/harness-session-client.d.ts.map +1 -1
- package/lib/types/client/harness-session-dropdown.d.ts.map +1 -1
- package/lib/types/client/harness-session-menu-bridge.d.ts +1 -0
- package/lib/types/client/harness-session-menu-bridge.d.ts.map +1 -1
- package/lib/types/client/harness-session-origin.d.ts +8 -0
- package/lib/types/client/harness-session-origin.d.ts.map +1 -0
- package/lib/types/client/harness-sidebar-client.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/native-screenshot.d.ts +25 -0
- package/lib/types/client/native-screenshot.d.ts.map +1 -0
- package/lib/types/client/screenshot-editor-icons.d.ts +6 -0
- package/lib/types/client/screenshot-editor-icons.d.ts.map +1 -0
- package/lib/types/client/screenshot-editor-model.d.ts +40 -0
- package/lib/types/client/screenshot-editor-model.d.ts.map +1 -0
- package/lib/types/client/screenshot-editor-render.d.ts +4 -0
- package/lib/types/client/screenshot-editor-render.d.ts.map +1 -0
- package/lib/types/client/screenshot-shortcut.d.ts +17 -0
- package/lib/types/client/screenshot-shortcut.d.ts.map +1 -0
- package/lib/types/dsh-remote/account-session-observer.d.ts +9 -0
- package/lib/types/dsh-remote/account-session-observer.d.ts.map +1 -0
- package/lib/types/dsh-remote/account-session-tools.d.ts +3 -0
- package/lib/types/dsh-remote/account-session-tools.d.ts.map +1 -0
- package/lib/types/dsh-remote/account-session-types.d.ts +71 -0
- package/lib/types/dsh-remote/account-session-types.d.ts.map +1 -0
- package/lib/types/dsh-remote/account-sessions.d.ts +36 -0
- package/lib/types/dsh-remote/account-sessions.d.ts.map +1 -0
- package/lib/types/dsh-remote/api-proxy-adapter.d.ts +28 -0
- package/lib/types/dsh-remote/api-proxy-adapter.d.ts.map +1 -1
- package/lib/types/dsh-remote/channel-manager.d.ts +3 -0
- package/lib/types/dsh-remote/channel-manager.d.ts.map +1 -1
- package/lib/types/dsh-remote/cloud-native-transport.d.ts +22 -0
- package/lib/types/dsh-remote/cloud-native-transport.d.ts.map +1 -0
- package/lib/types/dsh-remote/cloud-turn-history.d.ts +21 -0
- package/lib/types/dsh-remote/cloud-turn-history.d.ts.map +1 -0
- package/lib/types/dsh-remote/connection-diagnostics.d.ts +5 -0
- package/lib/types/dsh-remote/connection-diagnostics.d.ts.map +1 -1
- package/lib/types/dsh-remote/control-plane.d.ts +1 -0
- package/lib/types/dsh-remote/control-plane.d.ts.map +1 -1
- package/lib/types/dsh-remote/desktop-session-presence.d.ts +4 -1
- package/lib/types/dsh-remote/desktop-session-presence.d.ts.map +1 -1
- package/lib/types/dsh-remote/gateway-api.d.ts.map +1 -1
- package/lib/types/dsh-remote/host.d.ts +7 -1
- package/lib/types/dsh-remote/host.d.ts.map +1 -1
- package/lib/types/dsh-remote/native-history-cache.d.ts +12 -0
- package/lib/types/dsh-remote/native-history-cache.d.ts.map +1 -0
- package/lib/types/dsh-remote/native-history.d.ts +18 -0
- package/lib/types/dsh-remote/native-history.d.ts.map +1 -0
- package/lib/types/dsh-remote/native-socket.d.ts +17 -0
- package/lib/types/dsh-remote/native-socket.d.ts.map +1 -0
- package/lib/types/dsh-remote/native-transport.d.ts +29 -0
- package/lib/types/dsh-remote/native-transport.d.ts.map +1 -0
- package/lib/types/dsh-remote/protocol-v1.d.ts.map +1 -1
- package/lib/types/dsh-remote/realtime-transport.d.ts +3 -0
- package/lib/types/dsh-remote/realtime-transport.d.ts.map +1 -1
- package/lib/types/dsh-remote/runtime-secret-broker.d.ts +1 -0
- package/lib/types/dsh-remote/runtime-secret-broker.d.ts.map +1 -1
- package/lib/types/dsh-remote/transport-fragment.d.ts +5 -0
- package/lib/types/dsh-remote/transport-fragment.d.ts.map +1 -1
- package/lib/types/dsh-remote/turn-projector.d.ts +2 -1
- package/lib/types/dsh-remote/turn-projector.d.ts.map +1 -1
- package/lib/types/dsh-remote/turn-upload-outbox.d.ts +6 -3
- package/lib/types/dsh-remote/turn-upload-outbox.d.ts.map +1 -1
- package/lib/types/dsh-remote/types.d.ts +6 -3
- package/lib/types/dsh-remote/types.d.ts.map +1 -1
- package/lib/types/harness-embed-route.d.ts.map +1 -1
- package/lib/types/harness-native-transport-script.d.ts +11 -0
- package/lib/types/harness-native-transport-script.d.ts.map +1 -0
- package/lib/types/harness-session-restore-script.d.ts +1 -1
- package/lib/types/harness-session-restore-script.d.ts.map +1 -1
- package/lib/types/host-api.d.ts +3 -1
- package/lib/types/host-api.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/sdk/index.d.ts +34 -0
- package/lib/types/sdk/index.d.ts.map +1 -1
- package/lib/types/types.d.ts +2 -1
- package/lib/types/types.d.ts.map +1 -1
- package/package.json +6 -5
- package/lib/{bundle-runtime-CE73h3vS.js → bundle-runtime-QUG0eHSZ.js} +1 -1
- package/lib/{persistent-runtime-Cb8xQ4w0.js → persistent-runtime-oMlB7XX_.js} +6 -6
|
@@ -5,6 +5,2054 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/client/harness-session-origin.ts
|
|
11
|
+
function sessionOrigin(row) {
|
|
12
|
+
if (row.local) return [];
|
|
13
|
+
return [`实例:${row.runtimeName}`, ...row.sameDesktop ? [] : [`电脑:${row.desktopName}`]];
|
|
14
|
+
}
|
|
15
|
+
/** Current-version presentation adapter. Native card, title, time, status and copy remain owned by DSH. */
|
|
16
|
+
function installSessionOriginHover(doc, resolve) {
|
|
17
|
+
let active;
|
|
18
|
+
const owned = /* @__PURE__ */ new Set();
|
|
19
|
+
const clear = () => {
|
|
20
|
+
for (const node of owned) node.remove();
|
|
21
|
+
owned.clear();
|
|
22
|
+
};
|
|
23
|
+
const update = () => {
|
|
24
|
+
if (!active?.isConnected) {
|
|
25
|
+
clear();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!doc.querySelector("body > div[role=\"button\"][aria-label]")) return;
|
|
29
|
+
const { lines = [], title } = resolve(active) ?? {};
|
|
30
|
+
if (!title || !lines.length) {
|
|
31
|
+
clear();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
for (const card of doc.querySelectorAll("body > div[role=\"button\"][aria-label]")) {
|
|
35
|
+
const content = card.firstElementChild, heading = content?.firstElementChild;
|
|
36
|
+
const time = heading?.nextElementSibling;
|
|
37
|
+
if (heading?.textContent !== title || !time || time.children.length || !card.getAttribute("aria-label")?.endsWith(`: ${title}`)) continue;
|
|
38
|
+
let extra = content.querySelector("[data-arkme-session-origin]");
|
|
39
|
+
if (!extra) {
|
|
40
|
+
extra = doc.createElement("div");
|
|
41
|
+
extra.dataset.arkmeSessionOrigin = "";
|
|
42
|
+
content.append(extra);
|
|
43
|
+
owned.add(extra);
|
|
44
|
+
}
|
|
45
|
+
if (extra.textContent !== lines.join("")) extra.replaceChildren(...lines.map((line) => {
|
|
46
|
+
const node = doc.createElement("div");
|
|
47
|
+
node.className = time.className;
|
|
48
|
+
node.textContent = line;
|
|
49
|
+
return node;
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
for (const node of owned) if (!node.isConnected) owned.delete(node);
|
|
53
|
+
};
|
|
54
|
+
const over = (event) => {
|
|
55
|
+
const row = event.target?.closest("[data-slot=\"sidebar.workspaces\"] [role=\"treeitem\"][aria-selected]");
|
|
56
|
+
if (row && row !== active) {
|
|
57
|
+
clear();
|
|
58
|
+
active = row;
|
|
59
|
+
update();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const observer = new MutationObserver(update);
|
|
63
|
+
observer.observe(doc.body, {
|
|
64
|
+
subtree: true,
|
|
65
|
+
childList: true
|
|
66
|
+
});
|
|
67
|
+
doc.addEventListener("pointerover", over, true);
|
|
68
|
+
return () => {
|
|
69
|
+
observer.disconnect();
|
|
70
|
+
doc.removeEventListener("pointerover", over, true);
|
|
71
|
+
clear();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/dsh-remote/account-session-observer.ts
|
|
76
|
+
/** Browser/SDK receive-only invalidation adapter; disposal aborts the subscription. */
|
|
77
|
+
function observeDshAccountSession(route, fetchImpl, target, changed, options = {}) {
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const signal = options.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal;
|
|
80
|
+
(async () => {
|
|
81
|
+
const response = await fetchImpl(route, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
credentials: "same-origin",
|
|
84
|
+
headers: { "Content-Type": "application/json" },
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
operation: "remote.session.observe",
|
|
87
|
+
params: target
|
|
88
|
+
}),
|
|
89
|
+
signal
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok || !response.headers.get("content-type")?.includes("application/x-ndjson") || !response.body) throw new Error("远程连接不可用");
|
|
92
|
+
const reader = response.body.getReader(), decoder = new TextDecoder();
|
|
93
|
+
let buffer = "";
|
|
94
|
+
try {
|
|
95
|
+
while (!signal.aborted) {
|
|
96
|
+
const { value, done } = await reader.read();
|
|
97
|
+
if (done) throw new Error("远程连接已断开");
|
|
98
|
+
buffer += decoder.decode(value, { stream: true });
|
|
99
|
+
if (buffer.length > 4096) throw new Error("远程通知格式无效");
|
|
100
|
+
let end;
|
|
101
|
+
while ((end = buffer.indexOf("\n")) >= 0) {
|
|
102
|
+
const message = JSON.parse(buffer.slice(0, end));
|
|
103
|
+
buffer = buffer.slice(end + 1);
|
|
104
|
+
if (message.error) throw new Error(message.error);
|
|
105
|
+
if (message.changed && !signal.aborted) changed();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
await reader.cancel().catch(() => void 0);
|
|
110
|
+
reader.releaseLock();
|
|
111
|
+
}
|
|
112
|
+
})().catch((error) => {
|
|
113
|
+
if (!signal.aborted) options.onError?.(error);
|
|
114
|
+
});
|
|
115
|
+
return () => controller.abort();
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/record-owner-id.ts
|
|
119
|
+
function recordOwnerId(value) {
|
|
120
|
+
if (typeof value === "number") return Number.isSafeInteger(value) && value > 0 ? value : 0;
|
|
121
|
+
if (typeof value !== "string" || !/^[1-9]\d*$/.test(value)) return 0;
|
|
122
|
+
const id = BigInt(value);
|
|
123
|
+
if (id > 9223372036854775807n) return 0;
|
|
124
|
+
return id <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(id) : value;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/bot-avatar-ref.ts
|
|
128
|
+
const ARKME_BOT_AVATAR_REF_PATTERN = /^file_asset:\/\/[A-Za-z0-9_-]{8,128}$/;
|
|
129
|
+
/** Bot avatars cross process boundaries only as account-scoped file asset references. */
|
|
130
|
+
function isArkmeBotAvatarRef(value) {
|
|
131
|
+
return ARKME_BOT_AVATAR_REF_PATTERN.test(value.trim());
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/extensions/source.ts
|
|
135
|
+
function normalizeGitHubRepositoryURL(raw) {
|
|
136
|
+
const value = raw?.trim() ?? "";
|
|
137
|
+
if (value === "") return void 0;
|
|
138
|
+
let parsed;
|
|
139
|
+
try {
|
|
140
|
+
parsed = new URL(value);
|
|
141
|
+
} catch {
|
|
142
|
+
throw new TypeError("GitHub repository URL is invalid");
|
|
143
|
+
}
|
|
144
|
+
const host = parsed.hostname.toLowerCase();
|
|
145
|
+
if (parsed.protocol !== "https:" || !["github.com", "www.github.com"].includes(host) || parsed.username !== "" || parsed.password !== "" || parsed.port !== "" || parsed.search !== "" || parsed.hash !== "") throw new TypeError("GitHub repository URL is invalid");
|
|
146
|
+
const parts = parsed.pathname.replace(/\/$/, "").replace(/^\//, "").split("/");
|
|
147
|
+
if (parts.length !== 2) throw new TypeError("GitHub repository URL must point to a repository root");
|
|
148
|
+
const owner = parts[0];
|
|
149
|
+
const repository = parts[1].replace(/\.git$/, "");
|
|
150
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(owner) || !/^[A-Za-z0-9._-]{1,100}$/.test(repository) || repository.endsWith(".")) throw new TypeError("GitHub repository URL is invalid");
|
|
151
|
+
return `https://github.com/${owner.toLowerCase()}/${repository.toLowerCase()}`;
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/sdk/index.ts
|
|
155
|
+
const DEFAULT_ROUTE = "/arkme-self/api";
|
|
156
|
+
function expectedUserIdHeaders(expectedUserId) {
|
|
157
|
+
if (expectedUserId === void 0) return {};
|
|
158
|
+
if (!Number.isSafeInteger(expectedUserId) || expectedUserId <= 0) throw new TypeError("Arkme expected user ID must be a positive safe integer");
|
|
159
|
+
return { "X-Arkme-Expected-User-Id": String(expectedUserId) };
|
|
160
|
+
}
|
|
161
|
+
function expectedUserIdParam(expectedUserId) {
|
|
162
|
+
if (expectedUserId === void 0) return {};
|
|
163
|
+
expectedUserIdHeaders(expectedUserId);
|
|
164
|
+
return { expectedUserId };
|
|
165
|
+
}
|
|
166
|
+
var ArkmeClientError = class extends Error {
|
|
167
|
+
body;
|
|
168
|
+
constructor(body) {
|
|
169
|
+
super(body.message);
|
|
170
|
+
this.body = body;
|
|
171
|
+
this.name = "ArkmeClientError";
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function safeSdkCallDetail(detail) {
|
|
175
|
+
if (detail.videoRecord === void 0) return detail;
|
|
176
|
+
return {
|
|
177
|
+
...detail,
|
|
178
|
+
videoRecord: {
|
|
179
|
+
available: detail.videoRecord.available,
|
|
180
|
+
source: detail.videoRecord.source
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
var ArkmeSdk = class {
|
|
185
|
+
route;
|
|
186
|
+
fetchImpl;
|
|
187
|
+
constructor(options = {}) {
|
|
188
|
+
const route = options.route ?? DEFAULT_ROUTE;
|
|
189
|
+
if (!/^\/[A-Za-z0-9/_-]+$/.test(route) || route.startsWith("//")) throw new TypeError("Arkme SDK route must be a same-origin absolute path");
|
|
190
|
+
this.route = route;
|
|
191
|
+
this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
192
|
+
}
|
|
193
|
+
async capabilities(signal) {
|
|
194
|
+
const capabilities = await this.call("provider.capabilities", void 0, signal);
|
|
195
|
+
if (capabilities.contractVersion !== 1) throw new Error(`Unsupported Arkme provider contract version ${String(capabilities.contractVersion)}`);
|
|
196
|
+
return capabilities;
|
|
197
|
+
}
|
|
198
|
+
async state(signal) {
|
|
199
|
+
return await this.call("provider.state", void 0, signal);
|
|
200
|
+
}
|
|
201
|
+
/** Read the credential-free managed OpenAPI MCP lifecycle projection. */
|
|
202
|
+
async openApiMcpStatus(signal) {
|
|
203
|
+
return await this.call("openapi.mcp.status", void 0, signal);
|
|
204
|
+
}
|
|
205
|
+
/** Retry a degraded managed MCP connection without exposing or accepting credentials. */
|
|
206
|
+
async retryOpenApiMcp(signal) {
|
|
207
|
+
return await this.call("openapi.mcp.retry", void 0, signal);
|
|
208
|
+
}
|
|
209
|
+
/** List every Team visible to the current account using opaque OpenAPI references. */
|
|
210
|
+
async listTeams(options = {}) {
|
|
211
|
+
return await this.call("team.list", {
|
|
212
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
213
|
+
...options.pageCursor === void 0 ? {} : { pageCursor: options.pageCursor }
|
|
214
|
+
}, options.signal);
|
|
215
|
+
}
|
|
216
|
+
/** Safe directory projection shared with the built-in UI; never an implicit full candidate baseline. */
|
|
217
|
+
async listDirectory(section, options = {}) {
|
|
218
|
+
if (![
|
|
219
|
+
"groups",
|
|
220
|
+
"bots",
|
|
221
|
+
"unmarked-speakers",
|
|
222
|
+
"teams",
|
|
223
|
+
"contacts"
|
|
224
|
+
].includes(section)) throw new TypeError("Unknown directory section");
|
|
225
|
+
if (options.refresh === true && options.cursor !== void 0) throw new TypeError("Refresh must start a new directory page");
|
|
226
|
+
if ((await this.capabilities(options.signal)).features.contactDirectoryReads !== true) throw new Error("当前 Provider 不支持联系人目录读取");
|
|
227
|
+
return await this.call("directory.list", {
|
|
228
|
+
section,
|
|
229
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
230
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor },
|
|
231
|
+
...options.refresh === void 0 ? {} : { refresh: options.refresh }
|
|
232
|
+
}, options.signal);
|
|
233
|
+
}
|
|
234
|
+
/** Resolve exact Team Jotmo IDs or names only within the current account's Teams. */
|
|
235
|
+
async resolveTeams(items, signal) {
|
|
236
|
+
return await this.call("team.resolve", { items }, signal);
|
|
237
|
+
}
|
|
238
|
+
/** Read an authorized Team member page without exposing backend numeric identities. */
|
|
239
|
+
async listTeamMembers(teamRef, options = {}) {
|
|
240
|
+
return await this.call("team.members.list", {
|
|
241
|
+
teamRef,
|
|
242
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
243
|
+
...options.pageCursor === void 0 ? {} : { pageCursor: options.pageCursor }
|
|
244
|
+
}, options.signal);
|
|
245
|
+
}
|
|
246
|
+
/** Create Teams as an explicit current-human mutation; each item is independently idempotent. */
|
|
247
|
+
async createTeams(items, signal) {
|
|
248
|
+
return await this.call("team.create", { items }, signal);
|
|
249
|
+
}
|
|
250
|
+
/** Join Teams by exact public Jotmo ID as an explicit current-human mutation. */
|
|
251
|
+
async joinTeamsByJotmoID(items, signal) {
|
|
252
|
+
return await this.call("team.join-by-jotmo-id", { items }, signal);
|
|
253
|
+
}
|
|
254
|
+
/** Read Browser-safe installed extension projections without Host filesystem paths or runtime IDs. */
|
|
255
|
+
async installedExtensions(signal) {
|
|
256
|
+
return await this.call("extensions.installed-list", void 0, signal);
|
|
257
|
+
}
|
|
258
|
+
/** Search the market through the same Host owner used by Arkme UI and Tools. */
|
|
259
|
+
async searchExtensions(query = "", limit = 20, signal) {
|
|
260
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 50) throw new TypeError("Arkme extension search limit must be 1-50");
|
|
261
|
+
return await this.call("extensions.catalog.list", {
|
|
262
|
+
query: query.trim(),
|
|
263
|
+
limit
|
|
264
|
+
}, signal);
|
|
265
|
+
}
|
|
266
|
+
async extensionDetail(extensionId, signal) {
|
|
267
|
+
if (extensionId.trim() === "") throw new TypeError("Arkme extension ID must not be empty");
|
|
268
|
+
return await this.call("extensions.catalog.detail", { extensionId: extensionId.trim() }, signal);
|
|
269
|
+
}
|
|
270
|
+
/** Query V1/V2/V3 install authority before asking the user to approve installation. */
|
|
271
|
+
async extensionInstallPreview(extensionId, version, signal) {
|
|
272
|
+
if (extensionId.trim() === "") throw new TypeError("Arkme extension ID must not be empty");
|
|
273
|
+
return await this.call("extensions.install.preview", {
|
|
274
|
+
extensionId: extensionId.trim(),
|
|
275
|
+
...version === void 0 || version.trim() === "" ? {} : { version: version.trim() }
|
|
276
|
+
}, signal);
|
|
277
|
+
}
|
|
278
|
+
async extensionEnabledState(extensionId, signal) {
|
|
279
|
+
if (extensionId.trim() === "") throw new TypeError("Arkme extension ID must not be empty");
|
|
280
|
+
return await this.call("extensions.enabled-state", { extensionId }, signal);
|
|
281
|
+
}
|
|
282
|
+
/** Change desired activation without uninstalling the verified extension artifact. */
|
|
283
|
+
async setExtensionEnabled(extensionId, enabled, signal) {
|
|
284
|
+
if (extensionId.trim() === "") throw new TypeError("Arkme extension ID must not be empty");
|
|
285
|
+
return await this.call("extensions.enabled.set", {
|
|
286
|
+
extensionId,
|
|
287
|
+
enabled
|
|
288
|
+
}, signal);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Delete an owned marketplace extension and remove its current local runtime/Profile/source references.
|
|
292
|
+
* Consumers must call this only from an explicit current human delete action.
|
|
293
|
+
*/
|
|
294
|
+
async deleteExtension(extensionId, signal) {
|
|
295
|
+
const normalized = extensionId.trim();
|
|
296
|
+
if (normalized === "") throw new TypeError("Arkme extension ID must not be empty");
|
|
297
|
+
return await this.call("extensions.delete", { extensionId: normalized }, signal);
|
|
298
|
+
}
|
|
299
|
+
/** Build the same-origin URL used by every extension list/detail avatar surface. */
|
|
300
|
+
extensionIconUrl(extensionId, iconRef) {
|
|
301
|
+
if (extensionId.trim() === "" || !/^icon_v1_[a-f0-9]{64}$/.test(iconRef.trim())) throw new TypeError("Arkme extension icon identity is invalid");
|
|
302
|
+
return `${this.route}/extension-icon?extension_id=${encodeURIComponent(extensionId.trim())}&icon_ref=${encodeURIComponent(iconRef.trim())}`;
|
|
303
|
+
}
|
|
304
|
+
/** Upload or replace an owned extension icon without exposing registry signed transport. */
|
|
305
|
+
async setExtensionIcon(extensionId, file, options = {}) {
|
|
306
|
+
const mediaType = file.type.toLowerCase();
|
|
307
|
+
if (extensionId.trim() === "" || ![
|
|
308
|
+
"image/png",
|
|
309
|
+
"image/jpeg",
|
|
310
|
+
"image/webp"
|
|
311
|
+
].includes(mediaType)) throw new TypeError("Arkme extension icon must be PNG, JPEG, or WebP");
|
|
312
|
+
if (file.size <= 0 || file.size > 2 * 1024 * 1024) throw new TypeError("Arkme extension icon must be smaller than 2 MiB");
|
|
313
|
+
const mutationId = options.clientMutationId ?? crypto.randomUUID();
|
|
314
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(mutationId)) throw new TypeError("Arkme extension icon mutation id must be a UUID");
|
|
315
|
+
const response = await this.fetchImpl(`${this.route}/extension-icon/upload`, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
credentials: "same-origin",
|
|
318
|
+
headers: {
|
|
319
|
+
"Content-Type": mediaType,
|
|
320
|
+
"X-Arkme-Extension-Id": extensionId.trim(),
|
|
321
|
+
"X-Arkme-Idempotency-Key": mutationId
|
|
322
|
+
},
|
|
323
|
+
body: file,
|
|
324
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
325
|
+
});
|
|
326
|
+
let body;
|
|
327
|
+
try {
|
|
328
|
+
body = await response.json();
|
|
329
|
+
} catch {
|
|
330
|
+
throw new ArkmeClientError({
|
|
331
|
+
code: "local-response-invalid",
|
|
332
|
+
message: "Arkme 插件返回了无效响应",
|
|
333
|
+
retryable: true
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (!body.ok) throw new ArkmeClientError(body.error);
|
|
337
|
+
return body.value;
|
|
338
|
+
}
|
|
339
|
+
extensionPreviewUrl(extensionId, previewRef) {
|
|
340
|
+
if (extensionId.trim() === "" || !/^preview_v1_[a-f0-9]{64}$/.test(previewRef.trim())) throw new TypeError("Arkme extension preview identity is invalid");
|
|
341
|
+
return `${this.route}/extension-preview?extension_id=${encodeURIComponent(extensionId.trim())}&preview_ref=${encodeURIComponent(previewRef.trim())}`;
|
|
342
|
+
}
|
|
343
|
+
async addExtensionPreview(extensionId, file, options = {}) {
|
|
344
|
+
const mediaType = file.type.toLowerCase();
|
|
345
|
+
if (extensionId.trim() === "" || ![
|
|
346
|
+
"image/png",
|
|
347
|
+
"image/jpeg",
|
|
348
|
+
"image/webp"
|
|
349
|
+
].includes(mediaType)) throw new TypeError("Arkme extension preview must be PNG, JPEG, or WebP");
|
|
350
|
+
if (file.size <= 0 || file.size > 5 * 1024 * 1024) throw new TypeError("Arkme extension preview must be smaller than 5 MiB");
|
|
351
|
+
const mutationId = options.clientMutationId ?? crypto.randomUUID();
|
|
352
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(mutationId)) throw new TypeError("Arkme extension preview mutation id must be a UUID");
|
|
353
|
+
const response = await this.fetchImpl(`${this.route}/extension-preview/upload`, {
|
|
354
|
+
method: "POST",
|
|
355
|
+
credentials: "same-origin",
|
|
356
|
+
headers: {
|
|
357
|
+
"Content-Type": mediaType,
|
|
358
|
+
"X-Arkme-Extension-Id": extensionId.trim(),
|
|
359
|
+
"X-Arkme-Idempotency-Key": mutationId
|
|
360
|
+
},
|
|
361
|
+
body: file,
|
|
362
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
363
|
+
});
|
|
364
|
+
let body;
|
|
365
|
+
try {
|
|
366
|
+
body = await response.json();
|
|
367
|
+
} catch {
|
|
368
|
+
throw new ArkmeClientError({
|
|
369
|
+
code: "local-response-invalid",
|
|
370
|
+
message: "Arkme 插件返回了无效响应",
|
|
371
|
+
retryable: true
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
if (!body.ok) throw new ArkmeClientError(body.error);
|
|
375
|
+
return body.value;
|
|
376
|
+
}
|
|
377
|
+
async deleteExtensionPreview(extensionId, previewRef, expectedRevision, signal) {
|
|
378
|
+
if (extensionId.trim() === "" || !/^preview_v1_[a-f0-9]{64}$/.test(previewRef.trim()) || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0) throw new TypeError("Arkme extension preview delete parameters are invalid");
|
|
379
|
+
return await this.call("extensions.preview.delete", {
|
|
380
|
+
extensionId: extensionId.trim(),
|
|
381
|
+
previewRef: previewRef.trim(),
|
|
382
|
+
expectedRevision
|
|
383
|
+
}, signal);
|
|
384
|
+
}
|
|
385
|
+
async reorderExtensionPreviews(extensionId, orderedPreviewRefs, expectedRevision, signal) {
|
|
386
|
+
const refs = orderedPreviewRefs.map((value) => value.trim());
|
|
387
|
+
if (extensionId.trim() === "" || refs.length <= 0 || refs.length > 20 || refs.some((value) => !/^preview_v1_[a-f0-9]{64}$/.test(value)) || new Set(refs).size !== refs.length || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0) throw new TypeError("Arkme extension preview reorder parameters are invalid");
|
|
388
|
+
return await this.call("extensions.preview.reorder", {
|
|
389
|
+
extensionId: extensionId.trim(),
|
|
390
|
+
orderedPreviewRefs: refs,
|
|
391
|
+
expectedRevision
|
|
392
|
+
}, signal);
|
|
393
|
+
}
|
|
394
|
+
/** Run a user-triggered AI audit for one marketplace extension without installing it. */
|
|
395
|
+
async auditExtension(extensionId, signal) {
|
|
396
|
+
if (extensionId.trim() === "") throw new TypeError("Arkme extension ID must not be empty");
|
|
397
|
+
return await this.call("extensions.audit.check", { extensionId: extensionId.trim() }, signal);
|
|
398
|
+
}
|
|
399
|
+
async authStatus(signal) {
|
|
400
|
+
return await this.call("auth.status", void 0, signal);
|
|
401
|
+
}
|
|
402
|
+
/** Read recent call history through Browser-safe opaque call references. */
|
|
403
|
+
async callHistory(options = {}, signal) {
|
|
404
|
+
const limit = options.limit === void 0 ? void 0 : Math.trunc(options.limit);
|
|
405
|
+
if (limit !== void 0 && (!Number.isSafeInteger(limit) || limit <= 0 || limit > 50)) throw new TypeError("Arkme call history limit must be 1-50");
|
|
406
|
+
const cursor = options.cursor?.trim() ?? "";
|
|
407
|
+
return await this.call("calls.history.list", {
|
|
408
|
+
...limit === void 0 ? {} : { limit },
|
|
409
|
+
...cursor === "" ? {} : { cursor },
|
|
410
|
+
...options.includeRecentContacts === void 0 ? {} : { includeRecentContacts: options.includeRecentContacts }
|
|
411
|
+
}, signal);
|
|
412
|
+
}
|
|
413
|
+
/** Read one call detail using an unchanged callRef returned by callHistory(). */
|
|
414
|
+
async callDetail(callRef, signal) {
|
|
415
|
+
const normalized = callRef.trim();
|
|
416
|
+
if (normalized === "") throw new TypeError("Arkme call reference must not be empty");
|
|
417
|
+
return safeSdkCallDetail(await this.call("calls.history.detail", { callRef: normalized }, signal));
|
|
418
|
+
}
|
|
419
|
+
/** Retry call summary generation only from an explicit current human action. */
|
|
420
|
+
async retryCallSummary(callRef, signal) {
|
|
421
|
+
const normalized = callRef.trim();
|
|
422
|
+
if (normalized === "") throw new TypeError("Arkme call reference must not be empty");
|
|
423
|
+
return await this.call("calls.history.summary.retry", { callRef: normalized }, signal);
|
|
424
|
+
}
|
|
425
|
+
async profile(options = {}) {
|
|
426
|
+
return await this.call(options.refresh === true ? "user.profile.refresh" : "user.profile", void 0, options.signal);
|
|
427
|
+
}
|
|
428
|
+
/** Employee-only current-state lookup for the user bound to a private-chat source. */
|
|
429
|
+
async userBanStatus(sourceRef, signal) {
|
|
430
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme private-chat source reference must not be empty");
|
|
431
|
+
return await this.call("user-ban.status", { sourceRef }, signal);
|
|
432
|
+
}
|
|
433
|
+
async directMessageAdmission(sourceRef, signal) {
|
|
434
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme private-chat source reference must not be empty");
|
|
435
|
+
if ((await this.capabilities(signal)).features.directMessageAdmission !== true) throw new ArkmeClientError({
|
|
436
|
+
code: "CAPABILITY_UNSUPPORTED",
|
|
437
|
+
message: "当前 Arkme Provider 不支持私聊拒收,请升级",
|
|
438
|
+
retryable: false
|
|
439
|
+
});
|
|
440
|
+
return await this.call("chat.direct-message-admission", { sourceRef }, signal);
|
|
441
|
+
}
|
|
442
|
+
async setDirectMessageRefusal(sourceRef, refused, expectedRevision, signal) {
|
|
443
|
+
if (sourceRef.trim() === "" || typeof refused !== "boolean" || !Number.isSafeInteger(expectedRevision) || expectedRevision < 0) throw new TypeError("Arkme private-chat refusal input is invalid");
|
|
444
|
+
if ((await this.capabilities(signal)).features.directMessageAdmission !== true) throw new ArkmeClientError({
|
|
445
|
+
code: "CAPABILITY_UNSUPPORTED",
|
|
446
|
+
message: "当前 Arkme Provider 不支持私聊拒收,请升级",
|
|
447
|
+
retryable: false
|
|
448
|
+
});
|
|
449
|
+
return await this.call("chat.direct-message-refusal.set", {
|
|
450
|
+
sourceRef,
|
|
451
|
+
refused,
|
|
452
|
+
expectedRevision
|
|
453
|
+
}, signal);
|
|
454
|
+
}
|
|
455
|
+
/** Employee-only idempotent ban. Callers must obtain explicit human approval first. */
|
|
456
|
+
async banPrivateChatUser(sourceRef, remark = "", signal) {
|
|
457
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme private-chat source reference must not be empty");
|
|
458
|
+
if (Array.from(remark.trim()).length > 255) throw new TypeError("Arkme user-ban remark must be at most 255 characters");
|
|
459
|
+
return await this.call("user-ban.ban", {
|
|
460
|
+
sourceRef,
|
|
461
|
+
remark: remark.trim()
|
|
462
|
+
}, signal);
|
|
463
|
+
}
|
|
464
|
+
/** Employee-only idempotent unban. Callers must obtain explicit human approval first. */
|
|
465
|
+
async unbanPrivateChatUser(sourceRef, remark = "", signal) {
|
|
466
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme private-chat source reference must not be empty");
|
|
467
|
+
if (Array.from(remark.trim()).length > 255) throw new TypeError("Arkme user-ban remark must be at most 255 characters");
|
|
468
|
+
return await this.call("user-ban.unban", {
|
|
469
|
+
sourceRef,
|
|
470
|
+
remark: remark.trim()
|
|
471
|
+
}, signal);
|
|
472
|
+
}
|
|
473
|
+
async backgroundSoundPreference(signal) {
|
|
474
|
+
return await this.call("settings.background-sound.get", {}, signal);
|
|
475
|
+
}
|
|
476
|
+
async updateBackgroundSoundPreference(enabled, signal, expectedUserId) {
|
|
477
|
+
if (typeof enabled !== "boolean") throw new TypeError("Arkme background-sound preference must be boolean");
|
|
478
|
+
return await this.call("settings.background-sound.update", {
|
|
479
|
+
enabled,
|
|
480
|
+
...expectedUserIdParam(expectedUserId)
|
|
481
|
+
}, signal);
|
|
482
|
+
}
|
|
483
|
+
/** Validate a candidate Arkme ID with the same one-time account rule used by the built-in UI. */
|
|
484
|
+
async checkArkmeIdAvailability(arkmeId, signal) {
|
|
485
|
+
const normalized = arkmeId.trim();
|
|
486
|
+
if (normalized === "") throw new TypeError("Arkme ID must not be empty");
|
|
487
|
+
return await this.call("user.arkme-id.check", { arkmeId: normalized }, signal);
|
|
488
|
+
}
|
|
489
|
+
/** Set the current account Arkme ID once after explicit user confirmation. */
|
|
490
|
+
async setArkmeIdOnce(arkmeId, signal) {
|
|
491
|
+
const normalized = arkmeId.trim();
|
|
492
|
+
if (normalized === "") throw new TypeError("Arkme ID must not be empty");
|
|
493
|
+
return await this.call("user.arkme-id.set", { arkmeId: normalized }, signal);
|
|
494
|
+
}
|
|
495
|
+
/** Send a phone verification code for login, first bind, or phone rebind depending on auth state. */
|
|
496
|
+
async sendPhoneCode(phone, captcha, signal) {
|
|
497
|
+
const normalized = phone.replace(/[\s-]/g, "");
|
|
498
|
+
if (!/^1[3-9][0-9]{9}$/.test(normalized)) throw new TypeError("Phone number is invalid");
|
|
499
|
+
return await this.call("auth.phone.send", {
|
|
500
|
+
phone: normalized,
|
|
501
|
+
captcha
|
|
502
|
+
}, signal);
|
|
503
|
+
}
|
|
504
|
+
/** Verify a phone code and refresh the account auth/profile state. */
|
|
505
|
+
async verifyPhoneCode(phone, code, signal) {
|
|
506
|
+
const normalized = phone.replace(/[\s-]/g, "");
|
|
507
|
+
const normalizedCode = code.trim();
|
|
508
|
+
if (!/^1[3-9][0-9]{9}$/.test(normalized)) throw new TypeError("Phone number is invalid");
|
|
509
|
+
if (!/^[0-9]{6}$/.test(normalizedCode)) throw new TypeError("Phone verification code is invalid");
|
|
510
|
+
return await this.call("auth.phone.verify", {
|
|
511
|
+
phone: normalized,
|
|
512
|
+
code: normalizedCode
|
|
513
|
+
}, signal);
|
|
514
|
+
}
|
|
515
|
+
/** Search by an exact phone number or Arkme ID without exposing internal account identifiers. */
|
|
516
|
+
async searchContact(identifier, signal) {
|
|
517
|
+
const value = identifier.trim();
|
|
518
|
+
if (value === "" || value.length > 64) throw new TypeError("Arkme contact identifier is invalid");
|
|
519
|
+
return await this.call("contacts.search", { identifier: value }, signal);
|
|
520
|
+
}
|
|
521
|
+
/** Add the exact candidate returned by searchContact and open its idempotent private/pending chat. */
|
|
522
|
+
async addContact(contactRef, options = {}) {
|
|
523
|
+
const ref = contactRef.trim();
|
|
524
|
+
if (!/^arkme-contact-v1\.[0-9a-f-]{36}$/i.test(ref)) throw new TypeError("Arkme contact reference is invalid");
|
|
525
|
+
const remark = options.remark?.trim() ?? "";
|
|
526
|
+
if (Array.from(remark).length > 100) throw new TypeError("Arkme contact remark is too long");
|
|
527
|
+
const requestUid = options.requestUid ?? crypto.randomUUID();
|
|
528
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(requestUid)) throw new TypeError("Arkme contact request id must be a UUID");
|
|
529
|
+
return await this.call("contacts.add", {
|
|
530
|
+
contactRef: ref,
|
|
531
|
+
...remark === "" ? {} : { remark },
|
|
532
|
+
requestUid
|
|
533
|
+
}, options.signal);
|
|
534
|
+
}
|
|
535
|
+
/** Open a private chat for a registered candidate returned by searchContact without adding it as a contact. */
|
|
536
|
+
async openPrivateChatFromContact(contactRef, signal) {
|
|
537
|
+
const ref = contactRef.trim();
|
|
538
|
+
if (!/^arkme-contact-v1\.[0-9a-f-]{36}$/i.test(ref)) throw new TypeError("Arkme contact reference is invalid");
|
|
539
|
+
return await this.call("chat.private.open-from-contact", { contactRef: ref }, signal);
|
|
540
|
+
}
|
|
541
|
+
/** Create an initially owner-only group chat with an idempotent client mutation id. */
|
|
542
|
+
async createGroup(title, options = {}) {
|
|
543
|
+
const normalizedTitle = title.trim();
|
|
544
|
+
if (normalizedTitle === "" || Array.from(normalizedTitle).length > 80) throw new TypeError("Arkme group title is invalid");
|
|
545
|
+
const clientMutationId = options.clientMutationId ?? crypto.randomUUID();
|
|
546
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(clientMutationId)) throw new TypeError("Arkme group mutation id must be a UUID");
|
|
547
|
+
return await this.call("group.create", {
|
|
548
|
+
title: normalizedTitle,
|
|
549
|
+
clientMutationId
|
|
550
|
+
}, options.signal);
|
|
551
|
+
}
|
|
552
|
+
async listBots(signal) {
|
|
553
|
+
return await this.call("bots.list", void 0, signal);
|
|
554
|
+
}
|
|
555
|
+
/** Create a Bot without exposing the Host-owned one-time credential to the Consumer. */
|
|
556
|
+
async createBot(input, signal) {
|
|
557
|
+
const name = input.name.trim();
|
|
558
|
+
if (name === "") throw new TypeError("Arkme Bot name must not be empty");
|
|
559
|
+
if (input.provider !== "openclaw" && input.provider !== "webhook") throw new TypeError("Arkme Bot provider is unsupported");
|
|
560
|
+
const description = input.description?.trim() ?? "";
|
|
561
|
+
const avatar = input.avatar?.trim() ?? "";
|
|
562
|
+
if (avatar !== "" && !isArkmeBotAvatarRef(avatar)) throw new TypeError("Arkme Bot avatar must be a file_asset reference");
|
|
563
|
+
return await this.call("bots.create", {
|
|
564
|
+
name,
|
|
565
|
+
provider: input.provider,
|
|
566
|
+
...description === "" ? {} : { description },
|
|
567
|
+
...avatar === "" ? {} : { avatar }
|
|
568
|
+
}, signal);
|
|
569
|
+
}
|
|
570
|
+
/** Browse public marketplace extensions with server-owned query, ordering and cursor semantics. */
|
|
571
|
+
async extensionCatalog(options = {}) {
|
|
572
|
+
if (options.ownerUserId !== void 0 && (!Number.isSafeInteger(options.ownerUserId) || options.ownerUserId <= 0)) throw new TypeError("Arkme extension owner user id must be a positive safe integer");
|
|
573
|
+
return await this.call("extensions.catalog.list", {
|
|
574
|
+
...options.query === void 0 || options.query.trim() === "" ? {} : { query: options.query.trim() },
|
|
575
|
+
...options.sort === void 0 ? {} : { sort: options.sort },
|
|
576
|
+
...options.cursor === void 0 || options.cursor.trim() === "" ? {} : { cursor: options.cursor.trim() },
|
|
577
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
578
|
+
...options.ownerUserId === void 0 ? {} : { ownerUserId: options.ownerUserId },
|
|
579
|
+
...options.excludeExtensionId === void 0 || options.excludeExtensionId.trim() === "" ? {} : { excludeExtensionId: options.excludeExtensionId.trim() }
|
|
580
|
+
}, options.signal);
|
|
581
|
+
}
|
|
582
|
+
/** Read the AI-generated marketplace category tree. */
|
|
583
|
+
async extensionCategories(options = {}) {
|
|
584
|
+
return await this.call("extensions.classification.tree", { ...options.limit === void 0 ? {} : { limit: options.limit } }, options.signal);
|
|
585
|
+
}
|
|
586
|
+
/** Browse one AI category while retaining the same server-side ordering contract. */
|
|
587
|
+
async extensionCategoryItems(categoryId, options = {}) {
|
|
588
|
+
if (categoryId.trim() === "") throw new TypeError("Arkme extension category id must not be empty");
|
|
589
|
+
return await this.call("extensions.classification.items", {
|
|
590
|
+
categoryId: categoryId.trim(),
|
|
591
|
+
...options.query === void 0 || options.query.trim() === "" ? {} : { query: options.query.trim() },
|
|
592
|
+
...options.sort === void 0 ? {} : { sort: options.sort },
|
|
593
|
+
...options.cursor === void 0 || options.cursor.trim() === "" ? {} : { cursor: options.cursor.trim() },
|
|
594
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
595
|
+
}, options.signal);
|
|
596
|
+
}
|
|
597
|
+
/** List current-account sources with their Host-derived dynamic-cordis-v2 or profile-native-v3 publication route. */
|
|
598
|
+
async myExtensions(options = {}) {
|
|
599
|
+
return await this.call("extensions.mine.list", { ...options.currentSessionId === void 0 || options.currentSessionId.trim() === "" ? {} : { currentSessionId: options.currentSessionId.trim() } }, options.signal);
|
|
600
|
+
}
|
|
601
|
+
/** Publish one exact owned source through its Host-derived V2 or V3 route after explicit current-user intent. */
|
|
602
|
+
publishMyExtension(input, signal) {
|
|
603
|
+
if (input.ownedRef.trim() === "") throw new TypeError("Arkme extension reference must not be empty");
|
|
604
|
+
if (input.name.trim() === "" || input.name.trim().length > 120) throw new TypeError("Arkme extension name is invalid");
|
|
605
|
+
if (input.description.length > 2e3) throw new TypeError("Arkme extension description is too long");
|
|
606
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(input.version.trim())) throw new TypeError("Arkme extension version must be SemVer");
|
|
607
|
+
if (![
|
|
608
|
+
"private",
|
|
609
|
+
"unlisted",
|
|
610
|
+
"public"
|
|
611
|
+
].includes(input.visibility)) throw new TypeError("Arkme extension visibility is invalid");
|
|
612
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(input.clientMutationId)) throw new TypeError("Arkme extension client mutation id must be a UUID");
|
|
613
|
+
let githubRepositoryUrl;
|
|
614
|
+
try {
|
|
615
|
+
githubRepositoryUrl = normalizeGitHubRepositoryURL(input.githubRepositoryUrl);
|
|
616
|
+
} catch {
|
|
617
|
+
throw new TypeError("Arkme extension GitHub repository URL is invalid");
|
|
618
|
+
}
|
|
619
|
+
return this.call("extensions.mine.publish", {
|
|
620
|
+
ownedRef: input.ownedRef,
|
|
621
|
+
name: input.name,
|
|
622
|
+
description: input.description,
|
|
623
|
+
version: input.version,
|
|
624
|
+
visibility: input.visibility,
|
|
625
|
+
...input.changelog === void 0 || input.changelog.trim() === "" ? {} : { changelog: input.changelog },
|
|
626
|
+
...githubRepositoryUrl === void 0 ? {} : { githubRepositoryUrl },
|
|
627
|
+
clientMutationId: input.clientMutationId
|
|
628
|
+
}, signal);
|
|
629
|
+
}
|
|
630
|
+
async updateExtensionMetadata(extensionId, input, signal) {
|
|
631
|
+
const name = input.name.trim();
|
|
632
|
+
const description = input.description.trim();
|
|
633
|
+
if (extensionId.trim() === "" || name === "" || [...name].length > 120 || [...description].length > 2e3 || input.visibility !== "private" && input.visibility !== "public") throw new TypeError("Arkme extension metadata or visibility is invalid");
|
|
634
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(input.clientMutationId)) throw new TypeError("Arkme extension metadata mutation id must be a UUID");
|
|
635
|
+
return await this.call("extensions.metadata.update", {
|
|
636
|
+
extensionId: extensionId.trim(),
|
|
637
|
+
name,
|
|
638
|
+
description,
|
|
639
|
+
visibility: input.visibility,
|
|
640
|
+
clientMutationId: input.clientMutationId
|
|
641
|
+
}, signal);
|
|
642
|
+
}
|
|
643
|
+
async rotateExtensionShare(extensionId, clientMutationId, signal) {
|
|
644
|
+
if (extensionId.trim() === "" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(clientMutationId)) throw new TypeError("Arkme extension share rotation input is invalid");
|
|
645
|
+
return await this.call("extensions.share.rotate", {
|
|
646
|
+
extensionId: extensionId.trim(),
|
|
647
|
+
clientMutationId
|
|
648
|
+
}, signal);
|
|
649
|
+
}
|
|
650
|
+
async extensionShareDetail(shareRef, signal) {
|
|
651
|
+
const normalized = shareRef.trim();
|
|
652
|
+
if (!/^extshare_[0-9a-f]{32}$/.test(normalized)) throw new TypeError("Arkme extension share reference is invalid");
|
|
653
|
+
return await this.call("extensions.share.detail", { shareRef: normalized }, signal);
|
|
654
|
+
}
|
|
655
|
+
async extensionShareCatalogDetail(shareRef, signal) {
|
|
656
|
+
const normalized = shareRef.trim();
|
|
657
|
+
if (!/^extshare_[0-9a-f]{32}$/.test(normalized)) throw new TypeError("Arkme extension share reference is invalid");
|
|
658
|
+
return await this.call("extensions.share.resolve", { shareRef: normalized }, signal);
|
|
659
|
+
}
|
|
660
|
+
/** Read one current-user Arkme image through the authenticated Provider without exposing a signed OSS URL. */
|
|
661
|
+
async readImage(imageRef, signal) {
|
|
662
|
+
if (imageRef.trim() === "") throw new TypeError("Arkme image reference must not be empty");
|
|
663
|
+
return await this.call("image.read", { imageRef }, signal);
|
|
664
|
+
}
|
|
665
|
+
/** Convert a Provider image payload into a browser-renderable data URL. */
|
|
666
|
+
imageDataUrl(image) {
|
|
667
|
+
return `data:${image.mediaType};base64,${image.dataBase64}`;
|
|
668
|
+
}
|
|
669
|
+
/** Read the public World feed through the authenticated Provider boundary. */
|
|
670
|
+
async worldFeed(options = {}) {
|
|
671
|
+
return await this.call("world.feed", {
|
|
672
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
673
|
+
...options.offset === void 0 ? {} : { offset: options.offset }
|
|
674
|
+
}, options.signal);
|
|
675
|
+
}
|
|
676
|
+
/** Read the current account's public World posts through the authenticated Provider boundary. */
|
|
677
|
+
async myWorldFeed(options = {}) {
|
|
678
|
+
return await this.call("world.mine", {
|
|
679
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
680
|
+
...options.offset === void 0 ? {} : { offset: options.offset }
|
|
681
|
+
}, options.signal);
|
|
682
|
+
}
|
|
683
|
+
/** Read one user's public World homepage through the authenticated Provider boundary. */
|
|
684
|
+
async userWorldFeed(userId, options = {}) {
|
|
685
|
+
if (!Number.isSafeInteger(userId) || userId <= 0) throw new TypeError("Arkme World userId must be a positive integer");
|
|
686
|
+
return await this.call("world.user", {
|
|
687
|
+
userId,
|
|
688
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
689
|
+
...options.offset === void 0 ? {} : { offset: options.offset }
|
|
690
|
+
}, options.signal);
|
|
691
|
+
}
|
|
692
|
+
/** Open or reuse a private chat with a non-self World author from an unchanged authorRef. */
|
|
693
|
+
async openWorldAuthorPrivateChat(authorRef, signal) {
|
|
694
|
+
if (authorRef.trim() === "") throw new TypeError("Arkme World author reference must not be empty");
|
|
695
|
+
return await this.call("chat.world.private.open", { authorRef }, signal);
|
|
696
|
+
}
|
|
697
|
+
/** Open or reuse the same private chat used by the mobile "联系作者" entry. */
|
|
698
|
+
async openOfficialAuthorPrivateChat(signal) {
|
|
699
|
+
return await this.call("chat.official-author.private.open", {}, signal);
|
|
700
|
+
}
|
|
701
|
+
/** Read the browser-safe public profile for the mobile "联系作者" entry without creating a chat. */
|
|
702
|
+
async officialAuthorProfile(signal) {
|
|
703
|
+
return await this.call("chat.official-author.profile", {}, signal);
|
|
704
|
+
}
|
|
705
|
+
/** Resolve viewer-local labels for visible World authors without exposing their user IDs. */
|
|
706
|
+
async worldAuthorLabels(authorRefs, signal) {
|
|
707
|
+
const normalized = [...new Set(authorRefs.map((value) => value.trim()).filter((value) => value !== ""))];
|
|
708
|
+
if (normalized.length === 0) return [];
|
|
709
|
+
return await this.call("world.author-labels", { authorRefs: normalized.slice(0, 20) }, signal);
|
|
710
|
+
}
|
|
711
|
+
async worldVoiceprintPlaybackAvailability(recordRefs, signal) {
|
|
712
|
+
const normalized = [...new Set(recordRefs.map((value) => value.trim()).filter((value) => value !== ""))];
|
|
713
|
+
if (normalized.length === 0) return { items: [] };
|
|
714
|
+
return await this.call("world.voiceprint.availability", { recordRefs: normalized.slice(0, 20) }, signal);
|
|
715
|
+
}
|
|
716
|
+
async generateWorldVoiceprintPlayback(input, signal) {
|
|
717
|
+
if (input.recordRef.trim() === "") throw new TypeError("Arkme World record reference must not be empty");
|
|
718
|
+
return await this.call("world.voiceprint.playback.generate", {
|
|
719
|
+
recordRef: input.recordRef,
|
|
720
|
+
...input.chunkIndex === void 0 ? {} : { chunkIndex: input.chunkIndex }
|
|
721
|
+
}, signal);
|
|
722
|
+
}
|
|
723
|
+
async inviteWorldVoiceprint(recordRef, signal) {
|
|
724
|
+
if (recordRef.trim() === "") throw new TypeError("Arkme World record reference must not be empty");
|
|
725
|
+
return await this.call("world.voiceprint.invite", { recordRef }, signal);
|
|
726
|
+
}
|
|
727
|
+
async worldVoiceprintSocialContext(recordRef, options = {}) {
|
|
728
|
+
if (recordRef.trim() === "") throw new TypeError("Arkme World record reference must not be empty");
|
|
729
|
+
return await this.call("world.voiceprint.social-context", {
|
|
730
|
+
recordRef,
|
|
731
|
+
...options.forceRefresh === void 0 ? {} : { forceRefresh: options.forceRefresh }
|
|
732
|
+
}, options.signal);
|
|
733
|
+
}
|
|
734
|
+
/** Read comments and replies for one Provider-issued World record reference. */
|
|
735
|
+
async worldInteractions(recordRef, options = {}) {
|
|
736
|
+
if (recordRef.trim() === "") throw new TypeError("Arkme World record reference must not be empty");
|
|
737
|
+
return await this.call("world.interactions.list", {
|
|
738
|
+
recordRef,
|
|
739
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
740
|
+
...options.offset === void 0 ? {} : { offset: options.offset }
|
|
741
|
+
}, options.signal);
|
|
742
|
+
}
|
|
743
|
+
/** Publish a text comment or reply using a caller-stable mutation id. */
|
|
744
|
+
async createWorldTextInteraction(input, signal) {
|
|
745
|
+
if (input.targetRef.trim() === "" || input.textContent.trim() === "" || input.clientMutationId.trim() === "") throw new TypeError("Arkme World interaction target, text, and mutation id must not be empty");
|
|
746
|
+
return await this.call("world.interactions.create-text", {
|
|
747
|
+
targetRef: input.targetRef,
|
|
748
|
+
textContent: input.textContent,
|
|
749
|
+
clientMutationId: input.clientMutationId
|
|
750
|
+
}, signal);
|
|
751
|
+
}
|
|
752
|
+
async publishWorldText(input, signal) {
|
|
753
|
+
if (input.clientMutationId.trim() === "" || input.textContent.trim() === "") throw new TypeError("Arkme World publish text and mutation id must not be empty");
|
|
754
|
+
return await this.call("world.publish-text", {
|
|
755
|
+
clientMutationId: input.clientMutationId,
|
|
756
|
+
textContent: input.textContent
|
|
757
|
+
}, signal);
|
|
758
|
+
}
|
|
759
|
+
async publishWorldFileAssets(input, signal) {
|
|
760
|
+
if (input.clientMutationId.trim() === "" || input.textContent.trim() === "" || input.fileAssets.length === 0) throw new TypeError("Arkme World publish text, mutation id, and file assets must not be empty");
|
|
761
|
+
return await this.call("world.publish-file-assets", {
|
|
762
|
+
clientMutationId: input.clientMutationId,
|
|
763
|
+
textContent: input.textContent,
|
|
764
|
+
fileAssets: input.fileAssets
|
|
765
|
+
}, signal);
|
|
766
|
+
}
|
|
767
|
+
/** Resolve one Provider-issued World image ref without exposing its signed source URL. */
|
|
768
|
+
async readWorldImage(imageRef, signal) {
|
|
769
|
+
if (imageRef.trim() === "") throw new TypeError("Arkme World image reference must not be empty");
|
|
770
|
+
return await this.call("world.image.read", { imageRef }, signal);
|
|
771
|
+
}
|
|
772
|
+
/** Read the current account's Arrangement owner projection. */
|
|
773
|
+
async arrangements(options = {}) {
|
|
774
|
+
return await this.call("arrangements.list", {
|
|
775
|
+
...options.status === void 0 ? {} : { status: options.status },
|
|
776
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
777
|
+
...options.offset === void 0 ? {} : { offset: options.offset }
|
|
778
|
+
}, options.signal);
|
|
779
|
+
}
|
|
780
|
+
/** Read public reviews and rating summary for one extension. */
|
|
781
|
+
async extensionReviews(extensionId, options = {}) {
|
|
782
|
+
if (extensionId.trim() === "") throw new TypeError("Arkme extension id must not be empty");
|
|
783
|
+
return await this.call("extensions.reviews.list", {
|
|
784
|
+
extensionId,
|
|
785
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
786
|
+
...options.offset === void 0 ? {} : { offset: options.offset }
|
|
787
|
+
}, options.signal);
|
|
788
|
+
}
|
|
789
|
+
/** Read one Arrangement through a Provider-issued, account-bound reference. */
|
|
790
|
+
async arrangementDetail(arrangementRef, signal) {
|
|
791
|
+
if (arrangementRef.trim() === "") throw new TypeError("Arrangement reference must not be empty");
|
|
792
|
+
return await this.call("arrangements.detail", { arrangementRef }, signal);
|
|
793
|
+
}
|
|
794
|
+
async arrangementReminders(options = {}) {
|
|
795
|
+
return await this.call("arrangements.reminders.list", {
|
|
796
|
+
...options.unreadOnly === void 0 ? {} : { unreadOnly: options.unreadOnly },
|
|
797
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
798
|
+
...options.offset === void 0 ? {} : { offset: options.offset }
|
|
799
|
+
}, options.signal);
|
|
800
|
+
}
|
|
801
|
+
async arrangementReminderSummary(signal) {
|
|
802
|
+
return await this.call("arrangements.reminders.summary", void 0, signal);
|
|
803
|
+
}
|
|
804
|
+
async mutateArrangement(arrangementRef, intent, signal) {
|
|
805
|
+
const normalizedRef = arrangementRef.trim();
|
|
806
|
+
if (normalizedRef === "") throw new TypeError("Arrangement reference must not be empty");
|
|
807
|
+
if (![
|
|
808
|
+
"start-follow",
|
|
809
|
+
"cancel-follow",
|
|
810
|
+
"complete",
|
|
811
|
+
"cancel-complete",
|
|
812
|
+
"delete"
|
|
813
|
+
].includes(intent)) throw new TypeError("Arrangement mutation intent is invalid");
|
|
814
|
+
return await this.call("arrangements.mutate", {
|
|
815
|
+
arrangementRef: normalizedRef,
|
|
816
|
+
intent
|
|
817
|
+
}, signal);
|
|
818
|
+
}
|
|
819
|
+
async setArrangementReminderEnabled(arrangementRef, enabled, signal) {
|
|
820
|
+
const normalizedRef = arrangementRef.trim();
|
|
821
|
+
if (normalizedRef === "") throw new TypeError("Arrangement reference must not be empty");
|
|
822
|
+
return await this.call("arrangements.reminder-enabled", {
|
|
823
|
+
arrangementRef: normalizedRef,
|
|
824
|
+
enabled
|
|
825
|
+
}, signal);
|
|
826
|
+
}
|
|
827
|
+
async markArrangementRemindersRead(eventRefs, signal) {
|
|
828
|
+
const normalizedRefs = eventRefs.map((eventRef) => eventRef.trim());
|
|
829
|
+
if (normalizedRefs.length === 0 || normalizedRefs.some((eventRef) => eventRef === "")) throw new TypeError("Reminder references must not be empty");
|
|
830
|
+
if (new Set(normalizedRefs).size !== normalizedRefs.length) throw new TypeError("Reminder references must be unique");
|
|
831
|
+
if (normalizedRefs.length > 50) throw new TypeError("At most 50 reminder references are allowed");
|
|
832
|
+
return await this.call("arrangements.reminders.mark-read", { eventRefs: normalizedRefs }, signal);
|
|
833
|
+
}
|
|
834
|
+
async markAllArrangementRemindersRead(signal) {
|
|
835
|
+
return await this.call("arrangements.reminders.mark-all-read", void 0, signal);
|
|
836
|
+
}
|
|
837
|
+
async clearArrangementReminders(signal) {
|
|
838
|
+
return await this.call("arrangements.reminders.clear", void 0, signal);
|
|
839
|
+
}
|
|
840
|
+
/** Create a top-level review or reply through the account-bound Host owner. */
|
|
841
|
+
async createExtensionReview(input, signal) {
|
|
842
|
+
if (input.extensionId.trim() === "" || input.textContent.trim() === "" || input.clientMutationId.trim() === "") throw new TypeError("Arkme extension review id, text, and mutation id must not be empty");
|
|
843
|
+
return await this.call("extensions.reviews.create", {
|
|
844
|
+
extensionId: input.extensionId,
|
|
845
|
+
textContent: input.textContent,
|
|
846
|
+
...input.rating === void 0 ? {} : { rating: input.rating },
|
|
847
|
+
...input.parentReviewRef === void 0 ? {} : { parentReviewRef: input.parentReviewRef },
|
|
848
|
+
clientMutationId: input.clientMutationId
|
|
849
|
+
}, signal);
|
|
850
|
+
}
|
|
851
|
+
async listSources(directory, options = {}) {
|
|
852
|
+
if (options.localFirst === true && (await this.capabilities(options.signal)).features.localFirstDirectory !== true) throw new Error("当前 Provider 不支持本地会话目录");
|
|
853
|
+
return await this.call("sources.list", {
|
|
854
|
+
directory,
|
|
855
|
+
...options.localFirst === void 0 ? {} : { localFirst: options.localFirst },
|
|
856
|
+
...options.refresh === void 0 ? {} : { refresh: options.refresh },
|
|
857
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
858
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor }
|
|
859
|
+
}, options.signal);
|
|
860
|
+
}
|
|
861
|
+
async setBotDirectoryPin(botRef, pinned, signal) {
|
|
862
|
+
if ((await this.capabilities(signal)).features.localFirstDirectory !== true) throw new Error("当前 Provider 不支持本地会话目录");
|
|
863
|
+
await this.call("conversation.directory.bot-pin", {
|
|
864
|
+
botRef,
|
|
865
|
+
pinned
|
|
866
|
+
}, signal);
|
|
867
|
+
}
|
|
868
|
+
/** Read or explicitly set the existing topic home preference. */
|
|
869
|
+
async topicHomeVisibility(sourceRef, showInHome, signal) {
|
|
870
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme topic source reference must not be empty");
|
|
871
|
+
return await this.call("topic.home-visibility", {
|
|
872
|
+
sourceRef,
|
|
873
|
+
...showInHome === void 0 ? {} : { showInHome }
|
|
874
|
+
}, signal);
|
|
875
|
+
}
|
|
876
|
+
async listGroupMembers(sourceRef, signal) {
|
|
877
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme group source reference must not be empty");
|
|
878
|
+
return await this.call("group.members", {
|
|
879
|
+
sourceRef,
|
|
880
|
+
activeOnly: true
|
|
881
|
+
}, signal);
|
|
882
|
+
}
|
|
883
|
+
async groupAiPolishSettings(sourceRef, signal) {
|
|
884
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme group source reference must not be empty");
|
|
885
|
+
return await this.call("source.ai-polish.settings", { sourceRef }, signal);
|
|
886
|
+
}
|
|
887
|
+
async generateGroupAiPolishRule(sourceRef, requirement, options = {}) {
|
|
888
|
+
if (sourceRef.trim() === "" || requirement.trim() === "") throw new TypeError("Arkme group source reference and polish requirement must not be empty");
|
|
889
|
+
return await this.call("source.ai-polish.generate-rule", {
|
|
890
|
+
sourceRef,
|
|
891
|
+
requirement,
|
|
892
|
+
...options.threadMessages === void 0 ? {} : { threadMessages: options.threadMessages },
|
|
893
|
+
...options.targetRuleRef === void 0 || options.targetRuleRef.trim() === "" ? {} : { targetRuleRef: options.targetRuleRef.trim() }
|
|
894
|
+
}, options.signal);
|
|
895
|
+
}
|
|
896
|
+
async prepareEnableGroupAiPolishRule(sourceRef, ruleRef, signal) {
|
|
897
|
+
if (sourceRef.trim() === "" || ruleRef.trim() === "") throw new TypeError("Arkme group source and AI polish rule references must not be empty");
|
|
898
|
+
return await this.call("source.ai-polish.prepare-enable", {
|
|
899
|
+
sourceRef,
|
|
900
|
+
ruleRef
|
|
901
|
+
}, signal);
|
|
902
|
+
}
|
|
903
|
+
async confirmEnableGroupAiPolish(confirmationRef, signal) {
|
|
904
|
+
if (confirmationRef.trim() === "") throw new TypeError("Arkme AI polish confirmation reference must not be empty");
|
|
905
|
+
return await this.call("source.ai-polish.confirm-enable", { confirmationRef }, signal);
|
|
906
|
+
}
|
|
907
|
+
async prepareDisableGroupAiPolish(sourceRef, signal) {
|
|
908
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme group source reference must not be empty");
|
|
909
|
+
return await this.call("source.ai-polish.prepare-disable", { sourceRef }, signal);
|
|
910
|
+
}
|
|
911
|
+
async confirmDisableGroupAiPolish(confirmationRef, signal) {
|
|
912
|
+
if (confirmationRef.trim() === "") throw new TypeError("Arkme AI polish confirmation reference must not be empty");
|
|
913
|
+
return await this.call("source.ai-polish.confirm-disable", { confirmationRef }, signal);
|
|
914
|
+
}
|
|
915
|
+
async cachedSourceMembers(sourceRef, signal) {
|
|
916
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme source reference must not be empty");
|
|
917
|
+
return await this.call("source.members.cached", { sourceRef }, signal);
|
|
918
|
+
}
|
|
919
|
+
async pageSourceMembers(sourceRef, options = {}) {
|
|
920
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme source reference must not be empty");
|
|
921
|
+
if (options.limit !== void 0 && (!Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 100)) throw new TypeError("Arkme member page limit must be 1-100");
|
|
922
|
+
return await this.call("source.members.page", {
|
|
923
|
+
sourceRef,
|
|
924
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor },
|
|
925
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
926
|
+
}, options.signal);
|
|
927
|
+
}
|
|
928
|
+
async sourceMembersPresentation(sourceRef, memberRefs, signal) {
|
|
929
|
+
if (!sourceRef.trim() || memberRefs.length < 1 || memberRefs.length > 50 || memberRefs.some((ref) => !ref.trim())) throw new TypeError("Arkme member presentation needs 1-50 member references");
|
|
930
|
+
return await this.call("source.members.presentation", {
|
|
931
|
+
sourceRef,
|
|
932
|
+
memberRefs: [...memberRefs]
|
|
933
|
+
}, signal);
|
|
934
|
+
}
|
|
935
|
+
async listSourceMembers(sourceRef, signal) {
|
|
936
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme chat source reference must not be empty");
|
|
937
|
+
return await this.call("source.members", {
|
|
938
|
+
sourceRef,
|
|
939
|
+
activeOnly: true
|
|
940
|
+
}, signal);
|
|
941
|
+
}
|
|
942
|
+
async sourceMemberRecords(sourceRef, memberRef, mode, options = {}) {
|
|
943
|
+
if (sourceRef.trim() === "" || memberRef.trim() === "" || !["owner", "mentioned"].includes(mode)) throw new TypeError("Arkme source, member reference, and member-record mode must be valid");
|
|
944
|
+
return await this.call("source.member-records", {
|
|
945
|
+
sourceRef,
|
|
946
|
+
memberRef,
|
|
947
|
+
mode,
|
|
948
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
949
|
+
...options.beforeSequence === void 0 ? {} : { beforeSequence: options.beforeSequence }
|
|
950
|
+
}, options.signal);
|
|
951
|
+
}
|
|
952
|
+
async listGroupMemberCandidates(sourceRef, options = {}) {
|
|
953
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme group source reference must not be empty");
|
|
954
|
+
return await this.call("group.member-candidates", {
|
|
955
|
+
sourceRef,
|
|
956
|
+
...options.query === void 0 ? {} : { query: options.query },
|
|
957
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
958
|
+
...options.groupSourceRefs === void 0 ? {} : { groupSourceRefs: options.groupSourceRefs.map((value) => value.trim()).filter((value) => value !== "") }
|
|
959
|
+
}, options.signal);
|
|
960
|
+
}
|
|
961
|
+
async groupInvitePreview(sourceRef, signal) {
|
|
962
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme group source reference must not be empty");
|
|
963
|
+
return await this.call("group.invite-preview", { sourceRef }, signal);
|
|
964
|
+
}
|
|
965
|
+
async addGroupMembers(sourceRef, candidateRefs, signal) {
|
|
966
|
+
const refs = candidateRefs.map((value) => value.trim());
|
|
967
|
+
if (sourceRef.trim() === "" || refs.length === 0 || refs.some((value) => value === "")) throw new TypeError("Arkme group source and candidate references must not be empty");
|
|
968
|
+
return await this.call("group.members.add", {
|
|
969
|
+
sourceRef,
|
|
970
|
+
candidateRefs: refs
|
|
971
|
+
}, signal);
|
|
972
|
+
}
|
|
973
|
+
async groupSelfNickname(sourceRef, signal) {
|
|
974
|
+
if ((await this.capabilities(signal)).features.groupSelfNickname !== true) throw new Error("当前 Provider 不支持群昵称");
|
|
975
|
+
return await this.call("group.self-nickname", { sourceRef: sourceRef.trim() }, signal);
|
|
976
|
+
}
|
|
977
|
+
async setGroupSelfNickname(sourceRef, nickname, signal) {
|
|
978
|
+
if ((await this.capabilities(signal)).features.groupSelfNickname !== true) throw new Error("当前 Provider 不支持群昵称");
|
|
979
|
+
return await this.call("group.self-nickname.set", {
|
|
980
|
+
sourceRef: sourceRef.trim(),
|
|
981
|
+
nickname
|
|
982
|
+
}, signal);
|
|
983
|
+
}
|
|
984
|
+
async removeGroupMember(sourceRef, memberRef, options = {}) {
|
|
985
|
+
if (sourceRef.trim() === "" || memberRef.trim() === "") throw new TypeError("Arkme group source and member references must not be empty");
|
|
986
|
+
return await this.call("group.member-remove", {
|
|
987
|
+
sourceRef: sourceRef.trim(),
|
|
988
|
+
memberRef: memberRef.trim(),
|
|
989
|
+
preventRejoin: options.preventRejoin === true
|
|
990
|
+
}, options.signal);
|
|
991
|
+
}
|
|
992
|
+
async listGroupJoinRestrictions(sourceRef, options = {}) {
|
|
993
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme group source reference must not be empty");
|
|
994
|
+
return await this.call("group.join-restrictions", {
|
|
995
|
+
sourceRef: sourceRef.trim(),
|
|
996
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor.trim() },
|
|
997
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
998
|
+
}, options.signal);
|
|
999
|
+
}
|
|
1000
|
+
async setGroupJoinRestriction(sourceRef, memberRef, restricted, signal) {
|
|
1001
|
+
if (sourceRef.trim() === "" || memberRef.trim() === "" || typeof restricted !== "boolean") throw new TypeError("Arkme group source, member reference, and restriction are required");
|
|
1002
|
+
return await this.call("group.join-restriction.set", {
|
|
1003
|
+
sourceRef: sourceRef.trim(),
|
|
1004
|
+
memberRef: memberRef.trim(),
|
|
1005
|
+
restricted
|
|
1006
|
+
}, signal);
|
|
1007
|
+
}
|
|
1008
|
+
async listGroupBots(sourceRef, signal) {
|
|
1009
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme group source reference must not be empty");
|
|
1010
|
+
return await this.call("group.bots", { sourceRef }, signal);
|
|
1011
|
+
}
|
|
1012
|
+
async addGroupBot(sourceRef, botRef, signal) {
|
|
1013
|
+
if (sourceRef.trim() === "" || botRef.trim() === "") throw new TypeError("Arkme group source and Bot references must not be empty");
|
|
1014
|
+
return await this.call("group.bot.add", {
|
|
1015
|
+
sourceRef,
|
|
1016
|
+
botRef
|
|
1017
|
+
}, signal);
|
|
1018
|
+
}
|
|
1019
|
+
async readSource(sourceRef, options = {}) {
|
|
1020
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme source reference must not be empty");
|
|
1021
|
+
return await this.call("source.timeline", {
|
|
1022
|
+
sourceRef,
|
|
1023
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
1024
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor }
|
|
1025
|
+
}, options.signal);
|
|
1026
|
+
}
|
|
1027
|
+
async readSourceAround(sourceRef, itemUid, recordOwnerUserId, options = {}) {
|
|
1028
|
+
if (sourceRef.trim() === "" || itemUid.trim() === "" || recordOwnerId(recordOwnerUserId) === 0) throw new TypeError("Arkme timeline around requires a source, record uid, and record owner");
|
|
1029
|
+
return await this.call("source.timeline-around", {
|
|
1030
|
+
sourceRef,
|
|
1031
|
+
itemUid,
|
|
1032
|
+
recordOwnerUserId,
|
|
1033
|
+
...options.beforeLimit === void 0 ? {} : { beforeLimit: options.beforeLimit },
|
|
1034
|
+
...options.afterLimit === void 0 ? {} : { afterLimit: options.afterLimit }
|
|
1035
|
+
}, options.signal);
|
|
1036
|
+
}
|
|
1037
|
+
async messageReadReceiptSummaries(sourceRef, items, signal) {
|
|
1038
|
+
const normalizedSourceRef = sourceRef.trim();
|
|
1039
|
+
if (normalizedSourceRef === "" || items.length < 1 || items.length > 50) throw new TypeError(`Arkme message read receipts require one source and 1-${String(50)} messages`);
|
|
1040
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1041
|
+
const normalizedItems = items.map((item) => {
|
|
1042
|
+
const itemUid = item.itemUid.trim();
|
|
1043
|
+
const sequence = Math.trunc(item.sequence);
|
|
1044
|
+
const key = `${itemUid}\u0000${String(sequence)}`;
|
|
1045
|
+
if (itemUid === "" || !Number.isSafeInteger(item.sequence) || sequence <= 0 || seen.has(key)) throw new TypeError("Arkme message read receipt item identity is invalid or duplicated");
|
|
1046
|
+
seen.add(key);
|
|
1047
|
+
return {
|
|
1048
|
+
itemUid,
|
|
1049
|
+
sequence
|
|
1050
|
+
};
|
|
1051
|
+
});
|
|
1052
|
+
return await this.call("source.read-receipts.summary-list", {
|
|
1053
|
+
sourceRef: normalizedSourceRef,
|
|
1054
|
+
items: normalizedItems
|
|
1055
|
+
}, signal);
|
|
1056
|
+
}
|
|
1057
|
+
async messageReadReceiptDetail(sourceRef, itemUid, sequence, signal, options = {}) {
|
|
1058
|
+
const normalizedSourceRef = sourceRef.trim();
|
|
1059
|
+
const normalizedItemUid = itemUid.trim();
|
|
1060
|
+
if (normalizedSourceRef === "" || normalizedItemUid === "" || !Number.isSafeInteger(sequence) || sequence <= 0) throw new TypeError("Arkme group message read receipt identity is invalid");
|
|
1061
|
+
return await this.call("source.read-receipts.detail", {
|
|
1062
|
+
sourceRef: normalizedSourceRef,
|
|
1063
|
+
itemUid: normalizedItemUid,
|
|
1064
|
+
sequence,
|
|
1065
|
+
...options.basicOnly === void 0 ? {} : { basicOnly: options.basicOnly }
|
|
1066
|
+
}, signal);
|
|
1067
|
+
}
|
|
1068
|
+
async sendText(sourceRef, textContent, options = {}) {
|
|
1069
|
+
if (sourceRef.trim() === "" || textContent.trim() === "") throw new TypeError("Arkme source reference and text must not be empty");
|
|
1070
|
+
return await this.call("source.send-text", {
|
|
1071
|
+
sourceRef,
|
|
1072
|
+
textContent,
|
|
1073
|
+
recordUid: options.recordUid ?? crypto.randomUUID(),
|
|
1074
|
+
relationUid: options.relationUid ?? crypto.randomUUID(),
|
|
1075
|
+
...options.agentAuthored === true ? { agentAuthored: true } : {},
|
|
1076
|
+
...options.humanMentions === void 0 ? {} : { humanMentions: options.humanMentions },
|
|
1077
|
+
...options.botMentions === void 0 ? {} : { botMentions: options.botMentions },
|
|
1078
|
+
...options.botRefs === void 0 ? {} : { botRefs: options.botRefs },
|
|
1079
|
+
...expectedUserIdParam(options.expectedUserId)
|
|
1080
|
+
}, options.signal);
|
|
1081
|
+
}
|
|
1082
|
+
async copyMessageLink(sourceRef, actionRefs, signal) {
|
|
1083
|
+
const refs = actionRefs.map((value) => value.trim()).filter((value) => value !== "");
|
|
1084
|
+
if (sourceRef.trim() === "" || refs.length === 0 || refs.length > 100 || new Set(refs).size !== refs.length) throw new TypeError("Arkme message link action references must be 1-100 unique values");
|
|
1085
|
+
return await this.call("source.message-copy-link", {
|
|
1086
|
+
sourceRef,
|
|
1087
|
+
actionRefs: refs
|
|
1088
|
+
}, signal);
|
|
1089
|
+
}
|
|
1090
|
+
async reportMessage(messageRef, reportType, options = {}) {
|
|
1091
|
+
const normalizedMessageRef = messageRef.trim();
|
|
1092
|
+
const reason = options.reason?.trim() ?? "";
|
|
1093
|
+
if (normalizedMessageRef === "" || normalizedMessageRef.length > 4096) throw new TypeError("Arkme message reference must not be empty or exceed 4096 characters");
|
|
1094
|
+
if (![
|
|
1095
|
+
1,
|
|
1096
|
+
2,
|
|
1097
|
+
3,
|
|
1098
|
+
4
|
|
1099
|
+
].includes(reportType) || reportType === 4 && reason === "" || [...reason].length > 500) throw new TypeError("Arkme message report type or reason is invalid");
|
|
1100
|
+
const requestUid = (options.requestUid ?? crypto.randomUUID()).trim().toLowerCase();
|
|
1101
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(requestUid)) throw new TypeError("Arkme message report request UID is invalid");
|
|
1102
|
+
return await this.call("source.message-report", {
|
|
1103
|
+
messageRef: normalizedMessageRef,
|
|
1104
|
+
reportType,
|
|
1105
|
+
...reason === "" ? {} : { reason },
|
|
1106
|
+
requestUid
|
|
1107
|
+
}, options.signal);
|
|
1108
|
+
}
|
|
1109
|
+
async withdrawGroupMessage(messageWithdrawalRef, signal) {
|
|
1110
|
+
const normalized = messageWithdrawalRef.trim();
|
|
1111
|
+
if (normalized === "" || normalized.length > 4096) throw new TypeError("Arkme message withdrawal reference must not be empty or exceed 4096 characters");
|
|
1112
|
+
return await this.call("source.message-withdraw", { messageWithdrawalRef: normalized }, signal);
|
|
1113
|
+
}
|
|
1114
|
+
async resolveMessageCopyLink(sid, signal) {
|
|
1115
|
+
const normalizedSid = sid.trim();
|
|
1116
|
+
if (!/^[0-9A-Za-z]{16}$/.test(normalizedSid)) throw new TypeError("Arkme message copy-link sid must be 16 alphanumeric characters");
|
|
1117
|
+
return await this.call("source.message-copy-link.resolve", { sid: normalizedSid }, signal);
|
|
1118
|
+
}
|
|
1119
|
+
async extendMessageCopyLink(sid, textContent, options = {}) {
|
|
1120
|
+
const normalizedSid = sid.trim();
|
|
1121
|
+
const text = textContent.trim();
|
|
1122
|
+
const itemIndex = Math.trunc(options.itemIndex ?? 0);
|
|
1123
|
+
if (!/^[0-9A-Za-z]{16}$/.test(normalizedSid)) throw new TypeError("Arkme message copy-link sid must be 16 alphanumeric characters");
|
|
1124
|
+
if (text === "") throw new TypeError("Arkme message copy-link extension text must not be empty");
|
|
1125
|
+
if (itemIndex < 0 || itemIndex >= 100) throw new TypeError("Arkme message copy-link item index must be 0-99");
|
|
1126
|
+
return await this.call("source.message-copy-link.extend", {
|
|
1127
|
+
sid: normalizedSid,
|
|
1128
|
+
itemIndex,
|
|
1129
|
+
textContent: text,
|
|
1130
|
+
recordUid: options.recordUid ?? crypto.randomUUID()
|
|
1131
|
+
}, options.signal);
|
|
1132
|
+
}
|
|
1133
|
+
async sourceMessageExtensionContext(sourceRef, messageActionRef, signal) {
|
|
1134
|
+
if (sourceRef.trim() === "" || messageActionRef.trim() === "") throw new TypeError("Arkme source message extension requires a source and action reference");
|
|
1135
|
+
return await this.call("source.message-extension.context", {
|
|
1136
|
+
sourceRef,
|
|
1137
|
+
messageActionRef
|
|
1138
|
+
}, signal);
|
|
1139
|
+
}
|
|
1140
|
+
async extendSourceMessage(sourceRef, messageActionRef, textContent, options = {}) {
|
|
1141
|
+
const fileRefs = [...new Set((options.fileRefs ?? []).map((value) => value.trim()).filter((value) => value !== ""))];
|
|
1142
|
+
if (sourceRef.trim() === "" || messageActionRef.trim() === "" || textContent.trim() === "" && fileRefs.length === 0) throw new TypeError("Arkme source message extension requires a target and text or attachments");
|
|
1143
|
+
if (fileRefs.length > 9) throw new TypeError("Arkme source message extension accepts at most 9 attachments");
|
|
1144
|
+
return await this.call("source.message-extension.extend", {
|
|
1145
|
+
sourceRef,
|
|
1146
|
+
messageActionRef,
|
|
1147
|
+
textContent,
|
|
1148
|
+
recordUid: options.recordUid ?? crypto.randomUUID(),
|
|
1149
|
+
relationUid: options.relationUid ?? crypto.randomUUID(),
|
|
1150
|
+
...options.parentRecordUid?.trim() ? { parentRecordUid: options.parentRecordUid.trim() } : {},
|
|
1151
|
+
fileRefs
|
|
1152
|
+
}, options.signal);
|
|
1153
|
+
}
|
|
1154
|
+
async resolveLinkMetadata(url, signal) {
|
|
1155
|
+
const normalizedUrl = url.trim();
|
|
1156
|
+
if (normalizedUrl === "" || normalizedUrl.length > 2048) throw new TypeError("Arkme link metadata URL must not be empty");
|
|
1157
|
+
return await this.call("source.link-metadata.resolve", { url: normalizedUrl }, signal);
|
|
1158
|
+
}
|
|
1159
|
+
async forwardMessages(sourceRef, actionRefs, options = {}) {
|
|
1160
|
+
const refs = actionRefs.map((value) => value.trim()).filter((value) => value !== "");
|
|
1161
|
+
if (sourceRef.trim() === "" || refs.length === 0 || refs.length > 100 || new Set(refs).size !== refs.length) throw new TypeError("Arkme forward action references must be 1-100 unique values");
|
|
1162
|
+
return await this.call("source.forward-messages", {
|
|
1163
|
+
sourceRef,
|
|
1164
|
+
...options.targetSourceRef === void 0 || options.targetSourceRef.trim() === "" ? {} : { targetSourceRef: options.targetSourceRef.trim() },
|
|
1165
|
+
actionRefs: refs,
|
|
1166
|
+
recordUid: options.recordUid ?? crypto.randomUUID(),
|
|
1167
|
+
relationUid: options.relationUid ?? crypto.randomUUID(),
|
|
1168
|
+
...options.commentText === void 0 || options.commentText.trim() === "" ? {} : { commentText: options.commentText.trim() }
|
|
1169
|
+
}, options.signal);
|
|
1170
|
+
}
|
|
1171
|
+
async relatedRecordingEligibility(sourceRef, signal) {
|
|
1172
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme private-chat source reference must not be empty");
|
|
1173
|
+
return await this.call("related-recordings.eligibility", { sourceRef }, signal);
|
|
1174
|
+
}
|
|
1175
|
+
async relatedRecordings(sourceRef, options = {}) {
|
|
1176
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme private-chat source reference must not be empty");
|
|
1177
|
+
return await this.call("related-recordings.page", {
|
|
1178
|
+
sourceRef,
|
|
1179
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
1180
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor },
|
|
1181
|
+
...options.monthKey === void 0 ? {} : { monthKey: options.monthKey },
|
|
1182
|
+
...options.timezoneOffsetMillis === void 0 ? {} : { timezoneOffsetMillis: options.timezoneOffsetMillis },
|
|
1183
|
+
...options.includeTimeIndex === void 0 ? {} : { includeTimeIndex: options.includeTimeIndex }
|
|
1184
|
+
}, options.signal);
|
|
1185
|
+
}
|
|
1186
|
+
async sendRich(sourceRef, input, options = {}) {
|
|
1187
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme source reference must not be empty");
|
|
1188
|
+
return await this.call("source.send-rich", {
|
|
1189
|
+
sourceRef,
|
|
1190
|
+
...input,
|
|
1191
|
+
recordUid: options.recordUid ?? crypto.randomUUID(),
|
|
1192
|
+
relationUid: options.relationUid ?? crypto.randomUUID(),
|
|
1193
|
+
...expectedUserIdParam(options.expectedUserId)
|
|
1194
|
+
}, options.signal);
|
|
1195
|
+
}
|
|
1196
|
+
async favoriteStickers(signal) {
|
|
1197
|
+
return await this.call("favorite-stickers.list", void 0, signal);
|
|
1198
|
+
}
|
|
1199
|
+
async addFavoriteSticker(item, signal) {
|
|
1200
|
+
if (item.fileAssetUid.trim() === "") throw new TypeError("Arkme favorite sticker asset must not be empty");
|
|
1201
|
+
return await this.call("favorite-stickers.add", { item }, signal);
|
|
1202
|
+
}
|
|
1203
|
+
async sendFavoriteSticker(sourceRef, fileAssetUid, options = {}) {
|
|
1204
|
+
if (sourceRef.trim() === "" || fileAssetUid.trim() === "") throw new TypeError("Arkme sticker destination and asset must not be empty");
|
|
1205
|
+
return await this.call("favorite-stickers.send", {
|
|
1206
|
+
sourceRef,
|
|
1207
|
+
fileAssetUid,
|
|
1208
|
+
recordUid: options.recordUid ?? crypto.randomUUID(),
|
|
1209
|
+
relationUid: options.relationUid ?? crypto.randomUUID()
|
|
1210
|
+
}, options.signal);
|
|
1211
|
+
}
|
|
1212
|
+
async manageFavoriteSticker(fileAssetUid, action, signal) {
|
|
1213
|
+
if (fileAssetUid.trim() === "") throw new TypeError("Arkme favorite sticker asset must not be empty");
|
|
1214
|
+
return await this.call("favorite-stickers.manage", {
|
|
1215
|
+
fileAssetUid,
|
|
1216
|
+
action
|
|
1217
|
+
}, signal);
|
|
1218
|
+
}
|
|
1219
|
+
async longArticleDetail(sourceRef, itemUid, signal) {
|
|
1220
|
+
if (sourceRef.trim() === "" || itemUid.trim() === "") throw new TypeError("Arkme source and article references must not be empty");
|
|
1221
|
+
return await this.call("source.long-article.detail", {
|
|
1222
|
+
sourceRef,
|
|
1223
|
+
itemUid
|
|
1224
|
+
}, signal);
|
|
1225
|
+
}
|
|
1226
|
+
async publishLongArticle(sourceRef, input, signal) {
|
|
1227
|
+
return await this.call("source.long-article.publish", {
|
|
1228
|
+
sourceRef,
|
|
1229
|
+
...input
|
|
1230
|
+
}, signal);
|
|
1231
|
+
}
|
|
1232
|
+
async updateLongArticle(sourceRef, itemUid, input, signal) {
|
|
1233
|
+
if (sourceRef.trim() === "" || itemUid.trim() === "") throw new TypeError("Arkme source and article references must not be empty");
|
|
1234
|
+
return await this.call("source.long-article.update", {
|
|
1235
|
+
sourceRef,
|
|
1236
|
+
itemUid,
|
|
1237
|
+
...input
|
|
1238
|
+
}, signal);
|
|
1239
|
+
}
|
|
1240
|
+
async longArticleDraft(sourceRef, itemUid, signal) {
|
|
1241
|
+
return await this.call("source.long-article.draft.get", {
|
|
1242
|
+
sourceRef,
|
|
1243
|
+
...itemUid === void 0 ? {} : { itemUid }
|
|
1244
|
+
}, signal);
|
|
1245
|
+
}
|
|
1246
|
+
async saveLongArticleDraft(draft, signal) {
|
|
1247
|
+
if (draft.sourceRef.trim() === "") throw new TypeError("Arkme source reference must not be empty");
|
|
1248
|
+
await this.call("source.long-article.draft.put", draft, signal);
|
|
1249
|
+
}
|
|
1250
|
+
async deleteLongArticleDraft(sourceRef, itemUid, signal) {
|
|
1251
|
+
if (sourceRef.trim() === "") throw new TypeError("Arkme source reference must not be empty");
|
|
1252
|
+
await this.call("source.long-article.draft.delete", {
|
|
1253
|
+
sourceRef,
|
|
1254
|
+
...itemUid === void 0 ? {} : { itemUid }
|
|
1255
|
+
}, signal);
|
|
1256
|
+
}
|
|
1257
|
+
async fileCapabilities(signal) {
|
|
1258
|
+
const policy = await this.call("files.capabilities", void 0, signal);
|
|
1259
|
+
if (policy.version !== 1) throw new Error(`Unsupported Arkme file contract version ${String(policy.version)}`);
|
|
1260
|
+
return policy;
|
|
1261
|
+
}
|
|
1262
|
+
async searchFiles(options = {}) {
|
|
1263
|
+
const { signal, ...params } = options;
|
|
1264
|
+
return this.call("files.search", params, signal);
|
|
1265
|
+
}
|
|
1266
|
+
async localFiles(signal) {
|
|
1267
|
+
return this.call("files.local.list", void 0, signal);
|
|
1268
|
+
}
|
|
1269
|
+
async openLocalFileFolder(fileRef, signal) {
|
|
1270
|
+
return this.call("files.local.open-folder", { fileRef }, signal);
|
|
1271
|
+
}
|
|
1272
|
+
async openLocalFile(fileRef, signal) {
|
|
1273
|
+
return this.call("files.local.open", { fileRef }, signal);
|
|
1274
|
+
}
|
|
1275
|
+
async removeLocalFile(fileRef) {
|
|
1276
|
+
return this.call("files.local.remove", { fileRef });
|
|
1277
|
+
}
|
|
1278
|
+
async sendFiles(input) {
|
|
1279
|
+
return this.call("files.send", {
|
|
1280
|
+
...input.content,
|
|
1281
|
+
...input
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
async fileSendTasks(sourceRef, signal) {
|
|
1285
|
+
return this.call("files.send.tasks", sourceRef === void 0 ? {} : { sourceRef }, signal);
|
|
1286
|
+
}
|
|
1287
|
+
async retryFileSend(taskRef) {
|
|
1288
|
+
return this.call("files.send.retry", { taskRef });
|
|
1289
|
+
}
|
|
1290
|
+
async discardFileSend(taskRef) {
|
|
1291
|
+
return this.call("files.send.discard", { taskRef });
|
|
1292
|
+
}
|
|
1293
|
+
async reconcileFileSend(taskRef) {
|
|
1294
|
+
return this.call("files.send.reconcile", { taskRef });
|
|
1295
|
+
}
|
|
1296
|
+
async stageFileBytes(contentBase64, metadata) {
|
|
1297
|
+
return this.call("files.stage-bytes", {
|
|
1298
|
+
contentBase64,
|
|
1299
|
+
...metadata
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
async receiveFile(mediaRef, start = false, signal) {
|
|
1303
|
+
return this.call("files.receive", {
|
|
1304
|
+
mediaRef,
|
|
1305
|
+
start
|
|
1306
|
+
}, signal);
|
|
1307
|
+
}
|
|
1308
|
+
subscribeFileSends(sourceRef, listener, options = {}) {
|
|
1309
|
+
const controller = new AbortController();
|
|
1310
|
+
let timer;
|
|
1311
|
+
const poll = async () => {
|
|
1312
|
+
try {
|
|
1313
|
+
const tasks = await this.fileSendTasks(sourceRef, controller.signal);
|
|
1314
|
+
if (!controller.signal.aborted) listener(tasks);
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
if (!controller.signal.aborted) options.onError?.(error);
|
|
1317
|
+
} finally {
|
|
1318
|
+
if (!controller.signal.aborted) timer = setTimeout(() => {
|
|
1319
|
+
poll();
|
|
1320
|
+
}, Math.max(250, options.intervalMs ?? 750));
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
if (options.immediate !== false) poll();
|
|
1324
|
+
else timer = setTimeout(() => {
|
|
1325
|
+
poll();
|
|
1326
|
+
}, Math.max(250, options.intervalMs ?? 750));
|
|
1327
|
+
return () => {
|
|
1328
|
+
controller.abort();
|
|
1329
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
localFileUrl(fileRef, download = false) {
|
|
1333
|
+
if (!/^arkme-file-v1\.[0-9a-f-]{36}$/.test(fileRef)) throw new TypeError("Invalid local file reference");
|
|
1334
|
+
return `${this.route}/files/local?ref=${encodeURIComponent(fileRef)}${download ? "&download=1" : ""}`;
|
|
1335
|
+
}
|
|
1336
|
+
/** Stage locally. Reference-managed files require a Host-persisted draft or task to retain them beyond seven days. */
|
|
1337
|
+
async stageLongArticleImage(file, options = {}) {
|
|
1338
|
+
const payload = await (await this.fetchImpl(`${this.route}/files/long-article-stage`, {
|
|
1339
|
+
method: "POST",
|
|
1340
|
+
headers: {
|
|
1341
|
+
"Content-Type": file.type || "application/octet-stream",
|
|
1342
|
+
"X-Arkme-File-Name": encodeURIComponent(options.fileName ?? file.name ?? "image"),
|
|
1343
|
+
...expectedUserIdHeaders(options.expectedUserId)
|
|
1344
|
+
},
|
|
1345
|
+
body: file,
|
|
1346
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
1347
|
+
})).json();
|
|
1348
|
+
if (!payload.ok) throw new ArkmeClientError(payload.error);
|
|
1349
|
+
return payload.value;
|
|
1350
|
+
}
|
|
1351
|
+
async stageFile(file, options = {}) {
|
|
1352
|
+
const payload = await (await this.fetchImpl(`${this.route}/files/stage`, {
|
|
1353
|
+
method: "POST",
|
|
1354
|
+
headers: {
|
|
1355
|
+
"Content-Type": file.type || "application/octet-stream",
|
|
1356
|
+
"X-Arkme-File-Name": encodeURIComponent(options.fileName ?? file.name ?? "attachment"),
|
|
1357
|
+
...expectedUserIdHeaders(options.expectedUserId),
|
|
1358
|
+
...options.retention ? { "X-Arkme-File-Retention": options.retention } : {}
|
|
1359
|
+
},
|
|
1360
|
+
body: file,
|
|
1361
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
1362
|
+
})).json();
|
|
1363
|
+
if (!payload.ok) throw new ArkmeClientError(payload.error);
|
|
1364
|
+
return payload.value;
|
|
1365
|
+
}
|
|
1366
|
+
async upload(file, options = {}) {
|
|
1367
|
+
const fileName = (options.fileName ?? file.name ?? "attachment").trim();
|
|
1368
|
+
const payload = await (await this.fetchImpl(`${this.route}/upload`, {
|
|
1369
|
+
method: "POST",
|
|
1370
|
+
headers: {
|
|
1371
|
+
"Content-Type": file.type || "application/octet-stream",
|
|
1372
|
+
"X-Arkme-File-Name": encodeURIComponent(fileName),
|
|
1373
|
+
...expectedUserIdHeaders(options.expectedUserId)
|
|
1374
|
+
},
|
|
1375
|
+
body: file,
|
|
1376
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
1377
|
+
})).json();
|
|
1378
|
+
if (!payload.ok) throw new ArkmeClientError(payload.error);
|
|
1379
|
+
return payload.value;
|
|
1380
|
+
}
|
|
1381
|
+
mediaUrl(mediaRef) {
|
|
1382
|
+
if (mediaRef.trim() === "") throw new TypeError("Arkme media reference must not be empty");
|
|
1383
|
+
return `${this.route}/media?ref=${encodeURIComponent(mediaRef)}`;
|
|
1384
|
+
}
|
|
1385
|
+
async snapshot(options = {}) {
|
|
1386
|
+
return await this.call(options.refresh === true ? "records.refresh" : "records.cache", void 0, options.signal);
|
|
1387
|
+
}
|
|
1388
|
+
async calendarBuckets(options) {
|
|
1389
|
+
return await this.call("calendar.buckets", {
|
|
1390
|
+
startDate: options.startDate,
|
|
1391
|
+
endDate: options.endDate,
|
|
1392
|
+
...options.sourceRef === void 0 ? {} : { sourceRef: options.sourceRef },
|
|
1393
|
+
...options.timezone === void 0 ? {} : { timezone: options.timezone }
|
|
1394
|
+
}, options.signal);
|
|
1395
|
+
}
|
|
1396
|
+
async calendarRecords(options) {
|
|
1397
|
+
return await this.call("calendar.records", {
|
|
1398
|
+
bucketDate: options.bucketDate,
|
|
1399
|
+
...options.sourceRef === void 0 ? {} : { sourceRef: options.sourceRef },
|
|
1400
|
+
...options.oldestFirst === void 0 ? {} : { oldestFirst: options.oldestFirst },
|
|
1401
|
+
...options.timezone === void 0 ? {} : { timezone: options.timezone },
|
|
1402
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
1403
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor }
|
|
1404
|
+
}, options.signal);
|
|
1405
|
+
}
|
|
1406
|
+
/** Read a device-location fact using a short-lived capability from the personal calendar. */
|
|
1407
|
+
async calendarRecordLocation(locationRef, signal) {
|
|
1408
|
+
return await this.call("calendar.record-location", { locationRef }, signal);
|
|
1409
|
+
}
|
|
1410
|
+
/** Service-owned daily chat counts and exact first-message anchors, shared with Flutter. */
|
|
1411
|
+
async calendarChatStatistics(options) {
|
|
1412
|
+
const { signal, ...params } = options;
|
|
1413
|
+
return await this.call("calendar.chat-statistics", params, signal);
|
|
1414
|
+
}
|
|
1415
|
+
/** Search one viewer-owned conversation directory page by remark or nickname. */
|
|
1416
|
+
async searchConversationNames(query, options = {}) {
|
|
1417
|
+
const { signal, ...params } = options;
|
|
1418
|
+
return await this.call("search.conversations", {
|
|
1419
|
+
query,
|
|
1420
|
+
...params
|
|
1421
|
+
}, signal);
|
|
1422
|
+
}
|
|
1423
|
+
/** Search current-account server records, including retained DSH navigation identity. */
|
|
1424
|
+
async searchRemote(query, options = {}) {
|
|
1425
|
+
const { signal, ...params } = options;
|
|
1426
|
+
if ((await this.capabilities(signal)).features.remoteRecordSearch !== true) throw new Error("当前 Provider 不支持远端快记搜索");
|
|
1427
|
+
return await this.call("search.records", {
|
|
1428
|
+
query,
|
|
1429
|
+
...params
|
|
1430
|
+
}, signal);
|
|
1431
|
+
}
|
|
1432
|
+
async search(query, options = {}) {
|
|
1433
|
+
return await this.call("records.search", {
|
|
1434
|
+
query,
|
|
1435
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
1436
|
+
...options.beforeMillis === void 0 ? {} : { beforeMillis: options.beforeMillis },
|
|
1437
|
+
...options.syncAll === void 0 ? {} : { syncAll: options.syncAll }
|
|
1438
|
+
}, options.signal);
|
|
1439
|
+
}
|
|
1440
|
+
/** List the signed-in user's image library without exposing storage URLs. */
|
|
1441
|
+
async images(options = {}) {
|
|
1442
|
+
return await this.call("images.list", {
|
|
1443
|
+
...options.limit === void 0 ? {} : { limit: options.limit },
|
|
1444
|
+
...options.cursor?.trim() ? { cursor: options.cursor.trim() } : {}
|
|
1445
|
+
}, options.signal);
|
|
1446
|
+
}
|
|
1447
|
+
async createText(textContent, options = {}) {
|
|
1448
|
+
return await this.call("records.create", {
|
|
1449
|
+
recordUid: options.recordUid ?? crypto.randomUUID(),
|
|
1450
|
+
textContent
|
|
1451
|
+
}, options.signal);
|
|
1452
|
+
}
|
|
1453
|
+
async tags(options = {}) {
|
|
1454
|
+
return await this.call("records.tags.list", {
|
|
1455
|
+
limit: options.limit ?? 100,
|
|
1456
|
+
...options.query === void 0 ? {} : { query: options.query },
|
|
1457
|
+
...options.cursor === void 0 ? {} : { cursor: options.cursor }
|
|
1458
|
+
}, options.signal);
|
|
1459
|
+
}
|
|
1460
|
+
async tagRecords(normalizedTag, options = {}) {
|
|
1461
|
+
return await this.call("records.tags.query", {
|
|
1462
|
+
normalizedTag,
|
|
1463
|
+
limit: options.limit ?? 50,
|
|
1464
|
+
...options.cursorSendAt === void 0 ? {} : { cursorSendAt: options.cursorSendAt },
|
|
1465
|
+
...options.cursorRecordUid?.trim() ? { cursorRecordUid: options.cursorRecordUid.trim() } : {}
|
|
1466
|
+
}, options.signal);
|
|
1467
|
+
}
|
|
1468
|
+
async outbox(signal) {
|
|
1469
|
+
return await this.call("records.outbox", void 0, signal);
|
|
1470
|
+
}
|
|
1471
|
+
async retry(recordUid, signal) {
|
|
1472
|
+
return await this.call("records.retry", { recordUid }, signal);
|
|
1473
|
+
}
|
|
1474
|
+
subscribe(listener, options = {}) {
|
|
1475
|
+
const intervalMs = Math.min(6e4, Math.max(500, Math.trunc(options.intervalMs ?? 1e3)));
|
|
1476
|
+
let stopped = false;
|
|
1477
|
+
let timeout;
|
|
1478
|
+
let previousKey = "";
|
|
1479
|
+
const poll = async () => {
|
|
1480
|
+
if (stopped) return;
|
|
1481
|
+
try {
|
|
1482
|
+
const state = await this.state();
|
|
1483
|
+
if (stopped) return;
|
|
1484
|
+
const key = `${state.authStatus}:${String(state.userId ?? "")}:${String(state.revision)}`;
|
|
1485
|
+
if (key !== previousKey) {
|
|
1486
|
+
previousKey = key;
|
|
1487
|
+
listener(state);
|
|
1488
|
+
}
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
options.onError?.(error);
|
|
1491
|
+
} finally {
|
|
1492
|
+
if (!stopped) timeout = setTimeout(() => {
|
|
1493
|
+
poll();
|
|
1494
|
+
}, intervalMs);
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
if (options.immediate === false) timeout = setTimeout(() => {
|
|
1498
|
+
poll();
|
|
1499
|
+
}, intervalMs);
|
|
1500
|
+
else poll();
|
|
1501
|
+
return () => {
|
|
1502
|
+
stopped = true;
|
|
1503
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
async listDshAccountSessions(options = {}, signal) {
|
|
1507
|
+
const result = await this.call("remote.sessions.list", options, signal);
|
|
1508
|
+
if (result.contractVersion !== 1) throw new Error("账号会话协议版本不受支持");
|
|
1509
|
+
return result;
|
|
1510
|
+
}
|
|
1511
|
+
async readDshAccountSession(options, signal) {
|
|
1512
|
+
return await this.call("remote.session.read", options, signal);
|
|
1513
|
+
}
|
|
1514
|
+
async commandDshAccountSession(options, signal) {
|
|
1515
|
+
return await this.call("remote.session.command", options, signal);
|
|
1516
|
+
}
|
|
1517
|
+
/** Event-driven invalidation only. Abort or dispose closes the Host subscription. */
|
|
1518
|
+
observeDshAccountSession(target, changed, options = {}) {
|
|
1519
|
+
return observeDshAccountSession(this.route, this.fetchImpl, target, changed, options);
|
|
1520
|
+
}
|
|
1521
|
+
/** Account-authorized native DSH carrier. Callers retain native RPC result semantics. */
|
|
1522
|
+
async nativeDshRequest(options, signal) {
|
|
1523
|
+
return await this.call("remote.session.native", options, signal);
|
|
1524
|
+
}
|
|
1525
|
+
async currentDesktopSession(signal) {
|
|
1526
|
+
return await this.call("remote.currentSession", void 0, signal);
|
|
1527
|
+
}
|
|
1528
|
+
async remoteStatus(signal) {
|
|
1529
|
+
return await this.call("remote.getStatus", void 0, signal);
|
|
1530
|
+
}
|
|
1531
|
+
async renameRemoteDesktop(displayName, signal) {
|
|
1532
|
+
const normalized = displayName.trim();
|
|
1533
|
+
if (normalized === "" || [...normalized].length > 80) throw new TypeError("Remote desktop name must contain 1 to 80 characters");
|
|
1534
|
+
return await this.call("remote.renameDesktop", { displayName: normalized }, signal);
|
|
1535
|
+
}
|
|
1536
|
+
subscribeRemote(listener, options = {}) {
|
|
1537
|
+
const intervalMs = Math.min(6e4, Math.max(1e3, Math.trunc(options.intervalMs ?? 2e3)));
|
|
1538
|
+
let stopped = false;
|
|
1539
|
+
let timer;
|
|
1540
|
+
let revision = -1;
|
|
1541
|
+
const poll = async () => {
|
|
1542
|
+
if (stopped) return;
|
|
1543
|
+
try {
|
|
1544
|
+
const status = await this.remoteStatus();
|
|
1545
|
+
if (!stopped && status.revision !== revision) {
|
|
1546
|
+
revision = status.revision;
|
|
1547
|
+
listener(status);
|
|
1548
|
+
}
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
options.onError?.(error);
|
|
1551
|
+
} finally {
|
|
1552
|
+
if (!stopped) timer = setTimeout(() => {
|
|
1553
|
+
poll();
|
|
1554
|
+
}, intervalMs);
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
if (options.immediate === false) timer = setTimeout(() => {
|
|
1558
|
+
poll();
|
|
1559
|
+
}, intervalMs);
|
|
1560
|
+
else poll();
|
|
1561
|
+
return () => {
|
|
1562
|
+
stopped = true;
|
|
1563
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
async call(operation, params, signal) {
|
|
1567
|
+
const response = await this.fetchImpl(this.route, {
|
|
1568
|
+
method: "POST",
|
|
1569
|
+
credentials: "same-origin",
|
|
1570
|
+
headers: { "Content-Type": "application/json" },
|
|
1571
|
+
body: JSON.stringify({
|
|
1572
|
+
operation,
|
|
1573
|
+
...params === void 0 ? {} : { params }
|
|
1574
|
+
}),
|
|
1575
|
+
...signal === void 0 ? {} : { signal }
|
|
1576
|
+
});
|
|
1577
|
+
let body;
|
|
1578
|
+
try {
|
|
1579
|
+
body = await response.json();
|
|
1580
|
+
} catch {
|
|
1581
|
+
throw new ArkmeClientError({
|
|
1582
|
+
code: "local-response-invalid",
|
|
1583
|
+
message: "Arkme 插件返回了无效响应",
|
|
1584
|
+
retryable: true
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
if (!body.ok) throw new ArkmeClientError(body.error);
|
|
1588
|
+
return body.value;
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
function createArkmeSdk(options) {
|
|
1592
|
+
return new ArkmeSdk(options);
|
|
1593
|
+
}
|
|
1594
|
+
const defaultSdk = createArkmeSdk();
|
|
1595
|
+
async function callArkme(operation, params, signal) {
|
|
1596
|
+
return await defaultSdk.call(operation, params, signal);
|
|
1597
|
+
}
|
|
1598
|
+
//#endregion
|
|
1599
|
+
//#region src/client/harness-native-navigation.ts
|
|
1600
|
+
const HARNESS_NATIVE_OPEN = "arkme:native-session-open";
|
|
1601
|
+
/** The parent retains native documents; a selection never navigates or reloads them. */
|
|
1602
|
+
async function openNativeAccountSession(session, localRuntimeRef) {
|
|
1603
|
+
const surface = window.frameElement?.parentElement;
|
|
1604
|
+
if (!surface) throw new Error("DSH 对话容器尚未就绪");
|
|
1605
|
+
const doc = surface.ownerDocument;
|
|
1606
|
+
const runtimeRef = session.runtimeRef === localRuntimeRef ? "" : session.runtimeRef;
|
|
1607
|
+
doc.dispatchEvent(new doc.defaultView.CustomEvent(HARNESS_NATIVE_OPEN, { detail: {
|
|
1608
|
+
runtimeRef,
|
|
1609
|
+
sessionRef: session.sessionRef,
|
|
1610
|
+
accountId: surface.getAttribute("data-arkme-account-id") ?? "",
|
|
1611
|
+
accountScope: surface.getAttribute("data-arkme-account-scope") ?? ""
|
|
1612
|
+
} }));
|
|
1613
|
+
}
|
|
1614
|
+
//#endregion
|
|
1615
|
+
//#region src/client/harness-session-menu-bridge.ts
|
|
1616
|
+
const HARNESS_LOCAL_SESSION_OPEN = "arkme:harness-local-session-open";
|
|
1617
|
+
/** Private presentation bridge between Arkme and its existing same-origin Harness frame. */
|
|
1618
|
+
const HARNESS_MENU_OPEN = "arkme:harness-menu-open";
|
|
1619
|
+
const HARNESS_MENU_CLOSE = "arkme:harness-menu-close";
|
|
1620
|
+
const HARNESS_MENU_POSITION = "arkme:harness-menu-position";
|
|
1621
|
+
//#endregion
|
|
1622
|
+
//#region src/client/harness-account-sessions.tsx
|
|
1623
|
+
const SLOT$1 = "sidebar.workspaces";
|
|
1624
|
+
const FLOW = "sidebar.workspaces.directoryFlow";
|
|
1625
|
+
const OWN_FLOW = "arkme.accountSessions.directoryFlow";
|
|
1626
|
+
const accountSessionKey = (row) => `arkme:${encodeURIComponent(row.runtimeRef)}:${encodeURIComponent(row.sessionRef)}`;
|
|
1627
|
+
/** The native updated mode promotes newly discovered rows, even old remote
|
|
1628
|
+
* history. Project its display orders by actual activity time, not arrival. */
|
|
1629
|
+
function projectAccountSessionOrder(view, sessions) {
|
|
1630
|
+
if (view.orderBy !== "updated") return view;
|
|
1631
|
+
return {
|
|
1632
|
+
...view,
|
|
1633
|
+
sessionOrderByAccount: Object.fromEntries(Object.entries(view.sessionOrderByAccount).map(([key, ids]) => [key, [...ids].sort((a, b) => {
|
|
1634
|
+
const aTime = sessions.byId[a]?.updatedAt ?? -Infinity;
|
|
1635
|
+
const bTime = sessions.byId[b]?.updatedAt ?? -Infinity;
|
|
1636
|
+
return aTime === bTime ? a < b ? -1 : a > b ? 1 : 0 : bTime - aTime;
|
|
1637
|
+
})]))
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
/** Local rows keep native identity, blank/activity state and all native actions. */
|
|
1641
|
+
function projectAccountSessions(local, rows, selected) {
|
|
1642
|
+
const byId = { ...local.byId }, ids = [...local.ids];
|
|
1643
|
+
for (const row of selected && !rows.some((row) => accountSessionKey(row) === accountSessionKey(selected)) ? [...rows, selected] : rows) {
|
|
1644
|
+
if (row.archived || row.origin === "subagent") continue;
|
|
1645
|
+
const id = row.local ? row.sessionRef : accountSessionKey(row);
|
|
1646
|
+
if (byId[id]) continue;
|
|
1647
|
+
if (!row.local && window.__ARKME_NATIVE_DIRECTORY__) continue;
|
|
1648
|
+
byId[id] = {
|
|
1649
|
+
id,
|
|
1650
|
+
title: row.title,
|
|
1651
|
+
displayTitle: row.title,
|
|
1652
|
+
updatedAt: row.updatedAt,
|
|
1653
|
+
running: row.running,
|
|
1654
|
+
blank: row.blank
|
|
1655
|
+
};
|
|
1656
|
+
ids.push(id);
|
|
1657
|
+
}
|
|
1658
|
+
return {
|
|
1659
|
+
...local,
|
|
1660
|
+
byId,
|
|
1661
|
+
ids: [...new Set(ids)],
|
|
1662
|
+
current: selected ? accountSessionKey(selected) : local.current
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
function projectAccountWorkspaces(local, rows) {
|
|
1666
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1667
|
+
const names = /* @__PURE__ */ new Map();
|
|
1668
|
+
for (const row of rows) {
|
|
1669
|
+
if (!row.workspaceRef || row.archived) continue;
|
|
1670
|
+
const id = `${row.runtimeRef}:${row.workspaceRef}`;
|
|
1671
|
+
const owners = names.get(row.workspaceName) ?? /* @__PURE__ */ new Set();
|
|
1672
|
+
owners.add(id);
|
|
1673
|
+
names.set(row.workspaceName, owners);
|
|
1674
|
+
const workspaceId = row.local ? row.workspaceRef : `arkme:${id}`;
|
|
1675
|
+
const group = groups.get(id) ?? {
|
|
1676
|
+
workspaceId,
|
|
1677
|
+
title: row.workspaceName,
|
|
1678
|
+
path: row.workspaceName,
|
|
1679
|
+
sessionIds: []
|
|
1680
|
+
};
|
|
1681
|
+
group.sessionIds.push(row.local ? row.sessionRef : accountSessionKey(row));
|
|
1682
|
+
groups.set(id, group);
|
|
1683
|
+
}
|
|
1684
|
+
const items = [...local.items.map((item) => {
|
|
1685
|
+
const row = rows.find((row) => row.local && row.workspaceRef === item.workspaceId);
|
|
1686
|
+
const extra = [...groups.values()].find((group) => group.workspaceId === item.workspaceId);
|
|
1687
|
+
const merged = extra ? {
|
|
1688
|
+
...item,
|
|
1689
|
+
sessionIds: [.../* @__PURE__ */ new Set([...item.sessionIds, ...extra.sessionIds])]
|
|
1690
|
+
} : item;
|
|
1691
|
+
return row && (names.get(row.workspaceName)?.size ?? 0) > 1 ? {
|
|
1692
|
+
...merged,
|
|
1693
|
+
title: `${row.desktopName} · ${item.title}`
|
|
1694
|
+
} : merged;
|
|
1695
|
+
}), ...[...groups.values()].filter((group) => !local.items.some((item) => item.workspaceId === group.workspaceId))].map((item) => {
|
|
1696
|
+
if (!item.workspaceId.startsWith("arkme:")) return item;
|
|
1697
|
+
const row = rows.find((row) => `arkme:${row.runtimeRef}:${row.workspaceRef}` === item.workspaceId);
|
|
1698
|
+
return (names.get(row.workspaceName)?.size ?? 0) > 1 ? {
|
|
1699
|
+
...item,
|
|
1700
|
+
title: `${row.desktopName} · ${item.title}`
|
|
1701
|
+
} : item;
|
|
1702
|
+
});
|
|
1703
|
+
return {
|
|
1704
|
+
...local,
|
|
1705
|
+
items
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
const catalogKey = Symbol.for("arkme.account-session-catalog");
|
|
1709
|
+
/** Apply a complete authoritative catalog as add/update/delete; unchanged rows retain identity. */
|
|
1710
|
+
function reconcileAccountSessions(previous, incoming) {
|
|
1711
|
+
const known = new Map(previous.map((row) => [accountSessionKey(row), row]));
|
|
1712
|
+
const next = [...new Map(incoming.map((row) => [accountSessionKey(row), row])).values()].map((row) => {
|
|
1713
|
+
const old = known.get(accountSessionKey(row));
|
|
1714
|
+
return old && Object.keys(row).every((key) => key === "capabilities" ? row.capabilities.join("\0") === old.capabilities.join("\0") : row[key] === old[key]) ? old : row;
|
|
1715
|
+
});
|
|
1716
|
+
return next.length === previous.length && next.every((row, i) => row === previous[i]) ? previous : next;
|
|
1717
|
+
}
|
|
1718
|
+
function createCatalog(surface, remove) {
|
|
1719
|
+
const owner = surface.ownerDocument;
|
|
1720
|
+
let rows = [], localRuntimeRef, users = 0;
|
|
1721
|
+
let snapshot = {
|
|
1722
|
+
rows,
|
|
1723
|
+
localRuntimeRef
|
|
1724
|
+
}, active = true, busy = false;
|
|
1725
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1726
|
+
let controller = new AbortController();
|
|
1727
|
+
let stopRefresh;
|
|
1728
|
+
const publish = () => {
|
|
1729
|
+
snapshot = {
|
|
1730
|
+
rows,
|
|
1731
|
+
localRuntimeRef
|
|
1732
|
+
};
|
|
1733
|
+
listeners.forEach((listener) => listener());
|
|
1734
|
+
};
|
|
1735
|
+
const refresh = async () => {
|
|
1736
|
+
if (!active || busy || owner.hidden) return;
|
|
1737
|
+
busy = true;
|
|
1738
|
+
const signal = controller.signal;
|
|
1739
|
+
try {
|
|
1740
|
+
const next = [], seen = /* @__PURE__ */ new Set();
|
|
1741
|
+
let cursor;
|
|
1742
|
+
do {
|
|
1743
|
+
const page = await callArkme("remote.sessions.list", {
|
|
1744
|
+
limit: 100,
|
|
1745
|
+
...cursor ? { cursor } : {}
|
|
1746
|
+
}, signal);
|
|
1747
|
+
if (page.contractVersion !== 1 || !Array.isArray(page.items)) throw new Error("会话目录不可用");
|
|
1748
|
+
localRuntimeRef = page.localRuntimeRef;
|
|
1749
|
+
next.push(...page.items.map((row) => {
|
|
1750
|
+
const previous = rows.find((old) => accountSessionKey(old) === accountSessionKey(row));
|
|
1751
|
+
return page.warning && previous && row.workspaceName === "工作区信息暂不可用" ? {
|
|
1752
|
+
...row,
|
|
1753
|
+
workspaceName: previous.workspaceName
|
|
1754
|
+
} : row;
|
|
1755
|
+
}));
|
|
1756
|
+
cursor = page.nextCursor;
|
|
1757
|
+
if (cursor) {
|
|
1758
|
+
const key = JSON.stringify(cursor);
|
|
1759
|
+
if (seen.has(key)) throw new Error("会话目录游标重复");
|
|
1760
|
+
seen.add(key);
|
|
1761
|
+
}
|
|
1762
|
+
if (next.length > 1e4) throw new Error("会话目录过大");
|
|
1763
|
+
} while (cursor && active);
|
|
1764
|
+
if (!active || signal.aborted) return;
|
|
1765
|
+
const merged = reconcileAccountSessions(rows, next);
|
|
1766
|
+
if (merged !== rows || snapshot.localRuntimeRef !== localRuntimeRef) {
|
|
1767
|
+
rows = merged;
|
|
1768
|
+
window.__ARKME_NATIVE_DIRECTORY__?.publish(rows);
|
|
1769
|
+
publish();
|
|
1770
|
+
}
|
|
1771
|
+
} catch {} finally {
|
|
1772
|
+
if (signal === controller.signal) busy = false;
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
return {
|
|
1776
|
+
get signal() {
|
|
1777
|
+
return controller.signal;
|
|
1778
|
+
},
|
|
1779
|
+
start: () => {
|
|
1780
|
+
if (users++ !== 0) return;
|
|
1781
|
+
active = true;
|
|
1782
|
+
busy = false;
|
|
1783
|
+
controller = new AbortController();
|
|
1784
|
+
const resume = () => {
|
|
1785
|
+
if (surface.getAttribute("data-arkme-visible") !== "false") refresh();
|
|
1786
|
+
};
|
|
1787
|
+
const timer = setInterval(resume, 5e3);
|
|
1788
|
+
const observer = new MutationObserver(resume);
|
|
1789
|
+
observer.observe(surface, {
|
|
1790
|
+
attributes: true,
|
|
1791
|
+
attributeFilter: ["data-arkme-visible"]
|
|
1792
|
+
});
|
|
1793
|
+
document.addEventListener(HARNESS_MENU_OPEN, resume);
|
|
1794
|
+
owner.addEventListener("visibilitychange", resume);
|
|
1795
|
+
owner.defaultView?.addEventListener("focus", resume);
|
|
1796
|
+
stopRefresh = () => {
|
|
1797
|
+
clearInterval(timer);
|
|
1798
|
+
observer.disconnect();
|
|
1799
|
+
document.removeEventListener(HARNESS_MENU_OPEN, resume);
|
|
1800
|
+
owner.removeEventListener("visibilitychange", resume);
|
|
1801
|
+
owner.defaultView?.removeEventListener("focus", resume);
|
|
1802
|
+
};
|
|
1803
|
+
},
|
|
1804
|
+
getSnapshot: () => snapshot,
|
|
1805
|
+
subscribe: (listener) => {
|
|
1806
|
+
listeners.add(listener);
|
|
1807
|
+
return () => {
|
|
1808
|
+
listeners.delete(listener);
|
|
1809
|
+
};
|
|
1810
|
+
},
|
|
1811
|
+
refresh,
|
|
1812
|
+
dispose: () => {
|
|
1813
|
+
if (--users === 0) {
|
|
1814
|
+
active = false;
|
|
1815
|
+
controller.abort();
|
|
1816
|
+
stopRefresh?.();
|
|
1817
|
+
remove();
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
function accountSessionCatalog(surface) {
|
|
1823
|
+
const doc = surface.ownerDocument;
|
|
1824
|
+
const catalogs = doc[catalogKey] ??= /* @__PURE__ */ new Map();
|
|
1825
|
+
const account = `${surface.getAttribute("data-arkme-account-id")}:${surface.getAttribute("data-arkme-account-scope")}`;
|
|
1826
|
+
let entry = catalogs.get(account);
|
|
1827
|
+
if (!entry) {
|
|
1828
|
+
entry = createCatalog(surface, () => catalogs.delete(account));
|
|
1829
|
+
catalogs.set(account, entry);
|
|
1830
|
+
}
|
|
1831
|
+
return entry;
|
|
1832
|
+
}
|
|
1833
|
+
/** All native sources share one account-scoped catalog. */
|
|
1834
|
+
function AccountSessionBrowser({ Native, surface, ...props }) {
|
|
1835
|
+
const account = (0, react.useSyncExternalStore)((listener) => {
|
|
1836
|
+
const observer = new MutationObserver(listener);
|
|
1837
|
+
observer.observe(surface, {
|
|
1838
|
+
attributes: true,
|
|
1839
|
+
attributeFilter: ["data-arkme-account-id", "data-arkme-account-scope"]
|
|
1840
|
+
});
|
|
1841
|
+
return () => observer.disconnect();
|
|
1842
|
+
}, () => `${surface.getAttribute("data-arkme-account-id")}:${surface.getAttribute("data-arkme-account-scope")}`);
|
|
1843
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountBrowser, {
|
|
1844
|
+
Native,
|
|
1845
|
+
surface,
|
|
1846
|
+
...props
|
|
1847
|
+
}, account);
|
|
1848
|
+
}
|
|
1849
|
+
function AccountBrowser({ Native, surface, ...props }) {
|
|
1850
|
+
const sourceRuntime = null;
|
|
1851
|
+
const creating = (0, react.useRef)(false);
|
|
1852
|
+
const [operationError, setOperationError] = (0, react.useState)("");
|
|
1853
|
+
const local = props.useSessions((state) => state);
|
|
1854
|
+
const workspaces = props.useWorkspaces((state) => state);
|
|
1855
|
+
const catalog = (0, react.useMemo)(() => accountSessionCatalog(surface), []);
|
|
1856
|
+
const snapshot = (0, react.useSyncExternalStore)(catalog.subscribe, catalog.getSnapshot);
|
|
1857
|
+
const rows = (0, react.useMemo)(() => snapshot.rows, [snapshot.rows, sourceRuntime]);
|
|
1858
|
+
(0, react.useEffect)(() => {
|
|
1859
|
+
catalog.start();
|
|
1860
|
+
catalog.refresh();
|
|
1861
|
+
return () => catalog.dispose();
|
|
1862
|
+
}, [catalog]);
|
|
1863
|
+
(0, react.useEffect)(() => {
|
|
1864
|
+
catalog.refresh();
|
|
1865
|
+
}, [local.current, catalog]);
|
|
1866
|
+
const sessionState = (0, react.useMemo)(() => projectAccountSessions(local, rows), [local, rows]);
|
|
1867
|
+
const workspaceState = (0, react.useMemo)(() => projectAccountWorkspaces(workspaces, rows), [workspaces, rows]);
|
|
1868
|
+
const view = props.useStore((state) => state);
|
|
1869
|
+
const viewState = (0, react.useMemo)(() => projectAccountSessionOrder(view, sessionState), [view, sessionState]);
|
|
1870
|
+
const originResolver = (0, react.useRef)(() => void 0);
|
|
1871
|
+
originResolver.current = (element) => {
|
|
1872
|
+
const tree = element.closest("[role=\"tree\"]");
|
|
1873
|
+
if (!tree) return void 0;
|
|
1874
|
+
let candidates = sessionState.ids.filter((id) => {
|
|
1875
|
+
const row = sessionState.byId[id];
|
|
1876
|
+
return row && row.origin !== "subagent" && !workspaceState.archivedSessionIds.includes(id) && (!row.blank || id === sessionState.current);
|
|
1877
|
+
});
|
|
1878
|
+
let orderKey = "__flat_session_order__";
|
|
1879
|
+
if (viewState.groupBy === "workspace") {
|
|
1880
|
+
const heading = (element.parentElement?.parentElement)?.querySelector("[role=\"treeitem\"][aria-expanded]");
|
|
1881
|
+
const title = heading?.textContent;
|
|
1882
|
+
const workspace = workspaceState.items.filter((item) => item.title === title)[[...tree.querySelectorAll("[role=\"treeitem\"][aria-expanded]")].filter((node) => node.textContent === title).indexOf(heading)];
|
|
1883
|
+
orderKey = workspace?.workspaceId ?? "";
|
|
1884
|
+
candidates = candidates.filter((id) => workspace ? workspace.sessionIds.includes(id) : !workspaceState.items.some((item) => item.sessionIds.includes(id)));
|
|
1885
|
+
}
|
|
1886
|
+
const stored = viewState.sessionOrderByAccount[orderKey] ?? [];
|
|
1887
|
+
const ordered = [...stored.filter((id) => candidates.includes(id)), ...candidates.filter((id) => !stored.includes(id)).sort((a, b) => sessionState.byId[b].updatedAt - sessionState.byId[a].updatedAt || (a < b ? -1 : a > b ? 1 : 0))];
|
|
1888
|
+
const index = [...element.parentElement.parentElement.querySelectorAll(":scope > span > [role=\"treeitem\"][aria-selected]")].indexOf(element);
|
|
1889
|
+
const matches = ordered.filter((id) => [...element.children].some((node) => node.textContent === sessionState.byId[id]?.displayTitle));
|
|
1890
|
+
const id = matches.includes(ordered[index]) ? ordered[index] : matches.length === 1 ? matches[0] : void 0;
|
|
1891
|
+
if (!id) return void 0;
|
|
1892
|
+
const native = sessionState.byId[id];
|
|
1893
|
+
if (!native || ![...element.children].some((node) => node.textContent === native.displayTitle)) return void 0;
|
|
1894
|
+
const row = snapshot.rows.find((row) => (row.runtimeRef === snapshot.localRuntimeRef ? row.sessionRef : accountSessionKey(row)) === id);
|
|
1895
|
+
return row ? {
|
|
1896
|
+
title: native.displayTitle,
|
|
1897
|
+
lines: sessionOrigin(row)
|
|
1898
|
+
} : void 0;
|
|
1899
|
+
};
|
|
1900
|
+
(0, react.useEffect)(() => installSessionOriginHover(document, (element) => originResolver.current(element)), []);
|
|
1901
|
+
const useSessions = (select) => select(sessionState);
|
|
1902
|
+
const useWorkspaces = (select) => select(workspaceState);
|
|
1903
|
+
const useStore = (select) => select(viewState);
|
|
1904
|
+
const remote = (id) => rows.find((row) => row.local ? row.sessionRef === id && !local.byId[id] : accountSessionKey(row) === id);
|
|
1905
|
+
const navigate = (target) => Promise.resolve(openNativeAccountSession(target, snapshot.localRuntimeRef)).catch((error) => {
|
|
1906
|
+
if (!catalog.signal.aborted && error?.name !== "AbortError") setOperationError(error instanceof Error ? error.message : "打开会话失败");
|
|
1907
|
+
});
|
|
1908
|
+
const command = async (id, operation, body = {}) => {
|
|
1909
|
+
const target = remote(id);
|
|
1910
|
+
if (!target) throw new Error("会话不存在");
|
|
1911
|
+
const value = await callArkme("remote.session.command", {
|
|
1912
|
+
runtimeRef: target.runtimeRef,
|
|
1913
|
+
sessionRef: target.sessionRef,
|
|
1914
|
+
operation,
|
|
1915
|
+
body,
|
|
1916
|
+
requestRef: crypto.randomUUID()
|
|
1917
|
+
}, catalog.signal);
|
|
1918
|
+
await catalog.refresh();
|
|
1919
|
+
return value;
|
|
1920
|
+
};
|
|
1921
|
+
const open = (id) => {
|
|
1922
|
+
const target = remote(id);
|
|
1923
|
+
if (local.byId[id]) props.open(id);
|
|
1924
|
+
else if (target) navigate(target);
|
|
1925
|
+
};
|
|
1926
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Native, {
|
|
1927
|
+
...props,
|
|
1928
|
+
useSessions,
|
|
1929
|
+
useWorkspaces,
|
|
1930
|
+
useStore,
|
|
1931
|
+
open,
|
|
1932
|
+
renameSession: (id, title) => remote(id) ? command(id, "session.rename", { title }).then(() => void 0) : props.renameSession(id, title),
|
|
1933
|
+
archiveSession: async (id) => {
|
|
1934
|
+
if (!remote(id)) return props.archiveSession(id);
|
|
1935
|
+
await command(id, "session.archive");
|
|
1936
|
+
},
|
|
1937
|
+
forkSession: (id) => {
|
|
1938
|
+
if (!remote(id)) props.forkSession(id);
|
|
1939
|
+
else setOperationError("此操作需要在对话所属电脑上执行。");
|
|
1940
|
+
},
|
|
1941
|
+
startSession: (id) => {
|
|
1942
|
+
if (!id?.startsWith("arkme:")) {
|
|
1943
|
+
props.startSession(id);
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
const source = rows.find((row) => `arkme:${row.runtimeRef}:${row.workspaceRef}` === id);
|
|
1947
|
+
if (!source || creating.current) return;
|
|
1948
|
+
const blank = rows.find((row) => row.runtimeRef === source.runtimeRef && row.workspaceRef === source.workspaceRef && row.blank && !row.archived);
|
|
1949
|
+
if (blank) {
|
|
1950
|
+
navigate(blank);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
creating.current = true;
|
|
1954
|
+
callArkme("remote.session.command", {
|
|
1955
|
+
runtimeRef: source.runtimeRef,
|
|
1956
|
+
operation: "session.create",
|
|
1957
|
+
requestRef: crypto.randomUUID(),
|
|
1958
|
+
body: { workspace_ref: source.workspaceRef }
|
|
1959
|
+
}, catalog.signal).then((value) => {
|
|
1960
|
+
if (!catalog.signal.aborted) return navigate({
|
|
1961
|
+
...source,
|
|
1962
|
+
sessionRef: value.sessionId
|
|
1963
|
+
});
|
|
1964
|
+
}).catch((error) => {
|
|
1965
|
+
if (!catalog.signal.aborted) setOperationError(error instanceof Error ? error.message : "新建会话失败");
|
|
1966
|
+
}).finally(() => {
|
|
1967
|
+
creating.current = false;
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
}), operationError && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
1971
|
+
open: true,
|
|
1972
|
+
onClose: () => setOperationError(""),
|
|
1973
|
+
title: "无法执行操作",
|
|
1974
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: operationError }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1975
|
+
onClick: () => setOperationError(""),
|
|
1976
|
+
children: "知道了"
|
|
1977
|
+
})]
|
|
1978
|
+
})] });
|
|
1979
|
+
}
|
|
1980
|
+
/** Shadow the native entry using its public component/store/inject seats. The
|
|
1981
|
+
* directory picker gets its own declared slot; never borrow another entry's
|
|
1982
|
+
* render authorization or mutate the original registration. */
|
|
1983
|
+
function installHarnessAccountSessions(ctx, surface) {
|
|
1984
|
+
const slots = ctx.slots;
|
|
1985
|
+
if (typeof slots.entries !== "function" || typeof slots.subscribe !== "function") return () => {};
|
|
1986
|
+
const catalog = accountSessionCatalog(surface);
|
|
1987
|
+
catalog.start();
|
|
1988
|
+
catalog.refresh();
|
|
1989
|
+
let native, remove, updating = false;
|
|
1990
|
+
const sync = () => {
|
|
1991
|
+
if (updating) return;
|
|
1992
|
+
const next = slots.entries(SLOT$1).find((entry) => (entry.options.priority ?? 0) >= 0);
|
|
1993
|
+
if (next === native) return;
|
|
1994
|
+
updating = true;
|
|
1995
|
+
remove?.();
|
|
1996
|
+
remove = void 0;
|
|
1997
|
+
native = next;
|
|
1998
|
+
try {
|
|
1999
|
+
if (!next || typeof next.component !== "function" || typeof next.inject !== "function" || !next.store || !next.children?.[FLOW] || Object.keys(next.children).length !== 1) return;
|
|
2000
|
+
const Native = next.component;
|
|
2001
|
+
const Wrapped = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountSessionBrowser, {
|
|
2002
|
+
...props,
|
|
2003
|
+
Native,
|
|
2004
|
+
surface,
|
|
2005
|
+
renderSlot: (name, owner) => name === FLOW ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2006
|
+
"data-slot": FLOW,
|
|
2007
|
+
style: { display: "contents" },
|
|
2008
|
+
children: props.renderSlot(OWN_FLOW, owner)
|
|
2009
|
+
}) : null
|
|
2010
|
+
});
|
|
2011
|
+
const register = slots.register.bind(slots);
|
|
2012
|
+
const restoreBrowser = register({
|
|
2013
|
+
name: SLOT$1,
|
|
2014
|
+
priority: -100,
|
|
2015
|
+
store: next.store,
|
|
2016
|
+
inject: next.inject,
|
|
2017
|
+
locale: next.locale,
|
|
2018
|
+
children: { [OWN_FLOW]: next.children[FLOW] }
|
|
2019
|
+
}, Wrapped);
|
|
2020
|
+
let picker, restorePicker;
|
|
2021
|
+
const syncPicker = () => {
|
|
2022
|
+
const current = slots.entriesOfSlot(FLOW)[0];
|
|
2023
|
+
if (current === picker) return;
|
|
2024
|
+
restorePicker?.();
|
|
2025
|
+
restorePicker = void 0;
|
|
2026
|
+
picker = current;
|
|
2027
|
+
if (current && !current.children) restorePicker = register({
|
|
2028
|
+
name: OWN_FLOW,
|
|
2029
|
+
...current.options,
|
|
2030
|
+
store: current.store,
|
|
2031
|
+
inject: current.inject,
|
|
2032
|
+
locale: current.locale
|
|
2033
|
+
}, current.component);
|
|
2034
|
+
};
|
|
2035
|
+
const unsubscribe = slots.subscribe(FLOW, syncPicker);
|
|
2036
|
+
syncPicker();
|
|
2037
|
+
remove = () => {
|
|
2038
|
+
unsubscribe();
|
|
2039
|
+
restorePicker?.();
|
|
2040
|
+
restoreBrowser();
|
|
2041
|
+
};
|
|
2042
|
+
} finally {
|
|
2043
|
+
updating = false;
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
const unsubscribe = slots.subscribe(SLOT$1, sync);
|
|
2047
|
+
sync();
|
|
2048
|
+
return () => {
|
|
2049
|
+
unsubscribe();
|
|
2050
|
+
updating = true;
|
|
2051
|
+
remove?.();
|
|
2052
|
+
catalog.dispose();
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
//#endregion
|
|
8
2056
|
//#region src/client/harness-session-list-default.ts
|
|
9
2057
|
const SESSION_LIST_DEFAULT_MARKER = "arkme.harness.session-list-default.v1";
|
|
10
2058
|
function adoptFlatSessionDefault(groupBy, setGroupBy, storage) {
|
|
@@ -65,12 +2113,6 @@ window.__ModuleLoader__.load({
|
|
|
65
2113
|
});
|
|
66
2114
|
}
|
|
67
2115
|
//#endregion
|
|
68
|
-
//#region src/client/harness-session-menu-bridge.ts
|
|
69
|
-
/** Private presentation bridge between Arkme and its existing same-origin Harness frame. */
|
|
70
|
-
const HARNESS_MENU_OPEN = "arkme:harness-menu-open";
|
|
71
|
-
const HARNESS_MENU_CLOSE = "arkme:harness-menu-close";
|
|
72
|
-
const HARNESS_MENU_POSITION = "arkme:harness-menu-position";
|
|
73
|
-
//#endregion
|
|
74
2116
|
//#region src/client/arkme-theme.ts
|
|
75
2117
|
/**
|
|
76
2118
|
* Arkme semantic colors. DSH owns the active light/dark/system preference and
|
|
@@ -788,6 +2830,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
|
|
|
788
2830
|
}
|
|
789
2831
|
close();
|
|
790
2832
|
show();
|
|
2833
|
+
doc.dispatchEvent(new win.Event(HARNESS_MENU_OPEN));
|
|
791
2834
|
});
|
|
792
2835
|
function externalOpen(event) {
|
|
793
2836
|
const request = event.detail;
|
|
@@ -818,6 +2861,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
|
|
|
818
2861
|
const row = target.closest("[role=\"treeitem\"][aria-selected]");
|
|
819
2862
|
const buttonLabel = target.closest("button")?.getAttribute("aria-label") ?? "";
|
|
820
2863
|
const workspaceCreate = /^在“.+”中新建会话$/.test(buttonLabel) || /^New session in /.test(buttonLabel);
|
|
2864
|
+
if (current.create.contains(target)) doc.dispatchEvent(new doc.defaultView.Event(HARNESS_LOCAL_SESSION_OPEN));
|
|
821
2865
|
if (row && !target.closest("button, input, [role=\"menu\"]") || current.create.contains(target) || workspaceCreate || target.closest("nav button")) {
|
|
822
2866
|
const request = external;
|
|
823
2867
|
close(!request);
|
|
@@ -1154,6 +3198,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
|
|
|
1154
3198
|
let restoreDropdown;
|
|
1155
3199
|
let restoreDefault;
|
|
1156
3200
|
let restoreSummary;
|
|
3201
|
+
let restoreAccountSessions;
|
|
1157
3202
|
let restoreActivity;
|
|
1158
3203
|
const sync = () => {
|
|
1159
3204
|
const accountId = Number(surface.getAttribute("data-arkme-account-id"));
|
|
@@ -1164,10 +3209,13 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
|
|
|
1164
3209
|
priority: -100
|
|
1165
3210
|
}, () => null));
|
|
1166
3211
|
restoreDefault = installHarnessSessionListDefault(ctx);
|
|
3212
|
+
restoreAccountSessions = installHarnessAccountSessions(ctx, surface);
|
|
1167
3213
|
restoreSummary = installHarnessSessionSummary(ctx);
|
|
1168
3214
|
restoreActivity = installHarnessActivityReporter(ctx, surface);
|
|
1169
3215
|
restoreDropdown = installHarnessSessionDropdown(document);
|
|
1170
3216
|
} else if (!authenticated) {
|
|
3217
|
+
restoreAccountSessions?.();
|
|
3218
|
+
restoreAccountSessions = void 0;
|
|
1171
3219
|
restoreActivity?.();
|
|
1172
3220
|
restoreActivity = void 0;
|
|
1173
3221
|
restoreSummary?.();
|
|
@@ -1188,6 +3236,7 @@ body:has([data-arkme-self-topic-menu]) > [role="menu"] { z-index: 10030; }
|
|
|
1188
3236
|
sync();
|
|
1189
3237
|
return () => {
|
|
1190
3238
|
observer.disconnect();
|
|
3239
|
+
restoreAccountSessions?.();
|
|
1191
3240
|
restoreActivity?.();
|
|
1192
3241
|
restoreSummary?.();
|
|
1193
3242
|
restoreDefault?.();
|