@tonyclaw/agent-inspector 3.0.26 → 3.0.27

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 (72) hide show
  1. package/.output/cli.js +80 -11
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-SZRUDQc9.js → CompareDrawer-BVCAAbbu.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-BJvwOrSi.js → ProxyViewerContainer-CYOXB81n.js} +200 -200
  5. package/.output/public/assets/{ReplayDialog-DgfmqiM3.js → ReplayDialog-DMX23O5G.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CS6c7Qfn.js → RequestAnatomy-S17pYDRc.js} +1 -1
  7. package/.output/public/assets/{ResponseView-C0Ri_5gm.js → ResponseView-Cf1XrawD.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-7dYcHWyZ.js → StreamingChunkSequence-BdOrVlCi.js} +1 -1
  9. package/.output/public/assets/{_sessionId-D-V84cgw.js → _sessionId-BnKGmJrR.js} +1 -1
  10. package/.output/public/assets/{index-DgFWCxOs.js → index-9fMLCU4d.js} +1 -1
  11. package/.output/public/assets/{index-CSIL7-MK.js → index-B1atk19i.js} +2 -2
  12. package/.output/public/assets/{index-DrVJsks2.js → index-gY3I-7-D.js} +1 -1
  13. package/.output/public/assets/{json-viewer-C9Ji3gYN.js → json-viewer-CpyYpPjx.js} +1 -1
  14. package/.output/server/_libs/lucide-react.mjs +6 -6
  15. package/.output/server/{_sessionId-BQ6YQOO1.mjs → _sessionId-eA_h2_s6.mjs} +4 -2
  16. package/.output/server/_ssr/{CompareDrawer-CqNzIeMR.mjs → CompareDrawer-K2k9G7r1.mjs} +5 -3
  17. package/.output/server/_ssr/{ProxyViewerContainer-CMC7Vsbw.mjs → ProxyViewerContainer-CbyBpCAj.mjs} +27 -60
  18. package/.output/server/_ssr/{ReplayDialog-D9p_LaTr.mjs → ReplayDialog-DacI2Iv0.mjs} +5 -3
  19. package/.output/server/_ssr/{RequestAnatomy-B4ge3qNZ.mjs → RequestAnatomy-gh-pAdkO.mjs} +4 -2
  20. package/.output/server/_ssr/{ResponseView-BCqMKApI.mjs → ResponseView-BRJKN50-.mjs} +5 -3
  21. package/.output/server/_ssr/{StreamingChunkSequence-BA4zTLE4.mjs → StreamingChunkSequence-BQLVEuWY.mjs} +4 -2
  22. package/.output/server/_ssr/{index-CzxhDjlq.mjs → index-DuSWrjbH.mjs} +4 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-B3qV6iEJ.mjs → json-viewer-DNs1ZjKI.mjs} +4 -2
  25. package/.output/server/_ssr/{router-CZe_R31M.mjs → router-Dgx6-ot3.mjs} +712 -217
  26. package/.output/server/{_tanstack-start-manifest_v-B50VTLrH.mjs → _tanstack-start-manifest_v-CoGjnkQD.mjs} +1 -1
  27. package/.output/server/index.mjs +64 -64
  28. package/README.md +8 -0
  29. package/package.json +4 -5
  30. package/src/cli.ts +43 -9
  31. package/src/components/providers/ProviderCard.tsx +3 -36
  32. package/src/components/providers/ProviderForm.tsx +13 -12
  33. package/src/components/providers/ProvidersPanel.tsx +2 -2
  34. package/src/components/providers/SettingsDialog.tsx +3 -3
  35. package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -3
  36. package/src/contracts/anthropic.ts +0 -5
  37. package/src/contracts/index.ts +0 -4
  38. package/src/contracts/log.ts +0 -1
  39. package/src/contracts/openai.ts +0 -3
  40. package/src/knowledge/openclawClient.ts +22 -9
  41. package/src/knowledge/openclawGatewayClient.ts +20 -13
  42. package/src/lib/alertContract.ts +0 -3
  43. package/src/lib/ecosystemContract.ts +0 -3
  44. package/src/lib/export-logs.ts +1 -4
  45. package/src/lib/safeFetch.ts +100 -0
  46. package/src/lib/ssrfGuard.ts +133 -0
  47. package/src/lib/stopReason.ts +1 -4
  48. package/src/lib/unknown.ts +27 -0
  49. package/src/lib/useProviders.ts +1 -1
  50. package/src/mcp/mode.ts +3 -9
  51. package/src/mcp/server.ts +5 -14
  52. package/src/mcp/toolHandlers.ts +45 -54
  53. package/src/proxy/constants.ts +17 -0
  54. package/src/proxy/formats/anthropic/handler.ts +5 -1
  55. package/src/proxy/formats/anthropic/schemas.ts +1 -7
  56. package/src/proxy/formats/index.ts +1 -1
  57. package/src/proxy/formats/openai/schemas.ts +0 -3
  58. package/src/proxy/formats/registry.ts +0 -3
  59. package/src/proxy/handler.ts +91 -12
  60. package/src/proxy/identityProxy.ts +74 -1
  61. package/src/proxy/logImporter.ts +82 -12
  62. package/src/proxy/logIndex.ts +1 -4
  63. package/src/proxy/providerSecretStore.ts +181 -0
  64. package/src/proxy/providers.ts +84 -11
  65. package/src/proxy/schemas.ts +1 -8
  66. package/src/proxy/sqliteLogIndex.ts +0 -16
  67. package/src/proxy/store.ts +1 -5
  68. package/src/proxy/toolSchemaWarnings.ts +1 -9
  69. package/src/routes/api/logs.$id.replay.ts +17 -5
  70. package/src/routes/api/providers.$providerId.model-metadata.ts +11 -1
  71. package/src/routes/api/providers.$providerId.ts +18 -5
  72. package/src/routes/api/providers.ts +18 -7
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ routes: { __root__: { filePath: "C:/Users/claw/workspace/agent-inspector/src/routes/__root.tsx", children: ["/", "/api/alerts", "/api/config", "/api/groups", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/pi-agent", "/api/providers", "/api/runs", "/api/sessions", "/proxy/$", "/session/$sessionId", "/api/ecosystem/packages", "/api/ecosystem/tasks", "/api/knowledge/candidates", "/api/knowledge/project-context", "/api/knowledge/search", "/api/ecosystem/recipes/$recipeId/run", "/api/knowledge/sessions/$sessionId/candidates"], preloads: ["/assets/index-CSIL7-MK.js"], scripts: [{ attrs: { type: "module", async: true, src: "/assets/index-CSIL7-MK.js" } }] }, "/": { filePath: "C:/Users/claw/workspace/agent-inspector/src/routes/index.tsx", children: void 0, preloads: ["/assets/index-DrVJsks2.js", "/assets/ProxyViewerContainer-BJvwOrSi.js"] }, "/session/$sessionId": { filePath: "C:/Users/claw/workspace/agent-inspector/src/routes/session/$sessionId.tsx", children: void 0, preloads: ["/assets/_sessionId-D-V84cgw.js", "/assets/ProxyViewerContainer-BJvwOrSi.js"] } } });
1
+ const tsrStartManifest = () => ({ routes: { __root__: { filePath: "C:/Users/claw/workspace/agent-inspector/src/routes/__root.tsx", children: ["/", "/api/alerts", "/api/config", "/api/groups", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/pi-agent", "/api/providers", "/api/runs", "/api/sessions", "/proxy/$", "/session/$sessionId", "/api/ecosystem/packages", "/api/ecosystem/tasks", "/api/knowledge/candidates", "/api/knowledge/project-context", "/api/knowledge/search", "/api/ecosystem/recipes/$recipeId/run", "/api/knowledge/sessions/$sessionId/candidates"], preloads: ["/assets/index-B1atk19i.js"], scripts: [{ attrs: { type: "module", async: true, src: "/assets/index-B1atk19i.js" } }] }, "/": { filePath: "C:/Users/claw/workspace/agent-inspector/src/routes/index.tsx", children: void 0, preloads: ["/assets/index-gY3I-7-D.js", "/assets/ProxyViewerContainer-CYOXB81n.js"] }, "/session/$sessionId": { filePath: "C:/Users/claw/workspace/agent-inspector/src/routes/session/$sessionId.tsx", children: void 0, preloads: ["/assets/_sessionId-BnKGmJrR.js", "/assets/ProxyViewerContainer-CYOXB81n.js"] } } });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -20,121 +20,121 @@ const assets = {
20
20
  "/assets/agent-inspector-58K1_MsC.ico": {
21
21
  "type": "image/vnd.microsoft.icon",
22
22
  "etag": '"38fa-ZD8kPABHYjPIkcH82FMY6iZM7fI"',
23
- "mtime": "2026-07-11T00:24:54.156Z",
23
+ "mtime": "2026-07-11T04:43:38.031Z",
24
24
  "size": 14586,
25
25
  "path": "../public/assets/agent-inspector-58K1_MsC.ico"
26
26
  },
27
27
  "/assets/alibaba-TTwafVwX.svg": {
28
28
  "type": "image/svg+xml",
29
29
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
30
- "mtime": "2026-07-11T00:24:54.156Z",
30
+ "mtime": "2026-07-11T04:43:38.031Z",
31
31
  "size": 5915,
32
32
  "path": "../public/assets/alibaba-TTwafVwX.svg"
33
33
  },
34
- "/assets/json-viewer-C9Ji3gYN.js": {
34
+ "/assets/CompareDrawer-BVCAAbbu.js": {
35
35
  "type": "text/javascript; charset=utf-8",
36
- "etag": '"1ae7-ywSQGg/SiMKKFOeSZzAyG3AC4DI"',
37
- "mtime": "2026-07-11T00:24:54.158Z",
36
+ "etag": '"4bc4-Zb2MZSke4PGsZAZQWlHQldYzm9M"',
37
+ "mtime": "2026-07-11T04:43:38.031Z",
38
+ "size": 19396,
39
+ "path": "../public/assets/CompareDrawer-BVCAAbbu.js"
40
+ },
41
+ "/assets/json-viewer-CpyYpPjx.js": {
42
+ "type": "text/javascript; charset=utf-8",
43
+ "etag": '"1ae7-AXjzdLeB9CtNkkvC/oR6PHIpZrw"',
44
+ "mtime": "2026-07-11T04:43:38.032Z",
38
45
  "size": 6887,
39
- "path": "../public/assets/json-viewer-C9Ji3gYN.js"
46
+ "path": "../public/assets/json-viewer-CpyYpPjx.js"
40
47
  },
41
- "/assets/CompareDrawer-SZRUDQc9.js": {
48
+ "/assets/index-gY3I-7-D.js": {
42
49
  "type": "text/javascript; charset=utf-8",
43
- "etag": '"4bc4-c5yQQGgiYt3gI4zOesfAlSc3dno"',
44
- "mtime": "2026-07-11T00:24:54.156Z",
45
- "size": 19396,
46
- "path": "../public/assets/CompareDrawer-SZRUDQc9.js"
50
+ "etag": '"265-61LM1QLhhYspwWAw/ft2ETl95Zg"',
51
+ "mtime": "2026-07-11T04:43:38.031Z",
52
+ "size": 613,
53
+ "path": "../public/assets/index-gY3I-7-D.js"
54
+ },
55
+ "/assets/index-9fMLCU4d.js": {
56
+ "type": "text/javascript; charset=utf-8",
57
+ "etag": '"1cd2d-vhf/v641OwP3pvt2nzTr2vXPzE4"',
58
+ "mtime": "2026-07-11T04:43:38.032Z",
59
+ "size": 118061,
60
+ "path": "../public/assets/index-9fMLCU4d.js"
47
61
  },
48
62
  "/assets/minimax-BPMzvuL-.jpeg": {
49
63
  "type": "image/jpeg",
50
64
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
51
- "mtime": "2026-07-11T00:24:54.156Z",
65
+ "mtime": "2026-07-11T04:43:38.031Z",
52
66
  "size": 6918,
53
67
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
54
68
  },
55
- "/assets/index-CSIL7-MK.js": {
56
- "type": "text/javascript; charset=utf-8",
57
- "etag": '"537cd-GPVivJXxNqhczKks68tQgmJTPDE"',
58
- "mtime": "2026-07-11T00:24:54.156Z",
59
- "size": 341965,
60
- "path": "../public/assets/index-CSIL7-MK.js"
61
- },
62
69
  "/assets/index-CmeF5XXS.css": {
63
70
  "type": "text/css; charset=utf-8",
64
71
  "etag": '"28a56-x7Wmw+oL5ZFDjFw5JRwjxSop3gg"',
65
- "mtime": "2026-07-11T00:24:54.156Z",
72
+ "mtime": "2026-07-11T04:43:38.031Z",
66
73
  "size": 166486,
67
74
  "path": "../public/assets/index-CmeF5XXS.css"
68
75
  },
69
- "/assets/index-DgFWCxOs.js": {
76
+ "/assets/RequestAnatomy-S17pYDRc.js": {
70
77
  "type": "text/javascript; charset=utf-8",
71
- "etag": '"1cd2d-3tCkJqLlCuTi9/Lz+b9h1l5nZgg"',
72
- "mtime": "2026-07-11T00:24:54.156Z",
73
- "size": 118061,
74
- "path": "../public/assets/index-DgFWCxOs.js"
78
+ "etag": '"43f3-DId6+xakRnGM+B/ZQpGxfeV9YU0"',
79
+ "mtime": "2026-07-11T04:43:38.032Z",
80
+ "size": 17395,
81
+ "path": "../public/assets/RequestAnatomy-S17pYDRc.js"
75
82
  },
76
- "/assets/ReplayDialog-DgfmqiM3.js": {
83
+ "/assets/ResponseView-Cf1XrawD.js": {
77
84
  "type": "text/javascript; charset=utf-8",
78
- "etag": '"3348-7mNTOezyB/6M8SHCRaNHCBplHZk"',
79
- "mtime": "2026-07-11T00:24:54.156Z",
80
- "size": 13128,
81
- "path": "../public/assets/ReplayDialog-DgfmqiM3.js"
85
+ "etag": '"47fe-kAztjtZjRuPE6xDPPKWIypxT0pw"',
86
+ "mtime": "2026-07-11T04:43:38.032Z",
87
+ "size": 18430,
88
+ "path": "../public/assets/ResponseView-Cf1XrawD.js"
82
89
  },
83
- "/assets/RequestAnatomy-CS6c7Qfn.js": {
90
+ "/assets/StreamingChunkSequence-BdOrVlCi.js": {
84
91
  "type": "text/javascript; charset=utf-8",
85
- "etag": '"43f3-iE7MB7sQUMWqMwG0cyR73BgkbuY"',
86
- "mtime": "2026-07-11T00:24:54.156Z",
87
- "size": 17395,
88
- "path": "../public/assets/RequestAnatomy-CS6c7Qfn.js"
92
+ "etag": '"e96-lQJVKxQQeI+CfoFlIj48wFou0qg"',
93
+ "mtime": "2026-07-11T04:43:38.032Z",
94
+ "size": 3734,
95
+ "path": "../public/assets/StreamingChunkSequence-BdOrVlCi.js"
89
96
  },
90
- "/assets/index-DrVJsks2.js": {
97
+ "/assets/ReplayDialog-DMX23O5G.js": {
91
98
  "type": "text/javascript; charset=utf-8",
92
- "etag": '"265-GhY3Vadghb4WWaqhfjEikIO6zQU"',
93
- "mtime": "2026-07-11T00:24:54.156Z",
94
- "size": 613,
95
- "path": "../public/assets/index-DrVJsks2.js"
99
+ "etag": '"3348-Y6DIYl0Hh8PNYNBckbjWZMRAVXk"',
100
+ "mtime": "2026-07-11T04:43:38.032Z",
101
+ "size": 13128,
102
+ "path": "../public/assets/ReplayDialog-DMX23O5G.js"
96
103
  },
97
- "/assets/ResponseView-C0Ri_5gm.js": {
104
+ "/assets/_sessionId-BnKGmJrR.js": {
98
105
  "type": "text/javascript; charset=utf-8",
99
- "etag": '"47fe-NHHoPnXJQBRlrFDWr/aopcQwmGk"',
100
- "mtime": "2026-07-11T00:24:54.156Z",
101
- "size": 18430,
102
- "path": "../public/assets/ResponseView-C0Ri_5gm.js"
106
+ "etag": '"36d-RhevzYZMuLpSJjgi/aeqAk1rL4A"',
107
+ "mtime": "2026-07-11T04:43:38.031Z",
108
+ "size": 877,
109
+ "path": "../public/assets/_sessionId-BnKGmJrR.js"
103
110
  },
104
- "/assets/StreamingChunkSequence-7dYcHWyZ.js": {
111
+ "/assets/index-B1atk19i.js": {
105
112
  "type": "text/javascript; charset=utf-8",
106
- "etag": '"e96-r+HXGxAmoKjHyD3J/MqfzPojMWo"',
107
- "mtime": "2026-07-11T00:24:54.156Z",
108
- "size": 3734,
109
- "path": "../public/assets/StreamingChunkSequence-7dYcHWyZ.js"
113
+ "etag": '"537cd-GY35Hkfq1WcIm1S1m2dfzIGL8WU"',
114
+ "mtime": "2026-07-11T04:43:38.031Z",
115
+ "size": 341965,
116
+ "path": "../public/assets/index-B1atk19i.js"
110
117
  },
111
118
  "/assets/zhipuai-BPNAnxo-.svg": {
112
119
  "type": "image/svg+xml",
113
120
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
114
- "mtime": "2026-07-11T00:24:54.156Z",
121
+ "mtime": "2026-07-11T04:43:38.031Z",
115
122
  "size": 11256,
116
123
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
117
124
  },
118
- "/assets/_sessionId-D-V84cgw.js": {
119
- "type": "text/javascript; charset=utf-8",
120
- "etag": '"36d-yOd6Us3ZWW0UESw+7JXnLzgeMbY"',
121
- "mtime": "2026-07-11T00:24:54.156Z",
122
- "size": 877,
123
- "path": "../public/assets/_sessionId-D-V84cgw.js"
124
- },
125
125
  "/assets/qwen-CONDcHqt.png": {
126
126
  "type": "image/png",
127
127
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
128
- "mtime": "2026-07-11T00:24:54.156Z",
128
+ "mtime": "2026-07-11T04:43:38.031Z",
129
129
  "size": 357059,
130
130
  "path": "../public/assets/qwen-CONDcHqt.png"
131
131
  },
132
- "/assets/ProxyViewerContainer-BJvwOrSi.js": {
132
+ "/assets/ProxyViewerContainer-CYOXB81n.js": {
133
133
  "type": "text/javascript; charset=utf-8",
134
- "etag": '"14071d-2HnQGZd4Md0+aEEDy7Sa4wv0GEY"',
135
- "mtime": "2026-07-11T00:24:54.158Z",
136
- "size": 1312541,
137
- "path": "../public/assets/ProxyViewerContainer-BJvwOrSi.js"
134
+ "etag": '"1404bd-m6RVh35PDpz6DtJ+NOh+zTs1XG8"',
135
+ "mtime": "2026-07-11T04:43:38.032Z",
136
+ "size": 1311933,
137
+ "path": "../public/assets/ProxyViewerContainer-CYOXB81n.js"
138
138
  }
139
139
  };
140
140
  function readAsset(id) {
package/README.md CHANGED
@@ -31,6 +31,14 @@ npm install -g @tonyclaw/agent-inspector-mcp
31
31
  agent-inspector-mcp doctor
32
32
  ```
33
33
 
34
+ Agent Inspector requires Node.js 22 or newer. Node.js 24 LTS is recommended for production use.
35
+
36
+ By default, Agent Inspector binds to `127.0.0.1`. With `--host 0.0.0.0`, remote coding tools may
37
+ use `/proxy`, while the UI, REST API, and MCP control plane remain local-only. Set
38
+ `AGENT_INSPECTOR_CONTROL_TOKEN` for authenticated remote API/MCP clients, or explicitly set
39
+ `AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL=1` only behind a trusted firewall or loopback-only port
40
+ mapping.
41
+
34
42
  `@tonyclaw/agent-inspector` runs the local UI, proxy, REST API, storage, and built-in
35
43
  `/api/mcp` endpoint. `@tonyclaw/agent-inspector-mcp` is the enhancement package for
36
44
  stdio-first MCP clients and setup automation; it bridges to the local Inspector endpoint without
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@tonyclaw/agent-inspector",
3
- "version": "3.0.26",
3
+ "version": "3.0.27",
4
4
  "type": "module",
5
5
  "description": "Agent observability and knowledge capture layer for AI coding tools.",
6
6
  "license": "MIT",
7
7
  "engines": {
8
- "node": ">=18.18.0"
8
+ "node": ">=22.0.0"
9
9
  },
10
10
  "author": "TonyClaw",
11
11
  "repository": {
@@ -55,8 +55,8 @@
55
55
  "dev": "vite dev",
56
56
  "start": "node .output/cli.js",
57
57
  "build": "vite build && bun build:cli && bun build:mcp",
58
- "build:cli": "npx esbuild src/cli.ts --bundle --platform=node --target=node18 --format=esm --outfile=.output/cli.js",
59
- "build:mcp": "npx esbuild packages/agent-inspector-mcp/src/cli.ts --bundle --platform=node --target=node20 --format=esm --outfile=packages/agent-inspector-mcp/dist/cli.js",
58
+ "build:cli": "npx esbuild src/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=.output/cli.js",
59
+ "build:mcp": "npx esbuild packages/agent-inspector-mcp/src/cli.ts --bundle --platform=node --target=node22 --format=esm --outfile=packages/agent-inspector-mcp/dist/cli.js",
60
60
  "postinstall": "node scripts/setup-windows-runtime.mjs && node scripts/setup-agent-skills.mjs",
61
61
  "prepublishOnly": "npm run build",
62
62
  "typecheck": "tsc --noEmit",
@@ -102,7 +102,6 @@
102
102
  },
103
103
  "devDependencies": {
104
104
  "@biomejs/biome": "^2.3.14",
105
- "@cfworker/json-schema": "^4.1.1",
106
105
  "@eslint/js": "^10.0.1",
107
106
  "@ladle/react": "^5.1.1",
108
107
  "@tailwindcss/vite": "^4.1.18",
package/src/cli.ts CHANGED
@@ -8,6 +8,7 @@ import { existsSync } from "node:fs";
8
8
  import type { Readable, Writable } from "node:stream";
9
9
  import {
10
10
  formatAccessHintLines,
11
+ isLoopbackHost,
11
12
  isValidBindHost,
12
13
  localUrlForPort,
13
14
  networkUrlsForBindHost,
@@ -31,6 +32,7 @@ const __dirname = dirname(__filename);
31
32
  const DEFAULT_PORT = 9527;
32
33
  const LEGACY_ALIAS_PORT = 9527;
33
34
  const DEFAULT_UPSTREAM_PORT = 9529;
35
+ const DEFAULT_BIND_HOST = "127.0.0.1";
34
36
  const LOCAL_PROBE_TIMEOUT_MS = 2000;
35
37
  const BRANDED_WINDOWS_RUNTIME_EXE = "agent-inspector.exe";
36
38
  const DEFAULT_CAPTURE_MODE = "simple";
@@ -229,6 +231,16 @@ function printAccessHints(port: number, host?: string): void {
229
231
  for (const line of formatAccessHintLines(port, networkUrls, host)) {
230
232
  console.log(line);
231
233
  }
234
+ if (!isLoopbackHost(host)) {
235
+ if (process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] === "1") {
236
+ console.warn(" Control plane: REMOTE ACCESS ENABLED by administrator override");
237
+ } else {
238
+ console.log(" Control plane: local only; remote clients may use /proxy");
239
+ if ((process.env["AGENT_INSPECTOR_CONTROL_TOKEN"] ?? "") !== "") {
240
+ console.log(" Remote API: Bearer token authentication enabled");
241
+ }
242
+ }
243
+ }
232
244
  }
233
245
 
234
246
  type BackgroundSupervisorOptions = {
@@ -289,13 +301,18 @@ function pickUpstreamPort(publicPort: number, legacyAliasPort: number | null): n
289
301
  async function tryStartIdentityProxy(
290
302
  identityPort: number,
291
303
  upstreamPort: number,
292
- upstreamHost: string | undefined,
304
+ listenHost: string,
305
+ upstreamHost: string,
293
306
  ): Promise<import("node:http").Server | null> {
294
307
  try {
295
308
  const server = await startIdentityProxy({
296
309
  listenPort: identityPort,
297
- upstreamHost: upstreamHost ?? "127.0.0.1",
310
+ listenHost,
311
+ upstreamHost,
298
312
  upstreamPort,
313
+ restrictRemoteControl: !isLoopbackHost(listenHost),
314
+ allowRemoteControl: process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] === "1",
315
+ controlToken: process.env["AGENT_INSPECTOR_CONTROL_TOKEN"],
299
316
  });
300
317
  return server;
301
318
  } catch (err) {
@@ -317,7 +334,8 @@ async function runStart(args: string[]): Promise<void> {
317
334
  process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
318
335
 
319
336
  let port = portDefault;
320
- let host = envHost !== undefined && envHost.trim().length > 0 ? envHost.trim() : undefined;
337
+ let host =
338
+ envHost !== undefined && envHost.trim().length > 0 ? envHost.trim() : DEFAULT_BIND_HOST;
321
339
  let open = true;
322
340
  let openWasSpecified = false;
323
341
  let background = false;
@@ -457,6 +475,18 @@ async function runStart(args: string[]): Promise<void> {
457
475
  return;
458
476
  }
459
477
 
478
+ if (
479
+ !enableIdentityProxy &&
480
+ !isLoopbackHost(host) &&
481
+ process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] !== "1"
482
+ ) {
483
+ console.error(
484
+ "Refusing an unprotected remote bind with --no-identity-proxy. Keep the identity proxy enabled or set AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL=1 explicitly.",
485
+ );
486
+ process.exitCode = 1;
487
+ return;
488
+ }
489
+
460
490
  /**
461
491
  * Check if a port is in use and kill the process using it
462
492
  */
@@ -475,6 +505,7 @@ async function runStart(args: string[]): Promise<void> {
475
505
  // the public-facing port(s). When the identity proxy is disabled we fall
476
506
  // back to TanStack listening on the public port directly.
477
507
  const upstreamPort = enableIdentityProxy ? pickUpstreamPort(port, legacyAliasPort) : port;
508
+ const upstreamHost = enableIdentityProxy ? DEFAULT_BIND_HOST : host;
478
509
  process.env["PORT"] = String(upstreamPort);
479
510
 
480
511
  const url = urlForHost(port, host);
@@ -627,10 +658,8 @@ async function runStart(args: string[]): Promise<void> {
627
658
  if (providersJson !== undefined) {
628
659
  serverEnv["AGENT_INSPECTOR_PROVIDERS_JSON"] = providersJson;
629
660
  }
630
- if (host !== undefined) {
631
- serverEnv["HOST"] = host;
632
- serverEnv["NITRO_HOST"] = host;
633
- }
661
+ serverEnv["HOST"] = upstreamHost;
662
+ serverEnv["NITRO_HOST"] = upstreamHost;
634
663
  serverEnv["AGENT_INSPECTOR_PUBLIC_PORT"] = String(port);
635
664
  serverEnv["PROXY_PORT"] = String(port);
636
665
  serverEnv["AGENT_INSPECTOR_CAPTURE_MODE"] = captureMode;
@@ -664,12 +693,17 @@ async function runStart(args: string[]): Promise<void> {
664
693
  server: import("node:http").Server;
665
694
  }> = [];
666
695
  if (enableIdentityProxy) {
667
- const primaryProxy = await tryStartIdentityProxy(port, upstreamPort, host);
696
+ const primaryProxy = await tryStartIdentityProxy(port, upstreamPort, host, upstreamHost);
668
697
  if (primaryProxy !== null) {
669
698
  identityProxies.push({ label: "primary", port, server: primaryProxy });
670
699
  }
671
700
  if (legacyAliasPort !== null) {
672
- const legacyProxy = await tryStartIdentityProxy(legacyAliasPort, upstreamPort, host);
701
+ const legacyProxy = await tryStartIdentityProxy(
702
+ legacyAliasPort,
703
+ upstreamPort,
704
+ host,
705
+ upstreamHost,
706
+ );
673
707
  if (legacyProxy !== null) {
674
708
  identityProxies.push({ label: "legacy", port: legacyAliasPort, server: legacyProxy });
675
709
  }
@@ -2,22 +2,16 @@ import { type JSX, useState } from "react";
2
2
  import { Button } from "../ui/button";
3
3
  import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
4
4
  import {
5
- Eye,
6
- EyeOff,
7
5
  Pencil,
8
6
  Trash2,
9
7
  RotateCw,
10
8
  CheckCircle,
11
9
  ExternalLink,
12
10
  Clock,
13
- Copy,
14
- Check,
15
11
  ChevronDown,
16
12
  ChevronRight,
17
13
  } from "lucide-react";
18
14
  import type { ProviderConfig } from "../../proxy/providers";
19
- import { copyTextToClipboard } from "../../lib/clipboard";
20
- import { maskApiKey } from "../../lib/mask";
21
15
  import {
22
16
  findProviderModelMetadata,
23
17
  providerHasContextMetadata,
@@ -174,18 +168,8 @@ export function ProviderCard({
174
168
  onRefreshMetadata,
175
169
  isRefreshingMetadata = false,
176
170
  }: ProviderCardProps): JSX.Element {
177
- const [showApiKey, setShowApiKey] = useState(false);
178
- const [copied, setCopied] = useState(false);
179
171
  const [showDetails, setShowDetails] = useState(false);
180
172
 
181
- function handleCopy() {
182
- void copyTextToClipboard(provider.apiKey).then((success) => {
183
- if (!success) return;
184
- setCopied(true);
185
- setTimeout(() => setCopied(false), 2000);
186
- });
187
- }
188
-
189
173
  const docsUrl =
190
174
  provider.apiDocsUrl ??
191
175
  Object.entries(KNOWN_PROVIDER_DOCS).find(([keyword]) =>
@@ -285,26 +269,9 @@ export function ProviderCard({
285
269
 
286
270
  {showDetails && (
287
271
  <div className="space-y-3 border-t border-border/60 pt-3">
288
- <div className="flex items-center gap-2">
289
- <code className="flex-1 truncate rounded bg-muted px-2 py-1 text-xs text-muted-foreground">
290
- {showApiKey ? provider.apiKey : maskApiKey(provider.apiKey)}
291
- </code>
292
- <button
293
- type="button"
294
- onClick={() => setShowApiKey((s) => !s)}
295
- className="p-1 text-muted-foreground transition-colors hover:text-foreground"
296
- aria-label={showApiKey ? "Hide API key" : "Show API key"}
297
- >
298
- {showApiKey ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
299
- </button>
300
- <button
301
- type="button"
302
- onClick={handleCopy}
303
- className="p-1 text-muted-foreground transition-colors hover:text-foreground"
304
- aria-label="Copy API key"
305
- >
306
- {copied ? <Check className="size-4 text-cyan-300" /> : <Copy className="size-4" />}
307
- </button>
272
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
273
+ <CheckCircle className="size-3.5 text-emerald-500" />
274
+ <span>API key configured</span>
308
275
  </div>
309
276
 
310
277
  {provider.models !== undefined && provider.models.length > 0 && (
@@ -4,7 +4,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
4
4
  import { Eye, EyeOff, Copy, Check, ChevronDown } from "lucide-react";
5
5
  import type { ProviderConfig, ProviderModelMetadata } from "../../lib/providerContract";
6
6
  import { copyTextToClipboard } from "../../lib/clipboard";
7
- import { maskApiKey } from "../../lib/mask";
8
7
  import {
9
8
  PATH_V1_CHAT_COMPLETIONS,
10
9
  PATH_V1_MESSAGES,
@@ -327,7 +326,7 @@ function EndpointUrlPreview({
327
326
  export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps): JSX.Element {
328
327
  const initialCapabilities = deriveProviderCapabilities(provider);
329
328
  const [name, setName] = useState(provider?.name ?? "");
330
- const [apiKey, setApiKey] = useState(provider?.apiKey ?? "");
329
+ const [apiKey, setApiKey] = useState("");
331
330
  const [showApiKey, setShowApiKey] = useState(false);
332
331
  const [copied, setCopied] = useState(false);
333
332
  const initialModels = provider?.models;
@@ -411,7 +410,8 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
411
410
  useEffect(() => {
412
411
  if (provider) {
413
412
  setName(provider.name);
414
- setApiKey(provider.apiKey);
413
+ setApiKey("");
414
+ setShowApiKey(false);
415
415
  const nextModels = (provider.models?.length ?? 0) > 0 ? provider.models : [""];
416
416
  setModels(nextModels);
417
417
  setModelMetadataDrafts(createMetadataDrafts(provider, nextModels));
@@ -482,7 +482,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
482
482
  if (!name.trim()) {
483
483
  newErrors.name = "Name is required";
484
484
  }
485
- if (!apiKey.trim()) {
485
+ if (provider === undefined && !apiKey.trim()) {
486
486
  newErrors.apiKey = "API key is required";
487
487
  }
488
488
  if (models.length === 0 || models.every((m) => !m.trim())) {
@@ -950,19 +950,15 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
950
950
 
951
951
  <div className="space-y-2">
952
952
  <label htmlFor="provider-apikey" className="text-sm font-medium">
953
- API Key <span className="text-destructive">*</span>
953
+ API Key {provider === undefined && <span className="text-destructive">*</span>}
954
954
  </label>
955
955
  <div className="flex items-center gap-2">
956
956
  <input
957
957
  id="provider-apikey"
958
- type="text"
959
- value={showApiKey || apiKey.length === 0 ? apiKey : maskApiKey(apiKey)}
958
+ type={showApiKey ? "text" : "password"}
959
+ value={apiKey}
960
960
  onChange={(e) => setApiKey(e.target.value)}
961
- onFocus={() => {
962
- if (!showApiKey && apiKey.length > 0) setShowApiKey(true);
963
- }}
964
- placeholder="sk-ant-..."
965
- readOnly={!showApiKey && apiKey.length > 0}
961
+ placeholder={provider === undefined ? "sk-ant-..." : "Leave blank to keep current key"}
966
962
  className="flex-1 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
967
963
  />
968
964
  <button
@@ -982,6 +978,11 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
982
978
  {copied ? <Check className="size-4 text-cyan-300" /> : <Copy className="size-4" />}
983
979
  </button>
984
980
  </div>
981
+ {provider !== undefined && apiKey.trim() === "" && (
982
+ <p className="text-xs text-muted-foreground">
983
+ A key is configured. Enter a new value only to rotate it.
984
+ </p>
985
+ )}
985
986
  {errors.apiKey !== undefined && <p className="text-xs text-destructive">{errors.apiKey}</p>}
986
987
  </div>
987
988
 
@@ -81,7 +81,7 @@ function endpointPayloadValue(
81
81
  function createProviderPayload(data: ProviderFormData, preserveEmptyEndpoints = false) {
82
82
  return {
83
83
  name: data.name,
84
- apiKey: data.apiKey,
84
+ ...(data.apiKey.trim() === "" ? {} : { apiKey: data.apiKey }),
85
85
  models: data.models,
86
86
  anthropicBaseUrl: endpointPayloadValue(data.anthropicBaseUrl, preserveEmptyEndpoints),
87
87
  openaiBaseUrl: endpointPayloadValue(data.openaiBaseUrl, preserveEmptyEndpoints),
@@ -430,7 +430,7 @@ export function ProvidersPanel({
430
430
  refreshProviders();
431
431
  // Only run connection test when critical fields (apiKey, model, base URLs) changed
432
432
  const criticalFieldsChanged =
433
- (data.apiKey ?? "") !== (editingProvider.apiKey ?? "") ||
433
+ data.apiKey.trim() !== "" ||
434
434
  JSON.stringify(data.models) !== JSON.stringify(editingProvider.models) ||
435
435
  (data.anthropicBaseUrl ?? "") !== (editingProvider.anthropicBaseUrl ?? "") ||
436
436
  (data.openaiBaseUrl ?? "") !== (editingProvider.openaiBaseUrl ?? "") ||
@@ -471,9 +471,9 @@ function McpSettingsTab(): JSX.Element {
471
471
  value: `agent-inspector-mcp doctor --url ${endpoint}`,
472
472
  },
473
473
  {
474
- id: "readonly",
475
- label: "Readonly mode",
476
- value: "AGENT_INSPECTOR_MCP_READONLY=1 agent-inspector",
474
+ id: "writes",
475
+ label: "Enable write tools",
476
+ value: "AGENT_INSPECTOR_MCP_WRITES=1 agent-inspector",
477
477
  },
478
478
  {
479
479
  id: "capabilities",
@@ -5,9 +5,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/
5
5
 
6
6
  const MAX_VISIBLE_LOGOS = 3;
7
7
 
8
- export const ROW_CHEVRON_SLOT_CLASS =
9
- "flex h-8 w-5 shrink-0 items-center justify-center text-muted-foreground";
10
-
11
8
  export type ProviderLogoStackItem = {
12
9
  provider: Provider;
13
10
  tooltip: string;
@@ -228,8 +228,3 @@ export const SseEventSchema = z.discriminatedUnion("type", [
228
228
 
229
229
  export type AnthropicResponse = z.infer<typeof AnthropicResponseSchema>;
230
230
  export type ResponseContentBlockType = z.infer<typeof ResponseContentBlock>;
231
-
232
- // Backward-compatible aliases (originally named Inspector*)
233
- export const InspectorRequestSchema = AnthropicRequestSchema;
234
- export const InspectorResponseSchema = AnthropicResponseSchema;
235
- export type InspectorResponse = AnthropicResponse;
@@ -12,7 +12,6 @@ export type {
12
12
  CapturedLog,
13
13
  LogBodyChunk,
14
14
  LogBodyPart,
15
- RequestFormat,
16
15
  StreamingChunk,
17
16
  TokenUsage,
18
17
  } from "./log";
@@ -46,9 +45,6 @@ export {
46
45
  } from "./openai";
47
46
  export type {
48
47
  OpenAIResponse,
49
- OpenAIResponsesRequest,
50
48
  OpenAIResponsesResponse,
51
- OpenAIResponsesSSEvent,
52
- OpenAIResponsesOutputItem,
53
49
  OpenAIToolCall,
54
50
  } from "./openai";
@@ -2,7 +2,6 @@ import { z } from "zod";
2
2
  import { JsonValueSchema } from "./json";
3
3
 
4
4
  export type ApiFormat = "anthropic" | "openai" | "unknown";
5
- export type RequestFormat = ApiFormat;
6
5
 
7
6
  /**
8
7
  * A single SSE event from a streaming response.
@@ -242,10 +242,7 @@ export const OpenAIResponsesSSEventSchema = z
242
242
  .passthrough();
243
243
 
244
244
  export type OpenAIResponse = z.infer<typeof OpenAIResponseSchema>;
245
- export type OpenAIResponsesRequest = z.infer<typeof OpenAIResponsesRequestSchema>;
246
245
  export type OpenAIResponsesResponse = z.infer<typeof OpenAIResponsesResponseSchema>;
247
- export type OpenAIResponsesSSEvent = z.infer<typeof OpenAIResponsesSSEventSchema>;
248
- export type OpenAIResponsesOutputItem = z.infer<typeof OpenAIResponsesOutputItemSchema>;
249
246
 
250
247
  export function parseOpenAIResponse(rawBody: string): OpenAIResponse | null {
251
248
  try {