@klarkxy/dsh-plugin-kit 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -2
- package/docs/README.zh-CN.md +21 -2
- package/lib/cli-media.d.ts +70 -0
- package/lib/cli-media.js +249 -0
- package/lib/cli-media.js.map +1 -0
- package/lib/cli-process.d.ts +33 -0
- package/lib/cli-process.js +166 -0
- package/lib/cli-process.js.map +1 -0
- package/lib/client-utils.d.ts +7 -1
- package/lib/client-utils.js +25 -1
- package/lib/client-utils.js.map +1 -1
- package/lib/contracts.d.ts +67 -111
- package/lib/contracts.js +34 -2
- package/lib/contracts.js.map +1 -1
- package/lib/domain.d.ts +26 -0
- package/lib/domain.js +41 -0
- package/lib/domain.js.map +1 -0
- package/lib/host-rpc.d.ts +22 -3
- package/lib/host-rpc.js +87 -68
- package/lib/host-rpc.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +2 -1
- package/lib/llm-call.d.ts +5 -1
- package/lib/llm-call.js +5 -1
- package/lib/llm-call.js.map +1 -1
- package/lib/model-menu.d.ts +3 -36
- package/lib/model-menu.js +11 -120
- package/lib/model-menu.js.map +1 -1
- package/lib/model-settings-client.d.ts +47 -0
- package/lib/model-settings-client.js +143 -0
- package/lib/model-settings-client.js.map +1 -0
- package/lib/model-settings.d.ts +55 -0
- package/lib/model-settings.js +60 -0
- package/lib/model-settings.js.map +1 -0
- package/lib/official-ui.js +1 -1
- package/lib/official-ui.js.map +1 -1
- package/package.json +57 -8
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
|
+
//#region src/cli-process.ts
|
|
4
|
+
var CliProcessError = class extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "CliProcessError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/** Own one process tree until it exits, including caller cancellation and protocol failure. */
|
|
13
|
+
async function runCli(executable, args, options) {
|
|
14
|
+
options.signal.throwIfAborted();
|
|
15
|
+
const timed = AbortSignal.timeout(options.timeoutMs ?? 9e5);
|
|
16
|
+
const signal = AbortSignal.any([options.signal, timed]);
|
|
17
|
+
const maxBytes = options.maxOutputBytes ?? 16777216;
|
|
18
|
+
const maxLine = options.maxLineBytes ?? 8388608;
|
|
19
|
+
let child;
|
|
20
|
+
try {
|
|
21
|
+
child = (options.spawnChild ?? spawn)(executable.command, [...executable.args ?? [], ...args], {
|
|
22
|
+
cwd: options.cwd,
|
|
23
|
+
env: executable.env ?? process.env,
|
|
24
|
+
shell: false,
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
detached: process.platform !== "win32",
|
|
27
|
+
stdio: [
|
|
28
|
+
"pipe",
|
|
29
|
+
"pipe",
|
|
30
|
+
"pipe"
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
} catch {
|
|
34
|
+
throw new CliProcessError("unavailable", "Unable to start the official CLI.");
|
|
35
|
+
}
|
|
36
|
+
let failure, total = 0, stderr = "", stdout = "", pending = Buffer.alloc(0);
|
|
37
|
+
const stdoutDecoder = new StringDecoder("utf8"), stderrDecoder = new StringDecoder("utf8");
|
|
38
|
+
let exited = false, exitCode = -1, stopping;
|
|
39
|
+
let lineWork = Promise.resolve();
|
|
40
|
+
const closed = new Promise((resolve) => {
|
|
41
|
+
child.once("close", (code) => {
|
|
42
|
+
exited = true;
|
|
43
|
+
exitCode = code ?? -1;
|
|
44
|
+
resolve();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
const stop = () => stopping ??= (async () => {
|
|
48
|
+
if (exited) return;
|
|
49
|
+
const pid = child.pid;
|
|
50
|
+
if (pid && process.platform === "win32") await new Promise((resolve) => {
|
|
51
|
+
const killer = spawn("taskkill.exe", [
|
|
52
|
+
"/PID",
|
|
53
|
+
String(pid),
|
|
54
|
+
"/T",
|
|
55
|
+
"/F"
|
|
56
|
+
], {
|
|
57
|
+
windowsHide: true,
|
|
58
|
+
shell: false,
|
|
59
|
+
stdio: "ignore"
|
|
60
|
+
});
|
|
61
|
+
let timer;
|
|
62
|
+
const done = () => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
resolve();
|
|
65
|
+
};
|
|
66
|
+
killer.once("error", () => {
|
|
67
|
+
child.kill();
|
|
68
|
+
done();
|
|
69
|
+
});
|
|
70
|
+
killer.once("close", done);
|
|
71
|
+
timer = setTimeout(() => {
|
|
72
|
+
killer.kill();
|
|
73
|
+
child.kill();
|
|
74
|
+
resolve();
|
|
75
|
+
}, 5e3);
|
|
76
|
+
});
|
|
77
|
+
else if (pid) try {
|
|
78
|
+
process.kill(-pid, "SIGTERM");
|
|
79
|
+
} catch {
|
|
80
|
+
child.kill();
|
|
81
|
+
}
|
|
82
|
+
else child.kill();
|
|
83
|
+
let timer;
|
|
84
|
+
await Promise.race([closed, new Promise((resolve) => {
|
|
85
|
+
timer = setTimeout(resolve, 2e3);
|
|
86
|
+
})]);
|
|
87
|
+
if (timer) clearTimeout(timer);
|
|
88
|
+
if (!exited) try {
|
|
89
|
+
pid && process.platform !== "win32" ? process.kill(-pid, "SIGKILL") : child.kill("SIGKILL");
|
|
90
|
+
} catch {}
|
|
91
|
+
await closed;
|
|
92
|
+
})();
|
|
93
|
+
const fail = (error) => {
|
|
94
|
+
failure ??= error;
|
|
95
|
+
stop();
|
|
96
|
+
};
|
|
97
|
+
const abort = () => fail(new CliProcessError(timed.aborted && !options.signal.aborted ? "timeout" : "cancelled", timed.aborted && !options.signal.aborted ? "The CLI request timed out." : "The CLI request was cancelled."));
|
|
98
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
99
|
+
child.on("error", () => fail(new CliProcessError("unavailable", "Unable to start the official CLI.")));
|
|
100
|
+
child.stdin.on("error", () => {
|
|
101
|
+
if (!exited) fail(new CliProcessError("process-io", "The CLI input stream closed unexpectedly."));
|
|
102
|
+
});
|
|
103
|
+
child.stdout.on("error", () => fail(new CliProcessError("process-io", "The CLI output stream failed.")));
|
|
104
|
+
child.stderr.on("error", () => fail(new CliProcessError("process-io", "The CLI error stream failed.")));
|
|
105
|
+
const count = (chunk) => {
|
|
106
|
+
total += chunk.length;
|
|
107
|
+
if (total > maxBytes) {
|
|
108
|
+
fail(new CliProcessError("output-limit", "The CLI output exceeded its limit."));
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
};
|
|
113
|
+
const acceptLine = (line) => {
|
|
114
|
+
if (!options.onLine || !line.trim()) return;
|
|
115
|
+
child.stdout.pause();
|
|
116
|
+
lineWork = lineWork.then(async () => {
|
|
117
|
+
if (!failure) await options.onLine(line);
|
|
118
|
+
}).catch(fail).finally(() => child.stdout.resume());
|
|
119
|
+
};
|
|
120
|
+
child.stdout.on("data", (chunk) => {
|
|
121
|
+
if (failure || !count(chunk)) return;
|
|
122
|
+
if (!options.onLine) {
|
|
123
|
+
stdout += stdoutDecoder.write(chunk);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
pending = Buffer.concat([pending, chunk]);
|
|
127
|
+
for (;;) {
|
|
128
|
+
const end = pending.indexOf(10);
|
|
129
|
+
if (end < 0) break;
|
|
130
|
+
if (end > maxLine) {
|
|
131
|
+
fail(new CliProcessError("output-limit", "A CLI protocol frame exceeded its limit."));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
acceptLine(pending.subarray(0, end).toString("utf8"));
|
|
135
|
+
pending = pending.subarray(end + 1);
|
|
136
|
+
}
|
|
137
|
+
if (pending.length > maxLine) fail(new CliProcessError("output-limit", "A CLI protocol frame exceeded its limit."));
|
|
138
|
+
});
|
|
139
|
+
child.stderr.on("data", (chunk) => {
|
|
140
|
+
if (count(chunk)) stderr = (stderr + stderrDecoder.write(chunk)).slice(-16384);
|
|
141
|
+
});
|
|
142
|
+
try {
|
|
143
|
+
if (signal.aborted) abort();
|
|
144
|
+
if (!failure) child.stdin.end(options.input ?? "");
|
|
145
|
+
await closed;
|
|
146
|
+
stdout += stdoutDecoder.end();
|
|
147
|
+
stderr += stderrDecoder.end();
|
|
148
|
+
if (pending.length && !failure) acceptLine(pending.toString("utf8"));
|
|
149
|
+
await lineWork;
|
|
150
|
+
if (failure) throw failure;
|
|
151
|
+
signal.throwIfAborted();
|
|
152
|
+
return {
|
|
153
|
+
stdout,
|
|
154
|
+
stderr,
|
|
155
|
+
exitCode
|
|
156
|
+
};
|
|
157
|
+
} finally {
|
|
158
|
+
signal.removeEventListener("abort", abort);
|
|
159
|
+
if (!exited) await stop();
|
|
160
|
+
if (stopping) await stopping;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { CliProcessError, runCli };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=cli-process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-process.js","names":[],"sources":["../src/cli-process.ts"],"sourcesContent":["import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'\nimport { StringDecoder } from 'node:string_decoder'\n\n/** A resolved executable, never a shell command or an argument string. */\nexport type CliExecutable = { command: string; args?: string[]; env?: NodeJS.ProcessEnv; displayPath: string }\nexport type CliOutput = { stdout: string; stderr: string; exitCode: number }\nexport class CliProcessError extends Error {\n constructor(public readonly code: string, message: string) { super(message); this.name = 'CliProcessError' }\n}\nexport type CliOptions = {\n cwd: string; signal: AbortSignal; input?: string; timeoutMs?: number;\n maxOutputBytes?: number; maxLineBytes?: number;\n onLine?: (line: string) => void | Promise<void>;\n spawnChild?: typeof spawn;\n}\n\n/** Own one process tree until it exits, including caller cancellation and protocol failure. */\nexport async function runCli(executable: CliExecutable, args: string[], options: CliOptions): Promise<CliOutput> {\n options.signal.throwIfAborted()\n const timed = AbortSignal.timeout(options.timeoutMs ?? 900_000)\n const signal = AbortSignal.any([options.signal, timed])\n const maxBytes = options.maxOutputBytes ?? 16 * 1024 * 1024\n const maxLine = options.maxLineBytes ?? 8 * 1024 * 1024\n let child: ChildProcessWithoutNullStreams\n try {\n child = (options.spawnChild ?? spawn)(executable.command, [...(executable.args ?? []), ...args], {\n cwd: options.cwd, env: executable.env ?? process.env, shell: false,\n windowsHide: true, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'],\n }) as ChildProcessWithoutNullStreams\n } catch { throw new CliProcessError('unavailable', 'Unable to start the official CLI.') }\n let failure: unknown, total = 0, stderr = '', stdout = '', pending = Buffer.alloc(0)\n const stdoutDecoder = new StringDecoder('utf8'), stderrDecoder = new StringDecoder('utf8')\n let exited = false, exitCode = -1, stopping: Promise<void> | undefined\n let lineWork = Promise.resolve()\n const closed = new Promise<void>(resolve => {\n child.once('close', code => { exited = true; exitCode = code ?? -1; resolve() })\n })\n const stop = () => stopping ??= (async () => {\n if (exited) return\n const pid = child.pid\n if (pid && process.platform === 'win32') {\n await new Promise<void>(resolve => {\n const killer = spawn('taskkill.exe', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, shell: false, stdio: 'ignore' })\n let timer: ReturnType<typeof setTimeout>\n const done = () => { clearTimeout(timer); resolve() }\n killer.once('error', () => { child.kill(); done() })\n killer.once('close', done)\n timer = setTimeout(() => { killer.kill(); child.kill(); resolve() }, 5_000)\n })\n } else if (pid) {\n try { process.kill(-pid, 'SIGTERM') } catch { child.kill() }\n } else child.kill()\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([closed, new Promise<void>(resolve => { timer = setTimeout(resolve, 2_000) })])\n if (timer) clearTimeout(timer)\n if (!exited) {\n try { pid && process.platform !== 'win32' ? process.kill(-pid, 'SIGKILL') : child.kill('SIGKILL') } catch { /* already exited */ }\n }\n await closed\n })()\n const fail = (error: unknown) => { failure ??= error; void stop() }\n const abort = () => fail(new CliProcessError(timed.aborted && !options.signal.aborted ? 'timeout' : 'cancelled', timed.aborted && !options.signal.aborted ? 'The CLI request timed out.' : 'The CLI request was cancelled.'))\n signal.addEventListener('abort', abort, { once: true })\n child.on('error', () => fail(new CliProcessError('unavailable', 'Unable to start the official CLI.')))\n child.stdin.on('error', () => { if (!exited) fail(new CliProcessError('process-io', 'The CLI input stream closed unexpectedly.')) })\n child.stdout.on('error', () => fail(new CliProcessError('process-io', 'The CLI output stream failed.')))\n child.stderr.on('error', () => fail(new CliProcessError('process-io', 'The CLI error stream failed.')))\n const count = (chunk: Buffer) => {\n total += chunk.length\n if (total > maxBytes) { fail(new CliProcessError('output-limit', 'The CLI output exceeded its limit.')); return false }\n return true\n }\n const acceptLine = (line: string) => {\n if (!options.onLine || !line.trim()) return\n child.stdout.pause()\n lineWork = lineWork.then(async () => { if (!failure) await options.onLine!(line) }).catch(fail).finally(() => child.stdout.resume())\n }\n child.stdout.on('data', (chunk: Buffer) => {\n if (failure || !count(chunk)) return\n if (!options.onLine) { stdout += stdoutDecoder.write(chunk); return }\n pending = Buffer.concat([pending, chunk])\n for (;;) {\n const end = pending.indexOf(10)\n if (end < 0) break\n if (end > maxLine) { fail(new CliProcessError('output-limit', 'A CLI protocol frame exceeded its limit.')); return }\n acceptLine(pending.subarray(0, end).toString('utf8'))\n pending = pending.subarray(end + 1)\n }\n if (pending.length > maxLine) fail(new CliProcessError('output-limit', 'A CLI protocol frame exceeded its limit.'))\n })\n child.stderr.on('data', (chunk: Buffer) => { if (count(chunk)) stderr = (stderr + stderrDecoder.write(chunk)).slice(-16_384) })\n try {\n if (signal.aborted) abort()\n if (!failure) child.stdin.end(options.input ?? '')\n await closed\n stdout += stdoutDecoder.end(); stderr += stderrDecoder.end()\n if (pending.length && !failure) acceptLine(pending.toString('utf8'))\n await lineWork\n if (failure) throw failure\n signal.throwIfAborted()\n return { stdout, stderr, exitCode }\n } finally {\n signal.removeEventListener('abort', abort)\n if (!exited) await stop()\n if (stopping) await stopping\n }\n}\n"],"mappings":"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"}
|
package/lib/client-utils.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
//#region src/client-utils.d.ts
|
|
2
|
+
/** Fixed pause for failing background reads; manual work is never replayed. */
|
|
3
|
+
declare function createPluginReadGate(now?: () => number): {
|
|
4
|
+
canRead: () => boolean;
|
|
5
|
+
reset: () => void;
|
|
6
|
+
run<T>(read: () => Promise<T>): Promise<T>;
|
|
7
|
+
};
|
|
2
8
|
interface NativeSurfaceClient {
|
|
3
9
|
sessions: {
|
|
4
10
|
binding?(sessionId: string): {
|
|
@@ -48,5 +54,5 @@ declare function useNativeSeat(client: NativeSurfaceClient, props?: unknown): {
|
|
|
48
54
|
/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
|
|
49
55
|
declare function useFeatureRefresh(client: NativeSurfaceClient, sessionId: string, refresh: () => void, working?: boolean, enabled?: boolean): void;
|
|
50
56
|
//#endregion
|
|
51
|
-
export { NativeSurfaceClient, selectedSessionId, useFeatureRefresh, useNativeSeat };
|
|
57
|
+
export { NativeSurfaceClient, createPluginReadGate, selectedSessionId, useFeatureRefresh, useNativeSeat };
|
|
52
58
|
//# sourceMappingURL=client-utils.d.ts.map
|
package/lib/client-utils.js
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
2
2
|
//#region src/client-utils.ts
|
|
3
|
+
/** Fixed pause for failing background reads; manual work is never replayed. */
|
|
4
|
+
function createPluginReadGate(now = Date.now) {
|
|
5
|
+
let pausedUntil = 0;
|
|
6
|
+
let epoch = 0;
|
|
7
|
+
return {
|
|
8
|
+
canRead: () => now() >= pausedUntil,
|
|
9
|
+
reset: () => {
|
|
10
|
+
epoch++;
|
|
11
|
+
pausedUntil = 0;
|
|
12
|
+
},
|
|
13
|
+
async run(read) {
|
|
14
|
+
const current = epoch;
|
|
15
|
+
try {
|
|
16
|
+
const value = await read();
|
|
17
|
+
if (current === epoch) pausedUntil = 0;
|
|
18
|
+
return value;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
const message = error instanceof Error ? error.message : "";
|
|
21
|
+
if (current === epoch && (/(?:^|\s)transport failure for .+: HTTP (?:404|405|503)\b/.test(message) || error instanceof TypeError && /^(?:Failed to fetch|fetch failed|NetworkError when attempting to fetch resource\.?)$/i.test(message))) pausedUntil = now() + 3e4;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
3
27
|
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
4
28
|
function selectedSessionId(client) {
|
|
5
29
|
const workspace = client.uiWorkspace?.current;
|
|
@@ -89,6 +113,6 @@ function useFeatureRefresh(client, sessionId, refresh, working = false, enabled
|
|
|
89
113
|
]);
|
|
90
114
|
}
|
|
91
115
|
//#endregion
|
|
92
|
-
export { selectedSessionId, useFeatureRefresh, useNativeSeat };
|
|
116
|
+
export { createPluginReadGate, selectedSessionId, useFeatureRefresh, useNativeSeat };
|
|
93
117
|
|
|
94
118
|
//# sourceMappingURL=client-utils.js.map
|
package/lib/client-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-utils.js","names":[],"sources":["../src/client-utils.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\n\nexport interface NativeSurfaceClient {\n sessions: {\n binding?(sessionId: string): { eventSource: { subscribe(listener: () => void): () => void } } | undefined\n }\n uiWorkspace: { current?: { getSnapshot(): { sessionId: string } | undefined; subscribe(listener: () => void): () => void } }\n uiSession: { adapter: { current: { getSnapshot(): { key?: string }; subscribe(listener: () => void): () => void } } }\n locale: { getSnapshot(): { active: string }; subscribe(listener: () => void): () => void }\n connection?: { generation?: { subscribe(listener: () => void): () => void } }\n}\n\n/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */\nexport function selectedSessionId(client: Pick<NativeSurfaceClient, 'uiWorkspace' | 'uiSession'>): string {\n const workspace = client.uiWorkspace?.current\n return workspace\n ? workspace.getSnapshot()?.sessionId ?? ''\n : client.uiSession.adapter.current.getSnapshot().key ?? ''\n}\n\n/**\n * Owner props are data the host composed, never a Cordis context: reading an\n * undeclared property off one throws instead of returning undefined, which\n * would crash the slot entry and abdicate its cell for good. Tolerate a\n * throwing read so a mistaken prop degrades to \"not supplied\".\n */\nfunction read(source: unknown, key: string): unknown {\n if (source === null || typeof source !== 'object') return undefined\n try {\n return (source as Record<string, unknown>)[key]\n } catch {\n return undefined\n }\n}\n\nfunction readString(source: unknown, key: string): string | undefined {\n const value = read(source, key)\n return typeof value === 'string' ? value : undefined\n}\n\n/** Owner props win; native settings only supplies close, so resolve its selected session. */\nexport function useNativeSeat(client: NativeSurfaceClient, props?: unknown) {\n const current = useSyncExternalStore(\n useCallback((fn: () => void) => (client.uiWorkspace?.current ?? client.uiSession.adapter.current).subscribe(fn), [client]),\n useCallback(() => selectedSessionId(client), [client]),\n () => '',\n )\n const language = useSyncExternalStore(\n useCallback((fn: () => void) => client.locale.subscribe(fn), [client]),\n useCallback(() => client.locale.getSnapshot().active, [client]),\n () => 'en',\n )\n const sessionId = readString(props, 'sessionId') ?? readString(read(props, 'owner'), 'sessionId') ?? current\n const requestedLocale = read(props, 'locale') ?? read(read(props, 'owner'), 'locale') ?? language\n return { sessionId, locale: (String(requestedLocale).startsWith('zh') ? 'zh' : 'en') as 'zh' | 'en',\n hidden: read(props, 'hidden') === true || read(read(props, 'owner'), 'hidden') === true }\n}\n\n/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */\nexport function useFeatureRefresh(\n client: NativeSurfaceClient, sessionId: string, refresh: () => void,\n working = false, enabled = true,\n): void {\n const refreshRef = useRef(refresh)\n refreshRef.current = refresh\n useEffect(() => {\n if (!enabled) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const queue = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n clearTimeout(timer)\n timer = setTimeout(() => refreshRef.current(), 150)\n }\n const source = sessionId ? client.sessions.binding?.(sessionId)?.eventSource : undefined\n const stopEvents = source?.subscribe(queue)\n const stopConnection = client.connection?.generation?.subscribe(queue)\n const visibility = () => { if (document.visibilityState === 'visible') queue() }\n window.addEventListener('focus', queue)\n document.addEventListener('visibilitychange', visibility)\n // Details are collapsed by default; opening an existing panel must get current state.\n document.addEventListener('toggle', queue, true)\n return () => {\n clearTimeout(timer)\n stopEvents?.(); stopConnection?.()\n window.removeEventListener('focus', queue)\n document.removeEventListener('visibilitychange', visibility)\n document.removeEventListener('toggle', queue, true)\n }\n }, [client, sessionId, enabled])\n useEffect(() => {\n if (!enabled || !working) return\n let delay = 250\n const until = Date.now() + 300_000\n let timer: ReturnType<typeof setTimeout>\n const tick = () => {\n if (Date.now() >= until) return\n if (document.visibilityState !== 'hidden') refreshRef.current()\n delay = Math.min(delay * 2, 2000)\n timer = setTimeout(tick, delay)\n }\n timer = setTimeout(tick, delay)\n return () => clearTimeout(timer)\n }, [client, sessionId, working, enabled])\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client-utils.js","names":[],"sources":["../src/client-utils.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\n\n/** Fixed pause for failing background reads; manual work is never replayed. */\nexport function createPluginReadGate(now: () => number = Date.now) {\n let pausedUntil = 0\n let epoch = 0\n return {\n canRead: () => now() >= pausedUntil,\n reset: () => { epoch++; pausedUntil = 0 },\n async run<T>(read: () => Promise<T>): Promise<T> {\n const current = epoch\n try {\n const value = await read()\n if (current === epoch) pausedUntil = 0\n return value\n } catch (error) {\n // The native Connection error is an Error with this exact HTTP text.\n // Authentication, application failures, and cancellation stay distinct.\n const message = error instanceof Error ? error.message : ''\n if (current === epoch && (/(?:^|\\s)transport failure for .+: HTTP (?:404|405|503)\\b/.test(message)\n || (error instanceof TypeError && /^(?:Failed to fetch|fetch failed|NetworkError when attempting to fetch resource\\.?)$/i.test(message)))) {\n pausedUntil = now() + 30_000\n }\n throw error\n }\n },\n }\n}\n\nexport interface NativeSurfaceClient {\n sessions: {\n binding?(sessionId: string): { eventSource: { subscribe(listener: () => void): () => void } } | undefined\n }\n uiWorkspace: { current?: { getSnapshot(): { sessionId: string } | undefined; subscribe(listener: () => void): () => void } }\n uiSession: { adapter: { current: { getSnapshot(): { key?: string }; subscribe(listener: () => void): () => void } } }\n locale: { getSnapshot(): { active: string }; subscribe(listener: () => void): () => void }\n connection?: { generation?: { subscribe(listener: () => void): () => void } }\n}\n\n/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */\nexport function selectedSessionId(client: Pick<NativeSurfaceClient, 'uiWorkspace' | 'uiSession'>): string {\n const workspace = client.uiWorkspace?.current\n return workspace\n ? workspace.getSnapshot()?.sessionId ?? ''\n : client.uiSession.adapter.current.getSnapshot().key ?? ''\n}\n\n/**\n * Owner props are data the host composed, never a Cordis context: reading an\n * undeclared property off one throws instead of returning undefined, which\n * would crash the slot entry and abdicate its cell for good. Tolerate a\n * throwing read so a mistaken prop degrades to \"not supplied\".\n */\nfunction read(source: unknown, key: string): unknown {\n if (source === null || typeof source !== 'object') return undefined\n try {\n return (source as Record<string, unknown>)[key]\n } catch {\n return undefined\n }\n}\n\nfunction readString(source: unknown, key: string): string | undefined {\n const value = read(source, key)\n return typeof value === 'string' ? value : undefined\n}\n\n/** Owner props win; native settings only supplies close, so resolve its selected session. */\nexport function useNativeSeat(client: NativeSurfaceClient, props?: unknown) {\n const current = useSyncExternalStore(\n useCallback((fn: () => void) => (client.uiWorkspace?.current ?? client.uiSession.adapter.current).subscribe(fn), [client]),\n useCallback(() => selectedSessionId(client), [client]),\n () => '',\n )\n const language = useSyncExternalStore(\n useCallback((fn: () => void) => client.locale.subscribe(fn), [client]),\n useCallback(() => client.locale.getSnapshot().active, [client]),\n () => 'en',\n )\n const sessionId = readString(props, 'sessionId') ?? readString(read(props, 'owner'), 'sessionId') ?? current\n const requestedLocale = read(props, 'locale') ?? read(read(props, 'owner'), 'locale') ?? language\n return { sessionId, locale: (String(requestedLocale).startsWith('zh') ? 'zh' : 'en') as 'zh' | 'en',\n hidden: read(props, 'hidden') === true || read(read(props, 'owner'), 'hidden') === true }\n}\n\n/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */\nexport function useFeatureRefresh(\n client: NativeSurfaceClient, sessionId: string, refresh: () => void,\n working = false, enabled = true,\n): void {\n const refreshRef = useRef(refresh)\n refreshRef.current = refresh\n useEffect(() => {\n if (!enabled) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const queue = () => {\n if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return\n clearTimeout(timer)\n timer = setTimeout(() => refreshRef.current(), 150)\n }\n const source = sessionId ? client.sessions.binding?.(sessionId)?.eventSource : undefined\n const stopEvents = source?.subscribe(queue)\n const stopConnection = client.connection?.generation?.subscribe(queue)\n const visibility = () => { if (document.visibilityState === 'visible') queue() }\n window.addEventListener('focus', queue)\n document.addEventListener('visibilitychange', visibility)\n // Details are collapsed by default; opening an existing panel must get current state.\n document.addEventListener('toggle', queue, true)\n return () => {\n clearTimeout(timer)\n stopEvents?.(); stopConnection?.()\n window.removeEventListener('focus', queue)\n document.removeEventListener('visibilitychange', visibility)\n document.removeEventListener('toggle', queue, true)\n }\n }, [client, sessionId, enabled])\n useEffect(() => {\n if (!enabled || !working) return\n let delay = 250\n const until = Date.now() + 300_000\n let timer: ReturnType<typeof setTimeout>\n const tick = () => {\n if (Date.now() >= until) return\n if (document.visibilityState !== 'hidden') refreshRef.current()\n delay = Math.min(delay * 2, 2000)\n timer = setTimeout(tick, delay)\n }\n timer = setTimeout(tick, delay)\n return () => clearTimeout(timer)\n }, [client, sessionId, working, enabled])\n}\n"],"mappings":"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"}
|
package/lib/contracts.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextFormed } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { ModelRoute } from "@klarkxy/dsh-model-route";
|
|
2
3
|
//#region src/contracts.d.ts
|
|
3
4
|
/** Public, browser-safe contracts. Host implementations stay in their owning packages. */
|
|
4
5
|
type ProducerSourceKind = `plugin:${string}`;
|
|
@@ -16,116 +17,6 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
declare function producerMessageSource(plugin: string): ProducerMessageSource;
|
|
19
|
-
/** A concrete provider model. Features store this on their own settings page. */
|
|
20
|
-
interface ModelRoute {
|
|
21
|
-
provider: string;
|
|
22
|
-
model: string;
|
|
23
|
-
reasoningEffort?: string;
|
|
24
|
-
}
|
|
25
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
26
|
-
type ModelRole = 'normal' | 'weak' | 'strong' | 'fantasy';
|
|
27
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
28
|
-
type ModelTarget = {
|
|
29
|
-
kind: 'role';
|
|
30
|
-
role: ModelRole;
|
|
31
|
-
} | {
|
|
32
|
-
kind: 'session';
|
|
33
|
-
} | ({
|
|
34
|
-
kind: 'model';
|
|
35
|
-
} & ModelRoute);
|
|
36
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
37
|
-
interface AiPolicy {
|
|
38
|
-
revision: number;
|
|
39
|
-
roles: Partial<Record<ModelRole, ModelRoute>>;
|
|
40
|
-
purposes: Record<string, ModelTarget>;
|
|
41
|
-
limits: {
|
|
42
|
-
concurrency: number;
|
|
43
|
-
timeoutMs: number;
|
|
44
|
-
maxInputChars: number;
|
|
45
|
-
maxOutputTokens: number;
|
|
46
|
-
maxAttempts: number;
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
50
|
-
interface PurposeSpec {
|
|
51
|
-
id: string;
|
|
52
|
-
label: string;
|
|
53
|
-
defaultTarget: ModelTarget;
|
|
54
|
-
maxOutputTokens?: number;
|
|
55
|
-
maxInputChars?: number;
|
|
56
|
-
timeoutMs?: number;
|
|
57
|
-
}
|
|
58
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
59
|
-
interface ResolvedRoute extends ModelRoute {
|
|
60
|
-
source: 'override' | 'purpose' | 'default';
|
|
61
|
-
target: ModelTarget;
|
|
62
|
-
policyRevision: number;
|
|
63
|
-
inheritedRole?: ModelRole;
|
|
64
|
-
}
|
|
65
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
66
|
-
interface AuxiliaryRequest {
|
|
67
|
-
purpose: string;
|
|
68
|
-
sessionId?: string;
|
|
69
|
-
input: string;
|
|
70
|
-
system: string;
|
|
71
|
-
sourceVersion: string;
|
|
72
|
-
contractVersion?: number;
|
|
73
|
-
promptVersion?: string;
|
|
74
|
-
schemaVersion?: string;
|
|
75
|
-
override?: ModelTarget;
|
|
76
|
-
signal?: AbortSignal;
|
|
77
|
-
isCurrent?: () => boolean;
|
|
78
|
-
priority?: 'interactive' | 'background';
|
|
79
|
-
insert?: {
|
|
80
|
-
maxChars: number;
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
84
|
-
interface UsageReceipt {
|
|
85
|
-
id: string;
|
|
86
|
-
plugin: string;
|
|
87
|
-
purpose: string;
|
|
88
|
-
sessionId?: string;
|
|
89
|
-
sourceVersion: string;
|
|
90
|
-
contractVersion?: number;
|
|
91
|
-
promptVersion?: string;
|
|
92
|
-
schemaVersion?: string;
|
|
93
|
-
route?: ResolvedRoute;
|
|
94
|
-
status: 'success' | 'failed' | 'cancelled' | 'superseded' | 'skipped';
|
|
95
|
-
attempts: number;
|
|
96
|
-
inputTokens?: number;
|
|
97
|
-
outputTokens?: number;
|
|
98
|
-
cost: number | null;
|
|
99
|
-
startedAt: number;
|
|
100
|
-
finishedAt: number;
|
|
101
|
-
error?: string;
|
|
102
|
-
}
|
|
103
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
104
|
-
interface AuxiliaryResult {
|
|
105
|
-
text: string;
|
|
106
|
-
receipt: UsageReceipt;
|
|
107
|
-
}
|
|
108
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
109
|
-
interface AiFeatureScope {
|
|
110
|
-
readonly plugin: string;
|
|
111
|
-
readonly signal: AbortSignal;
|
|
112
|
-
readonly active: boolean;
|
|
113
|
-
registerPurpose(spec: PurposeSpec): () => void;
|
|
114
|
-
run(request: AuxiliaryRequest): Promise<AuxiliaryResult>;
|
|
115
|
-
dispose(): void;
|
|
116
|
-
}
|
|
117
|
-
/** @deprecated Legacy call shape. Features should call host `llm` directly. */
|
|
118
|
-
interface AiServices {
|
|
119
|
-
activate(plugin: string): AiFeatureScope;
|
|
120
|
-
importPurposes(migrationId: string, defaults: Record<string, ModelTarget>, roles?: AiPolicy['roles']): Promise<AiPolicy>;
|
|
121
|
-
getPolicy(): AiPolicy;
|
|
122
|
-
updatePolicy(policy: Omit<AiPolicy, 'revision'>, expectedRevision: number): Promise<AiPolicy>;
|
|
123
|
-
resolve(purpose: string, sessionId?: string, override?: ModelTarget): Promise<ResolvedRoute>;
|
|
124
|
-
purposes(): Array<PurposeSpec & {
|
|
125
|
-
plugin: string;
|
|
126
|
-
}>;
|
|
127
|
-
usage(): UsageReceipt[];
|
|
128
|
-
}
|
|
129
20
|
type RpcResult<T = unknown> = {
|
|
130
21
|
ok: true;
|
|
131
22
|
value: T;
|
|
@@ -260,6 +151,8 @@ interface SubagentSessionLike {
|
|
|
260
151
|
readonly parentSession?: unknown;
|
|
261
152
|
readonly origin?: unknown;
|
|
262
153
|
readonly delegationDepth?: unknown;
|
|
154
|
+
readonly isSeeded?: unknown;
|
|
155
|
+
readonly classification?: unknown;
|
|
263
156
|
};
|
|
264
157
|
}
|
|
265
158
|
/**
|
|
@@ -274,6 +167,69 @@ interface SubagentSessionLike {
|
|
|
274
167
|
declare function isSubagentSession(session: unknown): boolean;
|
|
275
168
|
/** Project keys come only from the host-validated session directory, never client input. */
|
|
276
169
|
declare function projectIdFromCwd(cwd: string | undefined): string | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Remote contribution for model-settings change notifications.
|
|
172
|
+
* Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
|
|
173
|
+
* subscribers degrade silently when that package is not installed.
|
|
174
|
+
* The stream carries invalidations only — `ready` is never a settings snapshot.
|
|
175
|
+
*/
|
|
176
|
+
declare const MODEL_SETTINGS_CHANGES_REMOTE: {
|
|
177
|
+
package: string;
|
|
178
|
+
descriptors: {
|
|
179
|
+
id: string;
|
|
180
|
+
service: string;
|
|
181
|
+
namespace: string;
|
|
182
|
+
method: string;
|
|
183
|
+
mode: "stream";
|
|
184
|
+
invocation: {
|
|
185
|
+
kind: "direct";
|
|
186
|
+
};
|
|
187
|
+
parameters: readonly [];
|
|
188
|
+
cancellation: {
|
|
189
|
+
parameter: "signal";
|
|
190
|
+
};
|
|
191
|
+
}[];
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Repository extension: a native web provider announces that its local
|
|
195
|
+
* availability changed. Kind is `search` or `fetch`; id is the provider id.
|
|
196
|
+
* Listeners refresh management state and must not treat this as a network probe.
|
|
197
|
+
*/
|
|
198
|
+
declare const WEB_PROVIDER_AVAILABILITY_EVENT: "web/provider-availability-updated";
|
|
199
|
+
/**
|
|
200
|
+
* Optional display metadata a provider may attach when it registers directly
|
|
201
|
+
* with `ctx.web`. Execution and credentials stay with the provider; this object
|
|
202
|
+
* is display-only and must never carry secret values.
|
|
203
|
+
*/
|
|
204
|
+
interface DshWebManagementDescriptor {
|
|
205
|
+
readonly id: string;
|
|
206
|
+
readonly label: string;
|
|
207
|
+
readonly description: string;
|
|
208
|
+
readonly defaultBaseURL?: string;
|
|
209
|
+
readonly credentialRef?: string;
|
|
210
|
+
/** True when the key is shared with another surface. The page must not delete it. */
|
|
211
|
+
readonly credentialShared?: boolean;
|
|
212
|
+
/** Optional notice naming the settings surface that owns a shared credential. */
|
|
213
|
+
readonly credentialHint?: string;
|
|
214
|
+
readonly billing: 'request' | 'model-and-tools' | 'none' | 'unknown';
|
|
215
|
+
/** Optional provider-supplied billing summary. */
|
|
216
|
+
readonly pricing?: string;
|
|
217
|
+
/** Public HTTPS page explaining provider charges. */
|
|
218
|
+
readonly pricingUrl?: string;
|
|
219
|
+
/** Public HTTPS page where the user can create an API key. */
|
|
220
|
+
readonly signupUrl?: string;
|
|
221
|
+
/** Plugin that owns configuration. Native providers keep their settings there. */
|
|
222
|
+
readonly configurationOwner?: string;
|
|
223
|
+
/** Explicit settings link supplied by the provider, never inferred from its id. */
|
|
224
|
+
readonly configurationUrl?: string;
|
|
225
|
+
/** Fixed legacy route migrated by a bundle-owned adapter; not used by native plugins. */
|
|
226
|
+
readonly legacyId?: string;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Host Creator agent-preset id (`agentPresets.composedPreset`).
|
|
230
|
+
* Plugins that gate Creator-only tools or prompt sections compare against this.
|
|
231
|
+
*/
|
|
232
|
+
declare const CREATOR_PRESET_ID = "cordis";
|
|
277
233
|
//#endregion
|
|
278
|
-
export {
|
|
234
|
+
export { CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, ContextKnowledge, DshWebManagementDescriptor, EvidenceRef, KnowledgeKind, KnowledgeScope, MODEL_SETTINGS_CHANGES_REMOTE, MemoryMutationOptions, MemoryQuery, MemoryRecord, MemoryService, type ModelRoute, NewMemoryRecord, ProcedureKnowledge, ProducerMessageSource, ProducerSourceKind, RpcResult, SubagentSessionLike, TaskCheckpoint, TaskContract, WEB_PROVIDER_AVAILABILITY_EVENT, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
279
235
|
//# sourceMappingURL=contracts.d.ts.map
|
package/lib/contracts.js
CHANGED
|
@@ -18,7 +18,9 @@ const CHAT_EVENTS_SLOT = "dsh-editor.chat.events";
|
|
|
18
18
|
function isSubagentSession(session) {
|
|
19
19
|
const header = session?.header;
|
|
20
20
|
if (!header || typeof header !== "object") return false;
|
|
21
|
-
|
|
21
|
+
if (header.origin === "subagent" || Number(header.delegationDepth ?? 0) > 0) return true;
|
|
22
|
+
const humanFork = header.isSeeded === true && (header.delegationDepth === void 0 || header.delegationDepth === 0) && header.origin === void 0 && (header.classification === void 0 || header.classification === "ordinary" || header.classification === "free");
|
|
23
|
+
return Boolean(header.parentSession) && !humanFork;
|
|
22
24
|
}
|
|
23
25
|
/** Project keys come only from the host-validated session directory, never client input. */
|
|
24
26
|
function projectIdFromCwd(cwd) {
|
|
@@ -27,7 +29,37 @@ function projectIdFromCwd(cwd) {
|
|
|
27
29
|
if (!normalized) return void 0;
|
|
28
30
|
return normalized === "/" || /^[A-Za-z]:\/$/.test(normalized) ? normalized : normalized.replace(/\/+$/, "");
|
|
29
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Remote contribution for model-settings change notifications.
|
|
34
|
+
* Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);
|
|
35
|
+
* subscribers degrade silently when that package is not installed.
|
|
36
|
+
* The stream carries invalidations only — `ready` is never a settings snapshot.
|
|
37
|
+
*/
|
|
38
|
+
const MODEL_SETTINGS_CHANGES_REMOTE = {
|
|
39
|
+
package: "@klarkxy/dsh-model-hub",
|
|
40
|
+
descriptors: [{
|
|
41
|
+
id: "modelSettingsChanges/watch",
|
|
42
|
+
service: "modelSettingsChangesController",
|
|
43
|
+
namespace: "modelSettingsChanges",
|
|
44
|
+
method: "watch",
|
|
45
|
+
mode: "stream",
|
|
46
|
+
invocation: { kind: "direct" },
|
|
47
|
+
parameters: [],
|
|
48
|
+
cancellation: { parameter: "signal" }
|
|
49
|
+
}]
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Repository extension: a native web provider announces that its local
|
|
53
|
+
* availability changed. Kind is `search` or `fetch`; id is the provider id.
|
|
54
|
+
* Listeners refresh management state and must not treat this as a network probe.
|
|
55
|
+
*/
|
|
56
|
+
const WEB_PROVIDER_AVAILABILITY_EVENT = "web/provider-availability-updated";
|
|
57
|
+
/**
|
|
58
|
+
* Host Creator agent-preset id (`agentPresets.composedPreset`).
|
|
59
|
+
* Plugins that gate Creator-only tools or prompt sections compare against this.
|
|
60
|
+
*/
|
|
61
|
+
const CREATOR_PRESET_ID = "cordis";
|
|
30
62
|
//#endregion
|
|
31
|
-
export { CHAT_EVENTS_SLOT, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
63
|
+
export { CHAT_EVENTS_SLOT, CREATOR_PRESET_ID, MODEL_SETTINGS_CHANGES_REMOTE, WEB_PROVIDER_AVAILABILITY_EVENT, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
32
64
|
|
|
33
65
|
//# sourceMappingURL=contracts.js.map
|
package/lib/contracts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { ContextFormed } from '@deepseek-ai/dsh-llm'\n\n/** Public, browser-safe contracts. Host implementations stay in their owning packages. */\nexport type ProducerSourceKind = `plugin:${string}`\nexport type ProducerMessageSource = { kind: ProducerSourceKind; plugin: string } & ContextFormed\n\n/**\n * V4 sessions persist the producing extension in `kind`; the retired generic\n * `plugin` wrapper is not admitted. Each feature plugin declares its own kind.\n */\ndeclare module '@deepseek-ai/dsh-llm' {\n interface MessageSourceMap {\n [kind: ProducerSourceKind]: ProducerMessageSource\n }\n}\n\nexport function producerMessageSource(plugin: string): ProducerMessageSource {\n return { kind: `plugin:${plugin}`, plugin }\n}\n\n/** A concrete provider model. Features store this on their own settings page. */\nexport interface ModelRoute { provider: string; model: string; reasoningEffort?: string }\n\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport type ModelRole = 'normal' | 'weak' | 'strong' | 'fantasy'\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport type ModelTarget = { kind: 'role'; role: ModelRole } | { kind: 'session' } | ({ kind: 'model' } & ModelRoute)\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AiPolicy {\n revision: number\n roles: Partial<Record<ModelRole, ModelRoute>>\n purposes: Record<string, ModelTarget>\n limits: { concurrency: number; timeoutMs: number; maxInputChars: number; maxOutputTokens: number; maxAttempts: number }\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface PurposeSpec {\n id: string\n label: string\n defaultTarget: ModelTarget\n maxOutputTokens?: number\n maxInputChars?: number\n timeoutMs?: number\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface ResolvedRoute extends ModelRoute {\n source: 'override' | 'purpose' | 'default'\n target: ModelTarget\n policyRevision: number\n inheritedRole?: ModelRole\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AuxiliaryRequest {\n purpose: string\n sessionId?: string\n input: string\n system: string\n sourceVersion: string\n contractVersion?: number\n promptVersion?: string\n schemaVersion?: string\n override?: ModelTarget\n signal?: AbortSignal\n isCurrent?: () => boolean\n priority?: 'interactive' | 'background'\n insert?: { maxChars: number }\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface UsageReceipt {\n id: string\n plugin: string\n purpose: string\n sessionId?: string\n sourceVersion: string\n contractVersion?: number\n promptVersion?: string\n schemaVersion?: string\n route?: ResolvedRoute\n status: 'success' | 'failed' | 'cancelled' | 'superseded' | 'skipped'\n attempts: number\n inputTokens?: number\n outputTokens?: number\n cost: number | null\n startedAt: number\n finishedAt: number\n error?: string\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AuxiliaryResult { text: string; receipt: UsageReceipt }\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AiFeatureScope {\n readonly plugin: string\n readonly signal: AbortSignal\n readonly active: boolean\n registerPurpose(spec: PurposeSpec): () => void\n run(request: AuxiliaryRequest): Promise<AuxiliaryResult>\n dispose(): void\n}\n/** @deprecated Legacy call shape. Features should call host `llm` directly. */\nexport interface AiServices {\n activate(plugin: string): AiFeatureScope\n importPurposes(migrationId: string, defaults: Record<string, ModelTarget>, roles?: AiPolicy['roles']): Promise<AiPolicy>\n getPolicy(): AiPolicy\n updatePolicy(policy: Omit<AiPolicy, 'revision'>, expectedRevision: number): Promise<AiPolicy>\n resolve(purpose: string, sessionId?: string, override?: ModelTarget): Promise<ResolvedRoute>\n purposes(): Array<PurposeSpec & { plugin: string }>\n usage(): UsageReceipt[]\n}\n\nexport type RpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }\nexport const CHAT_EVENTS_SLOT = 'dsh-editor.chat.events'\nexport interface EvidenceRef { sessionId: string; seq: number; kind: 'user' | 'tool' | 'turn' | 'manual'; excerpt?: string }\nexport interface TaskContract {\n id: string\n sessionId: string\n sourceVersion: string\n revision: number\n goal: string\n deliverables: string[]\n inScope: string[]\n outOfScope: string[]\n constraints: string[]\n acceptance: string[]\n assumptions: string[]\n questions: string[]\n evidence: EvidenceRef[]\n readiness: 'pending' | 'clear-request' | 'user-confirmed' | 'disclosed-assumptions' | 'cancelled' | 'stale'\n updatedAt: number\n}\nexport interface TaskCheckpoint {\n id: string\n sessionId: string\n sourceVersion: string\n contractVersion?: number\n fromSeq: number\n toSeq: number\n revision: number\n status: 'running' | 'completed' | 'failed' | 'cancelled'\n items: Array<{ label: string; state: 'pending' | 'verified' | 'failed'; evidence: EvidenceRef[] }>\n constraints: string[]\n nextAction: string\n createdAt: number\n}\nexport type KnowledgeScope = { kind: 'global' } | { kind: 'project'; projectId: string }\nexport type KnowledgeKind = 'preference' | 'project-fact' | 'decision' | 'vocabulary' | 'activity' | 'lesson'\n/** Descriptive context, never an executable instruction or authorization. */\nexport interface ContextKnowledge {\n subject: string\n domain: string\n key: string\n aliases: string[]\n observedAt: number\n /** The user's own time expression, not an inferred completion date. */\n eventTime?: string\n activityStatus?: 'planned' | 'in-progress' | 'blocked' | 'paused' | 'completed' | 'cancelled' | 'unknown'\n}\n/** A scoped method. Observations remain candidates until explicitly accepted. */\nexport interface ProcedureKnowledge {\n origin: 'instruction' | 'observation'\n goal: string\n when: string[]\n steps: string[]\n avoid: string[]\n verify: string[]\n}\nexport interface MemoryRecord {\n id: string\n revision: number\n scope: KnowledgeScope\n kind: KnowledgeKind\n status: 'candidate' | 'active' | 'rejected' | 'superseded' | 'revoked' | 'deleted'\n title: string\n content: string\n tags: string[]\n evidence: EvidenceRef[]\n exceptions: string[]\n source: 'user' | 'memory' | 'dream' | 'self-improvement'\n context?: ContextKnowledge\n procedure?: ProcedureKnowledge\n createdAt: number\n updatedAt: number\n expiresAt?: number\n supersedes?: string[]\n /** Exact record revisions behind a derived candidate; revalidated on adoption. */\n basis?: Array<{ id: string; revision: number }>\n}\nexport type NewMemoryRecord = Omit<MemoryRecord, 'id' | 'revision' | 'createdAt' | 'updatedAt'>\nexport interface MemoryQuery { scope: KnowledgeScope; query?: string; kinds?: KnowledgeKind[]; statuses?: MemoryRecord['status'][]; limit?: number }\nexport interface MemoryMutationOptions { signal?: AbortSignal; isCurrent?: () => boolean }\nexport interface MemoryService {\n list(query: MemoryQuery): Promise<MemoryRecord[]>\n create(record: NewMemoryRecord, options?: MemoryMutationOptions): Promise<MemoryRecord>\n update(id: string, patch: Partial<Pick<MemoryRecord, 'title' | 'content' | 'tags' | 'exceptions' | 'status' | 'expiresAt'>>, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n /** Explicit user promotion, persisted atomically on the same record; preserves expiry and evidence. */\n promoteToGlobal(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n remove(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<void>\n recall(query: MemoryQuery): Promise<MemoryRecord[]>\n}\n\n/**\n * The session creation metadata the host snapshots when it creates a delegated child\n * thread. Mirrors `CreateAgentOptions.meta` in `@deepseek-ai/dsh-agent`; it is durable\n * session data, so it survives a restart and needs no ancestry walk.\n */\nexport interface SubagentSessionLike {\n readonly header?: {\n readonly parentSession?: unknown\n readonly origin?: unknown\n readonly delegationDepth?: unknown\n }\n}\n\n/**\n * True when `session` is a delegated child thread rather than a human-driven top-level\n * conversation.\n *\n * A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt\n * is delivered as the child's own user message, so delegated work is indistinguishable\n * from a person typing once you only look at the message. Plugins use this to keep\n * automatic writes and derived model calls on top-level sessions.\n */\nexport function isSubagentSession(session: unknown): boolean {\n const header = (session as SubagentSessionLike | null | undefined)?.header\n if (!header || typeof header !== 'object') return false\n return Boolean(header.parentSession)\n || header.origin === 'subagent'\n || Number(header.delegationDepth ?? 0) > 0\n}\n\n/** Project keys come only from the host-validated session directory, never client input. */\nexport function projectIdFromCwd(cwd: string | undefined): string | undefined {\n if (typeof cwd !== 'string') return undefined\n const normalized = cwd.trim().replaceAll('\\\\', '/')\n if (!normalized) return undefined\n return normalized === '/' || /^[A-Za-z]:\\/$/.test(normalized) ? normalized : normalized.replace(/\\/+$/, '')\n}\n"],"mappings":";AAgBA,SAAgB,sBAAsB,QAAuC;CAC3E,OAAO;EAAE,MAAM,UAAU;EAAU;CAAO;AAC5C;AA2FA,MAAa,mBAAmB;;;;;;;;;;AA+GhC,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,SAAU,SAAoD;CACpE,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,OAAO,QAAQ,OAAO,aAAa,KAC9B,OAAO,WAAW,cAClB,OAAO,OAAO,mBAAmB,CAAC,IAAI;AAC7C;;AAGA,SAAgB,iBAAiB,KAA6C;CAC5E,IAAI,OAAO,QAAQ,UAAU,OAAO,KAAA;CACpC,MAAM,aAAa,IAAI,KAAK,CAAC,CAAC,WAAW,MAAM,GAAG;CAClD,IAAI,CAAC,YAAY,OAAO,KAAA;CACxB,OAAO,eAAe,OAAO,gBAAgB,KAAK,UAAU,IAAI,aAAa,WAAW,QAAQ,QAAQ,EAAE;AAC5G"}
|
|
1
|
+
{"version":3,"file":"contracts.js","names":[],"sources":["../src/contracts.ts"],"sourcesContent":["import type { ContextFormed } from '@deepseek-ai/dsh-llm'\nimport type { ModelRoute } from '@klarkxy/dsh-model-route'\n\n/**\n * The canonical home is @klarkxy/dsh-model-route; re-exported here so existing\n * plugin-kit consumers keep compiling.\n */\nexport type { ModelRoute }\n\n/** Public, browser-safe contracts. Host implementations stay in their owning packages. */\nexport type ProducerSourceKind = `plugin:${string}`\nexport type ProducerMessageSource = { kind: ProducerSourceKind; plugin: string } & ContextFormed\n\n/**\n * V4 sessions persist the producing extension in `kind`; the retired generic\n * `plugin` wrapper is not admitted. Each feature plugin declares its own kind.\n */\ndeclare module '@deepseek-ai/dsh-llm' {\n interface MessageSourceMap {\n [kind: ProducerSourceKind]: ProducerMessageSource\n }\n}\n\nexport function producerMessageSource(plugin: string): ProducerMessageSource {\n return { kind: `plugin:${plugin}`, plugin }\n}\n\nexport type RpcResult<T = unknown> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }\nexport const CHAT_EVENTS_SLOT = 'dsh-editor.chat.events'\nexport interface EvidenceRef { sessionId: string; seq: number; kind: 'user' | 'tool' | 'turn' | 'manual'; excerpt?: string }\nexport interface TaskContract {\n id: string\n sessionId: string\n sourceVersion: string\n revision: number\n goal: string\n deliverables: string[]\n inScope: string[]\n outOfScope: string[]\n constraints: string[]\n acceptance: string[]\n assumptions: string[]\n questions: string[]\n evidence: EvidenceRef[]\n readiness: 'pending' | 'clear-request' | 'user-confirmed' | 'disclosed-assumptions' | 'cancelled' | 'stale'\n updatedAt: number\n}\nexport interface TaskCheckpoint {\n id: string\n sessionId: string\n sourceVersion: string\n contractVersion?: number\n fromSeq: number\n toSeq: number\n revision: number\n status: 'running' | 'completed' | 'failed' | 'cancelled'\n items: Array<{ label: string; state: 'pending' | 'verified' | 'failed'; evidence: EvidenceRef[] }>\n constraints: string[]\n nextAction: string\n createdAt: number\n}\nexport type KnowledgeScope = { kind: 'global' } | { kind: 'project'; projectId: string }\nexport type KnowledgeKind = 'preference' | 'project-fact' | 'decision' | 'vocabulary' | 'activity' | 'lesson'\n/** Descriptive context, never an executable instruction or authorization. */\nexport interface ContextKnowledge {\n subject: string\n domain: string\n key: string\n aliases: string[]\n observedAt: number\n /** The user's own time expression, not an inferred completion date. */\n eventTime?: string\n activityStatus?: 'planned' | 'in-progress' | 'blocked' | 'paused' | 'completed' | 'cancelled' | 'unknown'\n}\n/** A scoped method. Observations remain candidates until explicitly accepted. */\nexport interface ProcedureKnowledge {\n origin: 'instruction' | 'observation'\n goal: string\n when: string[]\n steps: string[]\n avoid: string[]\n verify: string[]\n}\nexport interface MemoryRecord {\n id: string\n revision: number\n scope: KnowledgeScope\n kind: KnowledgeKind\n status: 'candidate' | 'active' | 'rejected' | 'superseded' | 'revoked' | 'deleted'\n title: string\n content: string\n tags: string[]\n evidence: EvidenceRef[]\n exceptions: string[]\n source: 'user' | 'memory' | 'dream' | 'self-improvement'\n context?: ContextKnowledge\n procedure?: ProcedureKnowledge\n createdAt: number\n updatedAt: number\n expiresAt?: number\n supersedes?: string[]\n /** Exact record revisions behind a derived candidate; revalidated on adoption. */\n basis?: Array<{ id: string; revision: number }>\n}\nexport type NewMemoryRecord = Omit<MemoryRecord, 'id' | 'revision' | 'createdAt' | 'updatedAt'>\nexport interface MemoryQuery { scope: KnowledgeScope; query?: string; kinds?: KnowledgeKind[]; statuses?: MemoryRecord['status'][]; limit?: number }\nexport interface MemoryMutationOptions { signal?: AbortSignal; isCurrent?: () => boolean }\nexport interface MemoryService {\n list(query: MemoryQuery): Promise<MemoryRecord[]>\n create(record: NewMemoryRecord, options?: MemoryMutationOptions): Promise<MemoryRecord>\n update(id: string, patch: Partial<Pick<MemoryRecord, 'title' | 'content' | 'tags' | 'exceptions' | 'status' | 'expiresAt'>>, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n /** Explicit user promotion, persisted atomically on the same record; preserves expiry and evidence. */\n promoteToGlobal(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>\n remove(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<void>\n recall(query: MemoryQuery): Promise<MemoryRecord[]>\n}\n\n/**\n * The session creation metadata the host snapshots when it creates a delegated child\n * thread. Mirrors `CreateAgentOptions.meta` in `@deepseek-ai/dsh-agent`; it is durable\n * session data, so it survives a restart and needs no ancestry walk.\n */\nexport interface SubagentSessionLike {\n readonly header?: {\n readonly parentSession?: unknown\n readonly origin?: unknown\n readonly delegationDepth?: unknown\n readonly isSeeded?: unknown\n readonly classification?: unknown\n }\n}\n\n/**\n * True when `session` is a delegated child thread rather than a human-driven top-level\n * conversation.\n *\n * A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt\n * is delivered as the child's own user message, so delegated work is indistinguishable\n * from a person typing once you only look at the message. Plugins use this to keep\n * automatic writes and derived model calls on top-level sessions.\n */\nexport function isSubagentSession(session: unknown): boolean {\n const header = (session as SubagentSessionLike | null | undefined)?.header\n if (!header || typeof header !== 'object') return false\n if (header.origin === 'subagent' || Number(header.delegationDepth ?? 0) > 0) return true\n // Native human forks also persist parentSession for history lineage. A fork's\n // seeded header distinguishes it from legacy children whose parent is the\n // only marker. Native rc.2 forks omit depth; patched hosts may persist zero.\n // Explicit delegation and unknown metadata still keep the child guard.\n const humanFork = header.isSeeded === true\n && (header.delegationDepth === undefined || header.delegationDepth === 0)\n && header.origin === undefined\n && (header.classification === undefined || header.classification === 'ordinary' || header.classification === 'free')\n return Boolean(header.parentSession) && !humanFork\n}\n\n/** Project keys come only from the host-validated session directory, never client input. */\nexport function projectIdFromCwd(cwd: string | undefined): string | undefined {\n if (typeof cwd !== 'string') return undefined\n const normalized = cwd.trim().replaceAll('\\\\', '/')\n if (!normalized) return undefined\n return normalized === '/' || /^[A-Za-z]:\\/$/.test(normalized) ? normalized : normalized.replace(/\\/+$/, '')\n}\n\n/**\n * Remote contribution for model-settings change notifications.\n * Provided by `@klarkxy/dsh-model-hub` (`ModelSettingsChangesController`);\n * subscribers degrade silently when that package is not installed.\n * The stream carries invalidations only — `ready` is never a settings snapshot.\n */\nexport const MODEL_SETTINGS_CHANGES_REMOTE = {\n package: '@klarkxy/dsh-model-hub',\n descriptors: [{\n id: 'modelSettingsChanges/watch',\n service: 'modelSettingsChangesController',\n namespace: 'modelSettingsChanges',\n method: 'watch',\n mode: 'stream' as const,\n invocation: { kind: 'direct' as const },\n parameters: [] as const,\n cancellation: { parameter: 'signal' as const },\n }],\n}\n\n/**\n * Repository extension: a native web provider announces that its local\n * availability changed. Kind is `search` or `fetch`; id is the provider id.\n * Listeners refresh management state and must not treat this as a network probe.\n */\nexport const WEB_PROVIDER_AVAILABILITY_EVENT = 'web/provider-availability-updated' as const\n\n/**\n * Optional display metadata a provider may attach when it registers directly\n * with `ctx.web`. Execution and credentials stay with the provider; this object\n * is display-only and must never carry secret values.\n */\nexport interface DshWebManagementDescriptor {\n readonly id: string\n readonly label: string\n readonly description: string\n readonly defaultBaseURL?: string\n readonly credentialRef?: string\n /** True when the key is shared with another surface. The page must not delete it. */\n readonly credentialShared?: boolean\n /** Optional notice naming the settings surface that owns a shared credential. */\n readonly credentialHint?: string\n readonly billing: 'request' | 'model-and-tools' | 'none' | 'unknown'\n /** Optional provider-supplied billing summary. */\n readonly pricing?: string\n /** Public HTTPS page explaining provider charges. */\n readonly pricingUrl?: string\n /** Public HTTPS page where the user can create an API key. */\n readonly signupUrl?: string\n /** Plugin that owns configuration. Native providers keep their settings there. */\n readonly configurationOwner?: string\n /** Explicit settings link supplied by the provider, never inferred from its id. */\n readonly configurationUrl?: string\n /** Fixed legacy route migrated by a bundle-owned adapter; not used by native plugins. */\n readonly legacyId?: string\n}\n\n/**\n * Host Creator agent-preset id (`agentPresets.composedPreset`).\n * Plugins that gate Creator-only tools or prompt sections compare against this.\n */\nexport const CREATOR_PRESET_ID = 'cordis'\n"],"mappings":";AAuBA,SAAgB,sBAAsB,QAAuC;CAC3E,OAAO;EAAE,MAAM,UAAU;EAAU;CAAO;AAC5C;AAGA,MAAa,mBAAmB;;;;;;;;;;AAiHhC,SAAgB,kBAAkB,SAA2B;CAC3D,MAAM,SAAU,SAAoD;CACpE,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO;CAClD,IAAI,OAAO,WAAW,cAAc,OAAO,OAAO,mBAAmB,CAAC,IAAI,GAAG,OAAO;CAKpF,MAAM,YAAY,OAAO,aAAa,SAChC,OAAO,oBAAoB,KAAA,KAAa,OAAO,oBAAoB,MACpE,OAAO,WAAW,KAAA,MACjB,OAAO,mBAAmB,KAAA,KAAa,OAAO,mBAAmB,cAAc,OAAO,mBAAmB;CAC/G,OAAO,QAAQ,OAAO,aAAa,KAAK,CAAC;AAC3C;;AAGA,SAAgB,iBAAiB,KAA6C;CAC5E,IAAI,OAAO,QAAQ,UAAU,OAAO,KAAA;CACpC,MAAM,aAAa,IAAI,KAAK,CAAC,CAAC,WAAW,MAAM,GAAG;CAClD,IAAI,CAAC,YAAY,OAAO,KAAA;CACxB,OAAO,eAAe,OAAO,gBAAgB,KAAK,UAAU,IAAI,aAAa,WAAW,QAAQ,QAAQ,EAAE;AAC5G;;;;;;;AAQA,MAAa,gCAAgC;CAC3C,SAAS;CACT,aAAa,CAAC;EACZ,IAAI;EACJ,SAAS;EACT,WAAW;EACX,QAAQ;EACR,MAAM;EACN,YAAY,EAAE,MAAM,SAAkB;EACtC,YAAY,CAAC;EACb,cAAc,EAAE,WAAW,SAAkB;CAC/C,CAAC;AACH;;;;;;AAOA,MAAa,kCAAkC;;;;;AAoC/C,MAAa,oBAAoB"}
|
package/lib/domain.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/domain.d.ts
|
|
2
|
+
type PluginDomainSpec = {
|
|
3
|
+
version: number;
|
|
4
|
+
compatibleVersions?: readonly number[];
|
|
5
|
+
layout?: 'single' | 'per-record';
|
|
6
|
+
tables: Record<string, unknown>;
|
|
7
|
+
global?: never;
|
|
8
|
+
};
|
|
9
|
+
type PluginDomainHandle = {
|
|
10
|
+
table(name: string): {
|
|
11
|
+
entries(): IterableIterator<[unknown, unknown]>;
|
|
12
|
+
};
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Open table-only plugin state using the host's lossless legacy bootstrap.
|
|
17
|
+
* The JSON backend ignores compatibleVersions in single layout, and treats
|
|
18
|
+
* unsupported/malformed legacy files as absent in per-record layout. Probe
|
|
19
|
+
* an empty result so persistent settings cannot silently become defaults.
|
|
20
|
+
* A populated per-record tree is authoritative; the retained legacy source
|
|
21
|
+
* is an initial backup, not a current-data rollback after subsequent writes.
|
|
22
|
+
*/
|
|
23
|
+
declare function openCompatibleDomain<S extends PluginDomainSpec, D extends PluginDomainHandle>(open: (spec: S) => Promise<D>, spec: S): Promise<D>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { openCompatibleDomain };
|
|
26
|
+
//# sourceMappingURL=domain.d.ts.map
|