dsh-agent-persona 0.1.3 → 0.1.5
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/CHANGELOG.md +46 -0
- package/README.md +3 -3
- package/README_EN.md +3 -3
- package/lib/client.js +60 -25
- package/lib/index.js +22 -9
- package/lib/remote.d.ts +5 -0
- package/lib/remote.js +11 -5
- package/lib/typert.d.ts +6 -0
- package/lib/typert.js +11 -5
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,52 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.1.5] - 2026-10-20
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **适配最新版 DSH `0.2.1-rc.2`。** 在 0.2.1-rc.2 上实测通过:设置页的人设列表、新建、保存
|
|
12
|
+
(`listPersonas` / `savePersona`,正文按 `MAX_TEXT_BYTES` 的 64 KiB 上限测了 6 KB 长文)与
|
|
13
|
+
system prompt 注入都正常;0.1.4 为 0.2.0-rc.2 补的 strict codec 契约(`create`/`decode`/`encode`)
|
|
14
|
+
与图标命名在 0.2.1-rc.2 上没有变化,所以那次适配继续有效。
|
|
15
|
+
`dsh.compatibility.dshReleases` 补上 `0.2.1-rc.2`(与 `0.1.5-rc.1`、`0.1.5-rc.2`、`0.2.0-rc.2`
|
|
16
|
+
并列 `compatible`);宿主代码并不读取这个字段,它服务于插件市场/商店的版本提示,补上是为了让
|
|
17
|
+
"跑过哪些版本"与事实一致。
|
|
18
|
+
- 宿主对**每一个**工具调用都会跑 `tools.guard`(0.2.1-rc.2 上实测 `compress` 也被拦),于是下面那条
|
|
19
|
+
守卫的误判从"偶尔"变成"常态"。
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **只读工具不再因为参数里出现人设存储路径而被拒绝。** `personaStoreGuardReason` 原来只看参数里有没有那个
|
|
24
|
+
路径,于是 `read`、`grep`、`compress` 这类根本不能改写存储的调用也会被拒绝,并回一条"工具不能写入或删除它",
|
|
25
|
+
等于让会话连解释这个文件都做不到(同一个会话里 `compress` 就被挡过一次)。现在按工具名判断写意图:
|
|
26
|
+
`write`/`edit`/`multiedit`/`apply_patch` 等"参数即目标路径"的工具,路径指向存储才拒绝;`bash`/`pwsh`
|
|
27
|
+
等命令类工具仍要求同一个字符串里同时出现写特征(`rm`、`>`、`cp` 等);其余只读工具一律放行。
|
|
28
|
+
存储保护本身没有放松:直接写存储、命令里删/覆盖存储,仍然照旧拒绝。
|
|
29
|
+
|
|
30
|
+
## [0.1.4] - 2026-10-08
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **适配 DSH 0.2.0-rc.2。** 三处不兼容导致插件在新版本上完全无法使用:
|
|
35
|
+
|
|
36
|
+
1. **Typert strict codec 缺少 `create()` 工厂方法。** 0.2.0 的 typert-loader 和 gateway 要求每个 strict codec
|
|
37
|
+
提供 `create()` 并返回一个带 `parse()` 方法的对象,gateway 在处理 RPC 调用时执行 `codec.create().parse(value)`
|
|
38
|
+
进行边界校验。`generate-remote.mjs` 生成的 `remote.ts` 和 `typert.ts` 中的 codec 都只有
|
|
39
|
+
`{ mode, typeSymbol, schema }` 三个字段,缺少 `create`/`decode`/`encode`,导致宿主端 typert-loader 注册失败
|
|
40
|
+
(启动时报 warning "1 entry did not activate"),客户端 `ctx.remote.$mount()` 抛出异常使 `capturedApi` 始终
|
|
41
|
+
为 `undefined`,页面渲染回退为"插件还没加载好"错误提示,同时所有带参数的 RPC 调用(如 `sessionPrompt`、
|
|
42
|
+
`savePersona`)在 gateway 层因 `codec.create()` 返回 `undefined` 而报
|
|
43
|
+
"wire field \"input\" failed boundary validation"。
|
|
44
|
+
|
|
45
|
+
2. **图标组件名称变更。** `dsh-client-ui-primitives` 0.2.0-rc.2 将图标命名规则从尺寸后缀(如 `IconPlusOutline16`)
|
|
46
|
+
改为粗细后缀(如 `IconPlusOutlineRegular`),旧名称不再导出。从 primitives 解构出的 18 个图标全部为 `undefined`,
|
|
47
|
+
`verifyElements()` 检查失败,页面无法渲染。
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- `dsh.compatibility.dshReleases` 新增 `0.2.0-rc.2: compatible`。
|
|
52
|
+
|
|
7
53
|
## [0.1.3] - 2026-09-23
|
|
8
54
|
|
|
9
55
|
### Fixed
|
package/README.md
CHANGED
|
@@ -51,9 +51,9 @@ DSH 会把工作区指令(`AGENTS.md` 之类)变成一条 **user 角色**的
|
|
|
51
51
|
|
|
52
52
|
## 装
|
|
53
53
|
|
|
54
|
-
**实测过的 DSH 版本:`0.1.5-rc.1
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
**实测过的 DSH 版本:`0.1.5-rc.1`(作者的开发环境)、`0.1.5-rc.2`、`0.2.0-rc.2` 与最新版
|
|
55
|
+
`0.2.1-rc.2`。** `package.json` 的 `dsh.compatibility` 声明的就是这四项 —— 含义是"我们在这些版本上跑过",
|
|
56
|
+
**不是**对更早或更新版本做过兼容工作的承诺;其它版本请自行验证。
|
|
57
57
|
|
|
58
58
|
侧边栏卡片还需要 **`dsh-better-sidebar`**:没装它时,「注册到 dsh-better-sidebar」这个开关不会出现
|
|
59
59
|
(没有可注册的地方),插件其余功能照常。
|
package/README_EN.md
CHANGED
|
@@ -61,9 +61,9 @@ workspace cannot touch this layer.
|
|
|
61
61
|
|
|
62
62
|
## Install
|
|
63
63
|
|
|
64
|
-
**Tested against DSH `0.1.5-rc.1` (the author's development environment)
|
|
65
|
-
`package.json`'s `dsh.compatibility` declares — it means "we ran on
|
|
66
|
-
for older or newer releases; please verify on yours.
|
|
64
|
+
**Tested against DSH `0.1.5-rc.1` (the author's development environment), `0.1.5-rc.2`, `0.2.0-rc.2` and
|
|
65
|
+
the current `0.2.1-rc.2`.** That is what `package.json`'s `dsh.compatibility` declares — it means "we ran on
|
|
66
|
+
these", not a promise of compatibility work for older or newer releases; please verify on yours.
|
|
67
67
|
|
|
68
68
|
The sidebar card additionally needs **`dsh-better-sidebar`**: without it the `注册到 dsh-better-sidebar` switch is not
|
|
69
69
|
shown at all (there is nothing to register into), and everything else works as usual.
|
package/lib/client.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
// src/remote.ts
|
|
3
3
|
var PKG = "dsh-agent-persona";
|
|
4
|
-
var codec = (typeSymbol, parse) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
var codec = (typeSymbol, parse) => {
|
|
5
|
+
const schema = { parse };
|
|
6
|
+
return {
|
|
7
|
+
mode: "strict",
|
|
8
|
+
typeSymbol: `${PKG}#${typeSymbol}`,
|
|
9
|
+
schema,
|
|
10
|
+
create: () => schema,
|
|
11
|
+
decode: (v) => v,
|
|
12
|
+
encode: (v) => v
|
|
13
|
+
};
|
|
14
|
+
};
|
|
9
15
|
var passthrough = (value) => value;
|
|
10
16
|
var asInput = (value) => {
|
|
11
17
|
if (value === void 0 || value === null) return {};
|
|
@@ -61,25 +67,44 @@
|
|
|
61
67
|
Toast,
|
|
62
68
|
Pill,
|
|
63
69
|
Tooltip,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
IconCheckOutlineRegular,
|
|
71
|
+
IconChevronDownOutlineRegular,
|
|
72
|
+
IconChevronRightOutlineRegular,
|
|
73
|
+
IconAgentPresetOutlineRegular,
|
|
74
|
+
IconBrowseOutlineRegular,
|
|
75
|
+
IconChevronUpOutlineRegular,
|
|
76
|
+
IconCloseOutlineRegular,
|
|
77
|
+
IconEditOutlineRegular,
|
|
78
|
+
IconEllipsisOutlineRegular,
|
|
79
|
+
IconEnhanceOutlineRegular,
|
|
80
|
+
IconFolderOpenRegular,
|
|
81
|
+
IconListPenOutlineRegular,
|
|
82
|
+
IconPersonalizationOutlineRegular,
|
|
83
|
+
IconRefreshOutlineRegular,
|
|
84
|
+
IconUserOutlineRegular,
|
|
85
|
+
IconWarningOutlineRegular,
|
|
86
|
+
IconPlusOutlineRegular,
|
|
87
|
+
IconTrashOutlineRegular
|
|
82
88
|
} = primitives;
|
|
89
|
+
const sized = (icon, size) => (props) => h(icon, { size, ...props });
|
|
90
|
+
const IconCheckOutline14 = sized(IconCheckOutlineRegular, 14);
|
|
91
|
+
const IconChevronDownOutline14 = sized(IconChevronDownOutlineRegular, 14);
|
|
92
|
+
const IconChevronRightOutline14 = sized(IconChevronRightOutlineRegular, 14);
|
|
93
|
+
const IconChevronUpOutline14 = sized(IconChevronUpOutlineRegular, 14);
|
|
94
|
+
const IconAgentPresetOutline16 = sized(IconAgentPresetOutlineRegular, 16);
|
|
95
|
+
const IconBrowseOutline16 = sized(IconBrowseOutlineRegular, 16);
|
|
96
|
+
const IconCloseOutline16 = sized(IconCloseOutlineRegular, 16);
|
|
97
|
+
const IconEditOutline16 = sized(IconEditOutlineRegular, 16);
|
|
98
|
+
const IconEllipsisOutline16 = sized(IconEllipsisOutlineRegular, 16);
|
|
99
|
+
const IconEnhanceOutline16 = sized(IconEnhanceOutlineRegular, 16);
|
|
100
|
+
const IconFolderOpen16 = sized(IconFolderOpenRegular, 16);
|
|
101
|
+
const IconListPenOutline16 = sized(IconListPenOutlineRegular, 16);
|
|
102
|
+
const IconPersonalizationOutline16 = sized(IconPersonalizationOutlineRegular, 16);
|
|
103
|
+
const IconRefreshOutline16 = sized(IconRefreshOutlineRegular, 16);
|
|
104
|
+
const IconUserOutline16 = sized(IconUserOutlineRegular, 16);
|
|
105
|
+
const IconWarningOutline16 = sized(IconWarningOutlineRegular, 16);
|
|
106
|
+
const IconPlusOutline16 = sized(IconPlusOutlineRegular, 16);
|
|
107
|
+
const IconTrashOutline16 = sized(IconTrashOutlineRegular, 16);
|
|
83
108
|
const NS = "agent-persona";
|
|
84
109
|
const MOD_KEY = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? navigator.userAgent ?? "") ? "\u2318" : "Ctrl+";
|
|
85
110
|
const DESCRIPTION = "\u7ED9\u4E0D\u540C\u7684\u5DE5\u4F5C\u533A\u3001\u4E0D\u540C\u7684\u4F1A\u8BDD\u7528\u4E0D\u540C\u7684\u4EBA\u8BBE\u3002\u4EBA\u8BBE\u4F1A\u4F5C\u4E3A\u7CFB\u7EDF\u63D0\u793A\u8BCD\u6CE8\u5165\uFF0C\u4F18\u5148\u7EA7\u9AD8\u4E8E\u5DE5\u4F5C\u533A\u91CC\u7684 AGENTS.md\u3002";
|
|
@@ -784,6 +809,10 @@
|
|
|
784
809
|
}
|
|
785
810
|
return raw;
|
|
786
811
|
};
|
|
812
|
+
const sessionIdOf = (props) => {
|
|
813
|
+
const id = props?.sessionId ?? props?.session?.id ?? props?.scope?.sessionId;
|
|
814
|
+
return typeof id === "string" && id !== "" ? id : null;
|
|
815
|
+
};
|
|
787
816
|
const SidebarPersonaView = (props) => h(PersonaView, { ...props, host: "sidebar" });
|
|
788
817
|
class PersonaView extends React.Component {
|
|
789
818
|
constructor(props) {
|
|
@@ -793,9 +822,15 @@
|
|
|
793
822
|
componentDidMount() {
|
|
794
823
|
void this.load();
|
|
795
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* A sidebar tab stays mounted while the reader moves between sessions, so the view has to follow the
|
|
827
|
+
* session it is showing; otherwise it keeps rendering the one it was first opened for.
|
|
828
|
+
*/
|
|
829
|
+
componentDidUpdate(previous) {
|
|
830
|
+
if (this.sessionId() !== sessionIdOf(previous)) void this.load();
|
|
831
|
+
}
|
|
796
832
|
sessionId() {
|
|
797
|
-
|
|
798
|
-
return typeof id === "string" && id !== "" ? id : null;
|
|
833
|
+
return sessionIdOf(this.props);
|
|
799
834
|
}
|
|
800
835
|
async load() {
|
|
801
836
|
const api = apiOf(this.props);
|
package/lib/index.js
CHANGED
|
@@ -221,6 +221,10 @@ export const backupStoreFile = (storePath, stamp) => {
|
|
|
221
221
|
/** Write features that turn "mentions the store" into "writes the store" inside one shell command. */
|
|
222
222
|
const STORE_WRITE_MARKERS = ['>', 'rm ', 'rm\t', 'mv ', 'cp ', 'chmod', 'chflags', 'truncate', 'sed -i', 'tee ', 'write(', 'unlink(', 'rename('];
|
|
223
223
|
const STORE_REFUSAL = '人设存储由 agent-persona 插件管理:工具不能写入或删除它。需要修改人设时,请让用户在「设置 → Agent 人设」里操作。';
|
|
224
|
+
/** Tools whose own arguments name the file they write, so a store path in them is the target. */
|
|
225
|
+
const STORE_PATH_TOOLS = new Set(['write', 'edit', 'multiedit', 'apply_patch', 'create_file', 'delete_file', 'move_file']);
|
|
226
|
+
/** Tools that run a command line, where the store only matters next to a write feature. */
|
|
227
|
+
const STORE_SHELL_TOOLS = new Set(['bash', 'pwsh', 'shell', 'exec', 'terminal', 'run_command']);
|
|
224
228
|
/** Every string nested anywhere in a tool call's arguments, to a bounded depth. */
|
|
225
229
|
const stringsIn = (value, out = [], depth = 0) => {
|
|
226
230
|
if (depth > 6 || value === null || value === undefined)
|
|
@@ -251,23 +255,32 @@ const stringsIn = (value, out = [], depth = 0) => {
|
|
|
251
255
|
* whole command, so a read and a write of the store in one line is refused as a unit.
|
|
252
256
|
*/
|
|
253
257
|
export const personaStoreGuardReason = (execution, storePath) => {
|
|
258
|
+
const name = String(execution?.name ?? '');
|
|
259
|
+
const shell = STORE_SHELL_TOOLS.has(name);
|
|
260
|
+
// A read-only call cannot rewrite the store, so a path that merely appears in its arguments is not a
|
|
261
|
+
// write. Refusing those (`read`, `grep`, `compress`, ...) turned the guard into a trap for honest work:
|
|
262
|
+
// any tool whose arguments quoted the store path was denied with a message about writing it.
|
|
263
|
+
if (!shell && !STORE_PATH_TOOLS.has(name))
|
|
264
|
+
return undefined;
|
|
254
265
|
const values = stringsIn(execution?.arguments);
|
|
255
266
|
const target = resolve(storePath);
|
|
256
|
-
|
|
267
|
+
const base = basename(storePath);
|
|
268
|
+
const dir = dirname(storePath);
|
|
269
|
+
const pointsAtStore = values.some((value) => {
|
|
257
270
|
try {
|
|
258
271
|
return resolve(value) === target;
|
|
259
272
|
}
|
|
260
273
|
catch {
|
|
261
274
|
return false;
|
|
262
275
|
}
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return STORE_REFUSAL;
|
|
270
|
-
return undefined;
|
|
276
|
+
});
|
|
277
|
+
const nearStore = values.some((value) => value.includes(base) || value.includes(dir));
|
|
278
|
+
const writes = values.some((value) => STORE_WRITE_MARKERS.some((marker) => value.includes(marker)));
|
|
279
|
+
// A tool that names its own target writes exactly what it points at; a shell command has to mention a
|
|
280
|
+
// write feature before the path inside it counts.
|
|
281
|
+
if (shell)
|
|
282
|
+
return nearStore && writes ? STORE_REFUSAL : undefined;
|
|
283
|
+
return pointsAtStore || (nearStore && writes) ? STORE_REFUSAL : undefined;
|
|
271
284
|
};
|
|
272
285
|
export const sanitizePersona = (text, warn = () => { }) => {
|
|
273
286
|
const dropped = [];
|
package/lib/remote.d.ts
CHANGED
package/lib/remote.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
// 该文件由 scripts/generate-remote.mjs 生成,请勿手改(改 src/endpoints.ts 后重新生成)。
|
|
2
2
|
const PKG = "dsh-agent-persona";
|
|
3
|
-
const codec = (typeSymbol, parse) =>
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
3
|
+
const codec = (typeSymbol, parse) => {
|
|
4
|
+
const schema = { parse };
|
|
5
|
+
return {
|
|
6
|
+
mode: "strict",
|
|
7
|
+
typeSymbol: `${PKG}#${typeSymbol}`,
|
|
8
|
+
schema,
|
|
9
|
+
create: () => schema,
|
|
10
|
+
decode: (v) => v,
|
|
11
|
+
encode: (v) => v,
|
|
12
|
+
};
|
|
13
|
+
};
|
|
8
14
|
const passthrough = (value) => value;
|
|
9
15
|
const asInput = (value) => {
|
|
10
16
|
if (value === undefined || value === null)
|
package/lib/typert.d.ts
CHANGED
|
@@ -37,12 +37,18 @@ export declare const TYPERT: {
|
|
|
37
37
|
mode: string;
|
|
38
38
|
typeSymbol: string;
|
|
39
39
|
schema: z.ZodUnknown;
|
|
40
|
+
create: () => z.ZodUnknown;
|
|
41
|
+
decode: (v: any) => any;
|
|
42
|
+
encode: (v: any) => any;
|
|
40
43
|
};
|
|
41
44
|
}[];
|
|
42
45
|
result: {
|
|
43
46
|
mode: string;
|
|
44
47
|
typeSymbol: string;
|
|
45
48
|
schema: z.ZodUnknown;
|
|
49
|
+
create: () => z.ZodUnknown;
|
|
50
|
+
decode: (v: any) => any;
|
|
51
|
+
encode: (v: any) => any;
|
|
46
52
|
};
|
|
47
53
|
}[];
|
|
48
54
|
};
|
package/lib/typert.js
CHANGED
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
const PKG = "dsh-agent-persona";
|
|
4
4
|
const direct = { kind: "direct" };
|
|
5
|
-
const jsonCodec = (typeSymbol) =>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
5
|
+
const jsonCodec = (typeSymbol) => {
|
|
6
|
+
const schema = z.unknown();
|
|
7
|
+
return {
|
|
8
|
+
mode: "strict",
|
|
9
|
+
typeSymbol: `${PKG}#${typeSymbol}`,
|
|
10
|
+
schema,
|
|
11
|
+
create: () => schema,
|
|
12
|
+
decode: (v) => v,
|
|
13
|
+
encode: (v) => v,
|
|
14
|
+
};
|
|
15
|
+
};
|
|
10
16
|
const result = (typeSymbol) => jsonCodec(typeSymbol);
|
|
11
17
|
export const TYPERT = {
|
|
12
18
|
package: PKG,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agent-persona",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Workspace personas for DeepSeek Harness (DSH): create many system-prompt personas and scope each one by workspace path or session id (exact / prefix / regex / contains), managed from a Web settings page with Markdown editing and AI tuning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -81,7 +81,9 @@
|
|
|
81
81
|
"compatibility": {
|
|
82
82
|
"dshReleases": {
|
|
83
83
|
"0.1.5-rc.1": "compatible",
|
|
84
|
-
"0.1.5-rc.2": "compatible"
|
|
84
|
+
"0.1.5-rc.2": "compatible",
|
|
85
|
+
"0.2.0-rc.2": "compatible",
|
|
86
|
+
"0.2.1-rc.2": "compatible"
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
},
|