@tonyclaw/agent-inspector 2.0.41 → 2.0.42
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/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-DCg6S2cl.js → CompareDrawer-Cfqxlo-U.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-DsU6QETm.js → ProxyViewerContainer-CE9pAX4c.js} +26 -24
- package/.output/public/assets/{ReplayDialog-ClVgjSDE.js → ReplayDialog-daRidZo_.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BhqvLQp9.js → RequestAnatomy-CJ7EPQGQ.js} +1 -1
- package/.output/public/assets/{ResponseView-BZAJoK6B.js → ResponseView-xBmr54hB.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-CaORmoKX.js → StreamingChunkSequence-nK-0LpbM.js} +1 -1
- package/.output/public/assets/_sessionId-CbWEG5ej.js +1 -0
- package/.output/public/assets/index-D1MkoT4l.js +1 -0
- package/.output/public/assets/{main-CI3HFEcV.js → main-DOy_Q96H.js} +2 -2
- package/.output/server/{_sessionId-DQ0ljHQ8.mjs → _sessionId-DtYRZHlM.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-nkVa9epn.mjs → CompareDrawer-fy1ARwtx.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-CXA7iH3H.mjs → ProxyViewerContainer-9GJtCWCq.mjs} +131 -6
- package/.output/server/_ssr/{ReplayDialog-CK71-ucq.mjs → ReplayDialog-BrS7syOE.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-DDCUJJ3X.mjs → RequestAnatomy-BtWt1cWY.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-C6ZA7V3B.mjs → ResponseView-CkKGYE6m.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-DW5v_o6G.mjs → StreamingChunkSequence-C7rB3osr.mjs} +2 -2
- package/.output/server/_ssr/{index-Ckex98yq.mjs → index-D24WforP.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-ChJIVv7-.mjs → router-CVqVjBzJ.mjs} +1312 -467
- package/.output/server/{_tanstack-start-manifest_v-CJ4__weU.mjs → _tanstack-start-manifest_v-DSikvwru.mjs} +1 -1
- package/.output/server/index.mjs +57 -57
- package/README.md +36 -12
- package/package.json +1 -1
- package/src/components/providers/SettingsDialog.tsx +137 -0
- package/src/lib/runContract.ts +3 -0
- package/src/mcp/currentContext.ts +65 -0
- package/src/mcp/mode.ts +56 -0
- package/src/mcp/server.ts +466 -15
- package/src/mcp/toolHandlers.ts +372 -0
- package/src/proxy/evidenceExporter.ts +1 -0
- package/src/proxy/runStore.ts +74 -22
- package/.output/public/assets/_sessionId-BHqywvM3.js +0 -1
- package/.output/public/assets/index-B_LPYuM0.js +0 -1
package/src/mcp/toolHandlers.ts
CHANGED
|
@@ -16,10 +16,18 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { Buffer } from "node:buffer";
|
|
19
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import JSZip from "jszip";
|
|
19
22
|
import { z } from "zod";
|
|
20
23
|
import { getCurrentPort } from "../lib/serverPort";
|
|
21
24
|
import { LoopbackTimeoutError, type CallApiOptions } from "./loopback";
|
|
22
25
|
import { extractLastUserMessagePreview, extractResponsePreview } from "./previewExtractor";
|
|
26
|
+
import {
|
|
27
|
+
getCurrentContext,
|
|
28
|
+
setCurrentContext,
|
|
29
|
+
type InspectorCurrentContextPatch,
|
|
30
|
+
} from "./currentContext";
|
|
23
31
|
import {
|
|
24
32
|
type AddInspectorGroupSessionInput,
|
|
25
33
|
type CreateInspectorGroupInput,
|
|
@@ -36,6 +44,7 @@ import {
|
|
|
36
44
|
exportInspectorGroupEvidence,
|
|
37
45
|
getInspectorGroup,
|
|
38
46
|
listInspectorGroups,
|
|
47
|
+
readInspectorGroupEvidence,
|
|
39
48
|
updateInspectorGroup,
|
|
40
49
|
} from "../services/groups";
|
|
41
50
|
import {
|
|
@@ -43,6 +52,7 @@ import {
|
|
|
43
52
|
exportInspectorRunEvidence,
|
|
44
53
|
getInspectorRun,
|
|
45
54
|
listInspectorRecentFailures,
|
|
55
|
+
readInspectorRunEvidence,
|
|
46
56
|
updateInspectorRun,
|
|
47
57
|
} from "../services/runs";
|
|
48
58
|
|
|
@@ -423,6 +433,227 @@ export async function exportGroupEvidenceImpl(args: ExportGroupEvidenceArgs): Pr
|
|
|
423
433
|
return textJson(exported.value);
|
|
424
434
|
}
|
|
425
435
|
|
|
436
|
+
const SESSION_TIMELINE_DEFAULT_LIMIT = 20;
|
|
437
|
+
const SESSION_TIMELINE_LIMIT_MAX = 50;
|
|
438
|
+
|
|
439
|
+
export type SessionTimelineArgs = {
|
|
440
|
+
sessionId: string;
|
|
441
|
+
limit?: number;
|
|
442
|
+
offset?: number;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
function clampTimelineLimit(input: number | undefined): number {
|
|
446
|
+
if (input === undefined) return SESSION_TIMELINE_DEFAULT_LIMIT;
|
|
447
|
+
if (!Number.isFinite(input) || input < 1) return 1;
|
|
448
|
+
return Math.min(Math.floor(input), SESSION_TIMELINE_LIMIT_MAX);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function buildSessionTimeline(
|
|
452
|
+
sessionId: string,
|
|
453
|
+
logs: CapturedLog[],
|
|
454
|
+
total: number,
|
|
455
|
+
limit: number,
|
|
456
|
+
): Record<string, unknown> {
|
|
457
|
+
const sortedLogs = [...logs].sort((left, right) => left.id - right.id);
|
|
458
|
+
const events = sortedLogs.map((log, index) => ({
|
|
459
|
+
sequence: index + 1,
|
|
460
|
+
kind: "request",
|
|
461
|
+
logId: log.id,
|
|
462
|
+
...buildLogSummary(log),
|
|
463
|
+
}));
|
|
464
|
+
const first = sortedLogs[0];
|
|
465
|
+
const last = sortedLogs[sortedLogs.length - 1];
|
|
466
|
+
const errorCount = events.filter((event) => event.hasError).length;
|
|
467
|
+
const models = [...new Set(sortedLogs.map((log) => log.model).filter((model) => model !== null))];
|
|
468
|
+
const providers = [
|
|
469
|
+
...new Set(
|
|
470
|
+
sortedLogs.map((log) => log.providerName ?? null).filter((provider) => provider !== null),
|
|
471
|
+
),
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
sessionId,
|
|
476
|
+
logCoverage: "bounded",
|
|
477
|
+
total,
|
|
478
|
+
limit,
|
|
479
|
+
eventCount: events.length,
|
|
480
|
+
requestCount: events.length,
|
|
481
|
+
errorCount,
|
|
482
|
+
startedAt: first?.timestamp ?? null,
|
|
483
|
+
updatedAt: last?.timestamp ?? null,
|
|
484
|
+
models,
|
|
485
|
+
providers,
|
|
486
|
+
events,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export async function getSessionTimelineImpl(
|
|
491
|
+
callApi: CallApiFn,
|
|
492
|
+
args: SessionTimelineArgs,
|
|
493
|
+
): Promise<ToolResult> {
|
|
494
|
+
const limit = clampTimelineLimit(args.limit);
|
|
495
|
+
const offset = args.offset ?? 0;
|
|
496
|
+
const params = new URLSearchParams({
|
|
497
|
+
offset: String(offset),
|
|
498
|
+
limit: String(limit),
|
|
499
|
+
compact: "1",
|
|
500
|
+
sessionId: args.sessionId,
|
|
501
|
+
});
|
|
502
|
+
const path = `/api/logs?${params.toString()}`;
|
|
503
|
+
const res = await callApi(path);
|
|
504
|
+
if (!res.ok) return toolError(`GET ${path} returned ${res.status}`);
|
|
505
|
+
const rawBody: unknown = await res.json();
|
|
506
|
+
const parsed = LogsListResponseSchema.safeParse(rawBody);
|
|
507
|
+
if (!parsed.success) return toolError("GET /api/logs returned an unparseable timeline shape");
|
|
508
|
+
const logs = parsed.data.logs ?? [];
|
|
509
|
+
return textJson(
|
|
510
|
+
buildSessionTimeline(args.sessionId, logs, parsed.data.total ?? logs.length, limit),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
type EvidenceFileKind = "json" | "markdown" | "html" | "zip";
|
|
515
|
+
|
|
516
|
+
type EvidenceFileSummary = {
|
|
517
|
+
kind: EvidenceFileKind;
|
|
518
|
+
path: string;
|
|
519
|
+
exists: boolean;
|
|
520
|
+
bytes: number | null;
|
|
521
|
+
included?: boolean;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
type EvidencePaths = {
|
|
525
|
+
jsonPath: string;
|
|
526
|
+
markdownPath: string;
|
|
527
|
+
htmlPath: string;
|
|
528
|
+
exportedAt: string;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
function fileBytes(path: string): number | null {
|
|
532
|
+
if (!existsSync(path)) return null;
|
|
533
|
+
try {
|
|
534
|
+
return statSync(path).size;
|
|
535
|
+
} catch {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function evidenceFileSummary(kind: EvidenceFileKind, path: string): EvidenceFileSummary {
|
|
541
|
+
return {
|
|
542
|
+
kind,
|
|
543
|
+
path,
|
|
544
|
+
exists: existsSync(path),
|
|
545
|
+
bytes: fileBytes(path),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function evidenceFiles(evidence: EvidencePaths): EvidenceFileSummary[] {
|
|
550
|
+
return [
|
|
551
|
+
evidenceFileSummary("json", evidence.jsonPath),
|
|
552
|
+
evidenceFileSummary("markdown", evidence.markdownPath),
|
|
553
|
+
evidenceFileSummary("html", evidence.htmlPath),
|
|
554
|
+
];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function readEvidenceFile(path: string): Buffer | null {
|
|
558
|
+
if (!existsSync(path)) return null;
|
|
559
|
+
try {
|
|
560
|
+
return readFileSync(path);
|
|
561
|
+
} catch {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function exportEvidenceZip(
|
|
567
|
+
ownerKind: "run" | "group",
|
|
568
|
+
ownerId: string,
|
|
569
|
+
evidence: EvidencePaths,
|
|
570
|
+
): Promise<Record<string, unknown>> {
|
|
571
|
+
const zip = new JSZip();
|
|
572
|
+
const sourceFiles = evidenceFiles(evidence);
|
|
573
|
+
const includedFiles: EvidenceFileSummary[] = [];
|
|
574
|
+
|
|
575
|
+
for (const file of sourceFiles) {
|
|
576
|
+
const content = readEvidenceFile(file.path);
|
|
577
|
+
const included = content !== null;
|
|
578
|
+
includedFiles.push({ ...file, included });
|
|
579
|
+
if (included) {
|
|
580
|
+
zip.file(`${file.kind}.${file.kind === "markdown" ? "md" : file.kind}`, content);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
zip.file(
|
|
585
|
+
"manifest.json",
|
|
586
|
+
`${JSON.stringify(
|
|
587
|
+
{
|
|
588
|
+
ownerKind,
|
|
589
|
+
ownerId,
|
|
590
|
+
exportedAt: evidence.exportedAt,
|
|
591
|
+
bundledAt: new Date().toISOString(),
|
|
592
|
+
files: includedFiles,
|
|
593
|
+
},
|
|
594
|
+
null,
|
|
595
|
+
2,
|
|
596
|
+
)}\n`,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const zipPath = join(
|
|
600
|
+
dirname(evidence.jsonPath),
|
|
601
|
+
ownerKind === "run" ? "evidence.zip" : "group-evidence.zip",
|
|
602
|
+
);
|
|
603
|
+
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
|
604
|
+
writeFileSync(zipPath, buffer);
|
|
605
|
+
const zipSummary = evidenceFileSummary("zip", zipPath);
|
|
606
|
+
return {
|
|
607
|
+
ownerKind,
|
|
608
|
+
ownerId,
|
|
609
|
+
bundlePath: zipPath,
|
|
610
|
+
bytes: zipSummary.bytes,
|
|
611
|
+
files: [...includedFiles, zipSummary],
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function getEvidenceFilesImpl(args: { runId: string }): ToolResult {
|
|
616
|
+
const evidence = readInspectorRunEvidence(args.runId);
|
|
617
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
618
|
+
return textJson({
|
|
619
|
+
runId: args.runId,
|
|
620
|
+
exportedAt: evidence.value.evidence.exportedAt,
|
|
621
|
+
files: evidenceFiles(evidence.value.evidence),
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export async function exportEvidenceBundleImpl(args: { runId: string }): Promise<ToolResult> {
|
|
626
|
+
const evidence = readInspectorRunEvidence(args.runId);
|
|
627
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
628
|
+
return textJson(await exportEvidenceZip("run", args.runId, evidence.value.evidence));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export function getGroupEvidenceFilesImpl(args: { groupId: string }): ToolResult {
|
|
632
|
+
const evidence = readInspectorGroupEvidence(args.groupId);
|
|
633
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
634
|
+
return textJson({
|
|
635
|
+
groupId: args.groupId,
|
|
636
|
+
exportedAt: evidence.value.evidence.exportedAt,
|
|
637
|
+
files: evidenceFiles(evidence.value.evidence),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export async function exportGroupEvidenceBundleImpl(args: {
|
|
642
|
+
groupId: string;
|
|
643
|
+
}): Promise<ToolResult> {
|
|
644
|
+
const evidence = readInspectorGroupEvidence(args.groupId);
|
|
645
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
646
|
+
return textJson(await exportEvidenceZip("group", args.groupId, evidence.value.evidence));
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export function getCurrentContextImpl(): ToolResult {
|
|
650
|
+
return textJson(getCurrentContext());
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function setCurrentContextImpl(args: InspectorCurrentContextPatch): ToolResult {
|
|
654
|
+
return textJson(setCurrentContext(args));
|
|
655
|
+
}
|
|
656
|
+
|
|
426
657
|
export async function listModelsImpl(callApi: CallApiFn): Promise<ToolResult> {
|
|
427
658
|
const res = await callApi("/api/models");
|
|
428
659
|
if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
|
|
@@ -540,6 +771,147 @@ export async function testProviderImpl(callApi: CallApiFn, id: string): Promise<
|
|
|
540
771
|
return textJson(await res.json());
|
|
541
772
|
}
|
|
542
773
|
|
|
774
|
+
type ProviderDiagnosticIssue = {
|
|
775
|
+
path: string;
|
|
776
|
+
status: number | null;
|
|
777
|
+
error: string | null;
|
|
778
|
+
durationMs: number | null;
|
|
779
|
+
endpoint: string | null;
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
783
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function stringField(record: Record<string, unknown>, key: string): string | null {
|
|
787
|
+
const value = record[key];
|
|
788
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function numberField(record: Record<string, unknown>, key: string): number | null {
|
|
792
|
+
const value = record[key];
|
|
793
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function firstStringField(record: Record<string, unknown>, keys: string[]): string | null {
|
|
797
|
+
for (const key of keys) {
|
|
798
|
+
const value = stringField(record, key);
|
|
799
|
+
if (value !== null) return value;
|
|
800
|
+
}
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function firstNumberField(record: Record<string, unknown>, keys: string[]): number | null {
|
|
805
|
+
for (const key of keys) {
|
|
806
|
+
const value = numberField(record, key);
|
|
807
|
+
if (value !== null) return value;
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function collectProviderDiagnosticIssues(
|
|
813
|
+
value: unknown,
|
|
814
|
+
path: string,
|
|
815
|
+
issues: ProviderDiagnosticIssue[],
|
|
816
|
+
): void {
|
|
817
|
+
if (Array.isArray(value)) {
|
|
818
|
+
value.forEach((item, index) =>
|
|
819
|
+
collectProviderDiagnosticIssues(item, `${path}[${String(index)}]`, issues),
|
|
820
|
+
);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (!isRecord(value)) return;
|
|
824
|
+
|
|
825
|
+
const success = value["success"];
|
|
826
|
+
const status = firstNumberField(value, ["status", "responseStatus", "statusCode"]);
|
|
827
|
+
const error = firstStringField(value, ["error", "message", "errorMessage"]);
|
|
828
|
+
if (success === false || error !== null || (status !== null && status >= 400)) {
|
|
829
|
+
issues.push({
|
|
830
|
+
path,
|
|
831
|
+
status,
|
|
832
|
+
error,
|
|
833
|
+
durationMs: firstNumberField(value, ["durationMs", "elapsedMs", "latencyMs"]),
|
|
834
|
+
endpoint: firstStringField(value, ["endpoint", "url", "requestUrl", "baseUrl"]),
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
for (const [key, child] of Object.entries(value)) {
|
|
839
|
+
if (typeof child === "object" && child !== null) {
|
|
840
|
+
collectProviderDiagnosticIssues(child, `${path}.${key}`, issues);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function stringifyUnknown(value: unknown): string {
|
|
846
|
+
try {
|
|
847
|
+
const text = JSON.stringify(value);
|
|
848
|
+
return text === undefined ? "" : text.toLowerCase();
|
|
849
|
+
} catch {
|
|
850
|
+
return "";
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function providerDiagnosticHints(result: unknown): string[] {
|
|
855
|
+
const text = stringifyUnknown(result);
|
|
856
|
+
const hints: string[] = [];
|
|
857
|
+
if (text.includes("timeout") || text.includes("aborted")) {
|
|
858
|
+
hints.push(
|
|
859
|
+
"Provider test timed out or was aborted. Increase Settings > Proxy > Provider test timeout when the provider or model is slow.",
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (text.includes("localhost") || text.includes("127.0.0.1")) {
|
|
863
|
+
hints.push(
|
|
864
|
+
"The upstream URL contains localhost. If the agent or provider runs in another container, replace localhost with a reachable host/IP.",
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
if (text.includes("econnrefused") || text.includes("enotfound") || text.includes("network")) {
|
|
868
|
+
hints.push(
|
|
869
|
+
"This looks like a network or DNS failure. Verify the Inspector container/host can reach the provider base URL.",
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
if (
|
|
873
|
+
text.includes("401") ||
|
|
874
|
+
text.includes("403") ||
|
|
875
|
+
text.includes("unauthorized") ||
|
|
876
|
+
text.includes("forbidden")
|
|
877
|
+
) {
|
|
878
|
+
hints.push(
|
|
879
|
+
"This looks like an authentication failure. Check the provider API key and auth header mode.",
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
if (text.includes("404") || text.includes("not found")) {
|
|
883
|
+
hints.push(
|
|
884
|
+
"This looks like a base URL or path mismatch. Check whether the provider endpoint already includes /v1 or needs a vendor-specific path.",
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (text.includes("nonstream") || text.includes("non-stream")) {
|
|
888
|
+
hints.push(
|
|
889
|
+
"The failure mentions non-streaming. Compare the OpenAI/Anthropic non-streaming path and payload with the streaming request.",
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
return [...new Set(hints)];
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export async function diagnoseProviderTestImpl(
|
|
896
|
+
callApi: CallApiFn,
|
|
897
|
+
id: string,
|
|
898
|
+
): Promise<ToolResult> {
|
|
899
|
+
const res = await callApi(`/api/providers/${encodeURIComponent(id)}/test`, {
|
|
900
|
+
method: "POST",
|
|
901
|
+
});
|
|
902
|
+
if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
|
|
903
|
+
const result: unknown = await res.json();
|
|
904
|
+
const issues: ProviderDiagnosticIssue[] = [];
|
|
905
|
+
collectProviderDiagnosticIssues(result, "result", issues);
|
|
906
|
+
return textJson({
|
|
907
|
+
providerId: id,
|
|
908
|
+
issueCount: issues.length,
|
|
909
|
+
issues,
|
|
910
|
+
hints: providerDiagnosticHints(result),
|
|
911
|
+
result,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
543
915
|
export type CreateSessionKnowledgeArgs = {
|
|
544
916
|
sessionId: string;
|
|
545
917
|
};
|
|
@@ -48,6 +48,7 @@ function buildMarkdown(document: EvidenceDocument): string {
|
|
|
48
48
|
"",
|
|
49
49
|
`- Run ID: ${document.run.id}`,
|
|
50
50
|
`- Session ID: ${document.run.sessionId}`,
|
|
51
|
+
`- Group ID: ${document.run.groupId ?? "n/a"}`,
|
|
51
52
|
`- Status: ${document.run.status}`,
|
|
52
53
|
`- Project: ${document.run.project ?? "n/a"}`,
|
|
53
54
|
`- Agent: ${document.run.agent ?? "n/a"}`,
|
package/src/proxy/runStore.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type UpdateInspectorRunInput,
|
|
13
13
|
} from "../lib/runContract";
|
|
14
14
|
import { getDataDir } from "./dataDir";
|
|
15
|
+
import { addGroupSession, getGroup } from "./groupStore";
|
|
15
16
|
|
|
16
17
|
const RunsFileSchema = z.object({
|
|
17
18
|
runs: z.array(InspectorRunSchema),
|
|
@@ -48,6 +49,51 @@ function normalizeSessionId(input: string | undefined, runId: string): string {
|
|
|
48
49
|
return input.trim();
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function normalizeGroupId(input: string | null | undefined): string | null {
|
|
53
|
+
if (input === undefined || input === null) return null;
|
|
54
|
+
const normalized = stableId(input);
|
|
55
|
+
return normalized.length > 0 ? normalized : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function metadataText(metadata: InspectorRun["metadata"], key: string): string | undefined {
|
|
59
|
+
const value = metadata[key];
|
|
60
|
+
if (typeof value !== "string") return undefined;
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function optionalText(value: string | null): string | undefined {
|
|
66
|
+
return value ?? undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function autoGroupMemberId(runId: string): string {
|
|
70
|
+
return stableId(`run-${runId}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function runHasGroupMember(run: InspectorRun): boolean {
|
|
74
|
+
if (run.groupId === null) return false;
|
|
75
|
+
const group = getGroup(run.groupId);
|
|
76
|
+
if (group === null) return false;
|
|
77
|
+
return group.members.some((member) => member.id === autoGroupMemberId(run.id));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function syncRunGroupMember(run: InspectorRun, createIfMissing: boolean): void {
|
|
81
|
+
if (run.groupId === null) return;
|
|
82
|
+
if (!createIfMissing && !runHasGroupMember(run)) return;
|
|
83
|
+
|
|
84
|
+
addGroupSession(run.groupId, {
|
|
85
|
+
memberId: autoGroupMemberId(run.id),
|
|
86
|
+
sessionId: run.sessionId,
|
|
87
|
+
runId: run.id,
|
|
88
|
+
label: run.title,
|
|
89
|
+
agent: optionalText(run.agent),
|
|
90
|
+
provider: metadataText(run.metadata, "provider") ?? metadataText(run.metadata, "providerName"),
|
|
91
|
+
model: metadataText(run.metadata, "model") ?? metadataText(run.metadata, "modelName"),
|
|
92
|
+
status: run.status,
|
|
93
|
+
metadata: run.metadata,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
51
97
|
function readRuns(): InspectorRun[] {
|
|
52
98
|
const filePath = runsFilePath();
|
|
53
99
|
if (!existsSync(filePath)) return [];
|
|
@@ -83,9 +129,11 @@ export function createRun(input: CreateInspectorRunInput): InspectorRun {
|
|
|
83
129
|
|
|
84
130
|
const timestamp = nowIso();
|
|
85
131
|
const sessionId = normalizeSessionId(parsed?.sessionId, runId);
|
|
132
|
+
const groupId = normalizeGroupId(parsed?.groupId);
|
|
86
133
|
const run: InspectorRun = {
|
|
87
134
|
id: runId,
|
|
88
135
|
sessionId,
|
|
136
|
+
groupId,
|
|
89
137
|
title: parsed?.title ?? `Inspector Run ${runId}`,
|
|
90
138
|
task: parsed?.task ?? null,
|
|
91
139
|
project: parsed?.project ?? null,
|
|
@@ -99,6 +147,7 @@ export function createRun(input: CreateInspectorRunInput): InspectorRun {
|
|
|
99
147
|
};
|
|
100
148
|
|
|
101
149
|
writeRuns([...readRuns(), run]);
|
|
150
|
+
syncRunGroupMember(run, parsed?.sessionId !== undefined);
|
|
102
151
|
return run;
|
|
103
152
|
}
|
|
104
153
|
|
|
@@ -125,32 +174,35 @@ export function updateRunEvidence(
|
|
|
125
174
|
export function updateRun(runId: string, input: UpdateInspectorRunInput): InspectorRun | null {
|
|
126
175
|
const parsed = UpdateInspectorRunInputSchema.parse(input);
|
|
127
176
|
const runs = readRuns();
|
|
128
|
-
|
|
177
|
+
const run = runs.find((candidate) => candidate.id === runId) ?? null;
|
|
129
178
|
const updatedAt = nowIso();
|
|
130
179
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
metadata:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return updatedRun;
|
|
150
|
-
});
|
|
180
|
+
if (run === null) return null;
|
|
181
|
+
|
|
182
|
+
const updatedRun: InspectorRun = {
|
|
183
|
+
...run,
|
|
184
|
+
sessionId:
|
|
185
|
+
parsed.sessionId === undefined ? run.sessionId : normalizeSessionId(parsed.sessionId, run.id),
|
|
186
|
+
groupId: parsed.groupId === undefined ? run.groupId : normalizeGroupId(parsed.groupId),
|
|
187
|
+
title: parsed.title ?? run.title,
|
|
188
|
+
task: parsed.task === undefined ? run.task : parsed.task,
|
|
189
|
+
project: parsed.project === undefined ? run.project : parsed.project,
|
|
190
|
+
agent: parsed.agent === undefined ? run.agent : parsed.agent,
|
|
191
|
+
status: parsed.status ?? run.status,
|
|
192
|
+
tags: parsed.tags ?? run.tags,
|
|
193
|
+
metadata:
|
|
194
|
+
parsed.metadata === undefined ? run.metadata : { ...run.metadata, ...parsed.metadata },
|
|
195
|
+
updatedAt,
|
|
196
|
+
};
|
|
197
|
+
const updatedRuns = runs.map((candidate) => (candidate.id === runId ? updatedRun : candidate));
|
|
151
198
|
|
|
152
|
-
if (updatedRun === null) return null;
|
|
153
199
|
writeRuns(updatedRuns);
|
|
200
|
+
syncRunGroupMember(
|
|
201
|
+
updatedRun,
|
|
202
|
+
parsed.sessionId !== undefined ||
|
|
203
|
+
parsed.groupId !== undefined ||
|
|
204
|
+
updatedRun.sessionId !== updatedRun.id,
|
|
205
|
+
);
|
|
154
206
|
return updatedRun;
|
|
155
207
|
}
|
|
156
208
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as s,j as e}from"./main-CI3HFEcV.js";import{P as i}from"./ProxyViewerContainer-DsU6QETm.js";function t(){const{sessionId:o}=s.useParams();return e.jsx(i,{initialSessionId:o},o)}export{t as component};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{P as o}from"./ProxyViewerContainer-DsU6QETm.js";import"./main-CI3HFEcV.js";const r=o;export{r as component};
|