larkcc 0.4.0 → 0.6.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 (56) hide show
  1. package/CHANGELOG.md +61 -12
  2. package/README.md +188 -27
  3. package/dist/agent.d.ts +2 -1
  4. package/dist/agent.js +189 -38
  5. package/dist/agent.js.map +1 -1
  6. package/dist/app.js +14 -5
  7. package/dist/app.js.map +1 -1
  8. package/dist/cardkit.d.ts +125 -0
  9. package/dist/cardkit.js +448 -0
  10. package/dist/cardkit.js.map +1 -0
  11. package/dist/config.d.ts +23 -3
  12. package/dist/config.js +43 -47
  13. package/dist/config.js.map +1 -1
  14. package/dist/feishu.d.ts +50 -4
  15. package/dist/feishu.js +287 -97
  16. package/dist/feishu.js.map +1 -1
  17. package/dist/format/builder.d.ts +126 -37
  18. package/dist/format/builder.js +276 -116
  19. package/dist/format/builder.js.map +1 -1
  20. package/dist/format/card-optimize.d.ts +42 -0
  21. package/dist/format/card-optimize.js +74 -0
  22. package/dist/format/card-optimize.js.map +1 -0
  23. package/dist/format/constants.d.ts +80 -9
  24. package/dist/format/constants.js +140 -53
  25. package/dist/format/constants.js.map +1 -1
  26. package/dist/format/document.d.ts +4 -2
  27. package/dist/format/document.js +57 -35
  28. package/dist/format/document.js.map +1 -1
  29. package/dist/format/duration.d.ts +24 -0
  30. package/dist/format/duration.js +72 -0
  31. package/dist/format/duration.js.map +1 -0
  32. package/dist/format/guide.d.ts +19 -0
  33. package/dist/format/guide.js +201 -0
  34. package/dist/format/guide.js.map +1 -0
  35. package/dist/format/image-resolver.d.ts +31 -0
  36. package/dist/format/image-resolver.js +202 -0
  37. package/dist/format/image-resolver.js.map +1 -0
  38. package/dist/format/index.d.ts +8 -3
  39. package/dist/format/index.js +8 -2
  40. package/dist/format/index.js.map +1 -1
  41. package/dist/format/parser.d.ts +16 -0
  42. package/dist/format/parser.js +26 -12
  43. package/dist/format/parser.js.map +1 -1
  44. package/dist/format/sanitize.d.ts +3 -0
  45. package/dist/format/sanitize.js +16 -7
  46. package/dist/format/sanitize.js.map +1 -1
  47. package/dist/format/thinking.d.ts +29 -0
  48. package/dist/format/thinking.js +50 -0
  49. package/dist/format/thinking.js.map +1 -0
  50. package/dist/resources/format-guide.md +109 -0
  51. package/dist/streaming.d.ts +83 -0
  52. package/dist/streaming.js +301 -0
  53. package/dist/streaming.js.map +1 -0
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.js +1 -1
  56. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -7,23 +7,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-03-27
11
+
12
+ ### Added
13
+
14
+ - CardKit SDK migration: all API calls now use `client.cardkit.v1.*` instead of raw fetch with manual token management
15
+ - Streaming preview (summary) with correct object format `{ content, i18n_content }` for card list display
16
+ - Two-step streaming close: `card.settings({ streaming_mode: false })` then `card.update()`, aligned with openclaw-lark best practice
17
+ - `wide_screen_mode` and `update_multi` card config for all modes (CardKit, Update, non-streaming)
18
+ - Structured error handling: `CardKitApiError` class with `isRateLimit` and `isTableLimit` detection
19
+ - Shared `prepareOverflowContext()` helper extracted from `replyWithDocument()` for reuse in CardKit overflow
20
+ - Tool status display in CardKit mode: status text prepended to streaming content during tool calls
21
+
22
+ ### Fixed
23
+
24
+ - Thinking block not captured (SDK sends `type: "thinking"` blocks, code only handled text/tool_use)
25
+ - Abort not updating card (abort handler inside event loop, never reached when SDK exits)
26
+ - Tool status not displaying when no text content yet (`updateStatus` didn't trigger card creation or flush)
27
+ - Bottom model name not showing in card footer (extracted from `modelUsage` keys instead of non-existent `result.model`)
28
+ - CardKit status bar visibility (empty initial content prevented element rendering)
29
+ - Sequence number conflicts between concurrent `updateStatus` and `performFlush` calls
30
+ - `updateStatus` not awaited in agent.ts causing potential state race conditions
31
+ - Unnecessary token cache invalidation on every overflow (now only refreshes when <10min remaining)
32
+
33
+ ### Changed
34
+
35
+ - FlushController deduplication: CardKit mode now imports from `streaming.ts` instead of inline copy
36
+ - CardKit constructor no longer accepts `appId`/`appSecret` params (SDK handles token management)
37
+ - `@larksuiteoapi/node-sdk` upgraded from `^1` to `^1.60`
38
+ - Default `flush_interval_ms` changed from 300 to 200
39
+ - `/stop` now provides full abort feedback: signal received notification + success/failure message + distinct reaction
40
+
41
+ ### Refactored
42
+
43
+ - Removed separate `status_bar` element, merged tool status into `streaming_content` as in-memory prefix to eliminate timing issues
44
+
45
+ ## [0.5.0] - 2026-03-27
46
+
10
47
  ### Added
11
48
 
12
- - Card table limit handling for Feishu messages (max 5 tables per card)
13
- - `countTables()` function to accurately count tables in markdown (excludes code blocks)
14
- - `splitMarkdownByTables()` to split markdown by table boundaries
15
- - `CardTableConfig` for configurable thresholds (`max_tables_per_card`, `max_tables_split`)
16
- - Tables >10 auto-write to document, 6-10 split into multiple cards
49
+ - Streaming output support with two modes: CardKit (default) and Update
50
+ - **CardKit mode**: single-card architecture with state machine (idle creating streaming completed), aligned with official openclaw-lark approach
51
+ - **Update mode**: message patch API with auto-fallback chain (cardkit → update → none)
52
+ - Image resolver for external images (download and re-upload to Feishu CDN)
53
+ - Internal CDN domain skip list (feishucdn.com, larksuitecdn.com, etc.)
54
+ - Configurable `card_title` for all streaming modes (default: "Claude")
55
+ - Collapsible thinking section in CardKit complete output
56
+ - Per-tool status words for tool cards (replaces random thinking words)
57
+ - `prepublishOnly` script to package.json for safe npm publishing
58
+ - Card footer metadata (model, tokens, duration) in streaming cards
59
+
60
+ ### Changed
61
+
62
+ - Default streaming mode changed from `update` to `cardkit`
63
+ - CardKit mode no longer sends separate tool call cards (single-card design)
64
+
65
+ ### Removed
66
+
67
+ - `thinking_words` config option (replaced by per-tool status words)
17
68
 
18
69
  ### Fixed
19
70
 
20
- - Correct BlockType enum values to match official Feishu API docs
21
- - TABLE: 24 31
22
- - TABLE_CELL: 25 32
23
- - IFRAME: 27 26
24
- - IMAGE: 28 27
25
- - VIEW: 29 → 33
26
- - Add GRID (24) and GRID_COLUMN (25)
71
+ - Correct CardKit API format (card_json type, IM message reply, sequence counter)
72
+ - CardKit lazy card creation on first `append()` call
73
+ - Image resolver CDN download failures with internal domain blacklist
74
+ - ESM-compatible `__dirname` polyfill in guide.ts
75
+ - Align all docx block structures with Feishu SDK types to resolve 1770024 error
27
76
 
28
77
  ## [0.3.0] - 2026-03-26
29
78
 
package/README.md CHANGED
@@ -219,10 +219,13 @@ Bot: ✅ 已执行
219
219
 
220
220
  ```
221
221
  你:帮我重构整个项目...
222
- 你:/stop → ⏹ 已发送中断信号
222
+ 你:/stop → ⏹ 已发送中断信号,等待当前步骤完成...
223
+ → ✅ 已中断
223
224
  ```
224
225
 
225
- 任务超过 10 分钟无响应自动释放锁,无需重启。
226
+ - `/stop` 发送后先提示"已发送中断信号",中断完成后通知"已中断"
227
+ - 中断后卡片保留已生成的内容和思考过程,不会丢失
228
+ - 任务超过 10 分钟无响应自动释放锁,无需重启
226
229
 
227
230
  ## 图片支持
228
231
 
@@ -312,12 +315,18 @@ setup 不需要填 open_id,发第一条消息自动保存。
312
315
  - ✅ 启动/断开通知
313
316
  - 👌 处理中打 reaction,完成换 DONE
314
317
  - 💬 回复引用原消息
315
- - ⚡ 工具调用实时展示
316
- - 📋 Markdown 卡片渲染
318
+ - ⚡ 工具调用实时展示(首个工具调用即创建卡片,无需等待正文)
319
+ - 🌊 流式输出(互斥守卫 + 自适应节流 + 长间隔批处理)
320
+ - 📋 Markdown 卡片渲染(自动标题降级适配)
317
321
  - 📄 超长消息支持分段发送或写入云文档
322
+ - 🛡️ 文档写入容错(单表/批次失败不影响整篇文档)
323
+ - 🎯 格式指导注入(从源头优化输出质量)
318
324
  - 🖼 图片理解(支持富文本多图)
325
+ - 🧠 思考过程折叠面板(collapsible_panel,显示推理耗时)
326
+ - ⏱ 响应元数据(耗时、模型、token 数实时显示)
327
+ - 🖼 外部图片自动上传(下载 → 飞书 → 渲染,支持卡片和文档)
319
328
  - ⌨️ Slash 命令
320
- - ⏹ `/stop` 中断任务
329
+ - ⏹ `/stop` 中断任务(保留已有内容)
321
330
  - 👥 群聊 @ 触发
322
331
 
323
332
  ## Markdown 扩展语法
@@ -368,6 +377,97 @@ setup 不需要填 open_id,发第一条消息自动保存。
368
377
  \<u\> 显示为 <u>
369
378
  ```
370
379
 
380
+ ## 流式输出
381
+
382
+ 回复内容会以打字机效果逐字显示,无需等待完整输出。
383
+
384
+ 内部使用 FlushController:
385
+ - **互斥守卫** — 防止并发刷新冲突
386
+ - **自适应节流** — 根据上次刷新时间动态调整间隔
387
+ - **长间隔批处理** — 2 秒无新内容自动 flush 剩余缓冲
388
+
389
+ ### 配置
390
+
391
+ ```yaml
392
+ streaming:
393
+ enabled: true # 是否启用流式(默认 true)
394
+ mode: cardkit # update(message.patch)| cardkit | none
395
+ flush_interval_ms: 200 # 最小刷新间隔(毫秒)
396
+ thinking_enabled: false # 是否显示 Claude 思考过程
397
+ fallback_on_error: true # 失败时降级为一次性发送
398
+
399
+ # 卡片标题(所有模式生效)
400
+ card_title: Claude # 留空则不显示 header
401
+
402
+ # 图片自动解析(下载外部图片上传到飞书)
403
+ image_resolver:
404
+ enabled: true # 是否启用(默认 true)
405
+ ```
406
+
407
+ ### 模式说明
408
+
409
+ | 模式 | 说明 | 额外权限 |
410
+ |------|------|---------|
411
+ | `update` | 使用消息 patch API 模拟流式 | 无(默认即可用) |
412
+ | `cardkit` | 使用飞书 CardKit API(真正打字机效果) | `cardkit:card:write` |
413
+ | `none` | 禁用流式,等待完整输出后一次性发送 | 无 |
414
+
415
+ - `cardkit` 模式为默认模式,采用单卡片架构(对齐飞书官方 OpenClaw 方案),全程只有一个消息
416
+ - `cardkit` 模式使用飞书 SDK CardKit 客户端(自动 token 管理、结构化错误处理)
417
+ - `cardkit` 模式支持流式预览(summary)、工具状态栏实时展示(无需等待正文到来)、思考过程折叠面板、长内容自动溢出到云文档
418
+ - `cardkit` 模式关闭流式采用两步操作(settings + update),对齐官方最佳实践
419
+ - 中断时保留已有内容(正文 + 思考过程),不丢失已生成的回复
420
+ - `update` 模式使用消息 patch API 模拟流式,会发送独立的工具调用卡片
421
+ - 流式过程中如果内容超长,最终会自动写入云文档并回复链接
422
+
423
+ ### 中断
424
+
425
+ 流式输出过程中可以使用 `/stop` 中断,卡片会保留已生成的内容和思考过程。
426
+
427
+ ### 思考过程
428
+
429
+ 开启 `streaming.thinking_enabled: true` 后,Claude 的扩展思考过程会以折叠面板显示在回复上方:
430
+
431
+ - 流式期间显示 "⏳ 思考中..." 提示
432
+ - 完成后思考内容收起在折叠面板中,标题显示思考耗时(如 `💭 思考 3.2s`),点击可展开查看
433
+ - 超过 3000 字的思考内容自动截断
434
+ - 关闭时完全过滤思考内容,用户无感
435
+
436
+ ### 响应元数据
437
+
438
+ 每条回复底部自动显示耗时、模型和 token 用量:
439
+
440
+ ```
441
+ ⏱ 8.2s · claude-sonnet-4-6 · 1,234 tokens
442
+ ```
443
+
444
+ 仅显示在卡片消息中,云文档不追加。
445
+
446
+ ## 格式优化
447
+
448
+ ### 卡片自动优化
449
+
450
+ 发送到飞书卡片的 Markdown 会自动经过优化管线处理:
451
+
452
+ 1. **标题降级** — H1→H4, H2-H6→H5(飞书卡片只支持 H4/H5)
453
+ 2. **代码块保护** — 代码块内容不会被其他处理逻辑误解析
454
+ 3. **外部图片上传** — 自动下载外部图片并上传到飞书,替换为 `img_xxx` 格式(卡片和文档均支持)
455
+
456
+ ### 格式指导(System Prompt)
457
+
458
+ 默认启用,通过 Claude Code SDK 的 system prompt 注入飞书格式规范,从源头提升输出质量。整个会话只需注入一次,不重复消耗 token。
459
+
460
+ ```yaml
461
+ format_guide:
462
+ enabled: true # 是否启用(默认 true)
463
+ ```
464
+
465
+ **自定义格式指导:**
466
+
467
+ 编辑 `~/.larkcc/format-guide.md` 即可覆盖默认内容。格式指导内容是纯 Markdown,你可以根据实际需求增减规则。
468
+
469
+ 查看默认格式指导:`resources/format-guide.md`(随项目发布)。
470
+
371
471
  ## 消息类型支持
372
472
 
373
473
  | 类型 | 单聊 | 群聊 |
@@ -408,6 +508,25 @@ overflow:
408
508
  threshold: 2800 # 写文档阈值
409
509
  title_template: "{cwd} - {session_id} - {datetime}"
410
510
 
511
+ # 格式指导(从源头优化 Claude 输出质量)
512
+ format_guide:
513
+ enabled: true # 是否注入飞书格式要求到 prompt
514
+
515
+ # 卡片标题
516
+ card_title: Claude # 留空则不显示 header
517
+
518
+ # 流式输出配置
519
+ streaming:
520
+ enabled: true # 是否启用流式(默认 true)
521
+ mode: cardkit # update | cardkit | none
522
+ flush_interval_ms: 300 # 最小刷新间隔(毫秒)
523
+ thinking_enabled: false # 是否显示思考过程
524
+ fallback_on_error: true # 失败时降级
525
+
526
+ # 图片自动解析(下载外部图片上传到飞书)
527
+ image_resolver:
528
+ enabled: true # 是否启用(默认 true)
529
+
411
530
  commands:
412
531
  deploy: "部署到测试环境"
413
532
 
@@ -429,12 +548,8 @@ reaction:
429
548
  done: DONE # 完成
430
549
  error: OnIt # 出错
431
550
 
432
- # 思考状态词(随机显示)
433
- thinking_words:
434
- - "💭 思考中..."
435
- - "🔍 分析中..."
436
- - "💻 编码中..."
437
- # ... 更多状态词
551
+ # 卡片标题
552
+ card_title: Claude
438
553
 
439
554
  profiles:
440
555
  mybot:
@@ -489,9 +604,13 @@ profiles:
489
604
 
490
605
  - 支持 Markdown 格式(标题、代码块、列表等)
491
606
 
492
- **无效图片过滤:**
607
+ **图片处理:**
493
608
 
494
- 当内容包含 `blob:` 格式的无效图片 URL(如 websearch 返回的临时图片)时,系统会自动过滤并在消息末尾提示。
609
+ - `blob:` 格式的无效图片 URL 自动过滤
610
+ - 外部图片(`https://`)自动下载并上传到飞书,替换为内部 `img_xxx` 格式
611
+ - 上传失败的图片降级为链接,不影响整体流程
612
+ - 图片大小限制 10MB,下载超时 10 秒
613
+ - 卡片和云文档均支持图片渲染
495
614
 
496
615
  **自动清理:**
497
616
 
@@ -536,19 +655,45 @@ overflow:
536
655
 
537
656
  ### 权限
538
657
 
658
+ 权限按功能分组,建议全部开通:
659
+
660
+ #### 基础消息(必开)
661
+
539
662
  | 权限 | 用途 |
540
663
  |------|------|
541
- | `im:message` | 基础消息(含下载消息中的文件) |
542
- | `im:message:send_as_bot` | 发送消息 |
543
- | `im:message.p2p_msg:readonly` | 接收私聊 |
664
+ | `im:message` | 基础消息(含下载消息中的图片、文件) |
665
+ | `im:message:send_as_bot` | 以机器人身份发送消息、回复消息、更新消息(**流式输出也依赖此权限**) |
666
+ | `im:message.p2p_msg:readonly` | 接收私聊消息 |
544
667
  | `im:message.group_at_msg:readonly` | 接收群 @ 消息 |
545
- | `im:message.reactions:write_only` | 打 reaction |
546
- | `cardkit:card:write` | 发送卡片 |
547
- | `docx:document` | 创建/编辑云文档(超长消息写入) |
548
- | `drive:file` | 删除云空间文件(清理旧文档) |
668
+ | `im:message.reactions:write_only` | 打 reaction 表情(处理中/完成/出错状态) |
669
+
670
+ #### 卡片与富文本
671
+
672
+ | 权限 | 用途 |
673
+ |------|------|
674
+ | `cardkit:card:write` | 发送交互式卡片、CardKit 流式输出 |
549
675
 
550
676
  > 💡 `im:message` 权限已包含下载消息中资源文件(图片、文件)的能力
551
677
 
678
+ #### 云文档(超长消息写入)
679
+
680
+ | 权限 | 用途 |
681
+ |------|------|
682
+ | `docx:document` | 创建/编辑云文档(`overflow.mode: document` 时需要) |
683
+ | `drive:file` | 删除云空间文件(自动清理旧文档时需要) |
684
+
685
+ > 💡 不使用文档模式时,这两个权限可以不开
686
+
687
+ #### 开通步骤
688
+
689
+ 1. 进入 [飞书开发者后台](https://open.feishu.cn/) → 选择你的应用
690
+ 2. 左侧菜单 **权限管理** → 搜索并开通上述权限
691
+ 3. 点击 **权限管理** 页面上方的 **权限配置** → 批量开通
692
+ 4. 创建新版本 → 申请发布(企业自建应用管理员审批即可)
693
+ 5. 发布成功后权限生效
694
+
695
+ > ⚠️ 权限变更后需要重新发布应用版本,已运行的 larkcc 需要重启
696
+
552
697
  ### 事件订阅
553
698
 
554
699
  使用**长连接** → 订阅 `im.message.receive_v1`
@@ -748,7 +893,7 @@ You: [Error screenshot] How to fix this
748
893
  You: [Rich text with multiple images] Analyze these images
749
894
  ```
750
895
 
751
- > **Note:** Invalid image URLs (like `blob:` URLs from websearch) are automatically filtered.
896
+ > **Note:** Invalid image URLs (like `blob:` URLs from websearch) are automatically filtered. External images are automatically downloaded and uploaded to Feishu for proper rendering.
752
897
 
753
898
  ## File Support
754
899
 
@@ -821,21 +966,37 @@ larkcc -p mybot # Use specified bot
821
966
 
822
967
  ### Permissions
823
968
 
969
+ #### Basic Messaging (Required)
970
+
824
971
  | Permission | Purpose |
825
972
  |------------|---------|
826
- | `im:message` | Basic message (including file download) |
827
- | `im:message:send_as_bot` | Send messages |
973
+ | `im:message` | Basic message (including image/file download) |
974
+ | `im:message:send_as_bot` | Send/reply/update messages (**streaming also depends on this**) |
828
975
  | `im:message.p2p_msg:readonly` | Receive direct messages |
829
976
  | `im:message.group_at_msg:readonly` | Receive group @ messages |
830
- | `im:message.reactions:write_only` | Add reactions |
831
- | `cardkit:card:write` | Send cards |
832
- | `docx:document` | Create/edit cloud documents |
833
- | `drive:file` | Delete cloud files |
977
+ | `im:message.reactions:write_only` | Add reactions (processing/done/error status) |
978
+
979
+ #### Cards
980
+
981
+ | Permission | Purpose |
982
+ |------------|---------|
983
+ | `cardkit:card:write` | Send interactive cards, CardKit streaming |
984
+
985
+ #### Cloud Documents (for overflow mode)
986
+
987
+ | Permission | Purpose |
988
+ |------------|---------|
989
+ | `docx:document` | Create/edit cloud documents (when `overflow.mode: document`) |
990
+ | `drive:file` | Delete cloud files (auto-cleanup of old documents) |
834
991
 
835
992
  ### Event Subscription
836
993
 
837
994
  Use **Long Connection** → Subscribe to `im.message.receive_v1`
838
995
 
996
+ ## 致谢
997
+
998
+ CardKit 流式卡片实现参考了飞书官方 [openclaw-lark](https://github.com/larksuite/openclaw-lark) 项目的 API 用法与架构设计。
999
+
839
1000
  ## Contributing
840
1001
 
841
1002
  See [CONTRIBUTING.md](CONTRIBUTING.md)
package/dist/agent.d.ts CHANGED
@@ -4,6 +4,7 @@ export interface ImageInput {
4
4
  base64: string;
5
5
  mediaType: string;
6
6
  }
7
+ export type RunAgentResult = "completed" | "aborted";
7
8
  export declare function runAgent(prompt: string, cwd: string, config: LarkccConfig, client: lark.Client, chatId: string, rootMsgId: string, images?: ImageInput[], abortSignal?: AbortSignal, // 可选中断信号
8
- profile?: string): Promise<void>;
9
+ profile?: string): Promise<RunAgentResult>;
9
10
  export declare function ensureEnv(): void;