dsh-subagent-profile 0.3.2 → 0.3.4
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 +77 -40
- package/README.zh.md +111 -74
- package/docs/screenshots/dispatch-card.png +0 -0
- package/docs/screenshots/settings-page1.png +0 -0
- package/docs/screenshots/settings-page2.png +0 -0
- package/index.mjs +276 -81
- package/lib/client.js +3218 -166
- package/lib/core/adoption-reminder.mjs +48 -0
- package/lib/core/adoption-tracker.mjs +430 -0
- package/lib/core/background-ledger.mjs +71 -0
- package/lib/core/catalog-cache.mjs +45 -7
- package/lib/core/catalog.mjs +6 -6
- package/lib/core/cost-evidence.mjs +145 -0
- package/lib/core/cost-guard.mjs +71 -44
- package/lib/core/decision-trace.mjs +413 -0
- package/lib/core/delegation.mjs +111 -50
- package/lib/core/dispatch-gates.mjs +153 -0
- package/lib/core/dispatch-guard.mjs +156 -0
- package/lib/core/dispatch-schema.mjs +103 -14
- package/lib/core/dispatch-tool.mjs +220 -204
- package/lib/core/draft-gates.mjs +45 -0
- package/lib/core/drafts-store.mjs +45 -0
- package/lib/core/escape.mjs +130 -0
- package/lib/core/evolution-advice.mjs +224 -0
- package/lib/core/evolution-ledger.mjs +300 -0
- package/lib/core/evolution-summary.mjs +255 -0
- package/lib/core/http-routes.mjs +256 -72
- package/lib/core/intersection.mjs +6 -9
- package/lib/core/presets-sync.mjs +161 -43
- package/lib/core/prices.mjs +46 -0
- package/lib/core/profile-directory.mjs +139 -0
- package/lib/core/profile-provider.mjs +42 -39
- package/lib/core/profiles-store.mjs +103 -76
- package/lib/core/pure.mjs +110 -66
- package/lib/core/reminder-store.mjs +172 -0
- package/lib/core/shims.mjs +67 -76
- package/lib/core/whitelist.mjs +23 -17
- package/package.json +82 -83
- package/presets/orchestrator/agent.cordis.yml +59 -87
- package/presets/orchestrator/NOTICE +0 -3
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// lib/core/reminder-store.mjs — 提醒系统 host 侧 store + 审计同源。
|
|
2
|
+
// 一条提醒 = 一条治理审计记录(经 audit 钩子写入 governance-audit.jsonl,与 [6] 审计
|
|
3
|
+
// 同源);每个用户动作(采纳/忽略/拒绝/关闭)与「标记已读」都追加审计留痕。P0 三要件:
|
|
4
|
+
// severity 上色(P0 由 client 用错误色渲染)、常驻至已读(unread 标记,绝不自动消失)、
|
|
5
|
+
// 带 session 标识(sessionId 字段;全局异常显式 null,client 标注「全局」)。
|
|
6
|
+
// 隐私红线:只落结构字段(severity/kind/title/detail/sessionId/时间戳)与「未采纳」
|
|
7
|
+
// 条目的展示字段(子会话 id/模式、prompt 摘要 ≤200 字符、结算摘要结构、估算成本)。
|
|
8
|
+
// prompt 摘要不存原文,结算摘要无子输出正文;其余提醒类不携带这些字段(null)。
|
|
9
|
+
// 落盘 subagent-evolution/reminders.json(原子写 tmp+rename;损坏/形状不符
|
|
10
|
+
// fail-soft 从空起步)。import-free(仅 node 内置),可被裸 CI 单测。
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const SEVERITIES = new Set(['P0', 'P1', 'P2']);
|
|
16
|
+
const MAX_REMINDERS = 200;
|
|
17
|
+
|
|
18
|
+
function isRecordShape(rec) {
|
|
19
|
+
return rec !== null && typeof rec === 'object' && typeof rec.id === 'string'
|
|
20
|
+
&& typeof rec.severity === 'string' && typeof rec.kind === 'string'
|
|
21
|
+
&& typeof rec.title === 'string' && typeof rec.detail === 'string';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 结算摘要形状守卫(宽松):只接受非数组对象,具体字段逐个归一。
|
|
25
|
+
function isSettleShape(value) {
|
|
26
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeSettled(value) {
|
|
30
|
+
if (!isSettleShape(value)) return null;
|
|
31
|
+
return {
|
|
32
|
+
stopReason: typeof value.stopReason === 'string' ? value.stopReason : '',
|
|
33
|
+
elapsedMs: Number.isFinite(value.elapsedMs) ? value.elapsedMs : null,
|
|
34
|
+
...(Number.isFinite(value.childTotalTokens) ? { childTotalTokens: value.childTotalTokens } : {}),
|
|
35
|
+
...(isSettleShape(value.childUsage) ? { childUsage: value.childUsage } : {}),
|
|
36
|
+
...(typeof value.childSessionId === 'string' && value.childSessionId !== '' ? { childSessionId: value.childSessionId } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function loadState(stateFile, state, warn) {
|
|
41
|
+
if (stateFile === undefined) return;
|
|
42
|
+
try {
|
|
43
|
+
if (!existsSync(stateFile)) return;
|
|
44
|
+
const parsed = JSON.parse(readFileSync(stateFile, 'utf8'));
|
|
45
|
+
if (parsed === null || typeof parsed !== 'object' || parsed.v !== 1 || !Array.isArray(parsed.reminders)) {
|
|
46
|
+
warn('reminder store: 提醒文件版本或形状不符,从空状态起步');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const raw of parsed.reminders) {
|
|
50
|
+
if (!isRecordShape(raw)) continue;
|
|
51
|
+
state.reminders.push({
|
|
52
|
+
id: raw.id,
|
|
53
|
+
severity: SEVERITIES.has(raw.severity) ? raw.severity : 'P2',
|
|
54
|
+
kind: raw.kind,
|
|
55
|
+
title: raw.title,
|
|
56
|
+
detail: raw.detail,
|
|
57
|
+
sessionId: typeof raw.sessionId === 'string' && raw.sessionId !== '' ? raw.sessionId : null,
|
|
58
|
+
childId: typeof raw.childId === 'string' && raw.childId !== '' ? raw.childId : null,
|
|
59
|
+
childSessionId: typeof raw.childSessionId === 'string' && raw.childSessionId !== '' ? raw.childSessionId : null,
|
|
60
|
+
childMode: typeof raw.childMode === 'string' && raw.childMode !== '' ? raw.childMode : null,
|
|
61
|
+
promptExcerpt: typeof raw.promptExcerpt === 'string' ? raw.promptExcerpt : '',
|
|
62
|
+
settled: normalizeSettled(raw.settled),
|
|
63
|
+
costEstimated: Number.isFinite(raw.costEstimated) ? raw.costEstimated : null,
|
|
64
|
+
unread: raw.unread !== false,
|
|
65
|
+
createdAt: Number.isFinite(raw.createdAt) ? raw.createdAt : 0,
|
|
66
|
+
action: typeof raw.action === 'string' ? raw.action : null,
|
|
67
|
+
actedAt: Number.isFinite(raw.actedAt) ? raw.actedAt : null,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
warn('reminder store: 提醒文件读取失败,从空状态起步(' + (error instanceof Error ? error.message : String(error)) + ')');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function persistState(stateFile, state, warn) {
|
|
76
|
+
if (stateFile === undefined) return;
|
|
77
|
+
const tmp = stateFile + '.tmp';
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(dirname(stateFile), { recursive: true });
|
|
80
|
+
writeFileSync(tmp, JSON.stringify({ v: 1, reminders: state.reminders }, null, 2), 'utf8');
|
|
81
|
+
renameSync(tmp, stateFile);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
try { rmSync(tmp, { force: true }); } catch { /* best effort */ }
|
|
84
|
+
warn('reminder store: 提醒文件写入失败(' + (error instanceof Error ? error.message : String(error)) + ')');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function auditReminder(audit, kind, reminder, action) {
|
|
89
|
+
audit({
|
|
90
|
+
kind,
|
|
91
|
+
reminderId: reminder.id,
|
|
92
|
+
severity: reminder.severity,
|
|
93
|
+
reminderKind: reminder.kind,
|
|
94
|
+
...(reminder.sessionId !== null ? { sessionId: reminder.sessionId } : {}),
|
|
95
|
+
...(action !== undefined ? { action } : {}),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 产生一条提醒:severity 非法回退 P2;空 sessionId 归一 null(全局)。「未采纳」
|
|
100
|
+
// 条目额外携带子会话 id/模式、prompt 摘要(≤200 字符再截一次防超)、结算摘要与估算
|
|
101
|
+
// 成本;其余提醒类这些字段为空/null。创建即审计。
|
|
102
|
+
function recordReminder(state, persist, audit, { severity, kind, title, detail, sessionId, childId, childSessionId, childMode, promptExcerpt, settled, costEstimated }) {
|
|
103
|
+
const reminder = {
|
|
104
|
+
id: 'r-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8),
|
|
105
|
+
severity: SEVERITIES.has(severity) ? severity : 'P2',
|
|
106
|
+
kind: typeof kind === 'string' && kind !== '' ? kind : 'event',
|
|
107
|
+
title: typeof title === 'string' ? title : '',
|
|
108
|
+
detail: typeof detail === 'string' ? detail : '',
|
|
109
|
+
sessionId: typeof sessionId === 'string' && sessionId !== '' ? sessionId : null,
|
|
110
|
+
childId: typeof childId === 'string' && childId !== '' ? childId : null,
|
|
111
|
+
childSessionId: typeof childSessionId === 'string' && childSessionId !== '' ? childSessionId : null,
|
|
112
|
+
childMode: typeof childMode === 'string' && childMode !== '' ? childMode : null,
|
|
113
|
+
promptExcerpt: typeof promptExcerpt === 'string' ? promptExcerpt.slice(0, 200) : '',
|
|
114
|
+
settled: normalizeSettled(settled),
|
|
115
|
+
costEstimated: Number.isFinite(costEstimated) ? costEstimated : null,
|
|
116
|
+
unread: true,
|
|
117
|
+
createdAt: Date.now(),
|
|
118
|
+
action: null,
|
|
119
|
+
actedAt: null,
|
|
120
|
+
};
|
|
121
|
+
state.reminders.push(reminder);
|
|
122
|
+
if (state.reminders.length > state.maxReminders) state.reminders.shift();
|
|
123
|
+
persist();
|
|
124
|
+
auditReminder(audit, 'reminder-created', reminder);
|
|
125
|
+
return reminder;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 标记已读(unread=false):P0 角标常驻至已读的「已读」动作,逐条留痕。
|
|
129
|
+
function ackReminders(state, persist, audit, ids) {
|
|
130
|
+
const list = Array.isArray(ids) ? ids : [];
|
|
131
|
+
const acked = [];
|
|
132
|
+
for (const reminder of state.reminders) {
|
|
133
|
+
if (reminder.unread !== true || !list.includes(reminder.id)) continue;
|
|
134
|
+
reminder.unread = false;
|
|
135
|
+
acked.push(reminder.id);
|
|
136
|
+
auditReminder(audit, 'reminder-acked', reminder);
|
|
137
|
+
}
|
|
138
|
+
if (acked.length > 0) persist();
|
|
139
|
+
return acked;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 用户动作(采纳/忽略/拒绝/关闭):互斥终态 + 审计留痕(关闭留痕要求)。
|
|
143
|
+
function actOnReminder(state, persist, audit, id, action) {
|
|
144
|
+
const reminder = state.reminders.find((r) => r.id === id);
|
|
145
|
+
if (reminder === undefined) return false;
|
|
146
|
+
if (action !== 'adopt' && action !== 'ignore' && action !== 'reject' && action !== 'dismiss') return false;
|
|
147
|
+
reminder.action = action;
|
|
148
|
+
reminder.actedAt = Date.now();
|
|
149
|
+
reminder.unread = false;
|
|
150
|
+
persist();
|
|
151
|
+
auditReminder(audit, 'reminder-action', reminder, action);
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function listReminders(state, unreadOnly) {
|
|
156
|
+
const all = [...state.reminders].reverse();
|
|
157
|
+
return unreadOnly ? all.filter((r) => r.unread === true) : all;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function createReminderStore({ dshHome, audit = () => {}, warn = () => {}, maxReminders = MAX_REMINDERS } = {}) {
|
|
161
|
+
const state = { reminders: [], maxReminders: Number.isFinite(maxReminders) && maxReminders > 0 ? maxReminders : MAX_REMINDERS };
|
|
162
|
+
const stateFile = dshHome !== undefined && dshHome !== '' ? join(dshHome, 'subagent-evolution', 'reminders.json') : undefined;
|
|
163
|
+
loadState(stateFile, state, warn);
|
|
164
|
+
const persist = () => persistState(stateFile, state, warn);
|
|
165
|
+
return {
|
|
166
|
+
record: (entry) => recordReminder(state, persist, audit, entry),
|
|
167
|
+
ack: (ids) => ackReminders(state, persist, audit, ids),
|
|
168
|
+
actOn: (id, action) => actOnReminder(state, persist, audit, id, action),
|
|
169
|
+
list: ({ unreadOnly = false } = {}) => listReminders(state, unreadOnly),
|
|
170
|
+
unreadCount: () => state.reminders.filter((r) => r.unread === true).length,
|
|
171
|
+
};
|
|
172
|
+
}
|
package/lib/core/shims.mjs
CHANGED
|
@@ -1,51 +1,42 @@
|
|
|
1
|
-
// lib/core/shims.mjs — facade
|
|
2
|
-
//
|
|
3
|
-
// top-level-scattered import surface. Two failure classes:
|
|
1
|
+
// lib/core/shims.mjs — facade。唯一 import index.mjs 依赖的 @deepseek-ai
|
|
2
|
+
// 符号的模块,收敛此前散落在顶层的 import 面。两类失败:
|
|
4
3
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// wants for the delegation-depth safety gates: no weakened reimplementation is
|
|
11
|
-
// ever substituted, so a child can never be dispatched past MAX_DEPTH.
|
|
4
|
+
// 守卫型(fail-loud,无回退):assertSubagentMaxDepth / resolveChildDepth
|
|
5
|
+
// 是**静态** import 并再导出。宿主 rc 里改名/移除会让本模块——进而
|
|
6
|
+
// index.mjs——在加载时以 ESM 链接错误失败("The requested module ... does
|
|
7
|
+
// not provide an export named '...'")。这正是委派深度安全闸想要的加载时
|
|
8
|
+
// 隔离:绝不替换为弱化实现,子 Agent 永远不可能派发超过 MAX_DEPTH。
|
|
12
9
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// captureDelegatedPolicyOverrides / resolveChildAgentOptions / defineTool
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// local functionally-equivalent (or safe-degraded) implementation is used.
|
|
10
|
+
// 函数映射型(fail-soft + warn):foldConsumedWork / finalAssistantOutput /
|
|
11
|
+
// createUserMessage / appendDelegatedPolicyOverrides /
|
|
12
|
+
// captureDelegatedPolicyOverrides / resolveChildAgentOptions / defineTool
|
|
13
|
+
// 用 try/catch 包裹的动态顶层 await import 加载。导入失败(或命名导出被
|
|
14
|
+
// 改名/移除)时模块仍能加载,发出 console.warn(模块顶层没有 ctx,没有
|
|
15
|
+
// logger),并使用本地功能等价(或安全降级)实现。
|
|
20
16
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// also verifies the value is a function before accepting it.
|
|
17
|
+
// 动态 import 的包能加载但命名导出被改名/移除时,解构结果为 `undefined`
|
|
18
|
+
// 且**不抛**,所以 loadSoft 还校验值是函数后才接受。
|
|
24
19
|
|
|
25
20
|
import { randomUUID } from 'node:crypto';
|
|
26
21
|
import { createRequire } from 'node:module';
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// error BEFORE apply can run, which is the isolation this class exists for.
|
|
22
|
+
// 守卫型:静态、fail-loud、无回退。只保留这两个静态 @deepseek-ai import;
|
|
23
|
+
// 导出缺失会在 apply 能运行之前就以明确错误中止模块加载——这正是本类存在的隔离。
|
|
30
24
|
import { assertSubagentMaxDepth, resolveChildDepth } from '@deepseek-ai/dsh-subagent';
|
|
31
25
|
import { toStopReason } from './pure.mjs';
|
|
32
26
|
|
|
33
|
-
// warn
|
|
34
|
-
//
|
|
27
|
+
// warn:模块顶层没有 ctx / logger,降级用 console.warn。
|
|
28
|
+
// 前缀让来源在共享宿主日志中可辨认。
|
|
35
29
|
function warn(...parts) {
|
|
36
30
|
console.warn('[dsh-subagent-profile]', ...parts);
|
|
37
31
|
}
|
|
38
32
|
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// override the failure route (e.g. stub it to throw, or return a module object
|
|
44
|
-
// missing the symbol). It is exported as a seam for that purpose.
|
|
33
|
+
// 加载一个函数映射型 `@deepseek-ai` 符号,包或命名导出不可用时返回本地回退。
|
|
34
|
+
// `warnMessage` 携带可操作的用户面向文案。`importer` 是可注入加载器(缺省为
|
|
35
|
+
// 动态 `import`——见 DYNAMIC_IMPORT),测试 / 未来加载器可覆盖失败路径
|
|
36
|
+
// (如 stub 成抛错,或返回缺该符号的模块对象)。为此导出为接缝。
|
|
45
37
|
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// the dynamic import expression.
|
|
38
|
+
// 注意:`import` 是关键字,不能作值引用(`importer = import` 是 SyntaxError),
|
|
39
|
+
// 故缺省值是动态 import 表达式的薄包装。
|
|
49
40
|
const DYNAMIC_IMPORT = (specifier) => import(specifier);
|
|
50
41
|
async function loadSoft(pkg, symbol, fallback, warnMessage, importer = DYNAMIC_IMPORT) {
|
|
51
42
|
try {
|
|
@@ -60,11 +51,9 @@ async function loadSoft(pkg, symbol, fallback, warnMessage, importer = DYNAMIC_I
|
|
|
60
51
|
}
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
// ---
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// can exercise the degraded path even though the junction packages resolve
|
|
67
|
-
// successfully here.
|
|
54
|
+
// --- 本地回退(import-free、duck-typed、功能等价)------------------------------
|
|
55
|
+
// 每个都是保留官方助手可观察契约的最小本地实现;经 `__fallbacks` 导出,测试在
|
|
56
|
+
// junction 包解析成功时也能走降级路径。
|
|
68
57
|
|
|
69
58
|
// foldConsumedWork: **近似、非等价**——readResult 只读 `.end`(终止 turn/end 事件)来
|
|
70
59
|
// 推导 stopReason。shipped fold 是精密的 stepped/claimed 状态机;本降级实现取最后一个
|
|
@@ -80,8 +69,8 @@ function foldConsumedWorkFallback(events) {
|
|
|
80
69
|
return { ...(end === undefined ? {} : { end }), droppedUnrun: false };
|
|
81
70
|
}
|
|
82
71
|
|
|
83
|
-
// finalAssistantOutput
|
|
84
|
-
//
|
|
72
|
+
// finalAssistantOutput:相同的 fold 规则——最后一个非空 assistant/message,
|
|
73
|
+
// 否则累加的 text-delta 分片,再否则 undefined。
|
|
85
74
|
function finalAssistantOutputFallback(events) {
|
|
86
75
|
let message;
|
|
87
76
|
const partial = [];
|
|
@@ -99,11 +88,10 @@ function finalAssistantOutputFallback(events) {
|
|
|
99
88
|
return text.length > 0 ? [{ type: 'text', text }] : undefined;
|
|
100
89
|
}
|
|
101
90
|
|
|
102
|
-
// createUserMessage
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
// the shipped deepFreeze immutability; treat it as a plain message object.
|
|
91
|
+
// createUserMessage:构造 followup 驱动需要的 user 角色消息。官方助手用品牌化
|
|
92
|
+
// MessageId + deepFreeze;回退提供相同可观察形状(role/content/id/source)加
|
|
93
|
+
// 新鲜随机 id。注意:返回对象是**可变**的(无 deepFreeze)——消费方不得依赖
|
|
94
|
+
// 官方 deepFreeze 的不可变性;把它当普通消息对象。
|
|
107
95
|
function createUserMessageFallback(input) {
|
|
108
96
|
return {
|
|
109
97
|
...input,
|
|
@@ -112,10 +100,9 @@ function createUserMessageFallback(input) {
|
|
|
112
100
|
};
|
|
113
101
|
}
|
|
114
102
|
|
|
115
|
-
// appendDelegatedPolicyOverrides
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// faithfully to keep the child's own log reconstructable and the pin visible.
|
|
103
|
+
// appendDelegatedPolicyOverrides:委派策略写进子会话日志供重建。这也是安全
|
|
104
|
+
// 相关的「approval: never」钉,绝不能是 no-op——忠实复现追加,保持子会话自身
|
|
105
|
+
// 日志可重建、钉可见。
|
|
119
106
|
function appendDelegatedPolicyOverridesFallback(childSession, overrides) {
|
|
120
107
|
const o = overrides || {};
|
|
121
108
|
if (o.sandboxMode !== undefined) {
|
|
@@ -126,9 +113,8 @@ function appendDelegatedPolicyOverridesFallback(childSession, overrides) {
|
|
|
126
113
|
}
|
|
127
114
|
}
|
|
128
115
|
|
|
129
|
-
// captureDelegatedPolicyOverrides
|
|
130
|
-
//
|
|
131
|
-
// approval service. Optional chaining keeps a mock parent (tests) working.
|
|
116
|
+
// captureDelegatedPolicyOverrides:父的显式沙箱覆盖(无则 undefined)加父有
|
|
117
|
+
// approval 服务时的审批钉 'never'。可选链让 mock 父(测试)正常工作。
|
|
132
118
|
function captureDelegatedPolicyOverridesFallback(parent) {
|
|
133
119
|
return {
|
|
134
120
|
sandboxMode: parent.ctx?.get?.('sandboxPolicy')?.overrideOf?.(parent.session),
|
|
@@ -136,10 +122,9 @@ function captureDelegatedPolicyOverridesFallback(parent) {
|
|
|
136
122
|
};
|
|
137
123
|
}
|
|
138
124
|
|
|
139
|
-
// resolveChildAgentOptions
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// break delegation routing.
|
|
125
|
+
// resolveChildAgentOptions:合并父路由(provider/model/maxTokens,存在才带)
|
|
126
|
+
// 与 per-child 覆盖,并盖上子自身的深度。必须保持子配置语义——no-op 会静默
|
|
127
|
+
// 破坏委派路由。
|
|
143
128
|
function resolveChildAgentOptionsFallback(parent, requested, childDepth) {
|
|
144
129
|
const parentProvider = parent.options?.provider;
|
|
145
130
|
const parentModel = parent.options?.model;
|
|
@@ -153,19 +138,16 @@ function resolveChildAgentOptionsFallback(parent, requested, childDepth) {
|
|
|
153
138
|
};
|
|
154
139
|
}
|
|
155
140
|
|
|
156
|
-
// defineTool
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// of a cryptic module-not-found at import time(加载不崩溃,调用点才报错)。
|
|
141
|
+
// defineTool:没有 dsh-tools 就没有 dispatch 工具。在用途点 fail-loud 并给出
|
|
142
|
+
// 清晰可操作的文案——模块仍然加载,apply 期间调用它才暴露确切的缺失依赖
|
|
143
|
+
// (加载不崩溃,调用点才报错)。
|
|
160
144
|
function defineToolFallback() {
|
|
161
145
|
throw new Error('dsh-tools 缺失:dispatch 工具不可用');
|
|
162
146
|
}
|
|
163
147
|
|
|
164
|
-
// ---
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// keep the real functions when the junction resolves, and the fallbacks when a
|
|
168
|
-
// host rc renamed/removed a symbol.
|
|
148
|
+
// --- 接线函数映射型符号(模块顶层 await)---------------------------------------
|
|
149
|
+
// 七个加载相互独立,并行(Promise.all)以削减模块加载延迟;每个失败仍降级到
|
|
150
|
+
// 本地回退。junction 解析成功时保留真函数,宿主 rc 改名/移除符号时用回退。
|
|
169
151
|
const [foldConsumedWork, finalAssistantOutput, createUserMessage, appendDelegatedPolicyOverrides, captureDelegatedPolicyOverrides, resolveChildAgentOptions, defineTool] = await Promise.all([
|
|
170
152
|
loadSoft('@deepseek-ai/dsh-agent', 'foldConsumedWork', foldConsumedWorkFallback, 'dsh-agent 的 foldConsumedWork 不可用,结果裁切使用本地降级实现'),
|
|
171
153
|
loadSoft('@deepseek-ai/dsh-subagent', 'finalAssistantOutput', finalAssistantOutputFallback, 'dsh-subagent 的 finalAssistantOutput 不可用,子结果选取使用本地降级实现'),
|
|
@@ -176,9 +158,9 @@ const [foldConsumedWork, finalAssistantOutput, createUserMessage, appendDelegate
|
|
|
176
158
|
loadSoft('@deepseek-ai/dsh-tools', 'defineTool', defineToolFallback, 'dsh-tools 缺失:dispatch 工具不可用'),
|
|
177
159
|
]);
|
|
178
160
|
|
|
179
|
-
// readResult
|
|
180
|
-
//
|
|
181
|
-
//
|
|
161
|
+
// readResult:官方形状。终止 turn 的原因来自 foldConsumedWork;选取的输出来自
|
|
162
|
+
// finalAssistantOutput(最后一个非空 assistant/message,否则拼接的 text-delta
|
|
163
|
+
// 分片,再否则 undefined -> [])。
|
|
182
164
|
function readResult(child, boundary, cancelled) {
|
|
183
165
|
const own = child.session.events.slice(boundary);
|
|
184
166
|
const end = foldConsumedWork(own).end;
|
|
@@ -187,7 +169,7 @@ function readResult(child, boundary, cancelled) {
|
|
|
187
169
|
return { output: finalAssistantOutput(own) ?? [], stopReason };
|
|
188
170
|
}
|
|
189
171
|
|
|
190
|
-
// ---
|
|
172
|
+
// --- 版本探测(纯探测)--------------------------------------------------------
|
|
191
173
|
// 读取 @deepseek-ai 三包(subagent/agent/llm)的 package.json version,与
|
|
192
174
|
// peerDependencies 范围(>=PEER_MIN <PEER_MAX)比对后产出中文 warnings。纯探测:
|
|
193
175
|
// 只读 manifest、不 import 新符号、不触发副作用;每包独立 try/catch,失败记
|
|
@@ -209,6 +191,16 @@ function readPackageVersion(pkg) {
|
|
|
209
191
|
}
|
|
210
192
|
}
|
|
211
193
|
|
|
194
|
+
// 读本插件自身 package.json version(fail-soft 'unknown'),供维护区显示当前版本。
|
|
195
|
+
function readOwnPluginVersion() {
|
|
196
|
+
try {
|
|
197
|
+
const manifest = requirePkg('../../package.json');
|
|
198
|
+
return typeof manifest.version === 'string' && manifest.version !== '' ? manifest.version : 'unknown';
|
|
199
|
+
} catch {
|
|
200
|
+
return 'unknown';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
212
204
|
// 极简 semver 比较:major.minor.patch + 可选 `-预发布` 段(覆盖 peerDependencies
|
|
213
205
|
// 范围判断所需)。预发布 < 正式版;预发布段逐段比较,纯数字段按数值、否则按字典序
|
|
214
206
|
// (`0.1.0-rc.10` > `0.1.0-rc.6`)。
|
|
@@ -267,12 +259,11 @@ export function detectVersions(reader = readPackageVersion) {
|
|
|
267
259
|
warnings.push(`@deepseek-ai/${pkg} 版本 ${version} 超出 peerDependencies 范围(>=${PEER_MIN} <${PEER_MAX}),派发行为可能与预期不符`);
|
|
268
260
|
}
|
|
269
261
|
}
|
|
270
|
-
return { versions, warnings };
|
|
262
|
+
return { versions, warnings, pluginVersion: readOwnPluginVersion() };
|
|
271
263
|
}
|
|
272
264
|
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// fail-soft path without deleting node_modules).
|
|
265
|
+
// 仅供测试访问本地降级实现(包导入在这里可解析,真函数胜出;`__fallbacks`
|
|
266
|
+
// 让测试无需删 node_modules 即可走 fail-soft 路径)。
|
|
276
267
|
export const __fallbacks = {
|
|
277
268
|
foldConsumedWork: foldConsumedWorkFallback,
|
|
278
269
|
finalAssistantOutput: finalAssistantOutputFallback,
|
|
@@ -286,7 +277,7 @@ export const __fallbacks = {
|
|
|
286
277
|
export {
|
|
287
278
|
assertSubagentMaxDepth,
|
|
288
279
|
resolveChildDepth,
|
|
289
|
-
// ----
|
|
280
|
+
// ---- 测试接缝 / 可注入加载器(见 loadSoft 文档)----
|
|
290
281
|
loadSoft,
|
|
291
282
|
foldConsumedWork,
|
|
292
283
|
finalAssistantOutput,
|
|
@@ -296,6 +287,6 @@ export {
|
|
|
296
287
|
resolveChildAgentOptions,
|
|
297
288
|
defineTool,
|
|
298
289
|
readResult,
|
|
299
|
-
// ----
|
|
290
|
+
// ---- 版本探测接缝(见 detectVersions 文档)----
|
|
300
291
|
readPackageVersion,
|
|
301
292
|
};
|
package/lib/core/whitelist.mjs
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// import-free (no @deepseek-ai dependency).
|
|
1
|
+
// lib/core/whitelist.mjs — 目标预设白名单:从运行时名册推导,不硬编码——
|
|
2
|
+
// agentPresets 存在时取 system-trust 预设,否则用官方回退名单。从 index.mjs
|
|
3
|
+
// 逐字移出;import-free(无 @deepseek-ai 依赖)。
|
|
5
4
|
//
|
|
6
|
-
//
|
|
7
|
-
// `agentCtx.get('agentPresets')
|
|
8
|
-
// `resolveWhitelist(parent.ctx.get('agentPresets'))
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// unchanged. FALLBACK_WHITELIST is exported for the tests.
|
|
12
|
-
|
|
13
|
-
// The target-preset whitelist is derived from the runtime roster, not
|
|
14
|
-
// hard-coded: system-trust presets when agentPresets exists, else the
|
|
15
|
-
// shipped fallback names.
|
|
5
|
+
// 注入说明:原 apply 闭包版本自带 `agentCtx` 并自己读
|
|
6
|
+
// `agentCtx.get('agentPresets')`。调用方现在注入服务——
|
|
7
|
+
// `resolveWhitelist(parent.ctx.get('agentPresets'))`——使模块不依赖 apply 闭包
|
|
8
|
+
// 状态。`parent.ctx.get('agentPresets')` 得到完全相同的结果(服务或 undefined),
|
|
9
|
+
// 回退语义不变。FALLBACK_WHITELIST 导出供测试用。
|
|
16
10
|
export const FALLBACK_WHITELIST = ['standard', 'code', 'minimal'];
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
// 逃生舱叠加(可选):escapeSet 是非 system-trust 预设的显式放行集(Set 或数组,
|
|
13
|
+
// 由调用方在开关打开时传入)。只叠加——base 集原样保留、去重;逃生舱绝不替代或
|
|
14
|
+
// 缩减 base 集,也不升级 base 成员的语义(逃生舱成员仍属「非 system 放行」)。
|
|
15
|
+
// escapeSet 缺省/为空时零叠加(行为与旧单参调用完全一致)。
|
|
16
|
+
function withEscapeSet(base, escapeSet) {
|
|
17
|
+
if (escapeSet === undefined) return base;
|
|
18
|
+
const extras = [...escapeSet].filter((id) => typeof id === 'string' && id !== '');
|
|
19
|
+
if (extras.length === 0) return base;
|
|
20
|
+
return [...new Set([...base, ...extras])];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function resolveWhitelist(agentPresets, escapeSet) {
|
|
24
|
+
if (agentPresets === undefined) return withEscapeSet(FALLBACK_WHITELIST, escapeSet);
|
|
20
25
|
const presets = await agentPresets.list();
|
|
21
|
-
|
|
26
|
+
const base = (presets ?? []).filter((preset) => preset && preset.trust === 'system').map((preset) => preset.id);
|
|
27
|
+
return withEscapeSet(base, escapeSet);
|
|
22
28
|
}
|