@tonyclaw/agent-inspector 2.0.1 → 2.0.3
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 +344 -53
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-sVLGhCO3.js → CompareDrawer-D5A4bTfV.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-Da0jpBkp.js +101 -0
- package/.output/public/assets/{ReplayDialog-DxbFUqNW.js → ReplayDialog-CxUk_TF0.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-CSmGQa_g.js → RequestAnatomy-DIlzjgjJ.js} +1 -1
- package/.output/public/assets/ResponseView-DQCuKJ1G.js +1 -0
- package/.output/public/assets/{StreamingChunkSequence-BzqpY0TN.js → StreamingChunkSequence-DHk4SGGL.js} +1 -1
- package/.output/public/assets/_sessionId-dY1TTl7N.js +1 -0
- package/.output/public/assets/index-D7wwbwly.css +1 -0
- package/.output/public/assets/index-FqQZbfl2.js +1 -0
- package/.output/public/assets/{json-viewer-CKNMihlh.js → json-viewer-BbU0n8eM.js} +1 -1
- package/.output/public/assets/{main-yWf8dv9w.js → main-CZT_F-gu.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +8 -8
- package/.output/server/{_sessionId-DfHd0gd8.mjs → _sessionId-B-s9P7fJ.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-DGYAUWgF.mjs → CompareDrawer-C08L3UOO.mjs} +4 -4
- package/.output/server/_ssr/{ProxyViewerContainer-fawglkTo.mjs → ProxyViewerContainer-CMWl3Ijy.mjs} +414 -70
- package/.output/server/_ssr/{ReplayDialog-B4vlKa2W.mjs → ReplayDialog-CPDo9_G5.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-BNQvEIZK.mjs → RequestAnatomy-D9wt_K1E.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-X6X6G16_.mjs → ResponseView-DXaL7nY3.mjs} +4 -4
- package/.output/server/_ssr/{StreamingChunkSequence-BPVN3MnF.mjs → StreamingChunkSequence-B_hudZyb.mjs} +3 -3
- package/.output/server/_ssr/{index-CXmpc2X5.mjs → index-CuE_BN86.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-3XC3eq4R.mjs → json-viewer-Ci6kkjde.mjs} +2 -2
- package/.output/server/_ssr/{router-C0B2qvIM.mjs → router-BemxgIg7.mjs} +402 -131
- package/.output/server/{_tanstack-start-manifest_v-7tfsmd2I.mjs → _tanstack-start-manifest_v--L1_b4sd.mjs} +1 -1
- package/.output/server/index.mjs +62 -62
- package/README.md +50 -7
- package/package.json +3 -2
- package/scripts/setup-codex-skill.mjs +38 -0
- package/scripts/setup-windows-runtime.mjs +4 -3
- package/src/cli/onboard.ts +175 -68
- package/src/cli/templates/codex-skill-onboard.ts +210 -0
- package/src/components/providers/ProviderCard.tsx +2 -27
- package/src/components/providers/ProvidersPanel.tsx +16 -0
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +218 -0
- package/src/components/proxy-viewer/ConversationGroup.tsx +6 -0
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +33 -0
- package/src/components/proxy-viewer/TurnGroup.tsx +11 -1
- package/src/components/proxy-viewer/viewerState.ts +177 -0
- package/src/knowledge/openclawClient.ts +34 -5
- package/src/knowledge/openclawGatewayClient.ts +237 -0
- package/src/knowledge/openclawMarkdown.ts +146 -0
- package/src/lib/providerTestPrompt.ts +78 -0
- package/src/proxy/chunkStorage.ts +3 -4
- package/src/proxy/logger.ts +8 -15
- package/src/proxy/store.ts +8 -16
- package/src/routes/api/providers.$providerId.test.log.ts +7 -99
- package/.output/public/assets/ProxyViewerContainer-p9QvzZ6U.js +0 -101
- package/.output/public/assets/ResponseView-B5f89c8Z.js +0 -1
- package/.output/public/assets/_sessionId-BF7ftHV3.js +0 -1
- package/.output/public/assets/index-BU0PpLby.js +0 -1
- package/.output/public/assets/index-CpWG2hFn.css +0 -1
package/src/cli/onboard.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agent-inspector onboard` subcommand.
|
|
3
3
|
*
|
|
4
|
-
* Generates
|
|
5
|
-
*
|
|
4
|
+
* Generates Agent Inspector onboarding skills into the user's home dir.
|
|
5
|
+
* Claude Code gets a skill + slash command. Codex gets a skill under
|
|
6
|
+
* ~/.codex/skills that guides the user through connecting /api/mcp.
|
|
6
7
|
*
|
|
7
|
-
* Idempotent by default: re-runs without `--force`
|
|
8
|
-
* overwrites, `--dry-run` writes nothing. Designed as an explicit
|
|
9
|
-
* that can fail softly without blocking normal CLI use.
|
|
8
|
+
* Idempotent by default: re-runs without `--force` only write missing files.
|
|
9
|
+
* `--force` overwrites, `--dry-run` writes nothing. Designed as an explicit
|
|
10
|
+
* setup step that can fail softly without blocking normal CLI use.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
|
|
@@ -16,6 +17,10 @@ import { fileURLToPath } from "node:url";
|
|
|
16
17
|
|
|
17
18
|
import { detectAll, detectFirst } from "./detect-tools.js";
|
|
18
19
|
import { renderCommandOnboard } from "./templates/command-onboard.js";
|
|
20
|
+
import {
|
|
21
|
+
renderCodexSkillOnboard,
|
|
22
|
+
REQUIRED_CODEX_PHASE_HEADINGS,
|
|
23
|
+
} from "./templates/codex-skill-onboard.js";
|
|
19
24
|
import { renderSkillOnboard, REQUIRED_PHASE_HEADINGS } from "./templates/skill-onboard.js";
|
|
20
25
|
|
|
21
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -37,7 +42,23 @@ export type OnboardFlags = {
|
|
|
37
42
|
dryRun: boolean;
|
|
38
43
|
skipProvider: boolean;
|
|
39
44
|
skipToolWire: boolean;
|
|
45
|
+
codexOnly: boolean;
|
|
46
|
+
skipCodexSkill: boolean;
|
|
40
47
|
skillDir: string | null;
|
|
48
|
+
codexSkillDir: string | null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type OnboardTargets = {
|
|
52
|
+
claudeSkillFile: string;
|
|
53
|
+
claudeCommandFile: string;
|
|
54
|
+
codexSkillFile: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type PlannedFile = {
|
|
58
|
+
label: string;
|
|
59
|
+
path: string;
|
|
60
|
+
body: string;
|
|
61
|
+
enabled: boolean;
|
|
41
62
|
};
|
|
42
63
|
|
|
43
64
|
function parseFlags(argv: readonly string[]): OnboardFlags {
|
|
@@ -46,7 +67,10 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
|
|
|
46
67
|
dryRun: false,
|
|
47
68
|
skipProvider: false,
|
|
48
69
|
skipToolWire: false,
|
|
70
|
+
codexOnly: false,
|
|
71
|
+
skipCodexSkill: false,
|
|
49
72
|
skillDir: null,
|
|
73
|
+
codexSkillDir: null,
|
|
50
74
|
};
|
|
51
75
|
for (let i = 0; i < argv.length; i++) {
|
|
52
76
|
const arg = argv[i];
|
|
@@ -65,6 +89,12 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
|
|
|
65
89
|
case "--skip-tool-wire":
|
|
66
90
|
flags.skipToolWire = true;
|
|
67
91
|
break;
|
|
92
|
+
case "--codex-only":
|
|
93
|
+
flags.codexOnly = true;
|
|
94
|
+
break;
|
|
95
|
+
case "--skip-codex-skill":
|
|
96
|
+
flags.skipCodexSkill = true;
|
|
97
|
+
break;
|
|
68
98
|
case "--skill-dir": {
|
|
69
99
|
const next = argv[i + 1];
|
|
70
100
|
if (next === undefined) {
|
|
@@ -75,6 +105,18 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
|
|
|
75
105
|
i++;
|
|
76
106
|
break;
|
|
77
107
|
}
|
|
108
|
+
case "--codex-skill-dir": {
|
|
109
|
+
const next = argv[i + 1];
|
|
110
|
+
if (next === undefined) {
|
|
111
|
+
process.stderr.write(
|
|
112
|
+
"agent-inspector onboard: --codex-skill-dir requires a path argument\n",
|
|
113
|
+
);
|
|
114
|
+
process.exit(2);
|
|
115
|
+
}
|
|
116
|
+
flags.codexSkillDir = next;
|
|
117
|
+
i++;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
78
120
|
case "--help":
|
|
79
121
|
case "-h":
|
|
80
122
|
printHelp();
|
|
@@ -89,18 +131,21 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
|
|
|
89
131
|
}
|
|
90
132
|
|
|
91
133
|
function printHelp(): void {
|
|
92
|
-
process.stdout.write(`agent-inspector onboard
|
|
134
|
+
process.stdout.write(`agent-inspector onboard - install Agent Inspector onboarding skills
|
|
93
135
|
|
|
94
136
|
Usage:
|
|
95
137
|
agent-inspector onboard [options]
|
|
96
138
|
|
|
97
139
|
Options:
|
|
98
|
-
--force
|
|
99
|
-
--dry-run
|
|
100
|
-
--skip-provider
|
|
101
|
-
--skip-tool-wire
|
|
102
|
-
--skill
|
|
103
|
-
-
|
|
140
|
+
--force Overwrite existing generated skills and slash command
|
|
141
|
+
--dry-run Print target paths and template previews, write nothing
|
|
142
|
+
--skip-provider Skip the provider-setup phase in the Claude skill body
|
|
143
|
+
--skip-tool-wire Skip the wire-tool phase in the Claude skill body
|
|
144
|
+
--skip-codex-skill Install only the Claude Code skill and slash command
|
|
145
|
+
--codex-only Install only the Codex skill
|
|
146
|
+
--skill-dir <path> Override the target Claude root directory (default: ~/.claude)
|
|
147
|
+
--codex-skill-dir <path> Override the target Codex skills directory (default: ~/.codex/skills)
|
|
148
|
+
-h, --help Show this help
|
|
104
149
|
|
|
105
150
|
Exit codes:
|
|
106
151
|
0 success (or already installed)
|
|
@@ -109,16 +154,16 @@ Exit codes:
|
|
|
109
154
|
`);
|
|
110
155
|
}
|
|
111
156
|
|
|
112
|
-
function resolveTargets(flags: OnboardFlags): {
|
|
113
|
-
skillFile: string;
|
|
114
|
-
commandFile: string;
|
|
115
|
-
} {
|
|
157
|
+
function resolveTargets(flags: OnboardFlags): OnboardTargets {
|
|
116
158
|
const claudeRoot = flags.skillDir ?? join(homedir(), ".claude");
|
|
117
|
-
const
|
|
118
|
-
const
|
|
159
|
+
const claudeSkillDir = join(claudeRoot, "skills", SKILL_DIR_NAME);
|
|
160
|
+
const claudeCommandsDir = join(claudeRoot, "commands");
|
|
161
|
+
const codexRoot = process.env["CODEX_HOME"] ?? join(homedir(), ".codex");
|
|
162
|
+
const codexSkillsDir = flags.codexSkillDir ?? join(codexRoot, "skills");
|
|
119
163
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
164
|
+
claudeSkillFile: join(claudeSkillDir, SKILL_FILE_NAME),
|
|
165
|
+
claudeCommandFile: join(claudeCommandsDir, COMMAND_FILE_NAME),
|
|
166
|
+
codexSkillFile: join(codexSkillsDir, SKILL_DIR_NAME, SKILL_FILE_NAME),
|
|
122
167
|
};
|
|
123
168
|
}
|
|
124
169
|
|
|
@@ -135,6 +180,67 @@ function buildDetectedSummary(): string {
|
|
|
135
180
|
return ` - Detected: ${presentList}\n - Not detected: ${absentList}`;
|
|
136
181
|
}
|
|
137
182
|
|
|
183
|
+
function readPackageVersion(): string {
|
|
184
|
+
try {
|
|
185
|
+
const raw: unknown = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
|
|
186
|
+
if (isObject(raw) && typeof raw["version"] === "string") {
|
|
187
|
+
return raw["version"];
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// best-effort: leave version as the fallback
|
|
191
|
+
}
|
|
192
|
+
return "0.0.0";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function buildPlannedFiles(
|
|
196
|
+
flags: OnboardFlags,
|
|
197
|
+
targets: OnboardTargets,
|
|
198
|
+
version: string,
|
|
199
|
+
): PlannedFile[] {
|
|
200
|
+
const installClaude = !flags.codexOnly;
|
|
201
|
+
const installCodex = !flags.skipCodexSkill;
|
|
202
|
+
const detectedSummary = installClaude ? buildDetectedSummary() : "";
|
|
203
|
+
const claudeSkillBody = installClaude
|
|
204
|
+
? renderSkillOnboard({
|
|
205
|
+
version,
|
|
206
|
+
port: DEFAULT_PORT,
|
|
207
|
+
detectedSummary,
|
|
208
|
+
})
|
|
209
|
+
: "";
|
|
210
|
+
const commandBody = installClaude ? renderCommandOnboard() : "";
|
|
211
|
+
const codexSkillBody = installCodex
|
|
212
|
+
? renderCodexSkillOnboard({
|
|
213
|
+
version,
|
|
214
|
+
port: DEFAULT_PORT,
|
|
215
|
+
})
|
|
216
|
+
: "";
|
|
217
|
+
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
label: "Claude skill",
|
|
221
|
+
path: targets.claudeSkillFile,
|
|
222
|
+
body: claudeSkillBody,
|
|
223
|
+
enabled: installClaude,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
label: "Claude command",
|
|
227
|
+
path: targets.claudeCommandFile,
|
|
228
|
+
body: commandBody,
|
|
229
|
+
enabled: installClaude,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
label: "Codex skill",
|
|
233
|
+
path: targets.codexSkillFile,
|
|
234
|
+
body: codexSkillBody,
|
|
235
|
+
enabled: installCodex,
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function shouldWrite(file: PlannedFile, force: boolean): boolean {
|
|
241
|
+
return file.enabled && (force || !existsSync(file.path));
|
|
242
|
+
}
|
|
243
|
+
|
|
138
244
|
export function runOnboard(argv: readonly string[]): Promise<number> {
|
|
139
245
|
try {
|
|
140
246
|
return Promise.resolve(runOnboardSync(argv));
|
|
@@ -149,62 +255,57 @@ export function runOnboard(argv: readonly string[]): Promise<number> {
|
|
|
149
255
|
|
|
150
256
|
function runOnboardSync(argv: readonly string[]): number {
|
|
151
257
|
const flags = parseFlags(argv);
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// it the same as "already installed" so a partial state doesn't get
|
|
157
|
-
// half-rewritten on the next run.
|
|
158
|
-
if (!flags.force && existsSync(skillFile)) {
|
|
159
|
-
process.stdout.write(`agent-inspector onboard: already installed at ${skillFile}\n`);
|
|
160
|
-
process.stdout.write("Re-run with --force to refresh.\n");
|
|
161
|
-
return 0;
|
|
162
|
-
}
|
|
258
|
+
const targets = resolveTargets(flags);
|
|
259
|
+
const version = readPackageVersion();
|
|
260
|
+
const plannedFiles = buildPlannedFiles(flags, targets, version);
|
|
261
|
+
const enabledFiles = plannedFiles.filter((file) => file.enabled);
|
|
163
262
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
const raw: unknown = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
|
|
169
|
-
if (isObject(raw) && typeof raw["version"] === "string") {
|
|
170
|
-
version = raw["version"];
|
|
171
|
-
}
|
|
172
|
-
} catch {
|
|
173
|
-
// best-effort: leave version as the fallback
|
|
263
|
+
if (enabledFiles.length === 0) {
|
|
264
|
+
process.stderr.write("agent-inspector onboard: no onboarding target selected\n");
|
|
265
|
+
return 2;
|
|
174
266
|
}
|
|
175
267
|
|
|
176
|
-
const detectedSummary = buildDetectedSummary();
|
|
177
|
-
const skillBody = renderSkillOnboard({
|
|
178
|
-
version,
|
|
179
|
-
port: DEFAULT_PORT,
|
|
180
|
-
detectedSummary,
|
|
181
|
-
});
|
|
182
|
-
const commandBody = renderCommandOnboard();
|
|
183
|
-
|
|
184
268
|
if (flags.dryRun) {
|
|
185
269
|
process.stdout.write(`agent-inspector onboard --dry-run\n\n`);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
process.stdout.write(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
270
|
+
for (const file of enabledFiles) {
|
|
271
|
+
const status = existsSync(file.path) ? "exists" : "missing";
|
|
272
|
+
process.stdout.write(`${file.label}: ${file.path} (${status})\n`);
|
|
273
|
+
}
|
|
274
|
+
process.stdout.write(`\nClaude skill headings:\n`);
|
|
275
|
+
if (!flags.codexOnly) {
|
|
276
|
+
for (const heading of REQUIRED_PHASE_HEADINGS) {
|
|
277
|
+
process.stdout.write(` - ${heading}\n`);
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
process.stdout.write(` (disabled by --codex-only)\n`);
|
|
281
|
+
}
|
|
282
|
+
process.stdout.write(`\nCodex skill headings:\n`);
|
|
283
|
+
if (!flags.skipCodexSkill) {
|
|
284
|
+
for (const heading of REQUIRED_CODEX_PHASE_HEADINGS) {
|
|
285
|
+
process.stdout.write(` - ${heading}\n`);
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
process.stdout.write(` (disabled by --skip-codex-skill)\n`);
|
|
289
|
+
}
|
|
290
|
+
if (!flags.codexOnly) {
|
|
291
|
+
process.stdout.write(`\nDetected tools:\n${buildDetectedSummary()}\n`);
|
|
194
292
|
}
|
|
195
|
-
process.stdout.write(`\nDetected tools:\n${detectedSummary}\n`);
|
|
196
293
|
process.stdout.write(`\nNo files were written.\n`);
|
|
197
294
|
return 0;
|
|
198
295
|
}
|
|
199
296
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
297
|
+
const filesToWrite = plannedFiles.filter((file) => shouldWrite(file, flags.force));
|
|
298
|
+
if (filesToWrite.length === 0) {
|
|
299
|
+
process.stdout.write("agent-inspector onboard: all selected onboarding files already exist\n");
|
|
300
|
+
process.stdout.write("Re-run with --force to refresh.\n");
|
|
301
|
+
return 0;
|
|
302
|
+
}
|
|
204
303
|
|
|
205
304
|
try {
|
|
206
|
-
|
|
207
|
-
|
|
305
|
+
for (const file of filesToWrite) {
|
|
306
|
+
mkdirSync(dirname(file.path), { recursive: true });
|
|
307
|
+
writeFileSync(file.path, file.body, "utf8");
|
|
308
|
+
}
|
|
208
309
|
} catch (err) {
|
|
209
310
|
const msg = err instanceof Error ? err.message : String(err);
|
|
210
311
|
process.stderr.write(`agent-inspector onboard: failed to write files: ${msg}\n`);
|
|
@@ -214,11 +315,17 @@ function runOnboardSync(argv: readonly string[]): number {
|
|
|
214
315
|
const firstTool = detectFirst();
|
|
215
316
|
const toolHint = firstTool !== null ? firstTool.displayName : "no known tool detected";
|
|
216
317
|
|
|
217
|
-
|
|
218
|
-
|
|
318
|
+
for (const file of filesToWrite) {
|
|
319
|
+
process.stdout.write(`Installed ${file.label} to: ${file.path}\n`);
|
|
320
|
+
}
|
|
219
321
|
process.stdout.write(`\nNext steps:\n`);
|
|
220
|
-
|
|
221
|
-
|
|
322
|
+
if (!flags.codexOnly) {
|
|
323
|
+
process.stdout.write(` - Open Claude Code and run: /agent-inspector:onboard\n`);
|
|
324
|
+
}
|
|
325
|
+
if (!flags.skipCodexSkill) {
|
|
326
|
+
process.stdout.write(` - Open Codex and ask to use the agent-inspector-onboard skill\n`);
|
|
327
|
+
}
|
|
328
|
+
process.stdout.write(` - Refresh later: agent-inspector onboard --force\n`);
|
|
222
329
|
process.stdout.write(` - Detected primary tool: ${toolHint}\n`);
|
|
223
330
|
return 0;
|
|
224
331
|
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders `~/.codex/skills/agent-inspector-onboard/SKILL.md` for Codex.
|
|
3
|
+
*
|
|
4
|
+
* This is intentionally separate from the Claude Code onboarding skill because
|
|
5
|
+
* Codex uses `~/.codex/config.toml` and supports streamable HTTP MCP servers via
|
|
6
|
+
* `mcp_servers.<id>.url`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type CodexSkillOnboardContext = {
|
|
10
|
+
/** Package version (from `package.json`), stamped into the frontmatter. */
|
|
11
|
+
readonly version: string;
|
|
12
|
+
/** The default proxy port (mirrors `DEFAULT_PORT` in `src/cli.ts`). */
|
|
13
|
+
readonly port: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const REQUIRED_CODEX_PHASE_HEADINGS = [
|
|
17
|
+
"Phase 0: Preflight",
|
|
18
|
+
"Phase 1: Start Agent Inspector",
|
|
19
|
+
"Phase 2: Wire Codex MCP",
|
|
20
|
+
"Phase 3: Restart and verify Codex",
|
|
21
|
+
"Phase 4: First Inspector query",
|
|
22
|
+
"Phase 5: Capture and memory review",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export function renderCodexSkillOnboard(ctx: CodexSkillOnboardContext): string {
|
|
26
|
+
const { version, port } = ctx;
|
|
27
|
+
return `---
|
|
28
|
+
name: agent-inspector-onboard
|
|
29
|
+
description: Guide Codex users through connecting Agent Inspector v${version} as a local MCP server.
|
|
30
|
+
metadata:
|
|
31
|
+
author: agent-inspector
|
|
32
|
+
version: ${version}
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
# Agent Inspector onboard for Codex
|
|
36
|
+
|
|
37
|
+
Use this skill when the user wants to set up Agent Inspector inside Codex, connect the Agent Inspector MCP server, inspect captured model traffic from Codex, or turn Inspector sessions into reviewable knowledge candidates.
|
|
38
|
+
|
|
39
|
+
Agent Inspector runs a local web UI, proxy, REST API, and MCP endpoint on the same port. The default MCP endpoint is:
|
|
40
|
+
|
|
41
|
+
\`\`\`text
|
|
42
|
+
http://localhost:${port}/api/mcp
|
|
43
|
+
\`\`\`
|
|
44
|
+
|
|
45
|
+
This skill is installed at \`~/.codex/skills/agent-inspector-onboard/SKILL.md\`.
|
|
46
|
+
Codex user-level configuration lives in \`~/.codex/config.toml\`. Codex supports streamable HTTP MCP servers with \`mcp_servers.<id>.url\`, so no extra stdio bridge package is needed.
|
|
47
|
+
|
|
48
|
+
Keep this setup local and explicit. Agent Inspector MCP tools can read captured requests and provider configuration, so only wire it into trusted local Codex environments.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Phase 0: Preflight
|
|
53
|
+
|
|
54
|
+
Check that the package and Codex home exist without printing secrets.
|
|
55
|
+
|
|
56
|
+
\`\`\`powershell
|
|
57
|
+
Get-Command agent-inspector -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
|
|
58
|
+
Test-Path (Join-Path $env:USERPROFILE ".codex")
|
|
59
|
+
Test-Path (Join-Path $env:USERPROFILE ".codex\\config.toml")
|
|
60
|
+
\`\`\`
|
|
61
|
+
|
|
62
|
+
\`\`\`bash
|
|
63
|
+
command -v agent-inspector || true
|
|
64
|
+
test -d "$HOME/.codex" && echo "codex home: present" || echo "codex home: missing"
|
|
65
|
+
test -f "$HOME/.codex/config.toml" && echo "config: present" || echo "config: missing"
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
If \`agent-inspector\` is missing, ask the user to install it first:
|
|
69
|
+
|
|
70
|
+
\`\`\`bash
|
|
71
|
+
npm install -g @tonyclaw/agent-inspector
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
Do not read \`~/.codex/auth.json\`, token files, or unrelated Codex state files.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Phase 1: Start Agent Inspector
|
|
79
|
+
|
|
80
|
+
Start or reuse Agent Inspector before wiring MCP. Prefer the installed binary so Windows users get the branded runtime in Task Manager.
|
|
81
|
+
|
|
82
|
+
\`\`\`powershell
|
|
83
|
+
agent-inspector --background --no-open
|
|
84
|
+
Invoke-RestMethod -Uri "http://localhost:${port}/api/health" -TimeoutSec 3
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
\`\`\`bash
|
|
88
|
+
agent-inspector --background --no-open
|
|
89
|
+
curl -fsS "http://localhost:${port}/api/health"
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
If the health check fails, show only the relevant error and ask whether to diagnose startup before editing Codex config.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Phase 2: Wire Codex MCP
|
|
97
|
+
|
|
98
|
+
Add an \`agent-inspector\` MCP server to \`~/.codex/config.toml\`. Preserve the user's existing config and merge the table instead of replacing the file.
|
|
99
|
+
|
|
100
|
+
Recommended user-level config:
|
|
101
|
+
|
|
102
|
+
\`\`\`toml
|
|
103
|
+
[mcp_servers.agent-inspector]
|
|
104
|
+
url = "http://localhost:${port}/api/mcp"
|
|
105
|
+
startup_timeout_sec = 30
|
|
106
|
+
tool_timeout_sec = 60
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
If the user's Codex environment is managed by an admin allowlist, they may also need a matching identity rule in the managed requirements layer:
|
|
110
|
+
|
|
111
|
+
\`\`\`toml
|
|
112
|
+
[mcp_servers.agent-inspector.identity]
|
|
113
|
+
url = "http://localhost:${port}/api/mcp"
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
When editing:
|
|
117
|
+
|
|
118
|
+
- Ask before changing \`~/.codex/config.toml\`.
|
|
119
|
+
- Keep existing \`mcp_servers.*\`, \`projects.*\`, profiles, sandbox, approval, and model settings intact.
|
|
120
|
+
- Do not write provider API keys into Codex config.
|
|
121
|
+
- Do not expose Agent Inspector MCP beyond localhost.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Phase 3: Restart and verify Codex
|
|
126
|
+
|
|
127
|
+
Tell the user that Codex loads MCP servers at session startup. After saving \`config.toml\`, they should start a new Codex session or restart the Codex app.
|
|
128
|
+
|
|
129
|
+
Before restart, verify the HTTP endpoint itself:
|
|
130
|
+
|
|
131
|
+
\`\`\`bash
|
|
132
|
+
curl -sS -X POST "http://localhost:${port}/api/mcp" \\
|
|
133
|
+
-H "Content-Type: application/json" \\
|
|
134
|
+
-H "Accept: application/json, text/event-stream" \\
|
|
135
|
+
-d '{
|
|
136
|
+
"jsonrpc": "2.0",
|
|
137
|
+
"id": 1,
|
|
138
|
+
"method": "initialize",
|
|
139
|
+
"params": {
|
|
140
|
+
"protocolVersion": "2025-03-26",
|
|
141
|
+
"capabilities": {},
|
|
142
|
+
"clientInfo": { "name": "codex-onboard-check", "version": "0" }
|
|
143
|
+
}
|
|
144
|
+
}'
|
|
145
|
+
\`\`\`
|
|
146
|
+
|
|
147
|
+
Expected signal: a 200 response containing \`serverInfo\` with \`agent-inspector\`.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Phase 4: First Inspector query
|
|
152
|
+
|
|
153
|
+
After Codex restarts with the MCP server configured, ask Codex to use Agent Inspector:
|
|
154
|
+
|
|
155
|
+
\`\`\`text
|
|
156
|
+
Use Agent Inspector to list the latest captured logs.
|
|
157
|
+
\`\`\`
|
|
158
|
+
|
|
159
|
+
The expected tool is \`inspector_list_logs\`. If no logs exist yet, that is still a successful MCP connection.
|
|
160
|
+
|
|
161
|
+
Useful follow-up tools:
|
|
162
|
+
|
|
163
|
+
- \`inspector_list_logs\`
|
|
164
|
+
- \`inspector_get_log\`
|
|
165
|
+
- \`inspector_get_log_chunks\`
|
|
166
|
+
- \`inspector_list_sessions\`
|
|
167
|
+
- \`inspector_test_provider\`
|
|
168
|
+
- \`inspector_create_session_knowledge\`
|
|
169
|
+
- \`inspector_list_knowledge_candidates\`
|
|
170
|
+
- \`inspector_search_knowledge\`
|
|
171
|
+
- \`inspector_get_project_context\`
|
|
172
|
+
|
|
173
|
+
Provider mutation tools expose and modify local provider configuration. Ask for explicit user confirmation before using \`inspector_add_provider\`, \`inspector_update_provider\`, or \`inspector_replay_log\`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Phase 5: Capture and memory review
|
|
178
|
+
|
|
179
|
+
To capture Codex traffic through Agent Inspector, route a model client through:
|
|
180
|
+
|
|
181
|
+
\`\`\`text
|
|
182
|
+
http://localhost:${port}/proxy
|
|
183
|
+
\`\`\`
|
|
184
|
+
|
|
185
|
+
For Anthropic-compatible tools, set \`ANTHROPIC_BASE_URL=http://localhost:${port}/proxy\`. For OpenAI-compatible tools, set \`OPENAI_BASE_URL=http://localhost:${port}/proxy\`.
|
|
186
|
+
|
|
187
|
+
For a Provider Test memory probe, run a provider test in the Agent Inspector UI:
|
|
188
|
+
|
|
189
|
+
1. Open \`http://localhost:${port}\`.
|
|
190
|
+
2. Go to Settings -> Providers.
|
|
191
|
+
3. Add or edit a Provider.
|
|
192
|
+
4. Run Test.
|
|
193
|
+
5. Check the \`provider-test\` session.
|
|
194
|
+
|
|
195
|
+
Then ask Codex:
|
|
196
|
+
|
|
197
|
+
\`\`\`text
|
|
198
|
+
Use Agent Inspector to create knowledge candidates for the provider-test session, then list the candidates.
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
Expected flow:
|
|
202
|
+
|
|
203
|
+
- Raw Trace stays in Agent Inspector logs.
|
|
204
|
+
- Session Episode is reconstructed from the session.
|
|
205
|
+
- Memory Candidate is generated for review.
|
|
206
|
+
- Promotion to OpenClaw happens only after review through Agent Inspector.
|
|
207
|
+
|
|
208
|
+
Stop after showing candidates unless the user explicitly approves promotion.
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSX, type ReactNode,
|
|
1
|
+
import { type JSX, type ReactNode, useState } from "react";
|
|
2
2
|
import { Button } from "../ui/button";
|
|
3
3
|
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "../ui/tooltip";
|
|
4
4
|
import {
|
|
@@ -235,31 +235,6 @@ export function ProviderCard({
|
|
|
235
235
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
236
236
|
const [copied, setCopied] = useState(false);
|
|
237
237
|
const [showModelResults, setShowModelResults] = useState(false);
|
|
238
|
-
const hasLoggedRef = useRef<string | null>(null);
|
|
239
|
-
const lastTestedAtRef = useRef<string | undefined>(undefined);
|
|
240
|
-
|
|
241
|
-
// Reset log state when new test results arrive
|
|
242
|
-
if (testResults?.testedAt !== undefined && testResults.testedAt !== lastTestedAtRef.current) {
|
|
243
|
-
lastTestedAtRef.current = testResults.testedAt;
|
|
244
|
-
hasLoggedRef.current = null;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Call log API when user expands model test results for the first time
|
|
248
|
-
const handleToggleModelResults = useCallback(() => {
|
|
249
|
-
setShowModelResults((v) => {
|
|
250
|
-
const next = !v;
|
|
251
|
-
if (next && hasLoggedRef.current === null && testResults?.models !== undefined) {
|
|
252
|
-
hasLoggedRef.current = testResults.testedAt ?? "";
|
|
253
|
-
// Fire-and-forget: commit test results to dashboard log
|
|
254
|
-
void fetch(`/api/providers/${provider.id}/test/log`, {
|
|
255
|
-
method: "POST",
|
|
256
|
-
headers: { "Content-Type": "application/json" },
|
|
257
|
-
body: JSON.stringify(testResults),
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
return next;
|
|
261
|
-
});
|
|
262
|
-
}, [provider.id, testResults]);
|
|
263
238
|
|
|
264
239
|
function handleCopy() {
|
|
265
240
|
navigator.clipboard.writeText(provider.apiKey).catch(() => {});
|
|
@@ -382,7 +357,7 @@ export function ProviderCard({
|
|
|
382
357
|
<div className="border-t pt-2">
|
|
383
358
|
<button
|
|
384
359
|
type="button"
|
|
385
|
-
onClick={
|
|
360
|
+
onClick={() => setShowModelResults((value) => !value)}
|
|
386
361
|
className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
|
|
387
362
|
>
|
|
388
363
|
<span className="font-mono">{showModelResults ? "▾" : "▸"}</span>
|
|
@@ -54,6 +54,21 @@ function createProviderPayload(data: ProviderFormData) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
async function persistProviderTestLog(providerId: string, results: TestResults): Promise<void> {
|
|
58
|
+
if (results.models === undefined) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
await fetch(`/api/providers/${providerId}/test/log`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify(results),
|
|
66
|
+
});
|
|
67
|
+
} catch {
|
|
68
|
+
// Provider connection status is primary; log persistence is best-effort.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
type ProvidersPanelProps = {
|
|
58
73
|
externalProviders?: ProviderConfig[];
|
|
59
74
|
isLoading?: boolean;
|
|
@@ -227,6 +242,7 @@ export function ProvidersPanel({
|
|
|
227
242
|
if (res.ok) {
|
|
228
243
|
const results = await parseJsonResponse(res, ProviderTestResultsSchema);
|
|
229
244
|
updateTestResults(providerId, results);
|
|
245
|
+
await persistProviderTestLog(providerId, results);
|
|
230
246
|
} else {
|
|
231
247
|
updateTestResults(
|
|
232
248
|
providerId,
|