@kvell007/embed-labs-cli 0.1.0-alpha.98 → 0.1.0-alpha.99

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.
@@ -9,15 +9,38 @@ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
9
9
  const REPO_ROOT = path.resolve(SCRIPT_DIR, "../../../../..");
10
10
  const DEFAULT_TEMPLATE_ID = "taishanpi-1m-rk3566";
11
11
  const DEFAULT_HOST = "198.19.77.2";
12
- const DEFAULT_PORTS = "22,15301";
13
- const DEFAULT_STATUS_PORTS = "22";
14
- const TOOL_INTEGRITY_RELOGIN_MESSAGE = "工具完整性校验失败,请在当前电脑重新执行 embedlabs auth login --token <key>";
15
- const MCP_SERVER_VERSION = "0.2.41";
12
+ const DEFAULT_PORTS = "22";
13
+ const MCP_SERVER_VERSION_FALLBACK = "0.0.0";
16
14
  const MCP_ICON_FILES = [
17
15
  { fileName: "embed-labs-icon-dark.png", theme: "dark" },
18
16
  { fileName: "embed-labs-icon-light.png", theme: "light" }
19
17
  ];
20
18
 
19
+ function mcpServerVersion() {
20
+ const candidates = [
21
+ path.join(process.cwd(), "platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"),
22
+ path.join(process.cwd(), "platform_plugins", "opencode_plugin", "package.json"),
23
+ path.join(SCRIPT_DIR, "..", ".codex-plugin", "plugin.json"),
24
+ path.join(SCRIPT_DIR, "..", "package.json"),
25
+ path.join(SCRIPT_DIR, "..", "..", "package.json"),
26
+ path.join(REPO_ROOT, "platform_plugins", "codex_plugin", "plugins", "embed-labs", ".codex-plugin", "plugin.json"),
27
+ path.join(REPO_ROOT, "platform_plugins", "opencode_plugin", "package.json")
28
+ ];
29
+ for (const candidate of candidates) {
30
+ if (!existsSync(candidate)) continue;
31
+ try {
32
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
33
+ if (typeof parsed.version === "string" && parsed.version.trim()) {
34
+ return parsed.version.trim();
35
+ }
36
+ } catch {
37
+ // Keep server startup resilient when a packaged metadata file is missing
38
+ // or malformed; validation covers the normal release path.
39
+ }
40
+ }
41
+ return MCP_SERVER_VERSION_FALLBACK;
42
+ }
43
+
21
44
  function iconDataUri(fileName) {
22
45
  const candidates = [
23
46
  path.join(SCRIPT_DIR, "..", "assets", fileName),
@@ -40,9 +63,9 @@ function mcpServerInfo() {
40
63
  .filter(Boolean);
41
64
  return {
42
65
  name: "embed-labs",
43
- title: "Embed Labs",
44
- version: MCP_SERVER_VERSION,
45
- description: "Embed Labs MCP service for local-first embedded development boards, board knowledge, toolchain metadata, Local Bridge hardware access, and account/device-bound service logging.",
66
+ title: "EmbedLabs",
67
+ version: mcpServerVersion(),
68
+ description: "EmbedLabs local Native Agent compatibility bridge for embedded development boards, BoardPack knowledge, toolchain metadata, Local Bridge hardware access, and redacted installation-identity reporting.",
46
69
  websiteUrl: "https://embedboard.com/",
47
70
  ...(icons.length > 0 ? { icons } : {})
48
71
  };
@@ -50,8 +73,8 @@ function mcpServerInfo() {
50
73
 
51
74
  const tools = [
52
75
  {
53
- name: "dbt_auth_status",
54
- description: "Check whether the local Embed Labs CLI is signed in and bound to this computer. If not ready, return the exact login command and dashboard URL the user should use before any board/cloud tool call.",
76
+ name: "dbt_agent_status",
77
+ description: "Report local EmbedLabs Agent readiness, local model setup, installation identity, local memory, Go Local Bridge access, and redacted public-experience report state. Model provider keys stay on this computer.",
55
78
  inputSchema: {
56
79
  type: "object",
57
80
  properties: {}
@@ -59,7 +82,7 @@ const tools = [
59
82
  },
60
83
  {
61
84
  name: "dbt_current_board_status",
62
- description: "Get the latest local Embed Labs Bridge inventory snapshot for supported development boards, without server-side AI planning. Use this MCP tool directly for requests like 当前开发板状态, 开发板状态, board status, USB ECM, and SSH checks; it is not a shell command. Do not report optional control ports or unrelated local serial paths unless the user explicitly asks.",
85
+ description: "Compatibility-profile board inventory shortcut. Default clients should use embedlabs_action_run with action_id=detect_board, or embedlabs_chat for natural-language status requests. This is not a shell command. Do not report optional control ports or unrelated local serial paths unless the user explicitly asks.",
63
86
  inputSchema: {
64
87
  type: "object",
65
88
  properties: {
@@ -71,9 +94,60 @@ const tools = [
71
94
  }
72
95
  }
73
96
  },
97
+ {
98
+ name: "dbt_agent_action_schema",
99
+ description: "Return the local EmbedLabs Agent high-level action catalog or one action's machine-readable input_schema and safety contract. Use this when parameter, approval, target-selection, or must_not_infer rules are unclear. This discovery tool does not require login.",
100
+ inputSchema: {
101
+ type: "object",
102
+ properties: {
103
+ action_id: {
104
+ type: "string",
105
+ description: "Optional high-level Agent action id such as detect_board, manage_board_pack, query_board_knowledge, install_toolchain, build_project, flash_image, deploy_app, debug_board, capture_signal, submit_report, or create_fix_candidate. Omit to list all actions."
106
+ },
107
+ action: { type: "string", description: "Alias for action_id." },
108
+ include_raw: { type: "boolean" }
109
+ }
110
+ }
111
+ },
112
+ {
113
+ name: "dbt_agent_action_run",
114
+ description: "Run one EmbedLabs Agent high-level action through the local CLI Agent orchestration layer. The Agent owns local installation identity checks, memory, Go Local Bridge routing, approval gates, and redacted report creation.",
115
+ inputSchema: {
116
+ type: "object",
117
+ required: ["action_id"],
118
+ properties: {
119
+ action_id: {
120
+ type: "string",
121
+ description: "High-level Agent action id such as detect_board, manage_board_pack, query_board_knowledge, install_toolchain, build_project, flash_image, deploy_app, debug_board, capture_signal, submit_report, or create_fix_candidate."
122
+ },
123
+ action: { type: "string", description: "Alias for action_id." },
124
+ request: { type: "string", description: "Optional user request. Used as query when query is not provided." },
125
+ arguments_json: { type: "string", description: "Optional JSON object with action arguments. Keys are converted to CLI flags, for example board_id -> --board-id and approved -> --approve." },
126
+ include_raw: { type: "boolean" },
127
+ timeout_ms: { type: "number" }
128
+ }
129
+ }
130
+ },
131
+ {
132
+ name: "dbt_agent_chat",
133
+ description: "Run one natural-language EmbedLabs Native Agent chat turn through the local CLI Agent. IDE/plugin calls default to local-only deterministic Agent execution so the host IDE model is not duplicated by the user's local provider config. Set use_model=true only for explicit owner diagnostics that should invoke the local Native Agent model runtime.",
134
+ inputSchema: {
135
+ type: "object",
136
+ required: ["prompt"],
137
+ properties: {
138
+ prompt: { type: "string", description: "User request to pass to embedlabs agent chat." },
139
+ request: { type: "string", description: "Alias for prompt." },
140
+ session: { type: "string", description: "Optional local EmbedLabs chat session id to continue." },
141
+ new_session: { type: "boolean", description: "Create a new local chat session for this turn." },
142
+ use_model: { type: "boolean", description: "Opt in to the local Native Agent model runtime for this chat turn. Default false for IDE/plugin calls." },
143
+ include_raw: { type: "boolean" },
144
+ timeout_ms: { type: "number" }
145
+ }
146
+ }
147
+ },
74
148
  {
75
149
  name: "dbt_initial_image_flash",
76
- description: "Single high-level entrypoint for initialization-image flashing. Use this directly for requests like 刷写初始化镜像, 将泰山派1M RK3566的初始化镜像刷写到开发板, or RP2350/Pico initial firmware. The tool infers or accepts the board, validates installed SDK resources, calls Local Bridge flash plan/run, waits for completion when approved, and returns a compact user-facing result. Do not inspect plugin scripts, do not list tools, do not use dbt_board_debug, and do not print raw CLI help.",
150
+ description: "Compatibility-profile adapter for initialization-image flashing. Default clients must call embedlabs_action_run with action_id=flash_image and artifact_kind=initial_image or initial_firmware. This adapter maps historical requests to the Agent flash_image action, validates local SDK resources, calls Local Bridge flash plan/run, and returns a compact result.",
77
151
  inputSchema: {
78
152
  type: "object",
79
153
  properties: {
@@ -93,7 +167,7 @@ const tools = [
93
167
  },
94
168
  {
95
169
  name: "dbt_rp2350_monitor_status",
96
- description: "Read RP2350-Monitor status through Embed Labs Local Bridge on 18083, including board status, logic-analyzer caps, probe caps, pins, channels, and the browser UI URL. RP2350 Monitor supports USB serial and LAN IP/TCP connection modes. Report ui_url/browser_url for the web interface; bridge_url/monitor_bridge_url is the internal upstream and must not be opened as /rp2350-monitor/.",
170
+ description: "Read RP2350-Monitor status through EmbedLabs Local Bridge on 18083, including board status, logic-analyzer caps, probe caps, pins, channels, and the browser UI URL. RP2350 Monitor supports USB serial and LAN IP/TCP connection modes. Report ui_url/browser_url for the web interface; bridge_url/monitor_bridge_url is the internal upstream and must not be opened as /rp2350-monitor/.",
97
171
  inputSchema: {
98
172
  type: "object",
99
173
  properties: {
@@ -113,12 +187,12 @@ const tools = [
113
187
  },
114
188
  {
115
189
  name: "dbt_rp2350_capabilities",
116
- description: "Return the RP2350/Pico2 monitor capability catalog: supported operations, parameters, defaults, firmware commands, and preferred MCP tools. Call this before using unfamiliar RP2350 hardware protocols.",
190
+ description: "Full-profile diagnostics catalog for RP2350/Pico2 monitor operations, parameters, defaults, and firmware commands. Default clients should use embedlabs_action_schema/embedlabs_action_run with capture_signal, debug_board, or flash_image; this catalog is for engineering validation and fallback planning.",
117
191
  inputSchema: { type: "object", properties: { include_raw: { type: "boolean" } } }
118
192
  },
119
193
  {
120
194
  name: "dbt_rp2350_operation",
121
- description: "Run a supported RP2350 Monitor /api/operation action through the shared Local Bridge. Prefer specific tools for GPIO/UART/I2C/SPI/Wi-Fi/probe; use this for advanced catalog actions. Supports USB serial or board LAN IP/TCP transport.",
195
+ description: "Compatibility-profile engineering fallback for a supported RP2350 Monitor /api/operation action through the shared Local Bridge. Default clients should use embedlabs_action_run with action_id=capture_signal, debug_board, or another high-level Agent action first. Use this only after the high-level action cannot express an advanced catalog operation.",
122
196
  inputSchema: {
123
197
  type: "object",
124
198
  required: ["action"],
@@ -150,7 +224,7 @@ const tools = [
150
224
  },
151
225
  {
152
226
  name: "dbt_rp2350_uart_write",
153
- description: "Send text or HEX data over a configurable RP2350 UART channel through the already-running RP2350 Monitor hardware bridge. This is a direct hardware-control operation: do not search the workspace, create Pico SDK/CMake projects, or write firmware source for UART test-data requests. Accepts uart_id such as uart1 and data/data_format aliases. For closed-loop capture, prefer dbt_rp2350_logic_capture with a UART stimulus instead of calling this separately. Requires approved=true.",
227
+ description: "Compatibility-profile direct UART output fallback through the RP2350 Monitor hardware bridge. Default clients should normally call embedlabs_action_run with action_id=capture_signal and provide UART stimulus for closed-loop capture. Do not search the workspace, create Pico SDK/CMake projects, or write firmware source for UART test-data requests. Requires approved=true.",
154
228
  inputSchema: { type: "object", properties: { id: { type: "number" }, instance: { type: "number" }, uart_id: { type: "string", description: "Alias such as uart0 or uart1; normalized to instance." }, tx: { type: "number" }, rx: { type: "number" }, baud: { type: "number" }, text: { type: "string" }, hex: { type: "string" }, data: { type: "string", description: "Text or hex payload depending on data_format." }, data_format: { type: "string", description: "hex, text, string, utf8, or ascii." }, data_hex: { type: "string" }, line_ending: { type: "string" }, approved: { type: "boolean" }, approve: { type: "boolean" }, transport: { type: "string" }, local_device_id: { type: "string" }, monitor_bridge_url: { type: "string" }, serial_path: { type: "string" }, tcp: { type: "string" }, tcp_endpoint: { type: "string" }, host: { type: "string" }, port: { type: "number" }, timeout_ms: { type: "number" } } }
155
229
  },
156
230
  {
@@ -160,7 +234,7 @@ const tools = [
160
234
  },
161
235
  {
162
236
  name: "dbt_rp2350_logic_capture",
163
- description: "Capture a compact RP2350-Monitor logic-analyzer JSONL file and optionally generate UART/SPI/I2C stimulus in the same operation. This is the required entrypoint for requests like 'use UART1 to send test data and capture it with GP16/GP17': do not search the repo, create Pico SDK/CMake projects, or write firmware source. Requires approved=true because it arms local hardware. For closed-loop validation, do not call uart/spi write separately; pass stimulus here. UART/SPI/I2C/GPIO/logic functions may be combined freely when their GPIO sets do not overlap; only overlapping pins conflict. Example UART loopback: UART1 output GP8/GP9 wired to logic-analyzer inputs GP16/GP17, so call channels=[\"gpio16\",\"gpio17\"] and stimulus={\"action\":\"uart.write\",\"uart_id\":\"uart1\",\"tx\":8,\"rx\":9,...}. GP16/GP17 are analyzer inputs in that example, not UART1 output pins. Supports USB serial or board LAN IP/TCP transport.",
237
+ description: "Compatibility-profile adapter for RP2350 Monitor logic capture. Default clients should call embedlabs_action_run with action_id=capture_signal and pass analyzer pins plus optional UART/SPI/I2C stimulus. This adapter remains for compact/full engineering profiles. Do not search the repo, create Pico SDK/CMake projects, or write firmware source. Requires approved=true because it arms local hardware. UART/SPI/I2C/GPIO/logic functions may be combined when GPIO sets do not overlap. Example UART loopback: UART1 output GP8/GP9 wired to analyzer inputs GP16/GP17. GP16/GP17 are analyzer inputs in that example, not UART1 output pins.",
164
238
  inputSchema: {
165
239
  type: "object",
166
240
  properties: {
@@ -210,7 +284,7 @@ const tools = [
210
284
  },
211
285
  {
212
286
  name: "dbt_rp2350_logic_decode",
213
- description: "Decode a local RP2350-Monitor logic JSONL capture without touching hardware. Supported decoders: summary, bursts, edges, uart, spi, and structure/auto/unknown. The result includes summary_for_user, decoded_preview, protocol_data, and analysis so the AI can report actual sampled data; use structure/unknown when the protocol is not known.",
287
+ description: "Decode a local RP2350-Monitor logic JSONL capture without touching hardware. Supported decoders: summary, bursts, edges, uart, spi, and structure/auto/unknown. The result includes summary_for_user, decoded_preview, protocol_data, and analysis so the AI can report actual sampled data; use structure/unknown when the protocol is not known. A 0-byte UART/SPI decode is a capture/wiring/decoder-parameter result, not firmware corruption; do not reflash, reboot, or enter BOOTSEL unless the user explicitly asks for firmware recovery.",
214
288
  inputSchema: {
215
289
  type: "object",
216
290
  properties: {
@@ -237,7 +311,7 @@ const tools = [
237
311
  },
238
312
  {
239
313
  name: "dbt_rp2350_spi_transfer",
240
- description: "Send test bytes through a configurable RP2350 SPI channel via Embed Labs Local Bridge. Supports USB serial or board LAN IP/TCP transport. Pin choices must match the firmware pin map for the selected SPI instance; if pins are omitted, the bridge uses the documented SPI0 example SCK GP2, MOSI GP3, MISO GP0, CS GP1. Use this directly for requests like 通过默认SPI接口发送测试数据; mutating bus output requires approve=true.",
314
+ description: "Compatibility-profile direct SPI output fallback through EmbedLabs Local Bridge. Default clients should normally use embedlabs_action_run with action_id=capture_signal and SPI stimulus for test-data capture. Pin choices must match the firmware pin map; mutating bus output requires approve=true.",
241
315
  inputSchema: {
242
316
  type: "object",
243
317
  properties: {
@@ -280,7 +354,7 @@ const tools = [
280
354
  },
281
355
  {
282
356
  name: "dbt_rp2350_monitor_command",
283
- description: "Run an allowlisted RP2350-Monitor command through Embed Labs Local Bridge. Supports USB serial or board LAN IP/TCP transport. Mutating commands require approve=true.",
357
+ description: "Run an allowlisted RP2350-Monitor command through EmbedLabs Local Bridge. Supports USB serial or board LAN IP/TCP transport. Mutating commands require approve=true.",
284
358
  inputSchema: {
285
359
  type: "object",
286
360
  required: ["command"],
@@ -304,19 +378,20 @@ const tools = [
304
378
  },
305
379
  {
306
380
  name: "dbt_cloud_status",
307
- description: "Discover Embed Labs Cloud supported boards, knowledge file index, plugin/service metadata, and local toolchain download metadata. Server-side user workspaces, quota/billing tools, and Docker builds are disabled by default.",
381
+ description: "Compatibility-profile board metadata adapter. It routes through local Agent board actions for supported boards, knowledge, and toolchain metadata. It does not expose model/service catalogs; default clients should use embedlabs_action_schema/embedlabs_action_run with manage_board_pack, query_board_knowledge, or install_toolchain.",
308
382
  inputSchema: {
309
383
  type: "object",
310
384
  properties: {
311
385
  template_id: { type: "string" },
312
386
  template: { type: "string" },
387
+ query: { type: "string" },
313
388
  include_raw: { type: "boolean" }
314
389
  }
315
390
  }
316
391
  },
317
392
  {
318
393
  name: "dbt_board_knowledge_search",
319
- description: "Search bounded Embed Labs server-side board knowledge snippets for pinout, UART/serial, GPIO, build, flash, SDK, and workflow questions. For supported boards, use this instead of web search; if no snippet matches, report the board-pack gap.",
394
+ description: "Search bounded EmbedLabs server-side board knowledge snippets for pinout, UART/serial, GPIO, build, flash, SDK, and workflow questions. For supported boards, use this instead of web search; if no snippet matches, report the board-pack gap.",
320
395
  inputSchema: {
321
396
  type: "object",
322
397
  required: ["query"],
@@ -330,66 +405,27 @@ const tools = [
330
405
  },
331
406
  {
332
407
  name: "dbt_supported_boards",
333
- description: "List currently supported Embed Labs development boards across Cloud board registry and local toolchain environments. Use this directly for 当前支持哪些开发板 / supported boards questions.",
408
+ description: "Compatibility-profile supported-board shortcut. Default clients should call embedlabs_action_run with action_id=manage_board_pack and operation=list for supported-board questions. This adapter returns BoardPack catalog summaries and local toolchain status hints.",
334
409
  inputSchema: {
335
410
  type: "object",
336
411
  properties: {}
337
412
  }
338
413
  },
339
- {
340
- name: "dbt_task_status",
341
- description: "Read an Embed Labs Cloud task status, including output_tail and artifact metadata.",
342
- inputSchema: {
343
- type: "object",
344
- required: ["task_id"],
345
- properties: {
346
- task_id: { type: "string" }
347
- }
348
- }
349
- },
350
- {
351
- name: "dbt_task_log_download",
352
- description: "Download the first log artifact from a Cloud task.",
353
- inputSchema: {
354
- type: "object",
355
- required: ["task_id"],
356
- properties: {
357
- task_id: { type: "string" },
358
- output_path: { type: "string" }
359
- }
360
- }
361
- },
362
- {
363
- name: "dbt_artifact_download",
364
- description: "Download an Embed Labs Cloud artifact.",
365
- inputSchema: {
366
- type: "object",
367
- required: ["artifact_id", "output_path"],
368
- properties: {
369
- artifact_id: { type: "string" },
370
- output_path: { type: "string" }
371
- }
372
- }
373
- },
374
414
  {
375
415
  name: "dbt_board_debug",
376
- description: "Run managed-model local-board debug/deploy planning through Cloud API plus Local Bridge. Source edits and compilation stay in the user's local environment. Do not use this for initialization image flashing; use dbt_initial_image_flash instead.",
416
+ description: "High-level local board diagnostics/deploy handoff. Routes through Native Agent debug_board or deploy_app actions; source edits, compilation, and hardware access stay local. Do not use this for initialization image flashing; use embedlabs_action_run with action_id=flash_image and the appropriate artifact_kind instead.",
377
417
  inputSchema: {
378
418
  type: "object",
379
- required: ["prompt"],
380
419
  properties: {
381
- account_id: { type: "string" },
382
- workspace_id: { type: "string" },
383
420
  prompt: { type: "string" },
384
- provider: { type: "string" },
385
- model: { type: "string" },
386
421
  host: { type: "string" },
387
422
  ports: { type: "string" },
388
423
  artifact_path: { type: "string" },
389
- artifact_id: { type: "string" },
390
- artifact_task_id: { type: "string" },
391
- artifact_output: { type: "string" },
392
424
  remote_path: { type: "string" },
425
+ remote_dir: { type: "string" },
426
+ remote_file: { type: "string" },
427
+ wrapper_script: { type: "string" },
428
+ run_script: { type: "string" },
393
429
  run_command: { type: "string" },
394
430
  approve: { type: "boolean" },
395
431
  run: { type: "boolean" }
@@ -398,7 +434,7 @@ const tools = [
398
434
  },
399
435
  {
400
436
  name: "dbt_boot_logo_update",
401
- description: "Single high-level local boot-logo action. Use this directly for 启动logo修改/刷写启动Logo/logo替换. Pass only the image path and optional approval; the tool validates the local SDK, converts/resizes the image locally, repacks the local Rockchip resource image, patches the local boot.img, and returns a compact result. Do not call dbt_update_logo and dbt_compose_boot_logo manually unless this tool reports a product gap.",
437
+ description: "Compatibility-profile adapter for startup-logo work. Default clients must call embedlabs_action_run with action_id=flash_image and artifact_kind=boot_logo. This adapter validates the local SDK, converts/resizes the image locally, repacks the Rockchip resource image, patches boot.img, and returns a compact result.",
402
438
  inputSchema: {
403
439
  type: "object",
404
440
  required: ["logo_path"],
@@ -423,33 +459,9 @@ const tools = [
423
459
  }
424
460
  }
425
461
  },
426
- {
427
- name: "dbt_update_logo",
428
- description: "Ask the server to generate a small free boot-logo package for later local image merge and approved flashing. This does not allocate a paid server workspace.",
429
- inputSchema: {
430
- type: "object",
431
- required: ["logo_path"],
432
- properties: {
433
- account_id: { type: "string" },
434
- account: { type: "string" },
435
- project_id: { type: "string" },
436
- project: { type: "string" },
437
- board_id: { type: "string" },
438
- board: { type: "string" },
439
- variant_id: { type: "string" },
440
- variant: { type: "string" },
441
- logo_path: { type: "string" },
442
- kernel_logo_path: { type: "string" },
443
- output_path: { type: "string" },
444
- output: { type: "string" },
445
- rotate: { type: "string" },
446
- scale: { type: "string" }
447
- }
448
- }
449
- },
450
462
  {
451
463
  name: "dbt_compose_boot_logo",
452
- description: "Compose a downloaded server boot-logo package with a local base image using the cross-platform Embed CLI local composer. The result reports whether it is ready for approved flashing.",
464
+ description: "Compose a local boot-logo package with a local base image using the cross-platform Embed CLI local composer. The result reports whether it is ready for approved flashing.",
453
465
  inputSchema: {
454
466
  type: "object",
455
467
  required: ["package_path", "base_image_path", "output_path"],
@@ -514,7 +526,7 @@ const tools = [
514
526
  },
515
527
  {
516
528
  name: "dbt_local_toolchain_current",
517
- description: "Show the currently selected local development-board environment in the Embed Labs CLI registry.",
529
+ description: "Show the currently selected local development-board environment in the EmbedLabs CLI registry.",
518
530
  inputSchema: {
519
531
  type: "object",
520
532
  properties: {
@@ -525,7 +537,7 @@ const tools = [
525
537
  },
526
538
  {
527
539
  name: "dbt_local_toolchain_install",
528
- description: "Download, install, or repair a local development-board environment, such as TaishanPi LLVM/Qt support or Pico2W/RP2350 Monitor runtime support. If validation reports missing components for the user's requested workflow, call this with the matching board_id and mode to overlay the missing packages.",
540
+ description: "Compatibility-profile adapter for local SDK/toolchain install, update, or repair. Default clients should call embedlabs_action_run with action_id=install_toolchain and operation=install/update instead of selecting this tool directly.",
529
541
  inputSchema: {
530
542
  type: "object",
531
543
  properties: {
@@ -541,7 +553,8 @@ const tools = [
541
553
  install_root: { type: "string" },
542
554
  installRoot: { type: "string" },
543
555
  mode: { type: "string", enum: ["minimal", "runtime", "compile", "qt", "firmware", "full", "images"] },
544
- force: { type: "boolean" }
556
+ force: { type: "boolean" },
557
+ include_raw: { type: "boolean" }
545
558
  }
546
559
  }
547
560
  },
@@ -561,7 +574,7 @@ const tools = [
561
574
  },
562
575
  {
563
576
  name: "dbt_local_toolchain_validate",
564
- description: "Validate a local development-board environment release, such as TaishanPi LLVM/Qt support or Pico2W/RP2350 Monitor runtime support. The response includes missing_groups, summary_for_user, and repair_command; for TaishanPi Qt development, an ok=false result should be repaired with dbt_local_toolchain_install mode=qt before one-click cross compile or deploy.",
577
+ description: "Compatibility-profile validator for a local development-board environment release. Default clients should use embedlabs_action_run with action_id=install_toolchain and operation=list/install/update for repair decisions; this tool returns a compact validation summary and high-level repair command.",
565
578
  inputSchema: {
566
579
  type: "object",
567
580
  properties: {
@@ -576,7 +589,7 @@ const tools = [
576
589
  },
577
590
  {
578
591
  name: "dbt_taishanpi_initial_image_flash",
579
- description: "Install/repair the TaishanPi 1M-RK3566 images SDK, resolve images/current automatically, plan initialization image flashing through Embed Labs Local Bridge, and optionally run it when approved=true. Use this directly for TaishanPi requests like 烧写初始化镜像, 刷写1M RK3566初始化镜像, Loader/Maskrom 初始化镜像刷写. Do not use dbt_board_debug for this flow and do not ask the user to provide image_dir when the SDK is installed.",
592
+ description: "Compatibility-profile TaishanPi initial-image adapter. Default clients must call embedlabs_action_run with action_id=flash_image and artifact_kind=initial_image. This adapter installs/repairs the images SDK, resolves images/current automatically, plans flashing through Local Bridge, and optionally runs it when approved=true.",
580
593
  inputSchema: {
581
594
  type: "object",
582
595
  properties: {
@@ -598,7 +611,7 @@ const tools = [
598
611
  },
599
612
  {
600
613
  name: "dbt_rp2350_monitor_firmware_flash",
601
- description: "Download/install the optional RP2350 Monitor firmware package, locate rp2350_monitor.uf2, plan UF2 flashing through Embed Labs Local Bridge, and optionally flash it when approved=true. Use this only when the user explicitly asks for RP2350 Monitor, logic-analyzer, hardware-control, or Monitor firmware. For 初始化镜像/initial firmware requests use dbt_rp2350_initial_firmware_flash instead. Initialized boards use the Local Bridge inventory and picotool runtime handoff when available.",
614
+ description: "Download/install the optional RP2350 Monitor firmware package, locate rp2350_monitor.uf2, plan UF2 flashing through EmbedLabs Local Bridge, and optionally flash it when approved=true. Use this only when the user explicitly asks for RP2350 Monitor, logic-analyzer, hardware-control, or Monitor firmware. For 初始化镜像/initial firmware requests use embedlabs_action_run with action_id=flash_image and artifact_kind=initial_firmware instead. Initialized boards use the Local Bridge inventory and picotool runtime handoff when available.",
602
615
  inputSchema: {
603
616
  type: "object",
604
617
  properties: {
@@ -619,7 +632,7 @@ const tools = [
619
632
  },
620
633
  {
621
634
  name: "dbt_rp2350_initial_firmware_flash",
622
- description: "Download/install the RP2350 board initialization firmware package, locate the board-specific initial.uf2, plan UF2 flashing through Embed Labs Local Bridge, and optionally flash it when approved=true. Use this directly for user requests like 烧写初始化镜像, 刷初始化镜像, initial firmware, or initialization UF2. Do not ask the user to choose Monitor vs initial firmware for those requests. If Local Bridge inventory already shows an initialized RP2350 runtime, the flash plan should use the picotool runtime handoff after approval.",
635
+ description: "Compatibility-profile RP2350 initial-firmware adapter. Default clients must call embedlabs_action_run with action_id=flash_image and artifact_kind=initial_firmware. This adapter installs the board initialization firmware package, locates initial.uf2, plans UF2 flashing through Local Bridge, and optionally flashes it when approved=true.",
623
636
  inputSchema: {
624
637
  type: "object",
625
638
  properties: {
@@ -640,7 +653,7 @@ const tools = [
640
653
  },
641
654
  {
642
655
  name: "dbt_plugin_update_check",
643
- description: "Check the published Embed Labs Codex/OpenCode/Trae plugin release and report whether the local plugins should be updated.",
656
+ description: "Check the published EmbedLabs Codex/OpenCode/Trae plugin release and local plugin integrity, including repair_required, local_integrity, cache_stale, and release_package_stale diagnostics for same-version plugin repair.",
644
657
  inputSchema: {
645
658
  type: "object",
646
659
  properties: {
@@ -658,12 +671,12 @@ const tools = [
658
671
  },
659
672
  {
660
673
  name: "dbt_plugin_update",
661
- description: "Update the local Embed Labs Codex/OpenCode/Trae plugin installation from the published release. Restart the updated client after this finishes.",
674
+ description: "Update the local EmbedLabs Codex/OpenCode/Trae plugin installation from the published release. Omit target_plugin to update all retained IDE integrations. Restart the updated client after this finishes.",
662
675
  inputSchema: {
663
676
  type: "object",
664
677
  properties: {
665
- target_plugin: { type: "string", enum: ["codex", "opencode", "trae", "all"] },
666
- plugin: { type: "string", enum: ["codex", "opencode", "trae", "all"] },
678
+ target_plugin: { type: "string", enum: ["codex", "opencode", "trae", "all"], description: "Optional target client. Defaults to all, which updates Codex, OpenCode, and Trae together." },
679
+ plugin: { type: "string", enum: ["codex", "opencode", "trae", "all"], description: "Alias for target_plugin. Defaults to all when omitted." },
667
680
  release_url: { type: "string" },
668
681
  releaseUrl: { type: "string" },
669
682
  target: { type: "string" },
@@ -678,12 +691,11 @@ const tools = [
678
691
  },
679
692
  {
680
693
  name: "dbt_local_compile",
681
- description: "Compile a local TaishanPi source file with the local Embed Labs LLVM toolchain. The user must be signed in with an Embed Labs token for account attribution.",
694
+ description: "Compile a local TaishanPi source file with the local EmbedLabs LLVM toolchain. This runs on the user's computer through local toolchain metadata and the local installation identity.",
682
695
  inputSchema: {
683
696
  type: "object",
684
697
  required: ["source_path", "output_path"],
685
698
  properties: {
686
- account_id: { type: "string" },
687
699
  source_path: { type: "string" },
688
700
  output_path: { type: "string" },
689
701
  release_root: { type: "string" }
@@ -692,12 +704,11 @@ const tools = [
692
704
  },
693
705
  {
694
706
  name: "dbt_local_qt_smoke",
695
- description: "Build a local TaishanPi Qt/CMake application with the local Embed Labs LLVM/Qt toolchain. Defaults to the built-in smoke app, but for real demos pass source_path and target_name. Use official board-pack examples such as taishanpi/examples/modbus-loop-demo instead of ad hoc projects. The user must be signed in with an Embed Labs token for account attribution.",
707
+ description: "Build a local TaishanPi Qt/CMake application with the local EmbedLabs LLVM/Qt toolchain. Defaults to the built-in smoke app, but for real demos pass source_path and target_name. Use official board-pack examples such as taishanpi/examples/modbus-loop-demo instead of ad hoc projects. This runs on the user's computer.",
696
708
  inputSchema: {
697
709
  type: "object",
698
710
  required: ["build_dir"],
699
711
  properties: {
700
- account_id: { type: "string" },
701
712
  source_path: { type: "string" },
702
713
  build_dir: { type: "string" },
703
714
  target_name: { type: "string" },
@@ -707,7 +718,7 @@ const tools = [
707
718
  },
708
719
  {
709
720
  name: "dbt_taishanpi_qt_app_workflow",
710
- description: "Single high-level TaishanPi Qt application workflow. Use this directly for Qt/QtQuick app development, Modbus/UART demos, cross compilation, deployment, and runtime checks. The tool validates/repairs the Qt SDK, selects a board-pack template when source_path is omitted, builds with the local LLVM/Qt toolchain, and can deploy/run after approval. Do not search scripts, print CLI help, or create ad hoc serial backends.",
721
+ description: "Compatibility-profile TaishanPi Qt application adapter. Default clients must call embedlabs_action_run with action_id=deploy_app for Qt/QtQuick app development, demos, cross compilation, deployment, and runtime checks. This adapter validates/repairs the Qt SDK, selects a BoardPack template when source_path is omitted, builds with the local LLVM/Qt toolchain, and can deploy/run after approval.",
711
722
  inputSchema: {
712
723
  type: "object",
713
724
  properties: {
@@ -719,6 +730,10 @@ const tools = [
719
730
  local_device_id: { type: "string" },
720
731
  host: { type: "string" },
721
732
  remote_path: { type: "string" },
733
+ remote_dir: { type: "string" },
734
+ remote_file: { type: "string" },
735
+ wrapper_script: { type: "string" },
736
+ run_script: { type: "string" },
722
737
  deploy: { type: "boolean" },
723
738
  run: { type: "boolean" },
724
739
  approve: { type: "boolean" },
@@ -729,14 +744,14 @@ const tools = [
729
744
  },
730
745
  {
731
746
  name: "dbttool",
732
- description: "Generic Embed Labs action router. Use this when a board/protocol-specific action is not visible as a top-level MCP tool. For RP2350/Pico Monitor hardware-control requests such as UART/SPI/I2C/GPIO test output, logic-analyzer capture, Wi-Fi, probe, or firmware flashing, call the matching rp2350-* action directly through this router; do not search the workspace, create Pico SDK/CMake projects, write main.c, scrape the monitor UI, or use shell serial/curl commands. For closed-loop protocol validation, call action=rp2350-logic-capture once with a stimulus object, then action=rp2350-logic-decode. Action groups: catalog discovery (supported-boards, cloud-status, knowledge), generic board control (board-reboot, board-bootloader, image-flash/image-update), local environment (local-toolchain-list/install/uninstall/validate/current, local-compile, local-qt-smoke), RP2350 hardware control (rp2350-capabilities/status/gpio-read/gpio-write/uart-write/i2c-transfer/spi-transfer/logic-capture/logic-decode/wifi-manage/probe-debug/monitor-firmware-flash/initial-firmware-flash), TaishanPi workflows (taishanpi-qt-workflow, boot-logo-update, taishanpi-initial-image-flash), and task/artifact helpers. Server workspace and Docker build actions are disabled in the local-first product model.",
747
+ description: "Generic EmbedLabs compatibility router for engineering fallback. Default user requests must go through embedlabs_chat or embedlabs_action_run with high-level actions such as detect_board, query_board_knowledge, install_toolchain, build_project, flash_image, deploy_app, debug_board, capture_signal, manage_board_pack, submit_report, and create_fix_candidate. Use board/protocol-specific rp2350-* or taishanpi-* actions only in compact/full engineering profiles after the high-level Agent action cannot express the request or when validating a specific adapter. For closed-loop protocol validation, normally call the high-level capture_signal action; use rp2350-logic-capture/decode only as an engineering fallback. Do not scan the user's project to invent firmware, create Pico SDK/CMake projects, write main.c, scrape the monitor UI, or use shell serial/curl commands.",
733
748
  inputSchema: {
734
749
  type: "object",
735
750
  required: ["action"],
736
751
  properties: {
737
752
  action: {
738
753
  type: "string",
739
- description: "Compact action id such as supported-boards, status, cloud-status, board-reboot, board-bootloader, image-flash, rp2350-capabilities, rp2350-spi-transfer, rp2350-logic-capture, boot-logo-update, local-toolchain-install, local-compile, local-qt-smoke, taishanpi-qt-workflow, task-status, or artifact-download."
754
+ description: "Compact action id such as supported-boards, status, cloud-status, board-reboot, board-bootloader, image-flash, rp2350-capabilities, rp2350-spi-transfer, rp2350-logic-capture, boot-logo-update, local-toolchain-install, local-compile, local-qt-smoke, or taishanpi-qt-workflow."
740
755
  },
741
756
  request: { type: "string", description: "Optional natural-language request for high-level workflows." },
742
757
  arguments_json: { type: "string", description: "Optional JSON object string passed as the selected action arguments." }
@@ -745,12 +760,26 @@ const tools = [
745
760
  }
746
761
  ];
747
762
 
748
- const DEFAULT_EXPOSED_MCP_TOOL_NAMES = new Set([
749
- "dbt_auth_status",
763
+ const AGENT_MCP_TOOL_ALIASES = new Map([
764
+ ["embedlabs_agent_status", "dbt_agent_status"],
765
+ ["embedlabs_action_schema", "dbt_agent_action_schema"],
766
+ ["embedlabs_action_run", "dbt_agent_action_run"],
767
+ ["embedlabs_chat", "dbt_agent_chat"],
768
+ ["embedlabs_plugin_update_check", "dbt_plugin_update_check"],
769
+ ["embedlabs_plugin_update", "dbt_plugin_update"]
770
+ ]);
771
+
772
+ const AGENT_EXPOSED_MCP_TOOL_NAMES = new Set(AGENT_MCP_TOOL_ALIASES.keys());
773
+
774
+ const COMPACT_EXPOSED_MCP_TOOL_NAMES = new Set([
775
+ "dbt_agent_status",
750
776
  "dbt_current_board_status",
777
+ "dbt_agent_action_schema",
778
+ "dbt_agent_action_run",
779
+ "dbt_agent_chat",
751
780
  "dbt_supported_boards",
752
- "dbt_cloud_status",
753
781
  "dbt_board_knowledge_search",
782
+ "dbt_cloud_status",
754
783
  "dbt_initial_image_flash",
755
784
  "dbt_board_debug",
756
785
  "dbt_boot_logo_update",
@@ -765,16 +794,123 @@ const DEFAULT_EXPOSED_MCP_TOOL_NAMES = new Set([
765
794
  "dbttool"
766
795
  ]);
767
796
 
797
+ const FULL_EXPOSED_MCP_TOOL_NAMES = new Set([
798
+ ...COMPACT_EXPOSED_MCP_TOOL_NAMES,
799
+ "dbt_rp2350_monitor_status",
800
+ "dbt_rp2350_capabilities",
801
+ "dbt_rp2350_operation",
802
+ "dbt_rp2350_gpio_read",
803
+ "dbt_rp2350_gpio_write",
804
+ "dbt_rp2350_uart_write",
805
+ "dbt_rp2350_i2c_transfer",
806
+ "dbt_rp2350_logic_capture",
807
+ "dbt_rp2350_logic_decode",
808
+ "dbt_rp2350_spi_transfer",
809
+ "dbt_rp2350_wifi_manage",
810
+ "dbt_rp2350_probe_debug",
811
+ "dbt_rp2350_monitor_command",
812
+ "dbt_taishanpi_initial_image_flash",
813
+ "dbt_rp2350_monitor_firmware_flash",
814
+ "dbt_rp2350_initial_firmware_flash",
815
+ "dbt_local_compile",
816
+ "dbt_local_qt_smoke"
817
+ ]);
818
+
768
819
  function mcpToolProfile() {
769
820
  const profile = asString(process.env.EMBEDLABS_MCP_TOOL_PROFILE).toLowerCase();
770
821
  if (profile) return profile;
771
822
  if (["1", "true", "yes", "on"].includes(asString(process.env.EMBEDLABS_MCP_FULL_TOOLS).toLowerCase())) return "full";
772
- return "default";
823
+ return "agent";
773
824
  }
774
825
 
775
826
  function exposedMcpTools() {
776
- if (mcpToolProfile() === "full") return tools;
777
- return tools.filter((tool) => DEFAULT_EXPOSED_MCP_TOOL_NAMES.has(tool.name));
827
+ const profile = mcpToolProfile();
828
+ if (profile === "full") return tools.filter((tool) => FULL_EXPOSED_MCP_TOOL_NAMES.has(tool.name));
829
+ if (profile === "compact" || profile === "compat") return tools.filter((tool) => COMPACT_EXPOSED_MCP_TOOL_NAMES.has(tool.name));
830
+ return Array.from(AGENT_MCP_TOOL_ALIASES.entries()).map(([publicName, legacyName]) => {
831
+ const tool = tools.find((item) => item.name === legacyName);
832
+ return {
833
+ ...tool,
834
+ name: publicName,
835
+ description: agentProfileToolDescription(tool.description),
836
+ inputSchema: sanitizeAgentProfileInputSchema(tool.inputSchema)
837
+ };
838
+ });
839
+ }
840
+
841
+ function isMcpToolExposed(name) {
842
+ return exposedMcpTools().some((tool) => tool.name === name);
843
+ }
844
+
845
+ function canonicalMcpToolName(name) {
846
+ return AGENT_MCP_TOOL_ALIASES.get(name) || name;
847
+ }
848
+
849
+ function agentProfileToolDescription(description) {
850
+ return asString(description)
851
+ .replaceAll("dbt_agent_status", "embedlabs_agent_status")
852
+ .replaceAll("dbt_agent_action_schema", "embedlabs_action_schema")
853
+ .replaceAll("dbt_agent_action_run", "embedlabs_action_run")
854
+ .replaceAll("dbt_agent_chat", "embedlabs_chat")
855
+ .replaceAll("dbt_plugin_update_check", "embedlabs_plugin_update_check")
856
+ .replaceAll("dbt_plugin_update", "embedlabs_plugin_update")
857
+ .replaceAll("low-level MCP tools", "implementation-level tools");
858
+ }
859
+
860
+ function sanitizeAgentProfileInputSchema(schema) {
861
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return schema;
862
+ const sanitized = { ...schema };
863
+ if (sanitized.properties && typeof sanitized.properties === "object" && !Array.isArray(sanitized.properties)) {
864
+ const { include_raw: _includeRaw, ...properties } = sanitized.properties;
865
+ sanitized.properties = properties;
866
+ }
867
+ return sanitized;
868
+ }
869
+
870
+ function sanitizeAgentProfileArgs(args) {
871
+ if (!args || typeof args !== "object" || Array.isArray(args)) return {};
872
+ const { include_raw: _includeRaw, includeRaw: _includeRawCamel, ...rest } = args;
873
+ return rest;
874
+ }
875
+
876
+ function sanitizeMcpPublicValue(value, key = "") {
877
+ if (typeof value === "string") {
878
+ if (isLocalPathLikePublicKey(key)) return "[local-path]";
879
+ return summarizePublicText(value);
880
+ }
881
+ if (Array.isArray(value)) {
882
+ return value.map((item) => sanitizeMcpPublicValue(item, key));
883
+ }
884
+ if (!value || typeof value !== "object") return value;
885
+ return Object.fromEntries(
886
+ Object.entries(value)
887
+ .map(([entryKey, entryValue]) => [entryKey, sanitizeMcpPublicValue(entryValue, entryKey)])
888
+ );
889
+ }
890
+
891
+ function isLocalPathLikePublicKey(key) {
892
+ return [
893
+ "path",
894
+ "file",
895
+ "dir",
896
+ "directory",
897
+ "target",
898
+ "target_path",
899
+ "checked_path",
900
+ "source_path",
901
+ "package_path",
902
+ "marketplace_path",
903
+ "config_path",
904
+ "cache_path",
905
+ "report_path",
906
+ "reports_dir",
907
+ "session_path",
908
+ "memory_path",
909
+ "output_path",
910
+ "local_path",
911
+ "local_file",
912
+ "log_path"
913
+ ].includes(asString(key).toLowerCase());
778
914
  }
779
915
 
780
916
  function asString(value) {
@@ -840,13 +976,167 @@ function pushOptional(args, name, value) {
840
976
  if (text) args.push(`--${name}`, text);
841
977
  }
842
978
 
979
+ function toCliFlagName(name) {
980
+ return asString(name).replace(/[A-Z]/g, (item) => `-${item.toLowerCase()}`).replaceAll("_", "-");
981
+ }
982
+
983
+ const AGENT_ACTION_CLI_FLAGS = new Set([
984
+ "board",
985
+ "board-id",
986
+ "template",
987
+ "template-id",
988
+ "query",
989
+ "title",
990
+ "description",
991
+ "optimization-item-id",
992
+ "item-id",
993
+ "branch",
994
+ "branch-name",
995
+ "test-scope",
996
+ "notes",
997
+ "category",
998
+ "severity",
999
+ "component",
1000
+ "device",
1001
+ "device-id",
1002
+ "channel",
1003
+ "metadata-root",
1004
+ "source-url",
1005
+ "source-release-root",
1006
+ "install-root",
1007
+ "operation",
1008
+ "op",
1009
+ "mode",
1010
+ "force",
1011
+ "update",
1012
+ "uninstall",
1013
+ "remove",
1014
+ "list",
1015
+ "source",
1016
+ "output",
1017
+ "artifact",
1018
+ "artifact-path",
1019
+ "image",
1020
+ "image-path",
1021
+ "image-dir",
1022
+ "artifact-kind",
1023
+ "logo",
1024
+ "logo-path",
1025
+ "kernel-logo",
1026
+ "kernel-logo-path",
1027
+ "base-image",
1028
+ "base-image-path",
1029
+ "output-image",
1030
+ "output-image-path",
1031
+ "output-resource",
1032
+ "output-resource-path",
1033
+ "target-volume",
1034
+ "local-device-id",
1035
+ "variant",
1036
+ "hardware-profile",
1037
+ "remote-path",
1038
+ "remote-dir",
1039
+ "remote-file",
1040
+ "wrapper-script",
1041
+ "run-script",
1042
+ "run-command",
1043
+ "run",
1044
+ "timeout",
1045
+ "user",
1046
+ "partition",
1047
+ "partitions",
1048
+ "release-root",
1049
+ "profile",
1050
+ "build-profile",
1051
+ "approve",
1052
+ "host",
1053
+ "port",
1054
+ "serial-path",
1055
+ "monitor-bridge-url",
1056
+ "pin-base",
1057
+ "pin-count",
1058
+ "sample-rate",
1059
+ "sample-rate-hz",
1060
+ "samples",
1061
+ "output-path",
1062
+ "pull",
1063
+ "wait-timeout-ms",
1064
+ "read-timeout-ms",
1065
+ "pre-samples",
1066
+ "post-samples",
1067
+ "search-samples",
1068
+ "trigger-pin",
1069
+ "trigger-mode",
1070
+ "trigger-level",
1071
+ "trigger-mask",
1072
+ "trigger-value",
1073
+ "channel-names",
1074
+ "protocol",
1075
+ "decoder",
1076
+ "stimulus",
1077
+ "stimulus-json",
1078
+ "input-path",
1079
+ "capture-id",
1080
+ "swclk",
1081
+ "swdio",
1082
+ "reset"
1083
+ ]);
1084
+
1085
+ function appendAgentActionFlag(command, name, value) {
1086
+ const flag = toCliFlagName(name === "approved" ? "approve" : name);
1087
+ if (!flag) return;
1088
+ if (!AGENT_ACTION_CLI_FLAGS.has(flag)) return;
1089
+ if (value === undefined || value === null || value === "") return;
1090
+ if (typeof value === "boolean") {
1091
+ if (value) command.push(`--${flag}`);
1092
+ return;
1093
+ }
1094
+ if (Array.isArray(value)) {
1095
+ command.push(`--${flag}`, value.map((item) => asCliValue(item) || JSON.stringify(item)).join(","));
1096
+ return;
1097
+ }
1098
+ if (typeof value === "object") {
1099
+ command.push(`--${flag}`, JSON.stringify(value));
1100
+ return;
1101
+ }
1102
+ const text = asCliValue(value);
1103
+ if (text) command.push(`--${flag}`, text);
1104
+ }
1105
+
1106
+ function agentActionPayload(args) {
1107
+ const payload = objectValue(args.arguments_json) || {};
1108
+ for (const [key, value] of Object.entries(args || {})) {
1109
+ if (["action_id", "action", "name", "arguments_json", "include_raw", "timeout_ms"].includes(key)) continue;
1110
+ if (payload[key] === undefined) payload[key] = value;
1111
+ }
1112
+ if (asString(args.request) && payload.query === undefined && payload.prompt === undefined) {
1113
+ payload.query = asString(args.request);
1114
+ }
1115
+ return payload;
1116
+ }
1117
+
843
1118
  async function runEmbed(args) {
844
1119
  const command = resolveEmbedCommand();
845
1120
  const fullArgs = command.args.concat(ensureJson(args));
1121
+ for (let attempt = 0; attempt < 2; attempt += 1) {
1122
+ try {
1123
+ return await runEmbedOnce(command, fullArgs);
1124
+ } catch (error) {
1125
+ if (attempt === 0 && isTransientEmbedCliPipeError(undefined, error?.message)) {
1126
+ await wait(100);
1127
+ continue;
1128
+ }
1129
+ throw error;
1130
+ }
1131
+ }
1132
+ throw new Error("EmbedLabs CLI failed after retry.");
1133
+ }
1134
+
1135
+ async function runEmbedOnce(command, fullArgs) {
846
1136
  return await new Promise((resolve, reject) => {
847
1137
  const child = spawn(command.file, fullArgs, {
848
1138
  cwd: process.cwd(),
849
- env: process.env,
1139
+ env: embedCliEnv(),
850
1140
  stdio: ["ignore", "pipe", "pipe"]
851
1141
  });
852
1142
  let stdout = "";
@@ -872,6 +1162,21 @@ async function runEmbed(args) {
872
1162
  });
873
1163
  }
874
1164
 
1165
+ function isTransientEmbedCliPipeError(stdout, stderr) {
1166
+ return !asString(stdout).trim() && /\bEPIPE\b/.test(asString(stderr));
1167
+ }
1168
+
1169
+ function wait(ms) {
1170
+ return new Promise((resolve) => setTimeout(resolve, ms));
1171
+ }
1172
+
1173
+ function embedCliEnv() {
1174
+ return {
1175
+ ...process.env,
1176
+ EMBEDLABS_ENABLE_INTERNAL_DIRECT_COMMANDS: "1"
1177
+ };
1178
+ }
1179
+
875
1180
  async function runProcess(command, args, options = {}) {
876
1181
  return await new Promise((resolve) => {
877
1182
  const child = spawn(command, args, {
@@ -900,19 +1205,19 @@ async function runProcess(command, args, options = {}) {
900
1205
 
901
1206
  function parseEmbedJson(output) {
902
1207
  if (!output) {
903
- return { ok: false, error: { code: "empty_cli_output", message: "Embed Labs CLI returned an empty response." } };
1208
+ return { ok: false, error: { code: "empty_cli_output", message: "EmbedLabs CLI returned an empty response." } };
904
1209
  }
905
1210
  try {
906
1211
  return JSON.parse(output);
907
1212
  } catch {
908
- return { ok: false, error: { code: "non_json_cli_output", message: "Embed Labs CLI returned non-JSON output." } };
1213
+ return { ok: false, error: { code: "non_json_cli_output", message: "EmbedLabs CLI returned non-JSON output." } };
909
1214
  }
910
1215
  }
911
1216
 
912
1217
  function summarizeEmbedFailure(exitCode, parsed, stderr) {
913
1218
  const body = parsed && typeof parsed === "object" ? parsed : {};
914
1219
  const error = body.error && typeof body.error === "object" ? body.error : {};
915
- const message = asString(error.message) || asString(error.code) || summarizePublicText(stderr) || "Embed Labs CLI command failed.";
1220
+ const message = asString(error.message) || asString(error.code) || summarizePublicText(stderr) || "EmbedLabs CLI command failed.";
916
1221
  const code = asString(error.code) || "embed_cli_failed";
917
1222
  const summarizedError = {
918
1223
  code,
@@ -943,7 +1248,7 @@ function parseThrownEmbedFailure(error) {
943
1248
  ok: false,
944
1249
  error: {
945
1250
  code: "embed_cli_failed",
946
- message: summarizePublicText(text) || "Embed Labs CLI command failed."
1251
+ message: summarizePublicText(text) || "EmbedLabs CLI command failed."
947
1252
  }
948
1253
  };
949
1254
  }
@@ -952,7 +1257,7 @@ function toolFailureResult(error, context = {}) {
952
1257
  const parsed = parseThrownEmbedFailure(error);
953
1258
  const err = parsed?.error && typeof parsed.error === "object" ? parsed.error : {};
954
1259
  const code = asString(err.code) || "embed_cli_failed";
955
- const message = asString(err.message) || "Embed Labs CLI command failed.";
1260
+ const message = asString(err.message) || "EmbedLabs CLI command failed.";
956
1261
  const failure = {
957
1262
  ok: false,
958
1263
  ...(Number.isFinite(Number(parsed?.exit_code)) ? { exit_code: Number(parsed.exit_code) } : {}),
@@ -982,13 +1287,13 @@ function friendlyToolFailureSummary(code, message, context = {}) {
982
1287
  return "RP2350 Monitor 没有可用连接通道。支持两种方式:USB 串口连接,或局域网 IP/TCP 连接。请确认 USB 设备未被虚拟机占用,或者提供设备 IP/tcp_endpoint 后重试。";
983
1288
  }
984
1289
  if (code === "rp2350_monitor_bridge_handshake_failed") {
985
- return "RP2350 Monitor 固件控制通道没有响应。开发板已被识别,但 USB/TCP 命令握手失败;请重新上电或重新刷写 Monitor 固件后再试。";
1290
+ return "RP2350 Monitor 固件控制通道没有响应。开发板已被识别,但 USB/TCP 命令握手失败;请先执行 `embedlabs bridge restart` 后重试。如仍失败,再重新上电或重新刷写 Monitor 固件。";
986
1291
  }
987
1292
  if (code === "rp2350_monitor_unavailable") {
988
1293
  return "RP2350 Monitor 控制通道尚未就绪。请确认设备已刷入 Monitor 固件且未被虚拟机占用;如果使用 Wi-Fi/以太网连接,请提供设备局域网 IP 或 tcp_endpoint。";
989
1294
  }
990
1295
  if (/No such file or directory/i.test(text) && /usbmodem|ttyACM|COM\d+/i.test(text)) {
991
- return "RP2350 Monitor 的 USB 串口上游连接已经失效。可以重新插拔设备或重启 Embed Labs Local Bridge;如果开发板已联网,也可以提供局域网 IP/tcp_endpoint 走网络连接。";
1296
+ return "RP2350 Monitor 的 USB 串口上游连接已经失效。请先执行 `embedlabs bridge restart`;如果开发板已联网,也可以提供局域网 IP/tcp_endpoint 走网络连接。";
992
1297
  }
993
1298
  return `RP2350 Monitor 操作失败:${text}`;
994
1299
  }
@@ -996,17 +1301,14 @@ function friendlyToolFailureSummary(code, message, context = {}) {
996
1301
  }
997
1302
 
998
1303
  function isAuthSetupError(code) {
999
- return code === "auth_token_missing"
1000
- || code === "auth_token_rejected"
1001
- || code === "auth_not_ready"
1002
- || code === "tool_integrity_check_failed"
1003
- || code === "device_signature_required";
1304
+ return code === "tool_integrity_check_failed"
1305
+ || code === "installation_identity_required";
1004
1306
  }
1005
1307
 
1006
1308
  function authSetupDetails(details) {
1007
1309
  if (!details || typeof details !== "object" || Array.isArray(details)) return undefined;
1008
1310
  const allowed = {};
1009
- for (const key of ["dashboard_url", "login_command", "env_var", "auth_status_command", "device_status_command", "auth_file"]) {
1311
+ for (const key of ["agent_status_command", "identity_init_command", "identity_status_command", "memory_home"]) {
1010
1312
  const value = details[key];
1011
1313
  if (typeof value === "string" && value.trim()) {
1012
1314
  allowed[key] = value.slice(0, 500);
@@ -1019,9 +1321,17 @@ function summarizePublicText(value) {
1019
1321
  const text = asString(value).trim();
1020
1322
  if (!text) return undefined;
1021
1323
  return text
1022
- .replace(/(sk-[A-Za-z0-9_-]{8,})/g, "[redacted]")
1023
- .replace(/(npm_[A-Za-z0-9_-]{8,})/g, "[redacted]")
1024
- .replace(/(re_[A-Za-z0-9_-]{8,})/g, "[redacted]")
1324
+ .replace(/(sk-[A-Za-z0-9_-]{8,})/g, "[redacted:api_key]")
1325
+ .replace(/(npm_[A-Za-z0-9_-]{8,})/g, "[redacted:api_key]")
1326
+ .replace(/\b(re_[A-Za-z0-9_-]{8,})\b/g, "[redacted:api_key]")
1327
+ .replace(/\b((?:OPENAI|ANTHROPIC|BAI|DASHSCOPE|GEMINI|GOOGLE|AWS|NPM)[A-Z0-9_]*_KEY)\s*[:=]\s*['"]?[^'"\s,;}]+/gi, "$1=[redacted:api_key]")
1328
+ .replace(/\b(password|passwd|pwd|secret|token|api[_-]?key)\s*[:=]\s*['"]?[^'"\s,;}]+/gi, "$1=[redacted:secret]")
1329
+ .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "[redacted:email]")
1330
+ .replace(/\bhttps?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|0\.0\.0\.0)(?::\d+)?(?:\/[^\s,;)}\]]*)?/gi, "[redacted:local_url]")
1331
+ .replace(/\bfile:\/{2,3}(?:[A-Za-z]:\/|(?:Users|private|tmp|var\/folders|Volumes)\/)[^\s,;)}\]]+/gi, "[redacted:local_path]")
1332
+ .replace(/(?:\/Users|\/private|\/tmp|\/var\/folders|\/Volumes)\/[^\s,;)}\]]+/g, "[redacted:local_path]")
1333
+ .replace(/\b[A-Za-z]:\/Users\/[^\s,;)}\]]+/g, "[redacted:local_path]")
1334
+ .replace(/\b[A-Za-z]:\\[^\s,;)}\]]+/g, "[redacted:local_path]")
1025
1335
  .slice(0, 500);
1026
1336
  }
1027
1337
 
@@ -1031,32 +1341,49 @@ function responseData(response) {
1031
1341
  return body;
1032
1342
  }
1033
1343
 
1034
- async function pluginAuthStatus() {
1344
+ async function pluginAgentStatus() {
1035
1345
  let response;
1036
1346
  try {
1037
- response = await runEmbed(["auth", "status"]);
1347
+ response = await runEmbed(["agent", "status"]);
1038
1348
  } catch (error) {
1039
- return authNotReadyResult({
1040
- authenticated: false,
1041
- source_error: error instanceof Error ? error.message : String(error)
1042
- });
1043
- }
1044
- const status = responseData(response) || {};
1045
- if (status.authenticated === true && status.device_id && status.device_integrity === "ok") {
1046
1349
  return {
1047
- ok: true,
1048
- authenticated: true,
1049
- device_ready: true,
1050
- profile: status.profile,
1051
- source: status.source,
1052
- account_id: status.account_id,
1053
- api_key_id: status.api_key_id,
1054
- device_id: status.device_id,
1055
- device_integrity: status.device_integrity,
1056
- message: "Embed Labs token and device binding are ready."
1350
+ ok: false,
1351
+ error: {
1352
+ code: "embedlabs_cli_unavailable",
1353
+ message: "EmbedLabs CLI is not available to this plugin.",
1354
+ remediation: "Install or update the embedlabs CLI, then restart Codex/OpenCode/Trae.",
1355
+ details: {
1356
+ source_error: error instanceof Error ? error.message : String(error)
1357
+ }
1358
+ }
1057
1359
  };
1058
1360
  }
1059
- return authNotReadyResult(status);
1361
+ const status = responseData(response) || {};
1362
+ const statusNextCommands = Array.isArray(status.next_commands) ? status.next_commands : [];
1363
+ return {
1364
+ ok: true,
1365
+ local_agent_ready: status.runtime === "embedlabs-local-agent" || Boolean(status.installation),
1366
+ identity_scope: "local_installation",
1367
+ uses_local_installation_identity: true,
1368
+ runtime: status.runtime,
1369
+ client: status.client,
1370
+ installation: status.installation,
1371
+ model: status.model,
1372
+ memory: status.memory,
1373
+ bridge: status.bridge,
1374
+ closed_loop: status.closed_loop,
1375
+ upstream_adapter: status.upstream_adapter,
1376
+ public_experience: status.public_experience,
1377
+ auto_sync: status.auto_sync,
1378
+ message: "Local EmbedLabs Agent status was read from this computer. EmbedLabs uses local model configuration, local installation identity, BoardPack, SDK, Local Bridge, compile, flash, deploy, and debug workflows on this machine.",
1379
+ next_commands: statusNextCommands.length > 0 ? statusNextCommands : [
1380
+ {
1381
+ label: "check_local_agent",
1382
+ command: "embedlabs agent status --json",
1383
+ description: "Check local Agent identity, model config, memory, and Local Bridge status."
1384
+ }
1385
+ ]
1386
+ };
1060
1387
  }
1061
1388
 
1062
1389
  function formatCurrencyCents(value, currency = "USD") {
@@ -1065,45 +1392,6 @@ function formatCurrencyCents(value, currency = "USD") {
1065
1392
  return `${currency || "USD"} ${(amount / 100).toFixed(2)}`;
1066
1393
  }
1067
1394
 
1068
- function authNotReadyResult(status) {
1069
- const authenticated = status?.authenticated === true;
1070
- const integrity = asString(status?.device_integrity) || (status?.device_id ? "unknown" : "unbound");
1071
- const code = !authenticated ? "auth_token_missing" : integrity === "failed" ? "tool_integrity_check_failed" : "auth_not_ready";
1072
- const message = code === "tool_integrity_check_failed"
1073
- ? TOOL_INTEGRITY_RELOGIN_MESSAGE
1074
- : !authenticated
1075
- ? "Embed Labs API Token is not configured for this plugin."
1076
- : "Embed Labs API Token 已配置,但还没有完成本机设备绑定。";
1077
- return {
1078
- ok: false,
1079
- error: {
1080
- code,
1081
- message,
1082
- remediation: [
1083
- "1. Open https://api.embedboard.com/dashboard and sign in or register.",
1084
- "2. Create or copy your Embed Labs API Token.",
1085
- "3. Update the local CLI, then run: embedlabs auth login --token <your_token>",
1086
- "4. Verify device binding with: embedlabs auth device status",
1087
- "5. Restart Codex/OpenCode/Trae so the plugin picks up the refreshed CLI and auth file."
1088
- ].join("\n"),
1089
- details: {
1090
- dashboard_url: "https://api.embedboard.com/dashboard",
1091
- login_command: "embedlabs auth login --token <your_token>",
1092
- auth_status_command: "embedlabs auth status",
1093
- device_status_command: "embedlabs auth device status",
1094
- auth_file: ".embed-labs/auth.json",
1095
- current_status: {
1096
- authenticated,
1097
- source: status?.source,
1098
- profile: status?.profile,
1099
- device_id: status?.device_id,
1100
- device_integrity: integrity
1101
- }
1102
- }
1103
- }
1104
- };
1105
- }
1106
-
1107
1395
  function pickArtifact(response, preferredKind) {
1108
1396
  const data = responseData(response);
1109
1397
  const artifacts = Array.isArray(data?.artifacts) ? data.artifacts : [];
@@ -1120,73 +1408,72 @@ function pickArtifact(response, preferredKind) {
1120
1408
  async function cloudStatus(args) {
1121
1409
  const template = asString(args.template_id || args.template);
1122
1410
  const includeRaw = boolValue(args.include_raw);
1123
- const [modelDefault, serviceModes] = await Promise.all([
1124
- runEmbed(["model", "default"]),
1125
- runEmbed(["service", "modes"])
1126
- ]);
1127
1411
  if (!template) {
1128
1412
  const boards = await supportedBoards(args);
1129
1413
  return {
1130
- model_default: modelDefault,
1131
- service_modes: serviceModes,
1132
1414
  ...boards,
1415
+ compatibility_adapter: "dbt_cloud_status_to_agent_board_metadata",
1416
+ agent_actions: ["install_toolchain", "manage_board_pack"],
1133
1417
  selected_template_id: null,
1134
1418
  board_methods: undefined,
1135
- note: "No template_id was supplied, so this response lists supported boards only. Board methods are template-specific; call dbt_cloud_status with template_id for one selected board."
1419
+ model_service_catalog_retired: true,
1420
+ note: "No template_id was supplied, so this compatibility response lists supported boards only. Model/service catalogs are retired from the plugin surface. Normal clients should use embedlabs_action_run with action_id=manage_board_pack for supported-board/BoardPack catalog lists and action_id=install_toolchain only for SDK/toolchain install, update, uninstall, or package-status requests."
1136
1421
  };
1137
1422
  }
1138
- const [board, methods, knowledge] = await Promise.all([
1139
- runEmbed(["board", "registry", "show", template]),
1140
- runEmbed(["board", "methods", template]),
1141
- runEmbed(["board", "knowledge", template])
1423
+ const [boards, knowledge] = await Promise.all([
1424
+ supportedBoards(args),
1425
+ boardKnowledgeSearch({
1426
+ template_id: template,
1427
+ query: asString(args.query) || "board capabilities toolchain install flash deploy debug pinout workflow",
1428
+ include_raw: includeRaw
1429
+ })
1142
1430
  ]);
1143
- if (includeRaw) {
1144
- return { model_default: modelDefault, service_modes: serviceModes, board, methods, knowledge };
1145
- }
1146
- return compactCloudStatusResult({ template, modelDefault, serviceModes, board, methods, knowledge });
1147
- }
1148
-
1149
- function compactCloudStatusResult({ template, modelDefault, serviceModes, board, methods, knowledge }) {
1150
- const model = responseData(modelDefault) || {};
1151
- const service = responseData(serviceModes) || {};
1152
- const boardData = responseData(board) || {};
1153
- const methodData = responseData(methods) || {};
1154
- const knowledgeData = responseData(knowledge) || {};
1155
- const methodItems = Array.isArray(methodData.methods) ? methodData.methods : [];
1156
- const knowledgeItems =
1157
- (Array.isArray(knowledgeData.files) && knowledgeData.files) ||
1158
- (Array.isArray(knowledgeData.knowledge_files) && knowledgeData.knowledge_files) ||
1159
- (Array.isArray(knowledgeData.sources) && knowledgeData.sources) ||
1160
- [];
1161
- const method_table = methodItems.slice(0, 20).map((item) => compactObject({
1162
- method_id: item.method_id || item.id,
1163
- execution_plane: item.execution_plane,
1164
- tool: item.api?.mvp_tool || item.mvp_tool,
1165
- summary: summarizePublicText(item.summary || item.description)
1166
- }));
1431
+ return compactCloudStatusResult({ template, boards, knowledge, includeRaw });
1432
+ }
1433
+
1434
+ function compactCloudStatusResult({ template, boards, knowledge, includeRaw }) {
1435
+ const table = Array.isArray(boards?.table) ? boards.table : [];
1436
+ const selectedBoard =
1437
+ table.find((item) => item.template_id === template || item.board_id === template) ||
1438
+ table.find((item) => String(item.template_id || "").includes(template)) ||
1439
+ null;
1440
+ const knowledgeData = knowledge?.board_knowledge || knowledge?.execution?.knowledge || {};
1441
+ const method_table = [
1442
+ { method_id: "detect_board", tool: "embedlabs_action_run", summary: "Detect connected local hardware through Go Local Bridge inventory." },
1443
+ { method_id: "query_board_knowledge", tool: "embedlabs_action_run", summary: "Answer pinout, SDK, flash, debug, and board workflow questions from the board pack." },
1444
+ { method_id: "install_toolchain", tool: "embedlabs_action_run", summary: "List, install, update, or uninstall local board toolchain packages." },
1445
+ { method_id: "build_project", tool: "embedlabs_action_run", summary: "Build a local project with the installed local SDK/toolchain." },
1446
+ { method_id: "flash_image", tool: "embedlabs_action_run", summary: "Plan and run approved local flashing through Local Bridge." },
1447
+ { method_id: "deploy_app", tool: "embedlabs_action_run", summary: "Deploy and optionally run a local app on a selected board." },
1448
+ { method_id: "debug_board", tool: "embedlabs_action_run", summary: "Run local diagnostics for the selected connected board." },
1449
+ { method_id: "capture_signal", tool: "embedlabs_action_run", summary: "Capture and decode hardware signals through RP2350 Monitor/Local Bridge." },
1450
+ { method_id: "manage_board_pack", tool: "embedlabs_action_run", summary: "List or inspect BoardPack catalog/package metadata without adding visible MCP tools." }
1451
+ ];
1167
1452
  return compactObject({
1168
- ok: true,
1453
+ ok: boards?.ok !== false && knowledge?.ok !== false,
1454
+ compatibility_adapter: "dbt_cloud_status_to_agent_board_metadata",
1169
1455
  template_id: template,
1170
- model_default: compactObject({
1171
- provider: model.provider,
1172
- model: model.model,
1173
- display_name: model.display_name || model.name
1174
- }),
1175
- service_modes: compactObject({
1176
- default_mode: service.default_mode || service.defaultMode,
1177
- modes: Array.isArray(service.modes) ? service.modes.map((item) => item.id || item.name || item).slice(0, 10) : undefined
1178
- }),
1456
+ selected_template_id: template,
1457
+ model_service_catalog_retired: true,
1458
+ agent_actions: ["install_toolchain", "query_board_knowledge", "manage_board_pack"],
1179
1459
  board: compactObject({
1180
- template_id: boardData.template_id || boardData.id || template,
1181
- display_name: boardData.display_name || boardData.name,
1182
- board_id: boardData.board_id,
1183
- variant_id: boardData.variant_id
1460
+ template_id: selectedBoard?.template_id || template,
1461
+ display_name: selectedBoard?.board,
1462
+ category: selectedBoard?.category,
1463
+ install_status: selectedBoard?.install_status,
1464
+ local_version: selectedBoard?.local_version,
1465
+ server_version: selectedBoard?.server_version,
1466
+ modes: selectedBoard?.modes
1184
1467
  }),
1185
- method_count: methodItems.length,
1468
+ board_table: table,
1469
+ board_methods: method_table,
1470
+ method_count: method_table.length,
1186
1471
  method_table,
1187
- knowledge_file_count: knowledgeItems.length,
1188
- guidance: "Use dbt_board_knowledge_search for board facts and use the high-level MCP tool named in method_table. Raw registry, source URLs, and internal paths are omitted by default.",
1189
- raw_omitted: true
1472
+ board_knowledge: knowledgeData,
1473
+ summary_for_user: knowledge?.summary_for_user || knowledge?.answer || "Board metadata is available through local Agent compatibility actions.",
1474
+ guidance: "This is a compatibility adapter. For normal use, call embedlabs_action_schema and embedlabs_action_run for the listed high-level actions: manage_board_pack for board/BoardPack catalog lists, install_toolchain for SDK/toolchain package status and install/update/uninstall, and query_board_knowledge for factual board questions. Compatibility tools such as dbt_supported_boards and dbt_board_knowledge_search are diagnostics only. Model/provider configuration stays local and is not part of this tool.",
1475
+ raw: includeRaw ? { supported_boards: boards, knowledge } : undefined,
1476
+ raw_omitted: !includeRaw
1190
1477
  });
1191
1478
  }
1192
1479
 
@@ -1196,103 +1483,252 @@ async function boardKnowledgeSearch(args) {
1196
1483
  if (!query) {
1197
1484
  throw new Error("query is required");
1198
1485
  }
1199
- const command = ["board", "knowledge", "search", template, "--query", query];
1200
- pushOptional(command, "source", args.source);
1201
- pushOptional(command, "limit", args.limit);
1202
- return await runEmbed(command);
1486
+ const routed = await agentActionRun({
1487
+ action_id: "query_board_knowledge",
1488
+ arguments_json: JSON.stringify(compactObject({
1489
+ template_id: template,
1490
+ query,
1491
+ source: asString(args.source),
1492
+ limit: asString(args.limit)
1493
+ })),
1494
+ include_raw: args.include_raw
1495
+ });
1496
+ return {
1497
+ ...routed,
1498
+ compatibility_adapter: "dbt_board_knowledge_search_to_agent_query_board_knowledge",
1499
+ board_knowledge: routed.execution?.knowledge,
1500
+ summary_for_user: routed.execution?.summary ?? routed.answer,
1501
+ guidance: "This compatibility tool now routes through the local EmbedLabs Agent query_board_knowledge action. Prefer embedlabs_chat for natural-language questions or embedlabs_action_run when the action contract is explicit."
1502
+ };
1203
1503
  }
1204
1504
 
1205
- async function supportedBoards() {
1206
- const [registrySettled, localSettled] = await Promise.allSettled([
1207
- runEmbed(["board", "registry", "list"]),
1208
- runEmbed(["local", "toolchain", "list"])
1209
- ]);
1210
- const registry = settledRunValue(registrySettled);
1211
- const local = settledRunValue(localSettled);
1212
- const profiles = Array.isArray(responseData(registry)?.profiles) ? responseData(registry).profiles : [];
1213
- const environments = Array.isArray(responseData(local)?.environments) ? responseData(local).environments : [];
1214
- const localByBoard = new Map(environments.map((item) => [asString(item.board_id), item]));
1215
- const supported = profiles.map((profile) => {
1216
- const localEnv = localByBoard.get(asString(profile.template_id));
1217
- const methods = Array.isArray(profile.methods) ? profile.methods : [];
1218
- const runtimes = [...new Set(methods.map((method) => asString(method.runtime)).filter(Boolean))];
1219
- const category = boardUxCategory(profile, methods);
1220
- const capabilityGroups = boardCapabilityGroups(profile, methods);
1221
- return compactObject({
1222
- template_id: profile.template_id,
1223
- board_id: profile.board_id,
1224
- variant_id: profile.variant_id,
1225
- display_name: normalizeSupportedBoardDisplayName(profile.display_name || profile.variant_id || profile.template_id),
1226
- category,
1227
- support_level: profile.support_level,
1228
- cloud_method_count: methods.length,
1229
- cloud_method_runtimes: runtimes,
1230
- capability_groups: capabilityGroups,
1231
- recommended_next_step: recommendedBoardNextStep(profile, localEnv),
1232
- local_toolchain_status: localEnv?.status,
1233
- local_install_modes: Array.isArray(localEnv?.install_modes) ? localEnv.install_modes : undefined,
1234
- local_installed: localEnv?.installed,
1235
- local_installed_version: localEnv?.installed?.version,
1236
- server_latest_version: localEnv?.latest?.version,
1237
- local_install_command: localEnv?.install_command,
1238
- local_update_command: localEnv?.update_command,
1239
- local_uninstall_command: localEnv ? `embedlabs local toolchain uninstall --board ${profile.template_id}` : undefined
1505
+ async function supportedBoards(args = {}) {
1506
+ const routed = await agentActionRun({
1507
+ action_id: "manage_board_pack",
1508
+ arguments_json: JSON.stringify({ operation: "list" }),
1509
+ include_raw: args.include_raw
1510
+ });
1511
+ let toolchainRouted;
1512
+ try {
1513
+ toolchainRouted = await agentActionRun({
1514
+ action_id: "install_toolchain",
1515
+ arguments_json: JSON.stringify({ operation: "list" }),
1516
+ include_raw: args.include_raw
1517
+ });
1518
+ } catch (error) {
1519
+ toolchainRouted = {
1520
+ ok: false,
1521
+ error: { code: "toolchain_status_unavailable", message: error instanceof Error ? error.message : String(error) }
1522
+ };
1523
+ }
1524
+ const boardPack = routed.execution?.board_pack || {};
1525
+ const profiles = Array.isArray(boardPack.profiles) ? boardPack.profiles : [];
1526
+ const operation = toolchainRouted?.execution?.toolchain_operation || {};
1527
+ const environments = Array.isArray(operation.environments) ? operation.environments : [];
1528
+ const environmentByBoard = new Map(environments.map((item) => [item.board_id || item.template_id, item]));
1529
+ const table = profiles.map((profile) => {
1530
+ const templateId = profile.template_id || profile.board_id;
1531
+ const item = environmentByBoard.get(templateId) || {};
1532
+ return ({
1533
+ board: profile.display_name || item.display_name || templateId,
1534
+ template_id: templateId,
1535
+ category: boardUxCategory(profile, []),
1536
+ support_level: profile.support_level || "",
1537
+ method_count: profile.method_count ?? "",
1538
+ knowledge_file_count: profile.knowledge_file_count ?? "",
1539
+ main_functions: Array.isArray(item.modes) ? item.modes.join(", ") : "",
1540
+ install_status: item.status || "unknown",
1541
+ local_version: item.local_version || "not installed",
1542
+ server_version: item.server_version || "unknown",
1543
+ modes: Array.isArray(item.modes) ? item.modes.join(",") : "",
1544
+ next_step: item.install_command || `embedlabs agent action run install_toolchain --operation install --board ${templateId} --json`,
1545
+ show: `embedlabs agent action run manage_board_pack --operation show --template-id ${templateId} --json`,
1546
+ install: item.install_command,
1547
+ update: item.update_command,
1548
+ uninstall: item.uninstall_command
1240
1549
  });
1241
1550
  });
1242
- for (const env of environments) {
1243
- const boardId = asString(env.board_id);
1244
- if (!boardId || supported.some((item) => item.template_id === boardId)) continue;
1245
- supported.push(compactObject({
1246
- template_id: boardId,
1247
- board_id: boardId,
1248
- display_name: normalizeSupportedBoardDisplayName(env.display_name || boardId),
1249
- category: boardUxCategory({ template_id: boardId, board_id: boardId }, []),
1250
- support_level: "local_toolchain",
1251
- cloud_method_count: 0,
1252
- cloud_method_runtimes: [],
1253
- capability_groups: localToolchainCapabilityGroups(env),
1254
- recommended_next_step: recommendedBoardNextStep({ template_id: boardId, board_id: boardId }, env),
1255
- local_toolchain_status: env.status,
1256
- local_install_modes: Array.isArray(env.install_modes) ? env.install_modes : undefined,
1257
- local_installed: env.installed,
1258
- local_installed_version: env.installed?.version,
1259
- server_latest_version: env.latest?.version,
1260
- local_install_command: env.install_command,
1261
- local_update_command: env.update_command,
1262
- local_uninstall_command: `embedlabs local toolchain uninstall --board ${boardId}`
1263
- }));
1264
- }
1265
- supported.sort((a, b) => asString(a.display_name).localeCompare(asString(b.display_name)));
1266
- const table = supported.map((item) => ({
1267
- board: item.display_name,
1268
- template_id: item.template_id,
1269
- category: item.category,
1270
- main_functions: Array.isArray(item.capability_groups) ? item.capability_groups.join(", ") : "",
1271
- support_level: item.support_level,
1272
- install_status: item.local_toolchain_status || "unknown",
1273
- local_version: item.local_installed_version || "not installed",
1274
- server_version: item.server_latest_version || "unknown",
1275
- modes: Array.isArray(item.local_install_modes) ? item.local_install_modes.join(",") : "",
1276
- next_step: item.recommended_next_step,
1277
- install: item.local_install_command,
1278
- update: item.local_update_command,
1279
- uninstall: item.local_uninstall_command
1280
- }));
1281
1551
  return {
1282
- supported_boards: supported,
1552
+ ok: routed.ok,
1553
+ compatibility_adapter: "dbt_supported_boards_to_agent_manage_board_pack_list",
1554
+ agent_action: "manage_board_pack",
1555
+ companion_agent_action: "install_toolchain",
1556
+ supported_boards: profiles,
1557
+ board_packs: profiles,
1558
+ toolchain_environments: environments,
1283
1559
  table,
1284
- cloud_registry: {
1285
- available: !!registry,
1286
- count: profiles.length,
1287
- error: settledRunError(registrySettled)
1560
+ execution: routed.execution,
1561
+ toolchain_execution: toolchainRouted?.execution,
1562
+ next_commands: [
1563
+ ...(Array.isArray(routed.next_commands) ? routed.next_commands : []),
1564
+ ...(Array.isArray(toolchainRouted?.next_commands) ? toolchainRouted.next_commands : [])
1565
+ ],
1566
+ guidance: "When the user asks which boards are supported, show board, template_id, category, support_level, install_status, local_version, server_version, modes, and next_step as a Markdown table. Use embedlabs_action_run action_id=manage_board_pack with operation=list/show for BoardPack catalog questions, and action_id=install_toolchain with operation=list/install/update/uninstall only for SDK/toolchain package management. Do not call low-level toolchain tools unless the user explicitly asks for raw diagnostics, and never shell rm.",
1567
+ observed_at: new Date().toISOString()
1568
+ };
1569
+ }
1570
+
1571
+ async function agentActionSchema(args) {
1572
+ const actionId = asString(args.action_id || args.action || args.name);
1573
+ const command = actionId
1574
+ ? ["agent", "action", "schema", actionId]
1575
+ : ["agent", "action", "schema"];
1576
+ const result = await runEmbed(command);
1577
+ const data = responseData(result) || result?.data || {};
1578
+ return {
1579
+ ok: true,
1580
+ schema_source: "embedlabs-agent-action-catalog",
1581
+ action_id: actionId || undefined,
1582
+ response: boolValue(args.include_raw) ? result : undefined,
1583
+ raw_omitted: !boolValue(args.include_raw),
1584
+ action: data.action,
1585
+ actions: data.actions,
1586
+ definition: data.definition,
1587
+ guidance: "Use input_schema and safety.must_not_infer before calling hardware or local-file actions. Do not infer pins, private paths, target boards, or approval from protocol words or previous memory."
1588
+ };
1589
+ }
1590
+
1591
+ async function agentActionRun(args) {
1592
+ const startedAt = Date.now();
1593
+ const actionId = asString(args.action_id || args.action || args.name);
1594
+ if (!actionId) {
1595
+ throw new Error("action_id is required");
1596
+ }
1597
+ const command = ["agent", "action", "run", actionId];
1598
+ const payload = agentActionPayload(args);
1599
+ const legacyCaptureArgumentIssue = captureSignalLegacyActionArgumentIssue(actionId, payload);
1600
+ if (legacyCaptureArgumentIssue) return legacyCaptureArgumentIssue;
1601
+ for (const [key, value] of Object.entries(payload)) {
1602
+ appendAgentActionFlag(command, key, value);
1603
+ }
1604
+ const result = await runEmbed(command);
1605
+ const data = responseData(result) || result?.data || {};
1606
+ const executionOk = data.execution?.ok;
1607
+ const cliOk = result?.response?.ok !== false;
1608
+ const localDeviceId = localDeviceIdForTool(args) || inferLocalDeviceIdFromToolResponse(data);
1609
+ const summary = data.execution?.summary || data.answer || data.execution?.error?.message || `${actionId} completed.`;
1610
+ const logResult = await recordMcpToolEvent("embedlabs_action_run", {
1611
+ client: mcpClientName(),
1612
+ mode: "ide_plugin",
1613
+ success: cliOk && executionOk !== false,
1614
+ duration_ms: Date.now() - startedAt,
1615
+ local_device_id: localDeviceId,
1616
+ input_summary: `agent_action=${actionId} ${JSON.stringify(payload).slice(0, 180)}`,
1617
+ output_summary: asString(summary).slice(0, 240)
1618
+ });
1619
+ return {
1620
+ ok: cliOk,
1621
+ cli_ok: cliOk,
1622
+ execution_ok: executionOk,
1623
+ schema_hint: "Use embedlabs_action_schema before this tool when arguments are unclear.",
1624
+ action_id: actionId,
1625
+ response: boolValue(args.include_raw) ? result : undefined,
1626
+ raw_omitted: !boolValue(args.include_raw),
1627
+ execution: sanitizeMcpPublicValue(data.execution),
1628
+ report: sanitizeMcpPublicValue(data.report),
1629
+ secondary_reports: sanitizeMcpPublicValue(data.secondary_reports),
1630
+ auto_sync: sanitizeMcpPublicValue(data.auto_sync),
1631
+ next_commands: sanitizeMcpPublicValue(data.next_commands),
1632
+ answer: sanitizeMcpPublicValue(data.answer),
1633
+ public_experience: sanitizeMcpPublicValue(data.public_experience),
1634
+ usage: {
1635
+ mode: "ide_plugin",
1636
+ input_tokens: 0,
1637
+ output_tokens: 0,
1638
+ local_device_id: localDeviceId || undefined,
1639
+ mcp_service_log: logResult
1640
+ }
1641
+ };
1642
+ }
1643
+
1644
+ function captureSignalLegacyActionArgumentIssue(actionId, payload) {
1645
+ if (actionId !== "capture_signal" || !payload || typeof payload !== "object") return undefined;
1646
+ const normalizedKeys = new Map(Object.keys(payload).map((key) => [toCliFlagName(key), key]));
1647
+ if (normalizedKeys.has("channels") || normalizedKeys.has("channel")) {
1648
+ const originalKey = normalizedKeys.get("channels") || normalizedKeys.get("channel") || "channels";
1649
+ return legacyCaptureArgumentFailure({
1650
+ actionId,
1651
+ key: originalKey,
1652
+ message: "`channels` is a low-level compatibility alias. For the high-level capture_signal action, pass explicit `pin_base` and `pin_count` or `pins`."
1653
+ });
1654
+ }
1655
+ if (normalizedKeys.has("duration-ms")) {
1656
+ const originalKey = normalizedKeys.get("duration-ms") || "duration_ms";
1657
+ return legacyCaptureArgumentFailure({
1658
+ actionId,
1659
+ key: originalKey,
1660
+ message: "`duration_ms` is a low-level compatibility alias. For the high-level capture_signal action, pass explicit `samples`; compute samples = ceil(duration_ms * sample_rate / 1000)."
1661
+ });
1662
+ }
1663
+ return undefined;
1664
+ }
1665
+
1666
+ function legacyCaptureArgumentFailure({ actionId, key, message }) {
1667
+ return {
1668
+ ok: false,
1669
+ cli_ok: false,
1670
+ execution_ok: false,
1671
+ action_id: actionId,
1672
+ error: {
1673
+ code: "invalid_args",
1674
+ message: `capture_signal received unsupported ${key}: ${message}`
1288
1675
  },
1289
- local_toolchains: {
1290
- available: !!local,
1291
- count: environments.length,
1292
- error: settledRunError(localSettled)
1676
+ summary_for_user: `capture_signal received unsupported ${key}: ${message}`,
1677
+ schema_hint: "Call embedlabs_action_schema with action_id=capture_signal and use the high-level input schema.",
1678
+ next_commands: [
1679
+ {
1680
+ label: "describe_capture_signal",
1681
+ command: "embedlabs agent action describe capture_signal --json",
1682
+ description: "Review the high-level capture_signal schema before retrying."
1683
+ }
1684
+ ]
1685
+ };
1686
+ }
1687
+
1688
+ async function agentChat(args) {
1689
+ const startedAt = Date.now();
1690
+ const prompt = asString(args.prompt || args.request || args.message);
1691
+ if (!prompt) {
1692
+ throw new Error("prompt is required");
1693
+ }
1694
+ const command = ["agent", "chat", "--prompt", prompt];
1695
+ if (!boolValue(args.use_model || args.useModel)) {
1696
+ command.push("--local-only");
1697
+ }
1698
+ const session = asString(args.session || args.chat_session_id);
1699
+ if (session) command.push("--session", session);
1700
+ else if (boolValue(args.new_session || args.newSession)) command.push("--new-session");
1701
+ const result = await runEmbed(command);
1702
+ const data = responseData(result) || result?.data || {};
1703
+ const turns = Array.isArray(data.turns) ? data.turns : [];
1704
+ const firstTurn = turns[0] || {};
1705
+ const logResult = await recordMcpToolEvent("embedlabs_chat", {
1706
+ client: mcpClientName(),
1707
+ mode: "ide_plugin",
1708
+ success: result?.response?.ok !== false && !firstTurn.report,
1709
+ duration_ms: Date.now() - startedAt,
1710
+ input_summary: `agent_chat ${prompt.slice(0, 180)}`,
1711
+ output_summary: asString(firstTurn.answer || firstTurn.report?.error?.message || data.answer || "agent chat completed").slice(0, 240)
1712
+ });
1713
+ return {
1714
+ ok: result?.response?.ok !== false,
1715
+ mode: data.mode,
1716
+ answer: sanitizeMcpPublicValue(firstTurn.answer),
1717
+ intent: firstTurn.intent,
1718
+ chat_session: sanitizeMcpPublicValue(data.chat_session),
1719
+ report: sanitizeMcpPublicValue(firstTurn.report),
1720
+ secondary_reports: sanitizeMcpPublicValue(firstTurn.secondary_reports),
1721
+ public_experience: sanitizeMcpPublicValue(firstTurn.public_experience || data.public_experience),
1722
+ auto_sync: sanitizeMcpPublicValue(firstTurn.auto_sync || data.auto_sync),
1723
+ next_commands: sanitizeMcpPublicValue(data.next_commands || firstTurn.next_commands),
1724
+ response: boolValue(args.include_raw) ? result : undefined,
1725
+ usage: {
1726
+ mode: "ide_plugin",
1727
+ input_tokens: 0,
1728
+ output_tokens: 0,
1729
+ mcp_service_log: logResult
1293
1730
  },
1294
- guidance: "When the user asks which boards are supported, show board, category, main_functions, install_status, local_version, server_version, modes, and next_step as a Markdown table. Use install/update/uninstall through dbt_local_toolchain_install/dbt_local_toolchain_uninstall or dbttool actions rather than shell rm. Ask the user to pick a board/template_id before showing board-specific method details.",
1295
- observed_at: new Date().toISOString()
1731
+ guidance: "Continue with embedlabs_chat and the returned chat_session.chat_session_id when the user expects context. Use embedlabs_action_schema/embedlabs_action_run only when a specific high-level action contract is required."
1296
1732
  };
1297
1733
  }
1298
1734
 
@@ -1319,14 +1755,14 @@ function redactSensitive(value) {
1319
1755
  return asString(value)
1320
1756
  .replace(/(sk-[A-Za-z0-9_-]{8,})/g, "[redacted]")
1321
1757
  .replace(/(npm_[A-Za-z0-9_-]{8,})/g, "[redacted]")
1322
- .replace(/(re_[A-Za-z0-9_-]{8,})/g, "[redacted]")
1758
+ .replace(/\b(re_[A-Za-z0-9_-]{8,})\b/g, "[redacted]")
1323
1759
  .slice(0, 500);
1324
1760
  }
1325
1761
 
1326
1762
  function normalizeSupportedBoardDisplayName(value) {
1327
1763
  const text = asString(value);
1328
1764
  if (!text) return "";
1329
- if (/^TaishanPi 1M RK3566 server build template$/i.test(text)) return "TaishanPi 1M-RK3566";
1765
+ if (/taishanpi/i.test(text) && /rk3566/i.test(text) && /template/i.test(text)) return "TaishanPi 1M RK3566";
1330
1766
  return text;
1331
1767
  }
1332
1768
 
@@ -1381,65 +1817,81 @@ function recommendedBoardNextStep(profile, localEnv) {
1381
1817
  }
1382
1818
 
1383
1819
  async function boardDebug(args) {
1384
- const prompt = asString(args.prompt || args.request || args.reason) || "验证开发板状态";
1385
- const command = ["agent", "run", "--prompt", prompt];
1386
- pushOptional(command, "account", args.account_id || args.account);
1387
- pushOptional(command, "workspace", args.workspace_id || args.workspace);
1388
- pushOptional(command, "provider", args.provider);
1389
- pushOptional(command, "model", args.model);
1390
- pushOptional(command, "host", args.host || DEFAULT_HOST);
1391
- pushOptional(command, "ports", args.ports || DEFAULT_PORTS);
1392
- pushOptional(command, "artifact", args.artifact_path || args.artifact);
1393
- pushOptional(command, "artifact-id", args.artifact_id);
1394
- pushOptional(command, "artifact-task", args.artifact_task_id || args.task_id);
1395
- pushOptional(command, "artifact-output", args.artifact_output);
1396
- pushOptional(command, "remote-path", args.remote_path);
1397
- pushOptional(command, "run-command", args.run_command || args.runCommand);
1398
- if (boolValue(args.approve)) command.push("--approve");
1399
- if (boolValue(args.run)) command.push("--run");
1400
- return await runEmbed(command);
1820
+ const artifactPath = asString(args.artifact_path || args.artifact);
1821
+ const approved = boolValue(args.approve) || boolValue(args.approved);
1822
+ if (artifactPath && approved) {
1823
+ const routed = await agentActionRun({
1824
+ action_id: "deploy_app",
1825
+ arguments_json: JSON.stringify(compactObject({
1826
+ board_id: asString(args.board_id || args.board),
1827
+ local_device_id: asString(args.local_device_id || args.device_id || args.device),
1828
+ host: asString(args.host),
1829
+ user: asString(args.user),
1830
+ artifact_path: artifactPath,
1831
+ remote_path: asString(args.remote_path || args.remotePath),
1832
+ remote_dir: asString(args.remote_dir || args.remoteDir),
1833
+ remote_file: asString(args.remote_file || args.remoteFile),
1834
+ wrapper_script: asString(args.wrapper_script || args.run_script || args.wrapperScript || args.runScript),
1835
+ run_command: asString(args.run_command || args.runCommand),
1836
+ run: boolValue(args.run) || undefined,
1837
+ approve: true
1838
+ })),
1839
+ include_raw: args.include_raw,
1840
+ timeout_ms: args.timeout_ms
1841
+ });
1842
+ return {
1843
+ ok: routed.ok,
1844
+ compatibility_adapter: "dbt_board_debug_to_agent_deploy_app",
1845
+ agent_action: "deploy_app",
1846
+ execution: routed.execution,
1847
+ summary_for_user: routed.execution?.summary ?? routed.answer,
1848
+ response: boolValue(args.include_raw) ? routed.response : undefined
1849
+ };
1850
+ }
1851
+ const routed = await agentActionRun({
1852
+ action_id: "debug_board",
1853
+ arguments_json: JSON.stringify(compactObject({
1854
+ board_id: asString(args.board_id || args.board),
1855
+ local_device_id: asString(args.local_device_id || args.device_id || args.device),
1856
+ host: asString(args.host),
1857
+ scope: asString(args.scope)
1858
+ })),
1859
+ include_raw: args.include_raw,
1860
+ timeout_ms: args.timeout_ms
1861
+ });
1862
+ return {
1863
+ ok: routed.ok,
1864
+ compatibility_adapter: "dbt_board_debug_to_agent_debug_board",
1865
+ agent_action: "debug_board",
1866
+ execution: routed.execution,
1867
+ tool_results: routed.execution?.tool_results,
1868
+ summary_for_user: routed.execution?.summary ?? routed.answer,
1869
+ response: boolValue(args.include_raw) ? routed.response : undefined
1870
+ };
1401
1871
  }
1402
1872
 
1403
- async function currentBoardStatus(args, authStatus = undefined) {
1404
- const startedAt = Date.now();
1405
- const explicitHost = asString(args.host);
1406
- const ports = parsePorts(args.ports || DEFAULT_STATUS_PORTS);
1873
+ async function currentBoardStatus(args) {
1874
+ const startedAt = Date.now();
1407
1875
  const includeRaw = boolValue(args.include_raw);
1408
- const requestedLocalDeviceId = localDeviceIdForTool(args, authStatus);
1409
- const scan = await runEmbed(["device", "list"]);
1410
- const taishanHost = taishanStatusHostFromScan(scan);
1411
- const host = explicitHost || taishanHost;
1412
- const probe = host
1413
- ? await runEmbed(["tool", "call", "device.probe", "--input-json", JSON.stringify({ host, ports })])
1414
- : emptyBoardStatusProbe();
1415
- const summary = summarizeBoardStatus(scan, probe, ports);
1876
+ const requestedLocalDeviceId = localDeviceIdForTool(args);
1877
+ const detected = await runEmbed(detectBoardAgentCommand(args));
1878
+ const summary = summarizeAgentDetectBoardStatus(detected);
1416
1879
  const localDeviceId = requestedLocalDeviceId || (summary.local_device_ids?.length === 1 ? summary.local_device_ids[0] : "");
1417
- if (includeRaw && authStatus?.ok) {
1418
- summary.device_binding = {
1419
- authenticated: true,
1420
- account_id: authStatus.account_id,
1421
- api_key_id: authStatus.api_key_id,
1422
- device_id: authStatus.device_id,
1423
- device_integrity: authStatus.device_integrity
1424
- };
1425
- }
1426
1880
  if (localDeviceId) {
1427
1881
  summary.local_device_id = localDeviceId;
1428
1882
  }
1429
1883
  const logResult = await recordMcpToolEvent("dbt_current_board_status", {
1430
1884
  client: "codex",
1431
- mode: "local_ai",
1432
- server_model_used: false,
1885
+ mode: "ide_plugin",
1433
1886
  success: summary.query_ok === true,
1434
1887
  duration_ms: Date.now() - startedAt,
1435
1888
  local_device_id: localDeviceId,
1436
- input_summary: host ? `host=${host} ports=${ports.join(",")}` : "inventory_only",
1889
+ input_summary: "agent_action=detect_board inventory_only",
1437
1890
  output_summary: summary.status_text
1438
1891
  });
1439
1892
  if (includeRaw) {
1440
1893
  summary.usage = {
1441
- mode: "local_ai_mcp_tool",
1442
- server_model_used: false,
1894
+ mode: "ide_plugin",
1443
1895
  input_tokens: 0,
1444
1896
  output_tokens: 0,
1445
1897
  mcp_service_log: logResult
@@ -1450,6 +1902,64 @@ async function currentBoardStatus(args, authStatus = undefined) {
1450
1902
  return summary;
1451
1903
  }
1452
1904
 
1905
+ function detectBoardAgentCommand(args = {}) {
1906
+ const command = ["agent", "action", "run", "detect_board"];
1907
+ pushOptional(command, "board", args.board_id || args.board);
1908
+ pushOptional(command, "device", args.local_device_id || args.device_id || args.device);
1909
+ return command;
1910
+ }
1911
+
1912
+ function summarizeAgentDetectBoardStatus(actionResult) {
1913
+ const data = responseData(actionResult) || {};
1914
+ const execution = objectValue(data.execution) || data;
1915
+ const bridge = objectValue(execution.bridge) || objectValue(data.bridge) || {};
1916
+ const inventory = objectValue(bridge.inventory) || {};
1917
+ const rawBoards = Array.isArray(execution.boards)
1918
+ ? execution.boards
1919
+ : execution.board
1920
+ ? [execution.board]
1921
+ : Array.isArray(inventory.boards)
1922
+ ? inventory.boards
1923
+ : [];
1924
+ const boards = rawBoards.map((item) => summarizeBoardDevice(item, new Set()));
1925
+ const localDeviceIds = boards
1926
+ .map((item) => asString(item.local_device_id || item.device_id))
1927
+ .filter(Boolean);
1928
+ const statusText = asString(execution.summary)
1929
+ || asString(data.answer)
1930
+ || asString(inventory.summary_for_user)
1931
+ || asString(inventory.status_text)
1932
+ || (boards.length > 0
1933
+ ? boards.map((board) => `${board.display_name || board.board_id || "Board"} is ${board.status || "detected"}.`).join(" ")
1934
+ : "No supported development board is currently detected by Local Bridge.");
1935
+ const result = {
1936
+ board_connected: boards.some((item) => item.connected === true || item.status === "connected"),
1937
+ boards,
1938
+ local_device_ids: localDeviceIds,
1939
+ tcp_ports: [],
1940
+ status_text: statusText,
1941
+ bridge_board_status: compactObject({
1942
+ status_text: statusText,
1943
+ runtime: bridge.runtime,
1944
+ ok: bridge.ok,
1945
+ inventory_seq: inventory.inventory_seq
1946
+ }),
1947
+ agent_action: compactObject({
1948
+ action_id: "detect_board",
1949
+ ok: execution.ok,
1950
+ high_level_action: true
1951
+ }),
1952
+ observed_at: inventory.observed_at || execution.observed_at || data.observed_at,
1953
+ raw_result_omitted: true,
1954
+ model_answer_omitted: true
1955
+ };
1956
+ Object.defineProperty(result, "query_ok", {
1957
+ value: actionResult?.ok === true && actionResult?.response?.ok !== false,
1958
+ enumerable: false
1959
+ });
1960
+ return result;
1961
+ }
1962
+
1453
1963
  function taishanStatusHostFromScan(scanResult) {
1454
1964
  const scan = responseData(scanResult) || {};
1455
1965
  const devices = Array.isArray(scan.development_boards) ? scan.development_boards : (Array.isArray(scan.devices) ? scan.devices : []);
@@ -1501,8 +2011,7 @@ async function rp2350MonitorTool(toolName, capabilityId, args) {
1501
2011
  && data?.result?.ok !== false;
1502
2012
  const logResult = await recordMcpToolEvent(toolName, {
1503
2013
  client: "codex",
1504
- mode: "local_ai",
1505
- server_model_used: false,
2014
+ mode: "ide_plugin",
1506
2015
  success,
1507
2016
  duration_ms: Date.now() - startedAt,
1508
2017
  local_device_id: localDeviceId,
@@ -1510,7 +2019,7 @@ async function rp2350MonitorTool(toolName, capabilityId, args) {
1510
2019
  output_summary: asString(summary).slice(0, 240)
1511
2020
  });
1512
2021
  if (boolValue(args.include_raw)) {
1513
- return { ...response, usage: { mode: "local_ai_mcp_tool", server_model_used: false, input_tokens: 0, output_tokens: 0, local_device_id: localDeviceId || undefined, mcp_service_log: logResult } };
2022
+ return { ...response, usage: { mode: "ide_plugin", input_tokens: 0, output_tokens: 0, local_device_id: localDeviceId || undefined, mcp_service_log: logResult } };
1514
2023
  }
1515
2024
  return response;
1516
2025
  }
@@ -1540,8 +2049,7 @@ async function boardControl(args) {
1540
2049
  && data?.result?.ok !== false;
1541
2050
  const logResult = await recordMcpToolEvent("dbttool", {
1542
2051
  client: "codex",
1543
- mode: "local_ai",
1544
- server_model_used: false,
2052
+ mode: "ide_plugin",
1545
2053
  success,
1546
2054
  duration_ms: Date.now() - startedAt,
1547
2055
  local_device_id: localDeviceId,
@@ -1549,7 +2057,7 @@ async function boardControl(args) {
1549
2057
  output_summary: asString(summary).slice(0, 240)
1550
2058
  });
1551
2059
  if (boolValue(args.include_raw)) {
1552
- return { ...response, usage: { mode: "local_ai_mcp_tool", server_model_used: false, input_tokens: 0, output_tokens: 0, local_device_id: localDeviceId || undefined, mcp_service_log: logResult } };
2060
+ return { ...response, usage: { mode: "ide_plugin", input_tokens: 0, output_tokens: 0, local_device_id: localDeviceId || undefined, mcp_service_log: logResult } };
1553
2061
  }
1554
2062
  return response;
1555
2063
  }
@@ -1660,8 +2168,10 @@ function compactRp2350CapabilitiesResult(response) {
1660
2168
  return compactObject({
1661
2169
  ok: response?.ok !== false && response?.response?.ok !== false,
1662
2170
  capability_id: data.capability_id || "rp2350.monitor.capabilities",
1663
- summary_for_user: result.summary_for_user,
1664
- preferred_mcp_tools: Array.isArray(result.preferred_mcp_tools) ? result.preferred_mcp_tools : undefined,
2171
+ summary_for_user: "RP2350/Pico2 monitor capabilities are exposed as an engineering capability catalog. Default IDE profiles should use embedlabs_action_schema, embedlabs_action_run, or embedlabs_chat with high-level actions such as capture_signal, debug_board, or flash_image.",
2172
+ preferred_mcp_tools: ["embedlabs_action_schema", "embedlabs_action_run", "embedlabs_chat"],
2173
+ preferred_agent_actions: ["capture_signal", "debug_board", "flash_image"],
2174
+ engineering_fallback_tools: ["dbttool", "dbt_rp2350_* full-profile tools"],
1665
2175
  operation_table,
1666
2176
  transport_contract: result.transport_contract,
1667
2177
  pin_model: compactObject({
@@ -1672,7 +2182,7 @@ function compactRp2350CapabilitiesResult(response) {
1672
2182
  }),
1673
2183
  example_loopback_profiles: Array.isArray(result.example_loopback_profiles) ? result.example_loopback_profiles : undefined,
1674
2184
  unsupported: Array.isArray(result.unsupported) ? result.unsupported : undefined,
1675
- guidance: "In the default compact MCP profile, call RP2350 actions through dbttool, for example action=rp2350-spi-transfer, rp2350-logic-capture, or rp2350-logic-decode with arguments_json. In full engineering profile these actions may also appear as dbt_rp2350_* tools. For closed-loop capture, put stimulus in the logic-capture action and decode with logic-decode. Do not search the workspace, create Pico SDK/CMake projects, write firmware source, search web pages, or scrape the Monitor UI for RP2350 Monitor hardware-control requests.",
2185
+ guidance: "Normal RP2350/Pico Monitor requests should use embedlabs_action_schema/embedlabs_action_run or embedlabs_chat with the high-level capture_signal, debug_board, or flash_image actions. In compact/full engineering profiles only, dbttool can call rp2350-* actions such as rp2350-spi-transfer, rp2350-logic-capture, or rp2350-logic-decode after the high-level Agent action cannot express the request or when validating a specific adapter. For closed-loop capture, prefer high-level capture_signal; lower-level logic-capture/decode is an engineering fallback. Do not search the workspace, create Pico SDK/CMake projects, write firmware source, search web pages, or scrape the Monitor UI for RP2350 Monitor hardware-control requests.",
1676
2186
  raw_omitted: true
1677
2187
  });
1678
2188
  }
@@ -1698,7 +2208,53 @@ async function rp2350I2cTransfer(args) {
1698
2208
  }
1699
2209
 
1700
2210
  async function rp2350LogicCapture(args) {
1701
- return await rp2350MonitorTool("dbt_rp2350_logic_capture", "rp2350.monitor.logic.capture", args);
2211
+ const input = normalizeRp2350Input(args);
2212
+ const payload = compactObject({
2213
+ board_id: asString(args.board_id || args.board) || "pico2w-rp2350-monitor",
2214
+ local_device_id: asString(input.local_device_id || input.device_id || args.localDeviceId || args.deviceId || args.device),
2215
+ host: asString(input.host),
2216
+ port: input.port,
2217
+ monitor_bridge_url: asString(input.monitor_bridge_url || args.monitorBridgeUrl),
2218
+ serial_path: asString(input.serial_path || args.serialPath),
2219
+ pin_base: input.pin_base,
2220
+ pin_count: input.pin_count,
2221
+ sample_rate: input.sample_rate,
2222
+ samples: input.samples,
2223
+ output_path: asString(input.output_path || args.outputPath),
2224
+ pull: asString(input.pull),
2225
+ wait_timeout_ms: input.wait_timeout_ms,
2226
+ read_timeout_ms: input.read_timeout_ms,
2227
+ pre_samples: input.pre_samples,
2228
+ post_samples: input.post_samples,
2229
+ search_samples: input.search_samples,
2230
+ trigger_pin: input.trigger_pin,
2231
+ trigger_mode: asString(input.trigger_mode || args.triggerMode),
2232
+ trigger_level: input.trigger_level,
2233
+ trigger_mask: input.trigger_mask,
2234
+ trigger_value: input.trigger_value,
2235
+ channel_names: input.channel_names,
2236
+ protocol: asString(input.protocol),
2237
+ decoder: asString(input.decoder),
2238
+ stimulus: input.stimulus,
2239
+ approve: boolValue(args.approve) || boolValue(args.approved) || undefined
2240
+ });
2241
+ const routed = await agentActionRun({
2242
+ action_id: "capture_signal",
2243
+ arguments_json: JSON.stringify(payload),
2244
+ include_raw: args.include_raw,
2245
+ timeout_ms: args.timeout_ms
2246
+ });
2247
+ return {
2248
+ ok: routed.ok,
2249
+ compatibility_adapter: "dbt_rp2350_logic_capture_to_agent_capture_signal",
2250
+ agent_action: "capture_signal",
2251
+ execution: routed.execution,
2252
+ capture: routed.execution?.tool_results?.[0],
2253
+ decode: routed.execution?.tool_results?.[1],
2254
+ summary_for_user: routed.execution?.summary ?? routed.answer,
2255
+ response: boolValue(args.include_raw) ? routed.response : undefined,
2256
+ guidance: "This compatibility tool now routes through the Native Agent capture_signal action. Provide explicit analyzer pins, sample rate, sample count, and approved=true before arming local hardware."
2257
+ };
1702
2258
  }
1703
2259
 
1704
2260
  async function rp2350LogicDecode(args) {
@@ -1736,16 +2292,17 @@ async function recordMcpToolEvent(toolName, details) {
1736
2292
  const command = [
1737
2293
  "mcp", "log",
1738
2294
  "--tool", toolName,
1739
- "--client", details.client || "codex",
1740
- "--mode", details.mode || "local_ai",
1741
- "--server-model-used", details.server_model_used ? "true" : "false",
2295
+ "--client", details.client || mcpClientName(),
2296
+ "--mode", details.mode || "ide_plugin",
1742
2297
  "--success", details.success ? "true" : "false",
1743
2298
  "--request-id", requestId
1744
2299
  ];
1745
2300
  if (Number.isFinite(details.duration_ms)) command.push("--duration-ms", String(Math.max(0, Math.round(details.duration_ms))));
1746
2301
  if (asString(details.local_device_id)) command.push("--local-device-id", asString(details.local_device_id));
1747
- if (asString(details.input_summary)) command.push("--input-summary", asString(details.input_summary));
1748
- if (asString(details.output_summary)) command.push("--output-summary", asString(details.output_summary));
2302
+ const inputSummary = summarizePublicText(details.input_summary);
2303
+ const outputSummary = summarizePublicText(details.output_summary);
2304
+ if (inputSummary) command.push("--input-summary", inputSummary);
2305
+ if (outputSummary) command.push("--output-summary", outputSummary);
1749
2306
  const response = await runEmbed(command);
1750
2307
  return { recorded: response?.response?.ok === true, event_id: response?.response?.data?.event_id };
1751
2308
  } catch (error) {
@@ -1753,6 +2310,11 @@ async function recordMcpToolEvent(toolName, details) {
1753
2310
  }
1754
2311
  }
1755
2312
 
2313
+ function mcpClientName() {
2314
+ const value = String(process.env.EMBED_MCP_CLIENT || "").trim().toLowerCase();
2315
+ return value || "codex";
2316
+ }
2317
+
1756
2318
  function compactMcpLogError(error) {
1757
2319
  const message = error instanceof Error ? error.message : String(error);
1758
2320
  try {
@@ -1936,21 +2498,6 @@ function filteredRp2350Locators(locators) {
1936
2498
  return filtered.length > 0 ? filtered : undefined;
1937
2499
  }
1938
2500
 
1939
- async function updateLogo(args) {
1940
- const logoPath = asString(args.logo_path || args.logo);
1941
- if (!logoPath) throw new Error("logo_path is required");
1942
- const command = ["build", "image", "boot-logo", "--logo", logoPath];
1943
- pushOptional(command, "account", args.account_id || args.account);
1944
- pushOptional(command, "project", args.project_id || args.project);
1945
- pushOptional(command, "board", args.board_id || args.board || "taishanpi");
1946
- pushOptional(command, "variant", args.variant_id || args.variant);
1947
- pushOptional(command, "kernel-logo", args.kernel_logo_path || args.kernel_logo);
1948
- pushOptional(command, "output", args.output_path || args.output);
1949
- pushOptional(command, "rotate", args.rotate);
1950
- pushOptional(command, "scale", args.scale);
1951
- return await runEmbed(command);
1952
- }
1953
-
1954
2501
  async function composeBootLogo(args) {
1955
2502
  const packagePath = asString(args.package_path || args.package);
1956
2503
  const baseImagePath = asString(args.base_image_path || args.base_image);
@@ -2416,75 +2963,15 @@ async function localBootLogoCompose(args, sdk, logoPath, outputDir, outputImageP
2416
2963
  }
2417
2964
 
2418
2965
  async function bootLogoUpdate(args) {
2419
- const logoPath = asString(args.logo_path || args.logo);
2420
- if (!logoPath) throw new Error("logo_path is required");
2421
- if (!existsSync(logoPath)) {
2422
- return {
2423
- ok: false,
2424
- action: "boot_logo_update",
2425
- error: {
2426
- code: "logo_not_found",
2427
- message: `Logo image does not exist: ${logoPath}`
2428
- }
2429
- };
2430
- }
2431
-
2432
- const boardId = asString(args.board_id || args.board) || "taishanpi-1m-rk3566";
2433
- const outputDir = defaultActionDir("boot-logo", args.output_dir || args.outputDir);
2434
- const outputImagePath = asString(args.output_image_path || args.outputImage || args.output)
2435
- || path.join(outputDir, "boot-logo-boot.img");
2436
- const manifestPath = asString(args.manifest_path || args.manifest) || path.join(outputDir, "boot-logo-compose-manifest.json");
2437
- const sdk = await ensureLocalToolchain(args, boardId, "images");
2438
- if (!sdk.releaseRoot) {
2439
- return {
2440
- ok: false,
2441
- action: "boot_logo_update",
2442
- error: {
2443
- code: "local_sdk_missing",
2444
- message: "本机没有可用的 TaishanPi images SDK,无法本地生成启动 Logo 镜像。"
2445
- },
2446
- sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall)
2447
- };
2448
- }
2449
- const composeSummary = await localBootLogoCompose(args, sdk, logoPath, outputDir, outputImagePath, manifestPath);
2450
- if (!composeSummary.ok) {
2451
- return {
2452
- ok: false,
2453
- action: "boot_logo_update",
2454
- error: composeSummary.error,
2455
- sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall),
2456
- output_dir: outputDir
2457
- };
2458
- }
2459
- const approved = boolValue(args.approved) || boolValue(args.approve);
2460
- const readyForFlash = composeSummary.ready_for_flash === true;
2461
- const result = {
2462
- ok: readyForFlash,
2463
- action: "boot_logo_update",
2464
- board: "TaishanPi 1M RK3566",
2465
- status: readyForFlash ? (approved ? "composed_ready_for_flash" : "ready_for_approval") : "composed_not_flashable",
2466
- approved,
2467
- summary_for_user: readyForFlash
2468
- ? "启动 Logo 已在本机完成格式转换、resource.img 重打包和 boot.img 镜像生成。需要真实写入开发板时,应继续走受控的本地刷写入口。"
2469
- : "启动 Logo 本地生成未完成,不能直接作为刷写产物。",
2470
- sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall),
2471
- artifacts: compactObject({
2472
- logo_path: logoPath,
2473
- base_image_path: composeSummary.base_image_path,
2474
- output_image_path: outputImagePath,
2475
- output_resource_path: composeSummary.output_resource_path,
2476
- manifest_path: manifestPath
2477
- }),
2478
- compose: composeSummary,
2479
- next_step: readyForFlash
2480
- ? "如果用户明确批准写入开发板,后续应由同一高层工具调用 Local Bridge 的受控 boot 分区刷写能力,不能让模型手动拼命令。"
2481
- : "需要修复本地 SDK 图像转换或 resource 重打包工具后再刷写。",
2482
- raw_omitted: boolValue(args.include_raw) ? undefined : true
2966
+ const routed = await flashImageCompatibilityAction(args, {
2967
+ artifactKind: "boot_logo",
2968
+ defaultBoardId: "taishanpi-1m-rk3566",
2969
+ compatibilityAdapter: "dbt_boot_logo_update_to_agent_flash_image"
2970
+ });
2971
+ return {
2972
+ ...routed,
2973
+ guidance: "This compatibility tool now routes through the Native Agent flash_image action with artifact_kind=boot_logo. The Agent owns local logo conversion, resource.img repack, boot.img patching, and approved Local Bridge flashing."
2483
2974
  };
2484
- if (boolValue(args.include_raw)) {
2485
- result.raw = compactObject({ compose_result: composeSummary });
2486
- }
2487
- return result;
2488
2975
  }
2489
2976
 
2490
2977
  function shellQuote(value) {
@@ -2524,6 +3011,42 @@ function qtRuntimeCommand(remotePath) {
2524
3011
  ].join(" && ");
2525
3012
  }
2526
3013
 
3014
+ function remotePathBase(remotePath) {
3015
+ const value = asString(remotePath).replace(/\/+$/, "");
3016
+ return value ? path.posix.basename(value) : "";
3017
+ }
3018
+
3019
+ function resolveTaishanQtDeployPaths(args, artifactPath) {
3020
+ const artifactName = path.basename(artifactPath);
3021
+ const remoteFile = asString(args.remote_file || args.remoteFile);
3022
+ if (remoteFile) {
3023
+ return { remoteFile, expectedRemotePath: remoteFile };
3024
+ }
3025
+ const remoteDir = asString(args.remote_dir || args.remoteDir);
3026
+ if (remoteDir) {
3027
+ return {
3028
+ remoteDir,
3029
+ expectedRemotePath: `${remoteDir.replace(/\/+$/, "")}/${artifactName}`
3030
+ };
3031
+ }
3032
+ const remotePath = asString(args.remote_path || args.remotePath);
3033
+ if (!remotePath) {
3034
+ const defaultRemoteDir = "/userdata/embed-labs/apps";
3035
+ return {
3036
+ remoteDir: defaultRemoteDir,
3037
+ expectedRemotePath: `${defaultRemoteDir}/${artifactName}`
3038
+ };
3039
+ }
3040
+ if (remotePath.endsWith("/") || remotePathBase(remotePath) !== artifactName) {
3041
+ const normalizedDir = remotePath.replace(/\/+$/, "");
3042
+ return {
3043
+ remoteDir: normalizedDir,
3044
+ expectedRemotePath: `${normalizedDir}/${artifactName}`
3045
+ };
3046
+ }
3047
+ return { remoteFile: remotePath, expectedRemotePath: remotePath };
3048
+ }
3049
+
2527
3050
  function artifactPathFromBuild(buildResult) {
2528
3051
  const data = responseData(buildResult) || {};
2529
3052
  return asString(data.artifact_path || data.artifactPath || data.output_path || data.executable_path);
@@ -2541,109 +3064,40 @@ function taishanpiHostFromDeviceList(scanResult) {
2541
3064
  }
2542
3065
 
2543
3066
  async function taishanpiQtAppWorkflow(args) {
2544
- const request = asString(args.request || args.prompt);
2545
- const boardId = "taishanpi-1m-rk3566";
2546
- const selected = defaultQtExampleForRequest(request);
2547
- const outputDir = defaultActionDir("qt-app", args.output_dir || args.outputDir);
2548
- const explicitSource = asString(args.source_path || args.source);
2549
- let sourcePath = explicitSource || selected.sourcePath;
2550
- const projectDir = asString(args.project_dir || args.projectDir);
2551
- if (!explicitSource && projectDir && selected.sourcePath) {
2552
- mkdirSync(path.dirname(projectDir), { recursive: true });
2553
- cpSync(selected.sourcePath, projectDir, { recursive: true, force: true });
2554
- sourcePath = projectDir;
2555
- }
2556
- if (!sourcePath && selected.template !== "qt_smoke") {
2557
- return {
2558
- ok: false,
2559
- action: "taishanpi_qt_app_workflow",
2560
- error: {
2561
- code: "qt_template_not_found",
2562
- message: "未找到匹配需求的官方 Qt 示例模板。",
2563
- remediation: "请在包含 board-packs 的 Embed Labs 项目目录中运行,或传入 source_path 指向已有 Qt/CMake 工程。"
2564
- },
2565
- template: selected.template
2566
- };
2567
- }
2568
- const targetName = asString(args.target_name || args.targetName) || selected.targetName;
2569
- const buildDir = asString(args.build_dir || args.buildDir) || path.join(outputDir, targetName || "qt-app-build");
2570
-
2571
- const sdk = await ensureLocalToolchain(args, boardId, "qt");
2572
- const buildArgs = { build_dir: buildDir, target_name: targetName, release_root: sdk.releaseRoot };
2573
- if (sourcePath) buildArgs.source_path = sourcePath;
2574
- const buildResult = await localQtSmoke(buildArgs);
2575
- const artifactPath = artifactPathFromBuild(buildResult);
2576
-
2577
- let deployResult;
2578
- const approved = boolValue(args.approved) || boolValue(args.approve);
2579
- const shouldDeploy = boolValue(args.deploy) || boolValue(args.run);
2580
- if (shouldDeploy && approved) {
2581
- const scan = await runEmbed(["device", "list"]);
2582
- const host = asString(args.host) || taishanpiHostFromDeviceList(scan);
2583
- if (!host) {
2584
- deployResult = {
2585
- ok: false,
2586
- error: {
2587
- code: "runtime_host_not_found",
2588
- message: "Qt 程序已完成交叉编译,但当前未检测到可通过 SSH 部署的泰山派运行态主机。",
2589
- remediation: "确认泰山派已启动初始化系统并连接到当前电脑/网络后,再调用同一工具部署。"
2590
- }
2591
- };
2592
- } else if (!artifactPath) {
2593
- deployResult = {
2594
- ok: false,
2595
- error: {
2596
- code: "artifact_not_found",
2597
- message: "Qt 构建没有返回可部署的可执行文件路径。"
2598
- }
2599
- };
2600
- } else {
2601
- const remotePath = asString(args.remote_path || args.remotePath)
2602
- || `/userdata/embed-labs/apps/${path.basename(artifactPath)}`;
2603
- const command = ["deploy", "taishanpi", "--host", host, "--artifact", artifactPath, "--remote-path", remotePath, "--approve"];
2604
- if (boolValue(args.run)) {
2605
- command.push("--run", "--run-command", qtRuntimeCommand(remotePath));
2606
- }
2607
- deployResult = await runEmbed(command);
2608
- }
2609
- }
2610
-
2611
- const buildData = responseData(buildResult) || {};
2612
- const deployData = responseData(deployResult) || {};
2613
- const result = {
2614
- ok: buildResult?.response?.ok === true && (!deployResult || deployResult?.response?.ok === true || deployResult?.ok === false),
2615
- action: "taishanpi_qt_app_workflow",
2616
- board: "TaishanPi 1M RK3566",
2617
- status: deployResult ? (deployResult?.response?.ok === true ? "built_and_deployed" : "built_deploy_pending") : "built",
2618
- template: selected.template,
2619
- request: request || undefined,
2620
- sdk: compactSdkState(sdk.sdkCheck, sdk.sdkInstall),
2621
- source_path: sourcePath || "built-in Qt smoke source",
2622
- build: compactObject({
2623
- build_dir: buildDir,
2624
- target_name: targetName,
2625
- artifact_path: artifactPath,
2626
- artifact_size_bytes: buildData.artifact_size_bytes,
2627
- artifact_sha256: buildData.artifact_sha256,
2628
- summary_for_user: buildData.summary_for_user
2629
- }),
2630
- deploy: deployResult ? compactObject({
2631
- ok: deployResult?.response?.ok ?? deployResult?.ok,
2632
- host: deployData.host,
2633
- remote_path: deployData.remote_path,
2634
- ran: deployData.ran,
2635
- error: deployResult?.error || deployResult?.response?.error
2636
- }) : undefined,
2637
- summary_for_user: deployResult?.response?.ok === true
2638
- ? "Qt 程序已按泰山派 Qt 运行库路径完成构建、部署并启动。"
2639
- : "Qt 程序已通过本机 LLVM/Qt 工具链完成交叉编译。",
2640
- next_step: deployResult ? "查看部署/运行结果;如需协议采集,再调用 RP2350 Monitor 的对应高层工具。" : "如需上板运行,用户批准后用同一工具传入 deploy=true、run=true、approved=true。",
2641
- raw_omitted: boolValue(args.include_raw) ? undefined : true
3067
+ const payload = compactObject({
3068
+ board_id: asString(args.board_id || args.board) || "taishanpi-1m-rk3566",
3069
+ local_device_id: asString(args.local_device_id || args.localDeviceId || args.device_id || args.deviceId || args.device),
3070
+ host: asString(args.host),
3071
+ user: asString(args.user),
3072
+ artifact_path: asString(args.artifact_path || args.artifact || args.output_path || args.output),
3073
+ remote_path: asString(args.remote_path || args.remotePath),
3074
+ remote_dir: asString(args.remote_dir || args.remoteDir),
3075
+ remote_file: asString(args.remote_file || args.remoteFile),
3076
+ wrapper_script: asString(args.wrapper_script || args.run_script || args.wrapperScript || args.runScript),
3077
+ run_script: asString(args.run_script || args.runScript),
3078
+ run_command: asString(args.run_command || args.runCommand),
3079
+ run: boolValue(args.run) || undefined,
3080
+ approve: boolValue(args.approve) || boolValue(args.approved) || undefined,
3081
+ release_root: asString(args.release_root || args.releaseRoot),
3082
+ profile: asString(args.profile || args.build_profile || args.buildProfile),
3083
+ query: asString(args.request || args.prompt || args.description)
3084
+ });
3085
+ const routed = await agentActionRun({
3086
+ action_id: "deploy_app",
3087
+ arguments_json: JSON.stringify(payload),
3088
+ include_raw: args.include_raw,
3089
+ timeout_ms: args.timeout_ms
3090
+ });
3091
+ return {
3092
+ ok: routed.ok,
3093
+ compatibility_adapter: "dbt_taishanpi_qt_app_workflow_to_agent_deploy_app",
3094
+ agent_action: "deploy_app",
3095
+ execution: routed.execution,
3096
+ deploy: routed.execution?.deploy,
3097
+ summary_for_user: routed.execution?.summary ?? routed.answer,
3098
+ response: boolValue(args.include_raw) ? routed.response : undefined,
3099
+ guidance: "This compatibility tool now routes through the Native Agent deploy_app action. Use build_project first when a local artifact has not been built yet; deploy_app owns target resolution, remote path semantics, wrapper execution, and approved Local Bridge deployment."
2642
3100
  };
2643
- if (boolValue(args.include_raw)) {
2644
- result.raw = compactObject({ build_result: buildResult, deploy_result: deployResult });
2645
- }
2646
- return result;
2647
3101
  }
2648
3102
 
2649
3103
  async function localToolchainLatest(args) {
@@ -2678,25 +3132,98 @@ async function localToolchainCurrent(args) {
2678
3132
  return await runEmbed(command);
2679
3133
  }
2680
3134
 
3135
+ function compactLocalToolchainInstallResult(result) {
3136
+ const data = responseData(result) || {};
3137
+ const source = data.source && typeof data.source === "object" ? data.source : {};
3138
+ const validation = data.validation && typeof data.validation === "object" ? data.validation : {};
3139
+ const packages = Array.isArray(data.packages) ? data.packages : [];
3140
+ const sourceComponents = Array.isArray(source.components) ? source.components : [];
3141
+ return {
3142
+ ok: result?.response?.ok ?? result?.ok ?? true,
3143
+ board_id: data.board_id,
3144
+ version: data.version,
3145
+ channel: data.channel,
3146
+ mode: data.mode,
3147
+ install_root: data.install_root,
3148
+ release_root: data.release_root,
3149
+ source: compactObject({
3150
+ kind: source.kind,
3151
+ mirror_kind: source.mirror_kind,
3152
+ component_count: sourceComponents.length || undefined
3153
+ }),
3154
+ packages: packages.map((item) => compactObject({
3155
+ package_id: item.package_id || item.id,
3156
+ version: item.version,
3157
+ kind: item.kind
3158
+ })).slice(0, 8),
3159
+ installed_paths: Array.isArray(data.installed_paths) ? data.installed_paths.slice(0, 12) : undefined,
3160
+ removed_old_versions: data.removed_old_versions,
3161
+ validation: compactObject({
3162
+ ok: validation.ok,
3163
+ mode: validation.mode,
3164
+ missing_groups: validation.missing_groups,
3165
+ path_leaks: validation.path_leaks?.length ? validation.path_leaks.length : undefined,
3166
+ summary_for_user: validation.summary_for_user
3167
+ }),
3168
+ summary_for_user: validation.summary_for_user || `Local toolchain ${data.board_id || ""} ${data.version || ""} installed.`,
3169
+ raw_omitted: true
3170
+ };
3171
+ }
3172
+
2681
3173
  async function localToolchainInstall(args) {
2682
- const command = ["local", "toolchain", "install"];
2683
- pushOptional(command, "board", args.board_id || args.board);
2684
- pushOptional(command, "channel", args.channel);
2685
- pushOptional(command, "metadata-root", args.metadata_root || args.metadataRoot);
2686
- pushOptional(command, "source-url", args.source_url || args.sourceUrl);
2687
- pushOptional(command, "source-release-root", args.source_release_root || args.sourceReleaseRoot);
2688
- pushOptional(command, "install-root", args.install_root || args.installRoot);
2689
- pushOptional(command, "mode", args.mode);
2690
- if (boolValue(args.force)) command.push("--force");
2691
- return await runEmbed(command);
3174
+ const payload = compactObject({
3175
+ operation: boolValue(args.update) ? "update" : "install",
3176
+ board_id: asString(args.board_id || args.board),
3177
+ channel: asString(args.channel),
3178
+ metadata_root: asString(args.metadata_root || args.metadataRoot),
3179
+ source_url: asString(args.source_url || args.sourceUrl),
3180
+ source_release_root: asString(args.source_release_root || args.sourceReleaseRoot),
3181
+ install_root: asString(args.install_root || args.installRoot),
3182
+ mode: asString(args.mode),
3183
+ force: boolValue(args.force) || undefined
3184
+ });
3185
+ const routed = await agentActionRun({
3186
+ action_id: "install_toolchain",
3187
+ arguments_json: JSON.stringify(payload),
3188
+ include_raw: args.include_raw
3189
+ });
3190
+ return {
3191
+ ok: routed.ok,
3192
+ compatibility_adapter: "dbt_local_toolchain_install_to_agent_install_toolchain",
3193
+ agent_action: "install_toolchain",
3194
+ operation: payload.operation,
3195
+ execution: routed.execution,
3196
+ toolchain: routed.execution?.toolchain,
3197
+ toolchain_operation: routed.execution?.toolchain_operation,
3198
+ summary_for_user: routed.execution?.summary ?? routed.answer,
3199
+ response: boolValue(args.include_raw) ? routed.response : undefined,
3200
+ guidance: "This compatibility tool now routes through the local EmbedLabs Agent install_toolchain action. Prefer embedlabs_chat for natural-language SDK requests or embedlabs_action_run for explicit action calls."
3201
+ };
2692
3202
  }
2693
3203
 
2694
3204
  async function localToolchainUninstall(args) {
2695
3205
  const boardId = asString(args.board_id || args.board);
2696
3206
  if (!boardId) throw new Error("board_id is required");
2697
- const command = ["local", "toolchain", "uninstall", "--board", boardId];
2698
- pushOptional(command, "install-root", args.install_root || args.installRoot);
2699
- return await runEmbed(command);
3207
+ const routed = await agentActionRun({
3208
+ action_id: "install_toolchain",
3209
+ arguments_json: JSON.stringify(compactObject({
3210
+ operation: "uninstall",
3211
+ board_id: boardId,
3212
+ install_root: asString(args.install_root || args.installRoot)
3213
+ })),
3214
+ include_raw: args.include_raw
3215
+ });
3216
+ return {
3217
+ ok: routed.ok,
3218
+ compatibility_adapter: "dbt_local_toolchain_uninstall_to_agent_install_toolchain",
3219
+ agent_action: "install_toolchain",
3220
+ operation: "uninstall",
3221
+ execution: routed.execution,
3222
+ toolchain_operation: routed.execution?.toolchain_operation,
3223
+ summary_for_user: routed.execution?.summary ?? routed.answer,
3224
+ response: boolValue(args.include_raw) ? routed.response : undefined,
3225
+ guidance: "This compatibility tool now routes through the local EmbedLabs Agent install_toolchain action with operation=uninstall."
3226
+ };
2700
3227
  }
2701
3228
 
2702
3229
  async function localToolchainValidate(args) {
@@ -2715,21 +3242,33 @@ function compactLocalToolchainValidationResult(result) {
2715
3242
  const missing = Array.isArray(data.missing_paths) ? data.missing_paths : [];
2716
3243
  const pathLeaks = Array.isArray(data.path_leaks) ? data.path_leaks : [];
2717
3244
  const notes = Array.isArray(data.notes) ? data.notes.map(summarizePublicText).filter(Boolean).slice(0, 10) : [];
3245
+ const repairCommand = data.ok === true
3246
+ ? undefined
3247
+ : compactObject({
3248
+ action_id: "install_toolchain",
3249
+ operation: "install",
3250
+ board_id: data.board_id,
3251
+ mode: data.mode,
3252
+ command: `embedlabs agent action run install_toolchain --operation install${data.board_id ? ` --board-id ${data.board_id}` : ""}${data.mode ? ` --mode ${data.mode}` : ""} --json`
3253
+ });
2718
3254
  return compactObject({
2719
3255
  ok: data.ok === true,
2720
3256
  board_id: data.board_id,
2721
3257
  mode: data.mode,
3258
+ release_root: data.release_root,
2722
3259
  host: data.host,
2723
3260
  summary_for_user: data.summary_for_user,
2724
3261
  missing_groups: Array.isArray(data.missing_groups) ? data.missing_groups : undefined,
2725
- repair_command: data.repair_command,
3262
+ repair_command: repairCommand?.command,
3263
+ repair_agent_action: repairCommand,
3264
+ original_repair_command_omitted: Boolean(data.repair_command),
2726
3265
  checked_count: checked.length,
2727
3266
  missing_count: missing.length,
2728
3267
  path_leaks_count: pathLeaks.length,
2729
3268
  notes,
2730
3269
  guidance: data.ok === true
2731
3270
  ? "Summarize this as ready. Do not print checked_paths or internal registry/source paths unless the user asks for raw diagnostics."
2732
- : "Read summary_for_user, missing_groups, and repair_command. If the user wants to proceed with this workflow, call dbt_local_toolchain_install with the same board_id and mode.",
3271
+ : "Read summary_for_user and missing_groups, then repair through embedlabs_action_run with action_id=install_toolchain and operation=install/update. dbt_local_toolchain_install is retained only for compact/full engineering validation.",
2733
3272
  raw_omitted: true
2734
3273
  });
2735
3274
  }
@@ -2745,46 +3284,58 @@ function isRp2350InitialTarget(value) {
2745
3284
  }
2746
3285
 
2747
3286
  async function initialImageFlash(args) {
2748
- const requestedBoard = asString(args.board_id || args.board);
2749
- if (isRp2350InitialTarget(requestedBoard)) {
2750
- return await rp2350InitialFirmwareFlash(args);
2751
- }
2752
- if (isTaishanpiInitialTarget(requestedBoard)) {
2753
- return await taishanpiInitialImageFlash(args);
2754
- }
2755
-
2756
- const scan = await runEmbed(["device", "list"]);
2757
- const scanData = responseData(scan) || {};
2758
- const devices = Array.isArray(scanData.development_boards) ? scanData.development_boards : [];
2759
- const candidates = devices
2760
- .filter((item) => item?.board_id === "taishanpi" || isRp2350Device(item))
2761
- .map((item) => compactObject({
2762
- local_device_id: item.local_device_id || item.device_id,
2763
- board_id: item.board_id,
2764
- variant_id: item.variant_id,
2765
- display_name: item.display_name || item.name,
2766
- status: item.status
2767
- }));
2768
-
2769
- if (candidates.length === 1) {
2770
- const candidate = candidates[0];
2771
- const nextArgs = { ...args, local_device_id: args.local_device_id || candidate.local_device_id };
2772
- if (candidate.board_id === "taishanpi") return await taishanpiInitialImageFlash(nextArgs);
2773
- return await rp2350InitialFirmwareFlash({ ...nextArgs, board_id: candidate.variant_id || candidate.board_id });
2774
- }
3287
+ return await flashImageCompatibilityAction(args, {
3288
+ artifactKind: "initial",
3289
+ compatibilityAdapter: "dbt_initial_image_flash_to_agent_flash_image"
3290
+ });
3291
+ }
2775
3292
 
3293
+ function flashImageCompatibilityPayload(args, artifactKind, defaultBoardId) {
3294
+ return compactObject({
3295
+ board_id: asString(args.board_id || args.board) || defaultBoardId,
3296
+ local_device_id: asString(args.local_device_id || args.localDeviceId || args.device_id || args.deviceId || args.device),
3297
+ host: asString(args.host),
3298
+ user: asString(args.user),
3299
+ artifact_kind: artifactKind,
3300
+ artifact_path: asString(args.artifact_path || args.artifact),
3301
+ image_dir: asString(args.image_dir || args.imageDir),
3302
+ channel: asString(args.channel),
3303
+ metadata_root: asString(args.metadata_root || args.metadataRoot),
3304
+ source_url: asString(args.source_url || args.sourceUrl),
3305
+ source_release_root: asString(args.source_release_root || args.sourceReleaseRoot),
3306
+ install_root: asString(args.install_root || args.installRoot),
3307
+ logo_path: asString(args.logo_path || args.logo),
3308
+ kernel_logo_path: asString(args.kernel_logo_path || args.kernelLogo),
3309
+ base_image_path: asString(args.base_image_path || args.baseImage),
3310
+ output_image_path: asString(args.output_image_path || args.outputImage || args.output),
3311
+ output_resource_path: asString(args.output_resource_path || args.outputResource),
3312
+ target_volume: asString(args.target_volume || args.targetVolume || args.target_volume_path || args.targetVolumePath),
3313
+ partition: asString(args.partition),
3314
+ partitions: asString(args.partitions),
3315
+ profile: asString(args.profile),
3316
+ force: boolValue(args.force) || undefined,
3317
+ approve: boolValue(args.approve) || boolValue(args.approved) || undefined
3318
+ });
3319
+ }
3320
+
3321
+ async function flashImageCompatibilityAction(args, options) {
3322
+ const payload = flashImageCompatibilityPayload(args, options.artifactKind, options.defaultBoardId);
3323
+ const routed = await agentActionRun({
3324
+ action_id: "flash_image",
3325
+ arguments_json: JSON.stringify(payload),
3326
+ include_raw: args.include_raw,
3327
+ timeout_ms: args.timeout_ms
3328
+ });
2776
3329
  return {
2777
- ok: false,
2778
- error: {
2779
- code: candidates.length > 1 ? "ambiguous_board" : "target_not_found",
2780
- message: candidates.length > 1
2781
- ? "检测到多块可刷写开发板,请指定要刷写的开发板。"
2782
- : "当前未检测到可刷写初始化镜像的支持开发板。",
2783
- candidates,
2784
- remediation: candidates.length > 1
2785
- ? "重新调用 dbt_initial_image_flash,并传入对应 local_device_id。"
2786
- : "请连接开发板,或让开发板进入 Loader/Maskrom/BOOTSEL 后重新查询开发板状态。"
2787
- }
3330
+ ok: routed.ok,
3331
+ compatibility_adapter: options.compatibilityAdapter,
3332
+ agent_action: "flash_image",
3333
+ artifact_kind: payload.artifact_kind,
3334
+ execution: routed.execution,
3335
+ flash: routed.execution?.flash,
3336
+ summary_for_user: routed.execution?.summary ?? routed.answer,
3337
+ response: boolValue(args.include_raw) ? routed.response : undefined,
3338
+ guidance: "This compatibility tool now routes through the Native Agent flash_image action. Review the plan and re-run with approved=true only after explicit user approval."
2788
3339
  };
2789
3340
  }
2790
3341
 
@@ -2906,110 +3457,19 @@ function compactInitialFlashResult({ boardLabel, sdkCheck, sdkInstall, imageDir,
2906
3457
  }
2907
3458
 
2908
3459
  async function taishanpiInitialImageFlash(args) {
2909
- const sdkBoardId = asString(args.board_id || args.board) || "taishanpi-1m-rk3566";
2910
- const explicitImageDir = asString(args.image_dir || args.imageDir);
2911
- let sdkCheck = null;
2912
- let sdkInstall = null;
2913
- let releaseRoot = "";
2914
- if (!explicitImageDir) {
2915
- sdkCheck = await localToolchainValidate({ board_id: sdkBoardId, mode: "images" });
2916
- const checkData = responseData(sdkCheck) || {};
2917
- if (checkData.ok) {
2918
- releaseRoot = asString(checkData.release_root);
2919
- } else {
2920
- const installArgs = {
2921
- board_id: sdkBoardId,
2922
- channel: args.channel,
2923
- metadata_root: args.metadata_root || args.metadataRoot,
2924
- install_root: args.install_root || args.installRoot,
2925
- mode: "images",
2926
- force: args.force
2927
- };
2928
- sdkInstall = await localToolchainInstall(installArgs);
2929
- const installedData = responseData(sdkInstall) || {};
2930
- releaseRoot = asString(installedData.release_root);
2931
- }
2932
- }
2933
- const imageDir = explicitImageDir || (releaseRoot ? path.join(releaseRoot, "images", "current") : "");
2934
- const localDeviceId = asString(args.local_device_id || args.localDeviceId || args.device_id || args.deviceId);
2935
- const planCommand = ["flash", "plan", "--board", "taishanpi"];
2936
- if (imageDir) pushOptional(planCommand, "image-dir", imageDir);
2937
- if (localDeviceId) pushOptional(planCommand, "local-device-id", localDeviceId);
2938
- const plan = await runEmbed(planCommand);
2939
- const approved = boolValue(args.approved) || boolValue(args.approve);
2940
- if (!approved) {
2941
- return compactInitialFlashResult({
2942
- boardLabel: "TaishanPi 1M RK3566",
2943
- sdkCheck,
2944
- sdkInstall,
2945
- imageDir,
2946
- plan,
2947
- approved,
2948
- includeRaw: boolValue(args.include_raw)
2949
- });
2950
- }
2951
- const runCommand = ["flash", "run", "--board", "taishanpi", "--approve"];
2952
- if (imageDir) pushOptional(runCommand, "image-dir", imageDir);
2953
- if (localDeviceId) pushOptional(runCommand, "local-device-id", localDeviceId);
2954
- const flashed = await runEmbed(runCommand);
2955
- const wait = args.wait === undefined ? true : boolValue(args.wait);
2956
- const jobId = localJobIdFromRun(flashed);
2957
- const finalStatus = wait && jobId ? await waitForLocalJob(jobId, Number(args.timeout_ms) || 180000) : undefined;
2958
- return compactInitialFlashResult({
2959
- boardLabel: "TaishanPi 1M RK3566",
2960
- sdkCheck,
2961
- sdkInstall,
2962
- imageDir,
2963
- plan,
2964
- runResult: flashed,
2965
- finalStatus,
2966
- approved,
2967
- includeRaw: boolValue(args.include_raw)
3460
+ return await flashImageCompatibilityAction(args, {
3461
+ artifactKind: "initial_image",
3462
+ defaultBoardId: "taishanpi-1m-rk3566",
3463
+ compatibilityAdapter: "dbt_taishanpi_initial_image_flash_to_agent_flash_image"
2968
3464
  });
2969
3465
  }
2970
3466
 
2971
3467
  async function rp2350MonitorFirmwareFlash(args) {
2972
- const boardId = asString(args.board_id || args.board) || "pico2w-rp2350-monitor";
2973
- const installArgs = {
2974
- board_id: boardId,
2975
- channel: args.channel,
2976
- metadata_root: args.metadata_root || args.metadataRoot,
2977
- install_root: args.install_root || args.installRoot,
2978
- mode: "firmware",
2979
- force: args.force
2980
- };
2981
- const installed = await localToolchainInstall(installArgs);
2982
- const installedData = responseData(installed) || {};
2983
- const releaseRoot = asString(installedData.release_root);
2984
- const artifactPath = asString(args.artifact_path || args.artifact)
2985
- || (releaseRoot ? path.join(releaseRoot, "toolkit-runtime", "rp2350-monitor", "firmware", "rp2350_monitor.uf2") : "");
2986
- if (!artifactPath) {
2987
- throw new Error("RP2350 Monitor firmware UF2 could not be resolved after local toolchain install.");
2988
- }
2989
- const plan = await runEmbed(["flash", "plan", "--board", "rp2350", "--artifact", artifactPath]);
2990
- const approved = boolValue(args.approved) || boolValue(args.approve);
2991
- if (!approved) {
2992
- const planData = responseData(plan) || plan;
2993
- const automaticBootsel = !!planData.automatic_bootsel
2994
- || (Array.isArray(planData.steps) && planData.steps.some((step) => step && step.kind === "picotool_load_force_usb"));
2995
- return {
2996
- ok: true,
2997
- firmware_install: installed,
2998
- artifact_path: artifactPath,
2999
- flash_plan: plan,
3000
- next_step: automaticBootsel
3001
- ? "Flash plan is ready. After user approval, call this tool again with approved=true."
3002
- : "Flash plan needs a visible UF2 target volume before it can run."
3003
- };
3004
- }
3005
- const flashed = await runEmbed(["flash", "run", "--board", "rp2350", "--artifact", artifactPath, "--approve"]);
3006
- return {
3007
- ok: true,
3008
- firmware_install: installed,
3009
- artifact_path: artifactPath,
3010
- flash_plan: plan,
3011
- flash_result: flashed
3012
- };
3468
+ return await flashImageCompatibilityAction(args, {
3469
+ artifactKind: "monitor_firmware",
3470
+ defaultBoardId: "pico2w-rp2350-monitor",
3471
+ compatibilityAdapter: "dbt_rp2350_monitor_firmware_flash_to_agent_flash_image"
3472
+ });
3013
3473
  }
3014
3474
 
3015
3475
  function rp2350InitialFirmwarePath(boardId, releaseRoot) {
@@ -3019,46 +3479,11 @@ function rp2350InitialFirmwarePath(boardId, releaseRoot) {
3019
3479
  }
3020
3480
 
3021
3481
  async function rp2350InitialFirmwareFlash(args) {
3022
- const boardId = asString(args.board_id || args.board) || "pico2w-rp2350-monitor";
3023
- const installArgs = {
3024
- board_id: boardId,
3025
- channel: args.channel,
3026
- metadata_root: args.metadata_root || args.metadataRoot,
3027
- install_root: args.install_root || args.installRoot,
3028
- mode: "firmware",
3029
- force: args.force
3030
- };
3031
- const installed = await localToolchainInstall(installArgs);
3032
- const installedData = responseData(installed) || {};
3033
- const releaseRoot = asString(installedData.release_root);
3034
- const artifactPath = asString(args.artifact_path || args.artifact) || rp2350InitialFirmwarePath(boardId, releaseRoot);
3035
- if (!artifactPath) {
3036
- throw new Error("RP2350 initialization firmware UF2 could not be resolved after local toolchain install.");
3037
- }
3038
- const plan = await runEmbed(["flash", "plan", "--board", "rp2350", "--artifact", artifactPath]);
3039
- const approved = boolValue(args.approved) || boolValue(args.approve);
3040
- if (!approved) {
3041
- const planData = responseData(plan) || plan;
3042
- const automaticBootsel = !!planData.automatic_bootsel
3043
- || (Array.isArray(planData.steps) && planData.steps.some((step) => step && step.kind === "picotool_load_force_usb"));
3044
- return {
3045
- ok: true,
3046
- firmware_install: installed,
3047
- artifact_path: artifactPath,
3048
- flash_plan: plan,
3049
- next_step: automaticBootsel
3050
- ? "Initialization flash plan is ready. After user approval, call this tool again with approved=true."
3051
- : "Initialization flash plan needs a visible UF2 target volume before it can run."
3052
- };
3053
- }
3054
- const flashed = await runEmbed(["flash", "run", "--board", "rp2350", "--artifact", artifactPath, "--approve"]);
3055
- return {
3056
- ok: true,
3057
- firmware_install: installed,
3058
- artifact_path: artifactPath,
3059
- flash_plan: plan,
3060
- flash_result: flashed
3061
- };
3482
+ return await flashImageCompatibilityAction(args, {
3483
+ artifactKind: "initial_firmware",
3484
+ defaultBoardId: "pico2w-rp2350-monitor",
3485
+ compatibilityAdapter: "dbt_rp2350_initial_firmware_flash_to_agent_flash_image"
3486
+ });
3062
3487
  }
3063
3488
 
3064
3489
  async function pluginUpdateCheck(args) {
@@ -3068,7 +3493,7 @@ async function pluginUpdateCheck(args) {
3068
3493
  pushOptional(command, "codex-target", args.codex_target || args.codexTarget);
3069
3494
  pushOptional(command, "opencode-target", args.opencode_target || args.opencodeTarget);
3070
3495
  pushOptional(command, "trae-target", args.trae_target || args.traeTarget);
3071
- return await runEmbed(command);
3496
+ return sanitizeMcpPublicValue(await runEmbed(command));
3072
3497
  }
3073
3498
 
3074
3499
  async function pluginUpdate(args) {
@@ -3082,7 +3507,7 @@ async function pluginUpdate(args) {
3082
3507
  pushOptional(command, "codex-target", args.codex_target || args.codexTarget);
3083
3508
  pushOptional(command, "opencode-target", args.opencode_target || args.opencodeTarget);
3084
3509
  pushOptional(command, "trae-target", args.trae_target || args.traeTarget);
3085
- return await runEmbed(command);
3510
+ return sanitizeMcpPublicValue(await runEmbed(command));
3086
3511
  }
3087
3512
 
3088
3513
  async function localCompile(args) {
@@ -3091,7 +3516,6 @@ async function localCompile(args) {
3091
3516
  if (!sourcePath) throw new Error("source_path is required");
3092
3517
  if (!outputPath) throw new Error("output_path is required");
3093
3518
  const command = ["local", "compile", "taishanpi", "--source", sourcePath, "--output", outputPath];
3094
- pushOptional(command, "account", args.account_id || args.account);
3095
3519
  pushOptional(command, "release-root", args.release_root || args.releaseRoot);
3096
3520
  return await runEmbed(command);
3097
3521
  }
@@ -3100,240 +3524,30 @@ async function localQtSmoke(args) {
3100
3524
  const buildDir = asString(args.build_dir || args.buildDir);
3101
3525
  if (!buildDir) throw new Error("build_dir is required");
3102
3526
  const command = ["local", "build", "qt-smoke", "--build-dir", buildDir];
3103
- pushOptional(command, "account", args.account_id || args.account);
3104
3527
  pushOptional(command, "source", args.source_path || args.source);
3105
3528
  pushOptional(command, "target-name", args.target_name || args.targetName);
3106
3529
  pushOptional(command, "release-root", args.release_root || args.releaseRoot);
3107
3530
  return await runEmbed(command);
3108
3531
  }
3109
3532
 
3110
- function serverWorkspaceDisabledResult() {
3111
- return {
3112
- ok: false,
3113
- error: {
3114
- code: "server_workspaces_disabled",
3115
- message: "Embed Labs now uses the user's local SDK/toolchain environment for source edits, builds, and board operation. Server-side user workspaces, Docker builds, and image generation are not exposed by the plugin.",
3116
- remediation: "Use dbt_local_toolchain_install, dbt_local_toolchain_validate, dbt_local_compile, dbt_local_qt_smoke, and Local Bridge board tools. Cloud API remains available for account login, device binding, board knowledge, plugin releases, MCP service logs, and SDK downloads."
3117
- }
3118
- };
3119
- }
3120
-
3121
- function isServerWorkspaceToolName(name) {
3122
- return [
3123
- "dbt_workspace_provision",
3124
- "dbt_application_generate",
3125
- "dbt_source_list",
3126
- "dbt_source_get",
3127
- "dbt_source_search",
3128
- "dbt_source_put",
3129
- "dbt_source_patch",
3130
- "dbt_application_compile",
3131
- "dbt_image_generate",
3132
- "dbt_workspace_release"
3133
- ].includes(name);
3134
- }
3135
-
3136
- function isServerWorkspaceAction(action) {
3137
- return [
3138
- "provision",
3139
- "workspace-provision",
3140
- "source-list",
3141
- "list-source",
3142
- "source-get",
3143
- "source-read",
3144
- "read-source",
3145
- "source-search",
3146
- "search-source",
3147
- "source-put",
3148
- "source-write",
3149
- "write-source",
3150
- "source-patch",
3151
- "patch-source",
3152
- "app",
3153
- "application",
3154
- "application-generate",
3155
- "build-run",
3156
- "compile",
3157
- "application-compile",
3158
- "image",
3159
- "image-generate",
3160
- "release",
3161
- "workspace-release"
3162
- ].includes(action);
3163
- }
3164
-
3165
- async function workspaceProvision(args) {
3166
- const account = asString(args.account_id || args.account);
3167
- const project = asString(args.project_id || args.project);
3168
- if (!account) throw new Error("account_id is required");
3169
- if (!project) throw new Error("project_id is required");
3170
- const command = [
3171
- "build",
3172
- "workspace",
3173
- "provision",
3174
- "--account",
3175
- account,
3176
- "--project",
3177
- project,
3178
- "--template",
3179
- asString(args.template_id || args.template) || DEFAULT_TEMPLATE_ID,
3180
- "--copy-mode",
3181
- asString(args.copy_mode) || "skeleton"
3182
- ];
3183
- if (boolValue(args.dry_run)) command.push("--dry-run");
3184
- return await runEmbed(command);
3185
- }
3186
-
3187
- async function applicationGenerate(args) {
3188
- const workspace = asString(args.workspace_id || args.workspace);
3189
- const prompt = asString(args.prompt || args.request);
3190
- if (!workspace) throw new Error("workspace_id is required");
3191
- if (!prompt) throw new Error("prompt is required");
3192
- const command = ["build", "application", "generate", "--workspace", workspace, "--prompt", prompt];
3193
- pushOptional(command, "account", args.account_id || args.account);
3194
- pushOptional(command, "provider", args.provider);
3195
- pushOptional(command, "model", args.model);
3196
- pushOptional(command, "target", args.target);
3197
- const generated = await runEmbed(command);
3198
- if (!asString(args.download_output)) return generated;
3199
- const artifact = pickArtifact(generated, "firmware");
3200
- if (!artifact?.artifact_id) throw new Error("No downloadable application artifact found");
3201
- const downloaded = await runEmbed(["artifact", "download", artifact.artifact_id, "--output", args.download_output]);
3202
- return { generated, selected_artifact: artifact, downloaded };
3203
- }
3204
-
3205
- async function sourceList(args) {
3206
- const workspace = asString(args.workspace_id || args.workspace);
3207
- if (!workspace) throw new Error("workspace_id is required");
3208
- return await runEmbed(["build", "workspace", "source", "list", workspace]);
3209
- }
3210
-
3211
- async function sourceGet(args) {
3212
- const workspace = asString(args.workspace_id || args.workspace);
3213
- const sourcePath = asString(args.source_path || args.path);
3214
- if (!workspace) throw new Error("workspace_id is required");
3215
- if (!sourcePath) throw new Error("source_path is required");
3216
- const command = ["build", "workspace", "source", "get", workspace, "--path", sourcePath];
3217
- pushOptional(command, "output", args.output_path || args.output);
3218
- return await runEmbed(command);
3219
- }
3220
-
3221
- async function sourceSearch(args) {
3222
- const workspace = asString(args.workspace_id || args.workspace);
3223
- if (!workspace) throw new Error("workspace_id is required");
3224
- const command = ["build", "workspace", "source", "search", workspace];
3225
- pushOptional(command, "query", args.query);
3226
- pushOptional(command, "glob", args.glob);
3227
- pushOptional(command, "limit", args.limit);
3228
- return await runEmbed(command);
3229
- }
3230
-
3231
- async function sourcePut(args) {
3232
- const workspace = asString(args.workspace_id || args.workspace);
3233
- const localPath = asString(args.local_path || args.local);
3234
- const sourcePath = asString(args.source_path || args.path);
3235
- if (!workspace) throw new Error("workspace_id is required");
3236
- if (!localPath) throw new Error("local_path is required");
3237
- if (!sourcePath) throw new Error("source_path is required");
3238
- const command = ["build", "workspace", "source", "put", workspace, "--file", `${localPath}:${sourcePath}`];
3239
- pushOptional(command, "account", args.account_id || args.account);
3240
- return await runEmbed(command);
3241
- }
3242
-
3243
- async function sourcePatch(args) {
3244
- const workspace = asString(args.workspace_id || args.workspace);
3245
- const patchPath = asString(args.patch_path || args.patch);
3246
- if (!workspace) throw new Error("workspace_id is required");
3247
- if (!patchPath) throw new Error("patch_path is required");
3248
- const command = ["build", "workspace", "source", "patch", workspace, "--patch", patchPath];
3249
- pushOptional(command, "account", args.account_id || args.account);
3250
- return await runEmbed(command);
3251
- }
3252
-
3253
- async function applicationCompile(args) {
3254
- const workspace = asString(args.workspace_id || args.workspace);
3255
- if (!workspace) throw new Error("workspace_id is required");
3256
- const command = ["build", "application", "compile", "--workspace", workspace];
3257
- pushOptional(command, "account", args.account_id || args.account);
3258
- pushOptional(command, "source", args.source_path || args.source);
3259
- pushOptional(command, "output-name", args.output_name);
3260
- pushOptional(command, "execution-mode", args.execution_mode);
3261
- pushOptional(command, "compiler", args.compiler);
3262
- return await runEmbed(command);
3263
- }
3264
-
3265
- async function taskStatus(args) {
3266
- const task = asString(args.task_id || args.task);
3267
- if (!task) throw new Error("task_id is required");
3268
- return await runEmbed(["cloud", "task", "status", task]);
3269
- }
3270
-
3271
- function safeArtifactOutputFileName(name) {
3272
- return asString(name).replace(/[^A-Za-z0-9._-]+/g, "_") || "task.log";
3273
- }
3274
-
3275
- async function taskLogDownload(args) {
3276
- const task = asString(args.task_id || args.task);
3277
- if (!task) throw new Error("task_id is required");
3278
- const status = await taskStatus({ task_id: task });
3279
- const data = responseData(status);
3280
- const artifacts = Array.isArray(data?.artifacts) ? data.artifacts : [];
3281
- const log = artifacts.find((item) => item.kind === "log" || asString(item.name).endsWith(".log"));
3282
- if (!log?.artifact_id) throw new Error(`Task ${task} does not expose a log artifact`);
3283
- const output = asString(args.output_path || args.output) || path.join(".embed-labs", "artifacts", `${task}-${safeArtifactOutputFileName(log.name)}`);
3284
- const downloaded = await runEmbed(["artifact", "download", log.artifact_id, "--output", output]);
3285
- return { status, selected_artifact: log, downloaded };
3286
- }
3287
-
3288
- async function imageGenerate(args) {
3289
- const workspace = asString(args.workspace_id || args.workspace);
3290
- const prompt = asString(args.prompt || args.request);
3291
- if (!workspace) throw new Error("workspace_id is required");
3292
- if (!prompt) throw new Error("prompt is required");
3293
- const command = ["build", "image", "generate", "--workspace", workspace, "--prompt", prompt];
3294
- pushOptional(command, "account", args.account_id || args.account);
3295
- pushOptional(command, "provider", args.provider);
3296
- pushOptional(command, "model", args.model);
3297
- pushOptional(command, "image-profile", args.image_profile);
3298
- pushOptional(command, "execution-mode", args.execution_mode);
3299
- pushOptional(command, "worker-pool", args.worker_pool);
3300
- const generated = await runEmbed(command);
3301
- if (!asString(args.download_output)) return generated;
3302
- const artifact = pickArtifact(generated, "image");
3303
- if (!artifact?.artifact_id) throw new Error("No downloadable image artifact found");
3304
- const downloaded = await runEmbed(["artifact", "download", artifact.artifact_id, "--output", args.download_output]);
3305
- return { generated, selected_artifact: artifact, downloaded };
3306
- }
3307
-
3308
- async function artifactDownload(args) {
3309
- const artifact = asString(args.artifact_id || args.artifact);
3310
- const output = asString(args.output_path || args.output);
3311
- if (!artifact) throw new Error("artifact_id is required");
3312
- if (!output) throw new Error("output_path is required");
3313
- return await runEmbed(["artifact", "download", artifact, "--output", output]);
3314
- }
3315
-
3316
- async function workspaceRelease(args) {
3317
- const workspace = asString(args.workspace_id || args.workspace);
3318
- if (!workspace) throw new Error("workspace_id is required");
3319
- const command = ["build", "workspace", "release", workspace];
3320
- if (boolValue(args.dry_run)) command.push("--dry-run");
3321
- if (boolValue(args.no_settle_storage)) command.push("--no-settle-storage");
3322
- return await runEmbed(command);
3323
- }
3324
-
3325
3533
  async function callTool(name, args = {}) {
3326
- if (name === "dbt_auth_status") return await pluginAuthStatus();
3534
+ const requestedName = name;
3535
+ if (mcpToolProfile() === "agent" && AGENT_MCP_TOOL_ALIASES.has(requestedName)) {
3536
+ args = sanitizeAgentProfileArgs(args);
3537
+ }
3538
+ name = canonicalMcpToolName(name);
3539
+ if (name === "dbt_agent_status") return await pluginAgentStatus();
3540
+ if (name === "dbt_agent_action_schema") return await agentActionSchema(args);
3541
+ if (name === "dbt_agent_action_run") return await agentActionRun(args);
3542
+ if (name === "dbt_agent_chat") return await agentChat(args);
3327
3543
  if (name === "dbt_plugin_update_check") return await pluginUpdateCheck(args);
3328
3544
  if (name === "dbt_plugin_update") return await pluginUpdate(args);
3545
+ if (name === "dbt_current_board_status") return await currentBoardStatus(args);
3329
3546
  if (name === "dbt_supported_boards") return await supportedBoards(args);
3330
3547
  if (name === "dbt_local_toolchain_list") return await localToolchainList(args);
3331
3548
  if (name === "dbt_local_toolchain_installed") return await localToolchainInstalled(args);
3332
3549
  if (name === "dbt_local_toolchain_latest") return await localToolchainLatest(args);
3333
3550
  if (name === "dbt_local_toolchain_current") return await localToolchainCurrent(args);
3334
- const authStatus = await pluginAuthStatus();
3335
- if (!authStatus.ok) return authStatus;
3336
- if (name === "dbt_current_board_status" || name === "dbtstatus") return await currentBoardStatus(args, authStatus);
3337
3551
  if (name === "dbt_initial_image_flash") return await initialImageFlash(args);
3338
3552
  if (name === "dbt_rp2350_monitor_status") return await rp2350MonitorStatus(args);
3339
3553
  if (name === "dbt_rp2350_capabilities") return await rp2350Capabilities(args);
@@ -3350,13 +3564,8 @@ async function callTool(name, args = {}) {
3350
3564
  if (name === "dbt_rp2350_monitor_command") return await rp2350MonitorCommand(args);
3351
3565
  if (name === "dbt_cloud_status") return await cloudStatus(args);
3352
3566
  if (name === "dbt_board_knowledge_search") return await boardKnowledgeSearch(args);
3353
- if (isServerWorkspaceToolName(name)) return serverWorkspaceDisabledResult();
3354
- if (name === "dbt_task_status") return await taskStatus(args);
3355
- if (name === "dbt_task_log_download") return await taskLogDownload(args);
3356
- if (name === "dbt_artifact_download") return await artifactDownload(args);
3357
3567
  if (name === "dbt_board_debug") return await boardDebug(args);
3358
3568
  if (name === "dbt_boot_logo_update") return await bootLogoUpdate(args);
3359
- if (name === "dbt_update_logo") return await updateLogo(args);
3360
3569
  if (name === "dbt_compose_boot_logo") return await composeBootLogo(args);
3361
3570
  if (name === "dbt_local_toolchain_install") return await localToolchainInstall(args);
3362
3571
  if (name === "dbt_local_toolchain_uninstall") return await localToolchainUninstall(args);
@@ -3374,8 +3583,10 @@ async function callTool(name, args = {}) {
3374
3583
  let payload = {};
3375
3584
  if (asString(args.arguments_json)) payload = JSON.parse(args.arguments_json);
3376
3585
  if (asString(args.request) && !payload.prompt) payload.prompt = asString(args.request);
3586
+ if (["agent-action-schema", "action-schema", "agent-actions", "action-catalog"].includes(action)) return await agentActionSchema(payload);
3587
+ if (["agent-action-run", "action-run", "agent-run-action", "run-action"].includes(action)) return await agentActionRun(payload);
3377
3588
  if (["supported-boards", "boards", "board-list", "registry-list"].includes(action)) return await supportedBoards(payload);
3378
- if (["board-status", "status"].includes(action)) return await currentBoardStatus(payload, authStatus);
3589
+ if (["board-status", "status"].includes(action)) return await currentBoardStatus(payload);
3379
3590
  if (["board-reboot", "reboot", "restart", "device-reboot", "board-restart"].includes(action)) return await boardControl({ ...payload, operation: "reboot" });
3380
3591
  if (["board-bootloader", "bootloader", "loader", "enter-bootloader", "enter-loader", "bootsel"].includes(action)) return await boardControl({ ...payload, operation: "bootloader" });
3381
3592
  if (["image-flash", "flash-image", "image-update", "update-image", "board-image-flash", "board-image-update"].includes(action)) return await boardControl({ ...payload, operation: "image.flash" });
@@ -3412,10 +3623,6 @@ async function callTool(name, args = {}) {
3412
3623
  if (["local-compile", "compile-local"].includes(action)) return await localCompile(payload);
3413
3624
  if (["local-qt-smoke", "qt-smoke", "local-qt-build"].includes(action)) return await localQtSmoke(payload);
3414
3625
  if (["taishanpi-qt-app", "qt-app", "qt-workflow", "taishanpi-qt-workflow", "qt-develop"].includes(action)) return await taishanpiQtAppWorkflow(payload);
3415
- if (isServerWorkspaceAction(action)) return serverWorkspaceDisabledResult();
3416
- if (["task-status", "status-task"].includes(action)) return await taskStatus(payload);
3417
- if (["task-log", "task-log-download", "log-download"].includes(action)) return await taskLogDownload(payload);
3418
- if (["download", "artifact-download"].includes(action)) return await artifactDownload(payload);
3419
3626
  return { ok: false, error_code: "unsupported_dbt_action" };
3420
3627
  }
3421
3628
  throw new Error(`Unknown tool: ${name}`);
@@ -3458,7 +3665,17 @@ async function handleRpc(message) {
3458
3665
  let result;
3459
3666
  let toolCallFailed = false;
3460
3667
  try {
3461
- result = await callTool(message.params?.name, message.params?.arguments || {});
3668
+ const toolName = asString(message.params?.name);
3669
+ if (!isMcpToolExposed(toolName)) {
3670
+ throw new Error(JSON.stringify({
3671
+ error: {
3672
+ code: "mcp_tool_not_exposed",
3673
+ message: `${toolName || "requested tool"} is not exposed in the current EmbedLabs MCP tool profile.`,
3674
+ remediation: "Use embedlabs_action_run/embedlabs_chat in the default Agent profile, or enable EMBEDLABS_MCP_TOOL_PROFILE=compact/full for compatibility testing."
3675
+ }
3676
+ }));
3677
+ }
3678
+ result = await callTool(toolName, message.params?.arguments || {});
3462
3679
  toolCallFailed = result?.ok === false
3463
3680
  || result?.response?.ok === false
3464
3681
  || result?.response?.data?.result?.ok === false