@tencent-connect/dsh-qqbot 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/commands/model.js +1 -1
  3. package/dist/commands/model.js.map +1 -1
  4. package/dist/config.d.ts +6 -2
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +3 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/features/history-store.d.ts +8 -0
  9. package/dist/features/history-store.d.ts.map +1 -0
  10. package/dist/features/history-store.js +24 -0
  11. package/dist/features/history-store.js.map +1 -0
  12. package/dist/gateway/bootstrap.d.ts +12 -0
  13. package/dist/gateway/bootstrap.d.ts.map +1 -0
  14. package/dist/gateway/bootstrap.js +71 -0
  15. package/dist/gateway/bootstrap.js.map +1 -0
  16. package/dist/gateway/index.d.ts +3 -0
  17. package/dist/gateway/index.d.ts.map +1 -0
  18. package/dist/gateway/index.js +3 -0
  19. package/dist/gateway/index.js.map +1 -0
  20. package/dist/gateway/middleware-setup.d.ts +12 -0
  21. package/dist/gateway/middleware-setup.d.ts.map +1 -0
  22. package/dist/gateway/middleware-setup.js +80 -0
  23. package/dist/gateway/middleware-setup.js.map +1 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -120
  27. package/dist/index.js.map +1 -1
  28. package/dist/middleware/attachment.d.ts +11 -0
  29. package/dist/middleware/attachment.d.ts.map +1 -0
  30. package/dist/middleware/attachment.js +17 -0
  31. package/dist/middleware/attachment.js.map +1 -0
  32. package/dist/model/model-resolver.d.ts +11 -5
  33. package/dist/model/model-resolver.d.ts.map +1 -1
  34. package/dist/model/model-resolver.js +15 -6
  35. package/dist/model/model-resolver.js.map +1 -1
  36. package/dist/session/session-manager.d.ts.map +1 -1
  37. package/dist/session/session-manager.js +4 -5
  38. package/dist/session/session-manager.js.map +1 -1
  39. package/dist/setup.js +6 -6
  40. package/dist/setup.js.map +1 -1
  41. package/dist/transport/attachment.d.ts +18 -0
  42. package/dist/transport/attachment.d.ts.map +1 -0
  43. package/dist/transport/attachment.js +106 -0
  44. package/dist/transport/attachment.js.map +1 -0
  45. package/dist/transport/events.d.ts +80 -0
  46. package/dist/transport/events.d.ts.map +1 -0
  47. package/dist/transport/events.js +62 -0
  48. package/dist/transport/events.js.map +1 -0
  49. package/dist/transport/inbound.d.ts +1 -1
  50. package/dist/transport/inbound.d.ts.map +1 -1
  51. package/dist/transport/inbound.js +39 -14
  52. package/dist/transport/inbound.js.map +1 -1
  53. package/dist/transport/outbound-buffer.d.ts +12 -7
  54. package/dist/transport/outbound-buffer.d.ts.map +1 -1
  55. package/dist/transport/outbound-buffer.js +20 -22
  56. package/dist/transport/outbound-buffer.js.map +1 -1
  57. package/dist/transport/outbound.d.ts +11 -7
  58. package/dist/transport/outbound.d.ts.map +1 -1
  59. package/dist/transport/outbound.js +120 -69
  60. package/dist/transport/outbound.js.map +1 -1
  61. package/dist/transport/streaming-writer.d.ts +47 -0
  62. package/dist/transport/streaming-writer.d.ts.map +1 -0
  63. package/dist/transport/streaming-writer.js +110 -0
  64. package/dist/transport/streaming-writer.js.map +1 -0
  65. package/dist/transport/tool-presenter.d.ts +38 -0
  66. package/dist/transport/tool-presenter.d.ts.map +1 -0
  67. package/dist/transport/tool-presenter.js +117 -0
  68. package/dist/transport/tool-presenter.js.map +1 -0
  69. package/dist/types.d.ts +11 -0
  70. package/dist/types.d.ts.map +1 -1
  71. package/package.json +6 -4
@@ -0,0 +1,47 @@
1
+ /**
2
+ * StreamingWriter — QQ 流式文本写入器
3
+ *
4
+ * 边界条件设计:
5
+ * - 节流(throttle):固定间隔 flush,避免每 token 发 HTTP,同时保证长文本分多次推送
6
+ * - 串行队列(chain):保证 update 顺序,finish 前最后一次文本必达
7
+ * - 降级(sentChunkCount === 0):只有从未成功发送分片才降级静态,
8
+ * 避免"已流式部分 + 静态全量"的重复内容
9
+ * - 终态幂等(finished):finish/abort 后忽略 append,重复 finish 不重复 complete
10
+ * - 中止关闭(abort):abort 时若 session 已打开,串行 complete 关闭,避免悬挂
11
+ */
12
+ import type { Logger, ReplyTarget } from '../types.js';
13
+ import type { QQBotSender } from './outbound-buffer.js';
14
+ export interface StreamingWriterDeps {
15
+ bot: QQBotSender;
16
+ target: ReplyTarget;
17
+ logger: Logger;
18
+ /** 节流间隔(ms):连续 append 累积后,停顿该间隔才 update */
19
+ throttleMs: number;
20
+ }
21
+ export declare class StreamingWriter {
22
+ private readonly deps;
23
+ private fullText;
24
+ private session;
25
+ private sentChunkCount;
26
+ private failed;
27
+ private finished;
28
+ private aborted;
29
+ private throttleTimer;
30
+ private chain;
31
+ constructor(deps: StreamingWriterDeps);
32
+ /** 是否应降级为静态发送:从未成功发送过任何分片 */
33
+ get shouldFallback(): boolean;
34
+ /** 累积全文(降级静态发送时使用) */
35
+ get text(): string;
36
+ /** 追加增量文本,节流(throttle)后 update 全量 */
37
+ append(delta: string): void;
38
+ /** 结束:flush 最终文本 + complete 收尾 */
39
+ finish(): Promise<void>;
40
+ /** 中止(异常/取消):关闭已打开的流式会话,避免悬挂 */
41
+ abort(): void;
42
+ /** 入队一次 update(串行,保证顺序) */
43
+ private enqueue;
44
+ /** 推送当前累积文本(首次 openStream + update 全量) */
45
+ private pushUpdate;
46
+ }
47
+ //# sourceMappingURL=streaming-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-writer.d.ts","sourceRoot":"","sources":["../../src/transport/streaming-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,sBAAsB,CAAC;AAE3E,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,WAAW,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,eAAe;IAUP,OAAO,CAAC,QAAQ,CAAC,IAAI;IATxC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,KAAK,CAAoC;gBAEb,IAAI,EAAE,mBAAmB;IAE7D,6BAA6B;IAC7B,IAAW,cAAc,IAAI,OAAO,CAEnC;IAED,sBAAsB;IACtB,IAAW,IAAI,IAAI,MAAM,CAExB;IAED,qCAAqC;IAC9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAclC,kCAAkC;IACrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBpC,gCAAgC;IACzB,KAAK,IAAI,IAAI;IAwBpB,2BAA2B;IAC3B,OAAO,CAAC,OAAO;IAIf,0CAA0C;YAC5B,UAAU;CA2BzB"}
@@ -0,0 +1,110 @@
1
+ export class StreamingWriter {
2
+ deps;
3
+ fullText = '';
4
+ session = null;
5
+ sentChunkCount = 0;
6
+ failed = false;
7
+ finished = false;
8
+ aborted = false;
9
+ throttleTimer = null;
10
+ chain = Promise.resolve();
11
+ constructor(deps) {
12
+ this.deps = deps;
13
+ }
14
+ /** 是否应降级为静态发送:从未成功发送过任何分片 */
15
+ get shouldFallback() {
16
+ return this.sentChunkCount === 0;
17
+ }
18
+ /** 累积全文(降级静态发送时使用) */
19
+ get text() {
20
+ return this.fullText;
21
+ }
22
+ /** 追加增量文本,节流(throttle)后 update 全量 */
23
+ append(delta) {
24
+ if (this.finished)
25
+ return;
26
+ this.fullText += delta;
27
+ // throttle:仅在无 pending timer 时启动,固定间隔 flush。
28
+ // 不用 debounce(否则 chunk 持续到达时永远憋着,流式退化成一次性全量发送)
29
+ if (this.throttleTimer === null) {
30
+ this.throttleTimer = setTimeout(() => {
31
+ this.throttleTimer = null;
32
+ this.enqueue();
33
+ }, this.deps.throttleMs);
34
+ }
35
+ }
36
+ /** 结束:flush 最终文本 + complete 收尾 */
37
+ async finish() {
38
+ if (this.finished)
39
+ return;
40
+ this.finished = true;
41
+ if (this.throttleTimer) {
42
+ clearTimeout(this.throttleTimer);
43
+ this.throttleTimer = null;
44
+ }
45
+ // 立即把最终累积文本入队(覆盖节流未触发的场景)
46
+ this.enqueue();
47
+ await this.chain;
48
+ // 只要 session 存在就 complete(关闭流式消息,无论是否降级)
49
+ if (this.session) {
50
+ try {
51
+ await this.session.complete();
52
+ }
53
+ catch (err) {
54
+ this.deps.logger.error(`im-qqbot: stream complete failed: ${err instanceof Error ? err.message : String(err)}`);
55
+ }
56
+ }
57
+ }
58
+ /** 中止(异常/取消):关闭已打开的流式会话,避免悬挂 */
59
+ abort() {
60
+ if (this.finished)
61
+ return;
62
+ this.finished = true;
63
+ this.aborted = true;
64
+ if (this.throttleTimer) {
65
+ clearTimeout(this.throttleTimer);
66
+ this.throttleTimer = null;
67
+ }
68
+ // 串行 complete(排在 pending update 之后),关闭已打开的流式会话
69
+ this.chain = this.chain.then(async () => {
70
+ if (this.session) {
71
+ try {
72
+ await this.session.complete();
73
+ }
74
+ catch (err) {
75
+ this.deps.logger.error(`im-qqbot: stream abort complete failed: ${err instanceof Error ? err.message : String(err)}`);
76
+ }
77
+ }
78
+ });
79
+ }
80
+ /** 入队一次 update(串行,保证顺序) */
81
+ enqueue() {
82
+ this.chain = this.chain.then(() => this.pushUpdate());
83
+ }
84
+ /** 推送当前累积文本(首次 openStream + update 全量) */
85
+ async pushUpdate() {
86
+ if (this.aborted || this.failed || !this.fullText)
87
+ return;
88
+ if (!this.session) {
89
+ try {
90
+ this.session = this.deps.bot.openStream(this.deps.target);
91
+ this.deps.logger.debug(`im-qqbot: stream opened (chars=${this.fullText.length})`);
92
+ }
93
+ catch (err) {
94
+ // openStream 失败:标记 failed 避免重复尝试,后续走降级静态
95
+ this.failed = true;
96
+ this.deps.logger.warn(`im-qqbot: openStream failed, fallback to static: ${err instanceof Error ? err.message : String(err)}`);
97
+ return;
98
+ }
99
+ }
100
+ try {
101
+ await this.session.update(this.fullText);
102
+ this.sentChunkCount++;
103
+ }
104
+ catch (err) {
105
+ // update 失败:不标记 failed(session 已打开,finish 会 complete 关闭)
106
+ this.deps.logger.warn(`im-qqbot: stream update failed: ${err instanceof Error ? err.message : String(err)}`);
107
+ }
108
+ }
109
+ }
110
+ //# sourceMappingURL=streaming-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming-writer.js","sourceRoot":"","sources":["../../src/transport/streaming-writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 工具调用结果 → Markdown 格式化
3
+ *
4
+ * 参考 dsh-TUI 的 presentResult 机制(channel.ts presentResultView),
5
+ * 适配 QQ 消息通道:优先用工具自定义的 presentResult 视图,
6
+ * fallback 到 raw text。错误始终展示,成功结果按 config 开关。
7
+ */
8
+ /** dsh-tools 主机平面注册表(presentResult 能力) */
9
+ export interface ToolsRegistryLike {
10
+ get(name: string, scope?: unknown): {
11
+ presentResult?(args: unknown, result: unknown): unknown;
12
+ } | undefined;
13
+ }
14
+ /** 工具结果事件数据(tool/result) */
15
+ export interface ToolResultData {
16
+ message: {
17
+ content: Array<{
18
+ type: string;
19
+ content?: unknown;
20
+ isError?: boolean;
21
+ }>;
22
+ source: {
23
+ callId: string;
24
+ };
25
+ };
26
+ error?: {
27
+ name: string;
28
+ code: string;
29
+ };
30
+ meta?: unknown;
31
+ }
32
+ /**
33
+ * 将工具调用结果格式化为 Markdown 文本
34
+ *
35
+ * 返回 null 表示无需发送(无内容)。
36
+ */
37
+ export declare function formatToolResult(name: string, rawArgs: string, data: ToolResultData, toolsRegistry: ToolsRegistryLike | undefined, agent: unknown): string | null;
38
+ //# sourceMappingURL=tool-presenter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-presenter.d.ts","sourceRoot":"","sources":["../../src/transport/tool-presenter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG;QAClC,aAAa,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;KACzD,GAAG,SAAS,CAAC;CACf;AAED,4BAA4B;AAC5B,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE;QACP,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,CAAC,EAAE,OAAO,CAAC;YAAC,OAAO,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QACvE,MAAM,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAC5B,CAAC;IACF,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAWD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,cAAc,EACpB,aAAa,EAAE,iBAAiB,GAAG,SAAS,EAC5C,KAAK,EAAE,OAAO,GACb,MAAM,GAAG,IAAI,CAmBf"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * 工具调用结果 → Markdown 格式化
3
+ *
4
+ * 参考 dsh-TUI 的 presentResult 机制(channel.ts presentResultView),
5
+ * 适配 QQ 消息通道:优先用工具自定义的 presentResult 视图,
6
+ * fallback 到 raw text。错误始终展示,成功结果按 config 开关。
7
+ */
8
+ const RESULT_PREVIEW_LIMIT = 1500;
9
+ /**
10
+ * 将工具调用结果格式化为 Markdown 文本
11
+ *
12
+ * 返回 null 表示无需发送(无内容)。
13
+ */
14
+ export function formatToolResult(name, rawArgs, data, toolsRegistry, agent) {
15
+ // 1. 错误优先:始终展示
16
+ if (data.error) {
17
+ return `❌ 工具 \`${name}\` 执行失败\n\`${data.error.name}: ${data.error.code}\``;
18
+ }
19
+ // 2. 尝试工具自定义视图(presentResult)
20
+ const view = presentResultView(name, rawArgs, data, toolsRegistry, agent);
21
+ if (view) {
22
+ const rendered = renderView(name, view);
23
+ if (rendered)
24
+ return rendered;
25
+ }
26
+ // 3. fallback raw text
27
+ const block = data.message.content[0];
28
+ const result = block !== undefined && block.type === 'tool-result' ? textOf(block.content) : '';
29
+ if (!result)
30
+ return null;
31
+ return `🔧 \`${name}\` 完成\n${truncate(result)}`;
32
+ }
33
+ /** 调用工具自定义 presentResult,失败/不可用时返回 undefined */
34
+ function presentResultView(name, rawArgs, data, toolsRegistry, agent) {
35
+ if (!toolsRegistry)
36
+ return undefined;
37
+ try {
38
+ const tool = toolsRegistry.get(name, agent);
39
+ if (tool?.presentResult === undefined)
40
+ return undefined;
41
+ const block = data.message.content[0];
42
+ const content = block !== undefined && block.type === 'tool-result' ? block.content : [];
43
+ return tool.presentResult(JSON.parse(rawArgs), {
44
+ content,
45
+ isError: block?.isError === true,
46
+ ...(data.meta !== undefined ? { meta: data.meta } : {}),
47
+ });
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ }
53
+ /** 将结构化视图渲染为 Markdown */
54
+ function renderView(name, view) {
55
+ const header = `🔧 \`${name}\` 完成`;
56
+ switch (view.card) {
57
+ case 'terminal': {
58
+ const output = (view.output ?? '').trimEnd();
59
+ if (!output)
60
+ return null;
61
+ const exit = view.exitCode !== undefined && view.exitCode !== 0 ? `\n退出码 ${view.exitCode}` : '';
62
+ return `${header}\n\`\`\`\n${output}\n\`\`\`${exit}`;
63
+ }
64
+ case 'diff': {
65
+ return `${header}\n\`\`\`diff\n${truncate(renderDiffs(view.diffs))}\n\`\`\``;
66
+ }
67
+ case 'read': {
68
+ const content = view.content ? textOf(view.content) : '';
69
+ if (!content)
70
+ return null;
71
+ const path = view.path ? ` (${view.path})` : '';
72
+ return `${header}${path}\n\`\`\`\n${truncate(content)}\n\`\`\``;
73
+ }
74
+ case 'generic':
75
+ default: {
76
+ const content = view.content ? textOf(view.content) : '';
77
+ if (!content)
78
+ return null;
79
+ return `${header}\n${truncate(content)}`;
80
+ }
81
+ }
82
+ }
83
+ /** 提取 content blocks 中的纯文本 */
84
+ function textOf(content) {
85
+ if (!Array.isArray(content))
86
+ return '';
87
+ return content
88
+ .map((b) => {
89
+ if (typeof b === 'object' && b !== null && b.type === 'text') {
90
+ return b.text ?? '';
91
+ }
92
+ return '';
93
+ })
94
+ .filter(Boolean)
95
+ .join('');
96
+ }
97
+ /** 渲染 diff hunks 为纯文本 */
98
+ function renderDiffs(diffs) {
99
+ return diffs
100
+ .map((d) => {
101
+ if (typeof d === 'object' && d !== null) {
102
+ const { path, hunks } = d;
103
+ const body = (hunks ?? []).map((h) => h.text ?? '').join('\n');
104
+ return path ? `--- ${path} ---\n${body}` : body;
105
+ }
106
+ return '';
107
+ })
108
+ .filter(Boolean)
109
+ .join('\n');
110
+ }
111
+ /** 截断长文本,避免刷屏 */
112
+ function truncate(text) {
113
+ if (text.length <= RESULT_PREVIEW_LIMIT)
114
+ return text;
115
+ return `${text.slice(0, RESULT_PREVIEW_LIMIT)}\n…(已截断)`;
116
+ }
117
+ //# sourceMappingURL=tool-presenter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-presenter.js","sourceRoot":"","sources":["../../src/transport/tool-presenter.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -24,6 +24,17 @@ export interface QQAttachment {
24
24
  url?: string;
25
25
  size?: number;
26
26
  }
27
+ /** SDK 原始消息附件(snake_case 字段,来自 QQ 网关 C2C_MESSAGE_CREATE) */
28
+ export interface RawAttachment {
29
+ content_type: string;
30
+ filename: string;
31
+ size: number;
32
+ url: string;
33
+ asr_refer_text?: string;
34
+ width?: number;
35
+ height?: number;
36
+ [key: string]: unknown;
37
+ }
27
38
  /** 插件 Logger 接口 */
28
39
  export interface Logger {
29
40
  info(msg: string, ...args: unknown[]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAExC,cAAc;AACd,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,kBAAkB;AAClB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;CAC9B;AAED,YAAY;AACZ,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,mBAAmB;AACnB,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC9C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY;AACZ,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAExC,cAAc;AACd,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,kBAAkB;AAClB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,CAAC,EAAE,YAAY,EAAE,CAAC;CAC9B;AAED,YAAY;AACZ,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mBAAmB;AACnB,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencent-connect/dsh-qqbot",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "QQ Bot IM channel plugin for deepseek-harness (dsh)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,6 +16,7 @@
16
16
  "dev": "tsc --watch",
17
17
  "prepublishOnly": "npm run build",
18
18
  "lint": "eslint src/",
19
+ "test": "vitest run",
19
20
  "clean": "rm -rf dist",
20
21
  "ci:prepare-test-pkg": "node scripts/prepare-test-pkg.js"
21
22
  },
@@ -38,12 +39,12 @@
38
39
  "js-yaml": "^4.1.0"
39
40
  },
40
41
  "peerDependencies": {
41
- "@tencent-connect/qqbot-connector": "1.2.0",
42
42
  "@deepseek-ai/cordis": ">=4.0.1",
43
43
  "@deepseek-ai/dsh-agent": ">=0.1.0-rc.6",
44
44
  "@deepseek-ai/dsh-llm": ">=0.1.0-rc.6",
45
45
  "@deepseek-ai/dsh-session": ">=0.1.0-rc.6",
46
- "@deepseek-ai/schemastery": ">=3.18.1"
46
+ "@deepseek-ai/schemastery": ">=3.18.1",
47
+ "@tencent-connect/qqbot-connector": "1.2.0"
47
48
  },
48
49
  "peerDependenciesMeta": {
49
50
  "@deepseek-ai/cordis": {
@@ -70,6 +71,7 @@
70
71
  "@deepseek-ai/schemastery": "3.18.1",
71
72
  "@types/js-yaml": "^4.0.9",
72
73
  "@types/node": "^26.2.0",
73
- "typescript": "^5.5.0"
74
+ "typescript": "^5.5.0",
75
+ "vitest": "^4.1.8"
74
76
  }
75
77
  }