agentworkshop 0.7.61 → 0.7.63

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 (40) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +2488 -2526
  2. package/.output/.AgentWorkShop/prompts/host-tools.json +2488 -2526
  3. package/.output/nitro.json +1 -1
  4. package/.output/public/_nuxt/{DKHA3XxF.js → B5RD2ZST.js} +1 -1
  5. package/.output/public/_nuxt/{CeEzUIvN.js → DAfWhONg.js} +2 -2
  6. package/.output/public/_nuxt/{analysis.DfMg76el.css → analysis.BwhKdel9.css} +1 -1
  7. package/.output/public/_nuxt/builds/latest.json +1 -1
  8. package/.output/public/_nuxt/builds/meta/3097fe87-dd0d-4037-b7bb-ead0620af56b.json +1 -0
  9. package/.output/server/chunks/_/nitro.mjs +2186 -2281
  10. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  11. package/.output/server/chunks/build/{analysis-MVEnlO7A.mjs → analysis-CPIxT0FP.mjs} +5 -5
  12. package/.output/server/chunks/build/{analysis-MVEnlO7A.mjs.map → analysis-CPIxT0FP.mjs.map} +1 -1
  13. package/.output/server/chunks/routes/renderer.mjs +1 -1
  14. package/.output/server/chunks/virtual/entry.mjs +1 -1
  15. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  16. package/.output/server/package.json +1 -1
  17. package/README-zh.md +946 -945
  18. package/README.md +970 -969
  19. package/app/components/analysis/AnalysisEditModal.vue +192 -192
  20. package/config.yml +180 -180
  21. package/docs/cli.en.md +447 -447
  22. package/docs/cli.md +412 -412
  23. package/docs/full-test-plan.md +1 -1
  24. package/docs/plugins.en.md +1 -1
  25. package/docs/plugins.md +1 -1
  26. package/docs/sdk.en.md +1 -1
  27. package/docs/sdk.md +1 -1
  28. package/package.json +181 -181
  29. package/scripts/e2e-plc-plugin-closedloop.mjs +1 -1
  30. package/server/api/workshop/analysis/[id].patch.ts +100 -100
  31. package/server/plugins-builtin/diag-bridge/host/diagnosis.mjs +4 -2
  32. package/server/plugins-builtin/diag-bridge/host/plugin.mjs +2 -0
  33. package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +241 -243
  34. package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +96 -98
  35. package/server/services/workshop/agents/industrial/daq-tools.ts +16 -7
  36. package/server/services/workshop/agents/industrial/dcw-tools.ts +210 -255
  37. package/server/services/workshop/agents/industrial/index.ts +32 -32
  38. package/server/services/workshop/agents/industrial/param-tools.ts +0 -40
  39. package/server/services/workshop/prompts/loader.ts +15 -2
  40. package/.output/public/_nuxt/builds/meta/eba8574d-1991-47df-944c-18608bb58181.json +0 -1
@@ -6,7 +6,7 @@ import { baseOf, harnessOf, jpost, maxMinutesOf, maxTurnsOf, runKey, runningOfLi
6
6
  import { snapshotCore } from './snapshot.mjs'
7
7
  import { startingLines } from './constants.mjs'
8
8
 
9
- export async function startDiagnosis(ctx, { line, fromMs, toMs, question, scene, source, dataPath, dataPaths, harness, agentId, channelId }) {
9
+ export async function startDiagnosis(ctx, { line, fromMs, toMs, question, scene, source, dataPath, dataPaths, harness, agentId, channelId, analysisIntent }) {
10
10
  const base = baseOf(ctx)
11
11
  if (!base) return { ok: false, error: 'diag.base_url 非法(仅允许 http/https 且 host 为 127.0.0.1/localhost)。' }
12
12
  if (startingLines.has(line)) return { ok: false, error: `产线 ${line} 已有诊断正在启动,请稍后再试。` }
@@ -19,7 +19,8 @@ export async function startDiagnosis(ctx, { line, fromMs, toMs, question, scene,
19
19
  snap = await snapshotCore(ctx, line, fromMs, toMs)
20
20
  if (!snap.ok) return { ok: false, error: snap.error }
21
21
  }
22
- const q = question || `${line} 产线该时窗数据深度根因诊断`
22
+ const intent = String(analysisIntent ?? '').trim()
23
+ const q = question || (intent && intent !== 'deep-diagnosis' ? `${line} 产线该时窗数据分析(${intent})` : `${line} 产线该时窗数据深度根因诊断`)
23
24
  const sceneName = scene || `${line}_diag`
24
25
  // v2.1:统一走 IDD 任务管理面 /api/diagnosis/tasks——接收即返 task_id,
25
26
  // 诊断在 IDD 后台线程执行(内部 start+execute),Channel 绝不等待。
@@ -32,6 +33,7 @@ export async function startDiagnosis(ctx, { line, fromMs, toMs, question, scene,
32
33
  sceneName,
33
34
  userQuestion: q,
34
35
  harness: harness || harnessOf(ctx), // 缺省落 claude 引擎会因无 key 失败,必须显式(系统配置 plugins.diag-bridge.harness → kv → omp;diag_run 可按次覆盖)
36
+ ...(intent ? { analysisIntent: intent } : {}), // 分析意图透传(stability-report=产线稳定报告等;未知值由 IDD 400 拒,不做白名单硬编码)
35
37
  enhancement: 'off',
36
38
  reportLanguage: 'zh',
37
39
  maxTurns: maxTurnsOf(ctx), // 修复循环会加转数;150 不够走完全管线
@@ -109,6 +109,7 @@ export default {
109
109
  mode: { type: 'string', enum: ['async', 'sync'], description: 'async(缺省)=提交即返后台跑;sync=同步等待完成(最长 sync_timeout_s,超时自动转后台继续)' },
110
110
  sync_timeout_s: { type: 'number', description: 'sync 模式等待上限秒(缺省 240,上限 540)' },
111
111
  harness: { type: 'string', description: '诊断引擎按次覆盖(omp/claude/mock;缺省用插件设置)' },
112
+ analysis_intent: { type: 'string', description: '分析意图(可选):deep-diagnosis=深度根因诊断(缺省);stability-report=产线稳定报告(零根因分析,出分+等级+运行建议);quick-screen=快速筛查;doe-analysis=DOE 分析。非缺省意图由对应 skill 协议执行' },
112
113
  },
113
114
  required: [],
114
115
  },
@@ -158,6 +159,7 @@ export default {
158
159
  dataPath: dataPathDirect || undefined,
159
160
  dataPaths,
160
161
  harness: args.harness ? String(args.harness) : undefined,
162
+ analysisIntent: args.analysis_intent ? String(args.analysis_intent) : undefined,
161
163
  // 归因(分发层注入的调用方上下文):诊断完成后平台据此把结果回执给发起 Agent
162
164
  agentId: agent?.agentId ?? '',
163
165
  channelId: agent?.channelId ?? '',
@@ -1,243 +1,241 @@
1
- /**
2
- * dispatchHostTool —— 全引擎唯一工具分发入口,只做前置校验 + 路由。
3
- *
4
- * 分发语义与原实现逐字节对齐:插件工具优先 → workspace 协作/任务/记忆面 →
5
- * 工业工具族 → 插件兜底 → 未知工具报错。各工具族的实现体在 tools/* 中按行搬运,
6
- * 原先闭包捕获的 (identity / state / args / ws) 一律改为显式参数。
7
- *
8
- * 两处必须保持的细节:
9
- * - `return await handler(...)`:处理器在 try 内 await,异常才会落回本层的
10
- * 「工具执行异常」结果(直接 return 未 await 的 promise 会漏掉这个 catch);
11
- * - 工业工具族在 `getWorkspace()` 门控**之前**分流(只依赖 agentId)。
12
- */
13
- import { getChannelPluginsRepo } from '../../db/channel-plugins.repo'
14
- import { getChannelTwinProfile, modelBackedToolPolicyFor, type AmlChannelProfileKind } from '../../aml/twin/channel-profile'
15
- import { AML_TRAINING_TOOL_NAMES, AML_MODEL_BACKED_TOOL_NAMES } from './catalog'
16
- import { twinWriteGuard } from '../../aml/twin/feature-flags'
17
- import { listPluginTools, pluginOfTool } from '../plugin-tools'
18
- import type { HostToolBridgeContext, HostToolCall, HostToolResult } from './types'
19
- import { INDUSTRIAL_TOOL_NAMES, dispatchIndustrialTool } from './tools/industrial'
20
- import { guardIndustrialToolGrant } from '../industrial/grant-guard'
21
- import { handleCompleteTask, handleReportProgress } from './tools/progress'
22
- import { handleCancelTask, handleDispatchTask, handleGetMyTaskQueue, handleGetTaskDetails, handleListChannelTasks, handleReassignTask, handleRefuseTask, handleSubmitTask, handleUpdateTask } from './tools/tasks'
23
- import { handleGrantNodes, handleRevokeNodes } from './tools/team-delegation'
24
- import { handleBroadcastMessage, handleListOtherTeams, handlePollMessages, handleReadChannelMail, handleSearchOtherTeamsMemory, handleSendCrossChannelMessage, handleSendMessageToAgent } from './tools/messaging'
25
- import { handleSaveMemory, handleSearchMemory } from './tools/memory'
26
- import { handleCreateTeamAgent, handleGetQueueOverview, handleListTeamAgents, handleRemoveTeamAgent, handleUpdateTeamAgent } from './tools/team'
27
-
28
- /**
29
- * 分发一次 host tool 调用(全引擎唯一入口):
30
- * 插件工具优先 → workspace 面 → 工业工具族 → 插件兜底 → 未知工具。
31
- */
32
- /** 统一出口:所有工具结果经截断收口(单点修改,全族生效) */
33
- export async function dispatchHostTool(ctx: HostToolBridgeContext, req: HostToolCall): Promise<HostToolResult> {
34
- return truncateToolResult(await dispatchHostToolInner(ctx, req), req.toolName)
35
- }
36
-
37
- /** 工具结果统一出口上限:超长文本截断+尾注(2026-10-05 评审;export/diag 类可经 optOut 放行) */
38
- const TOOL_RESULT_MAX_CHARS = 16_000
39
- const TRUNCATE_OPT_OUT_TOOLS = new Set(['daq_export', 'diag_run'])
40
- function truncateToolResult(result: HostToolResult, toolName: string): HostToolResult {
41
- if (TRUNCATE_OPT_OUT_TOOLS.has(toolName)) return result
42
- const text = result.text ?? ''
43
- if (text.length <= TOOL_RESULT_MAX_CHARS) return result
44
- const head = text.slice(0, TOOL_RESULT_MAX_CHARS)
45
- return {
46
- ...result,
47
- text: head + `
48
- [truncated: ${text.length} → ${TOOL_RESULT_MAX_CHARS} 字符;请收窄时间窗/减少点位/分页后重试]`,
49
- }
50
- }
51
-
52
- async function dispatchHostToolInner(ctx: HostToolBridgeContext, req: HostToolCall): Promise<HostToolResult> {
53
- const identity = ctx.identity
54
- const state = ctx.state
55
- // 插件工具分发(ctx.omp.registerTool 注册的自定义工具;不依赖 workspace,优先于内置面)
56
- const pluginTool = listPluginTools().get(req.toolName)
57
- if (pluginTool) {
58
- // 团队级插件开关:显式关闭的插件,其工具在该团队拒绝执行(与注入过滤同源)
59
- const channelOff = getChannelPluginsRepo().explicitFor(identity.channelId)
60
- const owner = pluginOfTool(req.toolName)
61
- if (channelOff && owner && channelOff.get(owner) === false) {
62
- return {
63
- text: `该团队未启用插件「${owner}」,${req.toolName} 不可用。可在团队设置→插件中开启。`,
64
- isError: true,
65
- }
66
- }
67
- try {
68
- return await pluginTool.handler(req.arguments ?? {}, identity)
69
- }
70
- catch (err) {
71
- return {
72
- text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
73
- isError: true,
74
- }
75
- }
76
- }
77
- const args = req.arguments ?? {}
78
-
79
- // AML 解耦门控(fail-closed,与注入过滤同源;目录隐藏不是安全边界):
80
- // - legacy:全部 twin/explore 工具拒绝(原始行为);
81
- // - trial/mpc/bayes:仅 hybrid_twin,或 aml_optimization 的 aml 模式(已绑定模型);
82
- // 探索模式给「探索阶段」明确指引而非裸报错;
83
- // - optimization_explore:仅工艺优化 Channel;
84
- // - 场景/spec/校准请求族:hybrid_twin + 训练 Channel;
85
- // - 训练族(aml_job/dataset 等):工艺优化 Channel 拒绝(边训边优被结构禁止)。
86
- const twinKind: AmlChannelProfileKind = getChannelTwinProfile(identity.channelId).profile
87
- const isTwinTool = req.toolName.startsWith('twin_') || ['mpc_optimize', 'optimization_explore'].includes(req.toolName)
88
- if (isTwinTool) {
89
- const isSceneFamily = /^twin_(scene|physics|provider|calibration)/.test(req.toolName)
90
- if (req.toolName === 'optimization_explore') {
91
- if (twinKind !== 'aml_optimization') {
92
- return { text: `optimization_explore 仅适用于工艺优化 Channel(当前 profile=${twinKind})。`, isError: true }
93
- }
94
- }
95
- else if (AML_MODEL_BACKED_TOOL_NAMES.has(req.toolName)) {
96
- const policy = modelBackedToolPolicyFor(identity.channelId)
97
- if (!policy.allowed) {
98
- return {
99
- text: policy.reason === 'EXPLORATION_MODE_NO_MODEL'
100
- ? `当前为探索模式:Channel 尚未绑定 AML 模型,${req.toolName}(孪生验证/贝叶斯寻优)暂不可用。\n请继续用 optimization_explore 做真实激励探索并积累数据;待 AML 训练出合格模型并在 Channel 设置中绑定后,本工具自动启用。`
101
- : `${req.toolName} 要求模型受控状态(hybrid_twin 或 aml_optimization 的 aml 模式;当前 ${twinKind}${policy.reason ? `/${policy.reason}` : ''})。`,
102
- isError: true,
103
- }
104
- }
105
- }
106
- else if (isSceneFamily) {
107
- if (twinKind !== 'hybrid_twin' && twinKind !== 'aml_training') {
108
- return { text: `场景/物理 spec 工具仅对 hybrid_twin 或训练 Channel(aml_training)开放(当前 profile=${twinKind})。`, isError: true }
109
- }
110
- }
111
- else if (twinKind === 'legacy') {
112
- return { text: `Channel ${identity.channelId} 未启用 Twin profile,Twin/MPC 工具拒绝执行。`, isError: true }
113
- }
114
- }
115
- if (AML_TRAINING_TOOL_NAMES.has(req.toolName) && twinKind === 'aml_optimization') {
116
- return { text: `工艺优化 Channel 不承担建模与训练(职责解耦):${req.toolName} 仅限 AML 训练 Channel 使用。请在此专注 goal 探索与模型驱动寻优。`, isError: true }
117
- }
118
-
119
- // 所有真实写入口在统一分发层再做一次服务端 fail-closed 守卫;工具目录隐藏不是安全边界。
120
- if (['dcw_control', 'param_control', 'dcw_rollback', 'recipe_update', 'recipe_rollback', 'recipe_trial', 'recipe_apply'].includes(req.toolName)) {
121
- const profile = getChannelTwinProfile(identity.channelId)
122
- const guard = twinWriteGuard(identity.channelId, profile)
123
- if (!guard.allowed) return { text: `${guard.code}: ${guard.message}`, isError: true }
124
- }
125
-
126
- // 工业工具族不依赖 workspace(只按 agentId 查绑定与节点),先于 workspace 门控执行 ——
127
- // 否则 worker 首回合前的 REST/MCP 直调(my_industrial_nodes 等)会被误拒
128
- if (INDUSTRIAL_TOOL_NAMES.has(req.toolName)) {
129
- // 权限模型 v3:运行时 grant 复核(收权即失活;绑定面是静态授权,撤销后此处拦截)
130
- const denied = await guardIndustrialToolGrant(identity, req.toolName, args)
131
- if (denied) return denied
132
- return await dispatchIndustrialTool(identity.agentId, req.toolName, args, identity.channelId)
133
- }
134
-
135
- const ws = ctx.getWorkspace()
136
- if (!ws) {
137
- return { text: 'workspace 未就绪', isError: true }
138
- }
139
-
140
- try {
141
- switch (req.toolName) {
142
- case 'report_progress':
143
- return await handleReportProgress(args, state, ws)
144
- case 'complete_task':
145
- return await handleCompleteTask(args, state, ws)
146
- case 'submit_task':
147
- return await handleSubmitTask(args, state, ws)
148
- case 'dispatch_task':
149
- return await handleDispatchTask(args, ws, identity)
150
- case 'team_grant_nodes':
151
- return await handleGrantNodes(identity, args, ws)
152
- case 'team_revoke_nodes':
153
- return await handleRevokeNodes(identity, args, ws)
154
- case 'send_message_to_agent':
155
- return await handleSendMessageToAgent(args, state, ws)
156
- case 'send_cross_channel_message':
157
- return await handleSendCrossChannelMessage(args, ws)
158
- case 'list_other_teams':
159
- return await handleListOtherTeams(ws)
160
- case 'search_other_teams_memory':
161
- return await handleSearchOtherTeamsMemory(args, ws)
162
- case 'refuse_task':
163
- return await handleRefuseTask(args, ws)
164
- case 'poll_messages':
165
- return await handlePollMessages(args, state, ws)
166
- case 'read_channel_mail':
167
- return await handleReadChannelMail(args, ws)
168
- case 'broadcast_message':
169
- return await handleBroadcastMessage(args, identity, ws)
170
- case 'list_channel_tasks':
171
- return await handleListChannelTasks(ws)
172
- case 'get_my_task_queue':
173
- return await handleGetMyTaskQueue(identity, ws)
174
- case 'get_queue_overview':
175
- return await handleGetQueueOverview(ws)
176
- case 'reassign_task':
177
- return await handleReassignTask(args, ws)
178
- case 'update_task':
179
- return await handleUpdateTask(args, ws)
180
- case 'cancel_task':
181
- return await handleCancelTask(args, ws)
182
- case 'list_team_agents':
183
- return await handleListTeamAgents(ws)
184
- case 'get_task_details':
185
- return await handleGetTaskDetails(args, ws)
186
- case 'search_memory':
187
- return await handleSearchMemory(args, ws)
188
- case 'save_memory':
189
- return await handleSaveMemory(args, ws)
190
- case 'create_team_agent':
191
- return await handleCreateTeamAgent(args, ws)
192
- case 'update_team_agent':
193
- return await handleUpdateTeamAgent(args, ws)
194
- case 'remove_team_agent':
195
- return await handleRemoveTeamAgent(args, ws)
196
-
197
- // 工业工具族的名册(实现在 tools/industrial.ts):上面 workspace 门控前已分流,
198
- // 这里保留同名路由标签,与原分发表逐条对齐(不可达,行为与原实现一致)。
199
- case 'my_industrial_nodes':
200
- case 'aml_node_catalog':
201
- case 'aml_dataset_build':
202
- case 'aml_dataset_stats':
203
- case 'aml_job_submit':
204
- case 'aml_job_status':
205
- case 'aml_job_logs':
206
- case 'aml_job_cancel':
207
- case 'aml_leaderboard':
208
- case 'aml_model_find':
209
- case 'aml_model_promote':
210
- case 'aml_model_reference':
211
- case 'twin_scene_read':
212
- case 'twin_snapshot_create':
213
- case 'twin_trial_run':
214
- case 'mpc_optimize':
215
- case 'twin_gate_evaluate':
216
- case 'twin_calibration_request':
217
- case 'dcw_control':
218
- case 'dcw_read':
219
- case 'param_control':
220
- case 'param_read':
221
- case 'daq_query':
222
- case 'daq_frames':
223
- case 'daq_export':
224
- case 'dcw_judge':
225
- case 'dcw_rollback':
226
- case 'dcw_journal':
227
- case 'ops_log':
228
- case 'recipe_log':
229
- case 'line_context':
230
- case 'recipe_versions':
231
- case 'recipe_update':
232
- case 'recipe_rollback':
233
- return await dispatchIndustrialTool(identity.agentId, req.toolName, args, identity.channelId)
234
- }
235
- return { text: `未知工具: ${req.toolName}`, isError: true }
236
- }
237
- catch (err) {
238
- return {
239
- text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
240
- isError: true,
241
- }
242
- }
243
- }
1
+ /**
2
+ * dispatchHostTool —— 全引擎唯一工具分发入口,只做前置校验 + 路由。
3
+ *
4
+ * 分发语义与原实现逐字节对齐:插件工具优先 → workspace 协作/任务/记忆面 →
5
+ * 工业工具族 → 插件兜底 → 未知工具报错。各工具族的实现体在 tools/* 中按行搬运,
6
+ * 原先闭包捕获的 (identity / state / args / ws) 一律改为显式参数。
7
+ *
8
+ * 两处必须保持的细节:
9
+ * - `return await handler(...)`:处理器在 try 内 await,异常才会落回本层的
10
+ * 「工具执行异常」结果(直接 return 未 await 的 promise 会漏掉这个 catch);
11
+ * - 工业工具族在 `getWorkspace()` 门控**之前**分流(只依赖 agentId)。
12
+ */
13
+ import { getChannelPluginsRepo } from '../../db/channel-plugins.repo'
14
+ import { getChannelTwinProfile, modelBackedToolPolicyFor, type AmlChannelProfileKind } from '../../aml/twin/channel-profile'
15
+ import { AML_TRAINING_TOOL_NAMES, AML_MODEL_BACKED_TOOL_NAMES } from './catalog'
16
+ import { twinWriteGuard } from '../../aml/twin/feature-flags'
17
+ import { listPluginTools, pluginOfTool } from '../plugin-tools'
18
+ import type { HostToolBridgeContext, HostToolCall, HostToolResult } from './types'
19
+ import { INDUSTRIAL_TOOL_NAMES, dispatchIndustrialTool } from './tools/industrial'
20
+ import { guardIndustrialToolGrant } from '../industrial/grant-guard'
21
+ import { handleCompleteTask, handleReportProgress } from './tools/progress'
22
+ import { handleCancelTask, handleDispatchTask, handleGetMyTaskQueue, handleGetTaskDetails, handleListChannelTasks, handleReassignTask, handleRefuseTask, handleSubmitTask, handleUpdateTask } from './tools/tasks'
23
+ import { handleGrantNodes, handleRevokeNodes } from './tools/team-delegation'
24
+ import { handleBroadcastMessage, handleListOtherTeams, handlePollMessages, handleReadChannelMail, handleSearchOtherTeamsMemory, handleSendCrossChannelMessage, handleSendMessageToAgent } from './tools/messaging'
25
+ import { handleSaveMemory, handleSearchMemory } from './tools/memory'
26
+ import { handleCreateTeamAgent, handleGetQueueOverview, handleListTeamAgents, handleRemoveTeamAgent, handleUpdateTeamAgent } from './tools/team'
27
+
28
+ /**
29
+ * 分发一次 host tool 调用(全引擎唯一入口):
30
+ * 插件工具优先 → workspace 面 → 工业工具族 → 插件兜底 → 未知工具。
31
+ */
32
+ /** 统一出口:所有工具结果经截断收口(单点修改,全族生效) */
33
+ export async function dispatchHostTool(ctx: HostToolBridgeContext, req: HostToolCall): Promise<HostToolResult> {
34
+ return truncateToolResult(await dispatchHostToolInner(ctx, req), req.toolName)
35
+ }
36
+
37
+ /** 工具结果统一出口上限:超长文本截断+尾注(2026-10-05 评审;export/diag 类可经 optOut 放行) */
38
+ const TOOL_RESULT_MAX_CHARS = 16_000
39
+ const TRUNCATE_OPT_OUT_TOOLS = new Set(['daq_export', 'diag_run'])
40
+ function truncateToolResult(result: HostToolResult, toolName: string): HostToolResult {
41
+ if (TRUNCATE_OPT_OUT_TOOLS.has(toolName)) return result
42
+ const text = result.text ?? ''
43
+ if (text.length <= TOOL_RESULT_MAX_CHARS) return result
44
+ const head = text.slice(0, TOOL_RESULT_MAX_CHARS)
45
+ return {
46
+ ...result,
47
+ text: head + `
48
+ [truncated: ${text.length} → ${TOOL_RESULT_MAX_CHARS} 字符;请收窄时间窗/减少点位/分页后重试]`,
49
+ }
50
+ }
51
+
52
+ async function dispatchHostToolInner(ctx: HostToolBridgeContext, req: HostToolCall): Promise<HostToolResult> {
53
+ const identity = ctx.identity
54
+ const state = ctx.state
55
+ // 插件工具分发(ctx.omp.registerTool 注册的自定义工具;不依赖 workspace,优先于内置面)
56
+ const pluginTool = listPluginTools().get(req.toolName)
57
+ if (pluginTool) {
58
+ // 团队级插件开关:显式关闭的插件,其工具在该团队拒绝执行(与注入过滤同源)
59
+ const channelOff = getChannelPluginsRepo().explicitFor(identity.channelId)
60
+ const owner = pluginOfTool(req.toolName)
61
+ if (channelOff && owner && channelOff.get(owner) === false) {
62
+ return {
63
+ text: `该团队未启用插件「${owner}」,${req.toolName} 不可用。可在团队设置→插件中开启。`,
64
+ isError: true,
65
+ }
66
+ }
67
+ try {
68
+ return await pluginTool.handler(req.arguments ?? {}, identity)
69
+ }
70
+ catch (err) {
71
+ return {
72
+ text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
73
+ isError: true,
74
+ }
75
+ }
76
+ }
77
+ const args = req.arguments ?? {}
78
+
79
+ // AML 解耦门控(fail-closed,与注入过滤同源;目录隐藏不是安全边界):
80
+ // - legacy:全部 twin/explore 工具拒绝(原始行为);
81
+ // - trial/mpc/bayes:仅 hybrid_twin,或 aml_optimization 的 aml 模式(已绑定模型);
82
+ // 探索模式给「探索阶段」明确指引而非裸报错;
83
+ // - optimization_explore:仅工艺优化 Channel;
84
+ // - 场景/spec/校准请求族:hybrid_twin + 训练 Channel;
85
+ // - 训练族(aml_job/dataset 等):工艺优化 Channel 拒绝(边训边优被结构禁止)。
86
+ const twinKind: AmlChannelProfileKind = getChannelTwinProfile(identity.channelId).profile
87
+ const isTwinTool = req.toolName.startsWith('twin_') || ['mpc_optimize', 'optimization_explore'].includes(req.toolName)
88
+ if (isTwinTool) {
89
+ const isSceneFamily = /^twin_(scene|physics|provider|calibration)/.test(req.toolName)
90
+ if (req.toolName === 'optimization_explore') {
91
+ if (twinKind !== 'aml_optimization') {
92
+ return { text: `optimization_explore 仅适用于工艺优化 Channel(当前 profile=${twinKind})。`, isError: true }
93
+ }
94
+ }
95
+ else if (AML_MODEL_BACKED_TOOL_NAMES.has(req.toolName)) {
96
+ const policy = modelBackedToolPolicyFor(identity.channelId)
97
+ if (!policy.allowed) {
98
+ return {
99
+ text: policy.reason === 'EXPLORATION_MODE_NO_MODEL'
100
+ ? `当前为探索模式:Channel 尚未绑定 AML 模型,${req.toolName}(孪生验证/贝叶斯寻优)暂不可用。\n请继续用 optimization_explore 做真实激励探索并积累数据;待 AML 训练出合格模型并在 Channel 设置中绑定后,本工具自动启用。`
101
+ : `${req.toolName} 要求模型受控状态(hybrid_twin 或 aml_optimization 的 aml 模式;当前 ${twinKind}${policy.reason ? `/${policy.reason}` : ''})。`,
102
+ isError: true,
103
+ }
104
+ }
105
+ }
106
+ else if (isSceneFamily) {
107
+ if (twinKind !== 'hybrid_twin' && twinKind !== 'aml_training') {
108
+ return { text: `场景/物理 spec 工具仅对 hybrid_twin 或训练 Channel(aml_training)开放(当前 profile=${twinKind})。`, isError: true }
109
+ }
110
+ }
111
+ else if (twinKind === 'legacy') {
112
+ return { text: `Channel ${identity.channelId} 未启用 Twin profile,Twin/MPC 工具拒绝执行。`, isError: true }
113
+ }
114
+ }
115
+ if (AML_TRAINING_TOOL_NAMES.has(req.toolName) && twinKind === 'aml_optimization') {
116
+ return { text: `工艺优化 Channel 不承担建模与训练(职责解耦):${req.toolName} 仅限 AML 训练 Channel 使用。请在此专注 goal 探索与模型驱动寻优。`, isError: true }
117
+ }
118
+
119
+ // 所有真实写入口在统一分发层再做一次服务端 fail-closed 守卫;工具目录隐藏不是安全边界。
120
+ if (['dcw_control', 'param_control', 'dcw_rollback', 'recipe_update', 'recipe_rollback', 'recipe_trial', 'recipe_apply'].includes(req.toolName)) {
121
+ const profile = getChannelTwinProfile(identity.channelId)
122
+ const guard = twinWriteGuard(identity.channelId, profile)
123
+ if (!guard.allowed) return { text: `${guard.code}: ${guard.message}`, isError: true }
124
+ }
125
+
126
+ // 工业工具族不依赖 workspace(只按 agentId 查绑定与节点),先于 workspace 门控执行 ——
127
+ // 否则 worker 首回合前的 REST/MCP 直调(my_industrial_nodes 等)会被误拒
128
+ if (INDUSTRIAL_TOOL_NAMES.has(req.toolName)) {
129
+ // 权限模型 v3:运行时 grant 复核(收权即失活;绑定面是静态授权,撤销后此处拦截)
130
+ const denied = await guardIndustrialToolGrant(identity, req.toolName, args)
131
+ if (denied) return denied
132
+ return await dispatchIndustrialTool(identity.agentId, req.toolName, args, identity.channelId)
133
+ }
134
+
135
+ const ws = ctx.getWorkspace()
136
+ if (!ws) {
137
+ return { text: 'workspace 未就绪', isError: true }
138
+ }
139
+
140
+ try {
141
+ switch (req.toolName) {
142
+ case 'report_progress':
143
+ return await handleReportProgress(args, state, ws)
144
+ case 'complete_task':
145
+ return await handleCompleteTask(args, state, ws)
146
+ case 'submit_task':
147
+ return await handleSubmitTask(args, state, ws)
148
+ case 'dispatch_task':
149
+ return await handleDispatchTask(args, ws, identity)
150
+ case 'team_grant_nodes':
151
+ return await handleGrantNodes(identity, args, ws)
152
+ case 'team_revoke_nodes':
153
+ return await handleRevokeNodes(identity, args, ws)
154
+ case 'send_message_to_agent':
155
+ return await handleSendMessageToAgent(args, state, ws)
156
+ case 'send_cross_channel_message':
157
+ return await handleSendCrossChannelMessage(args, ws)
158
+ case 'list_other_teams':
159
+ return await handleListOtherTeams(ws)
160
+ case 'search_other_teams_memory':
161
+ return await handleSearchOtherTeamsMemory(args, ws)
162
+ case 'refuse_task':
163
+ return await handleRefuseTask(args, ws)
164
+ case 'poll_messages':
165
+ return await handlePollMessages(args, state, ws)
166
+ case 'read_channel_mail':
167
+ return await handleReadChannelMail(args, ws)
168
+ case 'broadcast_message':
169
+ return await handleBroadcastMessage(args, identity, ws)
170
+ case 'list_channel_tasks':
171
+ return await handleListChannelTasks(ws)
172
+ case 'get_my_task_queue':
173
+ return await handleGetMyTaskQueue(identity, ws)
174
+ case 'get_queue_overview':
175
+ return await handleGetQueueOverview(ws)
176
+ case 'reassign_task':
177
+ return await handleReassignTask(args, ws)
178
+ case 'update_task':
179
+ return await handleUpdateTask(args, ws)
180
+ case 'cancel_task':
181
+ return await handleCancelTask(args, ws)
182
+ case 'list_team_agents':
183
+ return await handleListTeamAgents(ws)
184
+ case 'get_task_details':
185
+ return await handleGetTaskDetails(args, ws)
186
+ case 'search_memory':
187
+ return await handleSearchMemory(args, ws)
188
+ case 'save_memory':
189
+ return await handleSaveMemory(args, ws)
190
+ case 'create_team_agent':
191
+ return await handleCreateTeamAgent(args, ws)
192
+ case 'update_team_agent':
193
+ return await handleUpdateTeamAgent(args, ws)
194
+ case 'remove_team_agent':
195
+ return await handleRemoveTeamAgent(args, ws)
196
+
197
+ // 工业工具族的名册(实现在 tools/industrial.ts):上面 workspace 门控前已分流,
198
+ // 这里保留同名路由标签,与原分发表逐条对齐(不可达,行为与原实现一致)。
199
+ case 'my_industrial_nodes':
200
+ case 'aml_node_catalog':
201
+ case 'aml_dataset_build':
202
+ case 'aml_dataset_stats':
203
+ case 'aml_job_submit':
204
+ case 'aml_job_status':
205
+ case 'aml_job_logs':
206
+ case 'aml_job_cancel':
207
+ case 'aml_leaderboard':
208
+ case 'aml_model_find':
209
+ case 'aml_model_promote':
210
+ case 'aml_model_reference':
211
+ case 'twin_scene_read':
212
+ case 'twin_snapshot_create':
213
+ case 'twin_trial_run':
214
+ case 'mpc_optimize':
215
+ case 'twin_gate_evaluate':
216
+ case 'twin_calibration_request':
217
+ case 'dcw_control':
218
+ case 'param_control':
219
+ case 'daq_query':
220
+ case 'daq_frames':
221
+ case 'daq_export':
222
+ case 'dcw_judge':
223
+ case 'dcw_rollback':
224
+ case 'dcw_journal':
225
+ case 'ops_log':
226
+ case 'recipe_log':
227
+ case 'line_context':
228
+ case 'recipe_versions':
229
+ case 'recipe_update':
230
+ case 'recipe_rollback':
231
+ return await dispatchIndustrialTool(identity.agentId, req.toolName, args, identity.channelId)
232
+ }
233
+ return { text: `未知工具: ${req.toolName}`, isError: true }
234
+ }
235
+ catch (err) {
236
+ return {
237
+ text: `工具执行异常(${req.toolName}): ${err instanceof Error ? err.message : String(err)}`,
238
+ isError: true,
239
+ }
240
+ }
241
+ }