@leviyuan/lodestar 0.1.12 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -21
- package/package.json +1 -1
- package/src/instructions.ts +9 -18
- package/src/session.ts +11 -8
package/README.md
CHANGED
|
@@ -16,18 +16,24 @@ AI 不是帮手,是倍率。它放大的不是体力,是你——你的直觉、
|
|
|
16
16
|
|
|
17
17
|
## 你会得到什么
|
|
18
18
|
|
|
19
|
-
- 🌊
|
|
20
|
-
- 🧠
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
19
|
+
- 🌊 **真·流式卡片**:token 级渲染同一张卡,不刷屏
|
|
20
|
+
- 🧠 **思考透明**:thinking 流式 + turn 后自动收起
|
|
21
|
+
- 🔧 **工具调用折叠**:每次工具一格面板,折起概述/展开细节
|
|
22
|
+
- 🔐 **审批就地完成**:工具卡上三按钮,不破坏时序
|
|
23
|
+
- ❓ **结构化追问**:Ask 选项行 + 自由文本回答 + 多题翻页
|
|
24
|
+
- ⌨️ **Type-ahead 不打断**:连珠炮全收,排队下一轮合并处理
|
|
25
|
+
- 🔢 **合并消息加序号**:`[#N]\n` 前缀让模型看清独立边界
|
|
26
|
+
- ⏳ **排队反应可见**:消息进队列加 ⏳,消化/取消自动清/换 ❌
|
|
27
|
+
- 📨 **mid-turn 切新卡**:中途新消息 → 下一 tool 边界切新卡续写
|
|
28
|
+
- ⏰ **定时唤醒可见化**:Cron / ScheduleWakeup 到点自开新卡
|
|
29
|
+
- 📊 **footer 实时指标**:`✅ ⏱时长 · 📊上下文% · 💰本轮成本`
|
|
30
|
+
- 📦 **`hi` 弹控制台**:跨群项目、上下文%、订阅额度一屏看完
|
|
31
|
+
- 📎 **图文双向互传**:`[file:]` 进、`[[send:]]` 出,路径白名单
|
|
32
|
+
- 📲 **关键时刻加急**:Ask / 审批 / done 锁屏推送,定时不打扰
|
|
33
|
+
- 🛑 **`stop` 软打断**:取消当前 turn + 清队列,子进程保活
|
|
34
|
+
- 🗂 **多项目并发**:一个 daemon 持 N 群 ↔ N session
|
|
35
|
+
- 🔄 **自动 resume**:重启自动续接,session_id 落盘不丢
|
|
36
|
+
- 🛡 **systemd 守护级**:WS watchdog + 单 PID + alive marker
|
|
31
37
|
|
|
32
38
|
## 怎么用
|
|
33
39
|
|
|
@@ -55,15 +61,26 @@ AI 不是帮手,是倍率。它放大的不是体力,是你——你的直觉、
|
|
|
55
61
|
|
|
56
62
|
### 1. 准备
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
**机器**:能常跑后台进程的 Linux/macOS(自家服务器、闲置 NAS、树莓派均可)。
|
|
65
|
+
|
|
66
|
+
**运行时**:[Bun](https://bun.sh) ≥ 1.0。
|
|
67
|
+
|
|
68
|
+
**Claude Code**:装好且能跑 —— 详见[官方文档](https://docs.anthropic.com/en/docs/claude-code)。**强烈建议用 claude.ai 账号 OAuth 登录**(`claude auth login`),而不是 `ANTHROPIC_API_KEY`:Cron / ScheduleWakeup / `/schedule` 等定时唤醒工具只在 OAuth 模式下注册。
|
|
69
|
+
|
|
70
|
+
**飞书自建应用**:去[飞书开放平台](https://open.feishu.cn/app)→ 创建企业自建应用,然后:
|
|
71
|
+
|
|
72
|
+
1. **添加机器人能力**:左侧"添加应用能力"→"机器人"启用。
|
|
73
|
+
2. **配置权限**(权限管理 → API 权限):
|
|
74
|
+
- 消息:`im:message:send_as_bot` `im:message` `im:chat:readonly` `im:resource`
|
|
75
|
+
- 加急:`im:message.urgent`(锁屏推送)
|
|
76
|
+
- 卡片:`cardkit:card:read` `cardkit:card:write` `cardkit:card.element:read` `cardkit:card.element:write` `cardkit:card.settings:read` `cardkit:card.settings:write`
|
|
77
|
+
3. **订阅事件**(事件与回调 → 事件订阅):
|
|
78
|
+
- 订阅方式选 **长连接**(WebSocket,不需要公网回调地址)
|
|
79
|
+
- 添加事件 `im.message.receive_v1`(接收群消息)
|
|
80
|
+
- 添加事件 `card.action.trigger`(卡片按钮回调)
|
|
81
|
+
4. **发布版本**(版本管理与发布)→ 创建版本 → 审批通过 / 自审通过 → 上线。**没发版的应用不会收到事件**,这一步常被忘记。
|
|
82
|
+
5. **拿凭据**:凭据与基础信息页拷 `App ID`(`cli_xxxxxxxxxx`)和 `App Secret`,下一步写到 `config.toml`。
|
|
83
|
+
6. **拉机器人进群**:想用的飞书群 → 群设置 → 群机器人 → 添加机器人 → 选你的应用。**群名要等于 `~/` 下的项目目录名**,daemon 用这个绑定群 ↔ Claude session。
|
|
67
84
|
|
|
68
85
|
### 2. 配置
|
|
69
86
|
|
package/package.json
CHANGED
package/src/instructions.ts
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Daemon ↔ model I/O contracts. Appended to claude's system prompt on
|
|
3
|
+
* every headless launch via `--append-system-prompt`. Three rules:
|
|
4
|
+
* inbound file marker, multi-content boundary marker, outbound file
|
|
5
|
+
* marker. Anything beyond pure I/O semantics (environment description,
|
|
6
|
+
* UX conventions, identity binding) was stripped 2026-05-16 — the
|
|
7
|
+
* model handles conversational flow natively, doesn't need to be told.
|
|
7
8
|
*/
|
|
8
9
|
export const CHANNEL_INSTRUCTIONS = [
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
'',
|
|
12
|
-
'Conventions for every turn:',
|
|
13
|
-
'- Open with one short acknowledgement so the user sees you started.',
|
|
14
|
-
'- Stream your conclusion before the turn ends; never end on a silent tool call. The card is your voice.',
|
|
15
|
-
'- For long work, drop progress sentences between tool calls so the user is not staring at a loading dot.',
|
|
16
|
-
'',
|
|
17
|
-
'Inbound user messages may carry a [file: /abs/path] hint when the user sent an image or attachment in Feishu. Read those files when relevant.',
|
|
18
|
-
'',
|
|
19
|
-
'To send a local file or image back to the user in this Feishu group, write the marker `[[send: /abs/path]]` (absolute path) anywhere in your reply, preferably on its own line at the end. The daemon strips every marker from the displayed card and posts the file as a separate Feishu message. Emit the marker only when the user asked for a file or when delivering a generated artifact (screenshot, diagram, exported doc) — not for arbitrary paths.',
|
|
20
|
-
'',
|
|
21
|
-
'The group name equals the working directory under $HOME and equals the Lodestar session name. Treat that binding as load-bearing — do not rename or move the directory.',
|
|
10
|
+
'- Text prefixed with `[file: /abs/path]` means a file is attached at that path; read it when relevant.',
|
|
11
|
+
'- A content block wrapped in `<u>...</u>` is an independent message — treat each `<u>` element in a multi-content turn as a separate input, even when their texts concatenate visually (e.g. `<u>1</u><u>45</u>` is two messages, not the number `145`).',
|
|
12
|
+
'- Write `[[send: /abs/path]]` anywhere in your reply (preferably on its own line) to deliver that file as a separate message. The marker is stripped from the displayed text. Emit only when the user asked for a file or you are delivering a generated artifact.',
|
|
22
13
|
].join('\n')
|
package/src/session.ts
CHANGED
|
@@ -511,14 +511,17 @@ export class Session {
|
|
|
511
511
|
const wasBusy = this.currentTurn !== null || this.openingTurn
|
|
512
512
|
this.pendingUserMessageCount++
|
|
513
513
|
this.lastUserOpenId = userOpenId
|
|
514
|
-
// When
|
|
515
|
-
// user turn
|
|
516
|
-
//
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
|
|
514
|
+
// When the SDK will merge this msg with siblings into a multi-
|
|
515
|
+
// content user turn, wrap it in `<u>...</u>` so the model sees a
|
|
516
|
+
// structural boundary it actually attends to. Tried U+001E
|
|
517
|
+
// (ASCII Record Separator) first — invisible and theoretically
|
|
518
|
+
// perfect, but Anthropic's tokenizer effectively drops control
|
|
519
|
+
// chars and `<u>1</u><u>45</u>` became "145" to the model
|
|
520
|
+
// (2026-05-16 accumulator test). HTML-tag wrap is visible but
|
|
521
|
+
// models parse `<tag>` boundaries very reliably from training.
|
|
522
|
+
// Solo (eager-open) msgs don't get wrapped — no sibling, no
|
|
523
|
+
// merge, no need. Contract declared in CHANNEL_INSTRUCTIONS.
|
|
524
|
+
const wireText = wasBusy ? `<u>${text}</u>` : text
|
|
522
525
|
this.proc!.sendUserText(wireText, files)
|
|
523
526
|
if (wasBusy && msgId) {
|
|
524
527
|
// Hold the slot in the map even if the API call hasn't returned
|