dsh-llm-workbuddy 0.1.2 → 0.1.3

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 (3) hide show
  1. package/README.md +9 -5
  2. package/lib/index.js +34 -12
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,7 +42,8 @@
42
42
  ```
43
43
 
44
44
  - **后端路由**(`lib/index.js` 的 `registerWorkbuddyRoutes`)运行在 `dsh web` 的
45
- HTTP 服务上,通过 Cordis 的 `ctx.webServer` 注册,headless profile 下自动跳过。
45
+ HTTP 服务上,通过 Cordis 的 `webServer` 服务注册(用 `ctx.get("webServer")`
46
+ 探测,`webServer` 不列入 `inject`),headless profile 下自动跳过。
46
47
  - **前端胶囊**(`lib/client.js`)是零依赖的原生浏览器 JS,由 DSH 的 `dsh.client`
47
48
  双端机制在 `window.__DSH_BOOT__` 中注入,serve 于
48
49
  `/plugins/dsh-llm-workbuddy/client.js`。
@@ -163,13 +164,16 @@ Web 登录后终端脚本也读得到同一份会话。
163
164
 
164
165
  ### 后端路由(`lib/index.js`)
165
166
 
166
- 插件在 `apply(ctx, config)` 里通过 `ctx.webServer.register(...)` 注册两条路由
167
- headless profile 无 `webServer`,自动跳过):
167
+ 插件在 `apply(ctx, config)` 里用 `ctx.get("webServer")` 探测 HTTP 服务(不在
168
+ `inject` 里,headless profile 无 `webServer` 时返回 `undefined`、自动跳过),
169
+ 然后通过 `webServer.register({ kind: 'exact', path, handler })` 注册两条精确
170
+ 路由,并用 `ctx.effect` 包裹以便热重载自动清理。HTTP 方法过滤在 handler 内自行
171
+ 判断(`req.method`),因为路由 API 本身不含方法字段:
168
172
 
169
173
  | 方法 + 路径 | 行为 |
170
174
  |---|---|
171
- | `GET /api/workbuddy/status` | 读取会话文件(默认 `~/.codebuddy-session.json`,或配置的 `sessionFile`)的 `auth.expiresAt` 判断会话是否有效,并 `fetch` 代理 `/health` 判断 `proxyUp`;返回 JSON:`{ sessionFile, authenticated, expiresAt, account, proxyUp, tokenValid, loginScriptAvailable }` |
172
- | `POST /api/workbuddy/login` | 若已有有效会话则直接返回 `alreadyLoggedIn`;否则用系统 `python3` `spawn` 包内 `login_workbuddy.py --session-file <sessionFile>`,从子进程 stdout 解析出 `authUrl` 立即返回 `{ authUrl, pending:true }`(设备流在后台继续,前端轮询 status 感知完成) |
175
+ | `GET /api/workbuddy/status` | 读取会话文件(默认 `~/.codebuddy-session.json`,或配置的 `sessionFile`)的 `auth.expiresAt` 判断会话是否有效,并 `fetch` 代理 `/health` 判断 `proxyUp`;返回 JSON:`{ sessionFile, authenticated, expiresAt, account, proxyUp, tokenValid, loginScriptAvailable }`;非 GET 返回 405 |
176
+ | `POST /api/workbuddy/login` | 若已有有效会话则直接返回 `alreadyLoggedIn`;否则用系统 `python3` `spawn` 包内 `login_workbuddy.py --session-file <sessionFile>`,从子进程 stdout 解析出 `authUrl` 立即返回 `{ authUrl, pending:true }`(设备流在后台继续,前端轮询 status 感知完成);非 POST 返回 405 |
173
177
 
174
178
  > 会话文件与登录脚本路径的解析顺序:
175
179
  > 1. 配置里显式指定的 `sessionFile` / `loginScript`;
package/lib/index.js CHANGED
@@ -38,7 +38,11 @@ import { MAX_TIMER_DELAY_MS, idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-ti
38
38
 
39
39
  /** Plugin identity (Cordis convention). */
40
40
  export const name = "llm-workbuddy";
41
- export const inject = ["llm", "webServer"];
41
+ // `llm` is a hard requirement (the adapter must always register). `webServer`
42
+ // is optional — headless profiles have no HTTP surface, so it must NOT be in
43
+ // `inject` (that would make the whole plugin PENDING forever in headless).
44
+ // We probe it at the use site instead. See cordis-tutorial/03-services.md.
45
+ export const inject = ["llm"];
42
46
 
43
47
  /** The single provider route this plugin owns. */
44
48
  export const PROVIDER = "workbuddy";
@@ -765,14 +769,28 @@ async function probeProxy(baseURL) {
765
769
  * file appears and reports `authenticated: true`.
766
770
  */
767
771
  function registerWorkbuddyRoutes(ctx, config) {
768
- if (ctx.webServer === undefined) return; // headless profile: no HTTP surface
772
+ // Probe the optional webServer service. In a headless profile it is absent
773
+ // and we skip the HTTP surface entirely — the adapter (registered in apply)
774
+ // still works. Because `webServer` is not in `inject`, this must be a
775
+ // runtime probe, not a dereference.
776
+ const webServer = ctx.get("webServer");
777
+ if (webServer === undefined) return;
769
778
  const baseURL = config_baseURL();
770
779
  const { sessionFile, loginScript } = resolveLoginPaths(config);
771
- ctx.webServer.register({
780
+ // The register() call returns a disposer. Wrapping it in ctx.effect ties it
781
+ // to the plugin fiber so config hot-edit / reload cleans the routes up
782
+ // instead of leaking them (a leaked route makes a duplicate re-register
783
+ // throw). Method filtering must be done inside the handler — the route API
784
+ // only accepts `{ kind, path, handler }`.
785
+ ctx.effect(() => webServer.register({
786
+ kind: "exact",
772
787
  path: "/api/workbuddy/status",
773
- exact: true,
774
- methods: ["GET"],
775
- async handler(_req, res) {
788
+ async handler(req, res) {
789
+ if (req.method !== "GET") {
790
+ res.writeHead(405, { "content-type": "application/json" });
791
+ res.end(JSON.stringify({ error: "method not allowed" }));
792
+ return;
793
+ }
776
794
  const session = readSessionStatus(sessionFile);
777
795
  const proxy = await probeProxy(baseURL);
778
796
  res.writeHead(200, { "content-type": "application/json" });
@@ -782,12 +800,16 @@ function registerWorkbuddyRoutes(ctx, config) {
782
800
  loginScriptAvailable: existsSync(loginScript),
783
801
  }));
784
802
  },
785
- });
786
- ctx.webServer.register({
803
+ }));
804
+ ctx.effect(() => webServer.register({
805
+ kind: "exact",
787
806
  path: "/api/workbuddy/login",
788
- exact: true,
789
- methods: ["POST"],
790
- async handler(_req, res) {
807
+ async handler(req, res) {
808
+ if (req.method !== "POST") {
809
+ res.writeHead(405, { "content-type": "application/json" });
810
+ res.end(JSON.stringify({ error: "method not allowed" }));
811
+ return;
812
+ }
791
813
  if (!existsSync(loginScript)) {
792
814
  res.writeHead(503, { "content-type": "application/json" });
793
815
  res.end(JSON.stringify({ error: "login_workbuddy.py not found" }));
@@ -817,7 +839,7 @@ function registerWorkbuddyRoutes(ctx, config) {
817
839
  res.writeHead(200, { "content-type": "application/json" });
818
840
  res.end(JSON.stringify({ authUrl, pending: true }));
819
841
  },
820
- });
842
+ }));
821
843
  }
822
844
 
823
845
  /** Lazily resolve the configured proxy base URL for the health probe. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-llm-workbuddy",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "WorkBuddy (via the local workbuddy2api proxy) LLM provider adapter for DeepSeek Harness, with a Web login-status widget",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",