@v587d/capital-generation 2.8.2 → 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.
Files changed (164) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +41 -20
  3. package/chart-ui/README.md +1 -1
  4. package/chart-ui/client.src.cjs +7 -16
  5. package/chart-ui/index.js +12 -33
  6. package/chart-ui/package.json +2 -2
  7. package/config/client.js +18 -39
  8. package/config/client.src.cjs +79 -217
  9. package/config/index.js +22 -72
  10. package/config/package.json +2 -2
  11. package/cordis.patch.yml +46 -51
  12. package/lib/agents/bash-guard.d.ts +9 -55
  13. package/lib/agents/bash-guard.js +27 -67
  14. package/lib/agents/preset-roster.d.ts +22 -0
  15. package/lib/agents/preset-roster.js +87 -0
  16. package/lib/agents/role-tool-policy.d.ts +46 -0
  17. package/lib/agents/role-tool-policy.js +205 -0
  18. package/lib/agents/root-tool-policy.d.ts +28 -124
  19. package/lib/agents/root-tool-policy.js +164 -237
  20. package/lib/agents/selected-skills-provider.d.ts +2 -6
  21. package/lib/agents/selected-skills-provider.js +27 -84
  22. package/lib/capital-settings.d.ts +27 -0
  23. package/lib/capital-settings.js +23 -0
  24. package/lib/chart/artifact-ref.d.ts +3 -8
  25. package/lib/chart/artifact-ref.js +3 -8
  26. package/lib/chart/downsample.d.ts +2 -5
  27. package/lib/chart/downsample.js +3 -8
  28. package/lib/chart/errors.d.ts +3 -7
  29. package/lib/chart/errors.js +1 -2
  30. package/lib/chart/event-context.d.ts +8 -0
  31. package/lib/chart/event-context.js +39 -0
  32. package/lib/chart/events.d.ts +21 -78
  33. package/lib/chart/events.js +40 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/runtime.d.ts +5 -18
  37. package/lib/chart/runtime.js +14 -34
  38. package/lib/chart/series.d.ts +10 -17
  39. package/lib/chart/series.js +22 -47
  40. package/lib/chart/source-token.d.ts +5 -13
  41. package/lib/chart/source-token.js +18 -17
  42. package/lib/chart/spec.d.ts +6 -12
  43. package/lib/chart/spec.js +7 -31
  44. package/lib/chart/tool.d.ts +6 -29
  45. package/lib/chart/tool.js +9 -23
  46. package/lib/chart-render-entry.d.ts +9 -0
  47. package/lib/chart-render-entry.js +17 -0
  48. package/lib/chart-source-entry.d.ts +13 -0
  49. package/lib/chart-source-entry.js +20 -0
  50. package/lib/collect-entry.d.ts +9 -0
  51. package/lib/collect-entry.js +10 -0
  52. package/lib/collector-runtime.d.ts +22 -0
  53. package/lib/collector-runtime.js +97 -0
  54. package/lib/data-collector/args.d.ts +6 -24
  55. package/lib/data-collector/args.js +6 -24
  56. package/lib/data-collector/dataset-tools.d.ts +7 -1
  57. package/lib/data-collector/dataset-tools.js +27 -56
  58. package/lib/data-collector/describe.d.ts +8 -33
  59. package/lib/data-collector/describe.js +17 -70
  60. package/lib/data-collector/hub.d.ts +27 -85
  61. package/lib/data-collector/hub.js +19 -85
  62. package/lib/data-collector/query-time.d.ts +7 -26
  63. package/lib/data-collector/query-time.js +8 -27
  64. package/lib/data-collector/store.d.ts +40 -117
  65. package/lib/data-collector/store.js +32 -111
  66. package/lib/data-collector/time-axis.d.ts +16 -76
  67. package/lib/data-collector/time-axis.js +25 -111
  68. package/lib/data-collector/tools.d.ts +6 -6
  69. package/lib/data-collector/tools.js +34 -43
  70. package/lib/dataset-read-entry.d.ts +9 -0
  71. package/lib/dataset-read-entry.js +7 -0
  72. package/lib/index.d.ts +7 -117
  73. package/lib/index.js +43 -422
  74. package/lib/net/eastmoney-client.d.ts +13 -65
  75. package/lib/net/eastmoney-client.js +12 -53
  76. package/lib/net/throttle.d.ts +2 -13
  77. package/lib/net/throttle.js +3 -14
  78. package/lib/ocr/client.d.ts +12 -21
  79. package/lib/ocr/client.js +23 -40
  80. package/lib/ocr/markdown.d.ts +20 -26
  81. package/lib/ocr/markdown.js +23 -36
  82. package/lib/ocr/store.d.ts +14 -47
  83. package/lib/ocr/store.js +15 -45
  84. package/lib/ocr-entry.d.ts +9 -0
  85. package/lib/ocr-entry.js +10 -0
  86. package/lib/policy-entry.d.ts +17 -0
  87. package/lib/policy-entry.js +22 -0
  88. package/lib/preset-claim.d.ts +22 -0
  89. package/lib/preset-claim.js +24 -0
  90. package/lib/preset-ids.d.ts +38 -0
  91. package/lib/preset-ids.js +46 -0
  92. package/lib/retriever-config.d.ts +37 -0
  93. package/lib/retriever-config.js +36 -0
  94. package/lib/retriever-runtime.d.ts +31 -0
  95. package/lib/retriever-runtime.js +82 -0
  96. package/lib/root-extras-entry.d.ts +17 -0
  97. package/lib/root-extras-entry.js +25 -0
  98. package/lib/runtime-entry.d.ts +15 -0
  99. package/lib/runtime-entry.js +5 -0
  100. package/lib/runtime.d.ts +21 -0
  101. package/lib/runtime.js +33 -0
  102. package/lib/search-entry.d.ts +9 -0
  103. package/lib/search-entry.js +9 -0
  104. package/lib/sources/eastmoney-http.js +20 -68
  105. package/lib/sources/ftshare-rest.d.ts +6 -31
  106. package/lib/sources/ftshare-rest.js +1194 -225
  107. package/lib/sources/fuyao-core.d.ts +8 -18
  108. package/lib/sources/fuyao-core.js +10 -21
  109. package/lib/sources/fuyao-rest.d.ts +2 -6
  110. package/lib/sources/fuyao-rest.js +23 -102
  111. package/lib/sources/security-code.d.ts +6 -12
  112. package/lib/sources/security-code.js +6 -12
  113. package/lib/sources/security-identity.d.ts +1 -4
  114. package/lib/sources/security-identity.js +3 -8
  115. package/lib/sources/tencent-http.js +14 -28
  116. package/lib/sources/tencent-public-core.d.ts +8 -60
  117. package/lib/sources/tencent-public-core.js +12 -77
  118. package/lib/sources/wind-mcp.d.ts +0 -5
  119. package/lib/sources/wind-mcp.js +23 -78
  120. package/lib/sources-entry.d.ts +9 -0
  121. package/lib/sources-entry.js +9 -0
  122. package/lib/time/tools.d.ts +8 -31
  123. package/lib/time/tools.js +39 -64
  124. package/lib/time-entry.d.ts +9 -0
  125. package/lib/time-entry.js +7 -0
  126. package/lib/tool-exec.d.ts +45 -11
  127. package/lib/tool-exec.js +59 -6
  128. package/lib/user-customization.d.ts +14 -0
  129. package/lib/user-customization.js +47 -0
  130. package/lib/watchlist/tools.d.ts +3 -13
  131. package/lib/watchlist/tools.js +10 -30
  132. package/lib/watchlist-entry.d.ts +9 -0
  133. package/lib/watchlist-entry.js +6 -0
  134. package/lib/web-retriever/engines.d.ts +6 -11
  135. package/lib/web-retriever/engines.js +9 -14
  136. package/lib/web-retriever/html-markdown.d.ts +14 -34
  137. package/lib/web-retriever/html-markdown.js +39 -75
  138. package/lib/web-retriever/local-fetch.d.ts +22 -58
  139. package/lib/web-retriever/local-fetch.js +33 -77
  140. package/lib/web-retriever/retriever.d.ts +6 -18
  141. package/lib/web-retriever/retriever.js +7 -7
  142. package/lib/web-retriever/sources.d.ts +37 -124
  143. package/lib/web-retriever/sources.js +64 -196
  144. package/lib/web-retriever/tools.d.ts +43 -27
  145. package/lib/web-retriever/tools.js +99 -137
  146. package/lib/web-retriever/wind-client.d.ts +14 -31
  147. package/lib/web-retriever/wind-client.js +16 -32
  148. package/locale/zh.json +1 -1
  149. package/package.json +67 -7
  150. package/preset/capital-generation/README.md +87 -53
  151. package/preset/capital-generation/agent.patch.yml +526 -337
  152. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  153. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  154. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +1 -1
  155. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  156. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
  157. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  158. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
  159. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  160. package/watchlist/client.js +15 -148
  161. package/watchlist/client.src.cjs +148 -662
  162. package/watchlist/holding-rules.js +11 -38
  163. package/watchlist/index.js +127 -397
  164. package/watchlist/package.json +2 -2
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Role tool surfaces by mounted role preset. `toolFilter` was removed upstream, so tools come
3
+ * from family rows (composition) and there is no allow-list here. Restricting the agent scope
4
+ * filters preset contributions; identity is the preset id, not root/child. Listeners on `ctx.root`.
5
+ */
6
+ import type { AgentLike, GuardedExecution, PolicyContext } from './root-tool-policy.js';
7
+ import { type RolePresetId } from '../preset-ids.js';
8
+ export { ROLE_PRESET_IDS, ROLE_PRESET_ID_LIST, type RolePresetId } from '../preset-ids.js';
9
+ /** Welded to `send_message` by `tool-subagent-control`; no host switch, so leaf roles deny it by name. */
10
+ export declare const SIBLING_DENIED_TOOLS: readonly ["interrupt_agent"];
11
+ /** Residual surface; no allow list: family rows define the tool surface. */
12
+ export interface RoleSurface {
13
+ /** Sibling names welded into a needed row that this persona must not have. */
14
+ readonly denySiblings?: readonly string[];
15
+ }
16
+ /** Leaf roles need `send_message`, so they inherit the welded `interrupt_agent`; specialist
17
+ * mounts no control row. Tolerant per-name deny records misses without failing open. */
18
+ export declare const ROLE_TOOL_SURFACES: Record<RolePresetId, RoleSurface>;
19
+ /** Role preset id only, undefined for root and other presets; failure means "do not converge". */
20
+ export declare function rolePresetIdOf(ctx: PolicyContext, agent: AgentLike | undefined): RolePresetId | undefined;
21
+ export interface RolePolicyProbe extends Omit<RolePolicyResult, 'outcome'> {
22
+ agentId: string | undefined;
23
+ presetId: string;
24
+ /** True = manually opened root with a role preset, false = delegated child; the paths converge differently. */
25
+ root: boolean;
26
+ /** Created-time restrict result, or a call-level guard denial. */
27
+ outcome: 'restricted' | 'unavailable' | 'guard-denied';
28
+ }
29
+ export interface RolePolicyResult {
30
+ outcome: 'restricted' | 'unavailable';
31
+ denied: string[];
32
+ /** Names invisible here and skipped harmlessly (their row is not mounted). */
33
+ failed: string[];
34
+ }
35
+ export declare function rolePolicyProbes(): RolePolicyProbe[];
36
+ /** Test-only: clears records and the dedupe table. */
37
+ export declare function resetRolePolicyProbes(): void;
38
+ /** Tolerant per-name deny: invisible names are recorded, never thrown; called on agent creation. */
39
+ export declare function restrictRoleAgentTools(agent: AgentLike | undefined, surface: RoleSurface): RolePolicyResult;
40
+ /** Call-level residual guard: created-time restrict misses manually opened role modes, so
41
+ * read identity per call. Fail open unless a role preset is confirmed. */
42
+ export declare function roleSiblingGuardReasonFor(ctx: PolicyContext): (exec: GuardedExecution | undefined) => string | undefined;
43
+ /** Installs the sibling guard globally once. */
44
+ export declare function registerRoleSiblingGuard(ctx: PolicyContext): void;
45
+ /** Dedupes by session id: `apply()` runs once per mounted role preset and duplicate probes would flood the readout. */
46
+ export declare function registerRoleToolPolicy(ctx: PolicyContext): void;
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Role tool surfaces by mounted role preset. `toolFilter` was removed upstream, so tools come
3
+ * from family rows (composition) and there is no allow-list here. Restricting the agent scope
4
+ * filters preset contributions; identity is the preset id, not root/child. Listeners on `ctx.root`.
5
+ */
6
+ import { isRootAgent, recordGuardDecision } from './root-tool-policy.js';
7
+ import { readPresetClaim } from '../preset-claim.js';
8
+ import { ROLE_PRESET_ID_LIST, ROLE_PRESET_IDS } from '../preset-ids.js';
9
+ export { ROLE_PRESET_IDS, ROLE_PRESET_ID_LIST } from '../preset-ids.js';
10
+ /** Welded to `send_message` by `tool-subagent-control`; no host switch, so leaf roles deny it by name. */
11
+ export const SIBLING_DENIED_TOOLS = ['interrupt_agent'];
12
+ /** Leaf roles need `send_message`, so they inherit the welded `interrupt_agent`; specialist
13
+ * mounts no control row. Tolerant per-name deny records misses without failing open. */
14
+ export const ROLE_TOOL_SURFACES = {
15
+ [ROLE_PRESET_IDS.collector]: { denySiblings: SIBLING_DENIED_TOOLS },
16
+ [ROLE_PRESET_IDS.junior]: { denySiblings: SIBLING_DENIED_TOOLS },
17
+ [ROLE_PRESET_IDS.retriever]: { denySiblings: SIBLING_DENIED_TOOLS },
18
+ [ROLE_PRESET_IDS.specialist]: {},
19
+ [ROLE_PRESET_IDS.analyst]: { denySiblings: SIBLING_DENIED_TOOLS },
20
+ };
21
+ /** Role preset id only, undefined for root and other presets; failure means "do not converge". */
22
+ export function rolePresetIdOf(ctx, agent) {
23
+ const claim = readPresetClaim(ctx, agent).claim;
24
+ return claim !== undefined && ROLE_PRESET_ID_LIST.includes(claim) ? claim : undefined;
25
+ }
26
+ const probes = [];
27
+ const MAX_PROBES = 12;
28
+ /** Session ids already converged: `apply()` runs once per mounted role preset. */
29
+ const handled = new Set();
30
+ /** One listener set when both host policy and this plugin mount. */
31
+ const installedRoots = new WeakSet();
32
+ /** Call-level guard installs independently of the listener. */
33
+ const installedGuardRoots = new WeakSet();
34
+ export function rolePolicyProbes() {
35
+ return probes.map((probe) => ({ ...probe, denied: [...probe.denied], failed: [...probe.failed] }));
36
+ }
37
+ /** Test-only: clears records and the dedupe table. */
38
+ export function resetRolePolicyProbes() {
39
+ probes.length = 0;
40
+ handled.clear();
41
+ }
42
+ /** Tolerant per-name deny: invisible names are recorded, never thrown; called on agent creation. */
43
+ export function restrictRoleAgentTools(agent, surface) {
44
+ const denied = [];
45
+ const failed = [];
46
+ let tools;
47
+ try {
48
+ tools = agent?.ctx?.tools;
49
+ }
50
+ catch {
51
+ return { outcome: 'unavailable', denied, failed };
52
+ }
53
+ if (tools === undefined || typeof tools.restrict !== 'function') {
54
+ return { outcome: 'unavailable', denied, failed };
55
+ }
56
+ for (const name of surface.denySiblings ?? []) {
57
+ try {
58
+ tools.restrict({ deny: [name] });
59
+ denied.push(name);
60
+ }
61
+ catch {
62
+ failed.push(name);
63
+ }
64
+ }
65
+ return { outcome: 'restricted', denied, failed };
66
+ }
67
+ /** Call-level residual guard: created-time restrict misses manually opened role modes, so
68
+ * read identity per call. Fail open unless a role preset is confirmed. */
69
+ export function roleSiblingGuardReasonFor(ctx) {
70
+ return (exec) => {
71
+ const name = exec?.name;
72
+ if (typeof name !== 'string' || !SIBLING_DENIED_TOOLS.includes(name))
73
+ return undefined;
74
+ const claim = readPresetClaim(ctx, exec?.agent);
75
+ if (claim.registryMissing || claim.failed || claim.claim === undefined)
76
+ return undefined;
77
+ if (!ROLE_PRESET_ID_LIST.includes(claim.claim))
78
+ return undefined;
79
+ return `${name} 不对人设 ${claim.claim} 开放:叶子角色只回传自己的结果,打断别的会话归主 Agent 决定。`
80
+ + '需要别的 Agent 停下来,用 send_message 回告主 Agent;不要自己换工具硬做。';
81
+ };
82
+ }
83
+ /** Installs the sibling guard globally once. */
84
+ export function registerRoleSiblingGuard(ctx) {
85
+ const ref = ctx.root ?? ctx;
86
+ if (installedGuardRoots.has(ref))
87
+ return;
88
+ installedGuardRoots.add(ref);
89
+ const reason = roleSiblingGuardReasonFor(ctx);
90
+ const install = () => {
91
+ let tools;
92
+ try {
93
+ tools = (ctx.get?.('tools') ?? ctx.tools);
94
+ }
95
+ catch {
96
+ return undefined;
97
+ }
98
+ if (tools === undefined || typeof tools.guard !== 'function') {
99
+ ctx.logger?.warn('capital-generation: 角色兄弟名字的调用级闸门没装上(guard 面不可用):手开角色会话仍可 interrupt 兄弟');
100
+ return undefined;
101
+ }
102
+ try {
103
+ // Same probe channel as restrict: one readout for both planes.
104
+ const dispose = tools.guard((execution) => {
105
+ const denied = reason(execution);
106
+ if (denied !== undefined)
107
+ recordGuardDecision(ctx, execution, String(execution?.name));
108
+ return denied;
109
+ });
110
+ return typeof dispose === 'function' ? dispose : undefined;
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ };
116
+ if (typeof ctx.effect === 'function') {
117
+ ctx.effect(() => {
118
+ const dispose = install() ?? (() => { });
119
+ return () => {
120
+ try {
121
+ dispose();
122
+ }
123
+ finally {
124
+ installedGuardRoots.delete(ref);
125
+ }
126
+ };
127
+ }, 'capital-generation.role-sibling-guard()');
128
+ return;
129
+ }
130
+ install();
131
+ }
132
+ /** Dedupes by session id: `apply()` runs once per mounted role preset and duplicate probes would flood the readout. */
133
+ export function registerRoleToolPolicy(ctx) {
134
+ const listenCtx = ctx.root ?? ctx;
135
+ if (typeof listenCtx.on !== 'function')
136
+ return;
137
+ // Host policy and the monolith may both call this; one listener set per root.
138
+ if (installedRoots.has(listenCtx))
139
+ return;
140
+ installedRoots.add(listenCtx);
141
+ const install = () => {
142
+ const stopCreated = listenCtx.on?.('agent/created', (payload) => {
143
+ try {
144
+ const agent = payload?.agent;
145
+ const id = typeof agent?.session?.id === 'string' ? agent.session.id : undefined;
146
+ if (id !== undefined && handled.has(id))
147
+ return;
148
+ const presetId = rolePresetIdOf(ctx, agent);
149
+ if (presetId === undefined)
150
+ return;
151
+ const surface = ROLE_TOOL_SURFACES[presetId];
152
+ if (surface.denySiblings === undefined || surface.denySiblings.length === 0) {
153
+ if (id !== undefined)
154
+ handled.add(id);
155
+ return;
156
+ }
157
+ const result = restrictRoleAgentTools(agent, surface);
158
+ if (id !== undefined)
159
+ handled.add(id);
160
+ probes.push({ agentId: id, root: isRootAgent(agent), presetId, ...result });
161
+ if (probes.length > MAX_PROBES)
162
+ probes.shift();
163
+ if (result.outcome === 'unavailable') {
164
+ ctx.logger?.warn(`capital-generation: 角色 ${presetId} 的兄弟名字收敛没装上(restrict 面不可用):该 child 仍看得见 ${SIBLING_DENIED_TOOLS.join(' / ')}`);
165
+ }
166
+ }
167
+ catch (error) {
168
+ ctx.logger?.warn(`capital-generation: 角色工具面收敛异常:${error instanceof Error ? error.message : String(error)}`);
169
+ }
170
+ });
171
+ const stopDisposed = listenCtx.on?.('agent/disposed', (payload) => {
172
+ const id = typeof payload?.agent?.session?.id === 'string' ? payload.agent.session.id : undefined;
173
+ if (id !== undefined)
174
+ handled.delete(id);
175
+ });
176
+ return () => {
177
+ try {
178
+ if (typeof stopCreated === 'function')
179
+ stopCreated();
180
+ }
181
+ catch { /* ignore */ }
182
+ try {
183
+ if (typeof stopDisposed === 'function')
184
+ stopDisposed();
185
+ }
186
+ catch { /* ignore */ }
187
+ };
188
+ };
189
+ if (typeof ctx.effect === 'function') {
190
+ ctx.effect(() => {
191
+ const dispose = install();
192
+ return () => {
193
+ try {
194
+ if (typeof dispose === 'function')
195
+ dispose();
196
+ }
197
+ finally {
198
+ installedRoots.delete(listenCtx);
199
+ }
200
+ };
201
+ }, 'capital-generation.role-tool-policy()');
202
+ return;
203
+ }
204
+ install();
205
+ }
@@ -1,85 +1,33 @@
1
1
  /**
2
- * 根 Agent 的工具收敛:把"只有子 Agent 该用"的工具从**主 Agent**的工具表里拿掉。
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
+ /** Outbound tools (search 2 + wind 2 + named sources 9); `ocr` is excluded because only its url form is restricted. */
31
6
  export declare const RETRIEVAL_DENIED_TOOLS: readonly ["anysearch_search", "web_retriever_fetch", "wind_docs_announcements", "wind_docs_news", "cls_telegraph", "wscn_lives", "cninfo_irm", "sseinfo_qa", "eastmoney_724", "eastmoney_stock_news", "eastmoney_reports", "sina_reports", "ths_eps_forecast"];
32
- /** 文档解析工具名(`web_retriever` 的第三个工作面)。 */
33
7
  export declare const OCR_TOOL_NAME = "ocr";
34
- /**
35
- * 根 Agent 的 `ocr` 只放行**本地形态**(`file` / `doc_id`),`url` 形态拒绝。
36
- *
37
- * 为什么不整名 deny:用户把 PDF 放在工作目录用 `@xxx.pdf` 推给主 Agent 是正当入口,
38
- * 解析它不需要经过委派;但 `url` 形态等于"主 Agent 自己决定去戳一个外部链接",
39
- * 那是「外部检索只经 web_retriever」这条结构约束唯一被留的口子。名字级 deny 做不到
40
- * 这个区分(`render_chart` 的教训一样:可见性不是权限),所以用调用级 guard。
41
- */
8
+ /** Name-level deny cannot express the form distinction; this is a call-level guard. */
42
9
  export declare const OCR_ROOT_URL_DENIED: string;
43
- /** 单次调用的判定:根 Agent 用 `ocr` 提交外部 `url` 才拒绝,其余一律放行。 */
44
- export declare function rootOcrGuardReason(exec: GuardedExecution | undefined): string | undefined;
45
- /**
46
- * shell 工具名:preset 里这两行是**平台成对**挂载的(`agent.patch.yml` 的 `tool-bash` /
47
- * `tool-pwsh`,靠 `process.platform === 'win32'` 互斥 `disabled`),所以名字由平台决定。
48
- *
49
- * ⛔ 但**闸门必须认全集**:只认本平台那一个,另一端就是静默放行——同一个能力两个入口,
50
- * 本地测试只覆盖了接好的那个(AGENTS.md §9.7 的第三种形状)。`bash-guard.ts` 的升级闸门
51
- * 从这里取名字,不再各写一份。
52
- *
53
- * 2026-10-15 用户决策「开 read、同时保留 bash」:shell **不再**进 {@link ROOT_AGENT_DENIED_TOOLS}
54
- * ——主 Agent 保留 bash / pwsh(读 skill 分册、纯计算);子会话侧仍由委派行的 `toolFilter`
55
- * 与 `bash-guard.ts` 的升级闸门收敛。
56
- */
10
+ export declare function ocrUrlFormRequested(exec: GuardedExecution | undefined): boolean;
11
+ /** Read per call because a created-time gate misses manually opened modes; fail open unless a confirmed Capital root. */
12
+ export declare function ocrFormGuardReasonFor(ctx: PolicyContext): (exec: GuardedExecution | undefined) => string | undefined;
13
+ /** Mounted as a platform pair; recognize both everywhere or the other platform's gate is dead. */
57
14
  export declare const SHELL_TOOL_NAMES: readonly ["bash", "pwsh"];
58
- export declare const ROOT_AGENT_DENIED_TOOLS: readonly ["render_chart", "subagent_visualization_specialist", "prepare_chart_source", "anysearch_search", "web_retriever_fetch", "wind_docs_announcements", "wind_docs_news", "cls_telegraph", "wscn_lives", "cninfo_irm", "sseinfo_qa", "eastmoney_724", "eastmoney_stock_news", "eastmoney_reports", "sina_reports", "ths_eps_forecast", "write", "edit"];
59
- /** 本 preset 的 id(`composedPreset` 的返回值)。 */
15
+ export declare const ROOT_AGENT_DENIED_TOOLS: readonly ["render_chart", "prepare_chart_source", "anysearch_search", "web_retriever_fetch", "wind_docs_announcements", "wind_docs_news", "cls_telegraph", "wscn_lives", "cninfo_irm", "sseinfo_qa", "eastmoney_724", "eastmoney_stock_news", "eastmoney_reports", "sina_reports", "ths_eps_forecast", "write", "edit"];
60
16
  export declare const CAPITAL_PRESET_ID = "capital-generation";
61
17
  interface ToolRestriction {
62
18
  allow?: readonly string[];
63
19
  deny?: readonly string[];
64
20
  }
65
- /**
66
- * `ctx.tools` 里本仓真正用到的两个方法。
67
- *
68
- * `guard` 是单调执行闸门(`dsh-tools` 的 `ToolGuard`):同步检查一次调用,返回字符串即拒绝,
69
- * **没有 allow 结果**,所以任何监听顺序都翻不回 allow。bash 闸门(`bash-guard.ts`)用它,
70
- * 因为「只按调用内容 deny」这件事只有这里有正确的语义。
71
- */
21
+ /** `guard` only returns a deny string, so listener order can never turn a deny into an allow. */
72
22
  interface RestrictedToolRuntime {
73
23
  restrict?(filter: ToolRestriction): unknown;
74
24
  guard?(guard: (execution: GuardedExecution) => string | undefined): unknown;
75
25
  }
76
- /** 闸门看到的调用形状(`name` + 解析后的 `arguments` + 调用方 agent)。 */
77
26
  export interface GuardedExecution {
78
27
  readonly name?: string;
79
28
  readonly arguments?: unknown;
80
29
  readonly agent?: AgentLike;
81
30
  }
82
- /** 事件载荷里我们真正用到的 Agent 形状。 */
83
31
  export interface AgentLike {
84
32
  readonly session?: {
85
33
  readonly id?: string;
@@ -92,7 +40,6 @@ export interface AgentLike {
92
40
  readonly [key: string]: unknown;
93
41
  };
94
42
  }
95
- /** 监听用的 context 形状(结构类型,便于单测注入假实现)。 */
96
43
  export interface PolicyContext {
97
44
  on?(event: string, listener: (payload: {
98
45
  agent?: AgentLike;
@@ -106,85 +53,42 @@ export interface PolicyContext {
106
53
  readonly [key: string]: unknown;
107
54
  }
108
55
  export type RootToolPolicyOutcome = 'restricted' | 'skipped' | 'unavailable';
109
- /** 根 Agent 判据:没有 parentSession。子 Agent 一律有(spawn provider 写入)。 */
110
56
  export declare function isRootAgent(agent: AgentLike | undefined): boolean;
111
- /** 会话 id(guard 归档用);缺失或空串返回 undefined。 */
112
57
  export declare function agentSessionId(agent: AgentLike | undefined): string | undefined;
113
- /** 只有本 preset 的 agent 才收敛:别的 preset 的 agent 不该被我们碰。 */
114
58
  export declare function isCapitalAgent(ctx: PolicyContext, agent: AgentLike | undefined): boolean;
115
- /**
116
- * 根收敛的**现场记录**(取证用,不改任何行为)。
117
- *
118
- * 为什么必须有:`registerRootToolPolicy` 有三条互不相同的静默出口——读不到 registry、
119
- * `composedPreset` 判 false、逐名 `restrict()` 抛错被 catch 跳过(此时 outcome 仍记
120
- * `restricted`)。三条都**不留痕迹**,而桌面端不落盘 `ctx.logger`,于是"收敛没生效"这件事
121
- * 在桌面端等于不可观测。2026-09-30 桌面端实测:Capital 根会话的 `request.header.tools`
122
- * 里 `pwsh` / `request_data` / `dc_status` / `render_chart` 全在,就是这条不可观测让它
123
- * 一路只表现为"模型不太守纪律"。
124
- *
125
- * 读法:`dc_status` 的 `root_tool_policy` 字段(模型可见、可回传,不依赖宿主日志)。
126
- */
59
+ /** Allows any Capital preset; guards for role children must use this, not the root id. */
60
+ export declare function isCapitalPresetAgent(ctx: PolicyContext, agent: AgentLike | undefined): boolean;
61
+ /** Diagnostic record surfaced via `dc_status`: silent convergence exits leave no other trace. */
127
62
  export interface RootPolicyProbe {
128
- /** 根 Agent 会话 id(子 Agent 也有 id,用来区分两条路径)。 */
129
63
  agentId: string | undefined;
130
64
  root: boolean;
131
- /** `composedPreset` 的原样返回值;`'no-registry'` = registry 或方法不在,`'error'` = 它抛错。 */
132
65
  presetId: string;
133
- outcome: 'pending' | 'restricted' | 'unavailable' | 'skipped' | 'not-capital';
134
- /** 真正被 `restrict({deny})` 接住的工具名。 */
66
+ outcome: 'pending' | 'restricted' | 'unavailable' | 'skipped' | 'not-capital' | 'guard-denied';
135
67
  denied: string[];
136
- /** `restrict()` 抛错因而被静默跳过的名字——非空就说明收敛只成功了一部分。 */
68
+ /** Non-empty means `restrict()` threw for those names and convergence was partial. */
137
69
  failed: string[];
138
70
  }
139
- /** 最近 {@link MAX_PROBES} 条收敛现场(进程内;不落盘,随宿主进程生灭)。 */
71
+ /** Shares the convergence channel so opened-mode and call-level failures line up. */
72
+ export declare function recordGuardDecision(ctx: PolicyContext, exec: GuardedExecution | undefined, toolName: string): void;
140
73
  export declare function rootPolicyProbes(): RootPolicyProbe[];
141
- /** 只给测试用:清空现场记录,避免用例之间互相污染。 */
142
74
  export declare function resetRootPolicyProbes(): void;
143
- /**
144
- * 只对根 Agent 生效地 deny {@link ROOT_AGENT_DENIED_TOOLS}。
145
- *
146
- * 逐名 restrict:`tools.restrict()` 对"本 scope 看不到的名字"会抛错(别的 preset、或行还没挂载),
147
- * 逐名调用把这种"本来就不该有"的情况变成无害跳过,而不是整批失败。
148
- * 任何异常都不抛出:调用方是 agent 创建路径,宁可少收敛也不能挡住建 agent。
149
- */
75
+ /** Per-name deny on the root only; name-not-visible throws become harmless skips. Never throws: called on agent creation. */
150
76
  export declare function restrictRootAgentTools(agent: AgentLike | undefined, record?: {
151
77
  denied: string[];
152
78
  failed: string[];
153
79
  }): RootToolPolicyOutcome;
154
- /**
155
- * 在**根 Agent 自己的 scope** 上装 `ocr` 的调用级闸门(子 Agent 不装:`url` 形态归它们)。
156
- *
157
- * 与 `restrictRootAgentTools` 同一个监听、同一份判据;guard 不可用或安装异常都不影响
158
- * 已经完成的 deny 收敛(宁可少一层闸门,不能挡住建 agent)。
159
- */
160
- export declare function installRootOcrGuard(agent: AgentLike | undefined): (() => void) | undefined;
161
- /** `tool-fs` 的写口工具名(唯一一份):`read` / `read_image` 是只读口,不在名单。 */
80
+ /** Install globally once; created-time identity misses manually opened modes. */
81
+ export declare function registerRootOcrGuard(ctx: PolicyContext): void;
162
82
  export declare const FS_WRITE_TOOL_NAMES: readonly ["write", "edit"];
163
- /** fs 写口拒绝文案:指回正确路径(读用 read,落盘走工具回传),并留可恢复的余地。 */
83
+ /** Points at the allowed paths (read, tool returns) instead of leaving the caller stuck. */
164
84
  export declare const FS_WRITE_READ_ONLY: string;
165
- /**
166
- * `tool-fs` 写口的调用级闸门(**全局**装在插件 ctx 上,一次安装管住本 preset 所有 agent)。
167
- *
168
- * 为什么装全局而不是像 `ocr` 那样按 agent 装:`agent/created` 的落层时机随宿主形状漂移
169
- * (桌面端 restrict 不写进链路层,见 `docs/dev/bash-gate.md` 的实测记录),而 guard 按
170
- * `exec.agent` 取链路、与实例绑哪层无关——这正是那份文档里声明的备选方向,先在写口上落地。
171
- *
172
- * ⛔ 全局 guard 会吃到本进程**所有**调用,所以判据必须 fail-**open**:registry 读不到、
173
- * `composedPreset` 抛错、拿不到 agent 的形状一律放行——宁可少拦一层(`ROOT_AGENT_DENIED_TOOLS`
174
- * 还在视图层兜着),也绝不能把别的 preset(开发会话的 read / write / edit)吃掉。
175
- * 拒绝只发生在"能确证调用方属于本 preset"时。
176
- */
85
+ /** Global call-level fs guard: the host global layer registers write/edit, so per-name restrict
86
+ * only shrinks children. Fail open unless a Capital preset is confirmed. */
177
87
  export declare function fsWriteGuardReasonFor(ctx: PolicyContext): (execution: GuardedExecution) => string | undefined;
178
- /**
179
- * 装 {@link fsWriteGuardReasonFor}。guard 不可用或安装异常都不外抛(调用方是装配路径);
180
- * disposer 由 `ctx.effect` 持有,随本 fiber 释放。
181
- */
182
88
  export declare function registerFsWriteGuard(ctx: PolicyContext): void;
183
- /**
184
- * 注册 `agent/created` 监听。监听器只做一件事:对本 preset 的根 Agent 收敛一次。
185
- *
186
- * 注册位置是 Root context(未打标签),因此能收到全部 agent;`agentPresets` 负责筛出本 preset。
187
- * Root 监听器不随本 fiber 卸载,所以显式用 `ctx.effect` 持有它的 disposer。
188
- */
189
- export declare function registerRootToolPolicy(ctx: PolicyContext): void;
89
+ export interface RootToolPolicyOptions {
90
+ /** Host policy passes false: family rows already close the surface, so record only. */
91
+ deny?: boolean;
92
+ }
93
+ export declare function registerRootToolPolicy(ctx: PolicyContext, options?: RootToolPolicyOptions): void;
190
94
  export {};