dsh-prompt-star 0.1.0 → 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/lib/client.js CHANGED
@@ -4,20 +4,26 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react = require("react");
8
7
  let react_jsx_runtime = require("react/jsx-runtime");
9
- //#region src/client/StarButton.tsx
8
+ let react = require("react");
9
+ //#region lib/types/client/StarButton.js
10
10
  /**
11
- * The ⭐ button rendered beside the conversation input. On click it reads the
12
- * current draft (from the session input snapshot), asks the host to generate a
13
- * fuller prompt, and writes it back with `inputActions.setDraft` — the user
14
- * presses Ctrl/Cmd+Z if they want the original draft back.
11
+ * The ⭐ button rendered beside the conversation input.
12
+ *
13
+ * On click it reads the current draft (session input snapshot), asks the host
14
+ * (over the `/dsh-prompt-star` Connection RPC channel) to read a few project
15
+ * doc files as context, assembles a fuller, more efficient prompt on the client,
16
+ * then writes it back with `inputActions.setDraft`. Press Ctrl/Cmd+Z to restore
17
+ * the original draft.
15
18
  *
16
19
  * The slot framework composes this component's props: the session standard seat
17
- * (`useInput`, `inputActions`, `useConversation`) plus the register-time
18
- * `generate` face the client plugin injects. The interface below is the minimal
19
- * structural shape this component needs; it matches the composed slot props.
20
+ * (`useInput`, `inputActions`) plus the register-time `context` face this client
21
+ * plugin injects. The interfaces below are the minimal structural shapes this
22
+ * component needs; they match the composed slot props without importing the
23
+ * harness client types.
20
24
  */
25
+ /** Common project-doc filenames are probed on the HOST; the client never
26
+ * enumerates them (it just asks the host for the read context). */
21
27
  const buttonStyle = {
22
28
  all: "unset",
23
29
  display: "inline-flex",
@@ -32,7 +38,25 @@ window.__ModuleLoader__.load({
32
38
  opacity: .9,
33
39
  userSelect: "none"
34
40
  };
35
- function StarButton({ generate, useInput, inputActions, useActiveRoot }) {
41
+ /** Build a fuller, structured prompt from the draft + any project context. */
42
+ function assemblePrompt(draft, context) {
43
+ return [
44
+ "请把下面的【我的草稿】整理成一段完整、清晰、高效的提示词,直接输出整理后的提示词本身。",
45
+ "",
46
+ "整理要求:",
47
+ "1. 明确任务目标、所需上下文、约束条件与期望输出形式。",
48
+ "2. 若【项目文档】提供了相关规范、术语或背景,请吸收进去,使提示词更贴合项目。",
49
+ "3. 语气中立专业,结构清晰(可用编号、小节或列表),忠于草稿原意,不虚构。",
50
+ "4. 长度以覆盖草稿要点为准,不要过度扩写。",
51
+ "",
52
+ "# 项目文档",
53
+ context.docs.length === 0 ? "(未读取到项目文档)" : context.docs.map((doc) => `## ${doc.name}\n${doc.text}`).join("\n\n"),
54
+ "",
55
+ "# 我的草稿",
56
+ draft
57
+ ].join("\n");
58
+ }
59
+ function StarButton({ context, useInput, inputActions }) {
36
60
  const input = useInput();
37
61
  const [busy, setBusy] = (0, react.useState)(false);
38
62
  const [error, setError] = (0, react.useState)(void 0);
@@ -45,49 +69,82 @@ window.__ModuleLoader__.load({
45
69
  setBusy(true);
46
70
  setError(void 0);
47
71
  try {
48
- const result = await generate({
49
- draft,
50
- workspaceRoot: useActiveRoot?.(),
51
- intent: void 0
52
- });
53
- if (result.prompt && result.prompt !== draft) {
54
- inputActions.setDraft(result.prompt);
55
- if (result.degraded) setError("模型调用失败,已保留原草稿");
56
- } else setError(result.degraded ? "模型调用失败,未生成新内容" : "生成结果与原草稿相同,未替换");
72
+ const res = await context();
73
+ if (!res.ok) {
74
+ setError(`读取项目文档失败: ${res.error?.message ?? "未知错误"}`);
75
+ return;
76
+ }
77
+ const value = res.value;
78
+ const full = assemblePrompt(draft, value);
79
+ if (full && full !== draft) {
80
+ inputActions.setDraft(full);
81
+ setError(value.docs.length === 0 ? "已按草稿整理(未读取到项目文档)" : `已整理并读取 ${value.docs.length} 个项目文档`);
82
+ } else setError("未生成新内容");
57
83
  } catch (cause) {
58
- const message = cause instanceof Error ? cause.message : String(cause);
59
- setError(`生成失败: ${message}`);
84
+ setError(`生成失败: ${cause instanceof Error ? cause.message : String(cause)}`);
60
85
  } finally {
61
86
  setBusy(false);
62
87
  }
63
88
  }
64
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
89
+ return (0, react_jsx_runtime.jsx)("button", {
65
90
  type: "button",
66
91
  style: buttonStyle,
67
- title: error ?? "prompt-star: 生成更完整的提示词",
92
+ title: error ?? "prompt-star: 整理成更完整的提示词",
68
93
  disabled: busy,
69
94
  onClick,
70
95
  children: busy ? "…" : "⭐"
71
96
  });
72
97
  }
73
98
  //#endregion
74
- //#region src/client/index.ts
75
- /** Cordis services this client plugin needs. */
76
- const inject = ["slots", "remote"];
99
+ //#region lib/types/types.js
100
+ /**
101
+ * dsh-prompt-star — host↔client RPC protocol (shared vocabulary).
102
+ *
103
+ * Transport: the generic Connection RPC channel `/dsh-prompt-star`
104
+ * (the host registers it with `ctx.connection.rpc.handle`, the browser calls
105
+ * `ctx.connection.rpc.call('/dsh-prompt-star', endpoint, payload)`).
106
+ *
107
+ * The host sells exactly one capability: reading a few project documentation
108
+ * files for the current workspace (it CAN read file contents natively; the
109
+ * browser client cannot). Everything else — assembling the fuller prompt from
110
+ * the draft + that context — stays on the client.
111
+ */
112
+ /** Absolute logical Connection RPC channel owned by this plugin. */
113
+ const RPC_CHANNEL = "/dsh-prompt-star";
114
+ /** Channel-relative endpoint that reads project doc files on the host. */
115
+ const EP_CONTEXT = "context";
116
+ //#endregion
117
+ //#region lib/types/client/index.js
118
+ /**
119
+ * Client plugin: mounts the ⭐ button into the composer input tool row
120
+ * (`conversation.input.right`, a session-scoped list slot) and wires it to the
121
+ * host's `/dsh-prompt-star` Connection RPC channel.
122
+ *
123
+ * This client half only needs the slot registry and the `connection` service.
124
+ * On click the button asks the host for project doc context (the browser cannot
125
+ * read file contents itself), then assembles the fuller prompt on the client and
126
+ * writes it back with `inputActions.setDraft`.
127
+ */
128
+ /** Cordis services this client plugin needs: the slot registry and the
129
+ * Connection service (both provided by the stock web-app composition). */
130
+ const inject = ["slots", "connection"];
77
131
  /**
78
132
  * Client plugin body: register the ⭐ button once the slot registry and the
79
- * remote face are up.
133
+ * Connection service are up.
80
134
  * @param ctx - client root context.
81
135
  */
82
136
  function apply(ctx) {
83
- ctx.inject(["slots", "remote"], (scope) => {
84
- const promptStar = scope.remote.promptStar;
85
- scope.slots.inject("conversation.input.right", () => scope.slots.register({
86
- name: "conversation.input.right",
87
- id: "prompt-star",
88
- order: 0,
89
- inject: () => ({ generate: promptStar.generate })
90
- }, StarButton));
137
+ ctx.inject(["slots", "connection"], (scope) => {
138
+ const root = scope;
139
+ const rpc = root.connection.rpc;
140
+ root.slots.inject("conversation.input.right", () => {
141
+ root.slots.register({
142
+ name: "conversation.input.right",
143
+ id: "prompt-star",
144
+ order: 0,
145
+ inject: () => ({ context: () => rpc.call(RPC_CHANNEL, EP_CONTEXT, {}) })
146
+ }, StarButton);
147
+ });
91
148
  });
92
149
  }
93
150
  //#endregion
package/lib/index.js CHANGED
@@ -1,283 +1,99 @@
1
- import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
2
- import { readFile } from "node:fs/promises";
3
- import { isAbsolute, join } from "node:path";
4
- import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
5
- import { deadline } from "@deepseek-ai/dsh-timeout";
6
- import { deepFreeze } from "@deepseek-ai/dsh-util-values";
7
- //#region \0@oxc-project+runtime@0.148.0/helpers/esm/usingCtx.js
8
- function _usingCtx() {
9
- var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
10
- var n = Error();
11
- return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
12
- }, e = {}, n = [];
13
- function using(r, e) {
14
- if (null != e) {
15
- if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
16
- if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
17
- if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
18
- if ("function" != typeof o) throw new TypeError("Object is not disposable.");
19
- t && (o = function o() {
20
- try {
21
- t.call(e);
22
- } catch (r) {
23
- return Promise.reject(r);
24
- }
25
- }), n.push({
26
- v: e,
27
- d: o,
28
- a: r
29
- });
30
- } else r && n.push({
31
- d: e,
32
- a: r
33
- });
34
- return e;
35
- }
36
- return {
37
- e,
38
- u: using.bind(null, !1),
39
- a: using.bind(null, !0),
40
- d: function d() {
41
- var o, t = this.e, s = 0;
42
- function next() {
43
- for (; o = n.pop();) try {
44
- if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
45
- if (o.d) {
46
- var r = o.d.call(o.v);
47
- if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
48
- } else s |= 1;
49
- } catch (r) {
50
- return err(r);
51
- }
52
- if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
53
- if (t !== e) throw t;
54
- }
55
- function err(n) {
56
- return t = t !== e ? new r(n, t) : n, next();
57
- }
58
- return next();
59
- }
60
- };
61
- }
1
+ //#region lib/types/types.js
2
+ /**
3
+ * dsh-prompt-star — host↔client RPC protocol (shared vocabulary).
4
+ *
5
+ * Transport: the generic Connection RPC channel `/dsh-prompt-star`
6
+ * (the host registers it with `ctx.connection.rpc.handle`, the browser calls
7
+ * `ctx.connection.rpc.call('/dsh-prompt-star', endpoint, payload)`).
8
+ *
9
+ * The host sells exactly one capability: reading a few project documentation
10
+ * files for the current workspace (it CAN read file contents natively; the
11
+ * browser client cannot). Everything else — assembling the fuller prompt from
12
+ * the draft + that context — stays on the client.
13
+ */
14
+ /** Absolute logical Connection RPC channel owned by this plugin. */
15
+ const RPC_CHANNEL = "/dsh-prompt-star";
62
16
  //#endregion
63
- //#region src/host/generate.ts
17
+ //#region lib/types/index.js
64
18
  /**
65
- * Host implementation of one prompt-star generate: probe the project's doc
66
- * files for context, then call the configured default LLM to turn the draft +
67
- * context into a fuller prompt. Returns a best-effort result (never throws) so
68
- * a model failure degrades to a copy rather than failing the click.
19
+ * Host entry for dsh-prompt-star.
20
+ *
21
+ * The ⭐ button lives in the client half; this host half registers ONE generic
22
+ * Connection RPC channel (`/dsh-prompt-star`) that reads a few project
23
+ * documentation files for the current workspace. The browser client CANNOT read
24
+ * file contents (the stock web app exposes no content-reading remote), so the
25
+ * read happens here and is shipped to the browser over the Connection channel.
26
+ *
27
+ * This entry follows the standard DSH plugin export contract `apply(ctx)`.
28
+ *
29
+ * The host-side services (`ctx.connection.rpc`, `ctx.fs`) are consumed through
30
+ * small local structural types instead of importing the harness packages, so the
31
+ * plugin's `tsc` stays inside its own `rootDir` and never drags harness source
32
+ * files into its program.
69
33
  */
70
- /** Doc files we probe (first match by priority wins as main context; all read). */
71
- const DOC_FILE_NAMES = [
72
- "CLAUDE.md",
34
+ /** Project doc files probed, in priority order. Reads are best-effort. */
35
+ const DOC_CANDIDATES = [
36
+ "README.md",
37
+ "README",
73
38
  "AGENTS.md",
74
- ".cursorrules",
75
- "README.md"
39
+ "CLAUDE.md",
40
+ "CONTRIBUTING.md",
41
+ "docs/README.md",
42
+ "docs/index.md",
43
+ ".cursorrules"
76
44
  ];
77
- /** Per-file read cap; keeps a huge README from eating the model input budget. */
78
- const MAX_DOC_BYTES = 8e3;
79
- /** Output-token cap for the generated prompt. */
80
- const MAX_OUTPUT_TOKENS = 1200;
81
- /** End-to-end generate deadline. */
82
- const TIMEOUT_MS = 2e4;
83
- /** Timeout code registered for the capability. */
84
- const PROMPT_STAR_TIMEOUT_CODE = "PROMPT_STAR_TIMEOUT";
45
+ /** Per-doc content cap so a huge README cannot flood the input. */
46
+ const MAX_DOC_CHARS = 4e3;
85
47
  /**
86
- * Model-visible purpose. `dsh-llm` types this as the union
87
- * `'compaction' | 'session-title'`; this plugin's own purpose requires a
88
- * one-line addition to that union (see README "Patch dsh-llm"). The cast keeps
89
- * this package compiling before the patch lands.
48
+ * Minimal host plugin body: mount the `/dsh-prompt-star` RPC channel.
49
+ * @param ctx - host root context.
90
50
  */
91
- const PURPOSE = "prompt-star";
92
- /** Inline template skeletons the model reuses as a shape, not a strict form. */
93
- const SKELETONS = [
94
- {
95
- id: "request",
96
- title: "提需求",
97
- hint: "目标 / 用户 / 验收标准 / 边界 / 依赖"
98
- },
99
- {
100
- id: "bug",
101
- title: "改 bug",
102
- hint: "现象 / 复现步骤 / 期望 vs 实际 / 触发条件 / 环境"
103
- },
104
- {
105
- id: "letter",
106
- title: "写信",
107
- hint: "称呼 / 目的 / 语气 / 落款"
108
- },
109
- {
110
- id: "generic",
111
- title: "通用",
112
- hint: "目标 / 输入 / 期望输出 / 约束"
113
- }
114
- ];
115
- /** Default workspace root used when the caller cannot supply one. */
116
- function defaultRoot(ctx) {
117
- return process.cwd();
118
- }
119
- /** Cap a path at the workspace root (defence-in-depth: refuses abs paths outside root). */
120
- function keepInside(root, path) {
121
- if (isAbsolute(path)) return path.startsWith(root) ? path : void 0;
122
- return join(root, path);
123
- }
124
- /** Probe the doc files, reading the first `MAX_DOC_BYTES` bytes of each hit. */
125
- async function readDocFiles(root) {
126
- const sources = [];
127
- for (const name of DOC_FILE_NAMES) {
128
- const path = keepInside(root, name);
129
- if (path === void 0) continue;
130
- try {
131
- const buf = await readFile(path);
132
- sources.push({
133
- path,
134
- name,
135
- read: true,
136
- bytes: Math.min(buf.byteLength, MAX_DOC_BYTES)
137
- });
138
- } catch {
139
- sources.push({
140
- path,
141
- name,
142
- read: false,
143
- bytes: 0
144
- });
145
- }
146
- }
147
- return sources;
148
- }
149
- /** Frame the doc context into the system prompt (references, not whole files). */
150
- function frameContext(sources) {
151
- const hits = sources.filter((source) => source.read);
152
- if (hits.length === 0) return "当前项目没有可读的说明文件(未找到 CLAUDE.md / AGENTS.md / .cursorrules / README.md)。";
153
- return hits.map((source) => `--- ${source.name} (${source.bytes} 字节) ---\n<probe-only: use this file as project context>`).join("\n");
154
- }
155
- /** Compose the system instruction that turns a draft into a fuller prompt. */
156
- function buildSystem(sources) {
157
- return [
158
- "你是提示词优化器。用户会给一段随手写的草稿,你把它扩写成一版更完整、更省 token、意图清晰的提示词。",
159
- "阅读下面用到的项目说明文件(若有)作为上下文,不要照抄,只借用其术语、技术栈与约定。",
160
- "可参考的模板骨架(选择与该草稿最贴切的一个作为形状,按需补充):",
161
- SKELETONS.map((skeleton) => `- ${skeleton.title}(${skeleton.id}):${skeleton.hint}`).join("\n"),
162
- "输出要求:",
163
- "- 只输出优化后的提示词正文,不要解释、不要 Markdown 包裹、不要前缀。",
164
- "- 保留用户草稿的原意,补全缺失的上下文与验收标准。",
165
- "- 用草稿同语言输出。",
166
- "",
167
- frameContext(sources)
168
- ].join("\n");
169
- }
170
- /** Map the LLM finish reason to an error, mirroring session-title-llm. */
171
- function finishError(finish) {
172
- switch (finish.kind) {
173
- case "stop": return;
174
- case "error":
175
- case "aborted": {
176
- const error = new Error(finish.failure.message);
177
- error.code = finish.failure.code;
178
- return error;
179
- }
180
- case "max-tokens": return /* @__PURE__ */ new Error("prompt-star: output reached maxTokens");
181
- case "tool-calls": return /* @__PURE__ */ new Error("prompt-star: model unexpectedly requested a tool");
182
- default: return /* @__PURE__ */ new Error(`prompt-star: unsupported finish reason "${String(finish.kind)}"`);
183
- }
184
- }
185
- /** Read the plain text of one assembled LLM reply. */
186
- function blocksToText(blocks) {
187
- return blocks.filter((block) => block.type === "text").map((block) => block.text).join(" ");
188
- }
189
- /**
190
- * Generate the optimized prompt.
191
- * @param ctx - Host context carrying the `llm` service.
192
- * @param request - draft + optional workspace root / intent.
193
- * @returns a settled result; degraded=true when the model call failed.
194
- */
195
- async function generatePrompt(ctx, request) {
196
- const sources = await readDocFiles(request.workspaceRoot ?? defaultRoot(ctx));
197
- const system = buildSystem(sources);
198
- const userText = request.intent?.trim() ? `草稿:\n${request.draft}\n\n请侧重「${request.intent}」模板。` : `草稿:\n${request.draft}`;
199
- try {
51
+ function apply(ctx) {
52
+ const host = ctx;
53
+ host.connection.rpc.handle(RPC_CHANNEL, async (endpoint, payload) => {
54
+ if (endpoint !== "context") return {
55
+ ok: false,
56
+ error: {
57
+ code: "unknown_endpoint",
58
+ message: `unknown endpoint '${endpoint}'`,
59
+ details: {}
60
+ }
61
+ };
200
62
  try {
201
- var _usingCtx$1 = _usingCtx();
202
- const messages = [createUserMessage({
203
- content: [{
204
- type: "text",
205
- text: userText
206
- }],
207
- source: {
208
- kind: "plugin",
209
- plugin: "dsh-prompt-star"
63
+ const root = (payload ?? {}).cwd?.trim() || process.cwd();
64
+ const docs = [];
65
+ const seen = /* @__PURE__ */ new Set();
66
+ let filesRead = 0;
67
+ for (const name of DOC_CANDIDATES) try {
68
+ const target = host.fs.resolve(name, { cwd: root });
69
+ const trimmed = (await host.fs.readText(target))?.trim();
70
+ if (trimmed && !seen.has(name)) {
71
+ seen.add(name);
72
+ docs.push({
73
+ name,
74
+ text: trimmed.slice(0, MAX_DOC_CHARS)
75
+ });
76
+ filesRead += 1;
210
77
  }
211
- })];
212
- const callDeadline = _usingCtx$1.u(deadline(request.signal ?? new AbortController().signal, TIMEOUT_MS, PROMPT_STAR_TIMEOUT_CODE));
213
- const options = deepFreeze({
214
- provider: void 0,
215
- model: void 0,
216
- messages,
217
- system,
218
- maxTokens: MAX_OUTPUT_TOKENS,
219
- purpose: PURPOSE,
220
- signal: callDeadline.signal
221
- });
222
- callDeadline.signal.throwIfAborted();
223
- const assembler = new BlockAssembler();
224
- for await (const chunk of ctx.llm.stream(options)) {
225
- callDeadline.signal.throwIfAborted();
226
- assembler.push(chunk);
227
- }
228
- callDeadline.signal.throwIfAborted();
229
- const terminalError = finishError(assembler.finish);
230
- if (terminalError !== void 0) throw terminalError;
231
- const blocks = assembler.blocks();
232
- if (blocks.some((block) => block.type === "tool-call")) throw new Error("prompt-star: output must contain text only");
233
- const prompt = blocksToText(blocks).trim();
234
- if (prompt.length === 0) throw new Error("prompt-star: model produced no text");
78
+ } catch {}
79
+ return {
80
+ ok: true,
81
+ value: {
82
+ docs,
83
+ filesRead
84
+ }
85
+ };
86
+ } catch (err) {
235
87
  return {
236
- prompt,
237
- intent: request.intent ?? inferIntent(prompt, system),
238
- sources,
239
- model: void 0,
240
- degraded: false
88
+ ok: false,
89
+ error: {
90
+ code: "internal",
91
+ message: err instanceof Error ? err.message : String(err),
92
+ details: {}
93
+ }
241
94
  };
242
- } catch (_) {
243
- _usingCtx$1.e = _;
244
- } finally {
245
- _usingCtx$1.d();
246
95
  }
247
- } catch (error) {
248
- return {
249
- prompt: request.draft,
250
- intent: request.intent ?? "generic",
251
- sources,
252
- model: void 0,
253
- degraded: true
254
- };
255
- }
96
+ });
256
97
  }
257
- /** Cheap intent label: prefer the supplied one, else the first skeleton hit. */
258
- function inferIntent(prompt, _system) {
259
- prompt.toLowerCase();
260
- return SKELETONS.find((skeleton) => skeleton.id === "request" || skeleton.id === "bug" || skeleton.id === "letter")?.id ?? "generic";
261
- }
262
- //#endregion
263
- //#region src/index.ts
264
- /** Host service backing the generated `ctx.remote.promptStar` namespace. */
265
- var PromptStarService = class extends TypertRemoteService {
266
- static inject = ["typert"];
267
- ctx;
268
- /** @param ctx - Host context exposing the LLM and workspace services. */
269
- constructor(ctx) {
270
- super(ctx, "promptStar", { namespace: "promptStar" });
271
- this.ctx = ctx;
272
- }
273
- /**
274
- * Turn a draft + project context into a fuller prompt.
275
- * @param request - draft and optional workspace root / intent / signal.
276
- * @returns generated prompt and the doc files probed for context.
277
- */
278
- @Remote("generate") async generate(request) {
279
- return generatePrompt(this.ctx, request);
280
- }
281
- };
282
98
  //#endregion
283
- export { PromptStarService, PromptStarService as default };
99
+ export { apply };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The ⭐ button rendered beside the conversation input.
3
+ *
4
+ * On click it reads the current draft (session input snapshot), asks the host
5
+ * (over the `/dsh-prompt-star` Connection RPC channel) to read a few project
6
+ * doc files as context, assembles a fuller, more efficient prompt on the client,
7
+ * then writes it back with `inputActions.setDraft`. Press Ctrl/Cmd+Z to restore
8
+ * the original draft.
9
+ *
10
+ * The slot framework composes this component's props: the session standard seat
11
+ * (`useInput`, `inputActions`) plus the register-time `context` face this client
12
+ * plugin injects. The interfaces below are the minimal structural shapes this
13
+ * component needs; they match the composed slot props without importing the
14
+ * harness client types.
15
+ */
16
+ import { type ReactElement } from 'react';
17
+ import type { ContextRpcResult } from '../types';
18
+ export interface StarButtonProps {
19
+ /** Host RPC caller that returns the project doc context. */
20
+ context(): Promise<ContextRpcResult>;
21
+ /** Session input snapshot hook (returns the current draft). */
22
+ useInput(): {
23
+ draft: string;
24
+ };
25
+ /** Session public input actions (write the whole draft). */
26
+ inputActions: {
27
+ setDraft(text: string): void;
28
+ };
29
+ }
30
+ export declare function StarButton({ context, useInput, inputActions }: StarButtonProps): ReactElement;
31
+ //# sourceMappingURL=StarButton.d.ts.map
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * The ⭐ button rendered beside the conversation input.
4
+ *
5
+ * On click it reads the current draft (session input snapshot), asks the host
6
+ * (over the `/dsh-prompt-star` Connection RPC channel) to read a few project
7
+ * doc files as context, assembles a fuller, more efficient prompt on the client,
8
+ * then writes it back with `inputActions.setDraft`. Press Ctrl/Cmd+Z to restore
9
+ * the original draft.
10
+ *
11
+ * The slot framework composes this component's props: the session standard seat
12
+ * (`useInput`, `inputActions`) plus the register-time `context` face this client
13
+ * plugin injects. The interfaces below are the minimal structural shapes this
14
+ * component needs; they match the composed slot props without importing the
15
+ * harness client types.
16
+ */
17
+ import { useState } from 'react';
18
+ /** Common project-doc filenames are probed on the HOST; the client never
19
+ * enumerates them (it just asks the host for the read context). */
20
+ const buttonStyle = {
21
+ all: 'unset',
22
+ display: 'inline-flex',
23
+ alignItems: 'center',
24
+ justifyContent: 'center',
25
+ width: '30px',
26
+ height: '30px',
27
+ cursor: 'pointer',
28
+ borderRadius: '6px',
29
+ fontSize: '16px',
30
+ lineHeight: 1,
31
+ opacity: 0.9,
32
+ userSelect: 'none',
33
+ };
34
+ /** Build a fuller, structured prompt from the draft + any project context. */
35
+ function assemblePrompt(draft, context) {
36
+ const projectContext = context.docs.length === 0
37
+ ? '(未读取到项目文档)'
38
+ : context.docs.map((doc) => `## ${doc.name}\n${doc.text}`).join('\n\n');
39
+ return [
40
+ '请把下面的【我的草稿】整理成一段完整、清晰、高效的提示词,直接输出整理后的提示词本身。',
41
+ '',
42
+ '整理要求:',
43
+ '1. 明确任务目标、所需上下文、约束条件与期望输出形式。',
44
+ '2. 若【项目文档】提供了相关规范、术语或背景,请吸收进去,使提示词更贴合项目。',
45
+ '3. 语气中立专业,结构清晰(可用编号、小节或列表),忠于草稿原意,不虚构。',
46
+ '4. 长度以覆盖草稿要点为准,不要过度扩写。',
47
+ '',
48
+ '# 项目文档',
49
+ projectContext,
50
+ '',
51
+ '# 我的草稿',
52
+ draft,
53
+ ].join('\n');
54
+ }
55
+ export function StarButton({ context, useInput, inputActions }) {
56
+ const input = useInput();
57
+ const [busy, setBusy] = useState(false);
58
+ const [error, setError] = useState(undefined);
59
+ async function onClick() {
60
+ const draft = input.draft.trim();
61
+ if (draft.length === 0) {
62
+ setError('草稿为空,先写点什么再点 ⭐');
63
+ return;
64
+ }
65
+ setBusy(true);
66
+ setError(undefined);
67
+ try {
68
+ const res = await context();
69
+ if (!res.ok) {
70
+ setError(`读取项目文档失败: ${res.error?.message ?? '未知错误'}`);
71
+ return;
72
+ }
73
+ const value = res.value;
74
+ const full = assemblePrompt(draft, value);
75
+ if (full && full !== draft) {
76
+ inputActions.setDraft(full);
77
+ setError(value.docs.length === 0
78
+ ? '已按草稿整理(未读取到项目文档)'
79
+ : `已整理并读取 ${value.docs.length} 个项目文档`);
80
+ }
81
+ else {
82
+ setError('未生成新内容');
83
+ }
84
+ }
85
+ catch (cause) {
86
+ const message = cause instanceof Error ? cause.message : String(cause);
87
+ setError(`生成失败: ${message}`);
88
+ }
89
+ finally {
90
+ setBusy(false);
91
+ }
92
+ }
93
+ return (_jsx("button", { type: "button", style: buttonStyle, title: error ?? 'prompt-star: 整理成更完整的提示词', disabled: busy, onClick: onClick, children: busy ? '…' : '⭐' }));
94
+ }
95
+ //# sourceMappingURL=StarButton.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Client plugin: mounts the ⭐ button into the composer input tool row
3
+ * (`conversation.input.right`, a session-scoped list slot) and wires it to the
4
+ * host's `/dsh-prompt-star` Connection RPC channel.
5
+ *
6
+ * This client half only needs the slot registry and the `connection` service.
7
+ * On click the button asks the host for project doc context (the browser cannot
8
+ * read file contents itself), then assembles the fuller prompt on the client and
9
+ * writes it back with `inputActions.setDraft`.
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ /** Cordis services this client plugin needs: the slot registry and the
13
+ * Connection service (both provided by the stock web-app composition). */
14
+ export declare const inject: string[];
15
+ /**
16
+ * Client plugin body: register the ⭐ button once the slot registry and the
17
+ * Connection service are up.
18
+ * @param ctx - client root context.
19
+ */
20
+ export declare function apply(ctx: Context): void;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Client plugin: mounts the ⭐ button into the composer input tool row
3
+ * (`conversation.input.right`, a session-scoped list slot) and wires it to the
4
+ * host's `/dsh-prompt-star` Connection RPC channel.
5
+ *
6
+ * This client half only needs the slot registry and the `connection` service.
7
+ * On click the button asks the host for project doc context (the browser cannot
8
+ * read file contents itself), then assembles the fuller prompt on the client and
9
+ * writes it back with `inputActions.setDraft`.
10
+ */
11
+ import { StarButton } from "./StarButton.js";
12
+ import { RPC_CHANNEL, EP_CONTEXT } from '../types';
13
+ /** Cordis services this client plugin needs: the slot registry and the
14
+ * Connection service (both provided by the stock web-app composition). */
15
+ export const inject = ['slots', 'connection'];
16
+ /**
17
+ * Client plugin body: register the ⭐ button once the slot registry and the
18
+ * Connection service are up.
19
+ * @param ctx - client root context.
20
+ */
21
+ export function apply(ctx) {
22
+ ctx.inject(['slots', 'connection'], (scope) => {
23
+ const root = scope;
24
+ const rpc = root.connection.rpc;
25
+ root.slots.inject('conversation.input.right', () => {
26
+ root.slots.register({
27
+ name: 'conversation.input.right',
28
+ id: 'prompt-star',
29
+ order: 0,
30
+ inject: () => ({
31
+ context: () => rpc.call(RPC_CHANNEL, EP_CONTEXT, {}),
32
+ }),
33
+ }, StarButton);
34
+ });
35
+ });
36
+ }
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Host entry for dsh-prompt-star.
3
+ *
4
+ * The ⭐ button lives in the client half; this host half registers ONE generic
5
+ * Connection RPC channel (`/dsh-prompt-star`) that reads a few project
6
+ * documentation files for the current workspace. The browser client CANNOT read
7
+ * file contents (the stock web app exposes no content-reading remote), so the
8
+ * read happens here and is shipped to the browser over the Connection channel.
9
+ *
10
+ * This entry follows the standard DSH plugin export contract `apply(ctx)`.
11
+ *
12
+ * The host-side services (`ctx.connection.rpc`, `ctx.fs`) are consumed through
13
+ * small local structural types instead of importing the harness packages, so the
14
+ * plugin's `tsc` stays inside its own `rootDir` and never drags harness source
15
+ * files into its program.
16
+ */
17
+ import type { Context } from '@deepseek-ai/cordis';
18
+ /**
19
+ * Minimal host plugin body: mount the `/dsh-prompt-star` RPC channel.
20
+ * @param ctx - host root context.
21
+ */
22
+ export declare function apply(ctx: Context): void;
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Host entry for dsh-prompt-star.
3
+ *
4
+ * The ⭐ button lives in the client half; this host half registers ONE generic
5
+ * Connection RPC channel (`/dsh-prompt-star`) that reads a few project
6
+ * documentation files for the current workspace. The browser client CANNOT read
7
+ * file contents (the stock web app exposes no content-reading remote), so the
8
+ * read happens here and is shipped to the browser over the Connection channel.
9
+ *
10
+ * This entry follows the standard DSH plugin export contract `apply(ctx)`.
11
+ *
12
+ * The host-side services (`ctx.connection.rpc`, `ctx.fs`) are consumed through
13
+ * small local structural types instead of importing the harness packages, so the
14
+ * plugin's `tsc` stays inside its own `rootDir` and never drags harness source
15
+ * files into its program.
16
+ */
17
+ import { RPC_CHANNEL, EP_CONTEXT } from './types';
18
+ /** Project doc files probed, in priority order. Reads are best-effort. */
19
+ const DOC_CANDIDATES = [
20
+ 'README.md',
21
+ 'README',
22
+ 'AGENTS.md',
23
+ 'CLAUDE.md',
24
+ 'CONTRIBUTING.md',
25
+ 'docs/README.md',
26
+ 'docs/index.md',
27
+ '.cursorrules',
28
+ ];
29
+ /** Per-doc content cap so a huge README cannot flood the input. */
30
+ const MAX_DOC_CHARS = 4000;
31
+ /**
32
+ * Minimal host plugin body: mount the `/dsh-prompt-star` RPC channel.
33
+ * @param ctx - host root context.
34
+ */
35
+ export function apply(ctx) {
36
+ const host = ctx;
37
+ // Registrations on `ctx.connection.rpc` belong to this plugin's Cordis fiber,
38
+ // so they are removed automatically when the plugin is torn down.
39
+ host.connection.rpc.handle(RPC_CHANNEL, async (endpoint, payload) => {
40
+ if (endpoint !== EP_CONTEXT) {
41
+ return {
42
+ ok: false,
43
+ error: { code: 'unknown_endpoint', message: `unknown endpoint '${endpoint}'`, details: {} },
44
+ };
45
+ }
46
+ try {
47
+ const req = (payload ?? {});
48
+ const root = req.cwd?.trim() || process.cwd();
49
+ const docs = [];
50
+ const seen = new Set();
51
+ let filesRead = 0;
52
+ for (const name of DOC_CANDIDATES) {
53
+ try {
54
+ const target = host.fs.resolve(name, { cwd: root });
55
+ const text = await host.fs.readText(target);
56
+ const trimmed = text?.trim();
57
+ if (trimmed && !seen.has(name)) {
58
+ seen.add(name);
59
+ docs.push({ name, text: trimmed.slice(0, MAX_DOC_CHARS) });
60
+ filesRead += 1;
61
+ }
62
+ }
63
+ catch {
64
+ // Absent / unreadable / outside the workspace: leave it out.
65
+ }
66
+ }
67
+ return { ok: true, value: { docs, filesRead } };
68
+ }
69
+ catch (err) {
70
+ return {
71
+ ok: false,
72
+ error: {
73
+ code: 'internal',
74
+ message: err instanceof Error ? err.message : String(err),
75
+ details: {},
76
+ },
77
+ };
78
+ }
79
+ });
80
+ }
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * dsh-prompt-star — host↔client RPC protocol (shared vocabulary).
3
+ *
4
+ * Transport: the generic Connection RPC channel `/dsh-prompt-star`
5
+ * (the host registers it with `ctx.connection.rpc.handle`, the browser calls
6
+ * `ctx.connection.rpc.call('/dsh-prompt-star', endpoint, payload)`).
7
+ *
8
+ * The host sells exactly one capability: reading a few project documentation
9
+ * files for the current workspace (it CAN read file contents natively; the
10
+ * browser client cannot). Everything else — assembling the fuller prompt from
11
+ * the draft + that context — stays on the client.
12
+ */
13
+ /** Absolute logical Connection RPC channel owned by this plugin. */
14
+ export declare const RPC_CHANNEL = "/dsh-prompt-star";
15
+ /** Channel-relative endpoint that reads project doc files on the host. */
16
+ export declare const EP_CONTEXT = "context";
17
+ export interface GenerateContextRequest {
18
+ /**
19
+ * Optional workspace directory to probe for doc files. When absent the host
20
+ * falls back to the DSH process working directory.
21
+ */
22
+ cwd?: string;
23
+ }
24
+ export interface DocContext {
25
+ /** One entry per successfully read project-doc file. */
26
+ docs: Array<{
27
+ name: string;
28
+ text: string;
29
+ }>;
30
+ /** Number of doc files read. */
31
+ filesRead: number;
32
+ }
33
+ /** The `context` endpoint's success/error envelope (Connection RPC result). */
34
+ export type ContextRpcResult = {
35
+ ok: true;
36
+ value: DocContext;
37
+ } | {
38
+ ok: false;
39
+ error: {
40
+ code: string;
41
+ message: string;
42
+ details: object;
43
+ };
44
+ };
45
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * dsh-prompt-star — host↔client RPC protocol (shared vocabulary).
3
+ *
4
+ * Transport: the generic Connection RPC channel `/dsh-prompt-star`
5
+ * (the host registers it with `ctx.connection.rpc.handle`, the browser calls
6
+ * `ctx.connection.rpc.call('/dsh-prompt-star', endpoint, payload)`).
7
+ *
8
+ * The host sells exactly one capability: reading a few project documentation
9
+ * files for the current workspace (it CAN read file contents natively; the
10
+ * browser client cannot). Everything else — assembling the fuller prompt from
11
+ * the draft + that context — stays on the client.
12
+ */
13
+ /** Absolute logical Connection RPC channel owned by this plugin. */
14
+ export const RPC_CHANNEL = '/dsh-prompt-star';
15
+ /** Channel-relative endpoint that reads project doc files on the host. */
16
+ export const EP_CONTEXT = 'context';
17
+ //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-prompt-star",
3
- "version": "0.1.0",
4
- "description": "DSH plugin: a ⭐ button beside the conversation input that reads your draft + the project's doc files and generates a fuller, more efficient prompt, filling the input directly (Ctrl/Cmd+Z to undo).",
3
+ "version": "0.1.3",
4
+ "description": "DSH plugin: a ⭐ button beside the conversation input that reads your draft + the project's doc files and turns them into a fuller, more efficient prompt, filling the input directly (Ctrl/Cmd+Z to undo).",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -29,13 +29,8 @@
29
29
  "patch": "./cordis.patch.yml"
30
30
  },
31
31
  "client": {
32
- "external": [
33
- "@deepseek-ai/dsh-api-gateway/client"
34
- ],
35
32
  "inject": [
36
- "@deepseek-ai/dsh-api-gateway",
37
- "@deepseek-ai/dsh-client-connection",
38
- "@deepseek-ai/dsh-client-locale"
33
+ "@deepseek-ai/dsh-client-connection"
39
34
  ],
40
35
  "platform": "web"
41
36
  }
@@ -49,21 +44,21 @@
49
44
  ],
50
45
  "peerDependencies": {
51
46
  "@deepseek-ai/cordis": "^4.0.2",
52
- "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1",
53
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
54
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-rc.1",
47
+ "@deepseek-ai/dsh-client-connection": "^0.1.2-rc.1",
48
+ "@deepseek-ai/dsh-fs": "^0.1.2-rc.1",
55
49
  "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
56
- "@deepseek-ai/dsh-client-store": "^0.1.2-rc.1",
57
50
  "react": "^18.3.1"
58
51
  },
59
52
  "devDependencies": {
60
53
  "@types/react": "~18.3.1",
61
54
  "react": "^18.3.1",
62
- "tsdown": "^0.14.0",
55
+ "tsdown": "^0.22.2",
63
56
  "typescript": "^5.5.0"
64
57
  },
65
58
  "scripts": {
66
59
  "bundle": "tsdown",
60
+ "bundle:host": "tsdown --env.DSH_BUILD_FACE host",
61
+ "bundle:client": "tsdown --env.DSH_BUILD_FACE client",
67
62
  "watch": "tsdown --watch"
68
63
  },
69
64
  "license": "MIT"