@v587d/capital-generation 2.8.3 → 2.9.1
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 +81 -1
- package/README.md +40 -19
- package/chart-ui/README.md +5 -4
- package/chart-ui/client.src.cjs +7 -16
- package/chart-ui/index.js +12 -33
- package/chart-ui/package.json +2 -2
- package/config/client.js +18 -39
- package/config/client.src.cjs +79 -217
- package/config/index.js +22 -72
- package/config/package.json +2 -2
- package/cordis.patch.yml +46 -51
- package/lib/agents/bash-guard.d.ts +9 -55
- package/lib/agents/bash-guard.js +27 -67
- package/lib/agents/preset-roster.d.ts +22 -0
- package/lib/agents/preset-roster.js +87 -0
- package/lib/agents/role-tool-policy.d.ts +46 -0
- package/lib/agents/role-tool-policy.js +205 -0
- package/lib/agents/root-tool-policy.d.ts +28 -124
- package/lib/agents/root-tool-policy.js +164 -237
- package/lib/agents/selected-skills-provider.d.ts +2 -6
- package/lib/agents/selected-skills-provider.js +27 -84
- package/lib/capital-settings.d.ts +27 -0
- package/lib/capital-settings.js +23 -0
- package/lib/chart/artifact-ref.d.ts +13 -8
- package/lib/chart/artifact-ref.js +27 -10
- package/lib/chart/downsample.d.ts +2 -5
- package/lib/chart/downsample.js +3 -8
- package/lib/chart/errors.d.ts +3 -7
- package/lib/chart/errors.js +1 -2
- package/lib/chart/event-context.d.ts +8 -0
- package/lib/chart/event-context.js +39 -0
- package/lib/chart/events.d.ts +21 -78
- package/lib/chart/events.js +43 -189
- package/lib/chart/html.d.ts +2 -5
- package/lib/chart/html.js +8 -15
- package/lib/chart/present-tool.d.ts +28 -0
- package/lib/chart/present-tool.js +128 -0
- package/lib/chart/runtime.d.ts +5 -18
- package/lib/chart/runtime.js +14 -34
- package/lib/chart/series.d.ts +10 -17
- package/lib/chart/series.js +22 -47
- package/lib/chart/source-token.d.ts +5 -13
- package/lib/chart/source-token.js +18 -17
- package/lib/chart/spec.d.ts +6 -12
- package/lib/chart/spec.js +7 -31
- package/lib/chart/tool.d.ts +7 -29
- package/lib/chart/tool.js +15 -25
- package/lib/chart-render-entry.d.ts +10 -0
- package/lib/chart-render-entry.js +14 -0
- package/lib/chart-source-entry.d.ts +13 -0
- package/lib/chart-source-entry.js +20 -0
- package/lib/collect-entry.d.ts +9 -0
- package/lib/collect-entry.js +10 -0
- package/lib/collector-runtime.d.ts +22 -0
- package/lib/collector-runtime.js +97 -0
- package/lib/data-collector/args.d.ts +6 -24
- package/lib/data-collector/args.js +6 -24
- package/lib/data-collector/dataset-tools.d.ts +7 -1
- package/lib/data-collector/dataset-tools.js +27 -56
- package/lib/data-collector/describe.d.ts +8 -33
- package/lib/data-collector/describe.js +17 -70
- package/lib/data-collector/hub.d.ts +27 -85
- package/lib/data-collector/hub.js +19 -85
- package/lib/data-collector/query-time.d.ts +7 -26
- package/lib/data-collector/query-time.js +8 -27
- package/lib/data-collector/store.d.ts +41 -117
- package/lib/data-collector/store.js +32 -111
- package/lib/data-collector/time-axis.d.ts +16 -76
- package/lib/data-collector/time-axis.js +25 -111
- package/lib/data-collector/tools.d.ts +6 -6
- package/lib/data-collector/tools.js +34 -43
- package/lib/dataset-read-entry.d.ts +9 -0
- package/lib/dataset-read-entry.js +7 -0
- package/lib/index.d.ts +7 -117
- package/lib/index.js +43 -422
- package/lib/net/eastmoney-client.d.ts +13 -65
- package/lib/net/eastmoney-client.js +12 -53
- package/lib/net/throttle.d.ts +2 -13
- package/lib/net/throttle.js +3 -14
- package/lib/ocr/client.d.ts +12 -21
- package/lib/ocr/client.js +23 -40
- package/lib/ocr/markdown.d.ts +20 -26
- package/lib/ocr/markdown.js +23 -36
- package/lib/ocr/store.d.ts +14 -47
- package/lib/ocr/store.js +15 -45
- package/lib/ocr-entry.d.ts +9 -0
- package/lib/ocr-entry.js +10 -0
- package/lib/policy-entry.d.ts +17 -0
- package/lib/policy-entry.js +22 -0
- package/lib/present-entry.d.ts +9 -0
- package/lib/present-entry.js +13 -0
- package/lib/preset-claim.d.ts +22 -0
- package/lib/preset-claim.js +24 -0
- package/lib/preset-ids.d.ts +38 -0
- package/lib/preset-ids.js +46 -0
- package/lib/retriever-config.d.ts +37 -0
- package/lib/retriever-config.js +36 -0
- package/lib/retriever-runtime.d.ts +31 -0
- package/lib/retriever-runtime.js +82 -0
- package/lib/root-extras-entry.d.ts +17 -0
- package/lib/root-extras-entry.js +25 -0
- package/lib/runtime-entry.d.ts +15 -0
- package/lib/runtime-entry.js +5 -0
- package/lib/runtime.d.ts +21 -0
- package/lib/runtime.js +33 -0
- package/lib/search-entry.d.ts +9 -0
- package/lib/search-entry.js +9 -0
- package/lib/sources/eastmoney-http.js +20 -68
- package/lib/sources/ftshare-rest.d.ts +6 -34
- package/lib/sources/ftshare-rest.js +84 -292
- package/lib/sources/fuyao-core.d.ts +8 -18
- package/lib/sources/fuyao-core.js +10 -21
- package/lib/sources/fuyao-rest.d.ts +2 -6
- package/lib/sources/fuyao-rest.js +23 -102
- package/lib/sources/security-code.d.ts +6 -12
- package/lib/sources/security-code.js +6 -12
- package/lib/sources/security-identity.d.ts +1 -4
- package/lib/sources/security-identity.js +3 -8
- package/lib/sources/tencent-http.js +14 -28
- package/lib/sources/tencent-public-core.d.ts +8 -60
- package/lib/sources/tencent-public-core.js +12 -77
- package/lib/sources/wind-mcp.d.ts +0 -5
- package/lib/sources/wind-mcp.js +23 -78
- package/lib/sources-entry.d.ts +9 -0
- package/lib/sources-entry.js +9 -0
- package/lib/time/tools.d.ts +8 -31
- package/lib/time/tools.js +30 -66
- package/lib/time-entry.d.ts +9 -0
- package/lib/time-entry.js +7 -0
- package/lib/tool-exec.d.ts +45 -11
- package/lib/tool-exec.js +59 -6
- package/lib/user-customization.d.ts +14 -0
- package/lib/user-customization.js +47 -0
- package/lib/watchlist/tools.d.ts +3 -13
- package/lib/watchlist/tools.js +10 -30
- package/lib/watchlist-entry.d.ts +9 -0
- package/lib/watchlist-entry.js +6 -0
- package/lib/web-retriever/engines.d.ts +6 -11
- package/lib/web-retriever/engines.js +9 -14
- package/lib/web-retriever/html-markdown.d.ts +14 -34
- package/lib/web-retriever/html-markdown.js +39 -75
- package/lib/web-retriever/local-fetch.d.ts +22 -58
- package/lib/web-retriever/local-fetch.js +33 -77
- package/lib/web-retriever/retriever.d.ts +6 -18
- package/lib/web-retriever/retriever.js +7 -7
- package/lib/web-retriever/sources.d.ts +37 -124
- package/lib/web-retriever/sources.js +64 -196
- package/lib/web-retriever/tools.d.ts +43 -27
- package/lib/web-retriever/tools.js +99 -137
- package/lib/web-retriever/wind-client.d.ts +14 -31
- package/lib/web-retriever/wind-client.js +16 -32
- package/locale/zh.json +1 -1
- package/package.json +72 -7
- package/preset/capital-generation/README.md +87 -53
- package/preset/capital-generation/agent.patch.yml +532 -337
- package/preset/capital-generation/skills/capital-chart-protocol/SKILL.md +6 -4
- package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
- package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-equity-research/SKILL.md +4 -2
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +30 -33
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +14 -16
- package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
- package/watchlist/client.js +15 -148
- package/watchlist/client.src.cjs +148 -662
- package/watchlist/holding-rules.js +11 -38
- package/watchlist/index.js +127 -397
- package/watchlist/package.json +2 -2
package/config/index.js
CHANGED
|
@@ -4,28 +4,10 @@ import { SELECTED_SKILL_CATALOG, SELECTED_SKILL_SKILL_DIRS } from '../selected-s
|
|
|
4
4
|
export const name = 'capital-config'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 1. **它是卡片的取数通道**:浏览器半边 `ctx.configForms.get(条目 id)` 与
|
|
12
|
-
* `whileServed([条目 id])` 都按它寻址;卡片本身注册进 `plugins.bundle.config`,
|
|
13
|
-
* key 是**组合包的包名**(`@v587d/capital-generation`),不是 `<包名>#<行 id>`。
|
|
14
|
-
* 条目 id 或包名任一处漂移,表现都是**卡片静默消失**(零报错)。
|
|
15
|
-
* 2. **它是取数配置的来源**:主插件 `@v587d/capital-generation` 经
|
|
16
|
-
* `settings.describe()` 读同一条目的解析值,拿 `fuyaoCredentialRef` /
|
|
17
|
-
* `retriever.credentialRef` / `retriever.windDocs.credentialRef` /
|
|
18
|
-
* `retriever.paddleOcr.credentialRef` 这些**引用名**去寻密钥。
|
|
19
|
-
*
|
|
20
|
-
* 密钥本身恒不进本条目:API Key 写在 credentials 域(`remote.credentials.set`),
|
|
21
|
-
* 这里只存引用名。唯一写进配置的偏好是回退开关 `retriever.localFetch.enabled`。
|
|
22
|
-
*
|
|
23
|
-
* ⛔ 字段必须 `.volatile()`:`volatileForm(schema)` 在没有任何 volatile 字段时返回
|
|
24
|
-
* `undefined`,该条目就**不进** describe 镜像——卡片读不到命名空间,写入也会被
|
|
25
|
-
* `Config field "…" is not volatile` 拒绝。非 volatile 字段仍是普通配置,只能改 profile 文件。
|
|
26
|
-
*
|
|
27
|
-
* 浏览器半边镜像了这个 id(`client.src.cjs` 的 `ENTRY_ID`),两处同改由
|
|
28
|
-
* `test/capital-config.test.mjs` 钉住。
|
|
7
|
+
* Profile entry id; 0.1.7 makes it the settings namespace itself. The browser half
|
|
8
|
+
* addresses it via `configForms.get(ENTRY_ID)` and the main plugin reads the same
|
|
9
|
+
* entry's `*.credentialRef` names; drift makes the card vanish silently. Every
|
|
10
|
+
* editable field must be `.volatile()` or describe omits it.
|
|
29
11
|
*/
|
|
30
12
|
export const SETTINGS_ENTRY_ID = 'capital-config'
|
|
31
13
|
|
|
@@ -36,11 +18,8 @@ const WindDocsSchema = z.object({
|
|
|
36
18
|
})
|
|
37
19
|
|
|
38
20
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* 数值口径对齐官方 `dsh-web-fetch-http`(timeoutMs=30000 / maxBodyChars=100000);
|
|
42
|
-
* `maxContentChars` 切在转换前的原始 HTML 上,所以按 HTML 体积给足。
|
|
43
|
-
* `userAgent` 默认是产品标识;显式配成空串时消费点回落到插件版本号。
|
|
21
|
+
* Must match `LOCAL_FETCH_DEFAULTS` in `src/index.ts` verbatim (test pins it).
|
|
22
|
+
* `maxContentChars` cuts the pre-conversion HTML, so it is sized by HTML volume.
|
|
44
23
|
*/
|
|
45
24
|
export const LOCAL_FETCH_DEFAULTS = {
|
|
46
25
|
enabled: true,
|
|
@@ -61,12 +40,8 @@ const LocalFetchSchema = z.object({
|
|
|
61
40
|
})
|
|
62
41
|
|
|
63
42
|
/**
|
|
64
|
-
*
|
|
65
|
-
* `
|
|
66
|
-
*
|
|
67
|
-
* `endpoint` / `model` 留空是把真值让给 `src/ocr/client.ts`;`pollBudgetMs: 0` = 客户端默认的
|
|
68
|
-
* 200000 毫秒自有等待预算,必须小于 `ocr` 工具声明的 240000 超时,否则框架先超时、
|
|
69
|
-
* 回执里没有 `doc_id` 可续查。
|
|
43
|
+
* Must match `PADDLE_OCR_DEFAULTS` in `src/index.ts` verbatim.
|
|
44
|
+
* `pollBudgetMs: 0` = client default 200000 ms, below the tool's 240000 timeout.
|
|
70
45
|
*/
|
|
71
46
|
export const PADDLE_OCR_DEFAULTS = {
|
|
72
47
|
endpoint: '',
|
|
@@ -84,15 +59,15 @@ const PaddleOcrSchema = z.object({
|
|
|
84
59
|
|
|
85
60
|
const SelectedSkillsShape = Object.fromEntries(SELECTED_SKILL_CATALOG.map((entry) => [
|
|
86
61
|
entry.key,
|
|
87
|
-
z.boolean().default(false).volatile().description(`Enable ${entry.name} for the
|
|
62
|
+
z.boolean().default(false).volatile().description(`Enable ${entry.name} for the Auto mode root Agent (experimental; default off)`),
|
|
88
63
|
]))
|
|
89
64
|
const SelectedSkillsSchema = z.object(SelectedSkillsShape)
|
|
90
65
|
|
|
91
66
|
const SELECTED_SKILL_DEFAULTS = Object.fromEntries(SELECTED_SKILL_CATALOG.map((entry) => [entry.key, false]))
|
|
92
67
|
|
|
93
68
|
export const Config = z.object({
|
|
94
|
-
customPersona: z.string().default('').volatile().description('
|
|
95
|
-
selectedSkills: SelectedSkillsSchema.default(SELECTED_SKILL_DEFAULTS).description('Explicitly selected community experiments; default off and visible only to the
|
|
69
|
+
customPersona: z.string().default('').volatile().description('自动模式附加人设文本'),
|
|
70
|
+
selectedSkills: SelectedSkillsSchema.default(SELECTED_SKILL_DEFAULTS).description('Explicitly selected community experiments; default off and visible only to the Auto mode root Agent'),
|
|
96
71
|
fuyaoCredentialRef: z.string().default('FUYAO_API_KEY').volatile().description('Fuyao credentials 引用名'),
|
|
97
72
|
ftshareCredentialRef: z.string().default('FTSHARE_API_KEY').volatile().description('FTShare(非凸科技)credentials 引用名'),
|
|
98
73
|
retriever: z.object({
|
|
@@ -115,30 +90,17 @@ export const Config = z.object({
|
|
|
115
90
|
})
|
|
116
91
|
|
|
117
92
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* 整体送达浏览器(`window.__ModuleLoader__.load` 是单文件 IIFE,没有按需分片),750 KB 会
|
|
123
|
-
* 让每一次打开插件页都付一遍——包括从不点「详情」的那次。旁路只在悬停时取一份,且取的是
|
|
124
|
-
* **磁盘上当前那份**,不是构建期抄的副本:抄来的原文会随快照更新而变旧,而这块 UI 说的
|
|
125
|
-
* 是"这就是原文"(控件对自己的状态说谎 = 事故)。
|
|
126
|
-
*
|
|
127
|
-
* 闭集:路径按 `SELECTED_SKILL_CATALOG` × `SELECTED_SKILL_SKILL_DIRS` 在模块加载时现算成
|
|
128
|
-
* `Map<skill 名, file: URL>`,请求里的名字**只做这张表的键**,不参与任何路径拼接——
|
|
129
|
-
* `%2F..%2F` 之类一律落在"表里没有这个名字"那一侧(404)。路径用 `URL` 表示:`fs` 直接收
|
|
130
|
-
* `file:` URL,省掉一次 `fileURLToPath`(也就省掉在 import 期依赖 node 内置模块)。
|
|
131
|
-
*
|
|
132
|
-
* ⛔ 认证围栏与 `/capital-charts`、`/capital-watchlist` 同一写法(`connection.requestRejection`,
|
|
133
|
-
* docs/dev/chart-presentation.md §6.1):`webServer.register({ kind: 'prefix' })` 本身不做任何认证。
|
|
93
|
+
* Read-only route for the "Selected Skills" detail hover: serves the packaged
|
|
94
|
+
* SKILL.md snapshot from disk instead of shipping ~750 KB in the client bundle.
|
|
95
|
+
* Request names only index a precomputed Map (never path fragments). Same auth
|
|
96
|
+
* fence as `/capital-charts` (`requestRejection`).
|
|
134
97
|
*/
|
|
135
98
|
export const SKILL_DOC_ROUTE = '/capital-skills'
|
|
136
|
-
/**
|
|
99
|
+
/** Byte cap per document: oversized files are rejected whole, never truncated. */
|
|
137
100
|
export const MAX_SKILL_DOC_BYTES = 128 * 1024
|
|
138
101
|
|
|
139
102
|
const SKILL_DOC_SUFFIX = 'SKILL.md'
|
|
140
103
|
|
|
141
|
-
/** catalog 里的顺序 = 卡片上的顺序,所以这里只多算一份"名字 → 快照文件"的索引。 */
|
|
142
104
|
export const SKILL_DOCS = new Map(SELECTED_SKILL_CATALOG.map((entry) => [
|
|
143
105
|
entry.name,
|
|
144
106
|
new URL(`../selected-skills/${entry.repository}/${SELECTED_SKILL_SKILL_DIRS[entry.repository]}/${entry.name}/${SKILL_DOC_SUFFIX}`, import.meta.url),
|
|
@@ -159,10 +121,7 @@ function sendJson(res, status, payload) {
|
|
|
159
121
|
res.end(JSON.stringify(payload))
|
|
160
122
|
}
|
|
161
123
|
|
|
162
|
-
/**
|
|
163
|
-
* @param options.readFile - `(url) => string | Buffer`,用例注入假读盘。
|
|
164
|
-
* @param options.authorize - `(req) => 401 | 403 | undefined`;非空即拒,**且一次都不碰文件系统**。
|
|
165
|
-
*/
|
|
124
|
+
/** `options.authorize` = `(req) => 401 | 403 | undefined`; non-empty rejects without touching fs. */
|
|
166
125
|
export function createSkillDocHandler(options = {}) {
|
|
167
126
|
const readFile = options.readFile ?? ((target) => fsModule().readFileSync(target, 'utf8'))
|
|
168
127
|
const statSize = options.statSize ?? ((target) => fsModule().statSync(target).size)
|
|
@@ -173,7 +132,7 @@ export function createSkillDocHandler(options = {}) {
|
|
|
173
132
|
try {
|
|
174
133
|
rejection = authorize(req)
|
|
175
134
|
} catch {
|
|
176
|
-
//
|
|
135
|
+
// Fail closed: an authorizer that cannot answer must not mean "no auth".
|
|
177
136
|
rejection = 401
|
|
178
137
|
}
|
|
179
138
|
if (rejection !== undefined && rejection !== null) {
|
|
@@ -220,7 +179,7 @@ export function createSkillDocHandler(options = {}) {
|
|
|
220
179
|
try {
|
|
221
180
|
bytes = statSize(target)
|
|
222
181
|
} catch {
|
|
223
|
-
//
|
|
182
|
+
// Snapshot missing from the package: say unreadable, never an empty document.
|
|
224
183
|
sendJson(res, 404, { error: 'skill_doc_unreadable', name })
|
|
225
184
|
return
|
|
226
185
|
}
|
|
@@ -243,23 +202,14 @@ export function createSkillDocHandler(options = {}) {
|
|
|
243
202
|
}
|
|
244
203
|
}
|
|
245
204
|
|
|
246
|
-
/**
|
|
247
|
-
* 卡片那侧拼 URL 的唯一口径(文件名里带 `.` 的 slug 也要能拼,所以走 encodeURIComponent)。
|
|
248
|
-
* 客户端 bundle 与这里是**同一条字符串**的两端:漂移的表现为"详情永远读不到",由
|
|
249
|
-
* `test/capital-config.test.mjs` 的同字断言钉住(客户端不能 import 本模块 ⇒ 只能对着产物量)。
|
|
250
|
-
*/
|
|
205
|
+
/** URL built identically in the client bundle; drift = details never load (test pins both). */
|
|
251
206
|
export function skillDocUrl(name) {
|
|
252
207
|
return `${SKILL_DOC_ROUTE}/${encodeURIComponent(name)}.md`
|
|
253
208
|
}
|
|
254
209
|
|
|
255
210
|
/**
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* `webServer` 只走 `inject`——`ctx.get('webServer')` 解析得到值**不代表**属性访问合法
|
|
259
|
-
* (cordis 的属性代理只认"本层声明过 inject"),探测式写法会把正确性押在装配顺序上,
|
|
260
|
-
* 这条踩过两次(见 `chart-ui/index.js` 的同一段注释)。认证面 `connection` 反之要**惰性**
|
|
261
|
-
* 解析:它缺席(Electron / file:// 这类没有浏览器认证面的载体)就退化成"没有认证面"的旧行为,
|
|
262
|
-
* 但解析抛错一律 401。
|
|
211
|
+
* `webServer` only via inject (probing `ctx.webServer` bets on mount order);
|
|
212
|
+
* `connection` resolved lazily: absent = no auth plane, throwing = 401.
|
|
263
213
|
*/
|
|
264
214
|
export function apply(ctx) {
|
|
265
215
|
const authorize = (req) => {
|
package/config/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v587d/config",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.9.1",
|
|
4
|
+
"description": "自动模式的取数凭据与偏好配置行:卡片坐在 @v587d/capital-generation 插件主页的「配置」段,点这一行本身没有表单。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
package/cordis.patch.yml
CHANGED
|
@@ -1,42 +1,29 @@
|
|
|
1
1
|
# Capital Generation is a distributable DSH profile bundle.
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# 的目录式注册。0.1.7 把该条目改名为 `agent-preset-registry` 并**删掉了 roots 机制**(registry
|
|
10
|
-
# 「既不扫目录也不接受预设路径」),于是每次启动静默报
|
|
11
|
-
# `dsh: [@v587d/capital-generation] patch: entry "agent-presets" not found` —— 预设永不注册,
|
|
12
|
-
# 历史会话(头部记着 `"agentPreset":"capital-generation"`)全部 `Unknown agent preset` 读不回。
|
|
2
|
+
# This file mounts host-plane rows only (the inserts below). Presets are ordinary entry rows and
|
|
3
|
+
# live in preset/capital-generation/agent.patch.yml, applied in order by package.json's
|
|
4
|
+
# `dsh.bundle.patch`; we insert our own preset declaration and never touch the registry's config.
|
|
5
|
+
# ⛔ Do not register presets by patching an `agent-presets` row's `config.roots`: 0.1.7 renamed the
|
|
6
|
+
# entry to `agent-preset-registry` and deleted the roots mechanism. The failure is a single boot warn
|
|
7
|
+
# (`patch: entry "agent-presets" not found`), so presets never register and every saved session
|
|
8
|
+
# reopening with `"agentPreset":"capital-generation"` reports `Unknown agent preset`.
|
|
13
9
|
|
|
14
|
-
# ──
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
10
|
+
# ── Chart frontend (host plane) ────────────────────────────────────────────────
|
|
11
|
+
# Host plane because client bundles are injected at index.html render time; a bundle mounted on a
|
|
12
|
+
# preset row never reaches an already-open page (see chart-ui/README.md).
|
|
13
|
+
# `name` must be a literal, path-like relative path — three pitfalls, each hit for real:
|
|
14
|
+
# 1. only `config` is interpolated, so `!!js` in `name` reaches the ESM import verbatim
|
|
15
|
+
# (`name.startsWith is not a function`, whole profile fails to boot).
|
|
16
|
+
# 2. a subpath specifier (`@v587d/capital-generation/chart-ui`) is neither path-like nor a
|
|
17
|
+
# resolvable package name, so locatePkgJson silently classifies the row as "not a client
|
|
18
|
+
# bundle": the bundle never enters __DSH_BOOT__, the UI is missing, nothing is logged.
|
|
19
|
+
# 3. the resolution base is this file's directory, not the profile root, so `./chart-ui/index.js`
|
|
20
|
+
# works under any node_modules layout; package identity comes from chart-ui/package.json
|
|
21
|
+
# (@v587d/capital-charts), which is why it cannot collide with the main package.
|
|
22
|
+
# Regression: test/chart-composition.test.mjs (name stays a literal path-like string and resolves).
|
|
19
23
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# 写过 !!js 的后果是启动期 `name.startsWith is not a function`——整个 profile 起不来。
|
|
24
|
-
# 2. **`name` 也不能是 subpath 说明符**(如 `@v587d/capital-generation/chart-ui`):
|
|
25
|
-
# dsh-client-modules 的 locatePkgJson 里
|
|
26
|
-
# `if (!pathLike && exactPackageSpecifier(loaderName) === undefined) return undefined`
|
|
27
|
-
# —— subpath 既非 path-like、打包名解析又返回 undefined,于是这颗行被**静默**当成
|
|
28
|
-
# "不是客户端包",bundle 永不进 __DSH_BOOT__(表现是图表 UI 不出现,且不报错)。
|
|
29
|
-
# 3. 解析基址是**本 patch 文件所在目录**,不是 profile 根:
|
|
30
|
-
# cordis-plugin-include 装载 patch 时会 `ctx.baseUrl = new URL('.', pathToFileURL(filename))`。
|
|
31
|
-
# 所以这里的相对路径就是本包目录下的 chart-ui/index.js —— 与 profile 的 node_modules
|
|
32
|
-
# 布局无关(pnpm 的 isolated 布局也照常工作)。
|
|
33
|
-
# locatePkgJson 随后按"最近的 package.json"归属,正好是 chart-ui/package.json,
|
|
34
|
-
# 包身份为 @v587d/capital-charts,与主包不撞名。
|
|
35
|
-
# 回归断言见 test/chart-composition.test.mjs(name 必须是字面量且 path-like,并真的解析一次)。
|
|
36
|
-
#
|
|
37
|
-
# ⚠ 目录名 / 包名与**行 id 是两条独立的链**(AGENTS.md §5.1):下面两颗行住在 `config/` 与
|
|
38
|
-
# `watchlist/`,行 id 却仍是 `capital-config` / `capital-watchlist`——后者是 settings 命名空间与
|
|
39
|
-
# 「包含的组件」里那两行的身份,改了就会吃掉用户已保存的配置。不许"顺手对齐"。
|
|
24
|
+
# ⚠ Directory name and row id are two independent chains (AGENTS.md §5.1): the rows below live in
|
|
25
|
+
# `config/` and `watchlist/`, yet keep ids `capital-config` / `capital-watchlist` — those ids are
|
|
26
|
+
# the settings namespace and the identity of the user's saved config; "aligning" them discards it.
|
|
40
27
|
- insert:
|
|
41
28
|
- id: capital-config
|
|
42
29
|
name: './config/index.js'
|
|
@@ -55,22 +42,30 @@
|
|
|
55
42
|
- id: capital-charts
|
|
56
43
|
name: './chart-ui/index.js'
|
|
57
44
|
|
|
58
|
-
# ──
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# `../lib/sources/fuyao-core.js`(零 import 的叶子内核)——**同一份** key 解析 / 信封判定 /
|
|
67
|
-
# 错误映射,不许有第二份(AGENTS.md §9.7)。这条跨包相对导入在本仓没有先例,因此由
|
|
68
|
-
# `test/watchlist-quote.test.mjs` 的**模块同一性探针**钉住:两处 import 必须拿到同一实例,
|
|
69
|
-
# 探针不过就重新设计,而不是先上线再说。
|
|
70
|
-
#
|
|
71
|
-
# `id` 与 `./watchlist/index.js` 的 path-like 字面量规则同上(!!js / subpath 两个坑
|
|
72
|
-
# 对这颗行同样成立)。
|
|
45
|
+
# ── User watchlist (host plane) ───────────────────────────────────────────────
|
|
46
|
+
# Cross-workspace user asset (~/.dsh/storages/capital_watchlist.json), so host plane for the same
|
|
47
|
+
# injection reason as chart-ui; the `name` rules above apply here too.
|
|
48
|
+
# Direction is the opposite of chart-ui's: this row reaches the network itself, so it imports
|
|
49
|
+
# ../lib/sources/fuyao-core.js (a zero-import leaf) rather than a second copy of key resolution,
|
|
50
|
+
# envelope checks and error mapping (AGENTS.md §9.7). No precedent for that cross-package relative
|
|
51
|
+
# import here, so test/watchlist-quote.test.mjs pins module identity: both import sites must get
|
|
52
|
+
# the same instance, and if the probe fails the design is wrong — don't ship it and hope.
|
|
73
53
|
- insert:
|
|
74
54
|
- id: capital-watchlist
|
|
75
55
|
name: './watchlist/index.js'
|
|
76
56
|
|
|
57
|
+
# ── Cross-preset runtime / policy (0.2.1) ─────────────────────────────────────
|
|
58
|
+
# Root and role presets mount separately, so apply() runs several times per process. Shared state
|
|
59
|
+
# (chart source tokens, chart artifacts) and the agent/created listeners (root convergence, shell
|
|
60
|
+
# and fs guards) must exist exactly once, which only a host-plane provide gives.
|
|
61
|
+
# These two rows use package subpath entries (@v587d/capital-generation/runtime and /policy): they
|
|
62
|
+
# are server-side, so the literal-path rule above doesn't apply — DSH composes its own
|
|
63
|
+
# @deepseek-ai/dsh-tool-subagent-control rows the same way.
|
|
64
|
+
- insert:
|
|
65
|
+
- id: capital-runtime
|
|
66
|
+
name: '@v587d/capital-generation/runtime'
|
|
67
|
+
|
|
68
|
+
- insert:
|
|
69
|
+
- id: capital-policy
|
|
70
|
+
name: '@v587d/capital-generation/policy'
|
|
71
|
+
|
|
@@ -1,60 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* 一律 EROFS),**不拦读**(`~/.dsh/.credentials.yaml`、`sessions/**` 整机可读),
|
|
7
|
-
* **不拦网络**(`curl` 直连 200)。这是 DSH 沙箱词汇表的既定边界——`SandboxMode`
|
|
8
|
-
* 只覆盖文件效果,官方文档亦如此声明。
|
|
9
|
-
* - 因此本文件**不做命令内容匹配**:正则挡不住 `node -e`/`python3 -c`,而 node/python
|
|
10
|
-
* 正是引入 bash 的目的(未来 data_analyst 也靠它)。禁区与出网纪律走 data_junior 的
|
|
11
|
-
* persona 软约束,不在这里伪装成安全边界。
|
|
12
|
-
* - 沙箱写边界也无法单独收窄:数据集管线自己就走沙箱,`store.writeContext()` 用**调用方
|
|
13
|
-
* session** 的 policy 且要求 mode ∈ {workspace-write, danger-full-access},把 data_junior
|
|
14
|
-
* 的会话钉成 read-only 会直接打断 `describe_dataset` 的 profile 落盘
|
|
15
|
-
* (`workspace_not_writable`)。bash 与 Dataset 管线共用同一把尺子(session policy)。
|
|
16
|
-
*
|
|
17
|
-
* 规则只留一层(2026-10-15 用户决策「开 read、同时保留 bash」后):
|
|
18
|
-
* A 结构层(**已随决策移除**):曾拒绝根会话的 shell 调用("只对被委派子会话开放")。
|
|
19
|
-
* 现在主 Agent 保留 bash / pwsh(读 skill 分册、纯计算);shell 归谁由可见性收敛——
|
|
20
|
-
* `ROOT_AGENT_DENIED_TOOLS` 不再含 shell 名,子会话侧由委派行的 `toolFilter` 把 shell
|
|
21
|
-
* 只给 data_junior。迁移的断言在 `test/bash-guard.test.mjs`(根会话放行是**新契约**)。
|
|
22
|
-
* B UX 层(保留,收窄到"被委派 / 身份不可考的调用方"):委派子会话的审批策略由框架固定为
|
|
23
|
-
* `never`(dsh-subagent 的 `captureDelegatedPolicyOverrides` → `approval/policy: never`,
|
|
24
|
-
* `dsh-user-approval.decide()` 在 never 时**直接返回 rejected、根本不调用 answerer**),
|
|
25
|
-
* 所以 `sandbox_permissions` 必然失败,而且模型会收到「the user rejected…」这种
|
|
26
|
-
* **把系统拒绝说成用户拒绝**的文案。这里提前拦下并给出正确路径:省一次必然白跑的往返 +
|
|
27
|
-
* 纠正归因。主 Agent 自己的升级照常走 ask 审批(那正是 escalation 的正当用途)。
|
|
28
|
-
*
|
|
29
|
-
* 为什么用 `tools.guard` 而不是 `tools/pre-execute`:guard 是**单调**的(没有 allow 结果,
|
|
30
|
-
* 任何监听顺序都无法把别人的 deny 翻回 allow),同步、拿到解析后的 `arguments`,且
|
|
31
|
-
* 「注册在 agent.ctx 上就只作用于该 agent」。pre-execute 是 waterfall,可扩展也意味着可被后续
|
|
32
|
-
* 监听改写。
|
|
33
|
-
*/
|
|
1
|
+
/** Shell guard for bash/pwsh: blocks only doomed sandbox escalation; command content is never
|
|
2
|
+
* inspected (the sandbox blocks writes, not reads/network). Delegated approvals are fixed to
|
|
3
|
+
* `never`, so escalation auto-rejects with a misleading "user rejected"; block early instead.
|
|
4
|
+
* Root escalation still uses ask; do not pin delegated sessions read-only (`store.writeContext`
|
|
5
|
+
* needs workspace-write). `tools.guard` is monotonic; pre-execute is a rewritable waterfall. */
|
|
34
6
|
import { type GuardedExecution, type PolicyContext } from './root-tool-policy.js';
|
|
35
|
-
/** 闸门看到的调用形状:guard 拿到的是解析后的 arguments,不是一个 shell 字符串。 */
|
|
36
7
|
export type BashGuardExecution = GuardedExecution;
|
|
37
|
-
/**
|
|
38
|
-
* 沙箱升级拒绝文案:说清"这不是用户拒绝",并给出正确路径。
|
|
39
|
-
* (A 结构层文案 `SHELL_DELEGATED_ONLY` 随 2026-10-15 决策删除,断言迁移到
|
|
40
|
-
* `test/bash-guard.test.mjs` 的"根会话放行"用例。)
|
|
41
|
-
*/
|
|
8
|
+
/** States that the rejection is the system's, not the user's, and gives the recovery path. */
|
|
42
9
|
export declare const SHELL_ESCALATION_UNREACHABLE: string;
|
|
43
|
-
/**
|
|
44
|
-
* 单次 shell 调用的判定:返回拒绝理由,或 undefined 放行。
|
|
45
|
-
*
|
|
46
|
-
* 2026-10-15 起主 Agent 的 shell 调用一律放行(A 层随决策移除);被拦的只有**升级参数**
|
|
47
|
-
* ——注意这里**只读两个字段**(调用方身份、`sandbox_permissions`/`justification`),
|
|
48
|
-
* 绝不解析 `command`——理由见文件头。升级判不了身份时 fail closed:根会话的形状是
|
|
49
|
-
* "有 session 且无 parentSession",证明不了就当最坏打算,不给"拿不到身份就放行"的口子。
|
|
50
|
-
*/
|
|
10
|
+
/** Reads identity plus `sandbox_permissions`/`justification`, never `command`; unknown identity fails closed. */
|
|
51
11
|
export declare function bashGuardReason(exec: BashGuardExecution | undefined): string | undefined;
|
|
52
|
-
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* 监听位置与 `registerRootToolPolicy` 同因:`agent/created` 的路由键是 agent 对象本身,
|
|
56
|
-
* 注册在 standing scope(打了标签)的监听器**收不到且不报错**,必须挂 `ctx.root`。
|
|
57
|
-
* 安装/释放都不得抛回 agent 创建路径——宁可少一层闸门,也不能挡住建 agent;
|
|
58
|
-
* 静默失败会留下诊断 warn(见 `registerBashGuard` 的返回值处理)。
|
|
59
|
-
*/
|
|
12
|
+
/** Installs on every Capital preset agent. Must listen on `ctx.root`: agent/created routes by
|
|
13
|
+
* the agent object, so tagged standing-scope listeners never fire. Failures only warn. */
|
|
60
14
|
export declare function registerBashGuard(ctx: PolicyContext): void;
|
package/lib/agents/bash-guard.js
CHANGED
|
@@ -1,64 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* 只覆盖文件效果,官方文档亦如此声明。
|
|
9
|
-
* - 因此本文件**不做命令内容匹配**:正则挡不住 `node -e`/`python3 -c`,而 node/python
|
|
10
|
-
* 正是引入 bash 的目的(未来 data_analyst 也靠它)。禁区与出网纪律走 data_junior 的
|
|
11
|
-
* persona 软约束,不在这里伪装成安全边界。
|
|
12
|
-
* - 沙箱写边界也无法单独收窄:数据集管线自己就走沙箱,`store.writeContext()` 用**调用方
|
|
13
|
-
* session** 的 policy 且要求 mode ∈ {workspace-write, danger-full-access},把 data_junior
|
|
14
|
-
* 的会话钉成 read-only 会直接打断 `describe_dataset` 的 profile 落盘
|
|
15
|
-
* (`workspace_not_writable`)。bash 与 Dataset 管线共用同一把尺子(session policy)。
|
|
16
|
-
*
|
|
17
|
-
* 规则只留一层(2026-10-15 用户决策「开 read、同时保留 bash」后):
|
|
18
|
-
* A 结构层(**已随决策移除**):曾拒绝根会话的 shell 调用("只对被委派子会话开放")。
|
|
19
|
-
* 现在主 Agent 保留 bash / pwsh(读 skill 分册、纯计算);shell 归谁由可见性收敛——
|
|
20
|
-
* `ROOT_AGENT_DENIED_TOOLS` 不再含 shell 名,子会话侧由委派行的 `toolFilter` 把 shell
|
|
21
|
-
* 只给 data_junior。迁移的断言在 `test/bash-guard.test.mjs`(根会话放行是**新契约**)。
|
|
22
|
-
* B UX 层(保留,收窄到"被委派 / 身份不可考的调用方"):委派子会话的审批策略由框架固定为
|
|
23
|
-
* `never`(dsh-subagent 的 `captureDelegatedPolicyOverrides` → `approval/policy: never`,
|
|
24
|
-
* `dsh-user-approval.decide()` 在 never 时**直接返回 rejected、根本不调用 answerer**),
|
|
25
|
-
* 所以 `sandbox_permissions` 必然失败,而且模型会收到「the user rejected…」这种
|
|
26
|
-
* **把系统拒绝说成用户拒绝**的文案。这里提前拦下并给出正确路径:省一次必然白跑的往返 +
|
|
27
|
-
* 纠正归因。主 Agent 自己的升级照常走 ask 审批(那正是 escalation 的正当用途)。
|
|
28
|
-
*
|
|
29
|
-
* 为什么用 `tools.guard` 而不是 `tools/pre-execute`:guard 是**单调**的(没有 allow 结果,
|
|
30
|
-
* 任何监听顺序都无法把别人的 deny 翻回 allow),同步、拿到解析后的 `arguments`,且
|
|
31
|
-
* 「注册在 agent.ctx 上就只作用于该 agent」。pre-execute 是 waterfall,可扩展也意味着可被后续
|
|
32
|
-
* 监听改写。
|
|
33
|
-
*/
|
|
34
|
-
import { agentSessionId, isCapitalAgent, SHELL_TOOL_NAMES } from './root-tool-policy.js';
|
|
35
|
-
/**
|
|
36
|
-
* shell 工具名全集(`bash` / `pwsh`,定义只在 {@link SHELL_TOOL_NAMES} 一份)。
|
|
37
|
-
*
|
|
38
|
-
* ⛔ 这里不能按 `process.platform` 挑一个:preset 里那两行是平台成对挂载的,Windows 上
|
|
39
|
-
* 模型调的是 `pwsh`,而闸门跑在**同一个进程**里——只认 `bash` 的话,两层判定在 Windows 上
|
|
40
|
-
* 一次都不触发,且不报错(2026-09-30 实测发现,症状是"§1.6 那道闸门在 Windows 上是死代码")。
|
|
41
|
-
*/
|
|
1
|
+
/** Shell guard for bash/pwsh: blocks only doomed sandbox escalation; command content is never
|
|
2
|
+
* inspected (the sandbox blocks writes, not reads/network). Delegated approvals are fixed to
|
|
3
|
+
* `never`, so escalation auto-rejects with a misleading "user rejected"; block early instead.
|
|
4
|
+
* Root escalation still uses ask; do not pin delegated sessions read-only (`store.writeContext`
|
|
5
|
+
* needs workspace-write). `tools.guard` is monotonic; pre-execute is a rewritable waterfall. */
|
|
6
|
+
import { agentSessionId, isCapitalPresetAgent, SHELL_TOOL_NAMES } from './root-tool-policy.js';
|
|
7
|
+
/** Both names on every platform: the preset mounts a pair, so matching only the local platform kills the Windows gate. */
|
|
42
8
|
const shellToolNames = new Set(SHELL_TOOL_NAMES);
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* `test/bash-guard.test.mjs` 的"根会话放行"用例。)
|
|
47
|
-
*/
|
|
9
|
+
/** One listener set per root context: host policy and legacy call sites install once. */
|
|
10
|
+
const installedRoots = new WeakSet();
|
|
11
|
+
/** States that the rejection is the system's, not the user's, and gives the recovery path. */
|
|
48
12
|
export const SHELL_ESCALATION_UNREACHABLE = 'shell 工具(bash / pwsh)的沙箱升级只有主 Agent 的会话能拿到用户审批:被委派的子会话拿不到'
|
|
49
13
|
+ '(框架把委派会话的审批策略固定为 never,不会去问用户),当前调用方也不是可确认的主 Agent 会话。'
|
|
50
14
|
+ '不要重试升级:缺数值用 describe_dataset / query_dataset,缺数据用 send_message 发 data_gap 回主 Agent。';
|
|
51
15
|
function isRecord(value) {
|
|
52
16
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
53
17
|
}
|
|
54
|
-
/**
|
|
55
|
-
* 单次 shell 调用的判定:返回拒绝理由,或 undefined 放行。
|
|
56
|
-
*
|
|
57
|
-
* 2026-10-15 起主 Agent 的 shell 调用一律放行(A 层随决策移除);被拦的只有**升级参数**
|
|
58
|
-
* ——注意这里**只读两个字段**(调用方身份、`sandbox_permissions`/`justification`),
|
|
59
|
-
* 绝不解析 `command`——理由见文件头。升级判不了身份时 fail closed:根会话的形状是
|
|
60
|
-
* "有 session 且无 parentSession",证明不了就当最坏打算,不给"拿不到身份就放行"的口子。
|
|
61
|
-
*/
|
|
18
|
+
/** Reads identity plus `sandbox_permissions`/`justification`, never `command`; unknown identity fails closed. */
|
|
62
19
|
export function bashGuardReason(exec) {
|
|
63
20
|
if (exec === undefined || typeof exec.name !== 'string' || !shellToolNames.has(exec.name))
|
|
64
21
|
return undefined;
|
|
@@ -70,7 +27,6 @@ export function bashGuardReason(exec) {
|
|
|
70
27
|
const knownRoot = exec.agent?.session !== undefined && (typeof parent !== 'string' || parent.length === 0);
|
|
71
28
|
return knownRoot ? undefined : SHELL_ESCALATION_UNREACHABLE;
|
|
72
29
|
}
|
|
73
|
-
/** 在某个 agent 自己的 scope 上安装 guard,返回 disposer(不可用时返回 undefined)。 */
|
|
74
30
|
function installAgentGuard(agent) {
|
|
75
31
|
let tools;
|
|
76
32
|
try {
|
|
@@ -89,25 +45,22 @@ function installAgentGuard(agent) {
|
|
|
89
45
|
return undefined;
|
|
90
46
|
}
|
|
91
47
|
}
|
|
92
|
-
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* 监听位置与 `registerRootToolPolicy` 同因:`agent/created` 的路由键是 agent 对象本身,
|
|
96
|
-
* 注册在 standing scope(打了标签)的监听器**收不到且不报错**,必须挂 `ctx.root`。
|
|
97
|
-
* 安装/释放都不得抛回 agent 创建路径——宁可少一层闸门,也不能挡住建 agent;
|
|
98
|
-
* 静默失败会留下诊断 warn(见 `registerBashGuard` 的返回值处理)。
|
|
99
|
-
*/
|
|
48
|
+
/** Installs on every Capital preset agent. Must listen on `ctx.root`: agent/created routes by
|
|
49
|
+
* the agent object, so tagged standing-scope listeners never fire. Failures only warn. */
|
|
100
50
|
export function registerBashGuard(ctx) {
|
|
101
51
|
const listenCtx = ctx.root ?? ctx;
|
|
102
52
|
if (typeof listenCtx.on !== 'function')
|
|
103
53
|
return;
|
|
104
|
-
|
|
54
|
+
if (installedRoots.has(listenCtx))
|
|
55
|
+
return;
|
|
56
|
+
installedRoots.add(listenCtx);
|
|
57
|
+
/** Session id -> disposer; released on agent/disposed to avoid cross-instance leaks. */
|
|
105
58
|
const guards = new Map();
|
|
106
59
|
const install = () => {
|
|
107
60
|
const stopCreated = listenCtx.on?.('agent/created', (payload) => {
|
|
108
61
|
try {
|
|
109
62
|
const agent = payload?.agent;
|
|
110
|
-
if (!
|
|
63
|
+
if (!isCapitalPresetAgent(ctx, agent))
|
|
111
64
|
return;
|
|
112
65
|
const dispose = installAgentGuard(agent);
|
|
113
66
|
if (dispose === undefined) {
|
|
@@ -133,7 +86,7 @@ export function registerBashGuard(ctx) {
|
|
|
133
86
|
guards.delete(id);
|
|
134
87
|
dispose();
|
|
135
88
|
}
|
|
136
|
-
catch { /*
|
|
89
|
+
catch { /* disposal must not break agent teardown */ }
|
|
137
90
|
});
|
|
138
91
|
return () => {
|
|
139
92
|
try {
|
|
@@ -158,7 +111,14 @@ export function registerBashGuard(ctx) {
|
|
|
158
111
|
if (typeof ctx.effect === 'function') {
|
|
159
112
|
ctx.effect(() => {
|
|
160
113
|
const dispose = install();
|
|
161
|
-
return () =>
|
|
114
|
+
return () => {
|
|
115
|
+
try {
|
|
116
|
+
dispose();
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
installedRoots.delete(listenCtx);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
162
122
|
}, 'capital-generation.bash-guard()');
|
|
163
123
|
return;
|
|
164
124
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hide delegation-only role presets from the host's new-session roster.
|
|
3
|
+
*
|
|
4
|
+
* The registry has no hidden flag. `remoteExportList()` is what the remote `agentPresets/list`
|
|
5
|
+
* endpoint invokes, so wrapping it changes the selector without touching `list()`, `resolve()`,
|
|
6
|
+
* delegation or settings management. Fail open: unknown shapes and missing methods leave the
|
|
7
|
+
* roster untouched, and fiber cleanup restores the original property descriptor.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
type EffectContext = {
|
|
11
|
+
effect?: (execute: () => () => void, label?: string) => unknown;
|
|
12
|
+
};
|
|
13
|
+
/** Drop internal rows without mutating the caller's array; unknown shapes pass through. */
|
|
14
|
+
export declare function filterPresetRoster(value: unknown): unknown;
|
|
15
|
+
/**
|
|
16
|
+
* Install on one registry instance. Returns false when the host exposes no roster method or the
|
|
17
|
+
* property cannot be shadowed; callers then behave exactly as if this filter did not exist.
|
|
18
|
+
*/
|
|
19
|
+
export declare function installPresetRosterFilter(service: unknown, ctx: EffectContext): boolean;
|
|
20
|
+
/** Wait for the registry service, then filter its remote roster. */
|
|
21
|
+
export declare function registerPresetRosterFilter(ctx: Context): void;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isInternalRolePresetId } from '../preset-ids.js';
|
|
2
|
+
// A future host may serve `agentPresets/list` straight from `list`; prefer the dedicated export.
|
|
3
|
+
const METHODS = ['remoteExportList', 'list'];
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null;
|
|
6
|
+
}
|
|
7
|
+
function isInternalRow(row) {
|
|
8
|
+
return isRecord(row) && isInternalRolePresetId(row.id);
|
|
9
|
+
}
|
|
10
|
+
/** Drop internal rows without mutating the caller's array; unknown shapes pass through. */
|
|
11
|
+
export function filterPresetRoster(value) {
|
|
12
|
+
try {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.filter((row) => !isInternalRow(row));
|
|
15
|
+
if (!isRecord(value) || !Array.isArray(value.presets))
|
|
16
|
+
return value;
|
|
17
|
+
return { ...value, presets: value.presets.filter((row) => !isInternalRow(row)) };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function pickMethod(service) {
|
|
24
|
+
return METHODS.find((method) => typeof service[method] === 'function');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Install on one registry instance. Returns false when the host exposes no roster method or the
|
|
28
|
+
* property cannot be shadowed; callers then behave exactly as if this filter did not exist.
|
|
29
|
+
*/
|
|
30
|
+
export function installPresetRosterFilter(service, ctx) {
|
|
31
|
+
if (!isRecord(service))
|
|
32
|
+
return false;
|
|
33
|
+
const method = pickMethod(service);
|
|
34
|
+
if (method === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
const original = service[method];
|
|
37
|
+
if (typeof original !== 'function')
|
|
38
|
+
return false;
|
|
39
|
+
const previous = Object.getOwnPropertyDescriptor(service, method);
|
|
40
|
+
const wrapper = (...args) => {
|
|
41
|
+
const result = original.call(service, ...args);
|
|
42
|
+
try {
|
|
43
|
+
if (isRecord(result) && typeof result.then === 'function') {
|
|
44
|
+
return result.then((value) => filterPresetRoster(value));
|
|
45
|
+
}
|
|
46
|
+
return filterPresetRoster(result);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
Object.defineProperty(service, method, {
|
|
54
|
+
configurable: true,
|
|
55
|
+
enumerable: previous?.enumerable ?? false,
|
|
56
|
+
writable: true,
|
|
57
|
+
value: wrapper,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const restore = () => {
|
|
64
|
+
if (service[method] !== wrapper)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
if (previous === undefined)
|
|
68
|
+
delete service[method];
|
|
69
|
+
else
|
|
70
|
+
Object.defineProperty(service, method, previous);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
if (typeof ctx.effect === 'function')
|
|
76
|
+
ctx.effect(() => restore, 'capital-preset-roster');
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
/** Wait for the registry service, then filter its remote roster. */
|
|
80
|
+
export function registerPresetRosterFilter(ctx) {
|
|
81
|
+
const host = ctx;
|
|
82
|
+
if (typeof host.inject !== 'function')
|
|
83
|
+
return;
|
|
84
|
+
host.inject(['agentPresets'], (injected) => {
|
|
85
|
+
installPresetRosterFilter(injected.agentPresets, injected);
|
|
86
|
+
});
|
|
87
|
+
}
|