@wingman-ai/gateway 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +29 -111
  2. package/dist/agent/config/agentConfig.cjs +2 -0
  3. package/dist/agent/config/agentConfig.d.ts +6 -0
  4. package/dist/agent/config/agentConfig.js +2 -0
  5. package/dist/agent/config/agentLoader.cjs +21 -18
  6. package/dist/agent/config/agentLoader.js +22 -19
  7. package/dist/agent/config/toolRegistry.cjs +19 -0
  8. package/dist/agent/config/toolRegistry.d.ts +4 -0
  9. package/dist/agent/config/toolRegistry.js +17 -1
  10. package/dist/agent/middleware/additional-messages.cjs +115 -11
  11. package/dist/agent/middleware/additional-messages.d.ts +9 -0
  12. package/dist/agent/middleware/additional-messages.js +115 -11
  13. package/dist/agent/tests/agentLoader.test.cjs +45 -0
  14. package/dist/agent/tests/agentLoader.test.js +45 -0
  15. package/dist/agent/tests/toolRegistry.test.cjs +2 -0
  16. package/dist/agent/tests/toolRegistry.test.js +2 -0
  17. package/dist/agent/tools/node_invoke.cjs +146 -0
  18. package/dist/agent/tools/node_invoke.d.ts +86 -0
  19. package/dist/agent/tools/node_invoke.js +109 -0
  20. package/dist/cli/commands/gateway.cjs +1 -1
  21. package/dist/cli/commands/gateway.js +1 -1
  22. package/dist/cli/core/agentInvoker.cjs +21 -3
  23. package/dist/cli/core/agentInvoker.d.ts +10 -0
  24. package/dist/cli/core/agentInvoker.js +21 -3
  25. package/dist/gateway/http/nodes.cjs +247 -0
  26. package/dist/gateway/http/nodes.d.ts +20 -0
  27. package/dist/gateway/http/nodes.js +210 -0
  28. package/dist/gateway/node.cjs +10 -1
  29. package/dist/gateway/node.d.ts +10 -1
  30. package/dist/gateway/node.js +10 -1
  31. package/dist/gateway/server.cjs +414 -27
  32. package/dist/gateway/server.d.ts +34 -0
  33. package/dist/gateway/server.js +408 -27
  34. package/dist/gateway/types.d.ts +6 -1
  35. package/dist/gateway/validation.cjs +2 -0
  36. package/dist/gateway/validation.d.ts +4 -0
  37. package/dist/gateway/validation.js +2 -0
  38. package/dist/tests/additionalMessageMiddleware.test.cjs +92 -0
  39. package/dist/tests/additionalMessageMiddleware.test.js +92 -0
  40. package/dist/tests/gateway-http-security.test.cjs +277 -0
  41. package/dist/tests/gateway-http-security.test.d.ts +1 -0
  42. package/dist/tests/gateway-http-security.test.js +271 -0
  43. package/dist/tests/gateway-node-mode.test.cjs +174 -0
  44. package/dist/tests/gateway-node-mode.test.d.ts +1 -0
  45. package/dist/tests/gateway-node-mode.test.js +168 -0
  46. package/dist/tests/gateway-origin-policy.test.cjs +60 -0
  47. package/dist/tests/gateway-origin-policy.test.d.ts +1 -0
  48. package/dist/tests/gateway-origin-policy.test.js +54 -0
  49. package/dist/tests/gateway.test.cjs +1 -0
  50. package/dist/tests/gateway.test.js +1 -0
  51. package/dist/tests/node-tools.test.cjs +77 -0
  52. package/dist/tests/node-tools.test.d.ts +1 -0
  53. package/dist/tests/node-tools.test.js +71 -0
  54. package/dist/tests/nodes-api.test.cjs +86 -0
  55. package/dist/tests/nodes-api.test.d.ts +1 -0
  56. package/dist/tests/nodes-api.test.js +80 -0
  57. package/dist/webui/assets/{index-DHbfLOUR.js → index-BMekSELC.js} +106 -106
  58. package/dist/webui/index.html +1 -1
  59. package/package.json +1 -1
package/README.md CHANGED
@@ -123,128 +123,46 @@ wingman provider login ollama # Optional
123
123
  wingman agent --local --agent <id> "prompt"
124
124
  ```
125
125
 
126
- ## Gateway Configuration (All the Ways + Why)
126
+ ## Secure Skills + MCP Proxy (TL;DR)
127
127
 
128
- Gateway behavior can be configured in three layers (higher priority wins): runtime flags, environment variables, and `wingman.config.json`. Use the config file for persistent defaults, then override per run when needed.
128
+ Main point: skill scanning and MCP proxy are separate toggles, both explicit, and `uv` checks only happen when the feature is enabled.
129
129
 
130
- ### 1) `wingman.config.json` (persistent defaults)
130
+ Key CLI commands:
131
131
 
132
- - `gateway.host` / `gateway.port` - bind address + port. Use `0.0.0.0` for LAN access, or change the port to avoid conflicts.
133
- - `gateway.stateDir` - where durable sessions and gateway state live. Point to fast local storage or a shared volume.
134
- - `gateway.fsRoots` - allowlist for Control UI working folders and output paths. Keep this tight for safety.
135
- - `gateway.auth.mode` / `gateway.auth.token` / `gateway.auth.password` - gateway auth strategy (token, password, or none) for remote access.
136
- - `gateway.auth.allowTailscale` - trust Tailscale identity headers so Tailnet users can access without tokens.
137
- - `gateway.controlUi.enabled` / `gateway.controlUi.port` - enable/disable Control UI and choose its port.
138
- - `gateway.controlUi.pairingRequired` - require pairing for Control UI clients (recommended).
139
- - `gateway.controlUi.allowInsecureAuth` - only for local dev when testing auth flows.
140
- - `gateway.adapters.discord.*` - Discord output adapter:
141
- - `enabled`, `token`, `mentionOnly`, `allowBots`, `allowedGuilds`, `allowedChannels`
142
- - `channelSessions` to pin channels to a session (or `agent:<id>:` to force routing)
143
- - `sessionCommand` for ad-hoc session overrides
144
- - `responseChunkSize` to fit Discord message limits
145
- - Optional `gatewayUrl`, `gatewayToken`, `gatewayPassword` to point the adapter at a remote gateway
146
-
147
- ### 2) Runtime flags (`wingman gateway start` / `run`)
148
-
149
- - `--host`, `--port` - override bind address + port for this run.
150
- - `--auth`, `--auth-mode`, `--token`, `--password` - enable auth without editing config.
151
- - `--discovery mdns|tailscale`, `--name` - advertise your gateway for LAN or Tailnet discovery.
152
- - `--max-nodes`, `--ping-interval`, `--ping-timeout` - tune scale and heartbeat behavior.
153
- - `--log-level` - dial verbosity for debugging or production.
154
-
155
- ### 3) Environment overrides
156
-
157
- - `WINGMAN_GATEWAY_TOKEN` - supply a token at runtime so you don't store secrets in config.
158
-
159
- ### Related gateway behavior (configured elsewhere)
160
-
161
- - `agents.bindings` - deterministic routing rules used by the gateway to select an agent per inbound channel/message.
162
- - `voice` - gateway TTS defaults (provider + settings), with optional per-agent overrides for voice-enabled UIs.
163
-
164
- ### Example configs (common setups)
132
+ ```bash
133
+ # gateway auth token
134
+ wingman gateway token --generate
135
+ export WINGMAN_GATEWAY_TOKEN="<token>"
165
136
 
166
- #### 1) Local dev (single user, no auth)
137
+ # gateway runtime
138
+ wingman gateway start
167
139
 
168
- ```json
169
- {
170
- "gateway": {
171
- "host": "127.0.0.1",
172
- "port": 18789,
173
- "auth": { "mode": "none" },
174
- "controlUi": { "enabled": true, "port": 18790 }
175
- }
176
- }
140
+ # skills
141
+ wingman skill browse
142
+ wingman skill install <skill-name>
143
+ wingman skill list
144
+ wingman skill remove <skill-name>
177
145
  ```
178
146
 
179
- #### 2) Shared LAN gateway (token auth + restricted outputs)
180
-
181
- ```json
182
- {
183
- "gateway": {
184
- "host": "0.0.0.0",
185
- "port": 18789,
186
- "fsRoots": ["~/Projects", "~/.wingman/outputs"],
187
- "auth": { "mode": "token" },
188
- "controlUi": { "enabled": true, "port": 18790, "pairingRequired": true }
189
- }
190
- }
191
- ```
147
+ - Skill scan runs on each `wingman skill install` only when `skills.security.scanOnInstall` is enabled.
148
+ - MCP proxy runs at agent runtime only when `gateway.mcpProxy.enabled` is enabled.
149
+ - If `uv` is missing for an enabled feature, Wingman fails with an error (no interactive prompt).
150
+ - Full config examples: `../docs-website/docs/configuration/skills.mdx`, `../docs-website/docs/configuration/gateway.mdx`, `../docs-website/docs/configuration/wingman-config.mdx`.
192
151
 
193
- Tip: set `WINGMAN_GATEWAY_TOKEN` at runtime so you do not store tokens in config.
194
-
195
- #### 3) Headless gateway + Discord output adapter
196
-
197
- ```json
198
- {
199
- "gateway": {
200
- "host": "0.0.0.0",
201
- "port": 18789,
202
- "auth": { "mode": "token" },
203
- "controlUi": { "enabled": false },
204
- "adapters": {
205
- "discord": {
206
- "enabled": true,
207
- "token": "DISCORD_BOT_TOKEN",
208
- "mentionOnly": true,
209
- "allowedGuilds": ["123456789012345678"],
210
- "allowedChannels": ["987654321098765432"],
211
- "channelSessions": {
212
- "987654321098765432": "agent:support:discord:channel:987654321098765432"
213
- }
214
- }
215
- }
216
- }
217
- }
218
- ```
152
+ ## Configuration
219
153
 
220
- #### 4) Remote access over Tailscale + voice TTS
221
-
222
- ```json
223
- {
224
- "gateway": {
225
- "host": "0.0.0.0",
226
- "port": 18789,
227
- "auth": { "mode": "token", "allowTailscale": true },
228
- "controlUi": { "enabled": true, "port": 18790, "pairingRequired": true }
229
- },
230
- "voice": {
231
- "provider": "elevenlabs",
232
- "defaultPolicy": "off",
233
- "elevenlabs": {
234
- "voiceId": "VOICE_ID",
235
- "modelId": "eleven_multilingual_v2",
236
- "stability": 0.4,
237
- "similarityBoost": 0.7
238
- }
239
- }
240
- }
241
- ```
154
+ Where to configure:
242
155
 
243
- Start discovery at runtime:
156
+ - Runtime flags: `wingman gateway start --help`
157
+ - Environment secret: `WINGMAN_GATEWAY_TOKEN`
158
+ - Persistent config: `.wingman/wingman.config.json`
159
+ - JSON schema: `https://getwingmanai.com/schemas/wingman.config.schema.json`
244
160
 
245
- ```bash
246
- wingman gateway start --discovery tailscale --name "Work Gateway"
247
- ```
161
+ Docs (full examples):
162
+
163
+ - Gateway: `../docs-website/docs/configuration/gateway.mdx`
164
+ - Skills: `../docs-website/docs/configuration/skills.mdx`
165
+ - Full config: `../docs-website/docs/configuration/wingman-config.mdx`
248
166
 
249
167
  ## Core Concepts
250
168
 
@@ -42,6 +42,8 @@ const AvailableToolNames = external_zod_namespaceObject["enum"]([
42
42
  "browser_control",
43
43
  "command_execute",
44
44
  "background_terminal",
45
+ "node_notify",
46
+ "node_run",
45
47
  "think",
46
48
  "code_search",
47
49
  "git_status",
@@ -9,6 +9,8 @@ export declare const AvailableToolNames: z.ZodEnum<{
9
9
  browser_control: "browser_control";
10
10
  command_execute: "command_execute";
11
11
  background_terminal: "background_terminal";
12
+ node_notify: "node_notify";
13
+ node_run: "node_run";
12
14
  think: "think";
13
15
  code_search: "code_search";
14
16
  git_status: "git_status";
@@ -44,6 +46,8 @@ export declare const AgentConfigSchema: z.ZodObject<{
44
46
  browser_control: "browser_control";
45
47
  command_execute: "command_execute";
46
48
  background_terminal: "background_terminal";
49
+ node_notify: "node_notify";
50
+ node_run: "node_run";
47
51
  think: "think";
48
52
  code_search: "code_search";
49
53
  git_status: "git_status";
@@ -148,6 +152,8 @@ export declare const AgentConfigSchema: z.ZodObject<{
148
152
  browser_control: "browser_control";
149
153
  command_execute: "command_execute";
150
154
  background_terminal: "background_terminal";
155
+ node_notify: "node_notify";
156
+ node_run: "node_run";
151
157
  think: "think";
152
158
  code_search: "code_search";
153
159
  git_status: "git_status";
@@ -9,6 +9,8 @@ const AvailableToolNames = external_zod_enum([
9
9
  "browser_control",
10
10
  "command_execute",
11
11
  "background_terminal",
12
+ "node_notify",
13
+ "node_run",
12
14
  "think",
13
15
  "code_search",
14
16
  "git_status",
@@ -45,6 +45,10 @@ function _define_property(obj, key, value) {
45
45
  }
46
46
  const logger = (0, external_logger_cjs_namespaceObject.createLogger)();
47
47
  const PROMPT_REFINEMENT_MARKER = "[[wingman:prompt-refinement]]";
48
+ const ALWAYS_ON_TOOL_NAMES = [
49
+ ...external_toolRegistry_cjs_namespaceObject.UI_TOOL_NAMES,
50
+ ...external_toolRegistry_cjs_namespaceObject.NODE_TOOL_NAMES
51
+ ];
48
52
  const normalizePromptRefinementPath = (agentName, rawPath)=>{
49
53
  const fallback = `/memories/agents/${agentName}/instructions.md`;
50
54
  if (!rawPath) return fallback;
@@ -244,16 +248,23 @@ class AgentLoader {
244
248
  ...this.runtimeToolOptions
245
249
  };
246
250
  };
251
+ const addAlwaysOnTools = async (existingTools, source)=>{
252
+ const alwaysOnTools = await (0, external_toolRegistry_cjs_namespaceObject.createTools)([
253
+ ...ALWAYS_ON_TOOL_NAMES
254
+ ], buildToolOptions(source));
255
+ if (0 === alwaysOnTools.length) return existingTools || [];
256
+ if (existingTools && existingTools.length > 0) {
257
+ const existing = new Set(existingTools.map((tool)=>tool.name));
258
+ const uniqueAlwaysOnTools = alwaysOnTools.filter((tool)=>!existing.has(tool.name));
259
+ return [
260
+ ...existingTools,
261
+ ...uniqueAlwaysOnTools
262
+ ];
263
+ }
264
+ return alwaysOnTools;
265
+ };
247
266
  if (config.tools && config.tools.length > 0) agent.tools = await (0, external_toolRegistry_cjs_namespaceObject.createTools)(config.tools, buildToolOptions(config));
248
- const uiTools = await (0, external_toolRegistry_cjs_namespaceObject.createTools)(external_toolRegistry_cjs_namespaceObject.UI_TOOL_NAMES, buildToolOptions(config));
249
- if (uiTools.length > 0) if (agent.tools && agent.tools.length > 0) {
250
- const existing = new Set(agent.tools.map((tool)=>tool.name));
251
- const uniqueUiTools = uiTools.filter((tool)=>!existing.has(tool.name));
252
- agent.tools = [
253
- ...agent.tools,
254
- ...uniqueUiTools
255
- ];
256
- } else agent.tools = uiTools;
267
+ agent.tools = await addAlwaysOnTools(agent.tools, config);
257
268
  if (config.mcp) agent.mcpConfig = config.mcp;
258
269
  if (config.mcpUseGlobal) agent.mcpUseGlobal = true;
259
270
  if (config.model) try {
@@ -280,15 +291,7 @@ class AgentLoader {
280
291
  sub.systemPrompt = applyPromptRefinement(sub.systemPrompt, subagent.name, subagent.promptRefinement);
281
292
  }
282
293
  if (subagent.tools && subagent.tools.length > 0) sub.tools = await (0, external_toolRegistry_cjs_namespaceObject.createTools)(subagent.tools, buildToolOptions(subagent));
283
- const subUiTools = await (0, external_toolRegistry_cjs_namespaceObject.createTools)(external_toolRegistry_cjs_namespaceObject.UI_TOOL_NAMES, buildToolOptions(subagent));
284
- if (subUiTools.length > 0) if (sub.tools && sub.tools.length > 0) {
285
- const existing = new Set(sub.tools.map((tool)=>tool.name));
286
- const uniqueUiTools = subUiTools.filter((tool)=>!existing.has(tool.name));
287
- sub.tools = [
288
- ...sub.tools,
289
- ...uniqueUiTools
290
- ];
291
- } else sub.tools = subUiTools;
294
+ sub.tools = await addAlwaysOnTools(sub.tools, subagent);
292
295
  if (subagent.model) try {
293
296
  sub.model = external_modelFactory_cjs_namespaceObject.ModelFactory.createModel(subagent.model, {
294
297
  reasoningEffort: subagent.reasoningEffort,
@@ -4,7 +4,7 @@ import { load } from "js-yaml";
4
4
  import { createLogger } from "../../logger.js";
5
5
  import { WingmanDirectory, validateAgentConfig } from "./agentConfig.js";
6
6
  import { ModelFactory } from "./modelFactory.js";
7
- import { UI_TOOL_NAMES, createTools } from "./toolRegistry.js";
7
+ import { NODE_TOOL_NAMES, UI_TOOL_NAMES, createTools } from "./toolRegistry.js";
8
8
  function _define_property(obj, key, value) {
9
9
  if (key in obj) Object.defineProperty(obj, key, {
10
10
  value: value,
@@ -17,6 +17,10 @@ function _define_property(obj, key, value) {
17
17
  }
18
18
  const logger = createLogger();
19
19
  const PROMPT_REFINEMENT_MARKER = "[[wingman:prompt-refinement]]";
20
+ const ALWAYS_ON_TOOL_NAMES = [
21
+ ...UI_TOOL_NAMES,
22
+ ...NODE_TOOL_NAMES
23
+ ];
20
24
  const normalizePromptRefinementPath = (agentName, rawPath)=>{
21
25
  const fallback = `/memories/agents/${agentName}/instructions.md`;
22
26
  if (!rawPath) return fallback;
@@ -216,16 +220,23 @@ class AgentLoader {
216
220
  ...this.runtimeToolOptions
217
221
  };
218
222
  };
223
+ const addAlwaysOnTools = async (existingTools, source)=>{
224
+ const alwaysOnTools = await createTools([
225
+ ...ALWAYS_ON_TOOL_NAMES
226
+ ], buildToolOptions(source));
227
+ if (0 === alwaysOnTools.length) return existingTools || [];
228
+ if (existingTools && existingTools.length > 0) {
229
+ const existing = new Set(existingTools.map((tool)=>tool.name));
230
+ const uniqueAlwaysOnTools = alwaysOnTools.filter((tool)=>!existing.has(tool.name));
231
+ return [
232
+ ...existingTools,
233
+ ...uniqueAlwaysOnTools
234
+ ];
235
+ }
236
+ return alwaysOnTools;
237
+ };
219
238
  if (config.tools && config.tools.length > 0) agent.tools = await createTools(config.tools, buildToolOptions(config));
220
- const uiTools = await createTools(UI_TOOL_NAMES, buildToolOptions(config));
221
- if (uiTools.length > 0) if (agent.tools && agent.tools.length > 0) {
222
- const existing = new Set(agent.tools.map((tool)=>tool.name));
223
- const uniqueUiTools = uiTools.filter((tool)=>!existing.has(tool.name));
224
- agent.tools = [
225
- ...agent.tools,
226
- ...uniqueUiTools
227
- ];
228
- } else agent.tools = uiTools;
239
+ agent.tools = await addAlwaysOnTools(agent.tools, config);
229
240
  if (config.mcp) agent.mcpConfig = config.mcp;
230
241
  if (config.mcpUseGlobal) agent.mcpUseGlobal = true;
231
242
  if (config.model) try {
@@ -252,15 +263,7 @@ class AgentLoader {
252
263
  sub.systemPrompt = applyPromptRefinement(sub.systemPrompt, subagent.name, subagent.promptRefinement);
253
264
  }
254
265
  if (subagent.tools && subagent.tools.length > 0) sub.tools = await createTools(subagent.tools, buildToolOptions(subagent));
255
- const subUiTools = await createTools(UI_TOOL_NAMES, buildToolOptions(subagent));
256
- if (subUiTools.length > 0) if (sub.tools && sub.tools.length > 0) {
257
- const existing = new Set(sub.tools.map((tool)=>tool.name));
258
- const uniqueUiTools = subUiTools.filter((tool)=>!existing.has(tool.name));
259
- sub.tools = [
260
- ...sub.tools,
261
- ...uniqueUiTools
262
- ];
263
- } else sub.tools = subUiTools;
266
+ sub.tools = await addAlwaysOnTools(sub.tools, subagent);
264
267
  if (subagent.model) try {
265
268
  sub.model = ModelFactory.createModel(subagent.model, {
266
269
  reasoningEffort: subagent.reasoningEffort,
@@ -27,6 +27,7 @@ __webpack_require__.d(__webpack_exports__, {
27
27
  createTools: ()=>createTools,
28
28
  UI_TOOL_NAMES: ()=>UI_TOOL_NAMES,
29
29
  getAvailableTools: ()=>getAvailableTools,
30
+ NODE_TOOL_NAMES: ()=>NODE_TOOL_NAMES,
30
31
  createTool: ()=>createTool
31
32
  });
32
33
  const external_logger_cjs_namespaceObject = require("../../logger.cjs");
@@ -36,6 +37,7 @@ const code_search_cjs_namespaceObject = require("../tools/code_search.cjs");
36
37
  const command_execute_cjs_namespaceObject = require("../tools/command_execute.cjs");
37
38
  const git_status_cjs_namespaceObject = require("../tools/git_status.cjs");
38
39
  const internet_search_cjs_namespaceObject = require("../tools/internet_search.cjs");
40
+ const node_invoke_cjs_namespaceObject = require("../tools/node_invoke.cjs");
39
41
  const terminal_session_manager_cjs_namespaceObject = require("../tools/terminal_session_manager.cjs");
40
42
  const think_cjs_namespaceObject = require("../tools/think.cjs");
41
43
  const ui_registry_cjs_namespaceObject = require("../tools/ui_registry.cjs");
@@ -47,6 +49,10 @@ const UI_TOOL_NAMES = [
47
49
  "ui_registry_get",
48
50
  "ui_present"
49
51
  ];
52
+ const NODE_TOOL_NAMES = [
53
+ "node_notify",
54
+ "node_run"
55
+ ];
50
56
  function createTool(name, options = {}) {
51
57
  const { workspace = process.cwd(), executionWorkspace, blockedCommands, allowScriptExecution = true, timeout = 300000, terminalOwnerId = "default", terminalSessionManager = (0, terminal_session_manager_cjs_namespaceObject.getSharedTerminalSessionManager)(), searchConfig = {
52
58
  provider: "duckduckgo",
@@ -93,6 +99,16 @@ function createTool(name, options = {}) {
93
99
  allowScriptExecution,
94
100
  commandTimeout: timeout
95
101
  });
102
+ case "node_notify":
103
+ return (0, node_invoke_cjs_namespaceObject.createNodeNotifyTool)({
104
+ nodeInvoker: options.nodeInvoker,
105
+ defaultTargetClientId: options.nodeDefaultTargetClientId
106
+ });
107
+ case "node_run":
108
+ return (0, node_invoke_cjs_namespaceObject.createNodeRunTool)({
109
+ nodeInvoker: options.nodeInvoker,
110
+ defaultTargetClientId: options.nodeDefaultTargetClientId
111
+ });
96
112
  case "think":
97
113
  return (0, think_cjs_namespaceObject.createThinkingTool)();
98
114
  case "code_search":
@@ -140,17 +156,20 @@ function getAvailableTools() {
140
156
  "browser_control",
141
157
  "command_execute",
142
158
  "background_terminal",
159
+ ...NODE_TOOL_NAMES,
143
160
  "think",
144
161
  "code_search",
145
162
  "git_status",
146
163
  ...UI_TOOL_NAMES
147
164
  ];
148
165
  }
166
+ exports.NODE_TOOL_NAMES = __webpack_exports__.NODE_TOOL_NAMES;
149
167
  exports.UI_TOOL_NAMES = __webpack_exports__.UI_TOOL_NAMES;
150
168
  exports.createTool = __webpack_exports__.createTool;
151
169
  exports.createTools = __webpack_exports__.createTools;
152
170
  exports.getAvailableTools = __webpack_exports__.getAvailableTools;
153
171
  for(var __rspack_i in __webpack_exports__)if (-1 === [
172
+ "NODE_TOOL_NAMES",
154
173
  "UI_TOOL_NAMES",
155
174
  "createTool",
156
175
  "createTools",
@@ -1,6 +1,7 @@
1
1
  import type { StructuredTool } from "@langchain/core/tools";
2
2
  import type { MCPServersConfig } from "@/types/mcp.js";
3
3
  import type { SearchConfig } from "../../cli/config/schema.js";
4
+ import { type NodeInvokeRequest, type NodeInvokeResult } from "../tools/node_invoke.js";
4
5
  import { type TerminalSessionManager } from "../tools/terminal_session_manager.js";
5
6
  import type { AvailableToolName } from "./agentConfig.js";
6
7
  export interface ToolOptions {
@@ -30,8 +31,11 @@ export interface ToolOptions {
30
31
  mcpConfigs?: MCPServersConfig[];
31
32
  skillsDirectory?: string;
32
33
  dynamicUiEnabled?: boolean;
34
+ nodeInvoker?: (request: NodeInvokeRequest) => Promise<NodeInvokeResult>;
35
+ nodeDefaultTargetClientId?: string;
33
36
  }
34
37
  export declare const UI_TOOL_NAMES: AvailableToolName[];
38
+ export declare const NODE_TOOL_NAMES: AvailableToolName[];
35
39
  /**
36
40
  * Create a tool by name with optional configuration
37
41
  */
@@ -5,6 +5,7 @@ import { createCodeSearchTool } from "../tools/code_search.js";
5
5
  import { createCommandExecuteTool } from "../tools/command_execute.js";
6
6
  import { createGitStatusTool } from "../tools/git_status.js";
7
7
  import { createInternetSearchTool } from "../tools/internet_search.js";
8
+ import { createNodeNotifyTool, createNodeRunTool } from "../tools/node_invoke.js";
8
9
  import { getSharedTerminalSessionManager } from "../tools/terminal_session_manager.js";
9
10
  import { createThinkingTool } from "../tools/think.js";
10
11
  import { createUiPresentTool, createUiRegistryGetTool, createUiRegistryListTool } from "../tools/ui_registry.js";
@@ -16,6 +17,10 @@ const UI_TOOL_NAMES = [
16
17
  "ui_registry_get",
17
18
  "ui_present"
18
19
  ];
20
+ const NODE_TOOL_NAMES = [
21
+ "node_notify",
22
+ "node_run"
23
+ ];
19
24
  function createTool(name, options = {}) {
20
25
  const { workspace = process.cwd(), executionWorkspace, blockedCommands, allowScriptExecution = true, timeout = 300000, terminalOwnerId = "default", terminalSessionManager = getSharedTerminalSessionManager(), searchConfig = {
21
26
  provider: "duckduckgo",
@@ -62,6 +67,16 @@ function createTool(name, options = {}) {
62
67
  allowScriptExecution,
63
68
  commandTimeout: timeout
64
69
  });
70
+ case "node_notify":
71
+ return createNodeNotifyTool({
72
+ nodeInvoker: options.nodeInvoker,
73
+ defaultTargetClientId: options.nodeDefaultTargetClientId
74
+ });
75
+ case "node_run":
76
+ return createNodeRunTool({
77
+ nodeInvoker: options.nodeInvoker,
78
+ defaultTargetClientId: options.nodeDefaultTargetClientId
79
+ });
65
80
  case "think":
66
81
  return createThinkingTool();
67
82
  case "code_search":
@@ -109,10 +124,11 @@ function getAvailableTools() {
109
124
  "browser_control",
110
125
  "command_execute",
111
126
  "background_terminal",
127
+ ...NODE_TOOL_NAMES,
112
128
  "think",
113
129
  "code_search",
114
130
  "git_status",
115
131
  ...UI_TOOL_NAMES
116
132
  ];
117
133
  }
118
- export { UI_TOOL_NAMES, createTool, createTools, getAvailableTools };
134
+ export { NODE_TOOL_NAMES, UI_TOOL_NAMES, createTool, createTools, getAvailableTools };
@@ -30,6 +30,7 @@ const external_node_path_namespaceObject = require("node:path");
30
30
  const external_langchain_namespaceObject = require("langchain");
31
31
  const external_utils_cjs_namespaceObject = require("../utils.cjs");
32
32
  const external_uiRegistry_cjs_namespaceObject = require("../uiRegistry.cjs");
33
+ const INJECTION_SOURCE = "additional-message-middleware";
33
34
  const normalizeRelativePath = (value)=>value.replace(/\\/g, "/");
34
35
  const toSafeRelativePath = (workspaceRoot, targetPath)=>{
35
36
  if (!workspaceRoot) return null;
@@ -56,12 +57,108 @@ const buildWorkingDirectoryMessage = (context)=>{
56
57
  if (!context.workspaceRoot) return null;
57
58
  return "** Working Directory **\n- Treat `./` as the current working directory for file and tool operations in this session.\n- Use relative paths such as `./test.md`; do not prepend the working directory absolute path.";
58
59
  };
60
+ const resolveConnectedNodeIds = async (context)=>{
61
+ if (context.nodeConnectedTargetsProvider) try {
62
+ const rawTargets = await context.nodeConnectedTargetsProvider();
63
+ if (!Array.isArray(rawTargets)) return [];
64
+ const seen = new Set();
65
+ const normalized = [];
66
+ for (const target of rawTargets){
67
+ if (!target || "object" != typeof target) continue;
68
+ const nodeId = target.nodeId?.trim();
69
+ if (!(!nodeId || seen.has(nodeId))) {
70
+ seen.add(nodeId);
71
+ normalized.push(nodeId);
72
+ }
73
+ }
74
+ return normalized;
75
+ } catch {
76
+ return [];
77
+ }
78
+ if (!context.nodeConnectedIdsProvider) return [];
79
+ try {
80
+ const rawIds = await context.nodeConnectedIdsProvider();
81
+ if (!Array.isArray(rawIds)) return [];
82
+ const seen = new Set();
83
+ const normalized = [];
84
+ for (const value of rawIds){
85
+ if ("string" != typeof value) continue;
86
+ const trimmed = value.trim();
87
+ if (!(!trimmed || seen.has(trimmed))) {
88
+ seen.add(trimmed);
89
+ normalized.push(trimmed);
90
+ }
91
+ }
92
+ return normalized;
93
+ } catch {
94
+ return [];
95
+ }
96
+ };
97
+ const resolveConnectedNodeTargets = async (context)=>{
98
+ if (context.nodeConnectedTargetsProvider) try {
99
+ const rawTargets = await context.nodeConnectedTargetsProvider();
100
+ if (!Array.isArray(rawTargets)) return [];
101
+ const seen = new Set();
102
+ const normalized = [];
103
+ for (const target of rawTargets){
104
+ if (!target || "object" != typeof target) continue;
105
+ const typedTarget = target;
106
+ const nodeId = typedTarget.nodeId?.trim();
107
+ if (!nodeId || seen.has(nodeId)) continue;
108
+ seen.add(nodeId);
109
+ const rawCapabilities = typedTarget.capabilities;
110
+ const capabilities = Array.isArray(rawCapabilities) ? rawCapabilities.filter((value)=>"string" == typeof value).map((value)=>value.trim()).filter(Boolean) : [];
111
+ normalized.push({
112
+ nodeId,
113
+ clientId: typedTarget.clientId?.trim() || void 0,
114
+ name: typedTarget.name?.trim() || void 0,
115
+ capabilities: capabilities.length > 0 ? capabilities : void 0
116
+ });
117
+ }
118
+ return normalized;
119
+ } catch {
120
+ return [];
121
+ }
122
+ const nodeIds = await resolveConnectedNodeIds(context);
123
+ return nodeIds.map((nodeId)=>({
124
+ nodeId
125
+ }));
126
+ };
127
+ const buildNodeTargetsMessage = async (context)=>{
128
+ const connectedNodeTargets = await resolveConnectedNodeTargets(context);
129
+ const connectedNodeIds = connectedNodeTargets.map((target)=>target.nodeId);
130
+ const defaultClientId = context.defaultNodeTargetClientId?.trim();
131
+ if (0 === connectedNodeIds.length && !defaultClientId) return null;
132
+ const lines = [
133
+ "** Connected Node Targets **"
134
+ ];
135
+ if (connectedNodeIds.length > 0) lines.push(`- Connected node IDs: ${connectedNodeIds.join(", ")}`);
136
+ else lines.push("- Connected node IDs: (none currently connected)");
137
+ const withMetadata = connectedNodeTargets.filter((target)=>Boolean(target.clientId) || Boolean(target.name) || target.capabilities && target.capabilities.length > 0);
138
+ if (withMetadata.length > 0) {
139
+ lines.push("- Connected node metadata:");
140
+ for (const target of withMetadata.slice(0, 8)){
141
+ const details = [];
142
+ if (target.clientId) details.push(`clientId: ${target.clientId}`);
143
+ if (target.name) details.push(`name: ${target.name}`);
144
+ if (target.capabilities && target.capabilities.length > 0) {
145
+ const preview = target.capabilities.slice(0, 6).join(", ");
146
+ const remaining = target.capabilities.length - 6;
147
+ details.push(remaining > 0 ? `capabilities: ${preview} (+${remaining} more)` : `capabilities: ${preview}`);
148
+ }
149
+ if (details.length > 0) lines.push(` - ${target.nodeId} (${details.join("; ")})`);
150
+ }
151
+ if (withMetadata.length > 8) lines.push(` - ... ${withMetadata.length - 8} more connected node(s)`);
152
+ }
153
+ if (defaultClientId) lines.push(`- Default node target clientId for this request: ${defaultClientId}`);
154
+ lines.push("- For node_notify/node_run, set target.nodeId or target.clientId when the user specifies a device.");
155
+ return lines.join("\n");
156
+ };
59
157
  const additionalMessageMiddleware = (context = {})=>({
60
- name: "additional-message-middleware",
158
+ name: INJECTION_SOURCE,
61
159
  [external_langchain_namespaceObject.MIDDLEWARE_BRAND]: true,
62
160
  beforeAgent: async (input)=>{
63
- const alreadyInjected = input.messages.some((message)=>message?.additional_kwargs?.source === "additional-message-middleware");
64
- if (alreadyInjected) return input;
161
+ const messagesWithoutInjected = input.messages.filter((message)=>message?.additional_kwargs?.source !== INJECTION_SOURCE);
65
162
  const lines = [
66
163
  (0, external_utils_cjs_namespaceObject.getConfidentialityNotice)(),
67
164
  `** Current Date Time (UTC): ${new Date().toISOString()} **`
@@ -70,6 +167,8 @@ const additionalMessageMiddleware = (context = {})=>({
70
167
  if (outputLocation) lines.push(outputLocation);
71
168
  const workingDirectory = buildWorkingDirectoryMessage(context);
72
169
  if (workingDirectory) lines.push(workingDirectory);
170
+ const nodeTargets = await buildNodeTargetsMessage(context);
171
+ if (nodeTargets) lines.push(nodeTargets);
73
172
  lines.push("** Long-term memory **\n- Use /memories/ for durable notes across threads.\n- Store stable preferences, project context, decisions, and research notes.\n- Avoid transient logs; keep entries concise and organized.\n- Suggested paths: /memories/preferences.md, /memories/projects/<name>/context.md, /memories/projects/<name>/decisions.md");
74
173
  if (false === context.dynamicUiEnabled) lines.push("** Dynamic UI **\n- Dynamic UI rendering is disabled for this gateway.\n- Respond with plain text and avoid calling UI presentation tools.");
75
174
  else {
@@ -82,14 +181,19 @@ const additionalMessageMiddleware = (context = {})=>({
82
181
  lines.push("** Dynamic UI Registry **\n" + summaryLines + "\n- Use ui_registry_get for schema details, then ui_present with textFallback.");
83
182
  }
84
183
  }
85
- input.messages.unshift(new external_langchain_namespaceObject.HumanMessage({
86
- content: lines.join("\n\n"),
87
- additional_kwargs: {
88
- ui_hidden: true,
89
- source: "additional-message-middleware"
90
- }
91
- }));
92
- return input;
184
+ return {
185
+ ...input,
186
+ messages: [
187
+ new external_langchain_namespaceObject.HumanMessage({
188
+ content: lines.join("\n\n"),
189
+ additional_kwargs: {
190
+ ui_hidden: true,
191
+ source: INJECTION_SOURCE
192
+ }
193
+ }),
194
+ ...messagesWithoutInjected
195
+ ]
196
+ };
93
197
  }
94
198
  });
95
199
  exports.additionalMessageMiddleware = __webpack_exports__.additionalMessageMiddleware;
@@ -1,4 +1,10 @@
1
1
  import { type AgentMiddleware } from "langchain";
2
+ export type ConnectedNodeTarget = {
3
+ nodeId: string;
4
+ clientId?: string;
5
+ name?: string;
6
+ capabilities?: string[];
7
+ };
2
8
  type AdditionalMessageContext = {
3
9
  workspaceRoot?: string | null;
4
10
  workdir?: string | null;
@@ -6,6 +12,9 @@ type AdditionalMessageContext = {
6
12
  outputVirtualPath?: string | null;
7
13
  dynamicUiEnabled?: boolean;
8
14
  skillsDirectory?: string;
15
+ nodeConnectedIdsProvider?: () => string[] | Promise<string[]>;
16
+ nodeConnectedTargetsProvider?: () => ConnectedNodeTarget[] | Promise<ConnectedNodeTarget[]>;
17
+ defaultNodeTargetClientId?: string;
9
18
  };
10
19
  export declare const additionalMessageMiddleware: (context?: AdditionalMessageContext) => AgentMiddleware;
11
20
  export {};