@leviyuan/lodestar 0.16.6 → 0.17.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.
Files changed (22) hide show
  1. package/README.md +61 -5
  2. package/dist/dsh-bridge.js +396 -0
  3. package/dist/lodestar-agent.js +10 -10
  4. package/dist/lodestar-setup.js +1 -1
  5. package/dist/lodestar-stop.js +2 -2
  6. package/dist/lodestar-update.js +3 -3
  7. package/dist/lodestar.js +164 -158
  8. package/package.json +250 -5
  9. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/LICENSE +0 -0
  10. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/Readme.md +0 -0
  11. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/esm.mjs +0 -0
  12. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/index.js +0 -0
  13. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/lib/argument.js +0 -0
  14. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/lib/command.js +0 -0
  15. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/lib/error.js +0 -0
  16. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/lib/help.js +0 -0
  17. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/lib/option.js +0 -0
  18. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/lib/suggestSimilar.js +0 -0
  19. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/package-support.json +0 -0
  20. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/package.json +0 -0
  21. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/typings/esm.d.mts +0 -0
  22. /package/node_modules/{commander → speech-rule-engine/node_modules/commander}/typings/index.d.ts +0 -0
package/README.md CHANGED
@@ -10,14 +10,16 @@ AI 不是帮手,是倍率。它放大的不是体力,是你 —— 你的直觉
10
10
 
11
11
  ---
12
12
 
13
- 在飞书群里使用 Codex 和 Claude Code。每个群对应一个项目目录和会话,回复、工具调用、提问和后台任务通过飞书卡片展示。
13
+ 在飞书群里使用 Codex、Claude Code DeepSeek Harness。每个群对应一个项目目录和会话,回复、工具调用、提问和后台任务通过飞书卡片展示。
14
14
 
15
- 支持 Codex、GLM Coding Plan、DeepSeek 和 Claude native。账号、模型与 effort 可在群里切换,选择按群保存。
15
+ 支持 Codex、GLM Coding Plan、DeepSeek、OpenRouter、Claude native DeepSeek Harness 原生后端。账号、模型与 effort 可在群里切换,选择按群保存。
16
16
 
17
17
  ## 安装
18
18
 
19
19
  支持 Windows、macOS 和 Linux,需要 Node.js ≥ 18.15。Bun 用于源码开发和构建。
20
20
 
21
+ DeepSeek Harness 子进程另需 Node 22.19+(22.x)或 Node 24+;可通过其账号配置的 `bin` 指定 Node 可执行文件。
22
+
21
23
  ```bash
22
24
  npm i -g @leviyuan/lodestar
23
25
  lodestar-setup
@@ -88,7 +90,7 @@ GLM 的套餐与用量显示在 `hi` 控制台,回复底部也会显示当前
88
90
 
89
91
  主 Agent 可以通过 `lodestar-agent` 查询实时身份,再把任务交给一个或多个模型。同一个任务选择多个身份时并发执行;后续追问可继续使用各模型的原生会话。
90
92
 
91
- 被调用的 Agent 可以编辑文件、执行命令、使用 MCP 和 Skill,也可以继续委派。它们与主 Agent 共享工作区,修改会立即可见。运行状态和结果通过卡片展示;需要用户输入时暂停,由主 Agent 回填答案后继续。委派产生的会话不会混入主群的 `rs` 历史列表。
93
+ 被调用的 Agent 可以编辑文件、执行命令、使用 MCP 和 Skill;委派只允许一层,被调用的 Agent 不能继续派工。它们与主 Agent 共享工作区,修改会立即可见。运行状态和结果通过卡片展示;需要用户输入时暂停,由主 Agent 回填答案后继续。委派产生的会话不会混入主群的 `rs` 历史列表。
92
94
 
93
95
  `lodestar-agent` 只能在 Lodestar 管理的 Agent 进程里调用。命令用法见 [Agent Skill](src/agent-skill.ts)。
94
96
 
@@ -128,9 +130,63 @@ tools = "Read,Write,Edit,Bash,Glob,Grep"
128
130
  bin = "/abs/path/to/claude-wrapper" # 可选的 Claude 可执行文件
129
131
  ```
130
132
 
131
- 账号配置使用 `[token_source.glm]`、`[token_source.deepseek]` 等节。GLM 和 DeepSeek 也可以从本机 Claude settings 中识别;识别后由对应 Token Source 注入凭据,避免不同账号的环境变量串用。项目的工具限制只作用于主会话,委派 Agent 使用完整工具集。
133
+ 账号配置使用 `[token_source.glm]`、`[token_source.deepseek]` 等节。GLM、DeepSeekOpenRouter 也可以从本机 Claude settings 中识别;识别后由对应 Token Source 注入凭据,避免不同账号的环境变量串用。项目的工具限制只作用于主会话,委派 Agent 使用完整工具集。
134
+
135
+ `md` / `model` 首页用独立分组展示 `claude`、`codex`、`dsh`:每组都有浅蓝标题栏、边框和 `Agent · 名称` 标题,默认展开,Token Source 行放在组内。选账号后进入模型和 effort 选择。除 OpenRouter 的默认九项外,各来源默认显示接口目录。接口中的模型使用「隐藏 / 显示」,只调整面板可见性,接口新增模型仍会出现。
136
+
137
+ 所有 Token Source 都提供「补录模型」,用于登记接口列表外的模型,并支持「删除」补录记录。记录保存在 `custom_models`;支持端点验证的来源会先验证,无法确认模型能力或 effort 的记录明确显示 `MISS`,不会凭空赋予运行能力。接口后来收录同名模型时自动转为接口项,不重复显示。删除补录记录需要先切换仍选用它的会话;默认模型和辅助模型中指向已删除补录项的配置也会清理。
138
+
139
+ 所有对话卡的模型标识固定为 `claude · 模型名/effort`、`codex · 模型名/effort` 或 `dsh · 模型名/effort`。窗口额度沿用原来的紧凑格式,如 `4.1h·7%·[6.9d·17%]`(重置倒计时与已用百分比);余额显示 `余额 $12.34` 或 `余额 ¥12.34`。缺失或读取失败显示 `MISS`。
140
+
141
+ OpenRouter 通过 Claude Agent SDK 运行。在群内发送 `openrouter-setup <api_key>`,再通过 `model` 面板选择模型和 effort;自建兼容端点用 `openrouter-setup <base_url> <api_key>`。也可在配置文件中添加:
142
+
143
+ ```toml
144
+ [token_source.openrouter]
145
+ agent = "claude"
146
+ api_key = "填写自己的 OpenRouter API key"
147
+ # base_url = "https://openrouter.ai/api" # SDK 自动追加 /v1/messages
148
+ # model = "anthropic/claude-fable-5.1" # 可选:默认运行模型,须获账号目录确认
149
+ # effort = "max" # 可选:仅覆盖默认运行模型的档位
150
+ # models = "anthropic/claude-fable-5.1,moonshotai/kimi-k3" # 可选:自定义可选列表
151
+ # slots = "haiku=anthropic/claude-fable-5.1" # 可选:辅助任务模型,须获账号目录确认且使用相同的 effort 参数模式
152
+ ```
153
+
154
+ OpenRouter 默认可选列表按 [Arena Agent Labs 榜单](https://arena.ai/leaderboard/agent?rankBy=labs) 的 2026-09-08 快照设置:前 12 家各取排名最高的模型,排除 OpenAI、Z.ai / GLM、DeepSeek,保留以下 9 项。
155
+
156
+ | Labs 排名 | 厂商 | OpenRouter 模型 | 默认档位 |
157
+ | --- | --- | --- | --- |
158
+ | 1 | Anthropic | `anthropic/claude-fable-5.1` | max |
159
+ | 3 | Moonshot | `moonshotai/kimi-k3` | max |
160
+ | 4 | Tencent | `tencent/hy4-preview` | high |
161
+ | 7 | Google | `google/gemini-3.8-flash` | high |
162
+ | 8 | SpaceXAI | `x-ai/grok-4.5` | high |
163
+ | 9 | Alibaba | `qwen/qwen3.8-max-0902` | xhigh |
164
+ | 10 | Meta | `meta/muse-spark-1.2` | xhigh |
165
+ | 11 | Xiaomi | `xiaomi/mimo-v2.5-pro` | 模型默认 |
166
+ | 12 | MiniMax | `minimax/minimax-m3` | 模型默认 |
167
+
168
+ 在 `md` / `model` → claude → OpenRouter 中,点「显示模型」选择账号目录中的模型,点每行「隐藏」移出面板列表。可见性自动持久化,不改当前运行模型;全部隐藏后也能继续显示或补录。未配置 `models` 时才使用上述九项;`models = ""` 明确表示没有已显示的接口模型,刷新或重启不会补回默认项。榜单变化不会覆盖用户维护的列表。
169
+
170
+ 候选目录来自 `/api/v1/models/user`,按账号供应商和隐私设置筛选,仅纳入支持文本和工具调用的交互模型;OpenAI、GLM、DeepSeek 及无法保证厂商范围的自动路由不会出现在添加候选中。目录刷新失败显示 `MISS`。显式配置但已下线的模型保留为可删除的 `MISS` 项。
171
+
172
+ effort 按上游声明提供。小米、MiniMax 等没有 effort 选择器的模型使用「模型默认」,实际请求不携带 effort 参数。两种参数模式之间切换时,空闲进程会保存原生会话并在下一轮用新环境恢复;相同模式下继续使用 SDK 热切换。未配置 `model` 时需要通过面板明确选择运行模型。
173
+
174
+ OpenRouter 余额来自 `/api/v1/credits`,按 `total_credits - total_usage` 计算,卡片仅显示 `余额 $…`,不附加 Key 限额或累计消费。现有 Key 已实测返回 200;接口权限和错误以实际响应为准。SDK 使用 Bearer token,并显式清空 `ANTHROPIC_API_KEY`。OpenRouter 官方的兼容保证限于 Anthropic 第一方供应商;上述九项的工具调用、模型与档位参数已做真实验证,验证范围见 [后端说明](docs/claude-agent-backend.md)。
175
+
176
+ DeepSeek Harness 使用独立的 `[token_source.deepseek-harness]` 账号与原生会话。在群内发送 `deepseek-harness-setup <api_key>`,再通过 `model` 面板选择该来源即可启用。自建端点用 `deepseek-harness-setup <base_url> <api_key>`;这里使用原生 API 根地址,不带 `/anthropic`。
177
+
178
+ DSH 也支持 GLM Coding Plan:已有 `[token_source.glm]` 时自动复用该账号,在 `md` → dsh → GLM Coding Plan 中选择模型。独立凭据用 `dsh-glm-setup [base_url] <api_key>`,配置节为 `[token_source.dsh-glm]`。它调用 Coding Plan 的 OpenAI 端点,复用 GLM 额度查询;不经过 OpenRouter 或 Claude SDK。账号接口返回的模型都进入列表,当前 DSH 原生目录未声明能力的项显示 `MISS`,也可隐藏和恢复。
179
+
180
+ ```toml
181
+ [token_source.deepseek-harness]
182
+ agent = "dsh"
183
+ api_key = "填写自己的 API key"
184
+ # bin = "/abs/path/to/node" # 可选:运行 DSH 的 Node 可执行文件
185
+ # model = "deepseek-v4-pro" # 可选:必须存在于 DSH 返回的模型目录
186
+ # effort = "high" # 可选:必须是该模型支持的推理强度
187
+ ```
132
188
 
133
- 手动修改配置后需重启 daemon;群内账号启用和模型补录会自行重载相关配置。模型路由、配置优先级和双后端差异见 [后端说明](docs/claude-agent-backend.md)。
189
+ 手动修改配置后需重启 daemon;群内账号启用和模型补录会自行重载相关配置。模型路由、配置优先级和后端差异见 [后端说明](docs/claude-agent-backend.md)。
134
190
 
135
191
  ## 开发
136
192
 
@@ -0,0 +1,396 @@
1
+ // src/dsh-bridge.ts
2
+ import { createInterface } from "node:readline";
3
+ import { randomUUID } from "node:crypto";
4
+ import { realpath } from "node:fs/promises";
5
+ import { createUserMessage, ReasoningEffortId } from "@deepseek-ai/dsh-llm";
6
+ import { SessionLogOffset } from "@deepseek-ai/dsh-session";
7
+ var name = "lodestar-bridge";
8
+ var inject = ["agents", "llm", "agentDefaultModel", "sessionPersistence", "tools", "systemPrompt", "userQuestions", "compaction", "shellEnv", "attachments", "sdkAppStartup"];
9
+ var PROVIDER = process.env.LODESTAR_DSH_PROVIDER ?? "deepseek-official";
10
+ if (!["deepseek-official", "zai-coding-cn", "zai"].includes(PROVIDER))
11
+ throw new Error(`Unsupported DSH provider: ${PROVIDER}`);
12
+ var DELEGATION_TOOLS = ["subagent", "subagent_fork", "workflow", "ralph", "send_message"];
13
+ function apply(ctx) {
14
+ let root;
15
+ let rootId;
16
+ let initialized = false;
17
+ let closing = false;
18
+ const lifecycle = new AbortController;
19
+ let opening;
20
+ let selected;
21
+ let turnRoute;
22
+ let lastEnd;
23
+ let activeTurn = 0;
24
+ let turnFailure;
25
+ let settledSeq = -1;
26
+ let settling;
27
+ const questions = new Map;
28
+ const notify = (method, params) => {
29
+ process.stdout.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + `
30
+ `);
31
+ };
32
+ const fail = (error) => {
33
+ const message = error instanceof Error ? error.message : String(error);
34
+ process.stderr.write(`lodestar-dsh: ${message}
35
+ `);
36
+ notify("failure", { message });
37
+ };
38
+ const live = () => {
39
+ if (!root || closing)
40
+ throw new Error("DSH session is not open");
41
+ return root.agent;
42
+ };
43
+ const delegationContext = process.env.DSH_LODESTAR_AGENT_CONTEXT;
44
+ delete process.env.DSH_LODESTAR_AGENT_CONTEXT;
45
+ ctx.shellEnv.register({
46
+ name: "lodestar-agent",
47
+ variables: { DSH_LODESTAR_AGENT_CONTEXT: { description: "Lodestar delegation context for the current root agent only." } },
48
+ resolve: (execution) => delegationContext && execution.agent === root?.agent ? { DSH_LODESTAR_AGENT_CONTEXT: delegationContext } : {}
49
+ });
50
+ const settle = () => {
51
+ if (root?.agent.status === "idle" && turnFailure && lastEnd?.data.turn !== activeTurn) {
52
+ const message = turnFailure;
53
+ turnFailure = undefined;
54
+ fail(new Error(`DSH turn could not be committed: ${message}`));
55
+ return;
56
+ }
57
+ if (settling || !lastEnd || !root || root.agent.status !== "idle" || lastEnd.seq <= settledSeq)
58
+ return;
59
+ const end = lastEnd;
60
+ settling = ctx.sessionPersistence.flush().then(() => {
61
+ settledSeq = end.seq;
62
+ notify("settled", { sessionId: rootId, event: end });
63
+ }).catch((error) => {
64
+ settledSeq = end.seq;
65
+ fail(error);
66
+ }).finally(() => {
67
+ settling = undefined;
68
+ settle();
69
+ });
70
+ };
71
+ ctx.on("session/event", (session, event) => {
72
+ notify("session.event", { sessionId: session.id, event });
73
+ if (session.id === rootId && event.type === "turn/start") {
74
+ turnRoute = selected;
75
+ if (root)
76
+ Object.assign(root.agent.options, selected);
77
+ activeTurn = event.data.turn;
78
+ turnFailure = undefined;
79
+ }
80
+ if (session.id === rootId && event.type === "turn/end") {
81
+ lastEnd = event;
82
+ queueMicrotask(settle);
83
+ }
84
+ });
85
+ ctx.on("agent/assistant-stream", ({ agent, frame }) => {
86
+ notify("assistant.frame", { sessionId: agent.session.id, frame });
87
+ });
88
+ ctx.on("agent/status", ({ agent, status }) => {
89
+ notify("session.status", { sessionId: agent.session.id, status });
90
+ if (agent.session.id === rootId && status === "idle")
91
+ settle();
92
+ });
93
+ ctx.on("agent/error", ({ agent, error }) => {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ if (agent.session.id === rootId)
96
+ turnFailure = message;
97
+ notify("agent.error", { sessionId: agent.session.id, message });
98
+ });
99
+ ctx.on("agent/created", ({ agent }) => {
100
+ if (agent.session.id === rootId)
101
+ return;
102
+ agent.ctx.tools.restrict({ deny: DELEGATION_TOOLS });
103
+ notify("subagent.started", { sessionId: agent.session.id, parentSessionId: agent.session.header.parentSession });
104
+ });
105
+ ctx.on("agent/disposed", ({ agent }) => {
106
+ if (agent.session.id !== rootId)
107
+ notify("subagent.disposed", { sessionId: agent.session.id });
108
+ });
109
+ ctx.on("approval/request", async () => "allowed-once");
110
+ ctx.on("user-questions/request", (request) => {
111
+ if (request.agent !== root?.agent || closing)
112
+ throw new Error("DSH question is not owned by this session");
113
+ const id = randomUUID();
114
+ return new Promise((resolve, reject) => {
115
+ const abort = () => {
116
+ questions.delete(id);
117
+ notify("question.cancelled", { id });
118
+ reject(new Error("DSH question cancelled"));
119
+ };
120
+ if (request.signal?.aborted)
121
+ return abort();
122
+ questions.set(id, {
123
+ resolve: (answer) => {
124
+ request.signal?.removeEventListener("abort", abort);
125
+ resolve(answer);
126
+ },
127
+ reject: (error) => {
128
+ request.signal?.removeEventListener("abort", abort);
129
+ reject(error);
130
+ }
131
+ });
132
+ request.signal?.addEventListener("abort", abort, { once: true });
133
+ notify("question", { id, questions: request.questions });
134
+ });
135
+ });
136
+ async function catalog() {
137
+ const models = await ctx.llm.listModels(PROVIDER);
138
+ if (!models.length)
139
+ throw new Error("DSH model catalog is empty");
140
+ return Promise.all(models.map(async (model) => {
141
+ const info = await ctx.llm.resolveModelInfo(PROVIDER, model.id);
142
+ if (!info.reasoning?.efforts.length || !info.reasoning.defaultEffort) {
143
+ throw new Error(`DSH model ${model.id} has no reasoning catalog/default`);
144
+ }
145
+ return {
146
+ model: model.id,
147
+ display: model.name,
148
+ efforts: info.reasoning.efforts.map((e) => e.id),
149
+ defaultEffort: info.reasoning.defaultEffort,
150
+ contextWindow: info.context?.contextWindow ?? null,
151
+ isDefault: ctx.agentDefaultModel.currentSelection().provider === PROVIDER && ctx.agentDefaultModel.currentSelection().model === model.id
152
+ };
153
+ }));
154
+ }
155
+ async function route(model, effort) {
156
+ const info = (await catalog()).find((entry) => entry.model === model);
157
+ if (!info || !info.efforts.some((e) => e === effort))
158
+ throw new Error(`DSH model/effort unavailable: ${model}/${effort}`);
159
+ return ctx.llm.resolveCallConfig({ provider: PROVIDER, model, reasoningEffort: ReasoningEffortId(effort) });
160
+ }
161
+ async function open(input) {
162
+ if (root || rootId)
163
+ throw new Error("DSH process already owns a session");
164
+ if (await realpath(input.cwd) !== await realpath(process.cwd()))
165
+ throw new Error("DSH workspace differs from process cwd");
166
+ selected = await route(input.model, input.effort);
167
+ turnRoute = selected;
168
+ const setup = (agentCtx, agent) => {
169
+ agentCtx.on("system-prompt/assemble", async (_assembly, context, next) => {
170
+ const assembly = await next();
171
+ if (context.scope !== agent)
172
+ return assembly;
173
+ return { ...assembly, variables: { ...assembly.variables, provider: turnRoute.provider, model: turnRoute.model } };
174
+ });
175
+ agentCtx.on("agent/request", async (payload, next) => {
176
+ const resolved = await next();
177
+ return payload.agent === agent ? { ...resolved, ...turnRoute } : resolved;
178
+ });
179
+ if (!input.allowDelegation)
180
+ agentCtx.tools.restrict({ deny: DELEGATION_TOOLS });
181
+ if (input.allowedTools)
182
+ agentCtx.tools.restrict({ allow: input.allowedTools });
183
+ if (input.developerInstructions) {
184
+ agentCtx.systemPrompt.section({ name: "lodestar:instructions", order: 999, text: input.developerInstructions });
185
+ }
186
+ Object.assign(agent.options, selected);
187
+ };
188
+ const launch = input.launch;
189
+ if (!["fresh", "resume", "fork"].includes(launch.kind))
190
+ throw new Error("unknown DSH launch kind");
191
+ if (launch.kind !== "fresh") {
192
+ if (launch.source.provider !== "dsh" || launch.source.cwd !== input.cwd)
193
+ throw new Error("DSH source provider/cwd mismatch");
194
+ const snapshot = await ctx.sessionPersistence.stat(launch.source.sessionId);
195
+ if (!snapshot || !snapshot.header.cwd || await realpath(snapshot.header.cwd) !== await realpath(input.cwd)) {
196
+ throw new Error("DSH stored session missing or belongs to another workspace");
197
+ }
198
+ }
199
+ rootId = launch.kind === "resume" ? launch.source.sessionId : `session-${randomUUID()}`;
200
+ lastEnd = undefined;
201
+ try {
202
+ lifecycle.signal.throwIfAborted();
203
+ if (launch.kind === "resume") {
204
+ root = await ctx.agents.resume({ resumeSessionId: rootId, agentOptions: selected, setup, signal: lifecycle.signal });
205
+ } else if (launch.kind === "fork") {
206
+ const stored = await ctx.sessionPersistence.open(launch.source.sessionId, "read");
207
+ let events;
208
+ try {
209
+ events = (await stored.read()).events;
210
+ } finally {
211
+ await stored.close();
212
+ }
213
+ const at = launch.through;
214
+ if (at && (at.provider !== "dsh" || at.kind !== "event" || at.source.sessionId !== launch.source.sessionId || !/^\d+$/.test(at.id) || !Number.isSafeInteger(Number(at.id))))
215
+ throw new Error("invalid DSH fork checkpoint");
216
+ const boundary = at ? events.find((e) => e.seq === Number(at.id) && e.type === "turn/end") : [...events].reverse().find((e) => e.type === "turn/end");
217
+ if (!boundary)
218
+ throw new Error("DSH fork source has no completed turn at requested checkpoint");
219
+ const seed = events.slice(0, boundary.seq + 1);
220
+ root = await ctx.agents.create({
221
+ sessionId: rootId,
222
+ seed,
223
+ inheritedEventCount: SessionLogOffset(seed.length),
224
+ meta: { cwd: input.cwd, parentSession: launch.source.sessionId, isSeeded: true },
225
+ agentOptions: selected,
226
+ setup,
227
+ signal: lifecycle.signal
228
+ });
229
+ } else {
230
+ root = await ctx.agents.create({ sessionId: rootId, meta: { cwd: input.cwd }, agentOptions: selected, setup, signal: lifecycle.signal });
231
+ }
232
+ await ctx.sessionPersistence.flush();
233
+ lifecycle.signal.throwIfAborted();
234
+ return { sessionId: rootId, models: await catalog() };
235
+ } catch (error) {
236
+ if (!root)
237
+ rootId = undefined;
238
+ throw error;
239
+ }
240
+ }
241
+ async function dispatch(method, params) {
242
+ if (closing && method !== "shutdown")
243
+ throw new Error("DSH runtime is closing");
244
+ if (method === "initialize") {
245
+ if (initialized)
246
+ throw new Error("DSH already initialized");
247
+ await ctx.get("loader")?.await();
248
+ initialized = true;
249
+ return { protocolVersion: 1, runtimeVersion: "0.1.5-alpha.2" };
250
+ }
251
+ if (!initialized)
252
+ throw new Error("DSH not initialized");
253
+ switch (method) {
254
+ case "model/list":
255
+ return catalog();
256
+ case "session/open": {
257
+ if (opening)
258
+ throw new Error("DSH session initialization is already in progress");
259
+ opening = open(params);
260
+ try {
261
+ return await opening;
262
+ } finally {
263
+ opening = undefined;
264
+ }
265
+ }
266
+ case "session/list": {
267
+ if (await realpath(params.cwd) !== await realpath(process.cwd()))
268
+ throw new Error("DSH history cwd differs from process workspace");
269
+ const entries = await ctx.sessionPersistence.list();
270
+ const rows = [];
271
+ for (const { header } of [...entries].sort((a, b) => b.header.createdAt - a.header.createdAt)) {
272
+ if (header.origin === "subagent" || header.cwd !== params.cwd)
273
+ continue;
274
+ const stored = await ctx.sessionPersistence.open(header.id, "read");
275
+ let preview = header.id;
276
+ try {
277
+ const { events } = await stored.read(0, 256);
278
+ const input = events.find((e) => e.type === "user/message" && e.data.source.kind === "user");
279
+ if (input?.type === "user/message") {
280
+ const text = input.data.content.filter((part) => part.type === "text").map((part) => part.text).join(" ");
281
+ if (text.trim())
282
+ preview = text.replace(/\s+/g, " ").slice(0, 120);
283
+ }
284
+ } finally {
285
+ await stored.close();
286
+ }
287
+ rows.push({ provider: "dsh", sessionId: header.id, cwd: header.cwd, ts: header.createdAt, preview });
288
+ }
289
+ return rows.sort((a, b) => b.ts - a.ts);
290
+ }
291
+ case "session/model": {
292
+ live();
293
+ selected = await route(params.model, params.effort);
294
+ return {};
295
+ }
296
+ case "session/prompt": {
297
+ const agent = live();
298
+ if (!Array.isArray(params.content) || !params.content.length)
299
+ throw new Error("empty DSH prompt");
300
+ if (params.content.some((part) => part.type === "image")) {
301
+ const routeInfo = await ctx.llm.resolveModelInfo(PROVIDER, selected.model);
302
+ if (!routeInfo.inputModalities?.includes("image"))
303
+ throw new Error(`DSH model ${selected.model} does not accept images`);
304
+ }
305
+ const content = await ctx.attachments.admitPromptContent(params.content);
306
+ const message = createUserMessage({ content, source: { kind: "user" } });
307
+ const delivery = params.mode === "auto" ? agent.status === "running" ? "steer" : "queue" : params.mode;
308
+ if (delivery === "steer")
309
+ agent.steer(message);
310
+ else if (delivery === "queue")
311
+ agent.followup(message);
312
+ else
313
+ throw new Error("invalid DSH input mode");
314
+ return { messageId: message.id };
315
+ }
316
+ case "session/cancel":
317
+ live().cancel(Object.freeze({ kind: "user" }), { keepInbox: true });
318
+ return {};
319
+ case "question/answer": {
320
+ const pending = questions.get(params.id);
321
+ if (!pending)
322
+ throw new Error("DSH question no longer pending");
323
+ if (params.error)
324
+ pending.reject(new Error(String(params.error)));
325
+ else
326
+ pending.resolve({ answers: params.answers });
327
+ questions.delete(params.id);
328
+ return {};
329
+ }
330
+ case "session/compact": {
331
+ const agent = live();
332
+ if (agent.status !== "idle")
333
+ throw new Error("DSH compaction requires an idle session");
334
+ const result = await ctx.compaction.compactNow(agent, new AbortController().signal);
335
+ await ctx.sessionPersistence.flush();
336
+ return result;
337
+ }
338
+ case "shutdown": {
339
+ closing = true;
340
+ lifecycle.abort(Object.freeze({ kind: "disposed" }));
341
+ if (opening) {
342
+ try {
343
+ await opening;
344
+ } catch (error) {
345
+ process.stderr.write(`lodestar-dsh: initialization cancelled during shutdown: ${String(error)}
346
+ `);
347
+ }
348
+ }
349
+ for (const pending of questions.values())
350
+ pending.reject(new Error("DSH shutdown"));
351
+ questions.clear();
352
+ if (root) {
353
+ for (const agent of ctx.agents.list()) {
354
+ if (agent === root.agent || ctx.agents.isOwnedBy(agent.id, root.agent)) {
355
+ agent.cancel(Object.freeze({ kind: "disposed" }));
356
+ }
357
+ }
358
+ await root.dispose();
359
+ }
360
+ await settling;
361
+ await ctx.sessionPersistence.flush();
362
+ return {};
363
+ }
364
+ default:
365
+ throw new Error(`unknown Lodestar DSH method: ${method}`);
366
+ }
367
+ }
368
+ const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
369
+ lines.on("line", (line) => {
370
+ let request;
371
+ try {
372
+ request = JSON.parse(line);
373
+ if (request.jsonrpc !== "2.0" || typeof request.id !== "number" || typeof request.method !== "string") {
374
+ throw new Error("invalid Lodestar DSH request envelope");
375
+ }
376
+ } catch (error) {
377
+ fail(error);
378
+ return;
379
+ }
380
+ dispatch(request.method, request.params).then((result) => process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: request.id, result }) + `
381
+ `), (error) => process.stdout.write(JSON.stringify({
382
+ jsonrpc: "2.0",
383
+ id: request.id,
384
+ error: { code: -32603, message: error instanceof Error ? error.message : String(error) }
385
+ }) + `
386
+ `));
387
+ });
388
+ ctx.effect(() => () => {
389
+ lines.close();
390
+ });
391
+ }
392
+ export {
393
+ name,
394
+ inject,
395
+ apply
396
+ };
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{resolve as E}from"node:path";import{fileURLToPath as b}from"node:url";async function C(e=process.argv.slice(2)){let n=e.shift()??"";if(!n||n==="help"||n==="--help"||n==="-h"){process.stdout.write(`${w()}
3
- `);return}let t=q();switch(n){case"identities":{let r=await d(t,"GET","/agents/identities");process.stdout.write(e.includes("--json")?`${JSON.stringify(r,null,2)}
4
- `:A(r));return}case"run":await P(t,e);return;case"follow-up":case"followup":await T(t,e);return;case"answer":await k(t,e);return;case"status":{let r=c(e[0],"status requires run_id"),s=await d(t,"GET",`/agents/runs/${encodeURIComponent(r)}`);if(process.stdout.write($(s)),s.status==="failed"||s.status==="cancelled")process.exitCode=1;return}case"cancel":{let r=c(e[0],"cancel requires run_id"),s=await d(t,"DELETE",`/agents/runs/${encodeURIComponent(r)}`);process.stdout.write(`${JSON.stringify(s)}
5
- `);return}default:throw Error(w())}}async function P(e,n){let t=g(n,!0),r=await m(t),s={identity_ids:t.identityIds,prompt:r,...t.effort?{effort:t.effort}:{}},i=await d(e,"POST","/agents/runs",s);await y(e,i,t.noWait)}async function T(e,n){let t=c(n.shift(),"follow-up requires run_id"),r=g(n,!1),i={prompt:await m(r),...r.identityId?{identity_id:r.identityId}:{},...r.effort?{effort:r.effort}:{}},o=await d(e,"POST",`/agents/runs/${encodeURIComponent(t)}/follow-up`,i);await y(e,o,r.noWait)}async function k(e,n){let t=c(n.shift(),"answer requires run_id"),r="",s="",i=!1,o={};for(let u=0;u<n.length;u++){let a=n[u],f=()=>c(n[++u],`${a} requires a value`);switch(a){case"--identity":case"-i":r=f();break;case"--request":s=f();break;case"--answer":{let p=f(),l=p.indexOf("=");if(l<=0)throw Error("--answer must be question-or-id=value");o[p.slice(0,l)]=p.slice(l+1);break}case"--stdin":i=!0;break;default:throw Error(`unknown answer option: ${a}`)}}if(i){let u=(await S()).trim(),a;try{a=JSON.parse(u)}catch{throw Error("answer stdin must be a JSON object")}if(!a||typeof a!=="object"||Array.isArray(a))throw Error("answer stdin must be a JSON object");for(let[f,p]of Object.entries(a))o[f]=String(p)}if(!s)throw Error("answer requires --request");if(Object.keys(o).length===0)throw Error("answer requires --answer or --stdin");let I=await d(e,"POST",`/agents/runs/${encodeURIComponent(t)}/answer`,{request_id:s,answers:o,...r?{identity_id:r}:{}});await h(e,String(I.run_id??t))}function g(e,n){let t={identityIds:[],identityId:"",effort:"",prompt:"",noWait:!1,readStdin:!1},r=[];for(let s=0;s<e.length;s++){let i=e[s],o=()=>c(e[++s],`${i} requires a value`);switch(i){case"--identity":case"-i":if(n)t.identityIds.push(o());else t.identityId=o();break;case"--effort":t.effort=o();break;case"--prompt":t.prompt=o();break;case"--stdin":t.readStdin=!0;break;case"--no-wait":t.noWait=!0;break;default:if(i.startsWith("-"))throw Error(`unknown option: ${i}`);r.push(i)}}if(t.identityIds=[...new Set(t.identityIds)],n&&t.identityIds.length===0)throw Error("run requires at least one --identity");if(!t.prompt&&r.length)t.prompt=r.join(" ");if(!t.prompt)t.readStdin=!0;return t}async function m(e){let n=e.readStdin?await S():"",t=e.prompt||n;if(!t.trim())throw Error("agent prompt is empty");return t}async function y(e,n,t){let r=String(n.run_id??"");if(!r)throw Error("agent API returned no run_id");if(t){process.stdout.write(`${JSON.stringify(n,null,2)}
6
- `);return}await h(e,r)}async function h(e,n){let t=!1,r=()=>{if(t)return;t=!0,d(e,"DELETE",`/agents/runs/${encodeURIComponent(n)}`).then(()=>process.exit(130),(s)=>{process.stderr.write(`lodestar-agent: cancellation failed: ${_(s)}
7
- `),process.exit(1)})};process.once("SIGINT",r),process.once("SIGTERM",r);try{while(!0){let s=await d(e,"GET",`/agents/runs/${encodeURIComponent(n)}`);if(s.status==="needs_input"||s.status==="completed"||s.status==="failed"||s.status==="cancelled"){if(process.stdout.write($(s)),s.status==="failed"||s.status==="cancelled")process.exitCode=1;return}await new Promise((i)=>setTimeout(i,1000))}}finally{process.off("SIGINT",r),process.off("SIGTERM",r)}}function q(){let e=String(process.env.LODESTAR_AGENT_URL??"").replace(/\/+$/,""),n=String(process.env.LODESTAR_AGENT_CAPABILITY??"");if(!e||!n)throw Error("lodestar-agent must run inside a Lodestar-managed Agent session (missing capability)");return{baseUrl:e,capability:n}}async function d(e,n,t,r){let s=await fetch(`${e.baseUrl}${t}`,{method:n,headers:{authorization:`Bearer ${e.capability}`,...r?{"content-type":"application/json"}:{}},...r?{body:JSON.stringify(r)}:{}}),i=await s.text(),o;try{o=JSON.parse(i)}catch{throw Error(`agent API ${n} ${t} returned HTTP ${s.status}: ${i||"(empty)"}`)}if(!s.ok)throw Error(o?.error??`agent API HTTP ${s.status}`);return o}function A(e){let n=[`catalog ${e.catalog_generation??"MISS"}`];for(let t of e.identities??[])n.push([t.status==="ready"?"✅":"MISS",t.id,t.display_name,t.model,`default-effort=${t.default_effort}`,t.source_default?"source-default":"",t.reason??""].filter(Boolean).join(" · "));for(let t of e.source_failures??[])n.push(`MISS · ${t.display} · ${t.reason}`);return`${n.join(`
2
+ import{realpathSync as Z}from"node:fs";import{fileURLToPath as P}from"node:url";async function J(z=process.argv.slice(2)){let B=z.shift()??"";if(!B||B==="help"||B==="--help"||B==="-h"){process.stdout.write(`${_()}
3
+ `);return}let j=R();switch(B){case"identities":{let D=await M(j,"GET","/agents/identities");process.stdout.write(z.includes("--json")?`${JSON.stringify(D,null,2)}
4
+ `:b(D));return}case"run":await S(j,z);return;case"follow-up":case"followup":await A(j,z);return;case"answer":await f(j,z);return;case"status":{let D=Q(z[0],"status requires run_id"),E=await M(j,"GET",`/agents/runs/${encodeURIComponent(D)}`);if(process.stdout.write(O(E)),E.status==="failed"||E.status==="cancelled")process.exitCode=1;return}case"cancel":{let D=Q(z[0],"cancel requires run_id"),E=await M(j,"DELETE",`/agents/runs/${encodeURIComponent(D)}`);process.stdout.write(`${JSON.stringify(E)}
5
+ `);return}default:throw Error(_())}}async function S(z,B){let j=$(B,!0),D=await L(j),E={identity_ids:j.identityIds,prompt:D,...j.effort?{effort:j.effort}:{}},G=await M(z,"POST","/agents/runs",E);await T(z,G,j.noWait)}async function A(z,B){let j=Q(B.shift(),"follow-up requires run_id"),D=$(B,!1),G={prompt:await L(D),...D.identityId?{identity_id:D.identityId}:{},...D.effort?{effort:D.effort}:{}},H=await M(z,"POST",`/agents/runs/${encodeURIComponent(j)}/follow-up`,G);await T(z,H,D.noWait)}async function f(z,B){let j=Q(B.shift(),"answer requires run_id"),D="",E="",G=!1,H={};for(let N=0;N<B.length;N++){let K=B[N],V=()=>Q(B[++N],`${K} requires a value`);switch(K){case"--identity":case"-i":D=V();break;case"--request":E=V();break;case"--answer":{let X=V(),Y=X.indexOf("=");if(Y<=0)throw Error("--answer must be question-or-id=value");H[X.slice(0,Y)]=X.slice(Y+1);break}case"--stdin":G=!0;break;default:throw Error(`unknown answer option: ${K}`)}}if(G){let N=(await W()).trim(),K;try{K=JSON.parse(N)}catch{throw Error("answer stdin must be a JSON object")}if(!K||typeof K!=="object"||Array.isArray(K))throw Error("answer stdin must be a JSON object");for(let[V,X]of Object.entries(K))H[V]=String(X)}if(!E)throw Error("answer requires --request");if(Object.keys(H).length===0)throw Error("answer requires --answer or --stdin");let U=await M(z,"POST",`/agents/runs/${encodeURIComponent(j)}/answer`,{request_id:E,answers:H,...D?{identity_id:D}:{}});await F(z,String(U.run_id??j))}function $(z,B){let j={identityIds:[],identityId:"",effort:"",prompt:"",noWait:!1,readStdin:!1},D=[];for(let E=0;E<z.length;E++){let G=z[E],H=()=>Q(z[++E],`${G} requires a value`);switch(G){case"--identity":case"-i":if(B)j.identityIds.push(H());else j.identityId=H();break;case"--effort":j.effort=H();break;case"--prompt":j.prompt=H();break;case"--stdin":j.readStdin=!0;break;case"--no-wait":j.noWait=!0;break;default:if(G.startsWith("-"))throw Error(`unknown option: ${G}`);D.push(G)}}if(j.identityIds=[...new Set(j.identityIds)],B&&j.identityIds.length===0)throw Error("run requires at least one --identity");if(!j.prompt&&D.length)j.prompt=D.join(" ");if(!j.prompt)j.readStdin=!0;return j}async function L(z){let B=z.readStdin?await W():"",j=z.prompt||B;if(!j.trim())throw Error("agent prompt is empty");return j}async function T(z,B,j){let D=String(B.run_id??"");if(!D)throw Error("agent API returned no run_id");if(j){process.stdout.write(`${JSON.stringify(B,null,2)}
6
+ `);return}await F(z,D)}async function F(z,B){let j=!1,D=()=>{if(j)return;j=!0,M(z,"DELETE",`/agents/runs/${encodeURIComponent(B)}`).then(()=>process.exit(130),(E)=>{process.stderr.write(`lodestar-agent: cancellation failed: ${C(E)}
7
+ `),process.exit(1)})};process.once("SIGINT",D),process.once("SIGTERM",D);try{while(!0){let E=await M(z,"GET",`/agents/runs/${encodeURIComponent(B)}`);if(E.status==="needs_input"||E.status==="completed"||E.status==="failed"||E.status==="cancelled"){if(process.stdout.write(O(E)),E.status==="failed"||E.status==="cancelled")process.exitCode=1;return}await new Promise((G)=>setTimeout(G,1000))}}finally{process.off("SIGINT",D),process.off("SIGTERM",D)}}function R(){if(process.env.DSH_LODESTAR_AGENT_CONTEXT!==void 0){let j=JSON.parse(process.env.DSH_LODESTAR_AGENT_CONTEXT);if(typeof j.baseUrl!=="string"||!j.baseUrl||typeof j.capability!=="string"||!j.capability)throw Error("invalid DSH Lodestar delegation context");return{baseUrl:j.baseUrl.replace(/\/+$/,""),capability:j.capability}}let z=String(process.env.LODESTAR_AGENT_URL??"").replace(/\/+$/,""),B=String(process.env.LODESTAR_AGENT_CAPABILITY??"");if(!z||!B)throw Error("lodestar-agent must run inside a Lodestar-managed Agent session (missing capability)");return{baseUrl:z,capability:B}}async function M(z,B,j,D){let E=await fetch(`${z.baseUrl}${j}`,{method:B,headers:{authorization:`Bearer ${z.capability}`,...D?{"content-type":"application/json"}:{}},...D?{body:JSON.stringify(D)}:{}}),G=await E.text(),H;try{H=JSON.parse(G)}catch{throw Error(`agent API ${B} ${j} returned HTTP ${E.status}: ${G||"(empty)"}`)}if(!E.ok)throw Error(H?.error??`agent API HTTP ${E.status}`);return H}function b(z){let B=[`catalog ${z.catalog_generation??"MISS"}`];for(let j of z.identities??[])B.push([j.status==="ready"?"✅":"MISS",j.id,j.display_name,j.model,`default-effort=${j.default_effort}`,j.source_default?"source-default":"",j.reason??""].filter(Boolean).join(" · "));for(let j of z.source_failures??[])B.push(`MISS · ${j.display} · ${j.reason}`);return`${B.join(`
8
8
  `)}
9
- `}function $(e){let n=[`# Lodestar agent ${e.run_id??"MISS"}`,"",`- Status: ${e.status??"MISS"}`,...e.parent_run_id?[`- Parent: ${e.parent_run_id} (${e.parent_kind??"delegate"})`]:[]];if(e.error)n.push(`- Error: ${e.error}`);for(let t of e.workers??[]){if(n.push("",`## ${t.identity_name??t.identity_id}`,"",`Status: ${t.status}`),t.session_id)n.push(`Session: ${t.session_id}`);if(t.error)n.push("",`Error: ${t.error}`);if(t.pending_input){n.push("",`Input request: ${t.pending_input.request_id}`);for(let r of t.pending_input.questions??[])if(n.push(`- [${r.id}] ${r.question}`),r.options?.length)n.push(` Options: ${r.options.map((s)=>s.label).join(" / ")}`);n.push("","Answer with:",`lodestar-agent answer ${e.run_id} --identity '${t.identity_id}' --request '${t.pending_input.request_id}' --stdin`)}if(t.output)n.push("",t.output)}if(e.presentation_errors?.length)n.push("","Presentation errors:",...e.presentation_errors.map((t)=>`- ${t}`));return`${n.join(`
9
+ `}function O(z){let B=[`# Lodestar agent ${z.run_id??"MISS"}`,"",`- Status: ${z.status??"MISS"}`,...z.parent_run_id?[`- Parent: ${z.parent_run_id} (${z.parent_kind??"delegate"})`]:[]];if(z.error)B.push(`- Error: ${z.error}`);for(let j of z.workers??[]){if(B.push("",`## ${j.identity_name??j.identity_id}`,"",`Status: ${j.status}`),j.session_id)B.push(`Session: ${j.session_id}`);if(j.error)B.push("",`Error: ${j.error}`);if(j.pending_input){B.push("",`Input request: ${j.pending_input.request_id}`);for(let D of j.pending_input.questions??[])if(B.push(`- [${D.id}] ${D.question}`),D.options?.length)B.push(` Options: ${D.options.map((E)=>E.label).join(" / ")}`);B.push("","Answer with:",`lodestar-agent answer ${z.run_id} --identity '${j.identity_id}' --request '${j.pending_input.request_id}' --stdin`)}if(j.output)B.push("",j.output)}if(z.presentation_errors?.length)B.push("","Presentation errors:",...z.presentation_errors.map((j)=>`- ${j}`));return`${B.join(`
10
10
  `)}
11
- `}async function S(){if(process.stdin.isTTY)return"";let e=[];for await(let n of process.stdin)e.push(Buffer.isBuffer(n)?n:Buffer.from(n));return Buffer.concat(e).toString("utf8")}function c(e,n){if(!e?.trim())throw Error(n);return e.trim()}function w(){return["Usage:"," lodestar-agent identities [--json]"," lodestar-agent run --identity <id> [--identity <id>...] [--effort <level>] --stdin"," lodestar-agent follow-up <run_id> [--identity <id>] [--effort <level>] --stdin"," lodestar-agent answer <run_id> [--identity <id>] --request <id> (--answer key=value | --stdin)"," lodestar-agent status <run_id>"," lodestar-agent cancel <run_id>"].join(`
12
- `)}function _(e){return e instanceof Error?e.message:String(e)}if(process.argv[1]&&E(process.argv[1])===b(import.meta.url))C().catch((e)=>{process.stderr.write(`lodestar-agent: ${_(e)}
13
- `),process.exitCode=1});export{g as parsePromptArgs,C as main};
11
+ `}async function W(){if(process.stdin.isTTY)return"";let z=[];for await(let B of process.stdin)z.push(Buffer.isBuffer(B)?B:Buffer.from(B));return Buffer.concat(z).toString("utf8")}function Q(z,B){if(!z?.trim())throw Error(B);return z.trim()}function _(){return["Usage:"," lodestar-agent identities [--json]"," lodestar-agent run --identity <id> [--identity <id>...] [--effort <level>] --stdin"," lodestar-agent follow-up <run_id> [--identity <id>] [--effort <level>] --stdin"," lodestar-agent answer <run_id> [--identity <id>] --request <id> (--answer key=value | --stdin)"," lodestar-agent status <run_id>"," lodestar-agent cancel <run_id>"].join(`
12
+ `)}function C(z){return z instanceof Error?z.message:String(z)}if(process.argv[1]&&Z(process.argv[1])===Z(P(import.meta.url)))J().catch((z)=>{process.stderr.write(`lodestar-agent: ${C(z)}
13
+ `),process.exitCode=1});export{$ as parsePromptArgs,J as main};
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as C,spawn as G}from"node:child_process";import{existsSync as B,mkdirSync as I,readFileSync as e}from"node:fs";import{createInterface as zz}from"node:readline/promises";import{delimiter as $z,dirname as qz,join as F}from"node:path";import{homedir as Jz}from"node:os";import{fileURLToPath as Qz}from"node:url";import{homedir as h}from"node:os";import{join as Y}from"node:path";var w=h(),g=process.platform==="win32";function S($,q,J){if($)return $;if(q)return Y(q,"lodestar");return J}function m(){if(g)return Y(process.env.APPDATA??Y(w,"AppData","Roaming"),"Lodestar");return Y(w,".config","lodestar")}function p(){if(g)return Y(process.env.LOCALAPPDATA??Y(w,"AppData","Local"),"Lodestar");return Y(w,".local","share","lodestar")}var v=S(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,m()),H=S(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,p()),O=process.env.LODESTAR_CONFIG??Y(v,"config.toml"),Lz=Y(H,"daemon.pid"),Mz=Y(H,"daemon.log"),Gz=Y(H,"session-chat-map.json"),kz=Y(H,"session-resume-map.json"),Oz=Y(H,"session-turns-map.json"),wz=Y(H,"session-model-map.json"),Ez=Y(H,"temp-session-leases.json"),xz=Y(H,"tasklist-map.json"),Bz=Y(H,"agent-runs"),Fz=Y(H,"agent-session-ids.json"),vz=Y(H,"managed-claude-plugin"),yz=Y(H,"context-window-cache.json"),Rz=Y(H,"notify-callbacks.json"),Tz=Y(H,"alive-on-shutdown.json"),uz=Y(H,"inbox"),gz=Y(H,"debug.sock"),Sz=Y(H,"debug-context.json");import{chmodSync as c,closeSync as y,fsyncSync as j,mkdirSync as l,openSync as _,renameSync as r,unlinkSync as s,writeFileSync as d}from"node:fs";import{randomUUID as a}from"node:crypto";import{dirname as P}from"node:path";var o=384;var i=448;function n($){let q=P($);l(q,{recursive:!0,mode:i})}function t($,q,J){n($);let Q=`${$}.${process.pid}.${a()}.tmp`,X=null;try{X=_(Q,"wx",J),d(X,q),j(X),y(X),X=null,r(Q,$);try{c($,J)}catch{}if(process.platform!=="win32"){let W=null;try{W=_(P($),"r"),j(W)}catch{}finally{if(W!==null)try{y(W)}catch{}}}}catch(W){if(X!==null)try{y(X)}catch{}try{s(Q)}catch{}throw W}}function R($,q){t($,q,o)}var z={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",red:"\x1B[31m"},M=zz({input:process.stdin,output:process.stdout});async function L($,q={}){while(!0){let J=q.default?`${z.dim} [${q.default}]${z.reset}`:"",Q=(await M.question(`${z.cyan}? ${z.reset}${$}${J}
2
+ import{execSync as C,spawn as G}from"node:child_process";import{existsSync as B,mkdirSync as I,readFileSync as e}from"node:fs";import{createInterface as zz}from"node:readline/promises";import{delimiter as $z,dirname as qz,join as F}from"node:path";import{homedir as Jz}from"node:os";import{fileURLToPath as Qz}from"node:url";import{homedir as h}from"node:os";import{join as Y}from"node:path";var w=h(),g=process.platform==="win32";function S($,q,J){if($)return $;if(q)return Y(q,"lodestar");return J}function m(){if(g)return Y(process.env.APPDATA??Y(w,"AppData","Roaming"),"Lodestar");return Y(w,".config","lodestar")}function p(){if(g)return Y(process.env.LOCALAPPDATA??Y(w,"AppData","Local"),"Lodestar");return Y(w,".local","share","lodestar")}var v=S(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,m()),H=S(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,p()),O=process.env.LODESTAR_CONFIG??Y(v,"config.toml"),Lz=Y(H,"daemon.pid"),Mz=Y(H,"daemon.log"),Gz=Y(H,"session-chat-map.json"),kz=Y(H,"session-resume-map.json"),Oz=Y(H,"session-turns-map.json"),wz=Y(H,"session-model-map.json"),Ez=Y(H,"temp-session-leases.json"),xz=Y(H,"tasklist-map.json"),Bz=Y(H,"agent-runs"),Fz=Y(H,"agent-session-ids.json"),vz=Y(H,"managed-claude-plugin"),yz=Y(H,"dsh"),Rz=Y(H,"context-window-cache.json"),Tz=Y(H,"notify-callbacks.json"),uz=Y(H,"alive-on-shutdown.json"),gz=Y(H,"inbox"),Sz=Y(H,"debug.sock"),jz=Y(H,"debug-context.json");import{chmodSync as c,closeSync as y,fsyncSync as j,mkdirSync as l,openSync as P,renameSync as r,unlinkSync as s,writeFileSync as d}from"node:fs";import{randomUUID as a}from"node:crypto";import{dirname as _}from"node:path";var o=384;var i=448;function n($){let q=_($);l(q,{recursive:!0,mode:i})}function t($,q,J){n($);let Q=`${$}.${process.pid}.${a()}.tmp`,X=null;try{X=P(Q,"wx",J),d(X,q),j(X),y(X),X=null,r(Q,$);try{c($,J)}catch{}if(process.platform!=="win32"){let W=null;try{W=P(_($),"r"),j(W)}catch{}finally{if(W!==null)try{y(W)}catch{}}}}catch(W){if(X!==null)try{y(X)}catch{}try{s(Q)}catch{}throw W}}function R($,q){t($,q,o)}var z={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",red:"\x1B[31m"},M=zz({input:process.stdin,output:process.stdout});async function L($,q={}){while(!0){let J=q.default?`${z.dim} [${q.default}]${z.reset}`:"",Q=(await M.question(`${z.cyan}? ${z.reset}${$}${J}
3
3
  ${z.green}>${z.reset} `)).trim();if(!Q&&q.default!==void 0)return q.default;if(!Q&&q.required){console.log(`${z.red}必填,请重新输入${z.reset}`);continue}return Q}}function Yz($){let q="═".repeat(58);console.log(`
4
4
  ${z.bold}${z.cyan}╔${q}╗${z.reset}`),console.log(`${z.bold}${z.cyan}║ ${$.padEnd(54)} ║${z.reset}`),console.log(`${z.bold}${z.cyan}╚${q}╝${z.reset}
5
5
  `)}function E($,q,J){console.log(`
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{execSync as f}from"node:child_process";import{existsSync as U,readFileSync as y}from"node:fs";import{homedir as W}from"node:os";import{join as q}from"node:path";var X=W(),B=process.platform==="win32";function b(J,K,Y){if(J)return J;if(K)return q(K,"lodestar");return Y}function H(){if(B)return q(process.env.APPDATA??q(X,"AppData","Roaming"),"Lodestar");return q(X,".config","lodestar")}function V(){if(B)return q(process.env.LOCALAPPDATA??q(X,"AppData","Local"),"Lodestar");return q(X,".local","share","lodestar")}var w=b(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,H()),Q=b(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,V()),x=process.env.LODESTAR_CONFIG??q(w,"config.toml"),Z=q(Q,"daemon.pid"),S=q(Q,"daemon.log"),E=q(Q,"session-chat-map.json"),v=q(Q,"session-resume-map.json"),h=q(Q,"session-turns-map.json"),k=q(Q,"session-model-map.json"),R=q(Q,"temp-session-leases.json"),F=q(Q,"tasklist-map.json"),T=q(Q,"agent-runs"),m=q(Q,"agent-session-ids.json"),C=q(Q,"managed-claude-plugin"),j=q(Q,"context-window-cache.json"),l=q(Q,"notify-callbacks.json"),c=q(Q,"alive-on-shutdown.json"),_=q(Q,"inbox"),t=q(Q,"debug.sock"),A=q(Q,"debug-context.json");import{execSync as G}from"node:child_process";import{readFileSync as L,writeFileSync as D}from"node:fs";function M(J){try{if(process.platform==="linux")return L(`/proc/${J}/cmdline`,"utf8").replace(/\0/g," ").trim();if(process.platform==="darwin")return G(`ps -p ${J} -o args=`,{encoding:"utf8",timeout:2000}).trim();if(process.platform==="win32")return G(`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${J}').CommandLine"`,{encoding:"utf8",timeout:5000}).trim()||null;return null}catch{return null}}function N(J,K){if(!Number.isFinite(J)||J<=0||!K)return!1;let Y=M(J);if(!Y)return!1;if(process.platform==="win32")return Y.toLowerCase().includes(K.toLowerCase());return Y.includes(K)}var z={reset:"\x1B[0m",bold:"\x1B[1m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"};function P(J){return new Promise((K)=>setTimeout(K,J))}async function u(){if(!U(Z)){console.log(`${z.yellow}Lodestar daemon 未运行${z.reset} ${z.dim}(${Z} 不存在)${z.reset}`);return}let J=y(Z,"utf8").split(`
3
- `),K=parseInt((J[0]??"").trim(),10),Y=(J[1]??"").trim();if(!Number.isFinite(K)||K<=0)console.error(`${z.red}PID 文件格式坏:${z.reset} ${Z}`),process.exit(1);if(Y&&!N(K,Y)){console.log(`${z.yellow}PID ${K} 上没有 daemon (stale 文件)${z.reset}`),console.log(`${z.dim}手删 ${Z} 后再试${z.reset}`);return}console.log(`${z.bold}停止 daemon${z.reset} (pid ${K})...`);try{if(process.platform==="win32")f(`taskkill /PID ${K} /T /F`,{stdio:"ignore"});else process.kill(K,"SIGTERM")}catch($){console.error(`${z.red}发信号失败:${z.reset} ${$?.message??$}`),process.exit(1)}for(let $=0;$<50;$++){if(!U(Z)){console.log(`${z.green}✓ daemon 已停${z.reset}`);return}await P(100)}console.log(`${z.yellow}已发 SIGTERM, 但 5s 内 daemon 没清掉 ${Z}${z.reset}`),console.log(`${z.dim}它可能还在收尾 (落盘 alive marker / 关 WS); 下次启动 pid-guard 会自动判别。${z.reset}`)}u().catch((J)=>{console.error(`${z.red}lodestar-stop:${z.reset} ${J?.message??J}`),process.exit(1)});
2
+ import{execSync as f}from"node:child_process";import{existsSync as U,readFileSync as y}from"node:fs";import{homedir as W}from"node:os";import{join as q}from"node:path";var X=W(),B=process.platform==="win32";function b(J,Q,Y){if(J)return J;if(Q)return q(Q,"lodestar");return Y}function H(){if(B)return q(process.env.APPDATA??q(X,"AppData","Roaming"),"Lodestar");return q(X,".config","lodestar")}function V(){if(B)return q(process.env.LOCALAPPDATA??q(X,"AppData","Local"),"Lodestar");return q(X,".local","share","lodestar")}var w=b(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,H()),K=b(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,V()),x=process.env.LODESTAR_CONFIG??q(w,"config.toml"),Z=q(K,"daemon.pid"),S=q(K,"daemon.log"),E=q(K,"session-chat-map.json"),v=q(K,"session-resume-map.json"),h=q(K,"session-turns-map.json"),k=q(K,"session-model-map.json"),F=q(K,"temp-session-leases.json"),R=q(K,"tasklist-map.json"),T=q(K,"agent-runs"),m=q(K,"agent-session-ids.json"),C=q(K,"managed-claude-plugin"),l=q(K,"dsh"),j=q(K,"context-window-cache.json"),c=q(K,"notify-callbacks.json"),t=q(K,"alive-on-shutdown.json"),_=q(K,"inbox"),A=q(K,"debug.sock"),I=q(K,"debug-context.json");import{execSync as G}from"node:child_process";import{readFileSync as L,writeFileSync as D}from"node:fs";function M(J){try{if(process.platform==="linux")return L(`/proc/${J}/cmdline`,"utf8").replace(/\0/g," ").trim();if(process.platform==="darwin")return G(`ps -p ${J} -o args=`,{encoding:"utf8",timeout:2000}).trim();if(process.platform==="win32")return G(`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${J}').CommandLine"`,{encoding:"utf8",timeout:5000}).trim()||null;return null}catch{return null}}function N(J,Q){if(!Number.isFinite(J)||J<=0||!Q)return!1;let Y=M(J);if(!Y)return!1;if(process.platform==="win32")return Y.toLowerCase().includes(Q.toLowerCase());return Y.includes(Q)}var z={reset:"\x1B[0m",bold:"\x1B[1m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"};function P(J){return new Promise((Q)=>setTimeout(Q,J))}async function u(){if(!U(Z)){console.log(`${z.yellow}Lodestar daemon 未运行${z.reset} ${z.dim}(${Z} 不存在)${z.reset}`);return}let J=y(Z,"utf8").split(`
3
+ `),Q=parseInt((J[0]??"").trim(),10),Y=(J[1]??"").trim();if(!Number.isFinite(Q)||Q<=0)console.error(`${z.red}PID 文件格式坏:${z.reset} ${Z}`),process.exit(1);if(Y&&!N(Q,Y)){console.log(`${z.yellow}PID ${Q} 上没有 daemon (stale 文件)${z.reset}`),console.log(`${z.dim}手删 ${Z} 后再试${z.reset}`);return}console.log(`${z.bold}停止 daemon${z.reset} (pid ${Q})...`);try{if(process.platform==="win32")f(`taskkill /PID ${Q} /T /F`,{stdio:"ignore"});else process.kill(Q,"SIGTERM")}catch($){console.error(`${z.red}发信号失败:${z.reset} ${$?.message??$}`),process.exit(1)}for(let $=0;$<50;$++){if(!U(Z)){console.log(`${z.green}✓ daemon 已停${z.reset}`);return}await P(100)}console.log(`${z.yellow}已发 SIGTERM, 但 5s 内 daemon 没清掉 ${Z}${z.reset}`),console.log(`${z.dim}它可能还在收尾 (落盘 alive marker / 关 WS); 下次启动 pid-guard 会自动判别。${z.reset}`)}u().catch((J)=>{console.error(`${z.red}lodestar-stop:${z.reset} ${J?.message??J}`),process.exit(1)});
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as g}from"node:child_process";import{existsSync as x}from"node:fs";import{homedir as p}from"node:os";import{join as o}from"node:path";var m=p(),a=process.platform==="win32";function $(t,l,r){if(t)return t;if(l)return o(l,"lodestar");return r}function u(){if(a)return o(process.env.APPDATA??o(m,"AppData","Roaming"),"Lodestar");return o(m,".config","lodestar")}function y(){if(a)return o(process.env.LOCALAPPDATA??o(m,"AppData","Local"),"Lodestar");return o(m,".local","share","lodestar")}var C=$(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,u()),e=$(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,y()),K=process.env.LODESTAR_CONFIG??o(C,"config.toml"),n=o(e,"daemon.pid"),f=o(e,"daemon.log"),S=o(e,"session-chat-map.json"),q=o(e,"session-resume-map.json"),z=o(e,"session-turns-map.json"),J=o(e,"session-model-map.json"),Q=o(e,"temp-session-leases.json"),Y=o(e,"tasklist-map.json"),Z=o(e,"agent-runs"),W=o(e,"agent-session-ids.json"),X=o(e,"managed-claude-plugin"),i=o(e,"context-window-cache.json"),B=o(e,"notify-callbacks.json"),G=o(e,"alive-on-shutdown.json"),N=o(e,"inbox"),U=o(e,"debug.sock"),F=o(e,"debug-context.json");var s={reset:"\x1B[0m",bold:"\x1B[1m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"},d=["@leviyuan/lodestar@latest","@openai/codex@latest","@anthropic-ai/claude-code@latest","@anthropic-ai/claude-agent-sdk@latest","@anthropic-ai/sdk@latest"];function b(){let t=process.platform==="win32"?"npm.cmd":"npm";return new Promise((l)=>{let r=g(t,["install","-g","--include=optional",...d],{stdio:"inherit",shell:process.platform==="win32"});r.on("exit",(c)=>l(c??1)),r.on("error",(c)=>{console.error(`${s.red}spawn npm 失败:${s.reset} ${c.message}`),l(1)})})}async function w(){console.log(`${s.bold}更新 Lodestar + Codex CLI + Claude Code / SDK${s.reset}`),console.log(`${s.dim}npm i -g ${d.join(" ")}${s.reset}
2
+ import{spawn as g}from"node:child_process";import{existsSync as x}from"node:fs";import{homedir as p}from"node:os";import{join as s}from"node:path";var m=p(),a=process.platform==="win32";function $(t,l,r){if(t)return t;if(l)return s(l,"lodestar");return r}function u(){if(a)return s(process.env.APPDATA??s(m,"AppData","Roaming"),"Lodestar");return s(m,".config","lodestar")}function y(){if(a)return s(process.env.LOCALAPPDATA??s(m,"AppData","Local"),"Lodestar");return s(m,".local","share","lodestar")}var C=$(process.env.LODESTAR_CONFIG_DIR,process.env.XDG_CONFIG_HOME,u()),e=$(process.env.LODESTAR_DATA_DIR,process.env.XDG_DATA_HOME,y()),K=process.env.LODESTAR_CONFIG??s(C,"config.toml"),n=s(e,"daemon.pid"),f=s(e,"daemon.log"),S=s(e,"session-chat-map.json"),q=s(e,"session-resume-map.json"),z=s(e,"session-turns-map.json"),J=s(e,"session-model-map.json"),Q=s(e,"temp-session-leases.json"),Y=s(e,"tasklist-map.json"),Z=s(e,"agent-runs"),W=s(e,"agent-session-ids.json"),X=s(e,"managed-claude-plugin"),B=s(e,"dsh"),G=s(e,"context-window-cache.json"),N=s(e,"notify-callbacks.json"),U=s(e,"alive-on-shutdown.json"),i=s(e,"inbox"),F=s(e,"debug.sock"),h=s(e,"debug-context.json");var o={reset:"\x1B[0m",bold:"\x1B[1m",cyan:"\x1B[36m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",dim:"\x1B[2m"},d=["@leviyuan/lodestar@latest","@openai/codex@latest","@anthropic-ai/claude-code@latest","@anthropic-ai/claude-agent-sdk@latest","@anthropic-ai/sdk@latest"];function b(){let t=process.platform==="win32"?"npm.cmd":"npm";return new Promise((l)=>{let r=g(t,["install","-g","--include=optional",...d],{stdio:"inherit",shell:process.platform==="win32"});r.on("exit",(c)=>l(c??1)),r.on("error",(c)=>{console.error(`${o.red}spawn npm 失败:${o.reset} ${c.message}`),l(1)})})}async function w(){console.log(`${o.bold}更新 Lodestar + Codex CLI + Claude Code / SDK${o.reset}`),console.log(`${o.dim}npm i -g ${d.join(" ")}${o.reset}
3
3
  `);let t=await b();if(t!==0)console.error(`
4
- ${s.red}更新失败 (npm exit ${t})${s.reset}`),process.exit(t);if(console.log(`
5
- ${s.green}✓ 更新完成${s.reset}`),x(n))console.log(),console.log(`${s.yellow}检测到 daemon 仍在跑老版本进程, 用新版本需要重启:${s.reset}`),console.log(` ${s.dim}# Linux systemd --user 托管的:${s.reset}`),console.log(` ${s.cyan}systemctl --user restart lodestar${s.reset} ${s.dim}# Linux${s.reset}`),console.log(` ${s.dim}# macOS launchd / Windows 任务计划:用对应服务管理器重启。${s.reset}`),console.log(` ${s.dim}# 或手动重启:${s.reset}`),console.log(` ${s.cyan}lodestar-stop && lodestar-daemon${s.reset}`)}w().catch((t)=>{console.error(`${s.red}lodestar-update:${s.reset} ${t?.message??t}`),process.exit(1)});
4
+ ${o.red}更新失败 (npm exit ${t})${o.reset}`),process.exit(t);if(console.log(`
5
+ ${o.green}✓ 更新完成${o.reset}`),x(n))console.log(),console.log(`${o.yellow}检测到 daemon 仍在跑老版本进程, 用新版本需要重启:${o.reset}`),console.log(` ${o.dim}# Linux systemd --user 托管的:${o.reset}`),console.log(` ${o.cyan}systemctl --user restart lodestar${o.reset} ${o.dim}# Linux${o.reset}`),console.log(` ${o.dim}# macOS launchd / Windows 任务计划:用对应服务管理器重启。${o.reset}`),console.log(` ${o.dim}# 或手动重启:${o.reset}`),console.log(` ${o.cyan}lodestar-stop && lodestar-daemon${o.reset}`)}w().catch((t)=>{console.error(`${o.red}lodestar-update:${o.reset} ${t?.message??t}`),process.exit(1)});