dsh-qqbot 1.0.0 → 1.0.1
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 +34 -17
- package/lib/client.js +155 -177
- package/lib/index.js +76 -53
- package/lib/types/client/QqbotSettingsSection.d.ts +6 -5
- package/lib/types/client/locales.d.ts +10 -6
- package/lib/types/client/settings-controller.d.ts +37 -31
- package/lib/types/index.d.ts +14 -7
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -7,26 +7,40 @@ QQ 官方机器人插件,作为 DeepSeek Harness 的**树外插件**维护(
|
|
|
7
7
|
|
|
8
8
|
## 安装
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
从 npm 装进你运行的 profile(无需本地构建):
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
pnpm dsh plugin --profile <name> add -w link:E:/Project/dsh-plugins/dsh-qqbot
|
|
13
|
+
dsh plugin --profile <name> add -w dsh-qqbot # 最新版
|
|
14
|
+
dsh plugin --profile <name> add -w dsh-qqbot@1.0.0 # 或固定版本
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
等价的手动方式(进入 profile 目录执行 pnpm):
|
|
19
18
|
|
|
20
19
|
```sh
|
|
21
20
|
cd "$env:DSH_HOME/profiles/<name>" # DSH_HOME 未设置时是 ~/.dsh/profiles/<name>
|
|
22
|
-
pnpm add -w
|
|
21
|
+
pnpm add -w dsh-qqbot
|
|
23
22
|
```
|
|
24
23
|
|
|
25
24
|
注意:
|
|
26
25
|
|
|
27
26
|
- **必须带 `-w`**:profile 目录自带 `pnpm-workspace.yaml`,不带 `-w` 时 pnpm 报 `ERR_PNPM_ADDING_TO_ROOT` 直接失败。
|
|
28
|
-
- **用 `link:` 而不是裸路径**:裸路径是 `file:` 语义(快照副本),改源码不生效;`link:` 直接指向源码目录,重新 `pnpm run build` 后即时生效,无需重新 add。
|
|
29
27
|
- 本包没有 `dsh.bundle` 声明,不会进入 profile 的 bundles 层,这符合预期——由下面的 patch 行按包名挂载;client 半由 `dsh.client` 扫描同一条挂载行自动进浏览器。
|
|
28
|
+
- 升级到新版本:重新 `add` 一次新版本号,然后重启 dsh。registry 版是安装时的快照(不再指向源码目录),改本地源码不影响它。
|
|
29
|
+
|
|
30
|
+
### 本地开发(link 到源码)
|
|
31
|
+
|
|
32
|
+
改插件源码时用 `link:` 直接指向源码目录:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
cd E:\Project\dsh-plugins\dsh-qqbot
|
|
36
|
+
pnpm install
|
|
37
|
+
pnpm run build
|
|
38
|
+
dsh plugin --profile <name> add -w link:E:/Project/dsh-plugins/dsh-qqbot
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **用 `link:` 而不是裸路径**:裸路径是 `file:` 语义(快照副本),改源码不生效;`link:` 直接指向源码目录,重新 `pnpm run build` 后即时生效,无需重新 add。
|
|
42
|
+
- 改完发布版后切回本地版用同一条 `link:` 命令(`pnpm add` 会把依赖 spec 从 registry 改写成 link)。
|
|
43
|
+
- 反之,切回发布版:`dsh plugin --profile <name> add -w dsh-qqbot@<版本>`。
|
|
30
44
|
|
|
31
45
|
在 **profile 自己的** `cordis.patch.yml`(`$DSH_HOME/profiles/<name>/cordis.patch.yml`)挂载(凭据在设置界面填写,不再写在这里):
|
|
32
46
|
|
|
@@ -44,13 +58,14 @@ pnpm add -w link:E:/Project/dsh-plugins/dsh-qqbot
|
|
|
44
58
|
|
|
45
59
|
| 字段 | 说明 |
|
|
46
60
|
| --- | --- |
|
|
47
|
-
| AppID | QQ 开放平台机器人应用的 AppID
|
|
48
|
-
| AppSecret |
|
|
49
|
-
|
|
61
|
+
| AppID | QQ 开放平台机器人应用的 AppID。**写穿不回显**:页面只显示「已配置/未配置」状态,输入框填写新值以覆盖;「清除 AppID」可移除。 |
|
|
62
|
+
| AppSecret | **写穿不回显**:页面只显示「已配置/未配置」,留空表示保持现有密钥;「清除密钥」可移除。 |
|
|
63
|
+
|
|
64
|
+
点击「保存」后**立即生效**:host 半按需从 credential 服务读取(`credentials/updated` 提交即触发),已连接的网关会用新身份自动重连,凭据被清空则自动断开。
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
存储位置:`$DSH_HOME/.credentials.yaml`(明文,等价于环境变量;由 harness 的 credential 服务管理)。页面写入的引用名是 **`QQBOT_CRED_APP_ID` / `QQBOT_CRED_APP_SECRET`**——故意与 `QQBOT_APP_ID`/`QQBOT_APP_SECRET` 环境变量不同名:credential 服务的环境层会遮蔽同名引用并使其只读,shell 里一旦导出过 `QQBOT_APP_ID`,页面就会永远变灰,因此设置页用自己的引用名,UI 写入永远优先于环境变量。之所以走 credential 服务而不是 settings 命名空间:harness 的 settings wire 面只服务硬编码的命名空间白名单(`agent-loop`、`shell` 等),树外插件注册的命名空间不会被暴露;credential 面接受任意引用名,无需改 harness。
|
|
52
67
|
|
|
53
|
-
|
|
68
|
+
配置解析顺序:**credential 库(设置页写入)→ `settings.yaml` 的 `qqbot:` 段 → cordis.yml 行的 config 基础层 → 环境变量 `QQBOT_APP_ID` / `QQBOT_APP_SECRET`**(无头环境可用环境变量兜底)。**沙箱开关**不在此页:仅在 cordis.yml 的 `config.sandbox: true` 里配置。四处都没有 AppID/AppSecret 时插件照常挂载,`/qqbot on` 会报错并指向设置页。
|
|
54
69
|
|
|
55
70
|
## 测试
|
|
56
71
|
|
|
@@ -58,7 +73,7 @@ pnpm add -w link:E:/Project/dsh-plugins/dsh-qqbot
|
|
|
58
73
|
pnpm test
|
|
59
74
|
```
|
|
60
75
|
|
|
61
|
-
host 的 `protocol` / `gateway` / `qqbot` / `settings-controller` 四套用例可直接跑(
|
|
76
|
+
host 的 `protocol` / `gateway` / `qqbot` / `settings-controller` 四套用例可直接跑(59 例,settings-controller 通过 `vi.mock` 替换 runtime 客户端模块以绕过浏览器 bundle)。`tests/*.client.spec.ts(x)` 被 vitest 排除:它们 import `@deepseek-ai/dsh-client-runtime/client`,而 registry 上发布的是浏览器 bundle(`lib/client.js`,import 时执行 `window.__ModuleLoader__.load(...)`),node/jsdom 无法加载。这些用例在 harness 源码树内开发期已验证,客户端开关与设置页的验证走浏览器手动确认(见「使用」)。
|
|
62
77
|
|
|
63
78
|
## 使用
|
|
64
79
|
|
|
@@ -70,16 +85,18 @@ host 的 `protocol` / `gateway` / `qqbot` / `settings-controller` 四套用例
|
|
|
70
85
|
|
|
71
86
|
## peer 依赖解析
|
|
72
87
|
|
|
73
|
-
|
|
88
|
+
两种安装方式的依赖解析不同:
|
|
74
89
|
|
|
75
|
-
-
|
|
76
|
-
-
|
|
90
|
+
- **registry 版(推荐)**:包从 profile 的 `node_modules` 向上解析 peer(`@deepseek-ai/cordis`、`dsh-*` Service Definition 等),与 harness 及 profile 的 `$DSH_HOME/profiles/node_modules` 回退**共享同一份实例**,没有双实例问题。
|
|
91
|
+
- **`link:` 开发版**:symlink 目标在 harness 之外,Node 从真实路径(realpath)向上解析,peer 命中 **dsh-qqbot 项目自己的 `node_modules`**(`pnpm install` 装的最新 registry 版)。这带来两个后果:
|
|
92
|
+
- 插件与 harness 各持一份 `@deepseek-ai/cordis`(双实例)。当前启动与挂载正常,但若插件代码对 `ctx` 做跨实例判断(`instanceof` 等)会失效。
|
|
93
|
+
- peer 版本按 `^` 取最新(例如 `dsh-session@0.1.0-rc.6`),可能比正在运行的 harness(rc.5)新。运行时服务走 harness 注入的 ctx,一般兼容;harness 升级或插件用到新 API 时需对齐 peer 版本。
|
|
77
94
|
|
|
78
95
|
## 版本耦合说明
|
|
79
96
|
|
|
80
97
|
client 半的浏览器 bundle 必须用 harness 的客户端加载协议构建:`tsdown.config.ts` 里硬编码了 `PLATFORM_MODULES`(模块表 external 列表,含 `@deepseek-ai/dsh-client-runtime/client` 的 store 豁免)和 `__ModuleLoader__.load({ id })` 的包名 id。如果 harness 更新了这份平台模块列表或 `__ModuleLoader__` 协议,需要同步更新 `tsdown.config.ts`,否则浏览器端加载会失败。
|
|
81
98
|
|
|
82
|
-
设置页依赖 harness
|
|
99
|
+
设置页依赖 harness 的:credential 能力(host 的 `credentials` 服务 + `api.credentials` wire 面,由 base 组合的 `@deepseek-ai/dsh-credentials-local` 提供)和 settings 面板(`settings.section` 槽位 + settings 服务,用于 `qqbot:` 段的 base 层解析),这些都在 web 基础组合里,无需额外挂载行;如果换成自定义 profile,需保证上述行在场。
|
|
83
100
|
|
|
84
101
|
## 发布
|
|
85
102
|
|
package/lib/client.js
CHANGED
|
@@ -8,54 +8,47 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
//#region src/client/settings-controller.ts
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Credential reference holding the robot application id. Deliberately NOT the
|
|
13
|
+
* environment-variable name: the credential store's env layer shadows a
|
|
14
|
+
* same-named ref and makes it read-only, which would disable the settings
|
|
15
|
+
* page wherever `QQBOT_APP_ID` is exported.
|
|
16
|
+
*/
|
|
17
|
+
const QQBOT_APP_ID_REF = "QQBOT_CRED_APP_ID";
|
|
18
|
+
/**
|
|
19
|
+
* Credential reference holding the robot application secret; distinct from
|
|
20
|
+
* `QQBOT_APP_SECRET` for the same shadowing reason.
|
|
21
|
+
*/
|
|
22
|
+
const QQBOT_APP_SECRET_REF = "QQBOT_CRED_APP_SECRET";
|
|
13
23
|
/** Human text for a rejected wire call or transport failure. */
|
|
14
24
|
function messageOf(error) {
|
|
15
25
|
return error instanceof Error ? error.message : String(error);
|
|
16
26
|
}
|
|
17
|
-
/** Read the redacted resolved AppID, defaulting absent values to ''. */
|
|
18
|
-
function appIdIn(view) {
|
|
19
|
-
if (typeof view.value !== "object" || view.value === null) return "";
|
|
20
|
-
const value = view.value["appId"];
|
|
21
|
-
return typeof value === "string" ? value : "";
|
|
22
|
-
}
|
|
23
|
-
/** Read the redacted resolved sandbox flag, defaulting to false. */
|
|
24
|
-
function sandboxIn(view) {
|
|
25
|
-
if (typeof view.value !== "object" || view.value === null) return false;
|
|
26
|
-
return view.value["sandbox"] === true;
|
|
27
|
-
}
|
|
28
|
-
/** Whether the redacted view reports a stored AppSecret. */
|
|
29
|
-
function secretSetIn(view) {
|
|
30
|
-
return view.secrets.some((secret) => secret.path.length === 1 && secret.path[0] === "appSecret" && secret.set);
|
|
31
|
-
}
|
|
32
27
|
/**
|
|
33
|
-
* Serializes the
|
|
34
|
-
* store. Latest load wins; a refused save reloads the
|
|
35
|
-
* re-renders from the fresh
|
|
28
|
+
* Serializes the two credential references' Host reads and writes behind one
|
|
29
|
+
* snapshot store. Latest load wins; a refused save reloads the views so the
|
|
30
|
+
* form re-renders from the fresh state and the user can re-apply the edit.
|
|
36
31
|
*/
|
|
37
32
|
var QqbotSettingsController = class {
|
|
38
33
|
api;
|
|
39
34
|
/** uSES-safe state source the section renders through its hooks compartment. */
|
|
40
35
|
store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
|
|
41
36
|
status: "idle",
|
|
42
|
-
|
|
43
|
-
sandbox: false,
|
|
37
|
+
appIdSet: false,
|
|
44
38
|
secretSet: false,
|
|
45
39
|
writable: false,
|
|
46
|
-
revision: void 0,
|
|
47
40
|
error: null
|
|
48
41
|
});
|
|
49
42
|
generation = 0;
|
|
50
43
|
/**
|
|
51
|
-
* @param api - the
|
|
44
|
+
* @param api - the credentials wire face.
|
|
52
45
|
*/
|
|
53
46
|
constructor(api) {
|
|
54
47
|
this.api = api;
|
|
55
48
|
}
|
|
56
49
|
/**
|
|
57
|
-
* Refresh the snapshot from `
|
|
58
|
-
* good state and surfaces the error.
|
|
50
|
+
* Refresh the snapshot from `credentials.describe`; a failure keeps the
|
|
51
|
+
* last good state and surfaces the error.
|
|
59
52
|
* @returns settlement after the read.
|
|
60
53
|
*/
|
|
61
54
|
async load() {
|
|
@@ -65,12 +58,20 @@ window.__ModuleLoader__.load({
|
|
|
65
58
|
state.error = null;
|
|
66
59
|
});
|
|
67
60
|
try {
|
|
68
|
-
const response = await this.api.
|
|
61
|
+
const response = await this.api.credentials.describe({ refs: [QQBOT_APP_ID_REF, QQBOT_APP_SECRET_REF] });
|
|
69
62
|
if (!response.result.ok) throw new Error(response.result.error.message);
|
|
70
|
-
const
|
|
71
|
-
|
|
63
|
+
const views = response.result.value.credentials;
|
|
64
|
+
const appIdView = views[QQBOT_APP_ID_REF];
|
|
65
|
+
const secretView = views[QQBOT_APP_SECRET_REF];
|
|
66
|
+
if (appIdView === void 0 || secretView === void 0) throw new Error("credentials domain did not answer the qqbot references");
|
|
72
67
|
if (generation !== this.generation) return;
|
|
73
|
-
this.
|
|
68
|
+
this.store.update((state) => {
|
|
69
|
+
state.status = "ready";
|
|
70
|
+
state.appIdSet = appIdView.configured;
|
|
71
|
+
state.secretSet = secretView.configured;
|
|
72
|
+
state.writable = appIdView.writable && secretView.writable;
|
|
73
|
+
state.error = null;
|
|
74
|
+
});
|
|
74
75
|
} catch (error) {
|
|
75
76
|
if (generation !== this.generation) return;
|
|
76
77
|
this.store.update((state) => {
|
|
@@ -80,67 +81,41 @@ window.__ModuleLoader__.load({
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
/**
|
|
83
|
-
* Apply one user edit as ordered
|
|
84
|
-
* and returns its message.
|
|
84
|
+
* Apply one user edit as ordered credential writes, stopping at the first
|
|
85
|
+
* refusal; a refusal reloads the views and returns its message.
|
|
85
86
|
* @param patch - the fields the user changed.
|
|
86
87
|
* @returns null on success; the Host's failure text otherwise.
|
|
87
88
|
*/
|
|
88
89
|
async save(patch) {
|
|
89
|
-
const
|
|
90
|
-
if (patch.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (patch.clearSecret === true) ops.push({
|
|
104
|
-
op: "unset",
|
|
105
|
-
path: ["appSecret"]
|
|
106
|
-
});
|
|
107
|
-
if (patch.appSecret !== void 0 && patch.appSecret.length > 0) ops.push({
|
|
108
|
-
op: "set",
|
|
109
|
-
path: ["appSecret"],
|
|
110
|
-
value: patch.appSecret
|
|
111
|
-
});
|
|
112
|
-
if (ops.length === 0) return null;
|
|
113
|
-
const revision = this.store.getSnapshot().revision;
|
|
114
|
-
let response;
|
|
90
|
+
const writes = [];
|
|
91
|
+
if (patch.clearAppId === true) writes.push(() => this.api.credentials.unset({ ref: QQBOT_APP_ID_REF }));
|
|
92
|
+
const appId = patch.appId;
|
|
93
|
+
if (appId !== void 0 && appId.length > 0) writes.push(() => this.api.credentials.set({
|
|
94
|
+
ref: QQBOT_APP_ID_REF,
|
|
95
|
+
value: appId
|
|
96
|
+
}));
|
|
97
|
+
if (patch.clearSecret === true) writes.push(() => this.api.credentials.unset({ ref: QQBOT_APP_SECRET_REF }));
|
|
98
|
+
const appSecret = patch.appSecret;
|
|
99
|
+
if (appSecret !== void 0 && appSecret.length > 0) writes.push(() => this.api.credentials.set({
|
|
100
|
+
ref: QQBOT_APP_SECRET_REF,
|
|
101
|
+
value: appSecret
|
|
102
|
+
}));
|
|
103
|
+
if (writes.length === 0) return null;
|
|
115
104
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
for (const write of writes) {
|
|
106
|
+
const response = await write();
|
|
107
|
+
if (!response.result.ok) {
|
|
108
|
+
await this.load();
|
|
109
|
+
return response.result.error.message;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
121
112
|
} catch (error) {
|
|
122
113
|
await this.load();
|
|
123
114
|
return messageOf(error);
|
|
124
115
|
}
|
|
125
|
-
|
|
126
|
-
await this.load();
|
|
127
|
-
return response.result.error.message;
|
|
128
|
-
}
|
|
129
|
-
this.accept(response.result.value, this.store.getSnapshot().writable);
|
|
116
|
+
await this.load();
|
|
130
117
|
return null;
|
|
131
118
|
}
|
|
132
|
-
/** Fold one redacted wire view into the snapshot. */
|
|
133
|
-
accept(view, writable) {
|
|
134
|
-
this.store.update((state) => {
|
|
135
|
-
state.status = "ready";
|
|
136
|
-
state.appId = appIdIn(view);
|
|
137
|
-
state.sandbox = sandboxIn(view);
|
|
138
|
-
state.secretSet = secretSetIn(view);
|
|
139
|
-
state.writable = writable;
|
|
140
|
-
state.revision = view.revision;
|
|
141
|
-
state.error = null;
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
119
|
};
|
|
145
120
|
/**
|
|
146
121
|
* Refresh only after the section left idle, so an unopened page never fetches
|
|
@@ -153,7 +128,7 @@ window.__ModuleLoader__.load({
|
|
|
153
128
|
}
|
|
154
129
|
//#endregion
|
|
155
130
|
//#region \0dsh-css:E:\Project\dsh-plugins\dsh-qqbot\src\client\QqbotSettingsSection.module.css.mjs
|
|
156
|
-
const css$1 = ".TV8fAG_section{flex-direction:column;gap:12px;width:100%;padding-bottom:8px;display:flex}.TV8fAG_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:600;line-height:24px}.TV8fAG_desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:20px}.TV8fAG_field{flex-direction:column;gap:6px;display:flex}.TV8fAG_fieldLabel{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:20px}.TV8fAG_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;max-width:360px;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}.TV8fAG_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.TV8fAG_input::placeholder{color:var(--dsw-alias-label-dimmed)}.TV8fAG_input:disabled{opacity:.6;cursor:default}.TV8fAG_fieldHint{color:var(--dsw-alias-label-dimmed);margin:0;font-size:12px;line-height:18px}.TV8fAG_secretRow{align-items:center;gap:8px;width:100%;max-width:360px;display:flex}.TV8fAG_secretRow .TV8fAG_input{flex:auto}.TV8fAG_clearButton{border:1px solid var(--dsw-alias-border-l2);height:28px;font:inherit;color:var(--dsw-alias-state-error-primary);cursor:pointer;background:0 0;border-radius:8px;flex:none;padding:0 10px;font-size:13px;line-height:18px}.TV8fAG_clearButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.TV8fAG_clearButton:disabled{opacity:.6;cursor:default}.
|
|
131
|
+
const css$1 = ".TV8fAG_section{flex-direction:column;gap:12px;width:100%;padding-bottom:8px;display:flex}.TV8fAG_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:600;line-height:24px}.TV8fAG_desc{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:20px}.TV8fAG_field{flex-direction:column;gap:6px;display:flex}.TV8fAG_fieldLabel{color:var(--dsw-alias-label-primary);align-items:center;gap:8px;font-size:13px;font-weight:500;line-height:20px;display:flex}.TV8fAG_chipSet{background:var(--dsw-alias-state-warn-tertiary);height:18px;color:var(--dsw-alias-state-warn-label);border-radius:999px;align-items:center;padding:0 6px;font-size:11px;font-weight:500;line-height:18px;display:inline-flex}.TV8fAG_chipUnset{background:var(--dsw-alias-bg-layer-1);height:18px;color:var(--dsw-alias-label-dimmed);border-radius:999px;align-items:center;padding:0 6px;font-size:11px;font-weight:500;line-height:18px;display:inline-flex}.TV8fAG_input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;max-width:360px;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}.TV8fAG_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.TV8fAG_input::placeholder{color:var(--dsw-alias-label-dimmed)}.TV8fAG_input:disabled{opacity:.6;cursor:default}.TV8fAG_fieldHint{color:var(--dsw-alias-label-dimmed);margin:0;font-size:12px;line-height:18px}.TV8fAG_secretRow{align-items:center;gap:8px;width:100%;max-width:360px;display:flex}.TV8fAG_secretRow .TV8fAG_input{flex:auto}.TV8fAG_clearButton{border:1px solid var(--dsw-alias-border-l2);height:28px;font:inherit;color:var(--dsw-alias-state-error-primary);cursor:pointer;background:0 0;border-radius:8px;flex:none;padding:0 10px;font-size:13px;line-height:18px}.TV8fAG_clearButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.TV8fAG_clearButton:disabled{opacity:.6;cursor:default}.TV8fAG_actions{align-items:center;gap:12px;display:flex}.TV8fAG_saveButton{height:32px;font:inherit;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);cursor:pointer;border:none;border-radius:8px;padding:0 16px;font-size:14px;font-weight:500;line-height:20px}.TV8fAG_saveButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.TV8fAG_saveButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.TV8fAG_saveButton:disabled{opacity:.5;cursor:default}.TV8fAG_bound{color:var(--dsw-alias-state-warn-label);font-size:12px;line-height:18px}.TV8fAG_messageOk{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.TV8fAG_messageError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}";
|
|
157
132
|
const tagId$1 = "dsh-qqbot/QqbotSettingsSection.module.css";
|
|
158
133
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
159
134
|
const tag = document.createElement("style");
|
|
@@ -163,67 +138,61 @@ window.__ModuleLoader__.load({
|
|
|
163
138
|
document.head.appendChild(tag);
|
|
164
139
|
}
|
|
165
140
|
var QqbotSettingsSection_module_css_default = {
|
|
166
|
-
"
|
|
167
|
-
"fieldLabel": "TV8fAG_fieldLabel",
|
|
141
|
+
"messageOk": "TV8fAG_messageOk",
|
|
168
142
|
"bound": "TV8fAG_bound",
|
|
169
|
-
"title": "TV8fAG_title",
|
|
170
|
-
"messageError": "TV8fAG_messageError",
|
|
171
|
-
"input": "TV8fAG_input",
|
|
172
|
-
"fieldCheck": "TV8fAG_fieldCheck",
|
|
173
|
-
"secretRow": "TV8fAG_secretRow",
|
|
174
|
-
"desc": "TV8fAG_desc",
|
|
175
143
|
"field": "TV8fAG_field",
|
|
144
|
+
"chipUnset": "TV8fAG_chipUnset",
|
|
145
|
+
"section": "TV8fAG_section",
|
|
146
|
+
"desc": "TV8fAG_desc",
|
|
147
|
+
"messageError": "TV8fAG_messageError",
|
|
148
|
+
"saveButton": "TV8fAG_saveButton",
|
|
176
149
|
"fieldHint": "TV8fAG_fieldHint",
|
|
177
150
|
"clearButton": "TV8fAG_clearButton",
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
151
|
+
"title": "TV8fAG_title",
|
|
152
|
+
"fieldLabel": "TV8fAG_fieldLabel",
|
|
153
|
+
"chipSet": "TV8fAG_chipSet",
|
|
154
|
+
"actions": "TV8fAG_actions",
|
|
155
|
+
"input": "TV8fAG_input",
|
|
156
|
+
"secretRow": "TV8fAG_secretRow"
|
|
181
157
|
};
|
|
182
158
|
//#endregion
|
|
183
159
|
//#region src/client/QqbotSettingsSection.tsx
|
|
184
160
|
/**
|
|
185
|
-
* The QQ Bot settings page: AppID
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* field in one ordered write, and the Host
|
|
161
|
+
* The QQ Bot settings page: AppID and AppSecret over the credential seam's
|
|
162
|
+
* value-free views. Both values are write-only on the wire — the page shows
|
|
163
|
+
* configured/not-configured chips and the inputs mean "enter a new value";
|
|
164
|
+
* an empty field keeps the stored one, and the clear buttons remove it.
|
|
165
|
+
* Saving applies every changed field in one ordered write, and the Host
|
|
166
|
+
* restarts a live gateway in place.
|
|
190
167
|
*/
|
|
191
168
|
function QqbotSettingsSection({ useQqbotSettings, useQqbotBound, save, t }) {
|
|
192
169
|
const snapshot = useQqbotSettings((state) => state);
|
|
193
170
|
const boundSessionId = useQqbotBound((state) => state.sessionId);
|
|
194
171
|
const [appId, setAppId] = (0, react.useState)("");
|
|
195
172
|
const [secret, setSecret] = (0, react.useState)("");
|
|
196
|
-
const [sandbox, setSandbox] = (0, react.useState)(false);
|
|
197
173
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
198
174
|
const [message, setMessage] = (0, react.useState)(null);
|
|
199
|
-
const revision = snapshot.status === "ready" ? snapshot.revision : void 0;
|
|
200
|
-
(0, react.useEffect)(() => {
|
|
201
|
-
if (snapshot.status !== "ready") return;
|
|
202
|
-
setAppId(snapshot.appId);
|
|
203
|
-
setSecret("");
|
|
204
|
-
setSandbox(snapshot.sandbox);
|
|
205
|
-
}, [revision]);
|
|
206
175
|
const ready = snapshot.status === "ready";
|
|
207
|
-
const dirty = ready && (appId
|
|
176
|
+
const dirty = ready && (appId.length > 0 || secret.length > 0);
|
|
208
177
|
const finish = (failure) => {
|
|
209
178
|
setBusy(false);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
179
|
+
if (failure === null) {
|
|
180
|
+
setAppId("");
|
|
181
|
+
setSecret("");
|
|
182
|
+
setMessage({
|
|
183
|
+
kind: "ok",
|
|
184
|
+
text: t("save.ok")
|
|
185
|
+
});
|
|
186
|
+
} else setMessage({
|
|
214
187
|
kind: "error",
|
|
215
188
|
text: failure
|
|
216
189
|
});
|
|
217
190
|
};
|
|
218
|
-
const
|
|
219
|
-
if (busy || !ready || !snapshot.writable
|
|
191
|
+
const runSave = (patch) => {
|
|
192
|
+
if (busy || !ready || !snapshot.writable) return;
|
|
220
193
|
setBusy(true);
|
|
221
194
|
setMessage(null);
|
|
222
|
-
save({
|
|
223
|
-
appId,
|
|
224
|
-
...secret.length > 0 ? { appSecret: secret } : {},
|
|
225
|
-
sandbox
|
|
226
|
-
}).then(finish, (error) => {
|
|
195
|
+
save(patch).then(finish, (error) => {
|
|
227
196
|
setBusy(false);
|
|
228
197
|
setMessage({
|
|
229
198
|
kind: "error",
|
|
@@ -231,20 +200,25 @@ window.__ModuleLoader__.load({
|
|
|
231
200
|
});
|
|
232
201
|
});
|
|
233
202
|
};
|
|
203
|
+
const onClickSave = () => {
|
|
204
|
+
if (!dirty) return;
|
|
205
|
+
runSave({
|
|
206
|
+
...appId.length > 0 ? { appId } : {},
|
|
207
|
+
...secret.length > 0 ? { appSecret: secret } : {}
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
const onClickClearAppId = () => {
|
|
211
|
+
runSave({
|
|
212
|
+
...appId.length > 0 ? { appId } : {},
|
|
213
|
+
...secret.length > 0 ? { appSecret: secret } : {},
|
|
214
|
+
clearAppId: true
|
|
215
|
+
});
|
|
216
|
+
};
|
|
234
217
|
const onClickClearSecret = () => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
save({
|
|
239
|
-
appId,
|
|
240
|
-
sandbox,
|
|
218
|
+
runSave({
|
|
219
|
+
...appId.length > 0 ? { appId } : {},
|
|
220
|
+
...secret.length > 0 ? { appSecret: secret } : {},
|
|
241
221
|
clearSecret: true
|
|
242
|
-
}).then(finish, (error) => {
|
|
243
|
-
setBusy(false);
|
|
244
|
-
setMessage({
|
|
245
|
-
kind: "error",
|
|
246
|
-
text: error instanceof Error ? error.message : String(error)
|
|
247
|
-
});
|
|
248
222
|
});
|
|
249
223
|
};
|
|
250
224
|
let statusLine = null;
|
|
@@ -265,20 +239,32 @@ window.__ModuleLoader__.load({
|
|
|
265
239
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
266
240
|
className: QqbotSettingsSection_module_css_default.field,
|
|
267
241
|
children: [
|
|
268
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
269
243
|
className: QqbotSettingsSection_module_css_default.fieldLabel,
|
|
270
|
-
children: t("appId.label")
|
|
244
|
+
children: [t("appId.label"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
245
|
+
className: snapshot.appIdSet ? QqbotSettingsSection_module_css_default.chipSet : QqbotSettingsSection_module_css_default.chipUnset,
|
|
246
|
+
children: snapshot.appIdSet ? t("chip.set") : t("chip.unset")
|
|
247
|
+
})]
|
|
271
248
|
}),
|
|
272
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
273
|
-
className: QqbotSettingsSection_module_css_default.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
249
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
250
|
+
className: QqbotSettingsSection_module_css_default.secretRow,
|
|
251
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
252
|
+
className: QqbotSettingsSection_module_css_default.input,
|
|
253
|
+
value: appId,
|
|
254
|
+
onChange: (event) => {
|
|
255
|
+
setAppId(event.target.value);
|
|
256
|
+
},
|
|
257
|
+
placeholder: snapshot.appIdSet ? t("appId.placeholder.set") : t("appId.placeholder"),
|
|
258
|
+
autoComplete: "off",
|
|
259
|
+
spellCheck: false,
|
|
260
|
+
disabled: !ready || !snapshot.writable
|
|
261
|
+
}), snapshot.appIdSet ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
262
|
+
type: "button",
|
|
263
|
+
className: QqbotSettingsSection_module_css_default.clearButton,
|
|
264
|
+
onClick: onClickClearAppId,
|
|
265
|
+
disabled: busy,
|
|
266
|
+
children: t("clear.appId")
|
|
267
|
+
}) : null]
|
|
282
268
|
}),
|
|
283
269
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
284
270
|
className: QqbotSettingsSection_module_css_default.fieldHint,
|
|
@@ -289,9 +275,12 @@ window.__ModuleLoader__.load({
|
|
|
289
275
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
290
276
|
className: QqbotSettingsSection_module_css_default.field,
|
|
291
277
|
children: [
|
|
292
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
293
279
|
className: QqbotSettingsSection_module_css_default.fieldLabel,
|
|
294
|
-
children: t("appSecret.label")
|
|
280
|
+
children: [t("appSecret.label"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
281
|
+
className: snapshot.secretSet ? QqbotSettingsSection_module_css_default.chipSet : QqbotSettingsSection_module_css_default.chipUnset,
|
|
282
|
+
children: snapshot.secretSet ? t("chip.set") : t("chip.unset")
|
|
283
|
+
})]
|
|
295
284
|
}),
|
|
296
285
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
297
286
|
className: QqbotSettingsSection_module_css_default.secretRow,
|
|
@@ -310,7 +299,7 @@ window.__ModuleLoader__.load({
|
|
|
310
299
|
className: QqbotSettingsSection_module_css_default.clearButton,
|
|
311
300
|
onClick: onClickClearSecret,
|
|
312
301
|
disabled: busy,
|
|
313
|
-
children: t("clear.
|
|
302
|
+
children: t("clear.secret")
|
|
314
303
|
}) : null]
|
|
315
304
|
}),
|
|
316
305
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -319,21 +308,6 @@ window.__ModuleLoader__.load({
|
|
|
319
308
|
})
|
|
320
309
|
]
|
|
321
310
|
}),
|
|
322
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
323
|
-
className: QqbotSettingsSection_module_css_default.fieldCheck,
|
|
324
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
325
|
-
type: "checkbox",
|
|
326
|
-
checked: sandbox,
|
|
327
|
-
onChange: (event) => {
|
|
328
|
-
setSandbox(event.target.checked);
|
|
329
|
-
},
|
|
330
|
-
disabled: !ready || !snapshot.writable
|
|
331
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sandbox.label") })]
|
|
332
|
-
}),
|
|
333
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
334
|
-
className: QqbotSettingsSection_module_css_default.fieldHint,
|
|
335
|
-
children: t("sandbox.hint")
|
|
336
|
-
}),
|
|
337
311
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
338
312
|
className: QqbotSettingsSection_module_css_default.actions,
|
|
339
313
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -416,25 +390,27 @@ window.__ModuleLoader__.load({
|
|
|
416
390
|
"toggle.off.label": "连接 QQ",
|
|
417
391
|
"settings.nav": "QQ Bot",
|
|
418
392
|
"settings.title": "QQ 官方机器人",
|
|
419
|
-
"settings.desc": "填写 QQ 开放平台机器人应用的 AppID 与 AppSecret 后,在会话中使用 /qqbot on 或输入框的「连接 QQ
|
|
393
|
+
"settings.desc": "填写 QQ 开放平台机器人应用的 AppID 与 AppSecret 后,在会话中使用 /qqbot on 或输入框的「连接 QQ」开关接入。两者保存后都不回显,页面仅显示配置状态;保存立即生效。",
|
|
394
|
+
"chip.set": "已配置",
|
|
395
|
+
"chip.unset": "未配置",
|
|
420
396
|
"appId.label": "AppID",
|
|
421
|
-
"appId.placeholder": "
|
|
422
|
-
"appId.
|
|
397
|
+
"appId.placeholder": "输入新的 AppID",
|
|
398
|
+
"appId.placeholder.set": "已配置(不回显),输入以覆盖",
|
|
399
|
+
"appId.hint": "QQ 开放平台机器人应用的 AppID;留空表示保持现有值。",
|
|
400
|
+
"clear.appId": "清除 AppID",
|
|
423
401
|
"appSecret.label": "AppSecret",
|
|
424
402
|
"appSecret.placeholder": "输入新的 AppSecret",
|
|
425
|
-
"appSecret.placeholder.set": "
|
|
403
|
+
"appSecret.placeholder.set": "已配置(不回显),输入以覆盖",
|
|
426
404
|
"appSecret.hint": "保存后不回显;留空表示保持现有密钥。",
|
|
427
405
|
"appSecret.hint.set": "已配置 AppSecret;留空保存将保持不变,点击「清除密钥」可移除。",
|
|
428
|
-
"clear.
|
|
429
|
-
"sandbox.label": "使用沙箱环境",
|
|
430
|
-
"sandbox.hint": "启用后连接沙箱网关(sandbox.api.sgroup.qq.com),用于开发调试。",
|
|
406
|
+
"clear.secret": "清除密钥",
|
|
431
407
|
"save.label": "保存",
|
|
432
408
|
"save.busy": "保存中…",
|
|
433
409
|
"save.ok": "已保存,立即生效。",
|
|
434
410
|
"bound.prefix": "已绑定会话:",
|
|
435
411
|
"status.loading": "正在读取配置…",
|
|
436
412
|
"status.error": "读取配置失败:",
|
|
437
|
-
"status.readonly": "
|
|
413
|
+
"status.readonly": "凭据被只读来源(如环境变量)遮蔽,无法在界面修改。"
|
|
438
414
|
};
|
|
439
415
|
/** English dictionary, checked complete against the zh key set. */
|
|
440
416
|
const en = {
|
|
@@ -446,25 +422,27 @@ window.__ModuleLoader__.load({
|
|
|
446
422
|
"toggle.off.label": "Connect QQ",
|
|
447
423
|
"settings.nav": "QQ Bot",
|
|
448
424
|
"settings.title": "QQ Official Bot",
|
|
449
|
-
"settings.desc": "Enter the AppID and AppSecret of your QQ open-platform bot application, then use /qqbot on or the \"Connect QQ\" toggle in a session. Saved changes apply immediately.",
|
|
425
|
+
"settings.desc": "Enter the AppID and AppSecret of your QQ open-platform bot application, then use /qqbot on or the \"Connect QQ\" toggle in a session. Both are write-only: the page shows only configured status. Saved changes apply immediately.",
|
|
426
|
+
"chip.set": "Configured",
|
|
427
|
+
"chip.unset": "Not configured",
|
|
450
428
|
"appId.label": "AppID",
|
|
451
|
-
"appId.placeholder": "
|
|
452
|
-
"appId.
|
|
429
|
+
"appId.placeholder": "Enter a new AppID",
|
|
430
|
+
"appId.placeholder.set": "Configured (not shown back), enter to replace",
|
|
431
|
+
"appId.hint": "The AppID of the bot application on the QQ open platform; leave empty to keep the stored value.",
|
|
432
|
+
"clear.appId": "Clear AppID",
|
|
453
433
|
"appSecret.label": "AppSecret",
|
|
454
434
|
"appSecret.placeholder": "Enter a new AppSecret",
|
|
455
|
-
"appSecret.placeholder.set": "
|
|
435
|
+
"appSecret.placeholder.set": "Configured (not shown back), enter to replace",
|
|
456
436
|
"appSecret.hint": "Never shown back after saving; leave empty to keep the stored value.",
|
|
457
437
|
"appSecret.hint.set": "An AppSecret is configured; saving with an empty field keeps it. Use \"Clear secret\" to remove it.",
|
|
458
|
-
"clear.
|
|
459
|
-
"sandbox.label": "Use the sandbox environment",
|
|
460
|
-
"sandbox.hint": "Route token, gateway, and sends through the sandbox host (sandbox.api.sgroup.qq.com) for development.",
|
|
438
|
+
"clear.secret": "Clear secret",
|
|
461
439
|
"save.label": "Save",
|
|
462
440
|
"save.busy": "Saving…",
|
|
463
441
|
"save.ok": "Saved, applied immediately.",
|
|
464
442
|
"bound.prefix": "Bound session: ",
|
|
465
443
|
"status.loading": "Loading configuration…",
|
|
466
444
|
"status.error": "Failed to load configuration: ",
|
|
467
|
-
"status.readonly": "
|
|
445
|
+
"status.readonly": "The credentials are shadowed by a read-only source (such as environment variables) and cannot be changed here."
|
|
468
446
|
};
|
|
469
447
|
//#endregion
|
|
470
448
|
//#region src/client/index.ts
|
|
@@ -520,8 +498,8 @@ window.__ModuleLoader__.load({
|
|
|
520
498
|
}, QqbotToggle));
|
|
521
499
|
const settings = new QqbotSettingsController(ctx.get("connection").api);
|
|
522
500
|
ctx.effect(() => {
|
|
523
|
-
const disposers = [ctx.remote.$on("
|
|
524
|
-
if (
|
|
501
|
+
const disposers = [ctx.remote.$on("credentials/updated", (ref) => {
|
|
502
|
+
if (ref === "QQBOT_CRED_APP_ID" || ref === "QQBOT_CRED_APP_SECRET") refreshIfLoaded(settings);
|
|
525
503
|
}), ctx.on("connection/reset", () => {
|
|
526
504
|
refreshIfLoaded(settings);
|
|
527
505
|
})];
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Schema from "@deepseek-ai/schemastery";
|
|
2
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
2
3
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
4
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4
5
|
//#region lib/types/protocol.js
|
|
@@ -435,10 +436,12 @@ var QqGatewayClient = class {
|
|
|
435
436
|
* QQ gateway; `/qqbot off` disconnects. Nothing connects until a session opts
|
|
436
437
|
* in, and QQ messages then arrive as ordinary user messages on that agent.
|
|
437
438
|
*
|
|
438
|
-
* The robot identity (AppID/AppSecret)
|
|
439
|
-
*
|
|
440
|
-
* the
|
|
441
|
-
*
|
|
439
|
+
* The robot identity (AppID/AppSecret) resolves in order: the credential
|
|
440
|
+
* references `QQBOT_APP_ID`/`QQBOT_APP_SECRET` (written by the Settings → QQ
|
|
441
|
+
* Bot page into the harness credential store), then the `qqbot` settings
|
|
442
|
+
* section (whose base layer is the composition entry), then the same-named
|
|
443
|
+
* environment variables. Committed credential or settings changes restart a
|
|
444
|
+
* live gateway in place.
|
|
442
445
|
*
|
|
443
446
|
* The bridge is also an approval answerer for its bound agent: a tool call that
|
|
444
447
|
* asks for confirmation is forwarded to the originating QQ chat as a prompt,
|
|
@@ -459,7 +462,11 @@ const Config = Schema.object({
|
|
|
459
462
|
const QQBOT_APP_ID_ENV = "QQBOT_APP_ID";
|
|
460
463
|
/** Environment variable fallback for the robot application secret. */
|
|
461
464
|
const QQBOT_APP_SECRET_ENV = "QQBOT_APP_SECRET";
|
|
462
|
-
/**
|
|
465
|
+
/** Credential reference for the robot application id (written by Settings → QQ Bot). */
|
|
466
|
+
const QQBOT_APP_ID_REF = credentialRef("QQBOT_CRED_APP_ID");
|
|
467
|
+
/** Credential reference for the robot application secret (written by Settings → QQ Bot). */
|
|
468
|
+
const QQBOT_APP_SECRET_REF = credentialRef("QQBOT_CRED_APP_SECRET");
|
|
469
|
+
/** Settings namespace carrying the identity base layer (the cordis.yml config). */
|
|
463
470
|
const QQBOT_SETTINGS_NAMESPACE = settingsNamespace("qqbot");
|
|
464
471
|
/** Prompt section name registered on the bound agent's scope. */
|
|
465
472
|
const QQBOT_CHANNEL_SECTION = "qqbot:channel";
|
|
@@ -531,28 +538,13 @@ function parseAnswer(text) {
|
|
|
531
538
|
if (REJECT_ANSWERS.has(first)) return "rejected";
|
|
532
539
|
}
|
|
533
540
|
/**
|
|
534
|
-
* Resolve robot credentials from a config section, then environment, then
|
|
535
|
-
* fail loud.
|
|
536
|
-
* @param config - the validated section (settings-resolved or composition entry).
|
|
537
|
-
* @returns the concrete credentials.
|
|
538
|
-
*/
|
|
539
|
-
function resolveCredentials(config) {
|
|
540
|
-
const appId = config.appId ?? process.env["QQBOT_APP_ID"];
|
|
541
|
-
const appSecret = config.appSecret ?? process.env["QQBOT_APP_SECRET"];
|
|
542
|
-
if (appId === void 0 || appId.length === 0 || appSecret === void 0 || appSecret.length === 0) throw new Error(`qqbot: appId and appSecret are required; set them in Settings → QQ Bot, or export ${QQBOT_APP_ID_ENV}/${QQBOT_APP_SECRET_ENV}`);
|
|
543
|
-
return {
|
|
544
|
-
appId,
|
|
545
|
-
appSecret
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
541
|
* Mount the QQ bot bridge. It registers the `/qqbot` command and connects the
|
|
550
542
|
* gateway only when a session runs `/qqbot on`; the bound agent's own scope,
|
|
551
543
|
* history, and cwd are reused unchanged. While bound, it also answers that
|
|
552
544
|
* agent's approval requests through the originating QQ chat.
|
|
553
545
|
* @param ctx - Cordis context carrying the agent registry, commands, and session events.
|
|
554
|
-
* @param config - composition entry; the
|
|
555
|
-
*
|
|
546
|
+
* @param config - composition entry; the `qqbot` settings section resolves its
|
|
547
|
+
* user layer over it once a settings provider is mounted.
|
|
556
548
|
*/
|
|
557
549
|
function apply(ctx, config) {
|
|
558
550
|
const logger = ctx.logger;
|
|
@@ -561,14 +553,36 @@ function apply(ctx, config) {
|
|
|
561
553
|
let boundSectionDispose;
|
|
562
554
|
let gateway;
|
|
563
555
|
let gatewaySettings;
|
|
556
|
+
let rejudgeGeneration = 0;
|
|
564
557
|
const originByMessage = /* @__PURE__ */ new Map();
|
|
565
558
|
const originByTurn = /* @__PURE__ */ new Map();
|
|
566
559
|
const replyText = /* @__PURE__ */ new Map();
|
|
567
560
|
let currentOrigin;
|
|
568
561
|
let pendingApproval;
|
|
569
|
-
/**
|
|
570
|
-
|
|
571
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Resolve the identity the bridge uses right now, or throw for the caller.
|
|
564
|
+
* Layer ladder: the credential store (written by Settings → QQ Bot), then
|
|
565
|
+
* the settings section (settings.yaml over the cordis.yml base), then the
|
|
566
|
+
* same-named environment variables.
|
|
567
|
+
* @returns the concrete credentials.
|
|
568
|
+
*/
|
|
569
|
+
async function resolveCurrentCredentials() {
|
|
570
|
+
const section = readSection();
|
|
571
|
+
const credentials = ctx.get("credentials");
|
|
572
|
+
let appId = section.appId;
|
|
573
|
+
let appSecret = section.appSecret;
|
|
574
|
+
if (credentials !== void 0) {
|
|
575
|
+
const [appIdCredential, appSecretCredential] = await Promise.all([credentials.resolve(QQBOT_APP_ID_REF), credentials.resolve(QQBOT_APP_SECRET_REF)]);
|
|
576
|
+
if (appIdCredential !== void 0) appId = appIdCredential.value;
|
|
577
|
+
if (appSecretCredential !== void 0) appSecret = appSecretCredential.value;
|
|
578
|
+
}
|
|
579
|
+
appId = appId ?? process.env["QQBOT_APP_ID"];
|
|
580
|
+
appSecret = appSecret ?? process.env["QQBOT_APP_SECRET"];
|
|
581
|
+
if (appId === void 0 || appId.length === 0 || appSecret === void 0 || appSecret.length === 0) throw new Error(`qqbot: appId and appSecret are required; set them in Settings → QQ Bot, or export ${QQBOT_APP_ID_ENV}/${QQBOT_APP_SECRET_ENV}`);
|
|
582
|
+
return {
|
|
583
|
+
appId,
|
|
584
|
+
appSecret
|
|
585
|
+
};
|
|
572
586
|
}
|
|
573
587
|
/** Resolve the reply path for one inbound message. */
|
|
574
588
|
function replyPathOf(message) {
|
|
@@ -611,9 +625,10 @@ function apply(ctx, config) {
|
|
|
611
625
|
agent.followup(userMessage);
|
|
612
626
|
}
|
|
613
627
|
/** Connect the gateway for the first bound session, or reuse the live one. */
|
|
614
|
-
function ensureGateway() {
|
|
628
|
+
async function ensureGateway() {
|
|
615
629
|
if (gateway === void 0) {
|
|
616
|
-
const creds = resolveCurrentCredentials();
|
|
630
|
+
const creds = await resolveCurrentCredentials();
|
|
631
|
+
rejudgeGeneration += 1;
|
|
617
632
|
gatewaySettings = {
|
|
618
633
|
appId: creds.appId,
|
|
619
634
|
appSecret: creds.appSecret,
|
|
@@ -634,48 +649,53 @@ function apply(ctx, config) {
|
|
|
634
649
|
return gateway;
|
|
635
650
|
}
|
|
636
651
|
/** Bind a session: register its channel section, then connect the gateway. */
|
|
637
|
-
function bind(agent) {
|
|
652
|
+
async function bind(agent) {
|
|
653
|
+
let live;
|
|
638
654
|
try {
|
|
639
|
-
|
|
655
|
+
live = await ensureGateway();
|
|
640
656
|
} catch (_missingCredentials) {
|
|
641
657
|
return `QQ bot credentials are not configured. Set AppID/AppSecret in Settings → QQ Bot, or export ${QQBOT_APP_ID_ENV}/${QQBOT_APP_SECRET_ENV}.`;
|
|
642
658
|
}
|
|
643
659
|
boundAgent = agent;
|
|
644
660
|
boundSectionDispose = agent.ctx.systemPrompt.section(channelSection);
|
|
645
|
-
|
|
661
|
+
live.start().catch((error) => {
|
|
646
662
|
const detail = error instanceof Error ? error.message : String(error);
|
|
647
663
|
logger.warn(`qqbot: gateway start failed: ${detail}`);
|
|
648
664
|
});
|
|
649
665
|
return null;
|
|
650
666
|
}
|
|
651
667
|
/**
|
|
652
|
-
* Re-evaluate the
|
|
668
|
+
* Re-evaluate the credential layers against the live gateway: removed
|
|
653
669
|
* credentials close it; a changed identity or sandbox flag restarts it with
|
|
654
|
-
* the new values, keeping the binding.
|
|
670
|
+
* the new values, keeping the binding. Asynchronous by nature (credential
|
|
671
|
+
* resolution is a per-call read), so a generation counter and a
|
|
672
|
+
* gateway-identity check discard stale evaluations.
|
|
655
673
|
*/
|
|
656
|
-
function
|
|
674
|
+
function rejudgeIdentity() {
|
|
657
675
|
const active = gateway;
|
|
658
676
|
if (active === void 0) return;
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
677
|
+
const generation = ++rejudgeGeneration;
|
|
678
|
+
resolveCurrentCredentials().then((creds) => {
|
|
679
|
+
if (generation !== rejudgeGeneration || gateway !== active) return;
|
|
680
|
+
const sandbox = readSection().sandbox === true;
|
|
681
|
+
const current = gatewaySettings;
|
|
682
|
+
if (current !== void 0 && current.appId === creds.appId && current.appSecret === creds.appSecret && current.sandbox === sandbox) return;
|
|
683
|
+
active.close();
|
|
684
|
+
gateway = void 0;
|
|
685
|
+
gatewaySettings = void 0;
|
|
686
|
+
if (boundAgent === void 0) return;
|
|
687
|
+
ensureGateway().then((live) => {
|
|
688
|
+
live.start();
|
|
689
|
+
}, (error) => {
|
|
690
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
691
|
+
logger.warn(`qqbot: gateway restart failed: ${detail}`);
|
|
692
|
+
});
|
|
693
|
+
}, () => {
|
|
694
|
+
if (generation !== rejudgeGeneration || gateway !== active) return;
|
|
663
695
|
logger.warn("qqbot: credentials removed while connected; gateway closed");
|
|
664
696
|
active.close();
|
|
665
697
|
gateway = void 0;
|
|
666
698
|
gatewaySettings = void 0;
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const sandbox = readSection().sandbox === true;
|
|
670
|
-
const current = gatewaySettings;
|
|
671
|
-
if (current !== void 0 && current.appId === creds.appId && current.appSecret === creds.appSecret && current.sandbox === sandbox) return;
|
|
672
|
-
active.close();
|
|
673
|
-
gateway = void 0;
|
|
674
|
-
gatewaySettings = void 0;
|
|
675
|
-
if (boundAgent === void 0) return;
|
|
676
|
-
ensureGateway().start().catch((error) => {
|
|
677
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
678
|
-
logger.warn(`qqbot: gateway restart failed: ${detail}`);
|
|
679
699
|
});
|
|
680
700
|
}
|
|
681
701
|
/** Disconnect and drop the binding, unwinding the scoped section. */
|
|
@@ -769,10 +789,10 @@ function apply(ctx, config) {
|
|
|
769
789
|
ctx.commands.register({
|
|
770
790
|
name: "qqbot",
|
|
771
791
|
description: "Connect or disconnect the QQ official bot for this session (/qqbot on | off)",
|
|
772
|
-
handler: (invocation) => {
|
|
792
|
+
handler: async (invocation) => {
|
|
773
793
|
const input = invocation.rawInput.trim();
|
|
774
794
|
if (input === "on") {
|
|
775
|
-
const failure = bind(invocation.agent);
|
|
795
|
+
const failure = await bind(invocation.agent);
|
|
776
796
|
if (failure !== null) return {
|
|
777
797
|
kind: "error",
|
|
778
798
|
text: failure
|
|
@@ -795,12 +815,15 @@ function apply(ctx, config) {
|
|
|
795
815
|
};
|
|
796
816
|
}
|
|
797
817
|
});
|
|
818
|
+
ctx.on("credentials/updated", (ref) => {
|
|
819
|
+
if (ref === QQBOT_APP_ID_REF || ref === QQBOT_APP_SECRET_REF) rejudgeIdentity();
|
|
820
|
+
});
|
|
798
821
|
installSettingsSection(ctx, QQBOT_SETTINGS_NAMESPACE, Config, config, {
|
|
799
822
|
setSource: (current) => {
|
|
800
823
|
readSection = current;
|
|
801
824
|
},
|
|
802
825
|
onChange: () => {
|
|
803
|
-
|
|
826
|
+
rejudgeIdentity();
|
|
804
827
|
}
|
|
805
828
|
});
|
|
806
829
|
ctx.effect(() => () => {
|
|
@@ -808,4 +831,4 @@ function apply(ctx, config) {
|
|
|
808
831
|
}, "qqbot");
|
|
809
832
|
}
|
|
810
833
|
//#endregion
|
|
811
|
-
export { Config, QQBOT_APP_ID_ENV, QQBOT_APP_SECRET_ENV, QQBOT_CHANNEL_ORDER, QQBOT_CHANNEL_PROMPT, QQBOT_CHANNEL_SECTION, QQBOT_SETTINGS_NAMESPACE, apply, inject, name, parseAnswer };
|
|
834
|
+
export { Config, QQBOT_APP_ID_ENV, QQBOT_APP_ID_REF, QQBOT_APP_SECRET_ENV, QQBOT_APP_SECRET_REF, QQBOT_CHANNEL_ORDER, QQBOT_CHANNEL_PROMPT, QQBOT_CHANNEL_SECTION, QQBOT_SETTINGS_NAMESPACE, apply, inject, name, parseAnswer };
|
|
@@ -3,10 +3,11 @@ import type { QqbotSettingsInjected } from './index.ts';
|
|
|
3
3
|
/** Full settings.section component props: runtime share + injected share + the locale seat. */
|
|
4
4
|
export type QqbotSettingsSectionProps = PropsRuntime<'settings.section'> & InjectFace<QqbotSettingsInjected> & PropsLocale<'qqbot'>;
|
|
5
5
|
/**
|
|
6
|
-
* The QQ Bot settings page: AppID
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* field in one ordered write, and the Host
|
|
6
|
+
* The QQ Bot settings page: AppID and AppSecret over the credential seam's
|
|
7
|
+
* value-free views. Both values are write-only on the wire — the page shows
|
|
8
|
+
* configured/not-configured chips and the inputs mean "enter a new value";
|
|
9
|
+
* an empty field keeps the stored one, and the clear buttons remove it.
|
|
10
|
+
* Saving applies every changed field in one ordered write, and the Host
|
|
11
|
+
* restarts a live gateway in place.
|
|
11
12
|
*/
|
|
12
13
|
export declare function QqbotSettingsSection({ useQqbotSettings, useQqbotBound, save, t }: QqbotSettingsSectionProps): import("react").JSX.Element;
|
|
@@ -10,17 +10,19 @@ export declare const zh: {
|
|
|
10
10
|
'settings.nav': string;
|
|
11
11
|
'settings.title': string;
|
|
12
12
|
'settings.desc': string;
|
|
13
|
+
'chip.set': string;
|
|
14
|
+
'chip.unset': string;
|
|
13
15
|
'appId.label': string;
|
|
14
16
|
'appId.placeholder': string;
|
|
17
|
+
'appId.placeholder.set': string;
|
|
15
18
|
'appId.hint': string;
|
|
19
|
+
'clear.appId': string;
|
|
16
20
|
'appSecret.label': string;
|
|
17
21
|
'appSecret.placeholder': string;
|
|
18
22
|
'appSecret.placeholder.set': string;
|
|
19
23
|
'appSecret.hint': string;
|
|
20
24
|
'appSecret.hint.set': string;
|
|
21
|
-
'clear.
|
|
22
|
-
'sandbox.label': string;
|
|
23
|
-
'sandbox.hint': string;
|
|
25
|
+
'clear.secret': string;
|
|
24
26
|
'save.label': string;
|
|
25
27
|
'save.busy': string;
|
|
26
28
|
'save.ok': string;
|
|
@@ -42,17 +44,19 @@ export declare const en: {
|
|
|
42
44
|
'settings.nav': string;
|
|
43
45
|
'settings.title': string;
|
|
44
46
|
'settings.desc': string;
|
|
47
|
+
'chip.set': string;
|
|
48
|
+
'chip.unset': string;
|
|
45
49
|
'appId.label': string;
|
|
46
50
|
'appId.placeholder': string;
|
|
51
|
+
'appId.placeholder.set': string;
|
|
47
52
|
'appId.hint': string;
|
|
53
|
+
'clear.appId': string;
|
|
48
54
|
'appSecret.label': string;
|
|
49
55
|
'appSecret.placeholder': string;
|
|
50
56
|
'appSecret.placeholder.set': string;
|
|
51
57
|
'appSecret.hint': string;
|
|
52
58
|
'appSecret.hint.set': string;
|
|
53
|
-
'clear.
|
|
54
|
-
'sandbox.label': string;
|
|
55
|
-
'sandbox.hint': string;
|
|
59
|
+
'clear.secret': string;
|
|
56
60
|
'save.label': string;
|
|
57
61
|
'save.busy': string;
|
|
58
62
|
'save.ok': string;
|
|
@@ -1,46 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Settings-page transport for the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Settings-page transport for the QQ bot identity: reads the credential seam's
|
|
3
|
+
* value-free views (`configured`/`source`/`writable`, never values) and writes
|
|
4
|
+
* user edits through `credentials.set`/`credentials.unset`, mirroring the
|
|
5
|
+
* result in a snapshot store the section renders through. The credentials
|
|
6
|
+
* domain is used because the settings wire face serves only a hardcoded
|
|
7
|
+
* namespace allowlist the harness owns; credentials take arbitrary reference
|
|
8
|
+
* names, so an out-of-tree plugin needs no harness patch.
|
|
7
9
|
*/
|
|
8
10
|
import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client';
|
|
9
11
|
import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Credential reference holding the robot application id. Deliberately NOT the
|
|
14
|
+
* environment-variable name: the credential store's env layer shadows a
|
|
15
|
+
* same-named ref and makes it read-only, which would disable the settings
|
|
16
|
+
* page wherever `QQBOT_APP_ID` is exported.
|
|
17
|
+
*/
|
|
18
|
+
export declare const QQBOT_APP_ID_REF = "QQBOT_CRED_APP_ID";
|
|
19
|
+
/**
|
|
20
|
+
* Credential reference holding the robot application secret; distinct from
|
|
21
|
+
* `QQBOT_APP_SECRET` for the same shadowing reason.
|
|
22
|
+
*/
|
|
23
|
+
export declare const QQBOT_APP_SECRET_REF = "QQBOT_CRED_APP_SECRET";
|
|
12
24
|
/** One user edit the section sends: absent fields stay untouched. */
|
|
13
25
|
export interface QqbotSettingsPatch {
|
|
14
|
-
/** New AppID;
|
|
26
|
+
/** New AppID; omitted or empty keeps the stored value. */
|
|
15
27
|
appId?: string;
|
|
16
28
|
/** New AppSecret; omitted or empty keeps the stored value. */
|
|
17
29
|
appSecret?: string;
|
|
18
|
-
/**
|
|
19
|
-
|
|
30
|
+
/** Remove the stored AppID. */
|
|
31
|
+
clearAppId?: boolean;
|
|
20
32
|
/** Remove the stored AppSecret. */
|
|
21
33
|
clearSecret?: boolean;
|
|
22
34
|
}
|
|
23
|
-
/** Snapshot state mirrored from the
|
|
35
|
+
/** Snapshot state mirrored from the credential seam's views. */
|
|
24
36
|
export interface QqbotSettingsState {
|
|
25
|
-
/** `loading` until the first accepted
|
|
37
|
+
/** `loading` until the first accepted views; `error` after a failed load. */
|
|
26
38
|
status: 'idle' | 'loading' | 'ready' | 'error';
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
sandbox: boolean;
|
|
31
|
-
/** Whether the user section stores an AppSecret (the value never rides the wire). */
|
|
39
|
+
/** Whether an AppID is currently stored (the value never rides the wire). */
|
|
40
|
+
appIdSet: boolean;
|
|
41
|
+
/** Whether an AppSecret is currently stored (the value never rides the wire). */
|
|
32
42
|
secretSet: boolean;
|
|
33
|
-
/** Whether the
|
|
43
|
+
/** Whether the credential store accepts writes for both references. */
|
|
34
44
|
writable: boolean;
|
|
35
|
-
/** Namespace revision the next write fences against. */
|
|
36
|
-
revision: number | undefined;
|
|
37
45
|
/** Human failure text of the latest load or save; null while healthy. */
|
|
38
46
|
error: string | null;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
|
-
* Serializes the
|
|
42
|
-
* store. Latest load wins; a refused save reloads the
|
|
43
|
-
* re-renders from the fresh
|
|
49
|
+
* Serializes the two credential references' Host reads and writes behind one
|
|
50
|
+
* snapshot store. Latest load wins; a refused save reloads the views so the
|
|
51
|
+
* form re-renders from the fresh state and the user can re-apply the edit.
|
|
44
52
|
*/
|
|
45
53
|
export declare class QqbotSettingsController {
|
|
46
54
|
private readonly api;
|
|
@@ -48,24 +56,22 @@ export declare class QqbotSettingsController {
|
|
|
48
56
|
readonly store: SnapshotStore<QqbotSettingsState>;
|
|
49
57
|
private generation;
|
|
50
58
|
/**
|
|
51
|
-
* @param api - the
|
|
59
|
+
* @param api - the credentials wire face.
|
|
52
60
|
*/
|
|
53
|
-
constructor(api: Pick<IApiClient, '
|
|
61
|
+
constructor(api: Pick<IApiClient, 'credentials'>);
|
|
54
62
|
/**
|
|
55
|
-
* Refresh the snapshot from `
|
|
56
|
-
* good state and surfaces the error.
|
|
63
|
+
* Refresh the snapshot from `credentials.describe`; a failure keeps the
|
|
64
|
+
* last good state and surfaces the error.
|
|
57
65
|
* @returns settlement after the read.
|
|
58
66
|
*/
|
|
59
67
|
load(): Promise<void>;
|
|
60
68
|
/**
|
|
61
|
-
* Apply one user edit as ordered
|
|
62
|
-
* and returns its message.
|
|
69
|
+
* Apply one user edit as ordered credential writes, stopping at the first
|
|
70
|
+
* refusal; a refusal reloads the views and returns its message.
|
|
63
71
|
* @param patch - the fields the user changed.
|
|
64
72
|
* @returns null on success; the Host's failure text otherwise.
|
|
65
73
|
*/
|
|
66
74
|
save(patch: QqbotSettingsPatch): Promise<string | null>;
|
|
67
|
-
/** Fold one redacted wire view into the snapshot. */
|
|
68
|
-
private accept;
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
71
77
|
* Refresh only after the section left idle, so an unopened page never fetches
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* QQ gateway; `/qqbot off` disconnects. Nothing connects until a session opts
|
|
8
8
|
* in, and QQ messages then arrive as ordinary user messages on that agent.
|
|
9
9
|
*
|
|
10
|
-
* The robot identity (AppID/AppSecret)
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
10
|
+
* The robot identity (AppID/AppSecret) resolves in order: the credential
|
|
11
|
+
* references `QQBOT_APP_ID`/`QQBOT_APP_SECRET` (written by the Settings → QQ
|
|
12
|
+
* Bot page into the harness credential store), then the `qqbot` settings
|
|
13
|
+
* section (whose base layer is the composition entry), then the same-named
|
|
14
|
+
* environment variables. Committed credential or settings changes restart a
|
|
15
|
+
* live gateway in place.
|
|
14
16
|
*
|
|
15
17
|
* The bridge is also an approval answerer for its bound agent: a tool call that
|
|
16
18
|
* asks for confirmation is forwarded to the originating QQ chat as a prompt,
|
|
@@ -20,6 +22,7 @@
|
|
|
20
22
|
*/
|
|
21
23
|
import type { Context } from '@deepseek-ai/cordis';
|
|
22
24
|
import Schema from '@deepseek-ai/schemastery';
|
|
25
|
+
import { type CredentialRef } from '@deepseek-ai/dsh-credentials';
|
|
23
26
|
import type { ApprovalOutcome } from '@deepseek-ai/dsh-user-approval';
|
|
24
27
|
import type { QqbotConfig } from './types.ts';
|
|
25
28
|
export type { QqbotConfig } from './types.ts';
|
|
@@ -32,7 +35,11 @@ export declare const Config: Schema<QqbotConfig>;
|
|
|
32
35
|
export declare const QQBOT_APP_ID_ENV = "QQBOT_APP_ID";
|
|
33
36
|
/** Environment variable fallback for the robot application secret. */
|
|
34
37
|
export declare const QQBOT_APP_SECRET_ENV = "QQBOT_APP_SECRET";
|
|
35
|
-
/**
|
|
38
|
+
/** Credential reference for the robot application id (written by Settings → QQ Bot). */
|
|
39
|
+
export declare const QQBOT_APP_ID_REF: CredentialRef;
|
|
40
|
+
/** Credential reference for the robot application secret (written by Settings → QQ Bot). */
|
|
41
|
+
export declare const QQBOT_APP_SECRET_REF: CredentialRef;
|
|
42
|
+
/** Settings namespace carrying the identity base layer (the cordis.yml config). */
|
|
36
43
|
export declare const QQBOT_SETTINGS_NAMESPACE: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
37
44
|
/** Prompt section name registered on the bound agent's scope. */
|
|
38
45
|
export declare const QQBOT_CHANNEL_SECTION = "qqbot:channel";
|
|
@@ -58,7 +65,7 @@ export declare function parseAnswer(text: string): ApprovalOutcome | undefined;
|
|
|
58
65
|
* history, and cwd are reused unchanged. While bound, it also answers that
|
|
59
66
|
* agent's approval requests through the originating QQ chat.
|
|
60
67
|
* @param ctx - Cordis context carrying the agent registry, commands, and session events.
|
|
61
|
-
* @param config - composition entry; the
|
|
62
|
-
*
|
|
68
|
+
* @param config - composition entry; the `qqbot` settings section resolves its
|
|
69
|
+
* user layer over it once a settings provider is mounted.
|
|
63
70
|
*/
|
|
64
71
|
export declare function apply(ctx: Context, config: QqbotConfig): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-qqbot",
|
|
3
3
|
"description": "QQ official bot bridge for DeepSeek Harness: /qqbot command, bound-session routing, QQ-side approvals, the composer connection toggle, and the QQ Bot settings page",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"test": "vitest run"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.5",
|
|
49
50
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.5",
|
|
50
51
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
51
52
|
},
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.5",
|
|
80
81
|
"@deepseek-ai/dsh-client-web-react": "^0.1.0-rc.5",
|
|
81
82
|
"@deepseek-ai/dsh-commands": "^0.1.0-rc.5",
|
|
83
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.5",
|
|
82
84
|
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.5",
|
|
83
85
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.5",
|
|
84
86
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.5",
|