@morlay/dsh-session-mode 0.0.14 → 0.0.16
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 +20 -7
- package/dist/client.cjs +4576 -3901
- package/dist/client.d.cts +41 -43
- package/dist/client.d.mts +41 -43
- package/dist/client.mjs +952 -1233
- package/dist/index.cjs +748 -70
- package/dist/index.d.mts +26 -55
- package/dist/index.mjs +742 -65
- package/dist/mode-config-CiQfAy4G.mjs +99 -0
- package/dist/mode-config-DOQ1VjcM.cjs +225 -0
- package/dist/rows-BT6Fpq-E.cjs +99 -0
- package/dist/rows-DnHpp-Ak.mjs +82 -0
- package/dist/rows.cjs +3 -86
- package/dist/rows.d.mts +31 -1
- package/dist/rows.mjs +1 -86
- package/dist/shared-R2j0SuWy.d.mts +118 -0
- package/package.json +70 -56
- package/src/client/SessionModePanel.tsx +803 -0
- package/src/client/api.ts +125 -1
- package/src/client/hints.ts +41 -0
- package/src/client/index.ts +71 -55
- package/src/client/locales.ts +80 -0
- package/src/index.ts +581 -20
- package/src/mode-config.ts +104 -0
- package/src/mode-sources.ts +4 -4
- package/src/mode-store.ts +53 -0
- package/src/modes.ts +23 -28
- package/src/per-agent.ts +53 -0
- package/src/ptc-describe.ts +97 -0
- package/src/ptc-sdk.ts +90 -0
- package/src/rows.ts +4 -8
- package/src/scope.ts +369 -71
- package/src/session-mode-store.ts +20 -0
- package/src/shared.ts +128 -3
- package/src/system-remainder.ts +39 -0
- package/src/tool-groups.ts +19 -0
- package/dist/shared-DO1nZbJ0.cjs +0 -64
- package/dist/shared-vjlwxiMt.mjs +0 -4
- package/src/client/BundleConfigPage.tsx +0 -631
- package/src/client/bundle-config.ts +0 -701
package/src/shared.ts
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
// 走 HTTP 路由而不是 Typert Remote:客户端的 remote 清单由上游硬编码,我们的服务不在里面。
|
|
3
3
|
// 会话当前模式不走这条通路:它是 session 投影(`sessionMode`),随会话列表一起到页面。
|
|
4
4
|
|
|
5
|
-
// 模式清单与切换的路由路径:宿主(web
|
|
6
|
-
|
|
5
|
+
// 模式清单与切换的路由路径:宿主(web 与桌面)在同一张路由表上服务它。前缀是项目的统一约定
|
|
6
|
+
// (`/api/morlay/v1/<域>`,与 `ui-conversation-message-actions` 的 `/api/morlay/v1/session/*` 同一套),
|
|
7
|
+
// 不带前缀的裸路径会与 SPA 的静态资源 fallback 抢同一个命名空间。
|
|
8
|
+
import { groupByKey, groupOfTool, groupTitleOf } from "@morlay/dsh-tool-guidance/groups";
|
|
9
|
+
|
|
10
|
+
export const SESSION_MODE_PATH = "/api/morlay/v1/session-mode";
|
|
7
11
|
|
|
8
12
|
// 模式可以改写的上游 **policy 规则**名,即上游 waterfall 的事件名(是事件名、不是路径,所以**不带**前导斜杠):
|
|
9
13
|
// `fs/write-intent`(写意图)与 `fs/edit-intent`(改意图)。home 在这里而不是 `modes.ts`:host 半的装配期校验
|
|
10
14
|
// 与 client 半的页面候选键都要它,而 client 半不 import `modes.ts`(免得把 schemastery 拖进浏览器包)。
|
|
11
15
|
// 「能拦什么」取决于上游是否在那条 waterfall 上独占决策槽——它不是配置能自己长出来的东西,所以这份名单是封闭的,
|
|
12
16
|
// 写进来的名字不在名单里就在装配期拒绝(见 `configProblem`)。
|
|
13
|
-
export const POLICY_NAMES = ["fs/write-intent", "fs/edit-intent"] as const;
|
|
17
|
+
export const POLICY_NAMES = ["fs/write-intent", "fs/edit-intent", "fs/delete-intent"] as const;
|
|
14
18
|
|
|
15
19
|
// 一条 policy 规则的名字。
|
|
16
20
|
export type PolicyName = (typeof POLICY_NAMES)[number];
|
|
@@ -38,3 +42,124 @@ export interface SessionModeSelectRequest {
|
|
|
38
42
|
export interface SessionModeSelectResult {
|
|
39
43
|
readonly mode: string;
|
|
40
44
|
}
|
|
45
|
+
|
|
46
|
+
// `GET /session-mode?sessionId=<id>` 的响应体:某个会话**当下生效**的那一份(不是模式的配置清单)。
|
|
47
|
+
// 侧边栏面板读它——工具是运行期实际可见的那一份(按需组加载后随之变化),技能/政策是名单合成后的结果。
|
|
48
|
+
export type SessionModeSwitchKey = "instructions" | "skills" | "runtimeContext";
|
|
49
|
+
|
|
50
|
+
// 面板能编辑的字段(会话级覆盖与"另存为"都以它为准)。
|
|
51
|
+
export interface SessionModeEditable {
|
|
52
|
+
readonly persona: { readonly prefix: string; readonly suffix: string };
|
|
53
|
+
readonly allowTools: readonly string[];
|
|
54
|
+
readonly denyTools: readonly string[];
|
|
55
|
+
readonly allowSkills: readonly string[];
|
|
56
|
+
readonly denySkills: readonly string[];
|
|
57
|
+
readonly allowPolicies: readonly string[];
|
|
58
|
+
readonly denyPolicies: readonly string[];
|
|
59
|
+
readonly instructions: boolean;
|
|
60
|
+
readonly skills: boolean;
|
|
61
|
+
readonly runtimeContext: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 全量目录:id + 描述(面板折叠组里逐条展示),`group` 用于工具的分组选择。
|
|
65
|
+
export type SessionModeTriState = "allow" | "deny";
|
|
66
|
+
|
|
67
|
+
export interface SessionModeDirectoryEntry {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly description: string;
|
|
70
|
+
readonly group?: string;
|
|
71
|
+
readonly groupLabel?: string;
|
|
72
|
+
// 模式默认下的判定(继承来的那档)。
|
|
73
|
+
// 模式配置里的显式档:`deny`(含组引用)、`allow`(白名单内)或 `default`(没显式配)。
|
|
74
|
+
readonly fallback?: SessionModeTriState | "default";
|
|
75
|
+
// 这个会话显式覆盖成的判定;`null`/缺省表示跟着默认。
|
|
76
|
+
readonly override?: SessionModeTriState | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SessionModeDirectory {
|
|
80
|
+
readonly tools: readonly SessionModeDirectoryEntry[];
|
|
81
|
+
readonly skills: readonly SessionModeDirectoryEntry[];
|
|
82
|
+
readonly policies: readonly SessionModeDirectoryEntry[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 工具的组 id:MCP 一个 server 一组(`mcp:<server>`),其余是稳定的短 id。
|
|
86
|
+
|
|
87
|
+
// 不可被开关收窄的工具:`run_code` 是 PTC 的唯一入口,`describe_tool` 是它取签名的手段。
|
|
88
|
+
export const PROTECTED_TOOLS: ReadonlySet<string> = new Set(["run_code", "describe_tool"]);
|
|
89
|
+
|
|
90
|
+
export function isProtectedTool(name: string): boolean {
|
|
91
|
+
return PROTECTED_TOOLS.has(name);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function toolGroupOf(name: string): string {
|
|
95
|
+
// 分组表的唯一 home 是 `@morlay/dsh-tool-guidance`(含 MCP 资源三件的 `mcp` 组)。
|
|
96
|
+
const known = groupOfTool(name);
|
|
97
|
+
if (known !== undefined) return known;
|
|
98
|
+
// MCP server 的特殊性:一个 server 一组(`mcp:<server>`),工具名里动态带 server——静态表覆盖不了,放最后兜底。
|
|
99
|
+
if (name.startsWith("mcp__")) {
|
|
100
|
+
const rest = name.slice("mcp__".length);
|
|
101
|
+
const separator = rest.indexOf("__");
|
|
102
|
+
if (separator > 0) return `mcp:${rest.slice(0, separator)}`;
|
|
103
|
+
}
|
|
104
|
+
if (name.startsWith("tool_group_")) return "onDemand";
|
|
105
|
+
return "other";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 常驻的组:这些组的工具默认就在目录里(对应 guidance 的 `injection: "init"` 那几个域)。
|
|
109
|
+
// 常驻判据来自 guidance 的 `injection`:`init` 与认不出的组常驻,`on-demand` 的组默认不进目录。
|
|
110
|
+
export function isResidentToolGroup(group: string): boolean {
|
|
111
|
+
if (group === "onDemand" || group === "other") return true;
|
|
112
|
+
if (group.startsWith("mcp:")) return false;
|
|
113
|
+
try {
|
|
114
|
+
return groupByKey(group).injection === "init";
|
|
115
|
+
} catch {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function toolGroupLabel(group: string): string {
|
|
121
|
+
// MCP server 的特殊性:组 id 自带 server 名,显示成 `MCP · <server>`。
|
|
122
|
+
if (group.startsWith("mcp:")) return `MCP · ${group.slice("mcp:".length)}`;
|
|
123
|
+
return groupTitleOf(group) ?? (group === "onDemand" ? "按需工具" : "其它");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 名单里引用整组时的写法:MCP 组直接用 `mcp:<server>`,其余加 `group:` 前缀。
|
|
127
|
+
export function groupRef(group: string): string {
|
|
128
|
+
return group.startsWith("mcp:") ? group : `group:${group}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function refGroup(ref: string): string {
|
|
132
|
+
return ref.startsWith("group:") ? ref.slice("group:".length) : ref;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 名单是否命中这个工具:写具体 id,或写它所属的组(`mcp:<server>` / `group:<id>`)。
|
|
136
|
+
export function deniedByList(deny: readonly string[], id: string): boolean {
|
|
137
|
+
const group = toolGroupOf(id);
|
|
138
|
+
return deny.some(
|
|
139
|
+
(ref) =>
|
|
140
|
+
ref === id ||
|
|
141
|
+
((ref.startsWith("group:") || ref.startsWith("mcp:")) && refGroup(ref) === group),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 两条 policy 的说明(面板与清单同源)。
|
|
146
|
+
export const POLICY_DESCRIPTIONS: Readonly<Record<PolicyName, string>> = {
|
|
147
|
+
"fs/write-intent": "文件写入前的意图检查(CAS 安全网)。",
|
|
148
|
+
"fs/edit-intent": "文件编辑前的意图检查(先读后改那条规则)。",
|
|
149
|
+
"fs/delete-intent": "文件删除前的意图检查。",
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export interface SessionModeActiveState {
|
|
153
|
+
// 展示用的三件:模式 id / 名称 / 描述。
|
|
154
|
+
readonly mode: { readonly id: string; readonly name: string; readonly description: string };
|
|
155
|
+
// 与 TOML 同构的配置本身。
|
|
156
|
+
readonly config: import("./mode-config.ts").ModeConfig;
|
|
157
|
+
// 这个会话的覆盖(全量的一份配置;没有就是 undefined)。
|
|
158
|
+
readonly session: import("./mode-config.ts").ModeConfig | undefined;
|
|
159
|
+
// 覆盖里**真正与模式默认不同**的字段(面板按字段显示来源)。
|
|
160
|
+
readonly overriddenFields?: readonly string[];
|
|
161
|
+
// 全量目录(工具 / 技能 / policy):id、描述、分组、配置值与覆盖值。
|
|
162
|
+
readonly catalog: SessionModeDirectory;
|
|
163
|
+
// 只有"另存为"出来的模式允许删除。
|
|
164
|
+
readonly deletable: boolean;
|
|
165
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// system prompt 的“变化部分”不再挤进 system,而是作为一条 `<system-remainder>` 的 user 消息追加到历史尾部:
|
|
2
|
+
// system 稳定 → 前缀缓存可复用;变化只在历史里追加,且只有内容变了才追加。
|
|
3
|
+
//
|
|
4
|
+
// 这里只放纯函数:怎么收集 section、什么时候 append 由插件接线,边界测试写在 `__tests__/system-remainder.spec.ts`。
|
|
5
|
+
|
|
6
|
+
import type { UserMessage } from "@deepseek-ai/dsh-llm";
|
|
7
|
+
|
|
8
|
+
// 一段会被搬进 remainder 的文本(section 名 + 文本)。
|
|
9
|
+
export interface RemainderSection {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly text: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 按 section 顺序拼成一段稳定文本:名字保留,方便模型知道每段是什么。
|
|
15
|
+
export function renderRemainder(sections: readonly RemainderSection[]): string {
|
|
16
|
+
return sections
|
|
17
|
+
.filter((section) => section.text.trim().length > 0)
|
|
18
|
+
.map((section) => `## ${section.name}\n\n${section.text.trim()}`)
|
|
19
|
+
.join("\n\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 与上一份比较:空白差异不算变化,避免每步都追加一条。
|
|
23
|
+
export function remainderChanged(previous: string | undefined, next: string): boolean {
|
|
24
|
+
return (previous ?? "").trim() !== next.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 会话里落盘用的 user 消息(`id` 由 Session 分配)。
|
|
28
|
+
export type RemainderMessage = Omit<UserMessage, "id">;
|
|
29
|
+
|
|
30
|
+
// 包成一条 user 消息;空内容返回 undefined(不追加)。
|
|
31
|
+
export function remainderMessage(text: string): RemainderMessage | undefined {
|
|
32
|
+
const body = text.trim();
|
|
33
|
+
if (body.length === 0) return undefined;
|
|
34
|
+
return {
|
|
35
|
+
role: "user",
|
|
36
|
+
content: [{ type: "text", text: `<system-remainder>\n${body}\n</system-remainder>` }],
|
|
37
|
+
source: { kind: "user" },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// 按需工具分组:一个组是**注册表里的名字面**(不含描述——被收窄掉的工具定义读不到,列表只给名字与数量)。
|
|
2
|
+
// 组名与组归属的事实 home 是 `shared.ts`(`toolGroupOf` / `isResidentToolGroup` / `groupRef`):host 与 client
|
|
3
|
+
// 两半都要它,这里只做"名字全集 → 按需组"的投影。
|
|
4
|
+
import { groupRef, isResidentToolGroup, toolGroupOf } from "./shared.ts";
|
|
5
|
+
|
|
6
|
+
// 名字面 → 按需组名(`mcp:<server>` / `group:<id>`)→ 这个组里的工具名(保持注册顺序)。
|
|
7
|
+
// 常驻组不进这张表:它们的工具默认就在目录里,不需要按需放行。
|
|
8
|
+
export function groupTools(names: Iterable<string>): Map<string, string[]> {
|
|
9
|
+
const groups = new Map<string, string[]>();
|
|
10
|
+
for (const name of names) {
|
|
11
|
+
const group = toolGroupOf(name);
|
|
12
|
+
if (isResidentToolGroup(group)) continue;
|
|
13
|
+
const key = groupRef(group);
|
|
14
|
+
const tools = groups.get(key);
|
|
15
|
+
if (tools === undefined) groups.set(key, [name]);
|
|
16
|
+
else tools.push(name);
|
|
17
|
+
}
|
|
18
|
+
return groups;
|
|
19
|
+
}
|
package/dist/shared-DO1nZbJ0.cjs
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// \0rolldown/runtime.js
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __exportAll = (all, no_symbols) => {
|
|
9
|
-
let target = {};
|
|
10
|
-
for (var name in all) __defProp(target, name, {
|
|
11
|
-
get: all[name],
|
|
12
|
-
enumerable: true
|
|
13
|
-
});
|
|
14
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
15
|
-
return target;
|
|
16
|
-
};
|
|
17
|
-
var __copyProps = (to, from, except, desc) => {
|
|
18
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
19
|
-
key = keys[i];
|
|
20
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
21
|
-
get: ((k) => from[k]).bind(null, key),
|
|
22
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
return to;
|
|
26
|
-
};
|
|
27
|
-
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
28
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
29
|
-
value: mod,
|
|
30
|
-
enumerable: true
|
|
31
|
-
}) : target, mod));
|
|
32
|
-
// src/shared.ts
|
|
33
|
-
const SESSION_MODE_PATH = "/session-mode";
|
|
34
|
-
const POLICY_NAMES = ["fs/write-intent", "fs/edit-intent"];
|
|
35
|
-
Object.defineProperty(exports, "POLICY_NAMES", {
|
|
36
|
-
enumerable: true,
|
|
37
|
-
get: function() {
|
|
38
|
-
return POLICY_NAMES;
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
Object.defineProperty(exports, "SESSION_MODE_PATH", {
|
|
42
|
-
enumerable: true,
|
|
43
|
-
get: function() {
|
|
44
|
-
return SESSION_MODE_PATH;
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
Object.defineProperty(exports, "__exportAll", {
|
|
48
|
-
enumerable: true,
|
|
49
|
-
get: function() {
|
|
50
|
-
return __exportAll;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
Object.defineProperty(exports, "__reExport", {
|
|
54
|
-
enumerable: true,
|
|
55
|
-
get: function() {
|
|
56
|
-
return __reExport;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
Object.defineProperty(exports, "__toESM", {
|
|
60
|
-
enumerable: true,
|
|
61
|
-
get: function() {
|
|
62
|
-
return __toESM;
|
|
63
|
-
}
|
|
64
|
-
});
|
package/dist/shared-vjlwxiMt.mjs
DELETED