dsh-cloud-experts 0.0.0-stage → 0.1.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 CHANGED
@@ -1,3 +1,37 @@
1
- # Temporary Holding Version
1
+ # dsh-cloud-experts
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 把远端服务器上的 Claude Code / Codex CLI 注册为 DSH 云端子智能体:插件经 SSH 调用远端 `fed-expert` wrapper(协议 `fed-expert/v1`),订阅凭据、模型选择、资源围栏全部留在远端服务器,本机零凭据。
4
+
5
+ ## 提供的能力
6
+
7
+ - 子智能体 provider:`cloud-claude` / `cloud-codex`(one-shot external provider,多轮靠 prompt 自携带上下文)
8
+ - 工具:`consult_claude` / `consult_codex`(由官方 `@deepseek-ai/dsh-tool-subagent` 挂接)
9
+
10
+ ## 前置要求
11
+
12
+ 远端服务器需部署配套的 `fed-expert` wrapper(接收 stdin prompt,stdout 返回答案,stderr 首行 `FED_ENVELOPE:{json}` 信封),并为本机配置一把 forced-command + `restrict` 的 SSH 专用钥。
13
+
14
+ ## 配置
15
+
16
+ | 键 | 默认 | 说明 |
17
+ |---|---|---|
18
+ | host | `allen-bwg` | SSH 目标(走本机 ssh config 别名,需已配置 HostName/User) |
19
+ | keyPath | 空 | 专用钥路径(空 = ssh config 默认身份) |
20
+ | remoteEntry | `~/bin/fed-expert` | 远端 wrapper 路径 |
21
+ | origin | `dsh-plugin` | 审计来源标记 |
22
+ | disposeGraceMs | 5000 | 取消时进程回收宽限 |
23
+
24
+ ## 安装
25
+
26
+ - **DSH Desktop / GO 插件市场**:按包名 `dsh-cloud-experts` 安装(依赖 peers `0.2.2-rc.1`);
27
+ - **开发模式(Desktop)**:本目录先 `npm install`(link 模式不代装依赖),再 Plugins → Install from path。
28
+
29
+ 装完新开会话,工具列表出现 `consult_claude` / `consult_codex` 即成功。
30
+
31
+ ## 版本
32
+
33
+ - 0.1.1:对齐官方 bundle 的 inject 声明(`subagents` + `subprocess`)。
34
+
35
+ ## License
36
+
37
+ MIT
@@ -0,0 +1,15 @@
1
+ - insert:
2
+ - id: subagent-cloud-experts
3
+ name: dsh-cloud-experts
4
+ - id: tool-subagent-cloud-claude
5
+ name: '@deepseek-ai/dsh-tool-subagent'
6
+ config:
7
+ provider: cloud-claude
8
+ toolName: consult_claude
9
+ maxDepth: provider-managed
10
+ - id: tool-subagent-cloud-codex
11
+ name: '@deepseek-ai/dsh-tool-subagent'
12
+ config:
13
+ provider: cloud-codex
14
+ toolName: consult_codex
15
+ maxDepth: provider-managed
package/lib/index.js ADDED
@@ -0,0 +1,149 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { NO_START_CAPABILITIES, settleRunResult, subprocessRunHandle } from "@deepseek-ai/dsh-subagent";
3
+ import { randomUUID } from "node:crypto";
4
+
5
+ const name = "cloud-experts";
6
+ // 与官方 bundle 一致:ctx.logger 原生访问、不注入(注入会因服务不存在而永久 pending)
7
+ const inject = ["subagents", "subprocess"];
8
+
9
+ const Config = z.object({
10
+ host: z.string().min(1).default("allen-bwg"),
11
+ keyPath: z.string().default(""),
12
+ remoteEntry: z.string().min(1).default("~/bin/fed-expert"),
13
+ origin: z.string().min(1).default("dsh-plugin"),
14
+ disposeGraceMs: z.number().default(5000)
15
+ });
16
+
17
+ const MINIMAL_ENV_KEYS = ["PATH", "SystemRoot", "USERPROFILE", "HOME"];
18
+
19
+ function minimalEnv() {
20
+ const env = {};
21
+ for (const key of MINIMAL_ENV_KEYS) if (process.env[key] !== undefined) env[key] = process.env[key];
22
+ return env;
23
+ }
24
+
25
+ function promptText(prompt) {
26
+ const parts = Array.isArray(prompt) ? prompt : [prompt];
27
+ const texts = [];
28
+ for (const part of parts) {
29
+ if (part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string") texts.push(part.text);
30
+ else if (typeof part === "string") texts.push(part);
31
+ }
32
+ return texts.join("\n");
33
+ }
34
+
35
+ function writeAll(stream, data) {
36
+ return new Promise((resolve, reject) => {
37
+ const CHUNK = 65536;
38
+ let offset = 0;
39
+ const next = () => {
40
+ while (offset < data.length) {
41
+ const slice = data.subarray(offset, offset + CHUNK);
42
+ offset += slice.length;
43
+ if (!stream.write(slice)) {
44
+ stream.once("drain", next);
45
+ return;
46
+ }
47
+ }
48
+ stream.end(resolve);
49
+ };
50
+ stream.on("error", reject);
51
+ next();
52
+ });
53
+ }
54
+
55
+ class CloudExpertProvider {
56
+ capabilities = NO_START_CAPABILITIES;
57
+ inheritsParentContext = false;
58
+
59
+ constructor(providerName, ctx, config, expert) {
60
+ this.name = providerName;
61
+ this.ctx = ctx;
62
+ this.config = config;
63
+ this.expert = expert;
64
+ }
65
+
66
+ start(request) {
67
+ if (request.signal.aborted) throw new Error(`cloud-experts: request aborted before ${this.expert} run`);
68
+ const text = promptText(request.prompt) || `(empty ${this.expert} consult)`;
69
+ const stamp = new Date().toISOString().replace(/[^0-9]/g, "").slice(0, 14);
70
+ const consultId = `PLUGIN-${stamp}-${this.expert}-${randomUUID().slice(0, 8)}`;
71
+ const remoteCmd = [this.config.remoteEntry, this.expert, "--id", consultId, "--origin", this.config.origin].join(" ");
72
+ const argv = ["ssh", "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", "ConnectTimeout=15"];
73
+ if (this.config.keyPath) argv.push("-i", this.config.keyPath);
74
+ argv.push(this.config.host, remoteCmd);
75
+
76
+ const child = this.ctx.subprocess.spawn({
77
+ argv,
78
+ cwd: request.cwd,
79
+ stdio: { stdin: "pipe", stdout: "pipe", stderr: "pipe" },
80
+ graceMs: this.config.disposeGraceMs,
81
+ signal: request.signal,
82
+ env: minimalEnv()
83
+ });
84
+
85
+ let out = "";
86
+ let err = "";
87
+ child.stdout?.on("data", (chunk) => { out += typeof chunk === "string" ? chunk : chunk.toString("utf8"); });
88
+ child.stdout?.on("error", () => {});
89
+ child.stderr?.on("data", (chunk) => { err += typeof chunk === "string" ? chunk : chunk.toString("utf8"); });
90
+ child.stderr?.on("error", () => {});
91
+ const stdinWrite = writeAll(child.stdin, Buffer.from(text, "utf8")).catch(() => {});
92
+
93
+ const controller = new AbortController();
94
+ const requestCancel = () => {
95
+ if (!controller.signal.aborted) controller.abort(new Error(`cloud-experts: ${this.expert} run cancelled locally`));
96
+ try { child.stdin?.end(); } catch {}
97
+ try { child.terminate(); } catch {}
98
+ };
99
+ const onAbort = () => requestCancel();
100
+ request.signal.addEventListener("abort", onAbort, { once: true });
101
+
102
+ const attempt = async () => {
103
+ await stdinWrite;
104
+ const info = await child.done;
105
+ const code = info && typeof info.code === "number" ? info.code : 0;
106
+ const envelope = /FED_ENVELOPE:(\{[^\n]*\})/.exec(err);
107
+ if (code === 0 && out.length > 0) {
108
+ return {
109
+ output: [{ type: "text", text: out }],
110
+ stopReason: "completed",
111
+ envelope: envelope ? envelope[1] : undefined
112
+ };
113
+ }
114
+ const failure = /FED_ERROR:([\w:.-]+)/.exec(err);
115
+ throw new Error(`cloud-experts: ${failure?.[1] ?? `exit-${code}`} ${err.slice(0, 200)}`);
116
+ };
117
+
118
+ const result = settleRunResult({
119
+ attempt,
120
+ collectOutput: () => out,
121
+ cancelled: () => request.signal.aborted,
122
+ onError: (error) => {
123
+ this.ctx.logger?.warn?.(`cloud-experts "${this.name}": run failed: %o`, error);
124
+ },
125
+ signal: request.signal,
126
+ onAbort
127
+ });
128
+
129
+ return subprocessRunHandle({
130
+ id: randomUUID(),
131
+ result,
132
+ signal: request.signal,
133
+ onAbort,
134
+ requestCancel,
135
+ teardown: async () => {
136
+ requestCancel();
137
+ try { await child.waitForExit(); } catch {}
138
+ }
139
+ });
140
+ }
141
+ }
142
+
143
+ function apply(ctx, config) {
144
+ const resolved = Config(config ?? {});
145
+ ctx.subagents.registerProvider(new CloudExpertProvider("cloud-claude", ctx, resolved, "claude"));
146
+ ctx.subagents.registerProvider(new CloudExpertProvider("cloud-codex", ctx, resolved, "codex"));
147
+ }
148
+
149
+ export { Config, apply, inject, name };
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "dsh-cloud-experts",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "description": "Cloud expert subagents (claude/codex on the fed VPS) over SSH fed-expert wrapper",
4
+ "version": "0.1.1",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./package.json": "./package.json",
10
+ "./cordis.patch.yml": "./cordis.patch.yml"
11
+ },
12
+ "files": [
13
+ "lib/index.js",
14
+ "cordis.patch.yml",
15
+ "README.md"
16
+ ],
17
+ "license": "MIT",
18
+ "dsh": {
19
+ "bundle": {
20
+ "patch": "./cordis.patch.yml"
21
+ }
22
+ },
23
+ "peerDependencies": {
24
+ "@deepseek-ai/cordis": "~4.1.0",
25
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
26
+ "@deepseek-ai/dsh-session": "0.2.2-rc.1",
27
+ "@deepseek-ai/dsh-subagent": "0.2.2-rc.1",
28
+ "@deepseek-ai/dsh-subprocess": "0.2.2-rc.1",
29
+ "@deepseek-ai/dsh-timeout": "0.2.2-rc.1"
30
+ },
31
+ "dependencies": {
32
+ "@deepseek-ai/dsh-tool-subagent": "0.2.2-rc.1",
33
+ "@deepseek-ai/schemastery": "~3.19.0"
34
+ }
35
+ }