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.
- package/README.md +9 -5
- package/lib/index.js +34 -12
- 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`
|
|
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)`
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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