@v587d/capital-generation 2.8.3 → 2.9.0
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 +54 -1
- package/README.md +40 -19
- package/chart-ui/README.md +1 -1
- 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 +3 -8
- package/lib/chart/artifact-ref.js +3 -8
- 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 +40 -189
- package/lib/chart/html.d.ts +2 -5
- package/lib/chart/html.js +8 -15
- 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 +6 -29
- package/lib/chart/tool.js +9 -23
- package/lib/chart-render-entry.d.ts +9 -0
- package/lib/chart-render-entry.js +17 -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 +40 -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/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 +67 -7
- package/preset/capital-generation/README.md +87 -53
- package/preset/capital-generation/agent.patch.yml +526 -337
- 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 +1 -1
- package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
- package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
- package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
- 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
|
@@ -1,195 +1,111 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* 为什么需要它:preset 行注册在 standing composition 层,而主 Agent 与子 Agent 的
|
|
5
|
-
* scope 都是 standing 的**子节点(兄弟)**。因此:
|
|
6
|
-
* - 在 preset 层用 `tools.restrict({deny})` 会把子 Agent 一起 deny
|
|
7
|
-
* (`dsh-tools` 的 `view()` 里 allow/deny 只是交集过滤,永远不能把祖先 deny 掉的名字加回来),
|
|
8
|
-
* 连 `visualization_specialist` 的 `toolFilter.allow: [render_chart]` 都会失效;
|
|
9
|
-
* - 只在**根 Agent 自己的 scope** 上 deny,才既拿掉主 Agent 的入口、又不影响子 Agent。
|
|
10
|
-
*
|
|
11
|
-
* ⚠️ 监听必须注册在**未打 scope 标签的 context**(`ctx.root`)上。
|
|
12
|
-
* 实测(2026-09-17,dsh 0.1.5-rc.1):`agent/created` 的路由键是 **agent 对象本身**
|
|
13
|
-
* (`dsh-agent` 的 `agentCarrier(agent) = scopeTarget(agent, agent)`),不是 agent 的 scope key。
|
|
14
|
-
* `dsh-scope` 的准入规则只放行"未打标签的监听器"或"键在 carrier key 祖先链上"的监听器,
|
|
15
|
-
* 所以注册在 standing scope 上的监听器**收不到**这个事件——表现是主 Agent 的工具表照旧有
|
|
16
|
-
* `render_chart`,而且没有任何报错(静默失效)。宿主平面行之所以能用 `ctx.on('agent/created')`,
|
|
17
|
-
* 正是因为它们未打标签。因此这里取 `ctx.root`,再用 `agentPresets.composedPreset(agent.ctx)`
|
|
18
|
-
* 筛出本 preset 的 agent。
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* 出网工具名(唯一一份事实来源):检索面 2 + wind 2 + 具名来源查询面 9。
|
|
22
|
-
*
|
|
23
|
-
* ⚠️ **不含 `ocr`**:主 Agent 保留它的本地形态(`file` / `doc_id`),只由
|
|
24
|
-
* {@link rootOcrGuardReason} 关掉 `url` 形态——名字级 deny 做不到这个区分。
|
|
25
|
-
*
|
|
26
|
-
* 主 Agent 侧「外部检索只经 web_retriever 回传」必须是**结构件**,不能只写在 persona 里:
|
|
27
|
-
* 2026-09-24 review 复现——persona 禁直连清单当时写的是 `web_retriever_*` 通配,工具改名后
|
|
28
|
-
* 通配一个都不匹配,禁令名存实亡,而工具表里那 13 个入口始终摆着。逐名 deny 由本清单决定,
|
|
29
|
-
* preset 与测试都从这里取名字(`test/persona.test.mjs` 交叉核验),新增来源只改这一处。
|
|
2
|
+
* Root Agent tool convergence on the root's own scope: preset-level deny would also hit
|
|
3
|
+
* children. Listeners must live on `ctx.root`; tagged standing-scope listeners miss agent/created.
|
|
30
4
|
*/
|
|
5
|
+
import { CAPITAL_PRESET_IDS, ROOT_PRESET_ID } from '../preset-ids.js';
|
|
6
|
+
import { readPresetClaim } from '../preset-claim.js';
|
|
7
|
+
/** Outbound tools (search 2 + wind 2 + named sources 9); `ocr` is excluded because only its url form is restricted. */
|
|
31
8
|
export const RETRIEVAL_DENIED_TOOLS = [
|
|
32
9
|
'anysearch_search', 'web_retriever_fetch',
|
|
33
10
|
'wind_docs_announcements', 'wind_docs_news',
|
|
34
11
|
'cls_telegraph', 'wscn_lives', 'cninfo_irm', 'sseinfo_qa',
|
|
35
12
|
'eastmoney_724', 'eastmoney_stock_news', 'eastmoney_reports', 'sina_reports', 'ths_eps_forecast',
|
|
36
13
|
];
|
|
37
|
-
/** 文档解析工具名(`web_retriever` 的第三个工作面)。 */
|
|
38
14
|
export const OCR_TOOL_NAME = 'ocr';
|
|
39
|
-
/**
|
|
40
|
-
* 根 Agent 的 `ocr` 只放行**本地形态**(`file` / `doc_id`),`url` 形态拒绝。
|
|
41
|
-
*
|
|
42
|
-
* 为什么不整名 deny:用户把 PDF 放在工作目录用 `@xxx.pdf` 推给主 Agent 是正当入口,
|
|
43
|
-
* 解析它不需要经过委派;但 `url` 形态等于"主 Agent 自己决定去戳一个外部链接",
|
|
44
|
-
* 那是「外部检索只经 web_retriever」这条结构约束唯一被留的口子。名字级 deny 做不到
|
|
45
|
-
* 这个区分(`render_chart` 的教训一样:可见性不是权限),所以用调用级 guard。
|
|
46
|
-
*/
|
|
15
|
+
/** Name-level deny cannot express the form distinction; this is a call-level guard. */
|
|
47
16
|
export const OCR_ROOT_URL_DENIED = 'ocr 的 url 形态只对被委派的 web_retriever 开放:外部 PDF 链接请委派 subagent_web_retriever 解析。'
|
|
48
17
|
+ '工作目录里的本地文档(file)与已解析产物(doc_id + pages/query)可以直接调用。';
|
|
49
|
-
/** guard 的调用形状里 `arguments` 是解析后的对象(不是 JSON 字符串)。 */
|
|
50
18
|
function isRecord(value) {
|
|
51
19
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
52
20
|
}
|
|
53
|
-
|
|
54
|
-
export function rootOcrGuardReason(exec) {
|
|
21
|
+
export function ocrUrlFormRequested(exec) {
|
|
55
22
|
if (exec?.name !== OCR_TOOL_NAME)
|
|
56
|
-
return
|
|
23
|
+
return false;
|
|
57
24
|
const args = isRecord(exec.arguments) ? exec.arguments : undefined;
|
|
58
|
-
|
|
25
|
+
return args !== undefined && typeof args.url === 'string' && args.url.length > 0;
|
|
26
|
+
}
|
|
27
|
+
/** Read per call because a created-time gate misses manually opened modes; fail open unless a confirmed Capital root. */
|
|
28
|
+
export function ocrFormGuardReasonFor(ctx) {
|
|
29
|
+
return (exec) => {
|
|
30
|
+
if (!ocrUrlFormRequested(exec))
|
|
31
|
+
return undefined;
|
|
32
|
+
const claim = readPresetClaim(ctx, exec?.agent);
|
|
33
|
+
// Global guard sees every process call; only a confirmed Capital root is blocked.
|
|
34
|
+
if (claim.registryMissing || claim.failed || claim.claim !== CAPITAL_PRESET_ID)
|
|
35
|
+
return undefined;
|
|
59
36
|
return OCR_ROOT_URL_DENIED;
|
|
60
|
-
|
|
37
|
+
};
|
|
61
38
|
}
|
|
62
|
-
/**
|
|
63
|
-
* shell 工具名:preset 里这两行是**平台成对**挂载的(`agent.patch.yml` 的 `tool-bash` /
|
|
64
|
-
* `tool-pwsh`,靠 `process.platform === 'win32'` 互斥 `disabled`),所以名字由平台决定。
|
|
65
|
-
*
|
|
66
|
-
* ⛔ 但**闸门必须认全集**:只认本平台那一个,另一端就是静默放行——同一个能力两个入口,
|
|
67
|
-
* 本地测试只覆盖了接好的那个(AGENTS.md §9.7 的第三种形状)。`bash-guard.ts` 的升级闸门
|
|
68
|
-
* 从这里取名字,不再各写一份。
|
|
69
|
-
*
|
|
70
|
-
* 2026-10-15 用户决策「开 read、同时保留 bash」:shell **不再**进 {@link ROOT_AGENT_DENIED_TOOLS}
|
|
71
|
-
* ——主 Agent 保留 bash / pwsh(读 skill 分册、纯计算);子会话侧仍由委派行的 `toolFilter`
|
|
72
|
-
* 与 `bash-guard.ts` 的升级闸门收敛。
|
|
73
|
-
*/
|
|
39
|
+
/** Mounted as a platform pair; recognize both everywhere or the other platform's gate is dead. */
|
|
74
40
|
export const SHELL_TOOL_NAMES = ['bash', 'pwsh'];
|
|
75
41
|
export const ROOT_AGENT_DENIED_TOOLS = [
|
|
76
|
-
/**
|
|
42
|
+
/** Charts only flow through junior -> visualization_specialist. */
|
|
77
43
|
'render_chart',
|
|
78
|
-
/** 孙 Agent 的创建工具只属于 data_junior:主 Agent 拿到它只会绕过 gate。 */
|
|
79
|
-
'subagent_visualization_specialist',
|
|
80
|
-
/** 只签发给 delegated data agent(运行时本就拒绝根会话),留在表里只会诱导误调。 */
|
|
81
44
|
'prepare_chart_source',
|
|
82
|
-
/**
|
|
83
|
-
* 十三个出网工具全部收敛:外部检索只有一个入口(web_retriever 子 Agent),它的兄弟行
|
|
84
|
-
* (data_collector / data_junior / visualization_specialist)的 allow 里本来就没有这些名字,
|
|
85
|
-
* 因此根侧 deny 不影响它们,只拿掉主 Agent 自己的直连能力。
|
|
86
|
-
* 唯一的例外是 `ocr`:它同时服务"解析外部 PDF 链接"与"解析用户放在工作目录的本地文档",
|
|
87
|
-
* 后者是主 Agent 的正当入口,所以不整名 deny,改由 {@link rootOcrGuardReason} 关掉 `url` 形态。
|
|
88
|
-
*
|
|
89
|
-
* 宿主自己挂的 `web_search` / `web_fetch` **已于 2026-09-30 从这份名单清掉**:双端实测
|
|
90
|
-
* (Linux 根会话 22 颗、桌面端 40 颗的工具表)都没有这两个名字,`restrict()` 对它们只会
|
|
91
|
-
* 抛错被跳过。留着不伤人,但代价是**每次现场记录都固定多两条 `failed`**,把"真有名字
|
|
92
|
-
* deny 不动"这种信号埋掉。它们仍由 persona 与 `capital-web-protocol` 的文案约束;
|
|
93
|
-
* **宿主若哪天把它们挂进根作用域,要重新加回这份名单**(`test/root-tool-policy.test.mjs`
|
|
94
|
-
* 钉的是"当前不在了",不是"永远不该在")。
|
|
95
|
-
*/
|
|
96
45
|
...RETRIEVAL_DENIED_TOOLS,
|
|
97
|
-
/**
|
|
98
|
-
* 文件写口收敛:preset 挂 `tool-fs` 一颗(read / write / edit),主 Agent 只留 `read`
|
|
99
|
-
* (2026-10-15 用户决策「开 read、同时保留 bash」)。`write` / `edit` 这里逐名 deny 是
|
|
100
|
-
* **视图层**(restrict 落对层的形状里主 Agent 根本看不见写口);由于 restrict 落层依赖
|
|
101
|
-
* 宿主形状(桌面端不写进链路层,见 `docs/dev/bash-gate.md`),调用层还有
|
|
102
|
-
* {@link fsWriteGuardReasonFor} 兜底——两层拦同一件事:业务落盘只走工具实现
|
|
103
|
-
* (`store.writeContext` 等),模型不直接写文件。
|
|
104
|
-
*
|
|
105
|
-
* `read_image` 刻意不点名:它只读、且条件注册(依赖宿主 `attachments` 服务),照
|
|
106
|
-
* `web_search` / `web_fetch` 的教训不该在名单里养 `failed` 记录。`tool-fs-search`
|
|
107
|
-
* (glob / grep)本轮不挂:需求只是"读得动",发现面等真需求再开。
|
|
108
|
-
*/
|
|
46
|
+
/** Not a boundary (the host global layer registers them); the call-level fs guard is the real block. */
|
|
109
47
|
'write',
|
|
110
48
|
'edit',
|
|
111
49
|
];
|
|
112
|
-
|
|
113
|
-
export const CAPITAL_PRESET_ID = 'capital-generation';
|
|
114
|
-
/** 根 Agent 判据:没有 parentSession。子 Agent 一律有(spawn provider 写入)。 */
|
|
50
|
+
export const CAPITAL_PRESET_ID = ROOT_PRESET_ID;
|
|
115
51
|
export function isRootAgent(agent) {
|
|
116
52
|
if (agent?.session === undefined)
|
|
117
53
|
return false;
|
|
118
54
|
const parent = agent.session.header?.parentSession;
|
|
119
55
|
return typeof parent !== 'string' || parent.length === 0;
|
|
120
56
|
}
|
|
121
|
-
/** 会话 id(guard 归档用);缺失或空串返回 undefined。 */
|
|
122
57
|
export function agentSessionId(agent) {
|
|
123
58
|
const id = agent?.session?.id;
|
|
124
59
|
return typeof id === 'string' && id.length > 0 ? id : undefined;
|
|
125
60
|
}
|
|
126
|
-
/** registry 认领结果:字符串 = `composedPreset` 的原样返回值,`'no-registry'` = registry 或方法不在。 */
|
|
127
|
-
const NO_REGISTRY = 'no-registry';
|
|
128
|
-
/**
|
|
129
|
-
* 读 `agentPresets` registry 对某个 agent 的认领结果——**registry 读取只此一份**
|
|
130
|
-
* (两个判据的差异只在"读不到时认领还是放行",见各自调用点)。
|
|
131
|
-
* `composedPreset` 抛错按"不认领"返回 undefined(不能吞进 `'no-registry'`:那会改变缺省方向)。
|
|
132
|
-
*/
|
|
133
|
-
function presetClaimOf(ctx, agent) {
|
|
134
|
-
let presets;
|
|
135
|
-
try {
|
|
136
|
-
presets = ctx.get?.('agentPresets');
|
|
137
|
-
}
|
|
138
|
-
catch {
|
|
139
|
-
return NO_REGISTRY;
|
|
140
|
-
}
|
|
141
|
-
if (presets === undefined || typeof presets.composedPreset !== 'function')
|
|
142
|
-
return NO_REGISTRY;
|
|
143
|
-
try {
|
|
144
|
-
return presets.composedPreset(agent?.ctx);
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
/** 只有本 preset 的 agent 才收敛:别的 preset 的 agent 不该被我们碰。 */
|
|
151
61
|
export function isCapitalAgent(ctx, agent) {
|
|
152
|
-
const claim =
|
|
153
|
-
//
|
|
154
|
-
if (claim
|
|
62
|
+
const claim = readPresetClaim(ctx, agent);
|
|
63
|
+
// Registry missing: conservative claim, because per-name restrict rejects unknown names harmlessly.
|
|
64
|
+
if (claim.registryMissing)
|
|
65
|
+
return true;
|
|
66
|
+
return claim.claim === CAPITAL_PRESET_ID;
|
|
67
|
+
}
|
|
68
|
+
/** Allows any Capital preset; guards for role children must use this, not the root id. */
|
|
69
|
+
export function isCapitalPresetAgent(ctx, agent) {
|
|
70
|
+
const claim = readPresetClaim(ctx, agent);
|
|
71
|
+
if (claim.registryMissing)
|
|
155
72
|
return true;
|
|
156
|
-
return claim
|
|
73
|
+
return claim.claim !== undefined && CAPITAL_PRESET_IDS.includes(claim.claim);
|
|
157
74
|
}
|
|
158
75
|
const probes = [];
|
|
159
76
|
const MAX_PROBES = 8;
|
|
160
|
-
|
|
77
|
+
const installedPolicyRoots = new WeakSet();
|
|
78
|
+
const installedFsGuardRoots = new WeakSet();
|
|
79
|
+
const installedOcrGuardRoots = new WeakSet();
|
|
80
|
+
/** Shares the convergence channel so opened-mode and call-level failures line up. */
|
|
81
|
+
export function recordGuardDecision(ctx, exec, toolName) {
|
|
82
|
+
const agent = exec?.agent;
|
|
83
|
+
probes.push({
|
|
84
|
+
agentId: agentSessionId(agent),
|
|
85
|
+
root: isRootAgent(agent),
|
|
86
|
+
presetId: presetIdOf(ctx, agent),
|
|
87
|
+
outcome: 'guard-denied',
|
|
88
|
+
denied: [toolName],
|
|
89
|
+
failed: [],
|
|
90
|
+
});
|
|
91
|
+
if (probes.length > MAX_PROBES)
|
|
92
|
+
probes.shift();
|
|
93
|
+
}
|
|
161
94
|
export function rootPolicyProbes() {
|
|
162
95
|
return probes.map((probe) => ({ ...probe, denied: [...probe.denied], failed: [...probe.failed] }));
|
|
163
96
|
}
|
|
164
|
-
/** 只给测试用:清空现场记录,避免用例之间互相污染。 */
|
|
165
97
|
export function resetRootPolicyProbes() {
|
|
166
98
|
probes.length = 0;
|
|
167
99
|
}
|
|
168
|
-
/** `composedPreset` 的原样答案(诊断口径,不参与判定;判定仍只有一份在 {@link isCapitalAgent})。 */
|
|
169
100
|
function presetIdOf(ctx, agent) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
presets = ctx.get?.('agentPresets');
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
101
|
+
const claim = readPresetClaim(ctx, agent);
|
|
102
|
+
if (claim.registryMissing)
|
|
175
103
|
return 'no-registry';
|
|
176
|
-
|
|
177
|
-
if (presets === undefined || typeof presets.composedPreset !== 'function')
|
|
178
|
-
return 'no-registry';
|
|
179
|
-
try {
|
|
180
|
-
return presets.composedPreset(agent?.ctx) ?? 'undefined';
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
104
|
+
if (claim.failed)
|
|
183
105
|
return 'error';
|
|
184
|
-
|
|
106
|
+
return claim.claim ?? 'undefined';
|
|
185
107
|
}
|
|
186
|
-
/**
|
|
187
|
-
* 只对根 Agent 生效地 deny {@link ROOT_AGENT_DENIED_TOOLS}。
|
|
188
|
-
*
|
|
189
|
-
* 逐名 restrict:`tools.restrict()` 对"本 scope 看不到的名字"会抛错(别的 preset、或行还没挂载),
|
|
190
|
-
* 逐名调用把这种"本来就不该有"的情况变成无害跳过,而不是整批失败。
|
|
191
|
-
* 任何异常都不抛出:调用方是 agent 创建路径,宁可少收敛也不能挡住建 agent。
|
|
192
|
-
*/
|
|
108
|
+
/** Per-name deny on the root only; name-not-visible throws become harmless skips. Never throws: called on agent creation. */
|
|
193
109
|
export function restrictRootAgentTools(agent, record) {
|
|
194
110
|
if (!isRootAgent(agent))
|
|
195
111
|
return 'skipped';
|
|
@@ -208,87 +124,111 @@ export function restrictRootAgentTools(agent, record) {
|
|
|
208
124
|
record?.denied.push(name);
|
|
209
125
|
}
|
|
210
126
|
catch {
|
|
211
|
-
// 这个 scope 本来就没有这个名字(别的 preset / 该行未挂载):跳过即可。
|
|
212
127
|
record?.failed.push(name);
|
|
213
128
|
}
|
|
214
129
|
}
|
|
215
130
|
return 'restricted';
|
|
216
131
|
}
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
132
|
+
/** Install globally once; created-time identity misses manually opened modes. */
|
|
133
|
+
export function registerRootOcrGuard(ctx) {
|
|
134
|
+
const ref = ctx.root ?? ctx;
|
|
135
|
+
if (installedOcrGuardRoots.has(ref))
|
|
136
|
+
return;
|
|
137
|
+
installedOcrGuardRoots.add(ref);
|
|
138
|
+
const reason = ocrFormGuardReasonFor(ctx);
|
|
139
|
+
const install = () => {
|
|
140
|
+
let tools;
|
|
141
|
+
try {
|
|
142
|
+
// `get` before property access: property access throws on fibers without inject.
|
|
143
|
+
tools = (ctx.get?.('tools') ?? ctx.tools);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
if (tools === undefined || typeof tools.guard !== 'function') {
|
|
149
|
+
ctx.logger?.warn('capital-generation: `ocr` 的 url 形态闸门没装上(guard 面不可用):主模式人设仍可自己抓外部 PDF');
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const dispose = tools.guard((execution) => {
|
|
154
|
+
const denied = reason(execution);
|
|
155
|
+
if (denied !== undefined)
|
|
156
|
+
recordGuardDecision(ctx, execution, OCR_TOOL_NAME);
|
|
157
|
+
return denied;
|
|
158
|
+
});
|
|
159
|
+
return typeof dispose === 'function' ? dispose : undefined;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
if (typeof ctx.effect === 'function') {
|
|
166
|
+
ctx.effect(() => {
|
|
167
|
+
const dispose = install() ?? (() => { });
|
|
168
|
+
return () => {
|
|
169
|
+
try {
|
|
170
|
+
dispose();
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
installedOcrGuardRoots.delete(ref);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}, 'capital-generation.ocr-form-guard()');
|
|
177
|
+
return;
|
|
241
178
|
}
|
|
179
|
+
install();
|
|
242
180
|
}
|
|
243
|
-
/** `tool-fs` 的写口工具名(唯一一份):`read` / `read_image` 是只读口,不在名单。 */
|
|
244
181
|
export const FS_WRITE_TOOL_NAMES = ['write', 'edit'];
|
|
245
182
|
const fsWriteNames = new Set(FS_WRITE_TOOL_NAMES);
|
|
246
|
-
/**
|
|
183
|
+
/** Points at the allowed paths (read, tool returns) instead of leaving the caller stuck. */
|
|
247
184
|
export const FS_WRITE_READ_ONLY = 'write / edit 不开放(主 Agent 与子会话都不直接写文件):读文件用 read;'
|
|
248
185
|
+ '数据与产物走工具回传(DatasetRef / chart_ref / send_message),需要落盘的由工具实现完成。'
|
|
249
186
|
+ '若你判断确需写文件,用 send_message 把 data_gap 与已有证据发回主 Agent 协调。';
|
|
250
|
-
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
* 为什么装全局而不是像 `ocr` 那样按 agent 装:`agent/created` 的落层时机随宿主形状漂移
|
|
254
|
-
* (桌面端 restrict 不写进链路层,见 `docs/dev/bash-gate.md` 的实测记录),而 guard 按
|
|
255
|
-
* `exec.agent` 取链路、与实例绑哪层无关——这正是那份文档里声明的备选方向,先在写口上落地。
|
|
256
|
-
*
|
|
257
|
-
* ⛔ 全局 guard 会吃到本进程**所有**调用,所以判据必须 fail-**open**:registry 读不到、
|
|
258
|
-
* `composedPreset` 抛错、拿不到 agent 的形状一律放行——宁可少拦一层(`ROOT_AGENT_DENIED_TOOLS`
|
|
259
|
-
* 还在视图层兜着),也绝不能把别的 preset(开发会话的 read / write / edit)吃掉。
|
|
260
|
-
* 拒绝只发生在"能确证调用方属于本 preset"时。
|
|
261
|
-
*/
|
|
187
|
+
/** Global call-level fs guard: the host global layer registers write/edit, so per-name restrict
|
|
188
|
+
* only shrinks children. Fail open unless a Capital preset is confirmed. */
|
|
262
189
|
export function fsWriteGuardReasonFor(ctx) {
|
|
263
190
|
return (execution) => {
|
|
264
191
|
const name = execution?.name;
|
|
265
192
|
if (typeof name !== 'string' || !fsWriteNames.has(name))
|
|
266
193
|
return undefined;
|
|
267
|
-
|
|
194
|
+
const claim = readPresetClaim(ctx, execution.agent);
|
|
195
|
+
// One wrong denial would remove another preset's write access for the whole process.
|
|
196
|
+
if (claim.registryMissing || claim.failed || claim.claim === undefined)
|
|
197
|
+
return undefined;
|
|
198
|
+
if (!CAPITAL_PRESET_IDS.includes(claim.claim))
|
|
268
199
|
return undefined;
|
|
269
200
|
return FS_WRITE_READ_ONLY;
|
|
270
201
|
};
|
|
271
202
|
}
|
|
272
|
-
/**
|
|
273
|
-
* 装 {@link fsWriteGuardReasonFor}。guard 不可用或安装异常都不外抛(调用方是装配路径);
|
|
274
|
-
* disposer 由 `ctx.effect` 持有,随本 fiber 释放。
|
|
275
|
-
*/
|
|
276
203
|
export function registerFsWriteGuard(ctx) {
|
|
204
|
+
const ref = ctx.root ?? ctx;
|
|
205
|
+
if (installedFsGuardRoots.has(ref))
|
|
206
|
+
return;
|
|
207
|
+
installedFsGuardRoots.add(ref);
|
|
208
|
+
const reason = fsWriteGuardReasonFor(ctx);
|
|
277
209
|
const install = () => {
|
|
278
210
|
let tools;
|
|
279
211
|
try {
|
|
280
|
-
const direct = ctx.tools;
|
|
281
212
|
const viaGet = typeof ctx.get === 'function' ? ctx.get('tools') : undefined;
|
|
282
|
-
const candidate =
|
|
213
|
+
const candidate = viaGet !== undefined ? viaGet : ctx.tools;
|
|
283
214
|
tools = candidate;
|
|
284
215
|
}
|
|
285
216
|
catch {
|
|
286
217
|
return undefined;
|
|
287
218
|
}
|
|
288
|
-
if (tools === undefined || typeof tools.guard !== 'function')
|
|
219
|
+
if (tools === undefined || typeof tools.guard !== 'function') {
|
|
220
|
+
ctx.logger?.warn('capital-generation: fs 写口闸门没装上(guard 面不可用):`write` / `edit` 只剩 `tool-fs` 那行的行级开关在拦');
|
|
289
221
|
return undefined;
|
|
222
|
+
}
|
|
290
223
|
try {
|
|
291
|
-
const dispose = tools.guard(
|
|
224
|
+
const dispose = tools.guard((execution) => {
|
|
225
|
+
const denied = reason(execution);
|
|
226
|
+
// Desktop persists no host logs; a guard block otherwise looks like "no write tool".
|
|
227
|
+
if (denied !== undefined && typeof execution?.name === 'string' && fsWriteNames.has(execution.name)) {
|
|
228
|
+
recordGuardDecision(ctx, execution, execution.name);
|
|
229
|
+
}
|
|
230
|
+
return denied;
|
|
231
|
+
});
|
|
292
232
|
return typeof dispose === 'function' ? dispose : undefined;
|
|
293
233
|
}
|
|
294
234
|
catch {
|
|
@@ -296,30 +236,34 @@ export function registerFsWriteGuard(ctx) {
|
|
|
296
236
|
}
|
|
297
237
|
};
|
|
298
238
|
if (typeof ctx.effect === 'function') {
|
|
299
|
-
ctx.effect(() =>
|
|
239
|
+
ctx.effect(() => {
|
|
240
|
+
const dispose = install() ?? (() => { });
|
|
241
|
+
return () => {
|
|
242
|
+
try {
|
|
243
|
+
dispose();
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
installedFsGuardRoots.delete(ref);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}, 'capital-generation.fs-write-guard()');
|
|
300
250
|
return;
|
|
301
251
|
}
|
|
302
252
|
install();
|
|
303
253
|
}
|
|
304
|
-
|
|
305
|
-
* 注册 `agent/created` 监听。监听器只做一件事:对本 preset 的根 Agent 收敛一次。
|
|
306
|
-
*
|
|
307
|
-
* 注册位置是 Root context(未打标签),因此能收到全部 agent;`agentPresets` 负责筛出本 preset。
|
|
308
|
-
* Root 监听器不随本 fiber 卸载,所以显式用 `ctx.effect` 持有它的 disposer。
|
|
309
|
-
*/
|
|
310
|
-
export function registerRootToolPolicy(ctx) {
|
|
254
|
+
export function registerRootToolPolicy(ctx, options = {}) {
|
|
311
255
|
const listenCtx = ctx.root ?? ctx;
|
|
312
256
|
if (typeof listenCtx.on !== 'function')
|
|
313
257
|
return;
|
|
314
|
-
|
|
315
|
-
|
|
258
|
+
if (installedPolicyRoots.has(listenCtx))
|
|
259
|
+
return;
|
|
260
|
+
installedPolicyRoots.add(listenCtx);
|
|
316
261
|
const install = () => {
|
|
317
262
|
const stopCreated = listenCtx.on?.('agent/created', (payload) => {
|
|
318
263
|
try {
|
|
319
264
|
const agent = payload?.agent;
|
|
320
265
|
const isRoot = isRootAgent(agent);
|
|
321
|
-
//
|
|
322
|
-
// 把它们也塞进这份上限 8 条的窗口里,会把唯一有意义的那行挤掉。
|
|
266
|
+
// Root only: children always have a parentSession and would crowd the 8-entry window.
|
|
323
267
|
const probe = isRoot
|
|
324
268
|
? { agentId: agentSessionId(agent), root: true, presetId: presetIdOf(ctx, agent), outcome: 'pending', denied: [], failed: [] }
|
|
325
269
|
: undefined;
|
|
@@ -333,60 +277,43 @@ export function registerRootToolPolicy(ctx) {
|
|
|
333
277
|
probe.outcome = 'not-capital';
|
|
334
278
|
return;
|
|
335
279
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
probe
|
|
339
|
-
|
|
340
|
-
|
|
280
|
+
if (options.deny === false) {
|
|
281
|
+
// Composition already removed data/web/chart; per-name deny would only add `failed` noise.
|
|
282
|
+
if (probe !== undefined)
|
|
283
|
+
probe.outcome = 'skipped';
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
const outcome = restrictRootAgentTools(agent, probe);
|
|
287
|
+
if (probe !== undefined)
|
|
288
|
+
probe.outcome = outcome;
|
|
289
|
+
if (outcome === 'unavailable') {
|
|
290
|
+
ctx.logger?.warn(`capital-generation: 根 Agent 工具收敛未生效(${outcome}):主 Agent 仍会看到 ${ROOT_AGENT_DENIED_TOOLS.join(' / ')}`);
|
|
291
|
+
}
|
|
341
292
|
}
|
|
342
|
-
const dispose = installRootOcrGuard(agent);
|
|
343
|
-
const id = agentSessionId(agent);
|
|
344
|
-
if (dispose !== undefined && id !== undefined)
|
|
345
|
-
guards.set(id, dispose);
|
|
346
293
|
}
|
|
347
294
|
catch (error) {
|
|
348
295
|
ctx.logger?.warn(`capital-generation: 根 Agent 工具收敛异常:${error instanceof Error ? error.message : String(error)}`);
|
|
349
296
|
}
|
|
350
297
|
});
|
|
351
|
-
const stopDisposed = listenCtx.on?.('agent/disposed', (payload) => {
|
|
352
|
-
try {
|
|
353
|
-
const id = agentSessionId(payload?.agent);
|
|
354
|
-
if (id === undefined)
|
|
355
|
-
return;
|
|
356
|
-
const dispose = guards.get(id);
|
|
357
|
-
if (dispose === undefined)
|
|
358
|
-
return;
|
|
359
|
-
guards.delete(id);
|
|
360
|
-
dispose();
|
|
361
|
-
}
|
|
362
|
-
catch { /* 释放失败不该影响 agent 拆除路径 */ }
|
|
363
|
-
});
|
|
364
298
|
return () => {
|
|
365
299
|
try {
|
|
366
300
|
if (typeof stopCreated === 'function')
|
|
367
301
|
stopCreated();
|
|
368
302
|
}
|
|
369
303
|
catch { /* ignore */ }
|
|
370
|
-
try {
|
|
371
|
-
if (typeof stopDisposed === 'function')
|
|
372
|
-
stopDisposed();
|
|
373
|
-
}
|
|
374
|
-
catch { /* ignore */ }
|
|
375
|
-
for (const dispose of guards.values()) {
|
|
376
|
-
try {
|
|
377
|
-
dispose();
|
|
378
|
-
}
|
|
379
|
-
catch { /* ignore */ }
|
|
380
|
-
}
|
|
381
|
-
guards.clear();
|
|
382
304
|
};
|
|
383
305
|
};
|
|
384
306
|
if (typeof ctx.effect === 'function') {
|
|
385
307
|
ctx.effect(() => {
|
|
386
308
|
const dispose = install();
|
|
387
309
|
return () => {
|
|
388
|
-
|
|
389
|
-
dispose
|
|
310
|
+
try {
|
|
311
|
+
if (typeof dispose === 'function')
|
|
312
|
+
dispose();
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
installedPolicyRoots.delete(listenCtx);
|
|
316
|
+
}
|
|
390
317
|
};
|
|
391
318
|
}, 'capital-generation.root-tool-policy()');
|
|
392
319
|
return;
|
|
@@ -17,11 +17,7 @@ type RuntimeSummary = {
|
|
|
17
17
|
name: string;
|
|
18
18
|
description: string;
|
|
19
19
|
};
|
|
20
|
-
/**
|
|
21
|
-
* 索引块正文。**唯一实现**:条目集合来自 catalog 的 `selectedSkillsForTask`(与目录、闸门同源),
|
|
22
|
-
* 描述用**运行时那一份**(上游 frontmatter,也就是会话目录里模型看到的那句话),不是设置卡片里
|
|
23
|
-
* 策展过的中文摘要——同一个 skill 在同一段上下文里出现两种描述,只会让模型两处对照。
|
|
24
|
-
*/
|
|
20
|
+
/** Sole index renderer; descriptions use runtime frontmatter (what the model sees), not card summaries. */
|
|
25
21
|
export declare function renderTaskSkillIndex(taskId: string, summaries: readonly RuntimeSummary[]): string | undefined;
|
|
26
22
|
type FileSystemSkillConfig = {
|
|
27
23
|
providerName: string;
|
|
@@ -31,6 +27,6 @@ type FileSystemSkillConfig = {
|
|
|
31
27
|
};
|
|
32
28
|
type FileSystemSkillSource = Pick<FileSystemSkillProvider, 'list' | 'get' | 'dispose'>;
|
|
33
29
|
type FileSystemSkillFactory = (ctx: Context, control: SkillProviderControl, config: FileSystemSkillConfig) => FileSystemSkillSource;
|
|
34
|
-
/**
|
|
30
|
+
/** Registers selected community experiments on the Capital root Agent only. */
|
|
35
31
|
export declare function registerRootSelectedSkills(ctx: PolicyContext, selection: SelectedSkillSelectionSource, createFilesystemProvider?: FileSystemSkillFactory): void;
|
|
36
32
|
export {};
|