@klarkxy/dsh-plugin-kit 0.1.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/LICENSE +41 -0
- package/README.md +59 -0
- package/docs/README.zh-CN.md +43 -0
- package/lib/client-utils.d.ts +52 -0
- package/lib/client-utils.js +94 -0
- package/lib/client-utils.js.map +1 -0
- package/lib/contracts.d.ts +279 -0
- package/lib/contracts.js +33 -0
- package/lib/contracts.js.map +1 -0
- package/lib/host-rpc.d.ts +20 -0
- package/lib/host-rpc.js +95 -0
- package/lib/host-rpc.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/llm-call.d.ts +36 -0
- package/lib/llm-call.js +118 -0
- package/lib/llm-call.js.map +1 -0
- package/lib/model-menu.d.ts +42 -0
- package/lib/model-menu.js +152 -0
- package/lib/model-menu.js.map +1 -0
- package/lib/official-ui.d.ts +110 -0
- package/lib/official-ui.js +421 -0
- package/lib/official-ui.js.map +1 -0
- package/package.json +95 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
The Star And Thank Author License (SATA)
|
|
2
|
+
Version 2.1, September 2026
|
|
3
|
+
|
|
4
|
+
Copyright © 2026 Klarkxy(278370456@qq.com)
|
|
5
|
+
|
|
6
|
+
Project Url: https://github.com/klarkxy/dsh-plugins
|
|
7
|
+
https://github.com/klarkxy/sata-license
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in
|
|
17
|
+
all copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
And wait, one more thing — an invitation, not a condition: it would be best if
|
|
20
|
+
you star/+1/like the project(s) in the project url section above, and thank the
|
|
21
|
+
author(s) in the Copyright section. You may, and arguably you should — but the
|
|
22
|
+
permissions above hold either way, star or no star.
|
|
23
|
+
|
|
24
|
+
Here are some suggested ways:
|
|
25
|
+
|
|
26
|
+
- Email the authors a thank-you letter, and make friends with him/her/them.
|
|
27
|
+
- Report bugs or issues.
|
|
28
|
+
- Tell friends what a wonderful project this is.
|
|
29
|
+
- And, sure, you can just express thanks in your mind without telling the world.
|
|
30
|
+
|
|
31
|
+
Contributors of this project by forking have the option to add his/her name and
|
|
32
|
+
forked project url at copyright and project url sections, but shall not delete
|
|
33
|
+
or modify anything else in these two sections.
|
|
34
|
+
|
|
35
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
36
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
37
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
38
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
39
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
40
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
41
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @klarkxy/dsh-plugin-kit
|
|
2
|
+
|
|
3
|
+
The shared support package for the feature plugins in this repository: shared record types, the plugin-page model menu, host RPC registration, the official-UI style contract, and one native `llm` text call. It is a library, not a DSH bundle — adding it as a dependency gives a feature plugin the pieces it needs, not a new user-visible feature of its own.
|
|
4
|
+
|
|
5
|
+
[简体中文](docs/README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
## What this package is not
|
|
8
|
+
|
|
9
|
+
- It is a dependency of feature plugins, not a DSH bundle: it declares no `dsh.bundle` and registers no plugin page, so installing it on its own changes nothing the user can see.
|
|
10
|
+
- It never starts inference by itself, and makes no model call unless a feature asks for one.
|
|
11
|
+
- It does not route models by tier, retry, time out, queue, or record usage. Each feature chooses its model on its own plugin-page row and calls the host `llm` service directly; an empty selection uses the current session model, then the host default chat model.
|
|
12
|
+
|
|
13
|
+
## Entry points
|
|
14
|
+
|
|
15
|
+
- `.` — the shared contract types, plus `registerHostRpc`, `callLlmText` and `resolveFeatureModel`.
|
|
16
|
+
- `./contracts` — browser-safe types and constants: shared memory/knowledge records, `TaskContract` / `TaskCheckpoint`, `ModelRoute`, `RpcResult`, `CHAT_EVENTS_SLOT`, `projectIdFromCwd`.
|
|
17
|
+
- `./host-rpc` — host RPC registration helper and its context type.
|
|
18
|
+
- `./client-utils` — browser-safe helpers for native plugin-page seats: `selectedSessionId`, `useNativeSeat`, `useFeatureRefresh`. `react` is an optional peer.
|
|
19
|
+
- `./model-menu` — plugin-page model select: catalog parsing, empty-route handling, reasoning-effort options.
|
|
20
|
+
- `./llm-call` — `callLlmText` and `resolveFeatureModel` as separate exports.
|
|
21
|
+
- `./official-ui` — the shared browser-side style contract: `officialUiCss(roots)`, the `--dsw-*` token allowlist, and the focus/elevation helpers.
|
|
22
|
+
|
|
23
|
+
## Plugin UI convention
|
|
24
|
+
|
|
25
|
+
A feature plugin's browser half composes the host's own UI rather than
|
|
26
|
+
restyling controls of its own. Concretely:
|
|
27
|
+
|
|
28
|
+
- **Controls come from `@deepseek-ai/dsh-client-ui-primitives`** — `Button`,
|
|
29
|
+
`Input`, `Checkbox`, `Switch`, `Tag`, `Pill`, `SegmentedControl`, `Menu`,
|
|
30
|
+
`Modal`, `Tooltip`, `Toast`, `DisclosureRow`, `StateDot`, `PathLabel` and the
|
|
31
|
+
settings-form suite. The host owns their geometry, focus ring, states and
|
|
32
|
+
localisation seams, so a copy of one drifts out of style on the next theme
|
|
33
|
+
change. A native `<select>` is the single exception: the primitives ship none,
|
|
34
|
+
so keep the platform control and give it the contract's `dsh-ui-select` class.
|
|
35
|
+
- **Layout, type tiers, cards, fields, banners, empty states and floating
|
|
36
|
+
surfaces come from `./official-ui`**, scoped to the plugin's own root classes
|
|
37
|
+
so two plugins can mount the same class name without styling each other.
|
|
38
|
+
- **Colour, radius, elevation and focus are written as the host's own
|
|
39
|
+
`--dsw-*` tokens**, never aliased behind a private palette and never carrying
|
|
40
|
+
a literal fallback: the host publishes light and dark, so a literal pins one of
|
|
41
|
+
them. A stylesheet that reaches for an unknown token fails silently — a border
|
|
42
|
+
disappears, a label inherits the wrong colour — which is why
|
|
43
|
+
`src/official-ui.spec.ts` fails the build when a plugin's styling names a token
|
|
44
|
+
that is not on the allowlist.
|
|
45
|
+
|
|
46
|
+
`OFFICIAL_THEME_TOKEN_NAMES` is that allowlist. Adding a token to it is a
|
|
47
|
+
deliberate act: confirm the name against the host theme first.
|
|
48
|
+
|
|
49
|
+
## Deprecated types
|
|
50
|
+
|
|
51
|
+
`AiPolicy`, `PurposeSpec`, `ModelTarget`, `AiServices`, `AiFeatureScope`, `AuxiliaryRequest`, `AuxiliaryResult` and `UsageReceipt` are retained under `./contracts` marked `@deprecated`. They described the retired shared model-routing service; features must not use them for new calls.
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
pnpm --filter @klarkxy/dsh-plugin-kit typecheck
|
|
57
|
+
pnpm --filter @klarkxy/dsh-plugin-kit test
|
|
58
|
+
pnpm --filter @klarkxy/dsh-plugin-kit build
|
|
59
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @klarkxy/dsh-plugin-kit
|
|
2
|
+
|
|
3
|
+
本仓库功能插件共用的支撑包:共享记录类型、插件页模型菜单、宿主 RPC 注册、官方 UI 样式契约,以及一次原生 `llm` 文本调用。它是一个库,不是 DSH bundle——把它作为依赖装上,得到的是功能插件需要的零件,而不是一个新的用户可见功能。
|
|
4
|
+
|
|
5
|
+
[English](../README.md)
|
|
6
|
+
|
|
7
|
+
## 本包不做什么
|
|
8
|
+
|
|
9
|
+
- 它是功能插件的依赖,不是 DSH bundle:不声明 `dsh.bundle`,也不注册插件页,单独安装它不会让用户看到任何变化。
|
|
10
|
+
- 它自身从不发起推理:没有功能要求时,它不会发起任何模型调用。
|
|
11
|
+
- 它不做模型档位路由、重试、超时、排队或用量记录。每个功能在自己的插件页选择模型,并直接调用宿主 `llm`;未选择时使用当前会话模型,再回落到宿主默认对话模型。
|
|
12
|
+
|
|
13
|
+
## 入口
|
|
14
|
+
|
|
15
|
+
- `.` — 共享契约类型,以及 `registerHostRpc`、`callLlmText`、`resolveFeatureModel`。
|
|
16
|
+
- `./contracts` — 浏览器安全的类型与常量:共享记忆/知识记录、`TaskContract` / `TaskCheckpoint`、`ModelRoute`、`RpcResult`、`CHAT_EVENTS_SLOT`、`projectIdFromCwd`。
|
|
17
|
+
- `./host-rpc` — 宿主 RPC 注册辅助及其上下文类型。
|
|
18
|
+
- `./client-utils` — 浏览器安全的原生插件页座位辅助:`selectedSessionId`、`useNativeSeat`、`useFeatureRefresh`。`react` 是可选对等依赖。
|
|
19
|
+
- `./model-menu` — 插件页模型菜单:目录解析、空路由处理、思考强度选项。
|
|
20
|
+
- `./llm-call` — 单独导出 `callLlmText` 与 `resolveFeatureModel`。
|
|
21
|
+
- `./official-ui` — 浏览器端共享样式契约:`officialUiCss(roots)`、`--dsw-*` token 白名单,以及焦点环与层级辅助。
|
|
22
|
+
|
|
23
|
+
## 插件 UI 约定
|
|
24
|
+
|
|
25
|
+
功能插件的浏览器端用宿主自己的组件拼装界面,而不是自己重画控件。具体是:
|
|
26
|
+
|
|
27
|
+
- **控件来自 `@deepseek-ai/dsh-client-ui-primitives`**——`Button`、`Input`、`Checkbox`、`Switch`、`Tag`、`Pill`、`SegmentedControl`、`Menu`、`Modal`、`Tooltip`、`Toast`、`DisclosureRow`、`StateDot`、`PathLabel` 与设置表单套件。几何、焦点环、状态和本地化接口都由宿主掌握,抄一份就会在下次换肤时掉队。原生 `<select>` 是唯一例外:官方组件里没有它,保留平台控件并套用契约的 `dsh-ui-select` 即可。
|
|
28
|
+
- **排版、字阶、卡片、表单、横幅、空态和浮层来自 `./official-ui`**,并限定在该插件自己的根类名下,因此两个插件可以挂同名类而互不影响。
|
|
29
|
+
- **颜色、圆角、层级和焦点环直接写宿主的 `--dsw-*` token**,不另起一套私有别名,也不带字面量回落:宿主同时发布浅色与深色,字面量会把其中一套钉死。样式表里写错 token 不会报错——边框直接消失、文字继承错颜色——所以 `src/official-ui.spec.ts` 会在插件样式表点到白名单之外的 token 时让构建失败。
|
|
30
|
+
|
|
31
|
+
`OFFICIAL_THEME_TOKEN_NAMES` 就是这份白名单。往里加 token 是一个需要确认的动作:先对照宿主主题核实名字。
|
|
32
|
+
|
|
33
|
+
## 已停用的类型
|
|
34
|
+
|
|
35
|
+
`AiPolicy`、`PurposeSpec`、`ModelTarget`、`AiServices`、`AiFeatureScope`、`AuxiliaryRequest`、`AuxiliaryResult`、`UsageReceipt` 仍保留在 `./contracts` 并标注 `@deprecated`。它们描述已停用的共享模型路由服务,新调用不得使用。
|
|
36
|
+
|
|
37
|
+
## 开发
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pnpm --filter @klarkxy/dsh-plugin-kit typecheck
|
|
41
|
+
pnpm --filter @klarkxy/dsh-plugin-kit test
|
|
42
|
+
pnpm --filter @klarkxy/dsh-plugin-kit build
|
|
43
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/client-utils.d.ts
|
|
2
|
+
interface NativeSurfaceClient {
|
|
3
|
+
sessions: {
|
|
4
|
+
binding?(sessionId: string): {
|
|
5
|
+
eventSource: {
|
|
6
|
+
subscribe(listener: () => void): () => void;
|
|
7
|
+
};
|
|
8
|
+
} | undefined;
|
|
9
|
+
};
|
|
10
|
+
uiWorkspace: {
|
|
11
|
+
current?: {
|
|
12
|
+
getSnapshot(): {
|
|
13
|
+
sessionId: string;
|
|
14
|
+
} | undefined;
|
|
15
|
+
subscribe(listener: () => void): () => void;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
uiSession: {
|
|
19
|
+
adapter: {
|
|
20
|
+
current: {
|
|
21
|
+
getSnapshot(): {
|
|
22
|
+
key?: string;
|
|
23
|
+
};
|
|
24
|
+
subscribe(listener: () => void): () => void;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
locale: {
|
|
29
|
+
getSnapshot(): {
|
|
30
|
+
active: string;
|
|
31
|
+
};
|
|
32
|
+
subscribe(listener: () => void): () => void;
|
|
33
|
+
};
|
|
34
|
+
connection?: {
|
|
35
|
+
generation?: {
|
|
36
|
+
subscribe(listener: () => void): () => void;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
41
|
+
declare function selectedSessionId(client: Pick<NativeSurfaceClient, 'uiWorkspace' | 'uiSession'>): string;
|
|
42
|
+
/** Owner props win; native settings only supplies close, so resolve its selected session. */
|
|
43
|
+
declare function useNativeSeat(client: NativeSurfaceClient, props?: unknown): {
|
|
44
|
+
sessionId: string;
|
|
45
|
+
locale: "zh" | "en";
|
|
46
|
+
hidden: boolean;
|
|
47
|
+
};
|
|
48
|
+
/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
|
|
49
|
+
declare function useFeatureRefresh(client: NativeSurfaceClient, sessionId: string, refresh: () => void, working?: boolean, enabled?: boolean): void;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { NativeSurfaceClient, selectedSessionId, useFeatureRefresh, useNativeSeat };
|
|
52
|
+
//# sourceMappingURL=client-utils.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/client-utils.ts
|
|
3
|
+
/** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
|
|
4
|
+
function selectedSessionId(client) {
|
|
5
|
+
const workspace = client.uiWorkspace?.current;
|
|
6
|
+
return workspace ? workspace.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Owner props are data the host composed, never a Cordis context: reading an
|
|
10
|
+
* undeclared property off one throws instead of returning undefined, which
|
|
11
|
+
* would crash the slot entry and abdicate its cell for good. Tolerate a
|
|
12
|
+
* throwing read so a mistaken prop degrades to "not supplied".
|
|
13
|
+
*/
|
|
14
|
+
function read(source, key) {
|
|
15
|
+
if (source === null || typeof source !== "object") return void 0;
|
|
16
|
+
try {
|
|
17
|
+
return source[key];
|
|
18
|
+
} catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function readString(source, key) {
|
|
23
|
+
const value = read(source, key);
|
|
24
|
+
return typeof value === "string" ? value : void 0;
|
|
25
|
+
}
|
|
26
|
+
/** Owner props win; native settings only supplies close, so resolve its selected session. */
|
|
27
|
+
function useNativeSeat(client, props) {
|
|
28
|
+
const current = useSyncExternalStore(useCallback((fn) => (client.uiWorkspace?.current ?? client.uiSession.adapter.current).subscribe(fn), [client]), useCallback(() => selectedSessionId(client), [client]), () => "");
|
|
29
|
+
const language = useSyncExternalStore(useCallback((fn) => client.locale.subscribe(fn), [client]), useCallback(() => client.locale.getSnapshot().active, [client]), () => "en");
|
|
30
|
+
const sessionId = readString(props, "sessionId") ?? readString(read(props, "owner"), "sessionId") ?? current;
|
|
31
|
+
const requestedLocale = read(props, "locale") ?? read(read(props, "owner"), "locale") ?? language;
|
|
32
|
+
return {
|
|
33
|
+
sessionId,
|
|
34
|
+
locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
|
|
35
|
+
hidden: read(props, "hidden") === true || read(read(props, "owner"), "hidden") === true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
|
|
39
|
+
function useFeatureRefresh(client, sessionId, refresh, working = false, enabled = true) {
|
|
40
|
+
const refreshRef = useRef(refresh);
|
|
41
|
+
refreshRef.current = refresh;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!enabled) return;
|
|
44
|
+
let timer;
|
|
45
|
+
const queue = () => {
|
|
46
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
timer = setTimeout(() => refreshRef.current(), 150);
|
|
49
|
+
};
|
|
50
|
+
const stopEvents = (sessionId ? client.sessions.binding?.(sessionId)?.eventSource : void 0)?.subscribe(queue);
|
|
51
|
+
const stopConnection = client.connection?.generation?.subscribe(queue);
|
|
52
|
+
const visibility = () => {
|
|
53
|
+
if (document.visibilityState === "visible") queue();
|
|
54
|
+
};
|
|
55
|
+
window.addEventListener("focus", queue);
|
|
56
|
+
document.addEventListener("visibilitychange", visibility);
|
|
57
|
+
document.addEventListener("toggle", queue, true);
|
|
58
|
+
return () => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
stopEvents?.();
|
|
61
|
+
stopConnection?.();
|
|
62
|
+
window.removeEventListener("focus", queue);
|
|
63
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
64
|
+
document.removeEventListener("toggle", queue, true);
|
|
65
|
+
};
|
|
66
|
+
}, [
|
|
67
|
+
client,
|
|
68
|
+
sessionId,
|
|
69
|
+
enabled
|
|
70
|
+
]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!enabled || !working) return;
|
|
73
|
+
let delay = 250;
|
|
74
|
+
const until = Date.now() + 3e5;
|
|
75
|
+
let timer;
|
|
76
|
+
const tick = () => {
|
|
77
|
+
if (Date.now() >= until) return;
|
|
78
|
+
if (document.visibilityState !== "hidden") refreshRef.current();
|
|
79
|
+
delay = Math.min(delay * 2, 2e3);
|
|
80
|
+
timer = setTimeout(tick, delay);
|
|
81
|
+
};
|
|
82
|
+
timer = setTimeout(tick, delay);
|
|
83
|
+
return () => clearTimeout(timer);
|
|
84
|
+
}, [
|
|
85
|
+
client,
|
|
86
|
+
sessionId,
|
|
87
|
+
working,
|
|
88
|
+
enabled
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { selectedSessionId, useFeatureRefresh, useNativeSeat };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=client-utils.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { ContextFormed } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
//#region src/contracts.d.ts
|
|
3
|
+
/** Public, browser-safe contracts. Host implementations stay in their owning packages. */
|
|
4
|
+
type ProducerSourceKind = `plugin:${string}`;
|
|
5
|
+
type ProducerMessageSource = {
|
|
6
|
+
kind: ProducerSourceKind;
|
|
7
|
+
plugin: string;
|
|
8
|
+
} & ContextFormed;
|
|
9
|
+
/**
|
|
10
|
+
* V4 sessions persist the producing extension in `kind`; the retired generic
|
|
11
|
+
* `plugin` wrapper is not admitted. Each feature plugin declares its own kind.
|
|
12
|
+
*/
|
|
13
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
14
|
+
interface MessageSourceMap {
|
|
15
|
+
[kind: ProducerSourceKind]: ProducerMessageSource;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
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
|
+
type RpcResult<T = unknown> = {
|
|
130
|
+
ok: true;
|
|
131
|
+
value: T;
|
|
132
|
+
} | {
|
|
133
|
+
ok: false;
|
|
134
|
+
error: {
|
|
135
|
+
code: string;
|
|
136
|
+
message: string;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
declare const CHAT_EVENTS_SLOT = "dsh-editor.chat.events";
|
|
140
|
+
interface EvidenceRef {
|
|
141
|
+
sessionId: string;
|
|
142
|
+
seq: number;
|
|
143
|
+
kind: 'user' | 'tool' | 'turn' | 'manual';
|
|
144
|
+
excerpt?: string;
|
|
145
|
+
}
|
|
146
|
+
interface TaskContract {
|
|
147
|
+
id: string;
|
|
148
|
+
sessionId: string;
|
|
149
|
+
sourceVersion: string;
|
|
150
|
+
revision: number;
|
|
151
|
+
goal: string;
|
|
152
|
+
deliverables: string[];
|
|
153
|
+
inScope: string[];
|
|
154
|
+
outOfScope: string[];
|
|
155
|
+
constraints: string[];
|
|
156
|
+
acceptance: string[];
|
|
157
|
+
assumptions: string[];
|
|
158
|
+
questions: string[];
|
|
159
|
+
evidence: EvidenceRef[];
|
|
160
|
+
readiness: 'pending' | 'clear-request' | 'user-confirmed' | 'disclosed-assumptions' | 'cancelled' | 'stale';
|
|
161
|
+
updatedAt: number;
|
|
162
|
+
}
|
|
163
|
+
interface TaskCheckpoint {
|
|
164
|
+
id: string;
|
|
165
|
+
sessionId: string;
|
|
166
|
+
sourceVersion: string;
|
|
167
|
+
contractVersion?: number;
|
|
168
|
+
fromSeq: number;
|
|
169
|
+
toSeq: number;
|
|
170
|
+
revision: number;
|
|
171
|
+
status: 'running' | 'completed' | 'failed' | 'cancelled';
|
|
172
|
+
items: Array<{
|
|
173
|
+
label: string;
|
|
174
|
+
state: 'pending' | 'verified' | 'failed';
|
|
175
|
+
evidence: EvidenceRef[];
|
|
176
|
+
}>;
|
|
177
|
+
constraints: string[];
|
|
178
|
+
nextAction: string;
|
|
179
|
+
createdAt: number;
|
|
180
|
+
}
|
|
181
|
+
type KnowledgeScope = {
|
|
182
|
+
kind: 'global';
|
|
183
|
+
} | {
|
|
184
|
+
kind: 'project';
|
|
185
|
+
projectId: string;
|
|
186
|
+
};
|
|
187
|
+
type KnowledgeKind = 'preference' | 'project-fact' | 'decision' | 'vocabulary' | 'activity' | 'lesson';
|
|
188
|
+
/** Descriptive context, never an executable instruction or authorization. */
|
|
189
|
+
interface ContextKnowledge {
|
|
190
|
+
subject: string;
|
|
191
|
+
domain: string;
|
|
192
|
+
key: string;
|
|
193
|
+
aliases: string[];
|
|
194
|
+
observedAt: number;
|
|
195
|
+
/** The user's own time expression, not an inferred completion date. */
|
|
196
|
+
eventTime?: string;
|
|
197
|
+
activityStatus?: 'planned' | 'in-progress' | 'blocked' | 'paused' | 'completed' | 'cancelled' | 'unknown';
|
|
198
|
+
}
|
|
199
|
+
/** A scoped method. Observations remain candidates until explicitly accepted. */
|
|
200
|
+
interface ProcedureKnowledge {
|
|
201
|
+
origin: 'instruction' | 'observation';
|
|
202
|
+
goal: string;
|
|
203
|
+
when: string[];
|
|
204
|
+
steps: string[];
|
|
205
|
+
avoid: string[];
|
|
206
|
+
verify: string[];
|
|
207
|
+
}
|
|
208
|
+
interface MemoryRecord {
|
|
209
|
+
id: string;
|
|
210
|
+
revision: number;
|
|
211
|
+
scope: KnowledgeScope;
|
|
212
|
+
kind: KnowledgeKind;
|
|
213
|
+
status: 'candidate' | 'active' | 'rejected' | 'superseded' | 'revoked' | 'deleted';
|
|
214
|
+
title: string;
|
|
215
|
+
content: string;
|
|
216
|
+
tags: string[];
|
|
217
|
+
evidence: EvidenceRef[];
|
|
218
|
+
exceptions: string[];
|
|
219
|
+
source: 'user' | 'memory' | 'dream' | 'self-improvement';
|
|
220
|
+
context?: ContextKnowledge;
|
|
221
|
+
procedure?: ProcedureKnowledge;
|
|
222
|
+
createdAt: number;
|
|
223
|
+
updatedAt: number;
|
|
224
|
+
expiresAt?: number;
|
|
225
|
+
supersedes?: string[];
|
|
226
|
+
/** Exact record revisions behind a derived candidate; revalidated on adoption. */
|
|
227
|
+
basis?: Array<{
|
|
228
|
+
id: string;
|
|
229
|
+
revision: number;
|
|
230
|
+
}>;
|
|
231
|
+
}
|
|
232
|
+
type NewMemoryRecord = Omit<MemoryRecord, 'id' | 'revision' | 'createdAt' | 'updatedAt'>;
|
|
233
|
+
interface MemoryQuery {
|
|
234
|
+
scope: KnowledgeScope;
|
|
235
|
+
query?: string;
|
|
236
|
+
kinds?: KnowledgeKind[];
|
|
237
|
+
statuses?: MemoryRecord['status'][];
|
|
238
|
+
limit?: number;
|
|
239
|
+
}
|
|
240
|
+
interface MemoryMutationOptions {
|
|
241
|
+
signal?: AbortSignal;
|
|
242
|
+
isCurrent?: () => boolean;
|
|
243
|
+
}
|
|
244
|
+
interface MemoryService {
|
|
245
|
+
list(query: MemoryQuery): Promise<MemoryRecord[]>;
|
|
246
|
+
create(record: NewMemoryRecord, options?: MemoryMutationOptions): Promise<MemoryRecord>;
|
|
247
|
+
update(id: string, patch: Partial<Pick<MemoryRecord, 'title' | 'content' | 'tags' | 'exceptions' | 'status' | 'expiresAt'>>, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>;
|
|
248
|
+
/** Explicit user promotion, persisted atomically on the same record; preserves expiry and evidence. */
|
|
249
|
+
promoteToGlobal(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<MemoryRecord>;
|
|
250
|
+
remove(id: string, expectedRevision: number, options?: MemoryMutationOptions): Promise<void>;
|
|
251
|
+
recall(query: MemoryQuery): Promise<MemoryRecord[]>;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The session creation metadata the host snapshots when it creates a delegated child
|
|
255
|
+
* thread. Mirrors `CreateAgentOptions.meta` in `@deepseek-ai/dsh-agent`; it is durable
|
|
256
|
+
* session data, so it survives a restart and needs no ancestry walk.
|
|
257
|
+
*/
|
|
258
|
+
interface SubagentSessionLike {
|
|
259
|
+
readonly header?: {
|
|
260
|
+
readonly parentSession?: unknown;
|
|
261
|
+
readonly origin?: unknown;
|
|
262
|
+
readonly delegationDepth?: unknown;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* True when `session` is a delegated child thread rather than a human-driven top-level
|
|
267
|
+
* conversation.
|
|
268
|
+
*
|
|
269
|
+
* A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt
|
|
270
|
+
* is delivered as the child's own user message, so delegated work is indistinguishable
|
|
271
|
+
* from a person typing once you only look at the message. Plugins use this to keep
|
|
272
|
+
* automatic writes and derived model calls on top-level sessions.
|
|
273
|
+
*/
|
|
274
|
+
declare function isSubagentSession(session: unknown): boolean;
|
|
275
|
+
/** Project keys come only from the host-validated session directory, never client input. */
|
|
276
|
+
declare function projectIdFromCwd(cwd: string | undefined): string | undefined;
|
|
277
|
+
//#endregion
|
|
278
|
+
export { AiFeatureScope, AiPolicy, AiServices, AuxiliaryRequest, AuxiliaryResult, CHAT_EVENTS_SLOT, ContextKnowledge, EvidenceRef, KnowledgeKind, KnowledgeScope, MemoryMutationOptions, MemoryQuery, MemoryRecord, MemoryService, ModelRole, ModelRoute, ModelTarget, NewMemoryRecord, ProcedureKnowledge, ProducerMessageSource, ProducerSourceKind, PurposeSpec, ResolvedRoute, RpcResult, SubagentSessionLike, TaskCheckpoint, TaskContract, UsageReceipt, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
279
|
+
//# sourceMappingURL=contracts.d.ts.map
|
package/lib/contracts.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/contracts.ts
|
|
2
|
+
function producerMessageSource(plugin) {
|
|
3
|
+
return {
|
|
4
|
+
kind: `plugin:${plugin}`,
|
|
5
|
+
plugin
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
const CHAT_EVENTS_SLOT = "dsh-editor.chat.events";
|
|
9
|
+
/**
|
|
10
|
+
* True when `session` is a delegated child thread rather than a human-driven top-level
|
|
11
|
+
* conversation.
|
|
12
|
+
*
|
|
13
|
+
* A message's `source.kind === 'user'` cannot answer this: a lead agent's dispatch prompt
|
|
14
|
+
* is delivered as the child's own user message, so delegated work is indistinguishable
|
|
15
|
+
* from a person typing once you only look at the message. Plugins use this to keep
|
|
16
|
+
* automatic writes and derived model calls on top-level sessions.
|
|
17
|
+
*/
|
|
18
|
+
function isSubagentSession(session) {
|
|
19
|
+
const header = session?.header;
|
|
20
|
+
if (!header || typeof header !== "object") return false;
|
|
21
|
+
return Boolean(header.parentSession) || header.origin === "subagent" || Number(header.delegationDepth ?? 0) > 0;
|
|
22
|
+
}
|
|
23
|
+
/** Project keys come only from the host-validated session directory, never client input. */
|
|
24
|
+
function projectIdFromCwd(cwd) {
|
|
25
|
+
if (typeof cwd !== "string") return void 0;
|
|
26
|
+
const normalized = cwd.trim().replaceAll("\\", "/");
|
|
27
|
+
if (!normalized) return void 0;
|
|
28
|
+
return normalized === "/" || /^[A-Za-z]:\/$/.test(normalized) ? normalized : normalized.replace(/\/+$/, "");
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { CHAT_EVENTS_SLOT, isSubagentSession, producerMessageSource, projectIdFromCwd };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=contracts.js.map
|
|
@@ -0,0 +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"}
|