@obov/mext 0.5.0 → 0.6.0

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 (157) hide show
  1. package/dist/approval.d.ts +33 -0
  2. package/dist/approval.js +36 -0
  3. package/dist/approval.js.map +1 -0
  4. package/dist/audit.d.ts +123 -0
  5. package/dist/audit.js +221 -0
  6. package/dist/audit.js.map +1 -0
  7. package/dist/build-cli.d.ts +1 -0
  8. package/dist/build-cli.js.map +1 -1
  9. package/dist/build.d.ts +2 -0
  10. package/dist/build.js +30 -3
  11. package/dist/build.js.map +1 -1
  12. package/dist/caller.d.ts +53 -0
  13. package/dist/caller.js +51 -0
  14. package/dist/caller.js.map +1 -0
  15. package/dist/cli/build.js +4 -2
  16. package/dist/cli/build.js.map +1 -1
  17. package/dist/cli/dev-audit.d.ts +25 -0
  18. package/dist/cli/dev-audit.js +84 -0
  19. package/dist/cli/dev-audit.js.map +1 -0
  20. package/dist/cli/dev-lock.d.ts +3 -1
  21. package/dist/cli/dev-lock.js +12 -5
  22. package/dist/cli/dev-lock.js.map +1 -1
  23. package/dist/cli/dev-server.d.ts +9 -0
  24. package/dist/cli/dev-server.js +24 -1
  25. package/dist/cli/dev-server.js.map +1 -1
  26. package/dist/cli/dev-tap.d.ts +20 -0
  27. package/dist/cli/dev-tap.js +70 -0
  28. package/dist/cli/dev-tap.js.map +1 -0
  29. package/dist/cli/dev.js +16 -4
  30. package/dist/cli/dev.js.map +1 -1
  31. package/dist/cli/help.js +2 -0
  32. package/dist/cli/help.js.map +1 -1
  33. package/dist/cli/inspect.d.ts +7 -0
  34. package/dist/cli/inspect.js +699 -0
  35. package/dist/cli/inspect.js.map +1 -0
  36. package/dist/cli/main.js +8 -0
  37. package/dist/cli/main.js.map +1 -1
  38. package/dist/cli/output.d.ts +1 -1
  39. package/dist/cli/output.js +3 -2
  40. package/dist/cli/output.js.map +1 -1
  41. package/dist/cli/run.js +28 -4
  42. package/dist/cli/run.js.map +1 -1
  43. package/dist/cli/types.d.ts +9 -0
  44. package/dist/cli/types.js +77 -0
  45. package/dist/cli/types.js.map +1 -0
  46. package/dist/client/index.d.ts +10 -0
  47. package/dist/client/index.js +7 -0
  48. package/dist/client/index.js.map +1 -0
  49. package/dist/client/manifest.js +6 -2
  50. package/dist/client/manifest.js.map +1 -1
  51. package/dist/client/runner.js +37 -3
  52. package/dist/client/runner.js.map +1 -1
  53. package/dist/client/typed.d.ts +54 -0
  54. package/dist/client/typed.js +490 -0
  55. package/dist/client/typed.js.map +1 -0
  56. package/dist/client/types.d.ts +196 -0
  57. package/dist/client/types.js +2 -0
  58. package/dist/client/types.js.map +1 -0
  59. package/dist/command.d.ts +61 -5
  60. package/dist/command.js +42 -1
  61. package/dist/command.js.map +1 -1
  62. package/dist/config.d.ts +42 -10
  63. package/dist/config.js +32 -7
  64. package/dist/config.js.map +1 -1
  65. package/dist/errors.d.ts +21 -3
  66. package/dist/errors.js +13 -0
  67. package/dist/errors.js.map +1 -1
  68. package/dist/expose.d.ts +9 -0
  69. package/dist/expose.js +52 -0
  70. package/dist/expose.js.map +1 -0
  71. package/dist/index.d.ts +15 -6
  72. package/dist/index.js +7 -2
  73. package/dist/index.js.map +1 -1
  74. package/dist/inspect/agent.d.ts +104 -0
  75. package/dist/inspect/agent.js +125 -0
  76. package/dist/inspect/agent.js.map +1 -0
  77. package/dist/inspect/audit.d.ts +46 -0
  78. package/dist/inspect/audit.js +133 -0
  79. package/dist/inspect/audit.js.map +1 -0
  80. package/dist/inspect/compare.d.ts +12 -0
  81. package/dist/inspect/compare.js +31 -0
  82. package/dist/inspect/compare.js.map +1 -0
  83. package/dist/inspect/index.d.ts +17 -0
  84. package/dist/inspect/index.js +12 -0
  85. package/dist/inspect/index.js.map +1 -0
  86. package/dist/inspect/inspector.d.ts +168 -0
  87. package/dist/inspect/inspector.js +392 -0
  88. package/dist/inspect/inspector.js.map +1 -0
  89. package/dist/inspect/profiles.d.ts +95 -0
  90. package/dist/inspect/profiles.js +235 -0
  91. package/dist/inspect/profiles.js.map +1 -0
  92. package/dist/inspect/scenario.d.ts +126 -0
  93. package/dist/inspect/scenario.js +555 -0
  94. package/dist/inspect/scenario.js.map +1 -0
  95. package/dist/inspect/ui-server.d.ts +31 -0
  96. package/dist/inspect/ui-server.js +378 -0
  97. package/dist/inspect/ui-server.js.map +1 -0
  98. package/dist/inspect/ui.html +917 -0
  99. package/dist/instrumentation.d.ts +13 -2
  100. package/dist/instrumentation.js +11 -1
  101. package/dist/instrumentation.js.map +1 -1
  102. package/dist/invoke.d.ts +32 -5
  103. package/dist/invoke.js +150 -16
  104. package/dist/invoke.js.map +1 -1
  105. package/dist/manifest.d.ts +13 -2
  106. package/dist/manifest.js +5 -1
  107. package/dist/manifest.js.map +1 -1
  108. package/dist/mcp/constants.d.ts +5 -0
  109. package/dist/mcp/constants.js +5 -0
  110. package/dist/mcp/constants.js.map +1 -1
  111. package/dist/mcp/dispatch.d.ts +1 -1
  112. package/dist/mcp/dispatch.js +14 -3
  113. package/dist/mcp/dispatch.js.map +1 -1
  114. package/dist/mcp/help.d.ts +2 -0
  115. package/dist/mcp/help.js +6 -1
  116. package/dist/mcp/help.js.map +1 -1
  117. package/dist/mcp/http.d.ts +16 -2
  118. package/dist/mcp/http.js +76 -7
  119. package/dist/mcp/http.js.map +1 -1
  120. package/dist/mcp/index.d.ts +1 -1
  121. package/dist/mcp/index.js +1 -1
  122. package/dist/mcp/index.js.map +1 -1
  123. package/dist/mcp/server.d.ts +12 -1
  124. package/dist/mcp/server.js +63 -17
  125. package/dist/mcp/server.js.map +1 -1
  126. package/dist/mcp/stdio.d.ts +4 -0
  127. package/dist/mcp/stdio.js +3 -0
  128. package/dist/mcp/stdio.js.map +1 -1
  129. package/dist/mcp/tasks.d.ts +3 -1
  130. package/dist/mcp/tasks.js +6 -3
  131. package/dist/mcp/tasks.js.map +1 -1
  132. package/dist/principal.d.ts +22 -0
  133. package/dist/principal.js +29 -0
  134. package/dist/principal.js.map +1 -1
  135. package/dist/registry.d.ts +5 -0
  136. package/dist/registry.js +21 -0
  137. package/dist/registry.js.map +1 -1
  138. package/dist/remote/client.d.ts +2 -0
  139. package/dist/remote/client.js +3 -2
  140. package/dist/remote/client.js.map +1 -1
  141. package/dist/remote/oauth.d.ts +1 -1
  142. package/dist/remote/oauth.js +1 -1
  143. package/dist/remote/oauth.js.map +1 -1
  144. package/dist/start.js +6 -2
  145. package/dist/start.js.map +1 -1
  146. package/dist/suggest.js +1 -1
  147. package/dist/suggest.js.map +1 -1
  148. package/dist/typegen.d.ts +27 -0
  149. package/dist/typegen.js +62 -0
  150. package/dist/typegen.js.map +1 -0
  151. package/dist/version-constant.d.ts +5 -0
  152. package/dist/version-constant.js +6 -0
  153. package/dist/version-constant.js.map +1 -0
  154. package/dist/version.d.ts +1 -2
  155. package/dist/version.js +1 -2
  156. package/dist/version.js.map +1 -1
  157. package/package.json +12 -2
@@ -0,0 +1,168 @@
1
+ import type { AuditEvent } from "../audit.ts";
2
+ import { CallError } from "../client/typed.ts";
3
+ import type { CommandInfo, CommandList, InputAnswer, InputRequestInfo, ProgressUpdate, ServerInfo, TaskStatus } from "../client/types.ts";
4
+ import type { AuditSource } from "./audit.ts";
5
+ import { type Profile, type ProfileSet, type ProfileView } from "./profiles.ts";
6
+ /**
7
+ * Inspector (specs/inspector.md): 호출자 프로필별 타입 client · 호출 기록 (원시 요청 · 응답, 비밀 값 가림).
8
+ * CLI · 웹 UI · 시나리오 실행이 같은 객체를 쓴다. 호출 코드는 타입 client 하나 (별도 MCP 클라이언트 구현 없음)
9
+ */
10
+ export interface InspectTarget {
11
+ url: string;
12
+ /** flag = --url · env = MEXT_URL · dev = 이 프로젝트의 mext dev (.mext/dev/lock) */
13
+ source: "flag" | "env" | "dev";
14
+ }
15
+ /** 대상 서버를 정할 수 없다 · URL 형식 오류 · 없는 프로필 (CLI = exit 2) */
16
+ export declare class InspectUsageError extends Error {
17
+ constructor(message: string);
18
+ }
19
+ /** --url > MEXT_URL > 실행 중인 mext dev. 셋 다 없음 = InspectUsageError */
20
+ export declare function resolveTarget(options: {
21
+ url?: string;
22
+ env?: Record<string, string | undefined>;
23
+ cwd: string;
24
+ }): InspectTarget;
25
+ /** HTTP 왕복 하나 (원시 요청 · 응답). 비밀 값은 [redacted] */
26
+ export interface Exchange {
27
+ /** JSON-RPC method (tools/call · tools/list · server/discover · tasks/get …) */
28
+ rpc?: string;
29
+ request: {
30
+ method: string;
31
+ url: string;
32
+ headers: Record<string, string>;
33
+ body: unknown;
34
+ };
35
+ /** body = JSON 응답 · SSE 메시지 배열 (text/event-stream) · 그 외 텍스트 */
36
+ response?: {
37
+ status: number;
38
+ headers: Record<string, string>;
39
+ body: unknown;
40
+ };
41
+ /** 응답을 받지 못함 (연결 실패 · 취소) */
42
+ error?: string;
43
+ durationMs: number;
44
+ }
45
+ /** 오류 본문 { code, message, ...details } */
46
+ export type ErrorBody = Record<string, unknown> & {
47
+ code: string;
48
+ message: string;
49
+ };
50
+ export type CallOutcome = {
51
+ ok: true;
52
+ value: unknown;
53
+ } | {
54
+ ok: false;
55
+ error: ErrorBody;
56
+ };
57
+ export interface InputRecord {
58
+ kind: InputRequestInfo["kind"];
59
+ message: string;
60
+ /** 보낸 답 (true · false · { action, content }). 답 없음 = undefined */
61
+ answer?: InputAnswer;
62
+ }
63
+ /** 호출 하나의 기록 */
64
+ export interface CallRecord {
65
+ /** 이 Inspector 안에서 1부터 */
66
+ id: number;
67
+ /** 시작 시각 (ISO) */
68
+ at: string;
69
+ profile: string;
70
+ command: string;
71
+ input: Record<string, unknown>;
72
+ confirmed: boolean;
73
+ ok: boolean;
74
+ /** ok = 반환값 */
75
+ value?: unknown;
76
+ /** ok 아님 = 오류 본문 */
77
+ error?: ErrorBody;
78
+ durationMs: number;
79
+ progress: ProgressUpdate[];
80
+ inputs: InputRecord[];
81
+ /** task 로 실행됐으면 마지막 상태 */
82
+ task?: TaskStatus;
83
+ /**
84
+ * 이 호출의 tools/call 요청에 실은 trace ID (params._meta.traceparent, W3C trace context).
85
+ * 서버의 감사 이벤트 traceId 와 같다 = 호출과 이벤트를 잇는 값
86
+ */
87
+ traceId: string;
88
+ exchanges: Exchange[];
89
+ }
90
+ /** 입력 요청의 답 (종류별). 적지 않은 종류 = INPUT_UNAVAILABLE */
91
+ export interface Answers {
92
+ confirm?: boolean;
93
+ /** 값 객체 = 수락 · false = 거절 */
94
+ form?: Record<string, unknown> | false;
95
+ url?: boolean;
96
+ }
97
+ export interface InspectCallOptions {
98
+ /** 프로필 이름. 생략 = 프로필 파일의 default */
99
+ profile?: string;
100
+ /** 사람이 확인했다는 표시 (client.md §2.2): 확인 표시 전송 + confirm 입력 요청 승인 */
101
+ confirmed?: boolean;
102
+ answers?: Answers;
103
+ /** 입력 · 확인 요청을 모두 거절 */
104
+ decline?: boolean;
105
+ /** 입력 요청을 직접 처리 (웹 UI). answers · decline 보다 먼저 */
106
+ onInput?: (request: InputRequestInfo) => InputAnswer | Promise<InputAnswer>;
107
+ onProgress?: (update: ProgressUpdate) => void;
108
+ onTask?: (task: TaskStatus) => void;
109
+ signal?: AbortSignal;
110
+ }
111
+ export interface InspectorOptions {
112
+ /** 대상 서버의 MCP 엔드포인트 */
113
+ url: string;
114
+ profiles: ProfileSet;
115
+ /** 프로필 값을 읽을 환경변수 (기본 process.env) */
116
+ env?: Record<string, string | undefined>;
117
+ fetch?: typeof fetch;
118
+ /** 보관할 호출 기록 수 (기본 200. 넘으면 오래된 것부터 버린다) */
119
+ historyLimit?: number;
120
+ /** 호출 기록이 추가될 때 */
121
+ onRecord?: (record: CallRecord) => void;
122
+ /** 감사 출처 (§6). 없음 = 감사 이벤트 없음 */
123
+ audit?: AuditSource;
124
+ /** 보관할 감사 이벤트 수 (기본 500) */
125
+ auditLimit?: number;
126
+ }
127
+ /** Inspector 가 알리는 일: 호출 기록 추가 · 감사 이벤트 수신 */
128
+ export type InspectorEvent = {
129
+ type: "record";
130
+ record: CallRecord;
131
+ } | {
132
+ type: "audit";
133
+ event: AuditEvent;
134
+ };
135
+ export interface Inspector {
136
+ readonly url: string;
137
+ readonly profileSet: ProfileSet;
138
+ /** 표시용 프로필 목록 (값 없음) */
139
+ profiles(): ProfileView[];
140
+ /** 이름 확인 (생략 = default). 없는 이름 = InspectUsageError */
141
+ profile(name?: string): Profile;
142
+ /** 그 프로필에게 보이는 목록 (매번 서버에서 다시 읽는다). 실패 = CallError · ProfileUnavailableError */
143
+ commands(profile?: string): Promise<CommandList>;
144
+ describe(id: string, profile?: string): Promise<CommandInfo>;
145
+ server(profile?: string): Promise<ServerInfo>;
146
+ /** 호출. 호출 오류 · 프로필 값 없음 · 취소도 던지지 않고 기록 (ok: false) 으로 돌려준다 */
147
+ call(id: string, input?: Record<string, unknown>, options?: InspectCallOptions): Promise<CallRecord>;
148
+ /** 오래된 것부터 */
149
+ history(): CallRecord[];
150
+ clearHistory(): void;
151
+ /** 감사 출처가 연결돼 있는지 */
152
+ readonly auditConnected: boolean;
153
+ /** 받은 감사 이벤트 (오래된 것부터, 프로필의 비밀 값은 [redacted]) */
154
+ auditEvents(): AuditEvent[];
155
+ /** 값 안의 문자열에서 지금 읽을 수 있는 모든 프로필의 비밀 값을 가린다 (기록 · 이벤트와 같은 규칙) */
156
+ redact<T>(value: T): T;
157
+ /** 호출 기록 · 감사 이벤트 구독. 반환 = 해제 */
158
+ subscribe(listener: (event: InspectorEvent) => void): () => void;
159
+ /** 감사 출처 연결 해제 */
160
+ close(): void;
161
+ }
162
+ export declare function createInspector(options: InspectorOptions): Inspector;
163
+ /** 결과 요약 (기록 → 성공 값 또는 오류 본문) */
164
+ export declare function outcomeOf(record: CallRecord): CallOutcome;
165
+ /** 오류 본문. code · message 를 앞에 (출력 · 기록에서 먼저 보이게) */
166
+ export declare function orderedBody(error: CallError): ErrorBody;
167
+ /** 값 안의 문자열에서 비밀 값을 가린다 (객체 · 배열 재귀) */
168
+ export declare function redactValue(value: unknown, secrets: readonly string[]): unknown;
@@ -0,0 +1,392 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { randomBytes } from "node:crypto";
3
+ import { readDevLock } from "../cli/dev-lock.js";
4
+ import { VERSION } from "../version-constant.js";
5
+ import { CallError, createClient } from "../client/typed.js";
6
+ import { describeProfiles, findProfile, ProfileUnavailableError, REDACTED, redact, resolveProfile, } from "./profiles.js";
7
+ /** 대상 서버를 정할 수 없다 · URL 형식 오류 · 없는 프로필 (CLI = exit 2) */
8
+ export class InspectUsageError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "InspectUsageError";
12
+ }
13
+ }
14
+ /** --url > MEXT_URL > 실행 중인 mext dev. 셋 다 없음 = InspectUsageError */
15
+ export function resolveTarget(options) {
16
+ const env = options.env ?? process.env;
17
+ const pick = (url, source) => {
18
+ if (!/^https?:\/\//i.test(url))
19
+ throw new InspectUsageError(`${source === "env" ? "MEXT_URL" : "--url"} must be an http(s) URL (got "${url}")`);
20
+ return { url, source };
21
+ };
22
+ if (options.url !== undefined)
23
+ return pick(options.url, "flag");
24
+ if (env.MEXT_URL !== undefined && env.MEXT_URL !== "")
25
+ return pick(env.MEXT_URL, "env");
26
+ const lock = readDevLock(options.cwd);
27
+ if (lock?.url)
28
+ return { url: lock.url, source: "dev" };
29
+ throw new InspectUsageError('No server to inspect. Start "mext dev" in this project, or pass --url <url> (or set MEXT_URL).');
30
+ }
31
+ const DEFAULT_CLIENT_INFO = { name: "mext-inspect", version: VERSION };
32
+ /** 기록에 남기는 본문 길이 상한 (문자) */
33
+ const BODY_LIMIT = 256 * 1024;
34
+ /** 호출이 끝난 뒤 응답 본문 수집을 기다리는 시간 */
35
+ const DRAIN_MS = 500;
36
+ const SECRET_HEADERS = new Set(["authorization", "cookie", "proxy-authorization"]);
37
+ export function createInspector(options) {
38
+ const env = options.env ?? process.env;
39
+ const baseFetch = options.fetch ?? ((input, init) => fetch(input, init));
40
+ const limit = options.historyLimit ?? 200;
41
+ const recording = new AsyncLocalStorage();
42
+ const clients = new Map();
43
+ const records = [];
44
+ const listeners = new Set();
45
+ const notify = (event) => {
46
+ for (const listener of listeners) {
47
+ try {
48
+ listener(event);
49
+ }
50
+ catch {
51
+ // 구독자의 예외는 호출 · 수신에 영향을 주지 않는다
52
+ }
53
+ }
54
+ };
55
+ let seq = 0;
56
+ /* 감사 이벤트 (§6) */
57
+ const audits = [];
58
+ const auditLimit = options.auditLimit ?? 500;
59
+ /** 지금 읽을 수 있는 모든 프로필의 비밀 값 (이벤트의 입력 · 요약에 섞여 있으면 가린다) */
60
+ const allSecrets = () => options.profiles.profiles.flatMap((profile) => {
61
+ try {
62
+ return resolveProfile(profile, env).secrets;
63
+ }
64
+ catch {
65
+ return [];
66
+ }
67
+ });
68
+ const unsubscribeAudit = options.audit?.subscribe((raw) => {
69
+ const event = redactValue(raw, allSecrets());
70
+ audits.push(event);
71
+ if (audits.length > auditLimit)
72
+ audits.splice(0, audits.length - auditLimit);
73
+ notify({ type: "audit", event });
74
+ });
75
+ const recordingFetch = (async (input, init) => {
76
+ const store = recording.getStore();
77
+ if (!store)
78
+ return baseFetch(input, init);
79
+ init = withTrace(init, store.traceparent);
80
+ const started = Date.now();
81
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
82
+ const body = typeof init?.body === "string" ? parseBody(redact(init.body, store.secrets), "application/json") : undefined;
83
+ const exchange = {
84
+ request: { method: init?.method ?? "GET", url, headers: headersOf(new Headers(init?.headers), store.secrets), body },
85
+ durationMs: 0,
86
+ };
87
+ if (isRecord(body) && typeof body.method === "string")
88
+ exchange.rpc = body.method;
89
+ store.exchanges.push(exchange);
90
+ let response;
91
+ try {
92
+ response = await baseFetch(input, init);
93
+ }
94
+ catch (error) {
95
+ exchange.error = redact(error instanceof Error ? error.message : String(error), store.secrets);
96
+ exchange.durationMs = Date.now() - started;
97
+ throw error;
98
+ }
99
+ const type = response.headers.get("content-type") ?? "";
100
+ const captured = { status: response.status, headers: headersOf(response.headers, store.secrets), body: undefined };
101
+ exchange.response = captured;
102
+ exchange.durationMs = Date.now() - started;
103
+ if (response.body) {
104
+ const copy = response.clone();
105
+ let text = "";
106
+ const finish = () => {
107
+ captured.body = parseBody(redact(text, store.secrets), type);
108
+ exchange.durationMs = Date.now() - started;
109
+ };
110
+ const reader = copy.body.getReader();
111
+ const decoder = new TextDecoder();
112
+ const done = (async () => {
113
+ try {
114
+ for (;;) {
115
+ const chunk = await reader.read();
116
+ if (chunk.done)
117
+ break;
118
+ if (text.length < BODY_LIMIT)
119
+ text += decoder.decode(chunk.value, { stream: true });
120
+ }
121
+ }
122
+ catch {
123
+ // 취소 · 연결 종료 = 받은 데까지
124
+ }
125
+ finish();
126
+ })();
127
+ store.pending.push({ done, stop: () => void reader.cancel().catch(() => { }) });
128
+ }
129
+ return response;
130
+ });
131
+ const profileOf = (name) => {
132
+ const profile = findProfile(options.profiles, name);
133
+ if (!profile) {
134
+ const names = options.profiles.profiles.map((p) => p.name).join(", ");
135
+ throw new InspectUsageError(`Unknown profile "${name}". Profiles: ${names}`);
136
+ }
137
+ return profile;
138
+ };
139
+ /** 프로필별 client. 토큰 · 헤더는 요청마다 환경변수에서 읽는다 (저장하지 않는다) */
140
+ const clientOf = (profile) => {
141
+ let client = clients.get(profile.name);
142
+ if (!client) {
143
+ client = createClient({
144
+ url: options.url,
145
+ token: () => resolveProfile(profile, env).token,
146
+ headers: () => resolveProfile(profile, env).headers,
147
+ clientInfo: profile.clientInfo ?? DEFAULT_CLIENT_INFO,
148
+ fetch: recordingFetch,
149
+ });
150
+ clients.set(profile.name, client);
151
+ }
152
+ return client;
153
+ };
154
+ const push = (record) => {
155
+ records.push(record);
156
+ if (records.length > limit)
157
+ records.splice(0, records.length - limit);
158
+ options.onRecord?.(record);
159
+ notify({ type: "record", record });
160
+ return record;
161
+ };
162
+ return {
163
+ url: options.url,
164
+ profileSet: options.profiles,
165
+ profiles: () => describeProfiles(options.profiles, env),
166
+ profile: profileOf,
167
+ async commands(name) {
168
+ const profile = profileOf(name);
169
+ resolveProfile(profile, env);
170
+ const client = clientOf(profile);
171
+ client.refresh();
172
+ return client.commands();
173
+ },
174
+ async describe(id, name) {
175
+ const profile = profileOf(name);
176
+ resolveProfile(profile, env);
177
+ return clientOf(profile).describe(id);
178
+ },
179
+ async server(name) {
180
+ const profile = profileOf(name);
181
+ resolveProfile(profile, env);
182
+ return clientOf(profile).server();
183
+ },
184
+ async call(id, input = {}, o = {}) {
185
+ const profile = profileOf(o.profile);
186
+ const started = Date.now();
187
+ const traceId = randomBytes(16).toString("hex");
188
+ const record = {
189
+ id: ++seq,
190
+ at: new Date(started).toISOString(),
191
+ profile: profile.name,
192
+ command: id,
193
+ input,
194
+ confirmed: o.confirmed === true,
195
+ ok: false,
196
+ durationMs: 0,
197
+ progress: [],
198
+ inputs: [],
199
+ traceId,
200
+ exchanges: [],
201
+ };
202
+ const fail = (error) => {
203
+ record.error = error;
204
+ record.durationMs = Date.now() - started;
205
+ return push(record);
206
+ };
207
+ // 가릴 값 = 이 프로필뿐 아니라 읽을 수 있는 모든 프로필의 비밀 값 (다른 프로필의 토큰이 입력 · 결과에 섞여도 가린다)
208
+ let secrets;
209
+ try {
210
+ secrets = [...new Set([...resolveProfile(profile, env).secrets, ...allSecrets()])];
211
+ }
212
+ catch (error) {
213
+ if (!(error instanceof ProfileUnavailableError))
214
+ throw error;
215
+ return fail({ code: error.code, message: error.message, profile: error.profile, missing: [...error.missing] });
216
+ }
217
+ // 입력에 비밀 값이 그대로 들어온 경우 (환경변수와 같은 값) 도 기록에서 가린다
218
+ record.input = redactValue(input, secrets);
219
+ const store = { exchanges: record.exchanges, traceparent: `00-${traceId}-${randomBytes(8).toString("hex")}-01`, secrets, pending: [] };
220
+ const callOptions = {
221
+ onProgress: (update) => {
222
+ record.progress.push(update);
223
+ o.onProgress?.(update);
224
+ },
225
+ onTask: (task) => {
226
+ record.task = task;
227
+ o.onTask?.(task);
228
+ },
229
+ ...(o.confirmed === true ? { confirmed: true } : {}),
230
+ ...(o.signal ? { signal: o.signal } : {}),
231
+ };
232
+ const answer = inputAnswerer(id, o);
233
+ if (answer) {
234
+ callOptions.onInput = async (request) => {
235
+ const entry = { kind: request.kind, message: request.message };
236
+ record.inputs.push(entry);
237
+ entry.answer = await answer(request);
238
+ return entry.answer;
239
+ };
240
+ }
241
+ try {
242
+ const value = await recording.run(store, () => clientOf(profile).call(id, input, callOptions));
243
+ record.ok = true;
244
+ record.value = redactValue(value, secrets);
245
+ }
246
+ catch (error) {
247
+ record.error = redactValue(errorBodyOf(error, o.signal), secrets);
248
+ }
249
+ await settle(store.pending);
250
+ record.durationMs = Date.now() - started;
251
+ return push(record);
252
+ },
253
+ history: () => [...records],
254
+ clearHistory: () => void (records.length = 0),
255
+ auditConnected: options.audit !== undefined,
256
+ auditEvents: () => [...audits],
257
+ redact: (value) => redactValue(value, allSecrets()),
258
+ subscribe(listener) {
259
+ listeners.add(listener);
260
+ return () => void listeners.delete(listener);
261
+ },
262
+ close: () => unsubscribeAudit?.(),
263
+ };
264
+ }
265
+ /** 결과 요약 (기록 → 성공 값 또는 오류 본문) */
266
+ export function outcomeOf(record) {
267
+ return record.ok ? { ok: true, value: record.value } : { ok: false, error: record.error ?? { code: "UNKNOWN", message: "No result." } };
268
+ }
269
+ /** answers · decline · onInput → 입력 요청 처리기. 셋 다 없음 = undefined (client 가 INPUT_UNAVAILABLE) */
270
+ function inputAnswerer(command, o) {
271
+ if (o.onInput)
272
+ return o.onInput;
273
+ if (o.decline === true)
274
+ return () => false;
275
+ const answers = o.answers;
276
+ if (!answers)
277
+ return undefined;
278
+ return (request) => {
279
+ const given = answers[request.kind];
280
+ if (given === undefined) {
281
+ throw new CallError({
282
+ code: "INPUT_UNAVAILABLE",
283
+ message: `${command} asked "${request.message}" (${request.kind}), but no answer was given for ${request.kind} requests.`,
284
+ command,
285
+ kind: request.kind,
286
+ }, command);
287
+ }
288
+ if (typeof given === "boolean")
289
+ return given;
290
+ return { action: "accept", content: given };
291
+ };
292
+ }
293
+ function errorBodyOf(error, signal) {
294
+ if (error instanceof CallError)
295
+ return orderedBody(error);
296
+ const message = error instanceof Error ? error.message : String(error);
297
+ if (signal?.aborted)
298
+ return { code: "CANCELLED", message: message || "Cancelled." };
299
+ return { code: "CLIENT_ERROR", message };
300
+ }
301
+ /** 오류 본문. code · message 를 앞에 (출력 · 기록에서 먼저 보이게) */
302
+ export function orderedBody(error) {
303
+ const { code: _code, message: _message, ...details } = error.body;
304
+ return { code: error.code, message: error.message, ...details };
305
+ }
306
+ /** 수집 중인 응답 본문을 잠깐 기다리고, 끝나지 않은 것은 받은 데까지로 끊는다 */
307
+ async function settle(pending) {
308
+ if (pending.length === 0)
309
+ return;
310
+ let timer;
311
+ const all = Promise.all(pending.map((p) => p.done));
312
+ const timedOut = await Promise.race([
313
+ all.then(() => false),
314
+ new Promise((resolve) => (timer = setTimeout(() => resolve(true), DRAIN_MS))),
315
+ ]);
316
+ clearTimeout(timer);
317
+ if (timedOut) {
318
+ for (const p of pending)
319
+ p.stop();
320
+ await all;
321
+ }
322
+ }
323
+ function headersOf(headers, secrets) {
324
+ const out = {};
325
+ headers.forEach((value, name) => {
326
+ if (SECRET_HEADERS.has(name.toLowerCase())) {
327
+ const scheme = /^(Bearer|Basic)\s+/i.exec(value)?.[0] ?? "";
328
+ out[name] = name.toLowerCase() === "authorization" ? `${scheme}${REDACTED}` : REDACTED;
329
+ }
330
+ else
331
+ out[name] = redact(value, secrets);
332
+ });
333
+ return out;
334
+ }
335
+ /** 본문 텍스트 → JSON · SSE 메시지 배열 · 텍스트 (해석할 수 없으면 텍스트 그대로) */
336
+ function parseBody(text, contentType) {
337
+ if (text === "")
338
+ return undefined;
339
+ if (contentType.includes("text/event-stream")) {
340
+ const messages = [];
341
+ for (const block of text.split(/\r?\n\r?\n/)) {
342
+ const data = block
343
+ .split(/\r?\n/)
344
+ .filter((line) => line.startsWith("data:"))
345
+ .map((line) => line.slice(5).replace(/^ /, ""))
346
+ .join("\n");
347
+ if (data !== "")
348
+ messages.push(tryJson(data));
349
+ }
350
+ return messages;
351
+ }
352
+ return tryJson(text);
353
+ }
354
+ /**
355
+ * tools/call 요청에 traceparent 를 싣는다 (요청에 이미 있으면 그대로). 서버는 이 trace ID 를 감사 이벤트의 traceId 로 남긴다.
356
+ * 다른 요청 (discover · tasks/* 등) · JSON 이 아닌 본문은 건드리지 않는다
357
+ */
358
+ function withTrace(init, traceparent) {
359
+ if (typeof init?.body !== "string")
360
+ return init;
361
+ const message = tryJson(init.body);
362
+ if (!isRecord(message) || message.method !== "tools/call" || !isRecord(message.params))
363
+ return init;
364
+ const meta = isRecord(message.params._meta) ? message.params._meta : {};
365
+ if (meta.traceparent !== undefined)
366
+ return init;
367
+ return { ...init, body: JSON.stringify({ ...message, params: { ...message.params, _meta: { ...meta, traceparent } } }) };
368
+ }
369
+ function tryJson(text) {
370
+ try {
371
+ return JSON.parse(text);
372
+ }
373
+ catch {
374
+ return text;
375
+ }
376
+ }
377
+ /** 값 안의 문자열에서 비밀 값을 가린다 (객체 · 배열 재귀) */
378
+ export function redactValue(value, secrets) {
379
+ if (secrets.length === 0)
380
+ return value;
381
+ if (typeof value === "string")
382
+ return redact(value, secrets);
383
+ if (Array.isArray(value))
384
+ return value.map((v) => redactValue(v, secrets));
385
+ if (isRecord(value))
386
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactValue(v, secrets)]));
387
+ return value;
388
+ }
389
+ function isRecord(value) {
390
+ return typeof value === "object" && value !== null && !Array.isArray(value);
391
+ }
392
+ //# sourceMappingURL=inspector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector.js","sourceRoot":"","sources":["../../src/inspect/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,YAAY,EAAmB,MAAM,oBAAoB,CAAA;AAG7E,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,uBAAuB,EACvB,QAAQ,EACR,MAAM,EACN,cAAc,GAIf,MAAM,eAAe,CAAA;AAetB,yDAAyD;AACzD,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF;AAED,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,OAAgF;IAC5G,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAA;IACtC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,MAA+B,EAAiB,EAAE;QAC3E,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,GAAG,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,iCAAiC,GAAG,IAAI,CAAC,CAAA;QAC/I,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;IACxB,CAAC,CAAA;IACD,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC/D,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IACvF,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACrC,IAAI,IAAI,EAAE,GAAG;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IACtD,MAAM,IAAI,iBAAiB,CAAC,gGAAgG,CAAC,CAAA;AAC/H,CAAC;AA+HD,MAAM,mBAAmB,GAAG,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;AACtE,4BAA4B;AAC5B,MAAM,UAAU,GAAG,GAAG,GAAG,IAAI,CAAA;AAC7B,iCAAiC;AACjC,MAAM,QAAQ,GAAG,GAAG,CAAA;AACpB,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC,CAAA;AAclF,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAA;IACtC,MAAM,SAAS,GAAiB,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IACtF,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,IAAI,GAAG,CAAA;IACzC,MAAM,SAAS,GAAG,IAAI,iBAAiB,EAAa,CAAA;IACpD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAA;IAC7C,MAAM,OAAO,GAAiB,EAAE,CAAA;IAChC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAmC,CAAA;IAC5D,MAAM,MAAM,GAAG,CAAC,KAAqB,EAAQ,EAAE;QAC7C,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,QAAQ,CAAC,KAAK,CAAC,CAAA;YACjB,CAAC;YAAC,MAAM,CAAC;gBACP,+BAA+B;YACjC,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IACD,IAAI,GAAG,GAAG,CAAC,CAAA;IAEX,iBAAiB;IACjB,MAAM,MAAM,GAAiB,EAAE,CAAA;IAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAA;IAC5C,yDAAyD;IACzD,MAAM,UAAU,GAAG,GAAa,EAAE,CAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC5C,IAAI,CAAC;YACH,OAAO,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAA;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC,CAAC,CAAA;IACJ,MAAM,gBAAgB,GAAG,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE;QACxD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,CAAe,CAAA;QAC1D,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClB,IAAI,MAAM,CAAC,MAAM,GAAG,UAAU;YAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC,CAAA;QAC5E,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;IAEF,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC5C,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,CAAA;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QACzC,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC1B,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAA;QAC7F,MAAM,IAAI,GAAG,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACzH,MAAM,QAAQ,GAAa;YACzB,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE;YACpH,UAAU,EAAE,CAAC;SACd,CAAA;QACD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;YAAE,QAAQ,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAA;QACjF,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC9B,IAAI,QAAkB,CAAA;QACtB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;YAC9F,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAA;YAC1C,MAAM,KAAK,CAAA;QACb,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;QACvD,MAAM,QAAQ,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,SAAoB,EAAE,CAAA;QAC7H,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAA;QAC5B,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAA;QAC1C,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAA;YAC7B,IAAI,IAAI,GAAG,EAAE,CAAA;YACb,MAAM,MAAM,GAAG,GAAS,EAAE;gBACxB,QAAQ,CAAC,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAA;gBAC5D,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAA;YAC5C,CAAC,CAAA;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAK,CAAC,SAAS,EAAE,CAAA;YACrC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;YACjC,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE;gBACvB,IAAI,CAAC;oBACH,SAAS,CAAC;wBACR,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;wBACjC,IAAI,KAAK,CAAC,IAAI;4BAAE,MAAK;wBACrB,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU;4BAAE,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;oBACrF,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,sBAAsB;gBACxB,CAAC;gBACD,MAAM,EAAE,CAAA;YACV,CAAC,CAAC,EAAE,CAAA;YACJ,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,EAAE,CAAC,CAAA;QAChF,CAAC;QACD,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAiB,CAAA;IAElB,MAAM,SAAS,GAAG,CAAC,IAAa,EAAW,EAAE;QAC3C,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QACnD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACrE,MAAM,IAAI,iBAAiB,CAAC,oBAAoB,IAAI,gBAAgB,KAAK,EAAE,CAAC,CAAA;QAC9E,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;IAED,uDAAuD;IACvD,MAAM,QAAQ,GAAG,CAAC,OAAgB,EAAc,EAAE;QAChD,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACtC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,YAAY,CAAC;gBACpB,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,KAAK,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK;gBAC/C,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO;gBACnD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,mBAAmB;gBACrD,KAAK,EAAE,cAAc;aACtB,CAAC,CAAA;YACF,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACnC,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAED,MAAM,IAAI,GAAG,CAAC,MAAkB,EAAc,EAAE;QAC9C,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACpB,IAAI,OAAO,CAAC,MAAM,GAAG,KAAK;YAAE,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,CAAA;QACrE,OAAO,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAA;QAC1B,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QAClC,OAAO,MAAM,CAAA;IACf,CAAC,CAAA;IAED,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,UAAU,EAAE,OAAO,CAAC,QAAQ;QAC5B,QAAQ,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;QACvD,OAAO,EAAE,SAAS;QAClB,KAAK,CAAC,QAAQ,CAAC,IAAI;YACjB,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;YAC/B,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;YAC5B,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAA;YAChC,MAAM,CAAC,OAAO,EAAE,CAAA;YAChB,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAA;QAC1B,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI;YACrB,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;YAC/B,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;YAC5B,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACvC,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,IAAI;YACf,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;YAC/B,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;YAC5B,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAA;QACnC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE;YAC/B,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;YACpC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YAC1B,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YAC/C,MAAM,MAAM,GAAe;gBACzB,EAAE,EAAE,EAAE,GAAG;gBACT,EAAE,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE;gBACnC,OAAO,EAAE,OAAO,CAAC,IAAI;gBACrB,OAAO,EAAE,EAAE;gBACX,KAAK;gBACL,SAAS,EAAE,CAAC,CAAC,SAAS,KAAK,IAAI;gBAC/B,EAAE,EAAE,KAAK;gBACT,UAAU,EAAE,CAAC;gBACb,QAAQ,EAAE,EAAE;gBACZ,MAAM,EAAE,EAAE;gBACV,OAAO;gBACP,SAAS,EAAE,EAAE;aACd,CAAA;YACD,MAAM,IAAI,GAAG,CAAC,KAAgB,EAAc,EAAE;gBAC5C,MAAM,CAAC,KAAK,GAAG,KAAK,CAAA;gBACpB,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAA;gBACxC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAA;YACrB,CAAC,CAAA;YACD,wEAAwE;YACxE,IAAI,OAA0B,CAAA;YAC9B,IAAI,CAAC;gBACH,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC,CAAA;YACpF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,KAAK,YAAY,uBAAuB,CAAC;oBAAE,MAAM,KAAK,CAAA;gBAC5D,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YAChH,CAAC;YACD,+CAA+C;YAC/C,MAAM,CAAC,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,CAA4B,CAAA;YACrE,MAAM,KAAK,GAAc,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;YACjJ,MAAM,WAAW,GAAgB;gBAC/B,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE;oBACrB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;oBAC5B,CAAC,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,CAAA;gBACxB,CAAC;gBACD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;oBACf,MAAM,CAAC,IAAI,GAAG,IAAI,CAAA;oBAClB,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAA;gBAClB,CAAC;gBACD,GAAG,CAAC,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpD,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1C,CAAA;YACD,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;YACnC,IAAI,MAAM,EAAE,CAAC;gBACX,WAAW,CAAC,OAAO,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE;oBACtC,MAAM,KAAK,GAAgB,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAA;oBAC3E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;oBACzB,KAAK,CAAC,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,CAAA;oBACpC,OAAO,KAAK,CAAC,MAAM,CAAA;gBACrB,CAAC,CAAA;YACH,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAA;gBAC9F,MAAM,CAAC,EAAE,GAAG,IAAI,CAAA;gBAChB,MAAM,CAAC,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;YAC5C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,KAAK,GAAG,WAAW,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,CAAc,CAAA;YAChF,CAAC;YACD,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YAC3B,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAA;YACxC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAA;QACrB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC;QAC3B,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC7C,cAAc,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS;QAC3C,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC;QAC9B,MAAM,EAAE,CAAI,KAAQ,EAAK,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,CAAM;QACjE,SAAS,CAAC,QAAQ;YAChB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACvB,OAAO,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC9C,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,EAAE;KAClC,CAAA;AACH,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,SAAS,CAAC,MAAkB;IAC1C,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,CAAA;AACzI,CAAC;AAED,+FAA+F;AAC/F,SAAS,aAAa,CAAC,OAAe,EAAE,CAAqB;IAC3D,IAAI,CAAC,CAAC,OAAO;QAAE,OAAO,CAAC,CAAC,OAAO,CAAA;IAC/B,IAAI,CAAC,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,GAAG,EAAE,CAAC,KAAK,CAAA;IAC1C,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,CAAA;IACzB,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IAC9B,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,SAAS,CACjB;gBACE,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EAAE,GAAG,OAAO,WAAW,OAAO,CAAC,OAAO,MAAM,OAAO,CAAC,IAAI,kCAAkC,OAAO,CAAC,IAAI,YAAY;gBACzH,OAAO;gBACP,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,EACD,OAAO,CACR,CAAA;QACH,CAAC;QACD,IAAI,OAAO,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QAC5C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;IAC7C,CAAC,CAAA;AACH,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,MAA+B;IAClE,IAAI,KAAK,YAAY,SAAS;QAAE,OAAO,WAAW,CAAC,KAAK,CAAC,CAAA;IACzD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACtE,IAAI,MAAM,EAAE,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,CAAA;IACnF,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,CAAA;AAC1C,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,WAAW,CAAC,KAAgB;IAC1C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC,IAAI,CAAA;IACjE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;AACjE,CAAC;AAED,kDAAkD;AAClD,KAAK,UAAU,MAAM,CAAC,OAA2B;IAC/C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAChC,IAAI,KAAgD,CAAA;IACpD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACnD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;QAClC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC;QACrB,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;KACvF,CAAC,CAAA;IACF,YAAY,CAAC,KAAK,CAAC,CAAA;IACnB,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACjC,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,OAAgB,EAAE,OAA0B;IAC7D,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC9B,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YAC3D,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;QACxF,CAAC;;YAAM,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IACF,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,2DAA2D;AAC3D,SAAS,SAAS,CAAC,IAAY,EAAE,WAAmB;IAClD,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IACjC,IAAI,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAc,EAAE,CAAA;QAC9B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,KAAK;iBACf,KAAK,CAAC,OAAO,CAAC;iBACd,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;iBAC1C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;iBAC9C,IAAI,CAAC,IAAI,CAAC,CAAA;YACb,IAAI,IAAI,KAAK,EAAE;gBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;QAC/C,CAAC;QACD,OAAO,QAAQ,CAAA;IACjB,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAA;AACtB,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,IAA6B,EAAE,WAAmB;IACnE,IAAI,OAAO,IAAI,EAAE,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAClC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,YAAY,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IACnG,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IACvE,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IAC/C,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;AAC1H,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,OAA0B;IACpE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACtC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;IAC1E,IAAI,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACnH,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\"\nimport { randomBytes } from \"node:crypto\"\nimport type { AuditEvent } from \"../audit.ts\"\nimport { readDevLock } from \"../cli/dev-lock.ts\"\nimport { VERSION } from \"../version-constant.ts\"\nimport { CallError, createClient, type MextClient } from \"../client/typed.ts\"\nimport type { CallOptions, CommandInfo, CommandList, InputAnswer, InputRequestInfo, ProgressUpdate, ServerInfo, TaskStatus } from \"../client/types.ts\"\nimport type { AuditSource } from \"./audit.ts\"\nimport {\n describeProfiles,\n findProfile,\n ProfileUnavailableError,\n REDACTED,\n redact,\n resolveProfile,\n type Profile,\n type ProfileSet,\n type ProfileView,\n} from \"./profiles.ts\"\n\n/**\n * Inspector (specs/inspector.md): 호출자 프로필별 타입 client · 호출 기록 (원시 요청 · 응답, 비밀 값 가림).\n * CLI · 웹 UI · 시나리오 실행이 같은 객체를 쓴다. 호출 코드는 타입 client 하나 (별도 MCP 클라이언트 구현 없음)\n */\n\n/* 대상 서버 */\n\nexport interface InspectTarget {\n url: string\n /** flag = --url · env = MEXT_URL · dev = 이 프로젝트의 mext dev (.mext/dev/lock) */\n source: \"flag\" | \"env\" | \"dev\"\n}\n\n/** 대상 서버를 정할 수 없다 · URL 형식 오류 · 없는 프로필 (CLI = exit 2) */\nexport class InspectUsageError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"InspectUsageError\"\n }\n}\n\n/** --url > MEXT_URL > 실행 중인 mext dev. 셋 다 없음 = InspectUsageError */\nexport function resolveTarget(options: { url?: string; env?: Record<string, string | undefined>; cwd: string }): InspectTarget {\n const env = options.env ?? process.env\n const pick = (url: string, source: InspectTarget[\"source\"]): InspectTarget => {\n if (!/^https?:\\/\\//i.test(url)) throw new InspectUsageError(`${source === \"env\" ? \"MEXT_URL\" : \"--url\"} must be an http(s) URL (got \"${url}\")`)\n return { url, source }\n }\n if (options.url !== undefined) return pick(options.url, \"flag\")\n if (env.MEXT_URL !== undefined && env.MEXT_URL !== \"\") return pick(env.MEXT_URL, \"env\")\n const lock = readDevLock(options.cwd)\n if (lock?.url) return { url: lock.url, source: \"dev\" }\n throw new InspectUsageError('No server to inspect. Start \"mext dev\" in this project, or pass --url <url> (or set MEXT_URL).')\n}\n\n/* 호출 기록 */\n\n/** HTTP 왕복 하나 (원시 요청 · 응답). 비밀 값은 [redacted] */\nexport interface Exchange {\n /** JSON-RPC method (tools/call · tools/list · server/discover · tasks/get …) */\n rpc?: string\n request: { method: string; url: string; headers: Record<string, string>; body: unknown }\n /** body = JSON 응답 · SSE 메시지 배열 (text/event-stream) · 그 외 텍스트 */\n response?: { status: number; headers: Record<string, string>; body: unknown }\n /** 응답을 받지 못함 (연결 실패 · 취소) */\n error?: string\n durationMs: number\n}\n\n/** 오류 본문 { code, message, ...details } */\nexport type ErrorBody = Record<string, unknown> & { code: string; message: string }\n\nexport type CallOutcome = { ok: true; value: unknown } | { ok: false; error: ErrorBody }\n\nexport interface InputRecord {\n kind: InputRequestInfo[\"kind\"]\n message: string\n /** 보낸 답 (true · false · { action, content }). 답 없음 = undefined */\n answer?: InputAnswer\n}\n\n/** 호출 하나의 기록 */\nexport interface CallRecord {\n /** 이 Inspector 안에서 1부터 */\n id: number\n /** 시작 시각 (ISO) */\n at: string\n profile: string\n command: string\n input: Record<string, unknown>\n confirmed: boolean\n ok: boolean\n /** ok = 반환값 */\n value?: unknown\n /** ok 아님 = 오류 본문 */\n error?: ErrorBody\n durationMs: number\n progress: ProgressUpdate[]\n inputs: InputRecord[]\n /** task 로 실행됐으면 마지막 상태 */\n task?: TaskStatus\n /**\n * 이 호출의 tools/call 요청에 실은 trace ID (params._meta.traceparent, W3C trace context).\n * 서버의 감사 이벤트 traceId 와 같다 = 호출과 이벤트를 잇는 값\n */\n traceId: string\n exchanges: Exchange[]\n}\n\n/** 입력 요청의 답 (종류별). 적지 않은 종류 = INPUT_UNAVAILABLE */\nexport interface Answers {\n confirm?: boolean\n /** 값 객체 = 수락 · false = 거절 */\n form?: Record<string, unknown> | false\n url?: boolean\n}\n\nexport interface InspectCallOptions {\n /** 프로필 이름. 생략 = 프로필 파일의 default */\n profile?: string\n /** 사람이 확인했다는 표시 (client.md §2.2): 확인 표시 전송 + confirm 입력 요청 승인 */\n confirmed?: boolean\n answers?: Answers\n /** 입력 · 확인 요청을 모두 거절 */\n decline?: boolean\n /** 입력 요청을 직접 처리 (웹 UI). answers · decline 보다 먼저 */\n onInput?: (request: InputRequestInfo) => InputAnswer | Promise<InputAnswer>\n onProgress?: (update: ProgressUpdate) => void\n onTask?: (task: TaskStatus) => void\n signal?: AbortSignal\n}\n\nexport interface InspectorOptions {\n /** 대상 서버의 MCP 엔드포인트 */\n url: string\n profiles: ProfileSet\n /** 프로필 값을 읽을 환경변수 (기본 process.env) */\n env?: Record<string, string | undefined>\n fetch?: typeof fetch\n /** 보관할 호출 기록 수 (기본 200. 넘으면 오래된 것부터 버린다) */\n historyLimit?: number\n /** 호출 기록이 추가될 때 */\n onRecord?: (record: CallRecord) => void\n /** 감사 출처 (§6). 없음 = 감사 이벤트 없음 */\n audit?: AuditSource\n /** 보관할 감사 이벤트 수 (기본 500) */\n auditLimit?: number\n}\n\n/** Inspector 가 알리는 일: 호출 기록 추가 · 감사 이벤트 수신 */\nexport type InspectorEvent = { type: \"record\"; record: CallRecord } | { type: \"audit\"; event: AuditEvent }\n\nexport interface Inspector {\n readonly url: string\n readonly profileSet: ProfileSet\n /** 표시용 프로필 목록 (값 없음) */\n profiles(): ProfileView[]\n /** 이름 확인 (생략 = default). 없는 이름 = InspectUsageError */\n profile(name?: string): Profile\n /** 그 프로필에게 보이는 목록 (매번 서버에서 다시 읽는다). 실패 = CallError · ProfileUnavailableError */\n commands(profile?: string): Promise<CommandList>\n describe(id: string, profile?: string): Promise<CommandInfo>\n server(profile?: string): Promise<ServerInfo>\n /** 호출. 호출 오류 · 프로필 값 없음 · 취소도 던지지 않고 기록 (ok: false) 으로 돌려준다 */\n call(id: string, input?: Record<string, unknown>, options?: InspectCallOptions): Promise<CallRecord>\n /** 오래된 것부터 */\n history(): CallRecord[]\n clearHistory(): void\n /** 감사 출처가 연결돼 있는지 */\n readonly auditConnected: boolean\n /** 받은 감사 이벤트 (오래된 것부터, 프로필의 비밀 값은 [redacted]) */\n auditEvents(): AuditEvent[]\n /** 값 안의 문자열에서 지금 읽을 수 있는 모든 프로필의 비밀 값을 가린다 (기록 · 이벤트와 같은 규칙) */\n redact<T>(value: T): T\n /** 호출 기록 · 감사 이벤트 구독. 반환 = 해제 */\n subscribe(listener: (event: InspectorEvent) => void): () => void\n /** 감사 출처 연결 해제 */\n close(): void\n}\n\nconst DEFAULT_CLIENT_INFO = { name: \"mext-inspect\", version: VERSION }\n/** 기록에 남기는 본문 길이 상한 (문자) */\nconst BODY_LIMIT = 256 * 1024\n/** 호출이 끝난 뒤 응답 본문 수집을 기다리는 시간 */\nconst DRAIN_MS = 500\nconst SECRET_HEADERS = new Set([\"authorization\", \"cookie\", \"proxy-authorization\"])\n\ninterface Recording {\n exchanges: Exchange[]\n traceparent: string\n secrets: readonly string[]\n pending: Capture[]\n}\n\ninterface Capture {\n done: Promise<void>\n stop(): void\n}\n\nexport function createInspector(options: InspectorOptions): Inspector {\n const env = options.env ?? process.env\n const baseFetch: typeof fetch = options.fetch ?? ((input, init) => fetch(input, init))\n const limit = options.historyLimit ?? 200\n const recording = new AsyncLocalStorage<Recording>()\n const clients = new Map<string, MextClient>()\n const records: CallRecord[] = []\n const listeners = new Set<(event: InspectorEvent) => void>()\n const notify = (event: InspectorEvent): void => {\n for (const listener of listeners) {\n try {\n listener(event)\n } catch {\n // 구독자의 예외는 호출 · 수신에 영향을 주지 않는다\n }\n }\n }\n let seq = 0\n\n /* 감사 이벤트 (§6) */\n const audits: AuditEvent[] = []\n const auditLimit = options.auditLimit ?? 500\n /** 지금 읽을 수 있는 모든 프로필의 비밀 값 (이벤트의 입력 · 요약에 섞여 있으면 가린다) */\n const allSecrets = (): string[] =>\n options.profiles.profiles.flatMap((profile) => {\n try {\n return resolveProfile(profile, env).secrets\n } catch {\n return []\n }\n })\n const unsubscribeAudit = options.audit?.subscribe((raw) => {\n const event = redactValue(raw, allSecrets()) as AuditEvent\n audits.push(event)\n if (audits.length > auditLimit) audits.splice(0, audits.length - auditLimit)\n notify({ type: \"audit\", event })\n })\n\n const recordingFetch = (async (input, init) => {\n const store = recording.getStore()\n if (!store) return baseFetch(input, init)\n init = withTrace(init, store.traceparent)\n const started = Date.now()\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : input.url\n const body = typeof init?.body === \"string\" ? parseBody(redact(init.body, store.secrets), \"application/json\") : undefined\n const exchange: Exchange = {\n request: { method: init?.method ?? \"GET\", url, headers: headersOf(new Headers(init?.headers), store.secrets), body },\n durationMs: 0,\n }\n if (isRecord(body) && typeof body.method === \"string\") exchange.rpc = body.method\n store.exchanges.push(exchange)\n let response: Response\n try {\n response = await baseFetch(input, init)\n } catch (error) {\n exchange.error = redact(error instanceof Error ? error.message : String(error), store.secrets)\n exchange.durationMs = Date.now() - started\n throw error\n }\n const type = response.headers.get(\"content-type\") ?? \"\"\n const captured = { status: response.status, headers: headersOf(response.headers, store.secrets), body: undefined as unknown }\n exchange.response = captured\n exchange.durationMs = Date.now() - started\n if (response.body) {\n const copy = response.clone()\n let text = \"\"\n const finish = (): void => {\n captured.body = parseBody(redact(text, store.secrets), type)\n exchange.durationMs = Date.now() - started\n }\n const reader = copy.body!.getReader()\n const decoder = new TextDecoder()\n const done = (async () => {\n try {\n for (;;) {\n const chunk = await reader.read()\n if (chunk.done) break\n if (text.length < BODY_LIMIT) text += decoder.decode(chunk.value, { stream: true })\n }\n } catch {\n // 취소 · 연결 종료 = 받은 데까지\n }\n finish()\n })()\n store.pending.push({ done, stop: () => void reader.cancel().catch(() => {}) })\n }\n return response\n }) as typeof fetch\n\n const profileOf = (name?: string): Profile => {\n const profile = findProfile(options.profiles, name)\n if (!profile) {\n const names = options.profiles.profiles.map((p) => p.name).join(\", \")\n throw new InspectUsageError(`Unknown profile \"${name}\". Profiles: ${names}`)\n }\n return profile\n }\n\n /** 프로필별 client. 토큰 · 헤더는 요청마다 환경변수에서 읽는다 (저장하지 않는다) */\n const clientOf = (profile: Profile): MextClient => {\n let client = clients.get(profile.name)\n if (!client) {\n client = createClient({\n url: options.url,\n token: () => resolveProfile(profile, env).token,\n headers: () => resolveProfile(profile, env).headers,\n clientInfo: profile.clientInfo ?? DEFAULT_CLIENT_INFO,\n fetch: recordingFetch,\n })\n clients.set(profile.name, client)\n }\n return client\n }\n\n const push = (record: CallRecord): CallRecord => {\n records.push(record)\n if (records.length > limit) records.splice(0, records.length - limit)\n options.onRecord?.(record)\n notify({ type: \"record\", record })\n return record\n }\n\n return {\n url: options.url,\n profileSet: options.profiles,\n profiles: () => describeProfiles(options.profiles, env),\n profile: profileOf,\n async commands(name) {\n const profile = profileOf(name)\n resolveProfile(profile, env)\n const client = clientOf(profile)\n client.refresh()\n return client.commands()\n },\n async describe(id, name) {\n const profile = profileOf(name)\n resolveProfile(profile, env)\n return clientOf(profile).describe(id)\n },\n async server(name) {\n const profile = profileOf(name)\n resolveProfile(profile, env)\n return clientOf(profile).server()\n },\n async call(id, input = {}, o = {}) {\n const profile = profileOf(o.profile)\n const started = Date.now()\n const traceId = randomBytes(16).toString(\"hex\")\n const record: CallRecord = {\n id: ++seq,\n at: new Date(started).toISOString(),\n profile: profile.name,\n command: id,\n input,\n confirmed: o.confirmed === true,\n ok: false,\n durationMs: 0,\n progress: [],\n inputs: [],\n traceId,\n exchanges: [],\n }\n const fail = (error: ErrorBody): CallRecord => {\n record.error = error\n record.durationMs = Date.now() - started\n return push(record)\n }\n // 가릴 값 = 이 프로필뿐 아니라 읽을 수 있는 모든 프로필의 비밀 값 (다른 프로필의 토큰이 입력 · 결과에 섞여도 가린다)\n let secrets: readonly string[]\n try {\n secrets = [...new Set([...resolveProfile(profile, env).secrets, ...allSecrets()])]\n } catch (error) {\n if (!(error instanceof ProfileUnavailableError)) throw error\n return fail({ code: error.code, message: error.message, profile: error.profile, missing: [...error.missing] })\n }\n // 입력에 비밀 값이 그대로 들어온 경우 (환경변수와 같은 값) 도 기록에서 가린다\n record.input = redactValue(input, secrets) as Record<string, unknown>\n const store: Recording = { exchanges: record.exchanges, traceparent: `00-${traceId}-${randomBytes(8).toString(\"hex\")}-01`, secrets, pending: [] }\n const callOptions: CallOptions = {\n onProgress: (update) => {\n record.progress.push(update)\n o.onProgress?.(update)\n },\n onTask: (task) => {\n record.task = task\n o.onTask?.(task)\n },\n ...(o.confirmed === true ? { confirmed: true } : {}),\n ...(o.signal ? { signal: o.signal } : {}),\n }\n const answer = inputAnswerer(id, o)\n if (answer) {\n callOptions.onInput = async (request) => {\n const entry: InputRecord = { kind: request.kind, message: request.message }\n record.inputs.push(entry)\n entry.answer = await answer(request)\n return entry.answer\n }\n }\n try {\n const value = await recording.run(store, () => clientOf(profile).call(id, input, callOptions))\n record.ok = true\n record.value = redactValue(value, secrets)\n } catch (error) {\n record.error = redactValue(errorBodyOf(error, o.signal), secrets) as ErrorBody\n }\n await settle(store.pending)\n record.durationMs = Date.now() - started\n return push(record)\n },\n history: () => [...records],\n clearHistory: () => void (records.length = 0),\n auditConnected: options.audit !== undefined,\n auditEvents: () => [...audits],\n redact: <T>(value: T): T => redactValue(value, allSecrets()) as T,\n subscribe(listener) {\n listeners.add(listener)\n return () => void listeners.delete(listener)\n },\n close: () => unsubscribeAudit?.(),\n }\n}\n\n/** 결과 요약 (기록 → 성공 값 또는 오류 본문) */\nexport function outcomeOf(record: CallRecord): CallOutcome {\n return record.ok ? { ok: true, value: record.value } : { ok: false, error: record.error ?? { code: \"UNKNOWN\", message: \"No result.\" } }\n}\n\n/** answers · decline · onInput → 입력 요청 처리기. 셋 다 없음 = undefined (client 가 INPUT_UNAVAILABLE) */\nfunction inputAnswerer(command: string, o: InspectCallOptions): ((request: InputRequestInfo) => InputAnswer | Promise<InputAnswer>) | undefined {\n if (o.onInput) return o.onInput\n if (o.decline === true) return () => false\n const answers = o.answers\n if (!answers) return undefined\n return (request) => {\n const given = answers[request.kind]\n if (given === undefined) {\n throw new CallError(\n {\n code: \"INPUT_UNAVAILABLE\",\n message: `${command} asked \"${request.message}\" (${request.kind}), but no answer was given for ${request.kind} requests.`,\n command,\n kind: request.kind,\n },\n command,\n )\n }\n if (typeof given === \"boolean\") return given\n return { action: \"accept\", content: given }\n }\n}\n\nfunction errorBodyOf(error: unknown, signal: AbortSignal | undefined): ErrorBody {\n if (error instanceof CallError) return orderedBody(error)\n const message = error instanceof Error ? error.message : String(error)\n if (signal?.aborted) return { code: \"CANCELLED\", message: message || \"Cancelled.\" }\n return { code: \"CLIENT_ERROR\", message }\n}\n\n/** 오류 본문. code · message 를 앞에 (출력 · 기록에서 먼저 보이게) */\nexport function orderedBody(error: CallError): ErrorBody {\n const { code: _code, message: _message, ...details } = error.body\n return { code: error.code, message: error.message, ...details }\n}\n\n/** 수집 중인 응답 본문을 잠깐 기다리고, 끝나지 않은 것은 받은 데까지로 끊는다 */\nasync function settle(pending: readonly Capture[]): Promise<void> {\n if (pending.length === 0) return\n let timer: ReturnType<typeof setTimeout> | undefined\n const all = Promise.all(pending.map((p) => p.done))\n const timedOut = await Promise.race([\n all.then(() => false),\n new Promise<boolean>((resolve) => (timer = setTimeout(() => resolve(true), DRAIN_MS))),\n ])\n clearTimeout(timer)\n if (timedOut) {\n for (const p of pending) p.stop()\n await all\n }\n}\n\nfunction headersOf(headers: Headers, secrets: readonly string[]): Record<string, string> {\n const out: Record<string, string> = {}\n headers.forEach((value, name) => {\n if (SECRET_HEADERS.has(name.toLowerCase())) {\n const scheme = /^(Bearer|Basic)\\s+/i.exec(value)?.[0] ?? \"\"\n out[name] = name.toLowerCase() === \"authorization\" ? `${scheme}${REDACTED}` : REDACTED\n } else out[name] = redact(value, secrets)\n })\n return out\n}\n\n/** 본문 텍스트 → JSON · SSE 메시지 배열 · 텍스트 (해석할 수 없으면 텍스트 그대로) */\nfunction parseBody(text: string, contentType: string): unknown {\n if (text === \"\") return undefined\n if (contentType.includes(\"text/event-stream\")) {\n const messages: unknown[] = []\n for (const block of text.split(/\\r?\\n\\r?\\n/)) {\n const data = block\n .split(/\\r?\\n/)\n .filter((line) => line.startsWith(\"data:\"))\n .map((line) => line.slice(5).replace(/^ /, \"\"))\n .join(\"\\n\")\n if (data !== \"\") messages.push(tryJson(data))\n }\n return messages\n }\n return tryJson(text)\n}\n\n/**\n * tools/call 요청에 traceparent 를 싣는다 (요청에 이미 있으면 그대로). 서버는 이 trace ID 를 감사 이벤트의 traceId 로 남긴다.\n * 다른 요청 (discover · tasks/* 등) · JSON 이 아닌 본문은 건드리지 않는다\n */\nfunction withTrace(init: RequestInit | undefined, traceparent: string): RequestInit | undefined {\n if (typeof init?.body !== \"string\") return init\n const message = tryJson(init.body)\n if (!isRecord(message) || message.method !== \"tools/call\" || !isRecord(message.params)) return init\n const meta = isRecord(message.params._meta) ? message.params._meta : {}\n if (meta.traceparent !== undefined) return init\n return { ...init, body: JSON.stringify({ ...message, params: { ...message.params, _meta: { ...meta, traceparent } } }) }\n}\n\nfunction tryJson(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n\n/** 값 안의 문자열에서 비밀 값을 가린다 (객체 · 배열 재귀) */\nexport function redactValue(value: unknown, secrets: readonly string[]): unknown {\n if (secrets.length === 0) return value\n if (typeof value === \"string\") return redact(value, secrets)\n if (Array.isArray(value)) return value.map((v) => redactValue(v, secrets))\n if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactValue(v, secrets)]))\n return value\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n"]}