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,837 @@
1
+ # Pi Feishu Extension Architecture
2
+
3
+ ## Goal
4
+
5
+ Design a Feishu integration for Pi Agent as an installable extension-oriented package.
6
+
7
+ The integration should let users talk to Pi Agent from Feishu while preserving Pi's existing extensibility model: extensions, skills, prompt templates, session runtime, custom tools, UI prompts, and package-managed resources.
8
+
9
+ This is not intended to be a direct copy of `pi-feishu-cli`. That package is a useful prototype because it proves Feishu messaging, cards, streaming, attachments, and `AgentSessionRuntime` can work together. The target design here separates the transport host from Pi's extension package so the system can be installed, operated, tested, and evolved independently.
10
+
11
+ The MVP targets one Feishu app serving private chats and group chats. Full multi-tenant operation is not part of the MVP. Tenant fields may be stored for forward compatibility, but routing, permissions, and deployment should initially optimize for a single Feishu app in one workspace.
12
+
13
+ ## Design Principles
14
+
15
+ 1. Keep Pi core stable.
16
+ Feishu is an external transport and should not require large changes to `pi-main`.
17
+
18
+ 2. Make Feishu a first-class transport.
19
+ Feishu should not be treated as a terminal hack. It needs its own message normalization, session mapping, UI bridge, permission handling, and renderer.
20
+
21
+ 3. Separate runtime hosting from extension registration.
22
+ Receiving messages from Feishu requires a long-running process. Registering tools and commands belongs in Pi's extension system. These are related but not the same responsibility.
23
+
24
+ 4. Prefer per-conversation isolation.
25
+ One Feishu chat should not block all other chats. Queues, abort state, and session state should be scoped by a deterministic session key.
26
+
27
+ 5. Treat group chat and private chat differently.
28
+ Private chat should isolate by user. Group chat should default to one shared group session and require mentioning the bot.
29
+
30
+ 6. Avoid premature multi-tenancy.
31
+ Keep `tenantKey` as optional metadata, but do not design the MVP around multiple companies sharing one service.
32
+
33
+ 7. Keep deployment modes open.
34
+ Start with Feishu WebSocket because it is easier to run locally. Design the transport boundary so webhook mode can be added later.
35
+
36
+ ## Package Shape
37
+
38
+ Recommended package name:
39
+
40
+ ```text
41
+ pi-remote-feishu
42
+ ```
43
+
44
+ or, if it is published under the project scope:
45
+
46
+ ```text
47
+ @earendil-works/pi-remote-feishu
48
+ ```
49
+
50
+ Recommended package layout:
51
+
52
+ ```text
53
+ pi-remote-feishu/
54
+ package.json
55
+ README.md
56
+ ARCHITECTURE.md
57
+ src/
58
+ bin/
59
+ pi-remote-feishu.ts
60
+ config/
61
+ load-config.ts
62
+ schema.ts
63
+ feishu/
64
+ channel.ts
65
+ webhook.ts
66
+ verifier.ts
67
+ types.ts
68
+ bridge/
69
+ runtime-host.ts
70
+ conversation-router.ts
71
+ session-host-manager.ts
72
+ message-normalizer.ts
73
+ message-handler.ts
74
+ stream-renderer.ts
75
+ ui-context.ts
76
+ card-actions.ts
77
+ prompt-queue.ts
78
+ cards/
79
+ help.ts
80
+ models.ts
81
+ sessions.ts
82
+ stop.ts
83
+ permission.ts
84
+ result.ts
85
+ attachments/
86
+ processor.ts
87
+ mime.ts
88
+ temp-files.ts
89
+ store/
90
+ store.ts
91
+ json-store.ts
92
+ sqlite-store.ts
93
+ tools/
94
+ send-file-to-chat.ts
95
+ extensions/
96
+ index.ts
97
+ test/
98
+ suite/
99
+ ```
100
+
101
+ `package.json` should expose both a CLI and Pi package resources:
102
+
103
+ ```json
104
+ {
105
+ "name": "pi-remote-feishu",
106
+ "type": "module",
107
+ "bin": {
108
+ "pi-remote-feishu": "./dist/bin/pi-remote-feishu.js"
109
+ },
110
+ "pi": {
111
+ "extensions": [
112
+ "./dist/extensions/index.js"
113
+ ],
114
+ "skills": [
115
+ "./dist/skills"
116
+ ]
117
+ }
118
+ }
119
+ ```
120
+
121
+ The `pi.extensions` manifest lets Pi's package manager discover the extension automatically. The `bin` entry starts the Feishu transport host.
122
+
123
+ ## High-Level Architecture
124
+
125
+ ```text
126
+ Feishu user
127
+ -> Feishu channel or webhook
128
+ -> Event verification
129
+ -> Message normalization
130
+ -> Permission and routing
131
+ -> ConversationRouter
132
+ -> SessionHostManager
133
+ -> SessionHost
134
+ -> per-session PromptQueue
135
+ -> per-session AgentSessionRuntime
136
+ -> StreamRenderer
137
+ -> Feishu card/text/file response
138
+
139
+ Pi extension system
140
+ -> extensions/index.ts
141
+ -> register send_file_to_chat
142
+ -> register /feishu commands
143
+ -> bridge ExtensionUIContext to Feishu cards
144
+ ```
145
+
146
+ The important architectural split:
147
+
148
+ ```text
149
+ Transport Host
150
+ Long-running process. Owns Feishu connection and dispatches incoming messages.
151
+
152
+ Pi Extension
153
+ Loaded by Pi. Registers tools, commands, prompt guidance, and UI integration.
154
+ ```
155
+
156
+ ## Runtime Modes
157
+
158
+ ### 1. Feishu-only server mode
159
+
160
+ Command:
161
+
162
+ ```bash
163
+ pi-remote-feishu serve
164
+ ```
165
+
166
+ Behavior:
167
+
168
+ - Connects to Feishu.
169
+ - Creates or resumes Pi sessions based on Feishu chat identity.
170
+ - Sends responses back to Feishu.
171
+ - Does not start the terminal TUI.
172
+
173
+ This should be the default production mode.
174
+
175
+ ### 2. Hybrid local mode
176
+
177
+ Command:
178
+
179
+ ```bash
180
+ pi-remote-feishu tui
181
+ ```
182
+
183
+ Behavior:
184
+
185
+ - Starts the Pi interactive TUI.
186
+ - Connects Feishu in the same process.
187
+ - Useful for local debugging and demonstrations.
188
+
189
+ This mode is similar to the reference package, but should remain optional.
190
+
191
+ ### 3. Webhook mode
192
+
193
+ Command:
194
+
195
+ ```bash
196
+ pi-remote-feishu serve --transport webhook --port 8787
197
+ ```
198
+
199
+ Behavior:
200
+
201
+ - Starts an HTTP server.
202
+ - Validates Feishu challenge, signature, timestamp, and encrypted payloads.
203
+ - Dispatches normalized events into the same bridge layer.
204
+
205
+ Webhook mode should reuse the same `MessageHandler`, `ConversationRouter`, `SessionHostManager`, `PromptQueue`, and `StreamRenderer` as WebSocket mode.
206
+
207
+ ## Module Responsibilities
208
+
209
+ ### `feishu/channel.ts`
210
+
211
+ Owns Feishu SDK integration.
212
+
213
+ Responsibilities:
214
+
215
+ - Create Feishu WebSocket client.
216
+ - Emit normalized raw events.
217
+ - Send text, markdown, cards, images, and files.
218
+ - Update cards by message id or interaction token.
219
+ - Download message resources.
220
+ - Hide SDK-specific shapes behind local interfaces.
221
+
222
+ It should expose an interface similar to:
223
+
224
+ ```ts
225
+ export interface FeishuChannel {
226
+ connect(): Promise<void>;
227
+ disconnect(): Promise<void>;
228
+ onMessage(handler: (message: FeishuMessage) => void): void;
229
+ onCardAction(handler: (event: FeishuCardAction) => void): void;
230
+ sendText(chatId: string, text: string, options?: ReplyOptions): Promise<SendResult>;
231
+ sendCard(chatId: string, card: unknown, options?: ReplyOptions): Promise<SendResult>;
232
+ updateCard(messageId: string, card: unknown): Promise<void>;
233
+ updateCardByToken(token: string, card: unknown): Promise<void>;
234
+ sendFile(chatId: string, filePath: string, fileName?: string): Promise<void>;
235
+ downloadResource(messageId: string, fileKey: string, type: string): Promise<Buffer>;
236
+ }
237
+ ```
238
+
239
+ ### `feishu/webhook.ts`
240
+
241
+ Owns webhook transport.
242
+
243
+ Responsibilities:
244
+
245
+ - Start HTTP server.
246
+ - Handle Feishu URL verification challenge.
247
+ - Verify timestamp and signature.
248
+ - Decrypt event payloads when encrypt key is configured.
249
+ - Convert webhook payloads into the same local event model used by WebSocket mode.
250
+
251
+ This should not contain Pi-specific logic.
252
+
253
+ ### `bridge/runtime-host.ts`
254
+
255
+ Owns the lifecycle of one Pi runtime bound to one Feishu conversation.
256
+
257
+ Responsibilities:
258
+
259
+ - Create `AgentSessionRuntime`.
260
+ - Open the mapped Pi session file for one conversation.
261
+ - Bind extensions after session replacement.
262
+ - Expose a stable API to prompt, abort, switch model, list sessions, and list models.
263
+ - Keep Feishu context and Feishu UI context scoped to the active run.
264
+
265
+ It should be the only module that directly knows Pi runtime construction details.
266
+
267
+ ### `bridge/conversation-router.ts`
268
+
269
+ Maps Feishu identity to a conversation key.
270
+
271
+ Recommended MVP keys:
272
+
273
+ ```text
274
+ Private chat:
275
+ dm:{userOpenId}
276
+
277
+ Group chat, shared session:
278
+ group:{chatId}
279
+
280
+ Group chat, per-user optional mode:
281
+ group-user:{chatId}:{userOpenId}
282
+ ```
283
+
284
+ Supported modes:
285
+
286
+ ```ts
287
+ type PrivateChatScope = "per-user";
288
+ type GroupChatScope = "shared-chat" | "per-user";
289
+ ```
290
+
291
+ Behavior:
292
+
293
+ - Private chats always default to one session per user.
294
+ - Group chats default to one shared session per group.
295
+ - Group chats require bot mention by default.
296
+ - Group prompt text should include sender display information so Pi can reason about who spoke.
297
+ - Full multi-tenant routing is not required for MVP.
298
+ - `tenantKey` may be captured as optional metadata for future expansion.
299
+ - Session mappings are persisted in `store`.
300
+
301
+ ### `bridge/session-host-manager.ts`
302
+
303
+ Owns active `SessionHost` instances.
304
+
305
+ Pi's `AgentSessionRuntime` has one active `runtime.session`. Because Feishu is multi-user, sharing one runtime across all chats would cause session switching, abort, stream, and UI-context cross-talk. The manager should create or reuse one `SessionHost` per conversation key.
306
+
307
+ Recommended model:
308
+
309
+ ```ts
310
+ export interface SessionHost {
311
+ sessionKey: string;
312
+ runtime: AgentSessionRuntime;
313
+ queue: PromptQueue;
314
+ activeRun?: ActiveRun;
315
+ lastUsedAt: Date;
316
+ }
317
+ ```
318
+
319
+ Lifecycle:
320
+
321
+ - Create a host on first message for a conversation.
322
+ - Reuse it while active.
323
+ - Serialize prompts inside that host.
324
+ - Evict idle hosts after a configurable TTL.
325
+ - Persist the Pi session file mapping before eviction.
326
+ - Recreate the host from the persisted session file on the next message.
327
+
328
+ This gives private chats and group chats isolated runtime state without requiring changes to Pi core.
329
+
330
+ ### `bridge/prompt-queue.ts`
331
+
332
+ Serializes prompts per session key.
333
+
334
+ Avoid a global lock. A global lock means one busy group blocks every other chat.
335
+
336
+ Required behavior:
337
+
338
+ - Same `sessionKey`: prompts run sequentially.
339
+ - Different `sessionKey`: prompts may run concurrently.
340
+ - Store active abort controller per `sessionKey`.
341
+ - Stop button aborts only the active run for that session key.
342
+
343
+ ### `bridge/message-normalizer.ts`
344
+
345
+ Converts Feishu messages to Pi prompt input.
346
+
347
+ Responsibilities:
348
+
349
+ - Strip bot mention from group messages.
350
+ - Ignore unsupported messages.
351
+ - Detect slash commands.
352
+ - Preserve user display metadata for audit/debug.
353
+ - Attach text and images in the shape Pi expects.
354
+
355
+ Normalized shape:
356
+
357
+ ```ts
358
+ export interface NormalizedPiInput {
359
+ sessionKey: string;
360
+ chatId: string;
361
+ messageId: string;
362
+ userId: string;
363
+ tenantKey?: string;
364
+ chatType: "private" | "group";
365
+ senderName?: string;
366
+ text: string;
367
+ images: Array<{ type: "image"; data: string; mimeType: string }>;
368
+ attachmentNotes: string[];
369
+ command?: FeishuCommand;
370
+ }
371
+ ```
372
+
373
+ ### `attachments/processor.ts`
374
+
375
+ Handles Feishu message resources.
376
+
377
+ Responsibilities:
378
+
379
+ - Download images, files, audio, and video metadata.
380
+ - Pass supported images directly to Pi when the model supports image input.
381
+ - Convert small text files into prompt text.
382
+ - Save large or binary files to a controlled temporary directory.
383
+ - Add prompt-visible file notes.
384
+ - Clean temporary files after the run.
385
+
386
+ Safety requirements:
387
+
388
+ - Enforce size limits before sending files back to Feishu.
389
+ - Keep temporary files under a known directory.
390
+ - Never allow model-provided file paths to escape allowed output roots.
391
+
392
+ ### `bridge/stream-renderer.ts`
393
+
394
+ Converts Pi session events to Feishu output.
395
+
396
+ Responsibilities:
397
+
398
+ - Stream assistant text to Feishu card or markdown.
399
+ - Render thinking blocks in a readable style.
400
+ - Render tool execution start/update/end.
401
+ - Render compaction and retry events as status lines.
402
+ - Finalize card after completion.
403
+ - Show model errors clearly.
404
+
405
+ Renderer policy:
406
+
407
+ - Text deltas should be streamed.
408
+ - Thinking deltas can be collapsed, quoted, or hidden depending on config.
409
+ - Tool output should be concise by default.
410
+ - Large output should be summarized or attached as a file.
411
+
412
+ ### `bridge/ui-context.ts`
413
+
414
+ Implements `ExtensionUIContext` for Feishu.
415
+
416
+ This is critical for permission systems and extensions that call:
417
+
418
+ - `ctx.ui.confirm()`
419
+ - `ctx.ui.select()`
420
+ - `ctx.ui.notify()`
421
+
422
+ Feishu implementation:
423
+
424
+ - `confirm()` sends a card with yes/no buttons.
425
+ - `select()` sends a card with one button per option.
426
+ - `notify()` sends a text or compact card.
427
+ - `input()` can be phase 2 because it requires capturing the user's next message.
428
+ - TUI-specific methods are no-op or unavailable.
429
+
430
+ Avoid deadlocks:
431
+
432
+ - Feishu card actions must be handled while a prompt is waiting for UI input.
433
+ - If the SDK serializes message and card events by chat, disable that queue and use the package's own per-session queue.
434
+
435
+ ### `bridge/card-actions.ts`
436
+
437
+ Routes Feishu card button actions.
438
+
439
+ Supported action groups:
440
+
441
+ - `session`: new, switch, delete.
442
+ - `model`: select provider/model/thinking level.
443
+ - `stop`: abort active generation.
444
+ - `permission`: resolve UI confirm/select.
445
+ - `help`: navigate between help cards.
446
+
447
+ Card action handlers should return quickly. Slow work should be scheduled after Feishu receives an empty success response.
448
+
449
+ ### `cards/*`
450
+
451
+ Build Feishu interactive cards.
452
+
453
+ Cards should be deterministic data builders, not business logic.
454
+
455
+ Recommended cards:
456
+
457
+ - `help`: available commands and status.
458
+ - `sessions`: current session and switch/new/delete actions.
459
+ - `models`: provider, model, thinking level selection.
460
+ - `stop`: active generation control.
461
+ - `permission`: confirm/select prompts.
462
+ - `result`: final answer card fallback when streaming is unavailable.
463
+
464
+ ### `tools/send-file-to-chat.ts`
465
+
466
+ Registers a Pi tool that lets the agent send generated files back to the current Feishu chat.
467
+
468
+ Important design point:
469
+
470
+ - The tool should not know global Feishu state.
471
+ - It should read the current Feishu context from a scoped context provider set during the active Feishu prompt.
472
+
473
+ Tool behavior:
474
+
475
+ - Accept `filePath` and optional `fileName`.
476
+ - Validate file existence.
477
+ - Validate allowed directory.
478
+ - Validate file size.
479
+ - Send through `FeishuChannel.sendFile`.
480
+ - Return a normal Pi tool result.
481
+
482
+ ### `extensions/index.ts`
483
+
484
+ The installable Pi extension entry.
485
+
486
+ Responsibilities:
487
+
488
+ - Register `send_file_to_chat`.
489
+ - Register `/feishu status`.
490
+ - Register `/feishu sessions` if useful inside TUI.
491
+ - Add prompt guidelines explaining when to call `send_file_to_chat`.
492
+ - Optionally register markdown transformers for Feishu-specific output constraints.
493
+
494
+ This module should not open a Feishu socket by itself. Long-running transport belongs to the host process.
495
+
496
+ ## Configuration
497
+
498
+ Recommended config resolution order:
499
+
500
+ 1. CLI flags.
501
+ 2. Project config: `.pi/feishu.json`.
502
+ 3. User config: `~/.pi/agent/feishu.json`.
503
+ 4. Environment variables.
504
+
505
+ Recommended schema:
506
+
507
+ ```ts
508
+ export interface FeishuConfig {
509
+ appId: string;
510
+ appSecret: string;
511
+ encryptKey?: string;
512
+ verificationToken?: string;
513
+ botName?: string;
514
+ transport: "websocket" | "webhook";
515
+ webhook?: {
516
+ host?: string;
517
+ port: number;
518
+ path: string;
519
+ };
520
+ policy: {
521
+ requireMention: boolean;
522
+ dmEnabled: boolean;
523
+ groupEnabled: boolean;
524
+ allowUsers?: string[];
525
+ allowChats?: string[];
526
+ };
527
+ sessions: {
528
+ privateScope: "per-user";
529
+ groupScope: "shared-chat" | "per-user";
530
+ defaultCwd?: string;
531
+ store: "json" | "sqlite";
532
+ idleTtlMs: number;
533
+ };
534
+ rendering: {
535
+ mode: "stream-card" | "markdown" | "text";
536
+ showThinking: "hide" | "quote" | "plain";
537
+ showToolEvents: boolean;
538
+ };
539
+ files: {
540
+ allowedOutputDirs: string[];
541
+ maxUploadBytes: number;
542
+ tempDir?: string;
543
+ };
544
+ }
545
+ ```
546
+
547
+ ## Command Design
548
+
549
+ Feishu chat commands:
550
+
551
+ ```text
552
+ /help
553
+ /sessions
554
+ /models
555
+ /new
556
+ /stop
557
+ /reset
558
+ /status
559
+ ```
560
+
561
+ Recommended behavior:
562
+
563
+ - `/help`: show help card.
564
+ - `/sessions`: show session management card.
565
+ - `/models`: show model selection card.
566
+ - `/new`: create a new Pi session for the current Feishu session key.
567
+ - `/stop`: abort active generation for the current session key.
568
+ - `/reset`: clear mapping and start a new session.
569
+ - `/status`: show current model, session id, queue state, and transport health.
570
+
571
+ Pi slash commands from Feishu:
572
+
573
+ - Phase 1: only Feishu-specific commands are intercepted.
574
+ - Phase 2: forward unknown slash commands into Pi if they are safe and supported.
575
+
576
+ ## Conversation Model
577
+
578
+ Pi's `AgentSessionRuntime` exposes one active `runtime.session`. That is fine for a terminal, but Feishu can deliver messages from many users and groups at the same time. The integration should not multiplex all messages through one shared runtime.
579
+
580
+ Recommended MVP behavior:
581
+
582
+ ```text
583
+ Alice private chat
584
+ -> dm:alice
585
+ -> SessionHost A
586
+ -> Runtime A
587
+
588
+ Bob private chat
589
+ -> dm:bob
590
+ -> SessionHost B
591
+ -> Runtime B
592
+
593
+ Engineering group chat
594
+ -> group:chat-123
595
+ -> SessionHost C
596
+ -> Runtime C
597
+ ```
598
+
599
+ Concurrency rule:
600
+
601
+ - Same conversation key: serial execution.
602
+ - Different conversation keys: independent execution.
603
+ - Stop, permission cards, stream updates, temporary files, and Feishu context are bound to the active run inside one `SessionHost`.
604
+
605
+ Group chat prompt format should preserve speaker identity:
606
+
607
+ ```text
608
+ [Feishu group message]
609
+ Sender: Alice
610
+ Message:
611
+ Please check this error.
612
+ ```
613
+
614
+ Multi-tenant note:
615
+
616
+ - MVP does not need a tenant router.
617
+ - Store optional `tenantKey` if Feishu provides it.
618
+ - Future multi-tenant mode can prefix keys with `tenantKey`, for example `tenant:{tenantKey}:group:{chatId}`.
619
+
620
+ ## Data Flow
621
+
622
+ ### Normal Message
623
+
624
+ ```text
625
+ Feishu text/image/file
626
+ -> channel.onMessage
627
+ -> verify policy
628
+ -> normalize message
629
+ -> resolve session key
630
+ -> process attachments
631
+ -> acquire per-session queue
632
+ -> set Feishu context
633
+ -> set Feishu UI context
634
+ -> stream renderer starts
635
+ -> runtime.session.prompt()
636
+ -> renderer updates Feishu
637
+ -> cleanup context/temp files
638
+ -> release queue
639
+ ```
640
+
641
+ ### Permission Prompt
642
+
643
+ ```text
644
+ Pi tool call requires confirmation
645
+ -> extension calls ctx.ui.confirm()
646
+ -> Feishu UI context sends permission card
647
+ -> user clicks button
648
+ -> card action resolves pending dialog
649
+ -> tool call continues or is blocked
650
+ ```
651
+
652
+ ### Stop Generation
653
+
654
+ ```text
655
+ User clicks stop or sends /stop
656
+ -> card action or command resolves sessionKey
657
+ -> PromptQueue finds active run
658
+ -> runtime.session.abort()
659
+ -> stop card updates to cancelled
660
+ ```
661
+
662
+ ### File Return
663
+
664
+ ```text
665
+ Pi creates local file
666
+ -> model calls send_file_to_chat
667
+ -> tool validates file
668
+ -> reads current Feishu context
669
+ -> FeishuChannel.sendFile()
670
+ -> tool returns success/failure to Pi
671
+ ```
672
+
673
+ ## Store Design
674
+
675
+ Start with JSON, keep an interface that can support SQLite later.
676
+
677
+ ```ts
678
+ export interface FeishuStore {
679
+ getSessionMapping(sessionKey: string): Promise<SessionMapping | undefined>;
680
+ setSessionMapping(mapping: SessionMapping): Promise<void>;
681
+ deleteSessionMapping(sessionKey: string): Promise<void>;
682
+ listSessionMappings(filter?: SessionFilter): Promise<SessionMapping[]>;
683
+ }
684
+ ```
685
+
686
+ Mapping shape:
687
+
688
+ ```ts
689
+ export interface SessionMapping {
690
+ sessionKey: string;
691
+ appId: string;
692
+ tenantKey?: string;
693
+ chatType: "private" | "group";
694
+ chatId: string;
695
+ userId?: string;
696
+ cwd: string;
697
+ sessionFile: string;
698
+ createdAt: string;
699
+ updatedAt: string;
700
+ }
701
+ ```
702
+
703
+ JSON is enough for MVP. SQLite is better when:
704
+
705
+ - Many chats are active.
706
+ - Audit metadata is needed.
707
+ - Multiple worker processes are introduced.
708
+
709
+ ## Security and Permission Model
710
+
711
+ Minimum policy:
712
+
713
+ - Ignore group messages unless the bot is mentioned.
714
+ - Support allowlisted users and chats.
715
+ - Never expose raw app secret in logs.
716
+ - Verify webhook signatures when webhook mode is enabled.
717
+ - Store credentials in user config, not project config, unless explicitly intended.
718
+ - Restrict file sending to allowed directories.
719
+ - Treat Feishu attachment content as untrusted user input.
720
+
721
+ Recommended production policy:
722
+
723
+ - Private chats enabled by default.
724
+ - Group chats disabled unless configured.
725
+ - Project-local cwd requires explicit config.
726
+ - Dangerous tools still rely on Pi's permission extension or existing tool approval flow.
727
+
728
+ ## Error Handling
729
+
730
+ Expected errors:
731
+
732
+ - Feishu connection failure.
733
+ - Feishu token/auth failure.
734
+ - Unsupported message type.
735
+ - Attachment download failure.
736
+ - Pi model unavailable.
737
+ - Session file missing.
738
+ - Tool permission rejected.
739
+ - Card update token expired.
740
+
741
+ Response policy:
742
+
743
+ - User-facing failures should be sent back to Feishu as concise messages.
744
+ - Operational failures should be logged with structured metadata.
745
+ - Card update failures should not crash active generation.
746
+ - Attachment failures should become prompt-visible notes when possible.
747
+
748
+ ## Testing Strategy
749
+
750
+ Unit tests:
751
+
752
+ - Config loading priority.
753
+ - Message normalization.
754
+ - Session key generation.
755
+ - Prompt queue serialization.
756
+ - Card action routing.
757
+ - Attachment handling.
758
+ - File sending tool validation.
759
+
760
+ Integration tests with fakes:
761
+
762
+ - Fake Feishu channel.
763
+ - Faux Pi provider.
764
+ - Runtime prompt -> stream renderer -> sent Feishu updates.
765
+ - Permission card resolution.
766
+ - Stop generation abort.
767
+
768
+ Do not use real Feishu or paid provider APIs in normal tests.
769
+
770
+ ## MVP Plan
771
+
772
+ ### Phase 1: Local working bot
773
+
774
+ - Implement WebSocket channel.
775
+ - Implement config loading.
776
+ - Implement conversation routing for private chats and group chats.
777
+ - Implement session host manager.
778
+ - Use one runtime per active conversation.
779
+ - Implement message normalization.
780
+ - Implement per-session queue.
781
+ - Support text messages.
782
+ - Send plain markdown responses.
783
+
784
+ ### Phase 2: Pi-native extension package
785
+
786
+ - Add `extensions/index.ts`.
787
+ - Register `send_file_to_chat`.
788
+ - Add prompt guidelines for file delivery.
789
+ - Add package manifest under `package.json.pi`.
790
+ - Verify Pi package manager can discover the extension.
791
+
792
+ ### Phase 3: Rich Feishu experience
793
+
794
+ - Add streaming cards.
795
+ - Add `/help`, `/sessions`, `/models`.
796
+ - Add stop card.
797
+ - Add model and thinking level card actions.
798
+ - Add Feishu `ExtensionUIContext` bridge for confirm/select.
799
+
800
+ ### Phase 4: Attachments and files
801
+
802
+ - Support image input.
803
+ - Support small text file extraction.
804
+ - Save large files to temp directory.
805
+ - Support generated file upload through tool.
806
+ - Add cleanup and size limits.
807
+
808
+ ### Phase 5: Production deployment
809
+
810
+ - Add webhook transport.
811
+ - Add signature/encryption verification.
812
+ - Add SQLite store.
813
+ - Add structured logging.
814
+ - Add allowlist policy.
815
+ - Add deployment docs.
816
+ - Keep full multi-tenant routing as a later phase unless there is a concrete product need.
817
+
818
+ ## Key Interview Talking Points
819
+
820
+ 1. The main architectural decision is separating transport host from Pi extension registration.
821
+ 2. Feishu is modeled as a transport, not as a special case inside Pi core.
822
+ 3. Pi has one active session per runtime, so Feishu needs a `SessionHostManager` instead of one shared runtime.
823
+ 4. Private chat and group chat use different identity policies: private per user, group shared by default.
824
+ 5. Per-session queues avoid global blocking and make abort semantics precise.
825
+ 6. Stop, streaming, permission cards, and Feishu context are bound to one active run, avoiding cross-talk.
826
+ 7. `ExtensionUIContext` is the bridge that lets existing Pi permission and interaction flows work remotely.
827
+ 8. File delivery is implemented as a Pi tool, which keeps it model-callable and consistent with the extension system.
828
+ 9. Multi-tenancy is deliberately deferred: the MVP stores optional tenant metadata but does not pay the complexity cost early.
829
+ 10. The design starts simple with JSON storage and evolves to SQLite only when concurrency and audit requirements justify it.
830
+
831
+ ## Open Questions
832
+
833
+ 1. Should group chats always share one Pi session by default, or should some groups opt into per-user sessions?
834
+ 2. Should unknown Feishu slash commands be forwarded into Pi slash commands?
835
+ 3. Should generated files be uploaded automatically or only when the model calls `send_file_to_chat`?
836
+ 4. Should Feishu messages be allowed to use project cwd by default, or require explicit binding?
837
+ 5. Should there be a multi-worker deployment model, or is one process enough for the first public version?