dsh-prompt-star 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/lib/client.js +52 -74
- package/lib/index.js +89 -11
- package/lib/types/client/StarButton.d.ts +13 -36
- package/lib/types/client/StarButton.js +23 -51
- package/lib/types/client/index.d.ts +8 -8
- package/lib/types/client/index.js +15 -12
- package/lib/types/index.d.ts +13 -11
- package/lib/types/index.js +71 -12
- package/lib/types/types.d.ts +38 -39
- package/lib/types/types.js +14 -3
- package/package.json +4 -11
package/lib/client.js
CHANGED
|
@@ -10,29 +10,20 @@ window.__ModuleLoader__.load({
|
|
|
10
10
|
/**
|
|
11
11
|
* The ⭐ button rendered beside the conversation input.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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.
|
|
18
18
|
*
|
|
19
19
|
* The slot framework composes this component's props: the session standard seat
|
|
20
|
-
* (`useInput`, `inputActions
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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.
|
|
24
24
|
*/
|
|
25
|
-
/** Common project-doc filenames probed
|
|
26
|
-
|
|
27
|
-
"README.md",
|
|
28
|
-
"README",
|
|
29
|
-
"AGENTS.md",
|
|
30
|
-
"CLAUDE.md",
|
|
31
|
-
"CONTRIBUTING.md",
|
|
32
|
-
"docs/README.md",
|
|
33
|
-
"docs/index.md",
|
|
34
|
-
".cursorrules"
|
|
35
|
-
];
|
|
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). */
|
|
36
27
|
const buttonStyle = {
|
|
37
28
|
all: "unset",
|
|
38
29
|
display: "inline-flex",
|
|
@@ -47,35 +38,7 @@ window.__ModuleLoader__.load({
|
|
|
47
38
|
opacity: .9,
|
|
48
39
|
userSelect: "none"
|
|
49
40
|
};
|
|
50
|
-
/**
|
|
51
|
-
* or unreadable file is skipped rather than breaking the button. */
|
|
52
|
-
async function gatherContext(workspaceFiles, sessionId) {
|
|
53
|
-
const docs = [];
|
|
54
|
-
const seen = /* @__PURE__ */ new Set();
|
|
55
|
-
let filesRead = 0;
|
|
56
|
-
for (const path of DOC_CANDIDATES) try {
|
|
57
|
-
const result = await workspaceFiles.read(sessionId, path, {
|
|
58
|
-
offset: 1,
|
|
59
|
-
limit: 120
|
|
60
|
-
});
|
|
61
|
-
if (result.ok && result.value?.text) {
|
|
62
|
-
const text = result.value.text.trim();
|
|
63
|
-
if (text.length > 0 && !seen.has(path)) {
|
|
64
|
-
seen.add(path);
|
|
65
|
-
docs.push({
|
|
66
|
-
name: path,
|
|
67
|
-
text
|
|
68
|
-
});
|
|
69
|
-
filesRead += 1;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} catch {}
|
|
73
|
-
return {
|
|
74
|
-
docs,
|
|
75
|
-
filesRead
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/** Assemble a fuller, structured prompt from the draft + any project context. */
|
|
41
|
+
/** Build a fuller, structured prompt from the draft + any project context. */
|
|
79
42
|
function assemblePrompt(draft, context) {
|
|
80
43
|
return [
|
|
81
44
|
"请把下面的【我的草稿】整理成一段完整、清晰、高效的提示词,直接输出整理后的提示词本身。",
|
|
@@ -87,13 +50,13 @@ window.__ModuleLoader__.load({
|
|
|
87
50
|
"4. 长度以覆盖草稿要点为准,不要过度扩写。",
|
|
88
51
|
"",
|
|
89
52
|
"# 项目文档",
|
|
90
|
-
context.
|
|
53
|
+
context.docs.length === 0 ? "(未读取到项目文档)" : context.docs.map((doc) => `## ${doc.name}\n${doc.text}`).join("\n\n"),
|
|
91
54
|
"",
|
|
92
55
|
"# 我的草稿",
|
|
93
56
|
draft
|
|
94
57
|
].join("\n");
|
|
95
58
|
}
|
|
96
|
-
function StarButton({
|
|
59
|
+
function StarButton({ context, useInput, inputActions }) {
|
|
97
60
|
const input = useInput();
|
|
98
61
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
99
62
|
const [error, setError] = (0, react.useState)(void 0);
|
|
@@ -106,11 +69,16 @@ window.__ModuleLoader__.load({
|
|
|
106
69
|
setBusy(true);
|
|
107
70
|
setError(void 0);
|
|
108
71
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
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);
|
|
111
79
|
if (full && full !== draft) {
|
|
112
80
|
inputActions.setDraft(full);
|
|
113
|
-
setError(
|
|
81
|
+
setError(value.docs.length === 0 ? "已按草稿整理(未读取到项目文档)" : `已整理并读取 ${value.docs.length} 个项目文档`);
|
|
114
82
|
} else setError("未生成新内容");
|
|
115
83
|
} catch (cause) {
|
|
116
84
|
setError(`生成失败: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
@@ -128,43 +96,53 @@ window.__ModuleLoader__.load({
|
|
|
128
96
|
});
|
|
129
97
|
}
|
|
130
98
|
//#endregion
|
|
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
|
|
131
117
|
//#region lib/types/client/index.js
|
|
132
118
|
/**
|
|
133
119
|
* Client plugin: mounts the ⭐ button into the composer input tool row
|
|
134
120
|
* (`conversation.input.right`, a session-scoped list slot) and wires it to the
|
|
135
|
-
*
|
|
121
|
+
* host's `/dsh-prompt-star` Connection RPC channel.
|
|
136
122
|
*
|
|
137
|
-
* This
|
|
138
|
-
*
|
|
139
|
-
* assembles
|
|
140
|
-
*
|
|
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`.
|
|
141
127
|
*/
|
|
142
|
-
/** Cordis services this client plugin needs: the slot registry and the
|
|
143
|
-
*
|
|
144
|
-
const inject = [
|
|
145
|
-
"slots",
|
|
146
|
-
"remote",
|
|
147
|
-
"remote.workspaceFiles"
|
|
148
|
-
];
|
|
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"];
|
|
149
131
|
/**
|
|
150
132
|
* Client plugin body: register the ⭐ button once the slot registry and the
|
|
151
|
-
*
|
|
133
|
+
* Connection service are up.
|
|
152
134
|
* @param ctx - client root context.
|
|
153
135
|
*/
|
|
154
136
|
function apply(ctx) {
|
|
155
|
-
ctx.inject([
|
|
156
|
-
"slots",
|
|
157
|
-
"remote",
|
|
158
|
-
"remote.workspaceFiles"
|
|
159
|
-
], (scope) => {
|
|
137
|
+
ctx.inject(["slots", "connection"], (scope) => {
|
|
160
138
|
const root = scope;
|
|
161
|
-
const
|
|
139
|
+
const rpc = root.connection.rpc;
|
|
162
140
|
root.slots.inject("conversation.input.right", () => {
|
|
163
141
|
root.slots.register({
|
|
164
142
|
name: "conversation.input.right",
|
|
165
143
|
id: "prompt-star",
|
|
166
144
|
order: 0,
|
|
167
|
-
inject: () => ({
|
|
145
|
+
inject: () => ({ context: () => rpc.call(RPC_CHANNEL, EP_CONTEXT, {}) })
|
|
168
146
|
}, StarButton);
|
|
169
147
|
});
|
|
170
148
|
});
|
package/lib/index.js
CHANGED
|
@@ -1,21 +1,99 @@
|
|
|
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";
|
|
16
|
+
//#endregion
|
|
1
17
|
//#region lib/types/index.js
|
|
2
18
|
/**
|
|
3
19
|
* Host entry for dsh-prompt-star.
|
|
4
20
|
*
|
|
5
|
-
* The ⭐ button
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* bundle mounted as a Loader entry — which is what makes `dsh-client-modules`
|
|
11
|
-
* discover and serve its client bundle.
|
|
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.
|
|
12
26
|
*
|
|
13
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.
|
|
14
33
|
*/
|
|
34
|
+
/** Project doc files probed, in priority order. Reads are best-effort. */
|
|
35
|
+
const DOC_CANDIDATES = [
|
|
36
|
+
"README.md",
|
|
37
|
+
"README",
|
|
38
|
+
"AGENTS.md",
|
|
39
|
+
"CLAUDE.md",
|
|
40
|
+
"CONTRIBUTING.md",
|
|
41
|
+
"docs/README.md",
|
|
42
|
+
"docs/index.md",
|
|
43
|
+
".cursorrules"
|
|
44
|
+
];
|
|
45
|
+
/** Per-doc content cap so a huge README cannot flood the input. */
|
|
46
|
+
const MAX_DOC_CHARS = 4e3;
|
|
15
47
|
/**
|
|
16
|
-
* Minimal host plugin body
|
|
17
|
-
*
|
|
48
|
+
* Minimal host plugin body: mount the `/dsh-prompt-star` RPC channel.
|
|
49
|
+
* @param ctx - host root context.
|
|
18
50
|
*/
|
|
19
|
-
function apply(
|
|
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
|
+
};
|
|
62
|
+
try {
|
|
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;
|
|
77
|
+
}
|
|
78
|
+
} catch {}
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
value: {
|
|
82
|
+
docs,
|
|
83
|
+
filesRead
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
error: {
|
|
90
|
+
code: "internal",
|
|
91
|
+
message: err instanceof Error ? err.message : String(err),
|
|
92
|
+
details: {}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
20
98
|
//#endregion
|
|
21
|
-
export { apply
|
|
99
|
+
export { apply };
|
|
@@ -1,44 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The ⭐ button rendered beside the conversation input.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
9
|
*
|
|
10
10
|
* The slot framework composes this component's props: the session standard seat
|
|
11
|
-
* (`useInput`, `inputActions
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
15
|
*/
|
|
16
16
|
import { type ReactElement } from 'react';
|
|
17
|
-
|
|
18
|
-
export interface WorkspaceFilesRemote {
|
|
19
|
-
read(sessionId: string, path: string, range: {
|
|
20
|
-
offset?: number;
|
|
21
|
-
limit?: number;
|
|
22
|
-
}, signal?: AbortSignal): Promise<{
|
|
23
|
-
ok: boolean;
|
|
24
|
-
value?: {
|
|
25
|
-
text: string;
|
|
26
|
-
eof?: boolean;
|
|
27
|
-
};
|
|
28
|
-
}>;
|
|
29
|
-
list(sessionId: string, path: string, signal?: AbortSignal): Promise<{
|
|
30
|
-
ok: boolean;
|
|
31
|
-
value?: {
|
|
32
|
-
entries: Array<{
|
|
33
|
-
name: string;
|
|
34
|
-
type: string;
|
|
35
|
-
}>;
|
|
36
|
-
};
|
|
37
|
-
}>;
|
|
38
|
-
}
|
|
17
|
+
import type { ContextRpcResult } from '../types';
|
|
39
18
|
export interface StarButtonProps {
|
|
40
|
-
/**
|
|
41
|
-
|
|
19
|
+
/** Host RPC caller that returns the project doc context. */
|
|
20
|
+
context(): Promise<ContextRpcResult>;
|
|
42
21
|
/** Session input snapshot hook (returns the current draft). */
|
|
43
22
|
useInput(): {
|
|
44
23
|
draft: string;
|
|
@@ -47,8 +26,6 @@ export interface StarButtonProps {
|
|
|
47
26
|
inputActions: {
|
|
48
27
|
setDraft(text: string): void;
|
|
49
28
|
};
|
|
50
|
-
/** Current session identity, used as the wire identity for file reads. */
|
|
51
|
-
sessionId: string;
|
|
52
29
|
}
|
|
53
|
-
export declare function StarButton({
|
|
30
|
+
export declare function StarButton({ context, useInput, inputActions }: StarButtonProps): ReactElement;
|
|
54
31
|
//# sourceMappingURL=StarButton.d.ts.map
|
|
@@ -2,30 +2,21 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
/**
|
|
3
3
|
* The ⭐ button rendered beside the conversation input.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
10
|
*
|
|
11
11
|
* The slot framework composes this component's props: the session standard seat
|
|
12
|
-
* (`useInput`, `inputActions
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
16
|
*/
|
|
17
17
|
import { useState } from 'react';
|
|
18
|
-
/** Common project-doc filenames probed
|
|
19
|
-
|
|
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
|
-
];
|
|
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). */
|
|
29
20
|
const buttonStyle = {
|
|
30
21
|
all: 'unset',
|
|
31
22
|
display: 'inline-flex',
|
|
@@ -40,33 +31,9 @@ const buttonStyle = {
|
|
|
40
31
|
opacity: 0.9,
|
|
41
32
|
userSelect: 'none',
|
|
42
33
|
};
|
|
43
|
-
/**
|
|
44
|
-
* or unreadable file is skipped rather than breaking the button. */
|
|
45
|
-
async function gatherContext(workspaceFiles, sessionId) {
|
|
46
|
-
const docs = [];
|
|
47
|
-
const seen = new Set();
|
|
48
|
-
let filesRead = 0;
|
|
49
|
-
for (const path of DOC_CANDIDATES) {
|
|
50
|
-
try {
|
|
51
|
-
const result = await workspaceFiles.read(sessionId, path, { offset: 1, limit: 120 });
|
|
52
|
-
if (result.ok && result.value?.text) {
|
|
53
|
-
const text = result.value.text.trim();
|
|
54
|
-
if (text.length > 0 && !seen.has(path)) {
|
|
55
|
-
seen.add(path);
|
|
56
|
-
docs.push({ name: path, text });
|
|
57
|
-
filesRead += 1;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
// Not present / not readable: leave it out.
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return { docs, filesRead };
|
|
66
|
-
}
|
|
67
|
-
/** Assemble a fuller, structured prompt from the draft + any project context. */
|
|
34
|
+
/** Build a fuller, structured prompt from the draft + any project context. */
|
|
68
35
|
function assemblePrompt(draft, context) {
|
|
69
|
-
const projectContext = context.
|
|
36
|
+
const projectContext = context.docs.length === 0
|
|
70
37
|
? '(未读取到项目文档)'
|
|
71
38
|
: context.docs.map((doc) => `## ${doc.name}\n${doc.text}`).join('\n\n');
|
|
72
39
|
return [
|
|
@@ -85,7 +52,7 @@ function assemblePrompt(draft, context) {
|
|
|
85
52
|
draft,
|
|
86
53
|
].join('\n');
|
|
87
54
|
}
|
|
88
|
-
export function StarButton({
|
|
55
|
+
export function StarButton({ context, useInput, inputActions }) {
|
|
89
56
|
const input = useInput();
|
|
90
57
|
const [busy, setBusy] = useState(false);
|
|
91
58
|
const [error, setError] = useState(undefined);
|
|
@@ -98,13 +65,18 @@ export function StarButton({ workspaceFiles, useInput, inputActions, sessionId,
|
|
|
98
65
|
setBusy(true);
|
|
99
66
|
setError(undefined);
|
|
100
67
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
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);
|
|
103
75
|
if (full && full !== draft) {
|
|
104
76
|
inputActions.setDraft(full);
|
|
105
|
-
setError(
|
|
77
|
+
setError(value.docs.length === 0
|
|
106
78
|
? '已按草稿整理(未读取到项目文档)'
|
|
107
|
-
: `已整理并读取 ${
|
|
79
|
+
: `已整理并读取 ${value.docs.length} 个项目文档`);
|
|
108
80
|
}
|
|
109
81
|
else {
|
|
110
82
|
setError('未生成新内容');
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client plugin: mounts the ⭐ button into the composer input tool row
|
|
3
3
|
* (`conversation.input.right`, a session-scoped list slot) and wires it to the
|
|
4
|
-
*
|
|
4
|
+
* host's `/dsh-prompt-star` Connection RPC channel.
|
|
5
5
|
*
|
|
6
|
-
* This
|
|
7
|
-
*
|
|
8
|
-
* assembles
|
|
9
|
-
*
|
|
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
10
|
*/
|
|
11
11
|
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
-
/** Cordis services this client plugin needs: the slot registry and the
|
|
13
|
-
*
|
|
12
|
+
/** Cordis services this client plugin needs: the slot registry and the
|
|
13
|
+
* Connection service (both provided by the stock web-app composition). */
|
|
14
14
|
export declare const inject: string[];
|
|
15
15
|
/**
|
|
16
16
|
* Client plugin body: register the ⭐ button once the slot registry and the
|
|
17
|
-
*
|
|
17
|
+
* Connection service are up.
|
|
18
18
|
* @param ctx - client root context.
|
|
19
19
|
*/
|
|
20
20
|
export declare function apply(ctx: Context): void;
|
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client plugin: mounts the ⭐ button into the composer input tool row
|
|
3
3
|
* (`conversation.input.right`, a session-scoped list slot) and wires it to the
|
|
4
|
-
*
|
|
4
|
+
* host's `/dsh-prompt-star` Connection RPC channel.
|
|
5
5
|
*
|
|
6
|
-
* This
|
|
7
|
-
*
|
|
8
|
-
* assembles
|
|
9
|
-
*
|
|
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
10
|
*/
|
|
11
11
|
import { StarButton } from "./StarButton.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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'];
|
|
15
16
|
/**
|
|
16
17
|
* Client plugin body: register the ⭐ button once the slot registry and the
|
|
17
|
-
*
|
|
18
|
+
* Connection service are up.
|
|
18
19
|
* @param ctx - client root context.
|
|
19
20
|
*/
|
|
20
21
|
export function apply(ctx) {
|
|
21
|
-
ctx.inject(['slots', '
|
|
22
|
+
ctx.inject(['slots', 'connection'], (scope) => {
|
|
22
23
|
const root = scope;
|
|
23
|
-
const
|
|
24
|
+
const rpc = root.connection.rpc;
|
|
24
25
|
root.slots.inject('conversation.input.right', () => {
|
|
25
26
|
root.slots.register({
|
|
26
27
|
name: 'conversation.input.right',
|
|
27
28
|
id: 'prompt-star',
|
|
28
29
|
order: 0,
|
|
29
|
-
inject: () => ({
|
|
30
|
+
inject: () => ({
|
|
31
|
+
context: () => rpc.call(RPC_CHANNEL, EP_CONTEXT, {}),
|
|
32
|
+
}),
|
|
30
33
|
}, StarButton);
|
|
31
34
|
});
|
|
32
35
|
});
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host entry for dsh-prompt-star.
|
|
3
3
|
*
|
|
4
|
-
* The ⭐ button
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* bundle mounted as a Loader entry — which is what makes `dsh-client-modules`
|
|
10
|
-
* discover and serve its client bundle.
|
|
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.
|
|
11
9
|
*
|
|
12
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.
|
|
13
16
|
*/
|
|
14
17
|
import type { Context } from '@deepseek-ai/cordis';
|
|
15
18
|
/**
|
|
16
|
-
* Minimal host plugin body
|
|
17
|
-
*
|
|
19
|
+
* Minimal host plugin body: mount the `/dsh-prompt-star` RPC channel.
|
|
20
|
+
* @param ctx - host root context.
|
|
18
21
|
*/
|
|
19
|
-
export declare function apply(
|
|
20
|
-
export default apply;
|
|
22
|
+
export declare function apply(ctx: Context): void;
|
|
21
23
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/types/index.js
CHANGED
|
@@ -1,22 +1,81 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host entry for dsh-prompt-star.
|
|
3
3
|
*
|
|
4
|
-
* The ⭐ button
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* bundle mounted as a Loader entry — which is what makes `dsh-client-modules`
|
|
10
|
-
* discover and serve its client bundle.
|
|
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.
|
|
11
9
|
*
|
|
12
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.
|
|
13
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;
|
|
14
31
|
/**
|
|
15
|
-
* Minimal host plugin body
|
|
16
|
-
*
|
|
32
|
+
* Minimal host plugin body: mount the `/dsh-prompt-star` RPC channel.
|
|
33
|
+
* @param ctx - host root context.
|
|
17
34
|
*/
|
|
18
|
-
export function apply(
|
|
19
|
-
|
|
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
|
+
});
|
|
20
80
|
}
|
|
21
|
-
export default apply;
|
|
22
81
|
//# sourceMappingURL=index.js.map
|
package/lib/types/types.d.ts
CHANGED
|
@@ -1,46 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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.
|
|
4
12
|
*/
|
|
5
|
-
/**
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
readonly name: string;
|
|
11
|
-
/** Whether the file existed and was read. */
|
|
12
|
-
readonly read: boolean;
|
|
13
|
-
/** Bytes read, capped; 0 when `read` is false. */
|
|
14
|
-
readonly bytes: number;
|
|
15
|
-
}
|
|
16
|
-
/** Client → Host: a click of ⭐ with the current draft. */
|
|
17
|
-
export interface PromptStarRequest {
|
|
18
|
-
/** Current draft text (the editor's clipboard-text projection). */
|
|
19
|
-
readonly draft: string;
|
|
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 {
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* Optional workspace directory to probe for doc files. When absent the host
|
|
20
|
+
* falls back to the DSH process working directory.
|
|
23
21
|
*/
|
|
24
|
-
|
|
25
|
-
/** Pick a template intent explicitly; empty lets the model infer it. */
|
|
26
|
-
readonly intent?: string;
|
|
27
|
-
/** Optional caller cancellation; the host falls back to an internal deadline. */
|
|
28
|
-
readonly signal?: AbortSignal;
|
|
22
|
+
cwd?: string;
|
|
29
23
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
/** Model route actually used (provider/model when available). */
|
|
39
|
-
readonly model?: {
|
|
40
|
-
readonly provider: string;
|
|
41
|
-
readonly model: string;
|
|
42
|
-
};
|
|
43
|
-
/** True when a transient/LLM failure was swallowed and `prompt` is a best-effort copy. */
|
|
44
|
-
readonly degraded: boolean;
|
|
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;
|
|
45
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
|
+
};
|
|
46
45
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types/types.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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.
|
|
4
12
|
*/
|
|
5
|
-
|
|
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';
|
|
6
17
|
//# sourceMappingURL=types.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-prompt-star",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
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"
|
|
@@ -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-
|
|
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,11 +44,9 @@
|
|
|
49
44
|
],
|
|
50
45
|
"peerDependencies": {
|
|
51
46
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
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": {
|