@tonyclaw/agent-inspector 2.0.13 → 2.0.15
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.
- package/.output/cli.js +84 -0
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-CrMyE23D.js → CompareDrawer-B6WCFRJq.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-BZuo0px3.js → ProxyViewerContainer-CofGRJoO.js} +31 -31
- package/.output/public/assets/{ReplayDialog-TQGhDdTa.js → ReplayDialog-O_53zkX8.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-D6kQYtfa.js → RequestAnatomy-CqBSMZv5.js} +1 -1
- package/.output/public/assets/{ResponseView-C6ybWuB8.js → ResponseView-BvbA6zot.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-Bvs59_-I.js → StreamingChunkSequence-Bnf6djjc.js} +1 -1
- package/.output/public/assets/_sessionId-9ICCw6YA.js +1 -0
- package/.output/public/assets/index-DVkMYH8H.js +1 -0
- package/.output/public/assets/{main-DvFSJlOd.js → main-Biir9ZGe.js} +2 -2
- package/.output/server/{_sessionId-DHYJxPb7.mjs → _sessionId-B77Gh4IL.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-meimDBeA.mjs → CompareDrawer-D4-RHFqW.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-Co-WKq6u.mjs → ProxyViewerContainer-YP28-_2H.mjs} +31 -10
- package/.output/server/_ssr/{ReplayDialog-cclcGyR9.mjs → ReplayDialog-D9rQlrcY.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-to_rezzu.mjs → RequestAnatomy-BXCRWD9H.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-CZZI4IZd.mjs → ResponseView-DEwWMqSS.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-BTF4xSYX.mjs → StreamingChunkSequence-CdrTPf5v.mjs} +2 -2
- package/.output/server/_ssr/{index-D2fpJFP5.mjs → index-Chf1DYqT.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-RDBAF-Iu.mjs → router-BHXU5jWm.mjs} +89 -46
- package/.output/server/{_tanstack-start-manifest_v-DB9sseoH.mjs → _tanstack-start-manifest_v-D1dtacWy.mjs} +1 -1
- package/.output/server/index.mjs +59 -59
- package/README.md +141 -1
- package/package.json +1 -1
- package/src/cli.ts +91 -0
- package/src/components/ProxyViewer.tsx +32 -24
- package/src/components/ProxyViewerContainer.tsx +2 -1
- package/src/components/providers/SettingsDialog.tsx +10 -0
- package/src/lib/runtimeConfig.ts +4 -0
- package/src/lib/useStripConfig.ts +10 -2
- package/src/proxy/config.ts +33 -3
- package/src/proxy/handler.ts +30 -13
- package/src/proxy/logFinalizer.ts +4 -2
- package/src/routes/api/providers.$providerId.test.log.ts +17 -7
- package/.output/public/assets/_sessionId-BNG_mnaH.js +0 -1
- package/.output/public/assets/index-BUxIwHhC.js +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The npm package and CLI remain `agent-inspector`; the platform name is Agent Ins
|
|
|
7
7
|
## What It Does
|
|
8
8
|
|
|
9
9
|
- Runs a local transparent proxy for Anthropic and OpenAI-compatible APIs.
|
|
10
|
-
- Captures model requests, responses,
|
|
10
|
+
- Captures model requests, responses, tool definitions, token usage, and errors.
|
|
11
11
|
- Provides a web UI for browsing sessions and logs in real time.
|
|
12
12
|
- Includes a Chrome companion extension for quick capture status and log navigation.
|
|
13
13
|
- Generates reviewable knowledge candidates from agent sessions.
|
|
@@ -71,6 +71,146 @@ ANTHROPIC_BASE_URL=http://localhost:25947/proxy <tool>
|
|
|
71
71
|
|
|
72
72
|
The web UI runs at http://localhost:25947.
|
|
73
73
|
|
|
74
|
+
## Capture Modes
|
|
75
|
+
|
|
76
|
+
Agent Inspector starts in `simple` capture mode by default:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
agent-inspector
|
|
80
|
+
agent-inspector --mode simple
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Simple mode keeps the proxy lightweight for everyday use. It records the data
|
|
84
|
+
needed for the session timeline, request/response views, token usage, provider
|
|
85
|
+
routing, errors, and knowledge workflows, but it does not retain raw request
|
|
86
|
+
headers, upstream headers, or detailed streaming chunk artifacts.
|
|
87
|
+
|
|
88
|
+
Use `full` mode when you need protocol-level diagnostics:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
agent-inspector --mode full
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Full mode captures the additional raw details used by the `Raw Headers`,
|
|
95
|
+
`Headers`, and `Raw Response` views. It is useful for provider debugging,
|
|
96
|
+
header transformation checks, and SSE stream inspection, but it uses more CPU,
|
|
97
|
+
memory, and disk.
|
|
98
|
+
|
|
99
|
+
The mode is decided at process start. To change modes, restart the server. You
|
|
100
|
+
can also set `AGENT_INSPECTOR_MODE=full` or
|
|
101
|
+
`AGENT_INSPECTOR_CAPTURE_MODE=full`; the CLI flag takes precedence over the
|
|
102
|
+
environment.
|
|
103
|
+
|
|
104
|
+
## Jenkins Release Webhook
|
|
105
|
+
|
|
106
|
+
The `agent-inspector-release` Jenkins pipeline can be triggered from GitCode by
|
|
107
|
+
using the Jenkins Git plugin `notifyCommit` endpoint. The Jenkins instance must
|
|
108
|
+
be reachable from GitCode; `localhost`, LAN-only addresses, or NAT public IPs
|
|
109
|
+
without port forwarding will not work. Use a public Jenkins URL, a reverse
|
|
110
|
+
proxy, or a tunnel such as Cloudflare Tunnel, frp, or ngrok.
|
|
111
|
+
|
|
112
|
+
Create a GitCode WebHook under the repository settings with these values:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
URL:
|
|
116
|
+
https://<public-jenkins-host>/git/notifyCommit?url=https://gitcode.com/TonyClaw/agent-inspector&token=<jenkins-git-notify-token>
|
|
117
|
+
|
|
118
|
+
WebHook password/signing secret:
|
|
119
|
+
leave empty
|
|
120
|
+
|
|
121
|
+
POST content type:
|
|
122
|
+
application/json
|
|
123
|
+
|
|
124
|
+
Events:
|
|
125
|
+
Push events only
|
|
126
|
+
|
|
127
|
+
Active:
|
|
128
|
+
enabled
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The Jenkins job must have SCM polling enabled so the Git plugin can schedule the
|
|
132
|
+
pipeline after receiving the `notifyCommit` request. Do not commit the real
|
|
133
|
+
Jenkins notify token to the repository.
|
|
134
|
+
|
|
135
|
+
### Local Push Fallback
|
|
136
|
+
|
|
137
|
+
When GitCode cannot reach a local Jenkins instance from the public internet, the
|
|
138
|
+
tracked Husky `pre-push` hook can notify Jenkins from the developer machine that
|
|
139
|
+
runs `git push`.
|
|
140
|
+
|
|
141
|
+
Configure the local secret values once per clone:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
git config --local agentInspector.jenkinsUrl http://localhost:8080
|
|
145
|
+
git config --local agentInspector.repositoryUrl https://gitcode.com/TonyClaw/agent-inspector
|
|
146
|
+
git config --local agentInspector.jenkinsNotifyToken <jenkins-git-notify-token>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
On Windows PowerShell:
|
|
150
|
+
|
|
151
|
+
```powershell
|
|
152
|
+
git config --local agentInspector.jenkinsUrl http://localhost:8080
|
|
153
|
+
git config --local agentInspector.repositoryUrl https://gitcode.com/TonyClaw/agent-inspector
|
|
154
|
+
git config --local agentInspector.jenkinsNotifyToken <jenkins-git-notify-token>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
After a `main` branch push, the hook waits briefly and then calls Jenkins
|
|
158
|
+
`/git/notifyCommit` from the local machine. The hook can also be configured with
|
|
159
|
+
environment variables instead of local Git config:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
AGENT_INSPECTOR_JENKINS_URL=http://localhost:8080
|
|
163
|
+
AGENT_INSPECTOR_REPOSITORY_URL=https://gitcode.com/TonyClaw/agent-inspector
|
|
164
|
+
AGENT_INSPECTOR_JENKINS_NOTIFY_TOKEN=<jenkins-git-notify-token>
|
|
165
|
+
AGENT_INSPECTOR_JENKINS_PUSH_DELAY_SECONDS=10
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The hook writes local diagnostics to `.git/hooks/jenkins-pre-push.log`. The
|
|
169
|
+
tracked template contains no secret values; every developer must configure their
|
|
170
|
+
own token locally.
|
|
171
|
+
|
|
172
|
+
For repositories that do not use Husky, install the same template as a native Git
|
|
173
|
+
hook:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
cp scripts/hooks/pre-push-jenkins-notify .git/hooks/pre-push
|
|
177
|
+
chmod +x .git/hooks/pre-push
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Verified PR Workflow
|
|
181
|
+
|
|
182
|
+
For local Jenkins instances that GitCode cannot reach, use the verified PR script
|
|
183
|
+
instead of relying on GitCode WebHook delivery. The entire flow is outbound from
|
|
184
|
+
the developer machine:
|
|
185
|
+
|
|
186
|
+
1. Push the current commit to a temporary GitCode branch.
|
|
187
|
+
2. Trigger the local Jenkins job with `GIT_REF=<temporary-branch>`.
|
|
188
|
+
3. Wait for the Jenkins result.
|
|
189
|
+
4. Create a GitCode Pull Request to `main` only when Jenkins returns `SUCCESS`.
|
|
190
|
+
|
|
191
|
+
Configure local secrets once per clone:
|
|
192
|
+
|
|
193
|
+
```powershell
|
|
194
|
+
git config --local agentInspector.jenkinsUrl http://localhost:8080
|
|
195
|
+
git config --local agentInspector.jenkinsJob agent-inspector-release
|
|
196
|
+
git config --local agentInspector.jenkinsUser <jenkins-user>
|
|
197
|
+
git config --local agentInspector.jenkinsApiToken <jenkins-api-token>
|
|
198
|
+
git config --local agentInspector.gitcodeAccessToken <gitcode-access-token>
|
|
199
|
+
git config --local agentInspector.gitcodeOwner TonyClaw
|
|
200
|
+
git config --local agentInspector.gitcodeRepo agent-inspector
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Then run:
|
|
204
|
+
|
|
205
|
+
```powershell
|
|
206
|
+
.\scripts\jenkins\verified-pr.ps1
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The script pushes a branch named like `jenkins-verify/<user>/<sha>`, waits for
|
|
210
|
+
Jenkins, and creates a PR through the GitCode API after a successful build. The
|
|
211
|
+
temporary branch is kept as the PR source branch and is configured for deletion
|
|
212
|
+
after merge when GitCode supports source-branch pruning.
|
|
213
|
+
|
|
74
214
|
## Project Direction
|
|
75
215
|
|
|
76
216
|
Agent Inspector should stay close to the proven inspector codebase while growing into an agent knowledge layer. The memory flow has four layers:
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -11,6 +11,9 @@ const __dirname = dirname(__filename);
|
|
|
11
11
|
const DEFAULT_PORT = 25947;
|
|
12
12
|
const LOCAL_PROBE_TIMEOUT_MS = 2000;
|
|
13
13
|
const BRANDED_WINDOWS_RUNTIME_EXE = "agent-inspector.exe";
|
|
14
|
+
const DEFAULT_CAPTURE_MODE = "simple";
|
|
15
|
+
|
|
16
|
+
type CaptureMode = "simple" | "full";
|
|
14
17
|
|
|
15
18
|
process.title = "Agent Inspector";
|
|
16
19
|
|
|
@@ -56,6 +59,24 @@ async function isInspectorHealthy(port: number): Promise<boolean> {
|
|
|
56
59
|
}
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
async function getRunningCaptureMode(port: number): Promise<CaptureMode | null> {
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/config`, {
|
|
67
|
+
cache: "no-store",
|
|
68
|
+
signal: controller.signal,
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) return null;
|
|
71
|
+
const raw: unknown = await response.json();
|
|
72
|
+
return readCaptureMode(raw);
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
} finally {
|
|
76
|
+
clearTimeout(timeout);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
59
80
|
function isPortAcceptingConnections(port: number): Promise<boolean> {
|
|
60
81
|
return new Promise((resolve) => {
|
|
61
82
|
const socket = createConnection({ host: "127.0.0.1", port });
|
|
@@ -138,9 +159,31 @@ function resolveServerCommand(outputDir: string, serverPath: string): ServerComm
|
|
|
138
159
|
: { command: process.execPath, args: [serverPath] };
|
|
139
160
|
}
|
|
140
161
|
|
|
162
|
+
function parseCaptureMode(value: string | undefined): CaptureMode | null {
|
|
163
|
+
switch (value) {
|
|
164
|
+
case undefined:
|
|
165
|
+
return null;
|
|
166
|
+
case "simple":
|
|
167
|
+
return "simple";
|
|
168
|
+
case "full":
|
|
169
|
+
return "full";
|
|
170
|
+
default:
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function readCaptureMode(raw: unknown): CaptureMode | null {
|
|
176
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
177
|
+
const descriptor = Object.getOwnPropertyDescriptor(raw, "captureMode");
|
|
178
|
+
const value: unknown = descriptor?.value;
|
|
179
|
+
return typeof value === "string" ? parseCaptureMode(value) : null;
|
|
180
|
+
}
|
|
181
|
+
|
|
141
182
|
async function runStart(args: string[]): Promise<void> {
|
|
142
183
|
const envPort = process.env["PORT"];
|
|
143
184
|
const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
|
|
185
|
+
const envMode =
|
|
186
|
+
process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
|
|
144
187
|
|
|
145
188
|
let port = portDefault;
|
|
146
189
|
let open = true;
|
|
@@ -149,9 +192,34 @@ async function runStart(args: string[]): Promise<void> {
|
|
|
149
192
|
let forceRestart = false;
|
|
150
193
|
let configDir: string | undefined;
|
|
151
194
|
let providersJson: string | undefined;
|
|
195
|
+
let captureMode = DEFAULT_CAPTURE_MODE;
|
|
196
|
+
let captureModeWasSpecified = false;
|
|
197
|
+
|
|
198
|
+
if (envMode !== undefined && envMode !== "") {
|
|
199
|
+
const parsedMode = parseCaptureMode(envMode);
|
|
200
|
+
if (parsedMode === null) {
|
|
201
|
+
console.error(`Invalid capture mode: ${envMode}. Use simple or full.`);
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
captureMode = parsedMode;
|
|
206
|
+
captureModeWasSpecified = true;
|
|
207
|
+
}
|
|
152
208
|
|
|
153
209
|
for (let i = 0; i < args.length; i++) {
|
|
154
210
|
const arg = args[i] ?? "";
|
|
211
|
+
if (arg.startsWith("--mode=") || arg.startsWith("--capture-mode=")) {
|
|
212
|
+
const value = arg.slice(arg.indexOf("=") + 1);
|
|
213
|
+
const parsedMode = parseCaptureMode(value);
|
|
214
|
+
if (parsedMode === null) {
|
|
215
|
+
console.error(`Invalid capture mode: ${value}. Use simple or full.`);
|
|
216
|
+
process.exitCode = 1;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
captureMode = parsedMode;
|
|
220
|
+
captureModeWasSpecified = true;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
155
223
|
switch (arg) {
|
|
156
224
|
case "--port":
|
|
157
225
|
case "-p":
|
|
@@ -181,6 +249,20 @@ async function runStart(args: string[]): Promise<void> {
|
|
|
181
249
|
providersJson = args[i + 1];
|
|
182
250
|
i++;
|
|
183
251
|
break;
|
|
252
|
+
case "--mode":
|
|
253
|
+
case "--capture-mode": {
|
|
254
|
+
const value = args[i + 1];
|
|
255
|
+
const parsedMode = parseCaptureMode(value);
|
|
256
|
+
if (parsedMode === null) {
|
|
257
|
+
console.error(`Invalid capture mode: ${String(value)}. Use simple or full.`);
|
|
258
|
+
process.exitCode = 1;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
captureMode = parsedMode;
|
|
262
|
+
captureModeWasSpecified = true;
|
|
263
|
+
i++;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
184
266
|
default:
|
|
185
267
|
break;
|
|
186
268
|
}
|
|
@@ -270,6 +352,13 @@ async function runStart(args: string[]): Promise<void> {
|
|
|
270
352
|
|
|
271
353
|
if (!forceRestart && (await isInspectorHealthy(port))) {
|
|
272
354
|
console.log(`agent-inspector is already running at ${url}`);
|
|
355
|
+
if (captureModeWasSpecified) {
|
|
356
|
+
const runningMode = await getRunningCaptureMode(port);
|
|
357
|
+
if (runningMode !== null && runningMode !== captureMode) {
|
|
358
|
+
console.log(`Existing instance capture mode is ${runningMode}; requested ${captureMode}.`);
|
|
359
|
+
console.log(`Use --force-restart to restart with ${captureMode} mode.`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
273
362
|
console.log(`Use --force-restart to restart the existing instance.`);
|
|
274
363
|
if (open && openWasSpecified) {
|
|
275
364
|
openBrowser(url);
|
|
@@ -289,6 +378,7 @@ async function runStart(args: string[]): Promise<void> {
|
|
|
289
378
|
}
|
|
290
379
|
|
|
291
380
|
console.log(`Server running at ${url}`);
|
|
381
|
+
console.log(` Capture mode: ${captureMode}`);
|
|
292
382
|
console.log(` Proxy: ${url}/proxy`);
|
|
293
383
|
console.log(``);
|
|
294
384
|
console.log(`Route AI coding tools through the proxy:`);
|
|
@@ -339,6 +429,7 @@ async function runStart(args: string[]): Promise<void> {
|
|
|
339
429
|
if (providersJson !== undefined) {
|
|
340
430
|
serverEnv["AGENT_INSPECTOR_PROVIDERS_JSON"] = providersJson;
|
|
341
431
|
}
|
|
432
|
+
serverEnv["AGENT_INSPECTOR_CAPTURE_MODE"] = captureMode;
|
|
342
433
|
const serverProcess = spawn(serverCommand.command, serverCommand.args, {
|
|
343
434
|
stdio: background ? ["ignore", "ignore", "ignore"] : "inherit",
|
|
344
435
|
detached: background,
|
|
@@ -3,7 +3,7 @@ import { ArrowDownRight, ArrowLeft, ArrowUpRight, Check, Copy, Download, Plus }
|
|
|
3
3
|
|
|
4
4
|
import type { CapturedLog } from "../proxy/schemas";
|
|
5
5
|
import { exportLogsAsZip } from "../lib/export-logs";
|
|
6
|
-
import type { TimeDisplayFormat } from "../lib/runtimeConfig";
|
|
6
|
+
import type { CaptureMode, TimeDisplayFormat } from "../lib/runtimeConfig";
|
|
7
7
|
import { formatTimestampRange } from "../lib/timeDisplay";
|
|
8
8
|
import { cn, formatContextWindowTokens, formatTokens } from "../lib/utils";
|
|
9
9
|
import { useProviders } from "../lib/useProviders";
|
|
@@ -599,6 +599,7 @@ export type ProxyViewerProps = {
|
|
|
599
599
|
onClearGroup: (ids: number[]) => void;
|
|
600
600
|
viewMode: "simple" | "full";
|
|
601
601
|
onViewModeChange: (mode: "simple" | "full") => void;
|
|
602
|
+
captureMode: CaptureMode;
|
|
602
603
|
/** Live strip-Claude-Code-billing-header flag, sourced once at the container. */
|
|
603
604
|
strip: boolean;
|
|
604
605
|
/** Slow-response threshold in seconds. `0` disables the warning indicator. */
|
|
@@ -625,6 +626,7 @@ export function ProxyViewer({
|
|
|
625
626
|
onClearGroup,
|
|
626
627
|
viewMode,
|
|
627
628
|
onViewModeChange,
|
|
629
|
+
captureMode,
|
|
628
630
|
strip,
|
|
629
631
|
slowResponseThresholdSeconds,
|
|
630
632
|
timeDisplayFormat,
|
|
@@ -821,30 +823,36 @@ export function ProxyViewer({
|
|
|
821
823
|
))}
|
|
822
824
|
</SelectContent>
|
|
823
825
|
</Select>
|
|
824
|
-
|
|
825
|
-
<
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
826
|
+
{captureMode === "full" ? (
|
|
827
|
+
<div className="flex items-center border border-border rounded-md overflow-hidden">
|
|
828
|
+
<button
|
|
829
|
+
type="button"
|
|
830
|
+
onClick={() => onViewModeChange("simple")}
|
|
831
|
+
className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
|
|
832
|
+
viewMode === "simple"
|
|
833
|
+
? "bg-muted text-foreground"
|
|
834
|
+
: "text-muted-foreground hover:bg-muted/50"
|
|
835
|
+
}`}
|
|
836
|
+
>
|
|
837
|
+
Simple
|
|
838
|
+
</button>
|
|
839
|
+
<button
|
|
840
|
+
type="button"
|
|
841
|
+
onClick={() => onViewModeChange("full")}
|
|
842
|
+
className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
|
|
843
|
+
viewMode === "full"
|
|
844
|
+
? "bg-muted text-foreground"
|
|
845
|
+
: "text-muted-foreground hover:bg-muted/50"
|
|
846
|
+
}`}
|
|
847
|
+
>
|
|
848
|
+
Full
|
|
849
|
+
</button>
|
|
850
|
+
</div>
|
|
851
|
+
) : (
|
|
852
|
+
<div className="flex h-8 items-center rounded-md border border-border bg-muted px-3 text-xs text-foreground">
|
|
834
853
|
Simple
|
|
835
|
-
</
|
|
836
|
-
|
|
837
|
-
type="button"
|
|
838
|
-
onClick={() => onViewModeChange("full")}
|
|
839
|
-
className={`h-8 px-3 cursor-pointer transition-colors text-xs ${
|
|
840
|
-
viewMode === "full"
|
|
841
|
-
? "bg-muted text-foreground"
|
|
842
|
-
: "text-muted-foreground hover:bg-muted/50"
|
|
843
|
-
}`}
|
|
844
|
-
>
|
|
845
|
-
Full
|
|
846
|
-
</button>
|
|
847
|
-
</div>
|
|
854
|
+
</div>
|
|
855
|
+
)}
|
|
848
856
|
<div className="flex-1" />
|
|
849
857
|
{!hasSessionContext && (
|
|
850
858
|
<span className="text-muted-foreground text-xs font-mono">
|
|
@@ -330,7 +330,7 @@ export function ProxyViewerContainer({
|
|
|
330
330
|
|
|
331
331
|
// Read the strip config once at the container so the virtualized list does
|
|
332
332
|
// not need N independent SWR subscriptions per row.
|
|
333
|
-
const { strip, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
|
|
333
|
+
const { strip, captureMode, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
|
|
334
334
|
|
|
335
335
|
return (
|
|
336
336
|
<>
|
|
@@ -352,6 +352,7 @@ export function ProxyViewerContainer({
|
|
|
352
352
|
onClearGroup={handleClearGroup}
|
|
353
353
|
viewMode={viewMode}
|
|
354
354
|
onViewModeChange={setViewMode}
|
|
355
|
+
captureMode={captureMode}
|
|
355
356
|
strip={strip}
|
|
356
357
|
slowResponseThresholdSeconds={slowResponseThresholdSeconds}
|
|
357
358
|
timeDisplayFormat={timeDisplayFormat}
|
|
@@ -196,6 +196,7 @@ function OnboardingSettingsTab(): JSX.Element {
|
|
|
196
196
|
function ProxySettingsTab(): JSX.Element {
|
|
197
197
|
const {
|
|
198
198
|
strip,
|
|
199
|
+
captureMode,
|
|
199
200
|
slowResponseThresholdSeconds,
|
|
200
201
|
timeDisplayFormat,
|
|
201
202
|
isLoading,
|
|
@@ -205,6 +206,7 @@ function ProxySettingsTab(): JSX.Element {
|
|
|
205
206
|
} = useStripConfig();
|
|
206
207
|
const [error, setError] = useState<string | null>(null);
|
|
207
208
|
const [pending, setPending] = useState(false);
|
|
209
|
+
const alternateCaptureMode = captureMode === "full" ? "simple" : "full";
|
|
208
210
|
|
|
209
211
|
const handleToggle = useCallback(
|
|
210
212
|
async (next: boolean) => {
|
|
@@ -253,6 +255,14 @@ function ProxySettingsTab(): JSX.Element {
|
|
|
253
255
|
|
|
254
256
|
return (
|
|
255
257
|
<div className="space-y-4">
|
|
258
|
+
<div className="space-y-1">
|
|
259
|
+
<h3 className="text-sm font-semibold">Capture mode</h3>
|
|
260
|
+
<p className="text-xs text-muted-foreground">
|
|
261
|
+
This process is running in <code>{captureMode}</code> mode. Restart with{" "}
|
|
262
|
+
<code>agent-inspector --mode {alternateCaptureMode}</code> to change it.
|
|
263
|
+
</p>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
256
266
|
<div className="space-y-1">
|
|
257
267
|
<h3 className="text-sm font-semibold">Claude Code billing header</h3>
|
|
258
268
|
<p className="text-xs text-muted-foreground">
|
package/src/lib/runtimeConfig.ts
CHANGED
|
@@ -3,10 +3,13 @@ import { z } from "zod";
|
|
|
3
3
|
export const DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS = 10;
|
|
4
4
|
export const MAX_SLOW_RESPONSE_THRESHOLD_SECONDS = 600;
|
|
5
5
|
export const DEFAULT_TIME_DISPLAY_FORMAT = "time";
|
|
6
|
+
export const DEFAULT_CAPTURE_MODE = "simple";
|
|
6
7
|
|
|
7
8
|
export const TimeDisplayFormatSchema = z.enum(["time", "full"]);
|
|
9
|
+
export const CaptureModeSchema = z.enum(["simple", "full"]);
|
|
8
10
|
|
|
9
11
|
export type TimeDisplayFormat = z.infer<typeof TimeDisplayFormatSchema>;
|
|
12
|
+
export type CaptureMode = z.infer<typeof CaptureModeSchema>;
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Schema for the runtime proxy config. Shared between server
|
|
@@ -26,6 +29,7 @@ export const RuntimeConfigSchema = z.object({
|
|
|
26
29
|
.max(MAX_SLOW_RESPONSE_THRESHOLD_SECONDS)
|
|
27
30
|
.default(DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS),
|
|
28
31
|
timeDisplayFormat: TimeDisplayFormatSchema.default(DEFAULT_TIME_DISPLAY_FORMAT),
|
|
32
|
+
captureMode: CaptureModeSchema.default(DEFAULT_CAPTURE_MODE),
|
|
29
33
|
});
|
|
30
34
|
|
|
31
35
|
export type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import useSWR, { type SWRResponse, useSWRConfig } from "swr";
|
|
2
2
|
import { fetchJson } from "./apiClient";
|
|
3
3
|
import {
|
|
4
|
+
DEFAULT_CAPTURE_MODE,
|
|
4
5
|
DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
5
6
|
DEFAULT_TIME_DISPLAY_FORMAT,
|
|
7
|
+
type CaptureMode,
|
|
6
8
|
RuntimeConfigSchema,
|
|
7
9
|
type TimeDisplayFormat,
|
|
8
10
|
type RuntimeConfig,
|
|
@@ -22,7 +24,9 @@ async function fetcher(url: string): Promise<RuntimeConfig> {
|
|
|
22
24
|
/**
|
|
23
25
|
* PATCH the runtime config. Exported separately for testability.
|
|
24
26
|
*/
|
|
25
|
-
|
|
27
|
+
type RuntimeConfigPatch = Partial<Omit<RuntimeConfig, "captureMode">>;
|
|
28
|
+
|
|
29
|
+
export async function setRuntimeConfig(patch: RuntimeConfigPatch): Promise<RuntimeConfig> {
|
|
26
30
|
return fetchJson(
|
|
27
31
|
STRIP_CONFIG_SWR_KEY,
|
|
28
32
|
RuntimeConfigSchema,
|
|
@@ -37,6 +41,7 @@ export async function setRuntimeConfig(patch: Partial<RuntimeConfig>): Promise<R
|
|
|
37
41
|
|
|
38
42
|
export type UseStripConfig = {
|
|
39
43
|
strip: boolean;
|
|
44
|
+
captureMode: CaptureMode;
|
|
40
45
|
slowResponseThresholdSeconds: number;
|
|
41
46
|
timeDisplayFormat: TimeDisplayFormat;
|
|
42
47
|
isLoading: boolean;
|
|
@@ -66,16 +71,18 @@ export function useStripConfig(): UseStripConfig {
|
|
|
66
71
|
const { mutate: globalMutate } = useSWRConfig();
|
|
67
72
|
|
|
68
73
|
const strip = response.data?.stripClaudeCodeBillingHeader ?? false;
|
|
74
|
+
const captureMode = response.data?.captureMode ?? DEFAULT_CAPTURE_MODE;
|
|
69
75
|
const slowResponseThresholdSeconds =
|
|
70
76
|
response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS;
|
|
71
77
|
const timeDisplayFormat = response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT;
|
|
72
78
|
|
|
73
|
-
const optimisticConfig = (patch:
|
|
79
|
+
const optimisticConfig = (patch: RuntimeConfigPatch): RuntimeConfig => ({
|
|
74
80
|
stripClaudeCodeBillingHeader: response.data?.stripClaudeCodeBillingHeader ?? false,
|
|
75
81
|
hasSeenOnboarding: response.data?.hasSeenOnboarding ?? false,
|
|
76
82
|
slowResponseThresholdSeconds:
|
|
77
83
|
response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
78
84
|
timeDisplayFormat: response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT,
|
|
85
|
+
captureMode,
|
|
79
86
|
...patch,
|
|
80
87
|
});
|
|
81
88
|
|
|
@@ -113,6 +120,7 @@ export function useStripConfig(): UseStripConfig {
|
|
|
113
120
|
|
|
114
121
|
return {
|
|
115
122
|
strip,
|
|
123
|
+
captureMode,
|
|
116
124
|
slowResponseThresholdSeconds,
|
|
117
125
|
timeDisplayFormat,
|
|
118
126
|
isLoading: response.isLoading,
|
package/src/proxy/config.ts
CHANGED
|
@@ -11,9 +11,12 @@ import { join } from "node:path";
|
|
|
11
11
|
import { logger } from "./logger";
|
|
12
12
|
import { getDataDir } from "./dataDir";
|
|
13
13
|
import {
|
|
14
|
+
CaptureModeSchema,
|
|
15
|
+
DEFAULT_CAPTURE_MODE,
|
|
14
16
|
DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
15
17
|
DEFAULT_TIME_DISPLAY_FORMAT,
|
|
16
18
|
RuntimeConfigSchema,
|
|
19
|
+
type CaptureMode,
|
|
17
20
|
type RuntimeConfig,
|
|
18
21
|
} from "../lib/runtimeConfig";
|
|
19
22
|
|
|
@@ -52,7 +55,22 @@ function getConfigTempPath(): string {
|
|
|
52
55
|
// Internal: writeable singleton. Read via getConfig(), mutate via setConfig().
|
|
53
56
|
let currentConfig: RuntimeConfig = resolveInitialConfig();
|
|
54
57
|
|
|
58
|
+
type MutableRuntimeConfig = Omit<RuntimeConfig, "captureMode">;
|
|
59
|
+
|
|
60
|
+
function resolveInitialCaptureMode(): CaptureMode {
|
|
61
|
+
const value = process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
|
|
62
|
+
if (value === undefined || value === "") return DEFAULT_CAPTURE_MODE;
|
|
63
|
+
|
|
64
|
+
const result = CaptureModeSchema.safeParse(value);
|
|
65
|
+
if (result.success) return result.data;
|
|
66
|
+
|
|
67
|
+
logger.warn(`[config] Invalid capture mode ${value}; falling back to ${DEFAULT_CAPTURE_MODE}`);
|
|
68
|
+
return DEFAULT_CAPTURE_MODE;
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
function resolveInitialConfig(): RuntimeConfig {
|
|
72
|
+
const captureMode = resolveInitialCaptureMode();
|
|
73
|
+
|
|
56
74
|
// 1. Persisted file
|
|
57
75
|
const filePath = getConfigFilePath();
|
|
58
76
|
if (existsSync(filePath)) {
|
|
@@ -62,7 +80,7 @@ function resolveInitialConfig(): RuntimeConfig {
|
|
|
62
80
|
const result = RuntimeConfigSchema.safeParse(parsed);
|
|
63
81
|
if (result.success) {
|
|
64
82
|
logger.info(`[config] Loaded persisted config from ${filePath}`);
|
|
65
|
-
return result.data;
|
|
83
|
+
return { ...result.data, captureMode };
|
|
66
84
|
}
|
|
67
85
|
logger.warn(
|
|
68
86
|
`[config] Persisted config at ${filePath} is invalid; falling back to env/default`,
|
|
@@ -81,6 +99,7 @@ function resolveInitialConfig(): RuntimeConfig {
|
|
|
81
99
|
hasSeenOnboarding: false,
|
|
82
100
|
slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
83
101
|
timeDisplayFormat: DEFAULT_TIME_DISPLAY_FORMAT,
|
|
102
|
+
captureMode,
|
|
84
103
|
};
|
|
85
104
|
}
|
|
86
105
|
|
|
@@ -90,6 +109,7 @@ function resolveInitialConfig(): RuntimeConfig {
|
|
|
90
109
|
hasSeenOnboarding: false,
|
|
91
110
|
slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
92
111
|
timeDisplayFormat: DEFAULT_TIME_DISPLAY_FORMAT,
|
|
112
|
+
captureMode,
|
|
93
113
|
};
|
|
94
114
|
}
|
|
95
115
|
|
|
@@ -106,10 +126,11 @@ export function getConfig(): RuntimeConfig {
|
|
|
106
126
|
* do not throw — the in-memory value is always updated so the caller
|
|
107
127
|
* sees a consistent response.
|
|
108
128
|
*/
|
|
109
|
-
export function setConfig(patch: Partial<
|
|
129
|
+
export function setConfig(patch: Partial<MutableRuntimeConfig>): RuntimeConfig {
|
|
110
130
|
const next: RuntimeConfig = {
|
|
111
131
|
...currentConfig,
|
|
112
132
|
...patch,
|
|
133
|
+
captureMode: currentConfig.captureMode,
|
|
113
134
|
};
|
|
114
135
|
currentConfig = next;
|
|
115
136
|
persistConfig(next);
|
|
@@ -152,7 +173,7 @@ function persistConfig(value: RuntimeConfig): boolean {
|
|
|
152
173
|
}
|
|
153
174
|
|
|
154
175
|
try {
|
|
155
|
-
writeFileSync(tempPath, JSON.stringify(value), "utf-8");
|
|
176
|
+
writeFileSync(tempPath, JSON.stringify(toPersistedConfig(value)), "utf-8");
|
|
156
177
|
} catch (err) {
|
|
157
178
|
logger.error(`[config] Failed to write config temp file: ${String(err)}`);
|
|
158
179
|
return false;
|
|
@@ -173,3 +194,12 @@ function persistConfig(value: RuntimeConfig): boolean {
|
|
|
173
194
|
}
|
|
174
195
|
}
|
|
175
196
|
}
|
|
197
|
+
|
|
198
|
+
function toPersistedConfig(value: RuntimeConfig): Record<string, unknown> {
|
|
199
|
+
return {
|
|
200
|
+
stripClaudeCodeBillingHeader: value.stripClaudeCodeBillingHeader,
|
|
201
|
+
hasSeenOnboarding: value.hasSeenOnboarding,
|
|
202
|
+
slowResponseThresholdSeconds: value.slowResponseThresholdSeconds,
|
|
203
|
+
timeDisplayFormat: value.timeDisplayFormat,
|
|
204
|
+
};
|
|
205
|
+
}
|