pi-remote-feishu 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.
Files changed (102) hide show
  1. package/ARCHITECTURE.md +837 -0
  2. package/ARCHITECTURE.zh-CN.md +704 -0
  3. package/README.md +118 -0
  4. package/dist/attachments/mime.d.ts +11 -0
  5. package/dist/attachments/mime.js +27 -0
  6. package/dist/attachments/mime.js.map +1 -0
  7. package/dist/attachments/processor.d.ts +29 -0
  8. package/dist/attachments/processor.js +48 -0
  9. package/dist/attachments/processor.js.map +1 -0
  10. package/dist/attachments/temp-files.d.ts +16 -0
  11. package/dist/attachments/temp-files.js +32 -0
  12. package/dist/attachments/temp-files.js.map +1 -0
  13. package/dist/bin/pi-remote-feishu.d.ts +2 -0
  14. package/dist/bin/pi-remote-feishu.js +359 -0
  15. package/dist/bin/pi-remote-feishu.js.map +1 -0
  16. package/dist/bridge/card-actions.d.ts +13 -0
  17. package/dist/bridge/card-actions.js +156 -0
  18. package/dist/bridge/card-actions.js.map +1 -0
  19. package/dist/bridge/conversation-router.d.ts +21 -0
  20. package/dist/bridge/conversation-router.js +65 -0
  21. package/dist/bridge/conversation-router.js.map +1 -0
  22. package/dist/bridge/message-handler.d.ts +22 -0
  23. package/dist/bridge/message-handler.js +218 -0
  24. package/dist/bridge/message-handler.js.map +1 -0
  25. package/dist/bridge/message-normalizer.d.ts +17 -0
  26. package/dist/bridge/message-normalizer.js +81 -0
  27. package/dist/bridge/message-normalizer.js.map +1 -0
  28. package/dist/bridge/prompt-queue.d.ts +20 -0
  29. package/dist/bridge/prompt-queue.js +39 -0
  30. package/dist/bridge/prompt-queue.js.map +1 -0
  31. package/dist/bridge/runtime-host.d.ts +45 -0
  32. package/dist/bridge/runtime-host.js +133 -0
  33. package/dist/bridge/runtime-host.js.map +1 -0
  34. package/dist/bridge/session-host-manager.d.ts +59 -0
  35. package/dist/bridge/session-host-manager.js +143 -0
  36. package/dist/bridge/session-host-manager.js.map +1 -0
  37. package/dist/bridge/stream-renderer.d.ts +50 -0
  38. package/dist/bridge/stream-renderer.js +108 -0
  39. package/dist/bridge/stream-renderer.js.map +1 -0
  40. package/dist/bridge/ui-context.d.ts +19 -0
  41. package/dist/bridge/ui-context.js +150 -0
  42. package/dist/bridge/ui-context.js.map +1 -0
  43. package/dist/cards/common.d.ts +33 -0
  44. package/dist/cards/common.js +84 -0
  45. package/dist/cards/common.js.map +1 -0
  46. package/dist/cards/help.d.ts +11 -0
  47. package/dist/cards/help.js +31 -0
  48. package/dist/cards/help.js.map +1 -0
  49. package/dist/cards/models.d.ts +24 -0
  50. package/dist/cards/models.js +46 -0
  51. package/dist/cards/models.js.map +1 -0
  52. package/dist/cards/permission.d.ts +9 -0
  53. package/dist/cards/permission.js +17 -0
  54. package/dist/cards/permission.js.map +1 -0
  55. package/dist/cards/sessions.d.ts +11 -0
  56. package/dist/cards/sessions.js +54 -0
  57. package/dist/cards/sessions.js.map +1 -0
  58. package/dist/cards/status.d.ts +15 -0
  59. package/dist/cards/status.js +20 -0
  60. package/dist/cards/status.js.map +1 -0
  61. package/dist/cards/stop.d.ts +16 -0
  62. package/dist/cards/stop.js +24 -0
  63. package/dist/cards/stop.js.map +1 -0
  64. package/dist/config/load-config.d.ts +19 -0
  65. package/dist/config/load-config.js +51 -0
  66. package/dist/config/load-config.js.map +1 -0
  67. package/dist/config/schema.d.ts +52 -0
  68. package/dist/config/schema.js +260 -0
  69. package/dist/config/schema.js.map +1 -0
  70. package/dist/extensions/index.d.ts +7 -0
  71. package/dist/extensions/index.js +71 -0
  72. package/dist/extensions/index.js.map +1 -0
  73. package/dist/extensions/lark-cli-guard.d.ts +12 -0
  74. package/dist/extensions/lark-cli-guard.js +75 -0
  75. package/dist/extensions/lark-cli-guard.js.map +1 -0
  76. package/dist/feishu/channel.d.ts +28 -0
  77. package/dist/feishu/channel.js +191 -0
  78. package/dist/feishu/channel.js.map +1 -0
  79. package/dist/feishu/context.d.ts +5 -0
  80. package/dist/feishu/context.js +19 -0
  81. package/dist/feishu/context.js.map +1 -0
  82. package/dist/feishu/webhook.d.ts +5 -0
  83. package/dist/feishu/webhook.js +8 -0
  84. package/dist/feishu/webhook.js.map +1 -0
  85. package/dist/index.d.ts +30 -0
  86. package/dist/index.js +29 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/store/json-store.d.ts +25 -0
  89. package/dist/store/json-store.js +105 -0
  90. package/dist/store/json-store.js.map +1 -0
  91. package/dist/store/store.d.ts +7 -0
  92. package/dist/store/store.js +15 -0
  93. package/dist/store/store.js.map +1 -0
  94. package/dist/tools/send-file-to-chat.d.ts +26 -0
  95. package/dist/tools/send-file-to-chat.js +85 -0
  96. package/dist/tools/send-file-to-chat.js.map +1 -0
  97. package/dist/types.d.ts +300 -0
  98. package/dist/types.js +2 -0
  99. package/dist/types.js.map +1 -0
  100. package/package.json +67 -0
  101. package/skills/lark-doc-cli/SKILL.md +56 -0
  102. package/skills/lark-im-readonly/SKILL.md +49 -0
@@ -0,0 +1,81 @@
1
+ /** 支持的飞书机器人命令集合(以 / 开头) */
2
+ const COMMANDS = new Set(["help", "sessions", "models", "new", "stop", "reset", "status"]);
3
+ /**
4
+ * 解析以 / 开头的命令文本。
5
+ * 例如 "/models" -> { name: "models", args: "" };
6
+ * 不是命令或不在白名单里的命令返回 undefined。
7
+ */
8
+ function parseCommand(text) {
9
+ const trimmed = text.trim();
10
+ if (!trimmed.startsWith("/"))
11
+ return undefined;
12
+ const withoutSlash = trimmed.slice(1);
13
+ const [name = "", ...rest] = withoutSlash.split(/\s+/);
14
+ if (!COMMANDS.has(name))
15
+ return undefined;
16
+ return {
17
+ name: name,
18
+ args: rest.join(" "),
19
+ };
20
+ }
21
+ /**
22
+ * 去掉群聊消息开头的 @机器人 前缀。
23
+ * 例如 "@Pi check this" -> "check this"。
24
+ */
25
+ function stripBotMention(text) {
26
+ return text.replace(/^@\S+\s*/, "").trim();
27
+ }
28
+ /**
29
+ * 归一化消息:
30
+ * 把飞书消息 + 路由结果 + 附件信息,转换成传给 Pi 的 NormalizedPiInput。
31
+ *
32
+ * 重点:
33
+ * - 群聊文本会剥掉 @机器人 前缀
34
+ * - 群聊会拼入发送者身份和会话模式的上下文说明,让模型知道在群里回复谁
35
+ * - 附件处理结果(文本内容/保存路径/失败原因)会追加到提示词里
36
+ * - 如果消息是 /命令,解析出 command 字段
37
+ */
38
+ export function normalizeMessage(options) {
39
+ // 群聊去掉 @前缀,私聊直接 trim
40
+ const text = options.message.chatType === "group" ? stripBotMention(options.message.text) : options.message.text.trim();
41
+ const command = parseCommand(text);
42
+ const attachmentNotes = options.attachmentNotes ?? [];
43
+ const promptTextParts = [];
44
+ if (text.length > 0) {
45
+ if (options.message.chatType === "group") {
46
+ // 群聊场景:附加一段上下文说明,让模型知道当前会话模式及如何回复
47
+ const sender = options.message.senderName ?? options.message.userOpenId;
48
+ const groupMode = options.route.sessionKey.startsWith("group-user:") ? "per-user session" : "shared chat session";
49
+ promptTextParts.push([
50
+ "[Feishu group message]",
51
+ `Sender: ${sender}`,
52
+ `Session mode: ${groupMode}`,
53
+ "Reply naturally to the sender in the group. Keep the answer concise unless the user asks for detail.",
54
+ "Message:",
55
+ text,
56
+ ].join("\n"));
57
+ }
58
+ else {
59
+ // 私聊场景:直接使用消息文本
60
+ promptTextParts.push(text);
61
+ }
62
+ }
63
+ if (attachmentNotes.length > 0) {
64
+ promptTextParts.push(attachmentNotes.join("\n"));
65
+ }
66
+ const normalized = {
67
+ sessionKey: options.route.sessionKey,
68
+ chatId: options.message.chatId,
69
+ messageId: options.message.messageId,
70
+ userId: options.message.userOpenId,
71
+ chatType: options.message.chatType,
72
+ text: promptTextParts.join("\n\n"),
73
+ images: options.images ?? [],
74
+ attachmentNotes,
75
+ ...(options.message.tenantKey ? { tenantKey: options.message.tenantKey } : {}),
76
+ ...(options.message.senderName ? { senderName: options.message.senderName } : {}),
77
+ ...(command ? { command } : {}),
78
+ };
79
+ return normalized;
80
+ }
81
+ //# sourceMappingURL=message-normalizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-normalizer.js","sourceRoot":"","sources":["../../src/bridge/message-normalizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 提示词队列:保证"同一会话"内的多条飞书消息串行处理,
3
+ * 避免并发调用导致 Pi 会话状态错乱。
4
+ *
5
+ * 实现思路:维护一个 promise 链 tail,每次入队都把任务挂到链尾,
6
+ * 前一个任务完成后才会执行下一个。depth 用于观察当前排队深度。
7
+ */
8
+ export declare class PromptQueue {
9
+ /** 队尾 promise:前一个任务结束时 resolve,唤醒下一个任务 */
10
+ private tail;
11
+ /** 当前排队/执行中的任务数 */
12
+ private depthValue;
13
+ /** 当前队列深度(含正在执行的那个) */
14
+ get depth(): number;
15
+ /**
16
+ * 入队执行一个任务。
17
+ * 返回任务本身的返回值;多个任务之间严格串行。
18
+ */
19
+ enqueue<T>(run: () => Promise<T>): Promise<T>;
20
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 提示词队列:保证"同一会话"内的多条飞书消息串行处理,
3
+ * 避免并发调用导致 Pi 会话状态错乱。
4
+ *
5
+ * 实现思路:维护一个 promise 链 tail,每次入队都把任务挂到链尾,
6
+ * 前一个任务完成后才会执行下一个。depth 用于观察当前排队深度。
7
+ */
8
+ export class PromptQueue {
9
+ /** 队尾 promise:前一个任务结束时 resolve,唤醒下一个任务 */
10
+ tail = Promise.resolve();
11
+ /** 当前排队/执行中的任务数 */
12
+ depthValue = 0;
13
+ /** 当前队列深度(含正在执行的那个) */
14
+ get depth() {
15
+ return this.depthValue;
16
+ }
17
+ /**
18
+ * 入队执行一个任务。
19
+ * 返回任务本身的返回值;多个任务之间严格串行。
20
+ */
21
+ async enqueue(run) {
22
+ this.depthValue += 1;
23
+ const previous = this.tail;
24
+ let release = () => { };
25
+ // 在链尾挂一个"信号",前一个完成 -> release() -> 本任务启动
26
+ this.tail = new Promise((resolve) => {
27
+ release = resolve;
28
+ });
29
+ await previous;
30
+ try {
31
+ return await run();
32
+ }
33
+ finally {
34
+ this.depthValue -= 1;
35
+ release();
36
+ }
37
+ }
38
+ }
39
+ //# sourceMappingURL=prompt-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-queue.js","sourceRoot":"","sources":["../../src/bridge/prompt-queue.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,OAAO,WAAW;IACtB,0CAA0C;IAClC,IAAI,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAChD,mBAAmB;IACX,UAAU,GAAG,CAAC,CAAC;IAEvB,uBAAuB;IACvB,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAI,GAAqB;QACpC,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;QACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAC3B,IAAI,OAAO,GAAe,GAAG,EAAE,GAAE,CAAC,CAAC;QACnC,yCAAyC;QACzC,IAAI,CAAC,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACxC,OAAO,GAAG,OAAO,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,MAAM,QAAQ,CAAC;QACf,IAAI,CAAC;YACH,OAAO,MAAM,GAAG,EAAE,CAAC;QACrB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;YACrB,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,45 @@
1
+ import { type AgentSessionRuntime } from "@earendil-works/pi-coding-agent";
2
+ import type { FeishuChannel, FeishuConfig, NormalizedPiInput } from "../types.js";
3
+ /**
4
+ * 运行时宿主:把一个 Pi 的 AgentSessionRuntime 与飞书通道绑定,
5
+ * 并提供 prompt(生成)、abort(中止)、dispose(释放)等操作。
6
+ *
7
+ * 每次 prompt:
8
+ * 1. 注入飞书 UI 上下文(让 ui.select 变成飞书权限卡片)
9
+ * 2. 在 AsyncLocalStorage 里挂载 FeishuRunContext(供 send_file_to_chat 等工具读取)
10
+ * 3. 通过 renderPromptStream 以流式卡片方式渲染回复
11
+ * 4. 结束后清理临时附件、恢复之前的 UI 上下文
12
+ */
13
+ export declare class RuntimeHost {
14
+ readonly sessionKey: string;
15
+ readonly runtime: AgentSessionRuntime;
16
+ private readonly config;
17
+ private readonly channel;
18
+ constructor(options: {
19
+ sessionKey: string;
20
+ runtime: AgentSessionRuntime;
21
+ config: FeishuConfig;
22
+ channel: FeishuChannel;
23
+ });
24
+ /** 当前模型是否支持图片输入(决定附件图片是转为 base64 还是落盘) */
25
+ supportsImages(): boolean;
26
+ /** 把一条归一化消息送入 Pi 会话生成,并以流式方式渲染到飞书 */
27
+ prompt(input: NormalizedPiInput): Promise<void>;
28
+ /** 中止当前生成(/stop 或卡片上的"停止生成"按钮) */
29
+ abort(): Promise<void>;
30
+ /** 释放运行时资源(会话被回收/删除时调用) */
31
+ dispose(): Promise<void>;
32
+ }
33
+ /**
34
+ * 创建一个新的运行时宿主:
35
+ * - 有历史会话文件(sessionFile)则用 SessionManager.open 恢复
36
+ * - 否则用 SessionManager.create 新建
37
+ * - 注册 pi-remote-feishu-runtime 隐藏扩展,注入 send_file_to_chat 工具
38
+ */
39
+ export declare function createRuntimeHost(options: {
40
+ sessionKey: string;
41
+ sessionFile?: string;
42
+ cwd: string;
43
+ config: FeishuConfig;
44
+ channel: FeishuChannel;
45
+ }): Promise<RuntimeHost>;
@@ -0,0 +1,133 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager, } from "@earendil-works/pi-coding-agent";
4
+ import { cleanupTempAttachments } from "../attachments/temp-files.js";
5
+ import { createFeishuUIContext } from "./ui-context.js";
6
+ import { runWithFeishuContext } from "../feishu/context.js";
7
+ import { createSendFileToChatTool } from "../tools/send-file-to-chat.js";
8
+ import { renderPromptStream } from "./stream-renderer.js";
9
+ /**
10
+ * 运行时宿主:把一个 Pi 的 AgentSessionRuntime 与飞书通道绑定,
11
+ * 并提供 prompt(生成)、abort(中止)、dispose(释放)等操作。
12
+ *
13
+ * 每次 prompt:
14
+ * 1. 注入飞书 UI 上下文(让 ui.select 变成飞书权限卡片)
15
+ * 2. 在 AsyncLocalStorage 里挂载 FeishuRunContext(供 send_file_to_chat 等工具读取)
16
+ * 3. 通过 renderPromptStream 以流式卡片方式渲染回复
17
+ * 4. 结束后清理临时附件、恢复之前的 UI 上下文
18
+ */
19
+ export class RuntimeHost {
20
+ sessionKey;
21
+ runtime;
22
+ config;
23
+ channel;
24
+ constructor(options) {
25
+ this.sessionKey = options.sessionKey;
26
+ this.runtime = options.runtime;
27
+ this.config = options.config;
28
+ this.channel = options.channel;
29
+ }
30
+ /** 当前模型是否支持图片输入(决定附件图片是转为 base64 还是落盘) */
31
+ supportsImages() {
32
+ return this.runtime.session.model?.input?.includes("image") ?? false;
33
+ }
34
+ /** 把一条归一化消息送入 Pi 会话生成,并以流式方式渲染到飞书 */
35
+ async prompt(input) {
36
+ // 注入飞书 UI 上下文:Pi 的 ui.select/confirm 会转化为飞书卡片
37
+ const uiContext = createFeishuUIContext();
38
+ const previousUIContext = this.runtime.session.extensionRunner.getUIContext();
39
+ this.runtime.session.extensionRunner.setUIContext(uiContext, "rpc");
40
+ try {
41
+ // 挂载运行上下文,让 send_file_to_chat 等工具能拿到 channel/chatId
42
+ await runWithFeishuContext({
43
+ sessionKey: this.sessionKey,
44
+ chatId: input.chatId,
45
+ messageId: input.messageId,
46
+ channel: this.channel,
47
+ uiContext,
48
+ config: this.config,
49
+ }, async () => {
50
+ await renderPromptStream({
51
+ config: this.config,
52
+ channel: this.channel,
53
+ chatId: input.chatId,
54
+ replyTo: input.messageId,
55
+ session: this.runtime.session,
56
+ runPrompt: async () => {
57
+ await this.runtime.session.prompt(input.text, {
58
+ // steer:把模型回复内容交给事件订阅器,由我们决定如何渲染
59
+ streamingBehavior: "steer",
60
+ ...(input.images.length > 0 ? { images: input.images } : {}),
61
+ });
62
+ },
63
+ });
64
+ });
65
+ }
66
+ finally {
67
+ // 恢复之前的 UI 上下文(tui 模式),并清理本会话的临时附件
68
+ this.runtime.session.extensionRunner.setUIContext(previousUIContext, "tui");
69
+ await cleanupTempAttachments(this.config.files.tempDir ?? ".", this.sessionKey).catch(() => { });
70
+ }
71
+ }
72
+ /** 中止当前生成(/stop 或卡片上的"停止生成"按钮) */
73
+ async abort() {
74
+ await this.runtime.session.abort();
75
+ }
76
+ /** 释放运行时资源(会话被回收/删除时调用) */
77
+ async dispose() {
78
+ await this.runtime.dispose();
79
+ }
80
+ }
81
+ /**
82
+ * 创建一个新的运行时宿主:
83
+ * - 有历史会话文件(sessionFile)则用 SessionManager.open 恢复
84
+ * - 否则用 SessionManager.create 新建
85
+ * - 注册 pi-remote-feishu-runtime 隐藏扩展,注入 send_file_to_chat 工具
86
+ */
87
+ export async function createRuntimeHost(options) {
88
+ const cwd = resolve(options.cwd);
89
+ const agentDir = getAgentDir();
90
+ const sessionManager = options.sessionFile && existsSync(options.sessionFile)
91
+ ? SessionManager.open(options.sessionFile, undefined, cwd)
92
+ : SessionManager.create(cwd);
93
+ // 扩展工厂:为每个 Pi 会话注册 send_file_to_chat 工具
94
+ const createRuntime = async ({ cwd: runtimeCwd, agentDir: runtimeAgentDir, sessionManager: runtimeSessionManager, sessionStartEvent, }) => {
95
+ const services = await createAgentSessionServices({
96
+ cwd: runtimeCwd,
97
+ agentDir: runtimeAgentDir,
98
+ resourceLoaderOptions: {
99
+ extensionFactories: [
100
+ {
101
+ name: "pi-remote-feishu-runtime",
102
+ hidden: true,
103
+ factory(pi) {
104
+ pi.registerTool(createSendFileToChatTool(options.config));
105
+ },
106
+ },
107
+ ],
108
+ },
109
+ });
110
+ return {
111
+ ...(await createAgentSessionFromServices({
112
+ services,
113
+ sessionManager: runtimeSessionManager,
114
+ ...(sessionStartEvent ? { sessionStartEvent } : {}),
115
+ })),
116
+ services,
117
+ diagnostics: services.diagnostics,
118
+ };
119
+ };
120
+ const runtime = await createAgentSessionRuntime(createRuntime, {
121
+ cwd,
122
+ agentDir,
123
+ sessionManager,
124
+ });
125
+ await runtime.session.bindExtensions({});
126
+ return new RuntimeHost({
127
+ sessionKey: options.sessionKey,
128
+ runtime,
129
+ config: options.config,
130
+ channel: options.channel,
131
+ });
132
+ }
133
+ //# sourceMappingURL=runtime-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-host.js","sourceRoot":"","sources":["../../src/bridge/runtime-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAGL,8BAA8B,EAC9B,yBAAyB,EACzB,0BAA0B,EAC1B,WAAW,EACX,cAAc,GACf,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;;;;;;;GASG;AACH,MAAM,OAAO,WAAW;IACb,UAAU,CAAS;IACnB,OAAO,CAAsB;IACrB,MAAM,CAAe;IACrB,OAAO,CAAgB;IAExC,YAAY,OAKX;QACC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;IAED,0CAA0C;IAC1C,cAAc;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;IACvE,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,MAAM,CAAC,KAAwB;QACnC,8CAA8C;QAC9C,MAAM,SAAS,GAAG,qBAAqB,EAAE,CAAC;QAC1C,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,YAAY,EAAE,CAAC;QAC9E,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAEpE,IAAI,CAAC;YACH,oDAAoD;YACpD,MAAM,oBAAoB,CACxB;gBACE,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS;gBACT,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,EACD,KAAK,IAAI,EAAE;gBACT,MAAM,kBAAkB,CAAC;oBACvB,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,OAAO,EAAE,KAAK,CAAC,SAAS;oBACxB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO;oBAC7B,SAAS,EAAE,KAAK,IAAI,EAAE;wBACpB,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE;4BAC5C,iCAAiC;4BACjC,iBAAiB,EAAE,OAAO;4BAC1B,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;yBAC7D,CAAC,CAAC;oBACL,CAAC;iBACF,CAAC,CAAC;YACL,CAAC,CACF,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,mCAAmC;YACnC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,YAAY,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;YAC5E,MAAM,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAClG,CAAC;IACH,CAAC;IAED,kCAAkC;IAClC,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACrC,CAAC;IAED,2BAA2B;IAC3B,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAC/B,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAMvC;IACC,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,cAAc,GAClB,OAAO,CAAC,WAAW,IAAI,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC;QACpD,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC;QAC1D,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEjC,wCAAwC;IACxC,MAAM,aAAa,GAAqC,KAAK,EAAE,EAC7D,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,qBAAqB,EACrC,iBAAiB,GAClB,EAAE,EAAE;QACH,MAAM,QAAQ,GAAG,MAAM,0BAA0B,CAAC;YAChD,GAAG,EAAE,UAAU;YACf,QAAQ,EAAE,eAAe;YACzB,qBAAqB,EAAE;gBACrB,kBAAkB,EAAE;oBAClB;wBACE,IAAI,EAAE,0BAA0B;wBAChC,MAAM,EAAE,IAAI;wBACZ,OAAO,CAAC,EAAE;4BACR,EAAE,CAAC,YAAY,CAAC,wBAAwB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;wBAC5D,CAAC;qBACF;iBACF;aACF;SACF,CAAC,CAAC;QACH,OAAO;YACL,GAAG,CAAC,MAAM,8BAA8B,CAAC;gBACvC,QAAQ;gBACR,cAAc,EAAE,qBAAqB;gBACrC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpD,CAAC,CAAC;YACH,QAAQ;YACR,WAAW,EAAE,QAAQ,CAAC,WAAW;SAClC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,yBAAyB,CAAC,aAAa,EAAE;QAC7D,GAAG;QACH,QAAQ;QACR,cAAc;KACf,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAEzC,OAAO,IAAI,WAAW,CAAC;QACrB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,OAAO;QACP,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,59 @@
1
+ import type { FeishuChannel, FeishuConfig, FeishuIdentity, FeishuStore, SessionHost, SessionMapping } from "../types.js";
2
+ import { PromptQueue } from "./prompt-queue.js";
3
+ import { type RuntimeHost } from "./runtime-host.js";
4
+ /** 被管理器管理的会话宿主:在 SessionHost 基础上增加 runtimeHost 和强类型 queue */
5
+ interface ManagedSessionHost extends SessionHost {
6
+ runtimeHost: RuntimeHost;
7
+ queue: PromptQueue;
8
+ }
9
+ /**
10
+ * 会话宿主管理器:
11
+ * 以 sessionKey 为维度,在内存中维护一组 Pi 运行时实例,
12
+ * 并通过 FeishuStore 把"会话 key <-> 会话文件/工作目录"持久化到磁盘。
13
+ *
14
+ * 职责:
15
+ * - getHost:按需创建/复用运行时(懒加载)
16
+ * - newSession / switchToMapping / deleteMapping:会话生命周期管理
17
+ * - abort:中止当前生成
18
+ * - evictIdle:按 idleTtlMs 回收长时间不用的会话,释放内存
19
+ */
20
+ export declare class SessionHostManager {
21
+ private readonly hosts;
22
+ private readonly config;
23
+ private readonly store;
24
+ private readonly channel;
25
+ constructor(options: {
26
+ config: FeishuConfig;
27
+ store: FeishuStore;
28
+ channel: FeishuChannel;
29
+ });
30
+ /**
31
+ * 获取(或创建)某个会话 key 的宿主。
32
+ * - 内存中已有:刷新 lastUsedAt 直接返回
33
+ * - 没有:查存储恢复历史会话(cwd、sessionFile),否则新建,并持久化映射
34
+ */
35
+ getHost(sessionKey: string, identity: FeishuIdentity): Promise<ManagedSessionHost>;
36
+ /** 只读查询内存中是否有该会话的宿主(不创建) */
37
+ getActiveHost(sessionKey: string): ManagedSessionHost | undefined;
38
+ /** 新建一个空会话(/new 命令),清空上下文并重新绑定扩展 */
39
+ newSession(sessionKey: string, identity: FeishuIdentity): Promise<void>;
40
+ /** 切换到另一个持久化的会话(/sessions 卡片上的"切换"按钮) */
41
+ switchToMapping(sessionKey: string, targetSessionKey: string, identity: FeishuIdentity): Promise<boolean>;
42
+ /** 中止当前生成(/stop 或卡片"停止生成"按钮)。没有活动会话时返回 false */
43
+ abort(sessionKey: string): Promise<boolean>;
44
+ /** 删除会话:先释放内存中的运行时,再删除持久化映射 */
45
+ deleteMapping(sessionKey: string): Promise<void>;
46
+ /** 列出全部持久化的会话映射 */
47
+ listMappings(): Promise<SessionMapping[]>;
48
+ /**
49
+ * 回收空闲会话:超过 idleTtlMs 未使用且当前没有生成任务的会话,
50
+ * 释放运行时并移出内存(下次使用会从磁盘恢复)。
51
+ */
52
+ evictIdle(now?: Date): Promise<void>;
53
+ /**
54
+ * 把宿主的会话信息持久化到 FeishuStore:
55
+ * 记录会话文件路径、工作目录、创建/更新时间,以及(私聊或 per-user 模式下)用户 id。
56
+ */
57
+ private persistHost;
58
+ }
59
+ export {};
@@ -0,0 +1,143 @@
1
+ import { PromptQueue } from "./prompt-queue.js";
2
+ import { createRuntimeHost } from "./runtime-host.js";
3
+ /**
4
+ * 会话宿主管理器:
5
+ * 以 sessionKey 为维度,在内存中维护一组 Pi 运行时实例,
6
+ * 并通过 FeishuStore 把"会话 key <-> 会话文件/工作目录"持久化到磁盘。
7
+ *
8
+ * 职责:
9
+ * - getHost:按需创建/复用运行时(懒加载)
10
+ * - newSession / switchToMapping / deleteMapping:会话生命周期管理
11
+ * - abort:中止当前生成
12
+ * - evictIdle:按 idleTtlMs 回收长时间不用的会话,释放内存
13
+ */
14
+ export class SessionHostManager {
15
+ hosts = new Map();
16
+ config;
17
+ store;
18
+ channel;
19
+ constructor(options) {
20
+ this.config = options.config;
21
+ this.store = options.store;
22
+ this.channel = options.channel;
23
+ }
24
+ /**
25
+ * 获取(或创建)某个会话 key 的宿主。
26
+ * - 内存中已有:刷新 lastUsedAt 直接返回
27
+ * - 没有:查存储恢复历史会话(cwd、sessionFile),否则新建,并持久化映射
28
+ */
29
+ async getHost(sessionKey, identity) {
30
+ const existing = this.hosts.get(sessionKey);
31
+ if (existing) {
32
+ existing.lastUsedAt = new Date();
33
+ return existing;
34
+ }
35
+ const mapping = await this.store.getSessionMapping(sessionKey);
36
+ const cwd = mapping?.cwd ?? this.config.sessions.defaultCwd ?? process.cwd();
37
+ const runtimeHost = await createRuntimeHost({
38
+ sessionKey,
39
+ cwd,
40
+ config: this.config,
41
+ channel: this.channel,
42
+ ...(mapping?.sessionFile ? { sessionFile: mapping.sessionFile } : {}),
43
+ });
44
+ const host = {
45
+ sessionKey,
46
+ runtime: runtimeHost.runtime,
47
+ runtimeHost,
48
+ queue: new PromptQueue(),
49
+ lastUsedAt: new Date(),
50
+ };
51
+ this.hosts.set(sessionKey, host);
52
+ await this.persistHost(host, identity);
53
+ return host;
54
+ }
55
+ /** 只读查询内存中是否有该会话的宿主(不创建) */
56
+ getActiveHost(sessionKey) {
57
+ return this.hosts.get(sessionKey);
58
+ }
59
+ /** 新建一个空会话(/new 命令),清空上下文并重新绑定扩展 */
60
+ async newSession(sessionKey, identity) {
61
+ const host = await this.getHost(sessionKey, identity);
62
+ await host.runtime.newSession();
63
+ await host.runtime.session.bindExtensions({});
64
+ await this.persistHost(host, identity);
65
+ }
66
+ /** 切换到另一个持久化的会话(/sessions 卡片上的"切换"按钮) */
67
+ async switchToMapping(sessionKey, targetSessionKey, identity) {
68
+ const target = await this.store.getSessionMapping(targetSessionKey);
69
+ if (!target)
70
+ return false;
71
+ const host = await this.getHost(sessionKey, identity);
72
+ await host.runtime.switchSession(target.sessionFile, { cwdOverride: target.cwd });
73
+ await host.runtime.session.bindExtensions({});
74
+ await this.persistHost(host, identity);
75
+ return true;
76
+ }
77
+ /** 中止当前生成(/stop 或卡片"停止生成"按钮)。没有活动会话时返回 false */
78
+ async abort(sessionKey) {
79
+ const host = this.hosts.get(sessionKey);
80
+ if (!host)
81
+ return false;
82
+ await host.runtimeHost.abort();
83
+ return true;
84
+ }
85
+ /** 删除会话:先释放内存中的运行时,再删除持久化映射 */
86
+ async deleteMapping(sessionKey) {
87
+ const host = this.hosts.get(sessionKey);
88
+ if (host) {
89
+ await host.runtimeHost.dispose();
90
+ this.hosts.delete(sessionKey);
91
+ }
92
+ await this.store.deleteSessionMapping(sessionKey);
93
+ }
94
+ /** 列出全部持久化的会话映射 */
95
+ async listMappings() {
96
+ return await this.store.listSessionMappings();
97
+ }
98
+ /**
99
+ * 回收空闲会话:超过 idleTtlMs 未使用且当前没有生成任务的会话,
100
+ * 释放运行时并移出内存(下次使用会从磁盘恢复)。
101
+ */
102
+ async evictIdle(now = new Date()) {
103
+ const expired = [];
104
+ for (const [key, host] of this.hosts) {
105
+ if (now.getTime() - host.lastUsedAt.getTime() > this.config.sessions.idleTtlMs && !host.activeRun) {
106
+ expired.push(key);
107
+ }
108
+ }
109
+ for (const key of expired) {
110
+ const host = this.hosts.get(key);
111
+ if (host) {
112
+ await host.runtimeHost.dispose();
113
+ this.hosts.delete(key);
114
+ }
115
+ }
116
+ }
117
+ /**
118
+ * 把宿主的会话信息持久化到 FeishuStore:
119
+ * 记录会话文件路径、工作目录、创建/更新时间,以及(私聊或 per-user 模式下)用户 id。
120
+ */
121
+ async persistHost(host, identity) {
122
+ const sessionFile = host.runtime.session.sessionFile;
123
+ if (!sessionFile)
124
+ return;
125
+ const now = new Date().toISOString();
126
+ const previous = await this.store.getSessionMapping(host.sessionKey);
127
+ await this.store.setSessionMapping({
128
+ sessionKey: host.sessionKey,
129
+ appId: this.config.appId,
130
+ chatType: identity.chatType,
131
+ chatId: identity.chatId,
132
+ cwd: host.runtime.cwd,
133
+ sessionFile,
134
+ createdAt: previous?.createdAt ?? now,
135
+ updatedAt: now,
136
+ ...(identity.tenantKey ? { tenantKey: identity.tenantKey } : {}),
137
+ ...(identity.chatType === "private" || this.config.sessions.groupScope === "per-user"
138
+ ? { userId: identity.userOpenId }
139
+ : {}),
140
+ });
141
+ }
142
+ }
143
+ //# sourceMappingURL=session-host-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-host-manager.js","sourceRoot":"","sources":["../../src/bridge/session-host-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import type { FeishuChannel, FeishuConfig } from "../types.js";
2
+ /**
3
+ * ============================================================
4
+ * 流式渲染器
5
+ * ------------------------------------------------------------
6
+ * 把 Pi 会话运行过程中产生的事件流(文本增量、思考、工具调用、
7
+ * 压缩、自动重试等)实时转发成飞书流式 Markdown 消息。
8
+ *
9
+ * 支持三种渲染模式(config.rendering.mode):
10
+ * - stream-card:默认。通过 channel.streamMarkdown 流式输出
11
+ * - text:不渲染任何事件,只跑 prompt(静默执行)
12
+ * ============================================================
13
+ */
14
+ /** 助手消息事件(来自 Pi session 的事件流) */
15
+ interface AssistantMessageEvent {
16
+ type: string;
17
+ delta?: string;
18
+ error?: unknown;
19
+ }
20
+ /** 运行时事件(Pi session 订阅到的事件) */
21
+ interface RuntimeEvent {
22
+ type: string;
23
+ assistantMessageEvent?: AssistantMessageEvent;
24
+ toolName?: string;
25
+ partialResult?: unknown;
26
+ isError?: boolean;
27
+ attempt?: number;
28
+ maxAttempts?: number;
29
+ success?: boolean;
30
+ }
31
+ /** 可订阅会话接口:Pi 的 session 满足此结构 */
32
+ export interface SubscribableSession {
33
+ subscribe(listener: (event: RuntimeEvent) => void): () => void;
34
+ }
35
+ export interface StreamRenderOptions {
36
+ config: FeishuConfig;
37
+ channel: FeishuChannel;
38
+ chatId: string;
39
+ replyTo?: string;
40
+ session: SubscribableSession;
41
+ runPrompt(): Promise<void>;
42
+ }
43
+ /**
44
+ * 运行一次 prompt 并渲染输出:
45
+ * - mode 为 text 时静默执行,不渲染
46
+ * - 其他模式通过 channel.streamMarkdown 创建流式消息,期间把会话事件
47
+ * 转发给 writer,消息发送完成后 Pi 侧会自动结束流
48
+ */
49
+ export declare function renderPromptStream(options: StreamRenderOptions): Promise<void>;
50
+ export {};