@obov/mext 0.4.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.
- package/dist/approval.d.ts +33 -0
- package/dist/approval.js +36 -0
- package/dist/approval.js.map +1 -0
- package/dist/audit.d.ts +123 -0
- package/dist/audit.js +221 -0
- package/dist/audit.js.map +1 -0
- package/dist/build-cli.d.ts +1 -0
- package/dist/build-cli.js +2 -1
- package/dist/build-cli.js.map +1 -1
- package/dist/build.d.ts +2 -0
- package/dist/build.js +30 -3
- package/dist/build.js.map +1 -1
- package/dist/caller.d.ts +53 -0
- package/dist/caller.js +51 -0
- package/dist/caller.js.map +1 -0
- package/dist/cli/build.js +6 -3
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev-audit.d.ts +25 -0
- package/dist/cli/dev-audit.js +84 -0
- package/dist/cli/dev-audit.js.map +1 -0
- package/dist/cli/dev-lock.d.ts +3 -1
- package/dist/cli/dev-lock.js +12 -5
- package/dist/cli/dev-lock.js.map +1 -1
- package/dist/cli/dev-server.d.ts +9 -0
- package/dist/cli/dev-server.js +24 -1
- package/dist/cli/dev-server.js.map +1 -1
- package/dist/cli/dev-tap.d.ts +20 -0
- package/dist/cli/dev-tap.js +70 -0
- package/dist/cli/dev-tap.js.map +1 -0
- package/dist/cli/dev.js +16 -4
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/help.js +2 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inspect.d.ts +7 -0
- package/dist/cli/inspect.js +699 -0
- package/dist/cli/inspect.js.map +1 -0
- package/dist/cli/main.js +8 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js +3 -2
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/run.js +28 -4
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/types.d.ts +9 -0
- package/dist/cli/types.js +77 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.js +7 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/manifest.js +6 -2
- package/dist/client/manifest.js.map +1 -1
- package/dist/client/runner.js +37 -3
- package/dist/client/runner.js.map +1 -1
- package/dist/client/typed.d.ts +54 -0
- package/dist/client/typed.js +490 -0
- package/dist/client/typed.js.map +1 -0
- package/dist/client/types.d.ts +196 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/command.d.ts +61 -5
- package/dist/command.js +42 -1
- package/dist/command.js.map +1 -1
- package/dist/config.d.ts +42 -10
- package/dist/config.js +32 -7
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +21 -3
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -1
- package/dist/expose.d.ts +9 -0
- package/dist/expose.js +52 -0
- package/dist/expose.js.map +1 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/inspect/agent.d.ts +104 -0
- package/dist/inspect/agent.js +125 -0
- package/dist/inspect/agent.js.map +1 -0
- package/dist/inspect/audit.d.ts +46 -0
- package/dist/inspect/audit.js +133 -0
- package/dist/inspect/audit.js.map +1 -0
- package/dist/inspect/compare.d.ts +12 -0
- package/dist/inspect/compare.js +31 -0
- package/dist/inspect/compare.js.map +1 -0
- package/dist/inspect/index.d.ts +17 -0
- package/dist/inspect/index.js +12 -0
- package/dist/inspect/index.js.map +1 -0
- package/dist/inspect/inspector.d.ts +168 -0
- package/dist/inspect/inspector.js +392 -0
- package/dist/inspect/inspector.js.map +1 -0
- package/dist/inspect/profiles.d.ts +95 -0
- package/dist/inspect/profiles.js +235 -0
- package/dist/inspect/profiles.js.map +1 -0
- package/dist/inspect/scenario.d.ts +126 -0
- package/dist/inspect/scenario.js +555 -0
- package/dist/inspect/scenario.js.map +1 -0
- package/dist/inspect/ui-server.d.ts +31 -0
- package/dist/inspect/ui-server.js +378 -0
- package/dist/inspect/ui-server.js.map +1 -0
- package/dist/inspect/ui.html +917 -0
- package/dist/instrumentation.d.ts +13 -2
- package/dist/instrumentation.js +11 -1
- package/dist/instrumentation.js.map +1 -1
- package/dist/invoke.d.ts +32 -5
- package/dist/invoke.js +150 -16
- package/dist/invoke.js.map +1 -1
- package/dist/manifest.d.ts +13 -2
- package/dist/manifest.js +5 -1
- package/dist/manifest.js.map +1 -1
- package/dist/mcp/constants.d.ts +5 -0
- package/dist/mcp/constants.js +5 -0
- package/dist/mcp/constants.js.map +1 -1
- package/dist/mcp/dispatch.d.ts +1 -1
- package/dist/mcp/dispatch.js +14 -3
- package/dist/mcp/dispatch.js.map +1 -1
- package/dist/mcp/help.d.ts +2 -0
- package/dist/mcp/help.js +6 -1
- package/dist/mcp/help.js.map +1 -1
- package/dist/mcp/http.d.ts +16 -2
- package/dist/mcp/http.js +76 -7
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/install.d.ts +1 -1
- package/dist/mcp/install.js +3 -2
- package/dist/mcp/install.js.map +1 -1
- package/dist/mcp/server.d.ts +12 -1
- package/dist/mcp/server.js +63 -17
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +4 -0
- package/dist/mcp/stdio.js +28 -0
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/mcp/tasks.d.ts +3 -1
- package/dist/mcp/tasks.js +6 -3
- package/dist/mcp/tasks.js.map +1 -1
- package/dist/principal.d.ts +22 -0
- package/dist/principal.js +29 -0
- package/dist/principal.js.map +1 -1
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +21 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/client.d.ts +2 -0
- package/dist/remote/client.js +3 -2
- package/dist/remote/client.js.map +1 -1
- package/dist/remote/oauth.d.ts +1 -1
- package/dist/remote/oauth.js +1 -1
- package/dist/remote/oauth.js.map +1 -1
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +11 -0
- package/dist/runtime.js.map +1 -0
- package/dist/start.js +6 -2
- package/dist/start.js.map +1 -1
- package/dist/suggest.js +1 -1
- package/dist/suggest.js.map +1 -1
- package/dist/typegen.d.ts +27 -0
- package/dist/typegen.js +62 -0
- package/dist/typegen.js.map +1 -0
- package/dist/version-constant.d.ts +5 -0
- package/dist/version-constant.js +6 -0
- package/dist/version-constant.js.map +1 -0
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/dist/version.js.map +1 -1
- package/package.json +14 -3
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { AuditEvent } from "../audit.ts";
|
|
2
|
+
import { type Failure } from "./compare.ts";
|
|
3
|
+
import type { Inspector } from "./inspector.ts";
|
|
4
|
+
/**
|
|
5
|
+
* 에이전트 plugin 계약 (specs/inspector.md §7): 에이전트에게 말로 지시하고, 서버에 남은 감사 이벤트로 판정한다.
|
|
6
|
+
* inspector 는 에이전트 종류를 모른다. 에이전트별 연결 방법은 AgentClient 를 만드는 모듈 (plugin) 안에만 있다
|
|
7
|
+
*/
|
|
8
|
+
/** 에이전트 쪽에서 일어난 일. 판정에는 쓰지 않는다 (판정 = 서버의 감사 이벤트). idle · error 만 단계의 끝을 정한다 */
|
|
9
|
+
export type AgentEvent =
|
|
10
|
+
/** 에이전트가 한 말 */
|
|
11
|
+
{
|
|
12
|
+
type: "text";
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
/** 에이전트 쪽에서 본 도구 호출 (선택) */
|
|
16
|
+
| {
|
|
17
|
+
type: "tool";
|
|
18
|
+
name: string;
|
|
19
|
+
input?: unknown;
|
|
20
|
+
output?: unknown;
|
|
21
|
+
ok?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** 보낸 지시의 처리를 마쳤다 (더 올 호출이 없다). plugin 이 반드시 알린다 */
|
|
24
|
+
| {
|
|
25
|
+
type: "idle";
|
|
26
|
+
}
|
|
27
|
+
/** 연결 끊김 · 에이전트 실행 실패 */
|
|
28
|
+
| {
|
|
29
|
+
type: "error";
|
|
30
|
+
message: string;
|
|
31
|
+
};
|
|
32
|
+
export interface AgentMessage {
|
|
33
|
+
text: string;
|
|
34
|
+
}
|
|
35
|
+
export interface AgentClient {
|
|
36
|
+
/** 표시 이름 (결과 · 로그) */
|
|
37
|
+
readonly name?: string;
|
|
38
|
+
/** 연결하고 지시를 받을 수 있는지 확인. 실패 = throw (사람이 읽을 이유) */
|
|
39
|
+
connect(options?: {
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
/** 지시 하나를 보낸다. 전달이 확인되면 끝난다 (에이전트의 일이 끝나기를 기다리지 않는다) */
|
|
43
|
+
send(message: AgentMessage): Promise<void>;
|
|
44
|
+
/** 반환 = 해제 */
|
|
45
|
+
onEvent(listener: (event: AgentEvent) => void): () => void;
|
|
46
|
+
close?(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/** --agent 모듈의 default export: AgentClient, 또는 그것을 만드는 함수 */
|
|
49
|
+
export type AgentPlugin = AgentClient | ((context: AgentPluginContext) => AgentClient | Promise<AgentClient>);
|
|
50
|
+
export interface AgentPluginContext {
|
|
51
|
+
cwd: string;
|
|
52
|
+
env: Record<string, string | undefined>;
|
|
53
|
+
}
|
|
54
|
+
export declare function isAgentClient(value: unknown): value is AgentClient;
|
|
55
|
+
/** agent 단계의 기대 (각 항목 = 감사 이벤트의 일부) */
|
|
56
|
+
export interface AgentExpect {
|
|
57
|
+
/** 순서대로 있어야 하는 이벤트 (사이에 다른 이벤트가 있어도 된다) */
|
|
58
|
+
audit?: Record<string, unknown>[];
|
|
59
|
+
/** 있으면 안 되는 이벤트 */
|
|
60
|
+
none?: Record<string, unknown>[];
|
|
61
|
+
}
|
|
62
|
+
export interface AgentToolCall {
|
|
63
|
+
name: string;
|
|
64
|
+
input?: unknown;
|
|
65
|
+
output?: unknown;
|
|
66
|
+
ok?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export interface AgentObservation {
|
|
69
|
+
/** idle 까지 갔는가 (false = timeout · error · 취소) */
|
|
70
|
+
ok: boolean;
|
|
71
|
+
/** ok 아님 = 이유 ("timeout" · error 이벤트의 메시지 · "cancelled") */
|
|
72
|
+
problem?: string;
|
|
73
|
+
/** 보낸 지시문 */
|
|
74
|
+
instruction: string;
|
|
75
|
+
/** 관찰 구간에 온 이 에이전트의 감사 이벤트 (오래된 것부터) */
|
|
76
|
+
events: AuditEvent[];
|
|
77
|
+
/** 에이전트가 한 말 */
|
|
78
|
+
messages: string[];
|
|
79
|
+
/** 에이전트 쪽에서 본 도구 호출 */
|
|
80
|
+
tools: AgentToolCall[];
|
|
81
|
+
}
|
|
82
|
+
export declare const AGENT_TIMEOUT_MS = 120000;
|
|
83
|
+
/** idle 뒤 늦게 오는 감사 이벤트를 기다리는 시간 */
|
|
84
|
+
export declare const AGENT_SETTLE_MS = 500;
|
|
85
|
+
export interface InstructOptions {
|
|
86
|
+
text: string;
|
|
87
|
+
/** 이 에이전트의 호출을 가려내는 조건 (이벤트의 일부) */
|
|
88
|
+
from?: Record<string, unknown>;
|
|
89
|
+
timeoutMs?: number;
|
|
90
|
+
settleMs?: number;
|
|
91
|
+
signal?: AbortSignal;
|
|
92
|
+
}
|
|
93
|
+
/** connect 한 번 (같은 AgentClient 로 여러 번 불러도). 실패하면 다음 호출이 다시 시도한다 */
|
|
94
|
+
export declare function connectAgent(agent: AgentClient, signal?: AbortSignal): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* 지시 하나를 보내고 idle 까지 서버의 감사 이벤트를 모은다.
|
|
97
|
+
* 관찰 = send 직전부터 idle (+ settleMs) 까지 온 이벤트 중 inspector 자신의 호출 (trace ID) 이 아니고 from 에 맞는 것.
|
|
98
|
+
* 시작 시각 (at) 이 send 보다 이른 이벤트는 뺀다: 감사 출처가 연결할 때 다시 보내 주는 예전 이벤트 (서버와 시계가 같다고 본다)
|
|
99
|
+
*/
|
|
100
|
+
export declare function instructAgent(inspector: Inspector, agent: AgentClient, options: InstructOptions): Promise<AgentObservation>;
|
|
101
|
+
/** 기대와 관찰한 이벤트 비교. 못 찾은 audit 항목 · 걸린 none 항목 = failures */
|
|
102
|
+
export declare function checkAudit(expect: AgentExpect | undefined, events: readonly AuditEvent[], failures: Failure[]): void;
|
|
103
|
+
/** 실패 항목에 싣는 이벤트 요약 (입력 · 요약문 제외) */
|
|
104
|
+
export declare function summarizeAudit(event: AuditEvent): Record<string, unknown>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { matches } from "./compare.js";
|
|
2
|
+
export function isAgentClient(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null)
|
|
4
|
+
return false;
|
|
5
|
+
const v = value;
|
|
6
|
+
return typeof v.connect === "function" && typeof v.send === "function" && typeof v.onEvent === "function";
|
|
7
|
+
}
|
|
8
|
+
export const AGENT_TIMEOUT_MS = 120_000;
|
|
9
|
+
/** idle 뒤 늦게 오는 감사 이벤트를 기다리는 시간 */
|
|
10
|
+
export const AGENT_SETTLE_MS = 500;
|
|
11
|
+
const connections = new WeakMap();
|
|
12
|
+
/** connect 한 번 (같은 AgentClient 로 여러 번 불러도). 실패하면 다음 호출이 다시 시도한다 */
|
|
13
|
+
export function connectAgent(agent, signal) {
|
|
14
|
+
let connected = connections.get(agent);
|
|
15
|
+
if (!connected) {
|
|
16
|
+
connected = agent.connect(signal ? { signal } : {});
|
|
17
|
+
connections.set(agent, connected);
|
|
18
|
+
connected.catch(() => connections.delete(agent));
|
|
19
|
+
}
|
|
20
|
+
return connected;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 지시 하나를 보내고 idle 까지 서버의 감사 이벤트를 모은다.
|
|
24
|
+
* 관찰 = send 직전부터 idle (+ settleMs) 까지 온 이벤트 중 inspector 자신의 호출 (trace ID) 이 아니고 from 에 맞는 것.
|
|
25
|
+
* 시작 시각 (at) 이 send 보다 이른 이벤트는 뺀다: 감사 출처가 연결할 때 다시 보내 주는 예전 이벤트 (서버와 시계가 같다고 본다)
|
|
26
|
+
*/
|
|
27
|
+
export async function instructAgent(inspector, agent, options) {
|
|
28
|
+
const started = Date.now();
|
|
29
|
+
const received = [];
|
|
30
|
+
const messages = [];
|
|
31
|
+
const tools = [];
|
|
32
|
+
let finish = () => { };
|
|
33
|
+
const finished = new Promise((resolve) => (finish = resolve));
|
|
34
|
+
const stopAudit = inspector.subscribe((event) => {
|
|
35
|
+
if (event.type === "audit")
|
|
36
|
+
received.push(event.event);
|
|
37
|
+
});
|
|
38
|
+
const stopAgent = agent.onEvent((event) => {
|
|
39
|
+
if (event.type === "text")
|
|
40
|
+
messages.push(event.text);
|
|
41
|
+
else if (event.type === "tool") {
|
|
42
|
+
const { type: _type, ...call } = event;
|
|
43
|
+
tools.push(call);
|
|
44
|
+
}
|
|
45
|
+
else if (event.type === "idle")
|
|
46
|
+
finish(undefined);
|
|
47
|
+
else if (event.type === "error")
|
|
48
|
+
finish(event.message);
|
|
49
|
+
});
|
|
50
|
+
const timer = setTimeout(() => finish("timeout"), options.timeoutMs ?? AGENT_TIMEOUT_MS);
|
|
51
|
+
const onAbort = () => finish("cancelled");
|
|
52
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
53
|
+
if (options.signal?.aborted)
|
|
54
|
+
finish("cancelled");
|
|
55
|
+
let problem;
|
|
56
|
+
try {
|
|
57
|
+
try {
|
|
58
|
+
await agent.send({ text: options.text });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
finish(`send failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
}
|
|
63
|
+
problem = await finished;
|
|
64
|
+
// 감사 이벤트는 호출보다 늦게 올 수 있다 (감사 출처의 전달 지연)
|
|
65
|
+
if (problem === undefined)
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, options.settleMs ?? AGENT_SETTLE_MS));
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
71
|
+
stopAgent();
|
|
72
|
+
stopAudit();
|
|
73
|
+
}
|
|
74
|
+
const own = new Set(inspector.history().map((record) => record.traceId));
|
|
75
|
+
const events = received.filter((event) => {
|
|
76
|
+
if (event.traceId !== undefined && own.has(event.traceId))
|
|
77
|
+
return false;
|
|
78
|
+
const at = Date.parse(event.at);
|
|
79
|
+
if (Number.isFinite(at) && at < started)
|
|
80
|
+
return false;
|
|
81
|
+
return options.from === undefined || matches(options.from, event);
|
|
82
|
+
});
|
|
83
|
+
// 에이전트가 한 말 · 도구 호출에 프로필의 비밀 값이 섞여 있으면 가린다 (감사 이벤트는 받을 때 이미 가렸다)
|
|
84
|
+
return inspector.redact({
|
|
85
|
+
ok: problem === undefined,
|
|
86
|
+
...(problem !== undefined ? { problem } : {}),
|
|
87
|
+
instruction: options.text,
|
|
88
|
+
events,
|
|
89
|
+
messages,
|
|
90
|
+
tools,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** 기대와 관찰한 이벤트 비교. 못 찾은 audit 항목 · 걸린 none 항목 = failures */
|
|
94
|
+
export function checkAudit(expect, events, failures) {
|
|
95
|
+
let next = 0;
|
|
96
|
+
for (const [i, pattern] of (expect?.audit ?? []).entries()) {
|
|
97
|
+
const found = events.findIndex((event, index) => index >= next && matches(pattern, event));
|
|
98
|
+
if (found >= 0) {
|
|
99
|
+
next = found + 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// 같은 명령어의 이벤트를 보여 준다 (무엇이 달랐는지). 순서만 틀린 경우도 여기에 나온다
|
|
103
|
+
const same = typeof pattern.command === "string" ? events.filter((event) => event.command === pattern.command) : events;
|
|
104
|
+
failures.push({ path: `audit.${i}`, expected: pattern, actual: same.map(summarizeAudit) });
|
|
105
|
+
}
|
|
106
|
+
for (const [i, pattern] of (expect?.none ?? []).entries()) {
|
|
107
|
+
const hit = events.find((event) => matches(pattern, event));
|
|
108
|
+
if (hit)
|
|
109
|
+
failures.push({ path: `none.${i}`, expected: pattern, actual: summarizeAudit(hit) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** 실패 항목에 싣는 이벤트 요약 (입력 · 요약문 제외) */
|
|
113
|
+
export function summarizeAudit(event) {
|
|
114
|
+
return {
|
|
115
|
+
command: event.command,
|
|
116
|
+
outcome: event.outcome,
|
|
117
|
+
...(event.error ? { error: event.error.code } : {}),
|
|
118
|
+
surface: event.surface,
|
|
119
|
+
...(event.actor ? { actor: event.actor } : {}),
|
|
120
|
+
...(event.client ? { client: event.client.name } : {}),
|
|
121
|
+
...(event.approval ? { approval: event.approval } : {}),
|
|
122
|
+
at: event.at,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/inspect/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuditEvent } from \"../audit.ts\"\nimport { matches, type Failure } from \"./compare.ts\"\nimport type { Inspector } from \"./inspector.ts\"\n\n/**\n * 에이전트 plugin 계약 (specs/inspector.md §7): 에이전트에게 말로 지시하고, 서버에 남은 감사 이벤트로 판정한다.\n * inspector 는 에이전트 종류를 모른다. 에이전트별 연결 방법은 AgentClient 를 만드는 모듈 (plugin) 안에만 있다\n */\n\n/** 에이전트 쪽에서 일어난 일. 판정에는 쓰지 않는다 (판정 = 서버의 감사 이벤트). idle · error 만 단계의 끝을 정한다 */\nexport type AgentEvent =\n /** 에이전트가 한 말 */\n | { type: \"text\"; text: string }\n /** 에이전트 쪽에서 본 도구 호출 (선택) */\n | { type: \"tool\"; name: string; input?: unknown; output?: unknown; ok?: boolean }\n /** 보낸 지시의 처리를 마쳤다 (더 올 호출이 없다). plugin 이 반드시 알린다 */\n | { type: \"idle\" }\n /** 연결 끊김 · 에이전트 실행 실패 */\n | { type: \"error\"; message: string }\n\nexport interface AgentMessage {\n text: string\n}\n\nexport interface AgentClient {\n /** 표시 이름 (결과 · 로그) */\n readonly name?: string\n /** 연결하고 지시를 받을 수 있는지 확인. 실패 = throw (사람이 읽을 이유) */\n connect(options?: { signal?: AbortSignal }): Promise<void>\n /** 지시 하나를 보낸다. 전달이 확인되면 끝난다 (에이전트의 일이 끝나기를 기다리지 않는다) */\n send(message: AgentMessage): Promise<void>\n /** 반환 = 해제 */\n onEvent(listener: (event: AgentEvent) => void): () => void\n close?(): Promise<void>\n}\n\n/** --agent 모듈의 default export: AgentClient, 또는 그것을 만드는 함수 */\nexport type AgentPlugin = AgentClient | ((context: AgentPluginContext) => AgentClient | Promise<AgentClient>)\n\nexport interface AgentPluginContext {\n cwd: string\n env: Record<string, string | undefined>\n}\n\nexport function isAgentClient(value: unknown): value is AgentClient {\n if (typeof value !== \"object\" || value === null) return false\n const v = value as Record<string, unknown>\n return typeof v.connect === \"function\" && typeof v.send === \"function\" && typeof v.onEvent === \"function\"\n}\n\n/** agent 단계의 기대 (각 항목 = 감사 이벤트의 일부) */\nexport interface AgentExpect {\n /** 순서대로 있어야 하는 이벤트 (사이에 다른 이벤트가 있어도 된다) */\n audit?: Record<string, unknown>[]\n /** 있으면 안 되는 이벤트 */\n none?: Record<string, unknown>[]\n}\n\nexport interface AgentToolCall {\n name: string\n input?: unknown\n output?: unknown\n ok?: boolean\n}\n\nexport interface AgentObservation {\n /** idle 까지 갔는가 (false = timeout · error · 취소) */\n ok: boolean\n /** ok 아님 = 이유 (\"timeout\" · error 이벤트의 메시지 · \"cancelled\") */\n problem?: string\n /** 보낸 지시문 */\n instruction: string\n /** 관찰 구간에 온 이 에이전트의 감사 이벤트 (오래된 것부터) */\n events: AuditEvent[]\n /** 에이전트가 한 말 */\n messages: string[]\n /** 에이전트 쪽에서 본 도구 호출 */\n tools: AgentToolCall[]\n}\n\nexport const AGENT_TIMEOUT_MS = 120_000\n/** idle 뒤 늦게 오는 감사 이벤트를 기다리는 시간 */\nexport const AGENT_SETTLE_MS = 500\n\nexport interface InstructOptions {\n text: string\n /** 이 에이전트의 호출을 가려내는 조건 (이벤트의 일부) */\n from?: Record<string, unknown>\n timeoutMs?: number\n settleMs?: number\n signal?: AbortSignal\n}\n\nconst connections = new WeakMap<AgentClient, Promise<void>>()\n\n/** connect 한 번 (같은 AgentClient 로 여러 번 불러도). 실패하면 다음 호출이 다시 시도한다 */\nexport function connectAgent(agent: AgentClient, signal?: AbortSignal): Promise<void> {\n let connected = connections.get(agent)\n if (!connected) {\n connected = agent.connect(signal ? { signal } : {})\n connections.set(agent, connected)\n connected.catch(() => connections.delete(agent))\n }\n return connected\n}\n\n/**\n * 지시 하나를 보내고 idle 까지 서버의 감사 이벤트를 모은다.\n * 관찰 = send 직전부터 idle (+ settleMs) 까지 온 이벤트 중 inspector 자신의 호출 (trace ID) 이 아니고 from 에 맞는 것.\n * 시작 시각 (at) 이 send 보다 이른 이벤트는 뺀다: 감사 출처가 연결할 때 다시 보내 주는 예전 이벤트 (서버와 시계가 같다고 본다)\n */\nexport async function instructAgent(inspector: Inspector, agent: AgentClient, options: InstructOptions): Promise<AgentObservation> {\n const started = Date.now()\n const received: AuditEvent[] = []\n const messages: string[] = []\n const tools: AgentToolCall[] = []\n let finish: (problem: string | undefined) => void = () => {}\n const finished = new Promise<string | undefined>((resolve) => (finish = resolve))\n const stopAudit = inspector.subscribe((event) => {\n if (event.type === \"audit\") received.push(event.event)\n })\n const stopAgent = agent.onEvent((event) => {\n if (event.type === \"text\") messages.push(event.text)\n else if (event.type === \"tool\") {\n const { type: _type, ...call } = event\n tools.push(call)\n } else if (event.type === \"idle\") finish(undefined)\n else if (event.type === \"error\") finish(event.message)\n })\n const timer = setTimeout(() => finish(\"timeout\"), options.timeoutMs ?? AGENT_TIMEOUT_MS)\n const onAbort = (): void => finish(\"cancelled\")\n options.signal?.addEventListener(\"abort\", onAbort, { once: true })\n if (options.signal?.aborted) finish(\"cancelled\")\n let problem: string | undefined\n try {\n try {\n await agent.send({ text: options.text })\n } catch (error) {\n finish(`send failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n problem = await finished\n // 감사 이벤트는 호출보다 늦게 올 수 있다 (감사 출처의 전달 지연)\n if (problem === undefined) await new Promise((resolve) => setTimeout(resolve, options.settleMs ?? AGENT_SETTLE_MS))\n } finally {\n clearTimeout(timer)\n options.signal?.removeEventListener(\"abort\", onAbort)\n stopAgent()\n stopAudit()\n }\n const own = new Set(inspector.history().map((record) => record.traceId))\n const events = received.filter((event) => {\n if (event.traceId !== undefined && own.has(event.traceId)) return false\n const at = Date.parse(event.at)\n if (Number.isFinite(at) && at < started) return false\n return options.from === undefined || matches(options.from, event)\n })\n // 에이전트가 한 말 · 도구 호출에 프로필의 비밀 값이 섞여 있으면 가린다 (감사 이벤트는 받을 때 이미 가렸다)\n return inspector.redact({\n ok: problem === undefined,\n ...(problem !== undefined ? { problem } : {}),\n instruction: options.text,\n events,\n messages,\n tools,\n })\n}\n\n/** 기대와 관찰한 이벤트 비교. 못 찾은 audit 항목 · 걸린 none 항목 = failures */\nexport function checkAudit(expect: AgentExpect | undefined, events: readonly AuditEvent[], failures: Failure[]): void {\n let next = 0\n for (const [i, pattern] of (expect?.audit ?? []).entries()) {\n const found = events.findIndex((event, index) => index >= next && matches(pattern, event))\n if (found >= 0) {\n next = found + 1\n continue\n }\n // 같은 명령어의 이벤트를 보여 준다 (무엇이 달랐는지). 순서만 틀린 경우도 여기에 나온다\n const same = typeof pattern.command === \"string\" ? events.filter((event) => event.command === pattern.command) : events\n failures.push({ path: `audit.${i}`, expected: pattern, actual: same.map(summarizeAudit) })\n }\n for (const [i, pattern] of (expect?.none ?? []).entries()) {\n const hit = events.find((event) => matches(pattern, event))\n if (hit) failures.push({ path: `none.${i}`, expected: pattern, actual: summarizeAudit(hit) })\n }\n}\n\n/** 실패 항목에 싣는 이벤트 요약 (입력 · 요약문 제외) */\nexport function summarizeAudit(event: AuditEvent): Record<string, unknown> {\n return {\n command: event.command,\n outcome: event.outcome,\n ...(event.error ? { error: event.error.code } : {}),\n surface: event.surface,\n ...(event.actor ? { actor: event.actor } : {}),\n ...(event.client ? { client: event.client.name } : {}),\n ...(event.approval ? { approval: event.approval } : {}),\n at: event.at,\n }\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AuditEvent } from "../audit.ts";
|
|
2
|
+
/**
|
|
3
|
+
* 감사 출처 (specs/inspector.md §6): 서버의 감사 이벤트 (mext.md §11.1) 를 Inspector 로 잇는다.
|
|
4
|
+
* mext dev = 감사 tap 의 endpoint (mext.md §10). 그 외 = 호출 코드가 출처를 만들어 연결한다
|
|
5
|
+
*/
|
|
6
|
+
export interface AuditSource {
|
|
7
|
+
/** 이벤트마다 listener 호출. 반환 = 구독 해제 */
|
|
8
|
+
subscribe(listener: (event: AuditEvent) => void): () => void;
|
|
9
|
+
}
|
|
10
|
+
/** 같은 프로세스의 서버와 잇는 출처: mext.config.ts · startMcpHttpServer 의 audit.sink 에 push 를 넘긴다 */
|
|
11
|
+
export interface AuditChannel extends AuditSource {
|
|
12
|
+
push(event: AuditEvent): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function auditChannel(): AuditChannel;
|
|
15
|
+
export interface DevAuditCredentials {
|
|
16
|
+
/** mext dev 의 MCP 엔드포인트 (lock 의 url) */
|
|
17
|
+
url: string;
|
|
18
|
+
/** lock 의 auditToken */
|
|
19
|
+
token: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DevAuditSourceOptions {
|
|
22
|
+
/** 연결할 때마다 읽는다 (mext dev 를 다시 시작하면 토큰이 바뀐다). undefined = 실행 중인 dev 없음 → 잠시 뒤 다시 */
|
|
23
|
+
credentials: DevAuditCredentials | (() => DevAuditCredentials | undefined);
|
|
24
|
+
fetch?: typeof fetch;
|
|
25
|
+
/** 다시 연결하기 전 대기 (기본 500ms, 실패가 이어지면 5초까지 늘어난다) */
|
|
26
|
+
retryMs?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* mext dev 의 감사 tap endpoint 를 읽는 출처. 구독자가 있는 동안 연결 하나를 유지하고, 끊기면 ?after 로 이어 받는다.
|
|
30
|
+
* 토큰은 Authorization 헤더로만 보낸다 (출력하지 않는다)
|
|
31
|
+
*/
|
|
32
|
+
export declare function devAuditSource(options: DevAuditSourceOptions): AuditSource;
|
|
33
|
+
export type AuditAvailability = {
|
|
34
|
+
available: true;
|
|
35
|
+
source: AuditSource;
|
|
36
|
+
} | {
|
|
37
|
+
available: false;
|
|
38
|
+
reason: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* 대상 서버의 감사 출처. 대상이 이 프로젝트의 mext dev (대상 URL = .mext/dev/lock 의 url) 일 때만 자동 연결.
|
|
42
|
+
* 그 외 = 사용 불가 + 이유 (프로그램 API 로 출처를 직접 연결할 수 있다)
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveAudit(target: {
|
|
45
|
+
url: string;
|
|
46
|
+
}, cwd: string): AuditAvailability;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { DEV_AUDIT_PATH } from "../cli/dev-audit.js";
|
|
2
|
+
import { readDevLock } from "../cli/dev-lock.js";
|
|
3
|
+
export function auditChannel() {
|
|
4
|
+
const listeners = new Set();
|
|
5
|
+
return {
|
|
6
|
+
push(event) {
|
|
7
|
+
for (const listener of listeners)
|
|
8
|
+
listener(event);
|
|
9
|
+
},
|
|
10
|
+
subscribe(listener) {
|
|
11
|
+
listeners.add(listener);
|
|
12
|
+
return () => void listeners.delete(listener);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* mext dev 의 감사 tap endpoint 를 읽는 출처. 구독자가 있는 동안 연결 하나를 유지하고, 끊기면 ?after 로 이어 받는다.
|
|
18
|
+
* 토큰은 Authorization 헤더로만 보낸다 (출력하지 않는다)
|
|
19
|
+
*/
|
|
20
|
+
export function devAuditSource(options) {
|
|
21
|
+
const doFetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
22
|
+
const credentials = () => (typeof options.credentials === "function" ? options.credentials() : options.credentials);
|
|
23
|
+
const listeners = new Set();
|
|
24
|
+
let controller;
|
|
25
|
+
let last = 0;
|
|
26
|
+
let lastToken;
|
|
27
|
+
const pump = async (signal) => {
|
|
28
|
+
let wait = options.retryMs ?? 500;
|
|
29
|
+
while (!signal.aborted) {
|
|
30
|
+
try {
|
|
31
|
+
const current = credentials();
|
|
32
|
+
if (!current)
|
|
33
|
+
throw new Error("mext dev is not running");
|
|
34
|
+
// 새 mext dev 실행 (토큰이 바뀜) = seq 가 1부터 다시
|
|
35
|
+
if (current.token !== lastToken)
|
|
36
|
+
last = 0;
|
|
37
|
+
lastToken = current.token;
|
|
38
|
+
const url = new URL(DEV_AUDIT_PATH, current.url);
|
|
39
|
+
if (last > 0)
|
|
40
|
+
url.searchParams.set("after", String(last));
|
|
41
|
+
const response = await doFetch(url, { headers: { Authorization: `Bearer ${current.token}` }, signal });
|
|
42
|
+
if (!response.ok || !response.body) {
|
|
43
|
+
await response.body?.cancel();
|
|
44
|
+
throw new Error(`HTTP ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
wait = options.retryMs ?? 500;
|
|
47
|
+
const decoder = new TextDecoder();
|
|
48
|
+
let buffer = "";
|
|
49
|
+
for await (const chunk of response.body) {
|
|
50
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
51
|
+
let i;
|
|
52
|
+
while ((i = buffer.indexOf("\n")) >= 0) {
|
|
53
|
+
const text = buffer.slice(0, i);
|
|
54
|
+
buffer = buffer.slice(i + 1);
|
|
55
|
+
if (text === "")
|
|
56
|
+
continue;
|
|
57
|
+
const line = JSON.parse(text);
|
|
58
|
+
if (typeof line.seq === "number")
|
|
59
|
+
last = line.seq;
|
|
60
|
+
for (const listener of listeners)
|
|
61
|
+
listener(line.event);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// 연결 실패 · 끊김 = 잠시 뒤 다시
|
|
67
|
+
}
|
|
68
|
+
if (signal.aborted)
|
|
69
|
+
return;
|
|
70
|
+
await new Promise((resolve) => {
|
|
71
|
+
const timer = setTimeout(resolve, wait);
|
|
72
|
+
signal.addEventListener("abort", () => (clearTimeout(timer), resolve()), { once: true });
|
|
73
|
+
});
|
|
74
|
+
wait = Math.min(wait * 2, 5_000);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
subscribe(listener) {
|
|
79
|
+
listeners.add(listener);
|
|
80
|
+
if (!controller) {
|
|
81
|
+
controller = new AbortController();
|
|
82
|
+
void pump(controller.signal);
|
|
83
|
+
}
|
|
84
|
+
return () => {
|
|
85
|
+
listeners.delete(listener);
|
|
86
|
+
if (listeners.size === 0) {
|
|
87
|
+
controller?.abort();
|
|
88
|
+
controller = undefined;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 대상 서버의 감사 출처. 대상이 이 프로젝트의 mext dev (대상 URL = .mext/dev/lock 의 url) 일 때만 자동 연결.
|
|
96
|
+
* 그 외 = 사용 불가 + 이유 (프로그램 API 로 출처를 직접 연결할 수 있다)
|
|
97
|
+
*/
|
|
98
|
+
export function resolveAudit(target, cwd) {
|
|
99
|
+
const lock = readDevLock(cwd);
|
|
100
|
+
if (!lock?.url || !sameEndpoint(lock.url, target.url)) {
|
|
101
|
+
return {
|
|
102
|
+
available: false,
|
|
103
|
+
reason: "Audit events are only streamed from the mext dev server of this project. For other servers (remote, mext start), " +
|
|
104
|
+
'connect an audit source in code: createInspector({ audit }) from "@obov/mext/inspect".',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (lock.auditToken === undefined) {
|
|
108
|
+
return { available: false, reason: "The running mext dev does not expose audit events (started by an older mext). Restart mext dev." };
|
|
109
|
+
}
|
|
110
|
+
const url = lock.url;
|
|
111
|
+
return {
|
|
112
|
+
available: true,
|
|
113
|
+
source: devAuditSource({
|
|
114
|
+
credentials: () => {
|
|
115
|
+
const now = readDevLock(cwd);
|
|
116
|
+
return now?.url && now.auditToken !== undefined && sameEndpoint(now.url, url) ? { url: now.url, token: now.auditToken } : undefined;
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const LOOPBACK = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
122
|
+
function sameEndpoint(a, b) {
|
|
123
|
+
try {
|
|
124
|
+
const x = new URL(a);
|
|
125
|
+
const y = new URL(b);
|
|
126
|
+
const host = (u) => (LOOPBACK.has(u.hostname) ? "loopback" : u.hostname);
|
|
127
|
+
return x.protocol === y.protocol && host(x) === host(y) && x.port === y.port && x.pathname === y.pathname;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/inspect/audit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuditEvent } from \"../audit.ts\"\nimport { DEV_AUDIT_PATH, type DevAuditLine } from \"../cli/dev-audit.ts\"\nimport { readDevLock } from \"../cli/dev-lock.ts\"\n\n/**\n * 감사 출처 (specs/inspector.md §6): 서버의 감사 이벤트 (mext.md §11.1) 를 Inspector 로 잇는다.\n * mext dev = 감사 tap 의 endpoint (mext.md §10). 그 외 = 호출 코드가 출처를 만들어 연결한다\n */\n\nexport interface AuditSource {\n /** 이벤트마다 listener 호출. 반환 = 구독 해제 */\n subscribe(listener: (event: AuditEvent) => void): () => void\n}\n\n/** 같은 프로세스의 서버와 잇는 출처: mext.config.ts · startMcpHttpServer 의 audit.sink 에 push 를 넘긴다 */\nexport interface AuditChannel extends AuditSource {\n push(event: AuditEvent): void\n}\n\nexport function auditChannel(): AuditChannel {\n const listeners = new Set<(event: AuditEvent) => void>()\n return {\n push(event) {\n for (const listener of listeners) listener(event)\n },\n subscribe(listener) {\n listeners.add(listener)\n return () => void listeners.delete(listener)\n },\n }\n}\n\nexport interface DevAuditCredentials {\n /** mext dev 의 MCP 엔드포인트 (lock 의 url) */\n url: string\n /** lock 의 auditToken */\n token: string\n}\n\nexport interface DevAuditSourceOptions {\n /** 연결할 때마다 읽는다 (mext dev 를 다시 시작하면 토큰이 바뀐다). undefined = 실행 중인 dev 없음 → 잠시 뒤 다시 */\n credentials: DevAuditCredentials | (() => DevAuditCredentials | undefined)\n fetch?: typeof fetch\n /** 다시 연결하기 전 대기 (기본 500ms, 실패가 이어지면 5초까지 늘어난다) */\n retryMs?: number\n}\n\n/**\n * mext dev 의 감사 tap endpoint 를 읽는 출처. 구독자가 있는 동안 연결 하나를 유지하고, 끊기면 ?after 로 이어 받는다.\n * 토큰은 Authorization 헤더로만 보낸다 (출력하지 않는다)\n */\nexport function devAuditSource(options: DevAuditSourceOptions): AuditSource {\n const doFetch: typeof fetch = options.fetch ?? ((input, init) => fetch(input, init))\n const credentials = (): DevAuditCredentials | undefined => (typeof options.credentials === \"function\" ? options.credentials() : options.credentials)\n const listeners = new Set<(event: AuditEvent) => void>()\n let controller: AbortController | undefined\n let last = 0\n let lastToken: string | undefined\n\n const pump = async (signal: AbortSignal): Promise<void> => {\n let wait = options.retryMs ?? 500\n while (!signal.aborted) {\n try {\n const current = credentials()\n if (!current) throw new Error(\"mext dev is not running\")\n // 새 mext dev 실행 (토큰이 바뀜) = seq 가 1부터 다시\n if (current.token !== lastToken) last = 0\n lastToken = current.token\n const url = new URL(DEV_AUDIT_PATH, current.url)\n if (last > 0) url.searchParams.set(\"after\", String(last))\n const response = await doFetch(url, { headers: { Authorization: `Bearer ${current.token}` }, signal })\n if (!response.ok || !response.body) {\n await response.body?.cancel()\n throw new Error(`HTTP ${response.status}`)\n }\n wait = options.retryMs ?? 500\n const decoder = new TextDecoder()\n let buffer = \"\"\n for await (const chunk of response.body as unknown as AsyncIterable<Uint8Array>) {\n buffer += decoder.decode(chunk, { stream: true })\n let i: number\n while ((i = buffer.indexOf(\"\\n\")) >= 0) {\n const text = buffer.slice(0, i)\n buffer = buffer.slice(i + 1)\n if (text === \"\") continue\n const line = JSON.parse(text) as DevAuditLine\n if (typeof line.seq === \"number\") last = line.seq\n for (const listener of listeners) listener(line.event)\n }\n }\n } catch {\n // 연결 실패 · 끊김 = 잠시 뒤 다시\n }\n if (signal.aborted) return\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, wait)\n signal.addEventListener(\"abort\", () => (clearTimeout(timer), resolve()), { once: true })\n })\n wait = Math.min(wait * 2, 5_000)\n }\n }\n\n return {\n subscribe(listener) {\n listeners.add(listener)\n if (!controller) {\n controller = new AbortController()\n void pump(controller.signal)\n }\n return () => {\n listeners.delete(listener)\n if (listeners.size === 0) {\n controller?.abort()\n controller = undefined\n }\n }\n },\n }\n}\n\nexport type AuditAvailability = { available: true; source: AuditSource } | { available: false; reason: string }\n\n/**\n * 대상 서버의 감사 출처. 대상이 이 프로젝트의 mext dev (대상 URL = .mext/dev/lock 의 url) 일 때만 자동 연결.\n * 그 외 = 사용 불가 + 이유 (프로그램 API 로 출처를 직접 연결할 수 있다)\n */\nexport function resolveAudit(target: { url: string }, cwd: string): AuditAvailability {\n const lock = readDevLock(cwd)\n if (!lock?.url || !sameEndpoint(lock.url, target.url)) {\n return {\n available: false,\n reason:\n \"Audit events are only streamed from the mext dev server of this project. For other servers (remote, mext start), \" +\n 'connect an audit source in code: createInspector({ audit }) from \"@obov/mext/inspect\".',\n }\n }\n if (lock.auditToken === undefined) {\n return { available: false, reason: \"The running mext dev does not expose audit events (started by an older mext). Restart mext dev.\" }\n }\n const url = lock.url\n return {\n available: true,\n source: devAuditSource({\n credentials: () => {\n const now = readDevLock(cwd)\n return now?.url && now.auditToken !== undefined && sameEndpoint(now.url, url) ? { url: now.url, token: now.auditToken } : undefined\n },\n }),\n }\n}\n\nconst LOOPBACK = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"])\n\nfunction sameEndpoint(a: string, b: string): boolean {\n try {\n const x = new URL(a)\n const y = new URL(b)\n const host = (u: URL): string => (LOOPBACK.has(u.hostname) ? \"loopback\" : u.hostname)\n return x.protocol === y.protocol && host(x) === host(y) && x.port === y.port && x.pathname === y.pathname\n } catch {\n return false\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** 기대와 실제의 일부 비교 (specs/inspector.md §4 expect). 시나리오 · 에이전트 단계가 같이 쓴다 */
|
|
2
|
+
export interface Failure {
|
|
3
|
+
/** 다른 곳 (ok · value.status · error.code · commands.hidden · input · save.<변수> · audit.<n> · none.<n> · agent) */
|
|
4
|
+
path: string;
|
|
5
|
+
expected: unknown;
|
|
6
|
+
actual: unknown;
|
|
7
|
+
}
|
|
8
|
+
/** 일부 비교: 객체 = 적은 키만 (재귀) · 배열 = 길이와 각 요소 · 그 외 = 같은 값 */
|
|
9
|
+
export declare function partial(expected: unknown, actual: unknown, path: string, failures: Failure[]): void;
|
|
10
|
+
/** 일부 비교가 통과하는가 */
|
|
11
|
+
export declare function matches(expected: unknown, actual: unknown): boolean;
|
|
12
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** 기대와 실제의 일부 비교 (specs/inspector.md §4 expect). 시나리오 · 에이전트 단계가 같이 쓴다 */
|
|
2
|
+
/** 일부 비교: 객체 = 적은 키만 (재귀) · 배열 = 길이와 각 요소 · 그 외 = 같은 값 */
|
|
3
|
+
export function partial(expected, actual, path, failures) {
|
|
4
|
+
if (Array.isArray(expected)) {
|
|
5
|
+
if (!Array.isArray(actual))
|
|
6
|
+
return void failures.push({ path, expected, actual });
|
|
7
|
+
if (actual.length !== expected.length)
|
|
8
|
+
return void failures.push({ path: `${path}.length`, expected: expected.length, actual: actual.length });
|
|
9
|
+
expected.forEach((item, i) => partial(item, actual[i], `${path}.${i}`, failures));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (isRecord(expected)) {
|
|
13
|
+
if (!isRecord(actual))
|
|
14
|
+
return void failures.push({ path, expected, actual });
|
|
15
|
+
for (const [key, value] of Object.entries(expected))
|
|
16
|
+
partial(value, actual[key], `${path}.${key}`, failures);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!Object.is(expected, actual))
|
|
20
|
+
failures.push({ path, expected, actual });
|
|
21
|
+
}
|
|
22
|
+
/** 일부 비교가 통과하는가 */
|
|
23
|
+
export function matches(expected, actual) {
|
|
24
|
+
const failures = [];
|
|
25
|
+
partial(expected, actual, "", failures);
|
|
26
|
+
return failures.length === 0;
|
|
27
|
+
}
|
|
28
|
+
export function isRecord(value) {
|
|
29
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/inspect/compare.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAS1E,0DAA0D;AAC1D,MAAM,UAAU,OAAO,CAAC,QAAiB,EAAE,MAAe,EAAE,IAAY,EAAE,QAAmB;IAC3F,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QACjF,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM;YAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;QAC9I,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAA;QACjF,OAAM;IACR,CAAC;IACD,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QAC5E,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;YAAE,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,QAAQ,CAAC,CAAA;QAC5G,OAAM;IACR,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;AAC7E,CAAC;AAED,mBAAmB;AACnB,MAAM,UAAU,OAAO,CAAC,QAAiB,EAAE,MAAe;IACxD,MAAM,QAAQ,GAAc,EAAE,CAAA;IAC9B,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAA;IACvC,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC","sourcesContent":["/** 기대와 실제의 일부 비교 (specs/inspector.md §4 expect). 시나리오 · 에이전트 단계가 같이 쓴다 */\n\nexport interface Failure {\n /** 다른 곳 (ok · value.status · error.code · commands.hidden · input · save.<변수> · audit.<n> · none.<n> · agent) */\n path: string\n expected: unknown\n actual: unknown\n}\n\n/** 일부 비교: 객체 = 적은 키만 (재귀) · 배열 = 길이와 각 요소 · 그 외 = 같은 값 */\nexport function partial(expected: unknown, actual: unknown, path: string, failures: Failure[]): void {\n if (Array.isArray(expected)) {\n if (!Array.isArray(actual)) return void failures.push({ path, expected, actual })\n if (actual.length !== expected.length) return void failures.push({ path: `${path}.length`, expected: expected.length, actual: actual.length })\n expected.forEach((item, i) => partial(item, actual[i], `${path}.${i}`, failures))\n return\n }\n if (isRecord(expected)) {\n if (!isRecord(actual)) return void failures.push({ path, expected, actual })\n for (const [key, value] of Object.entries(expected)) partial(value, actual[key], `${path}.${key}`, failures)\n return\n }\n if (!Object.is(expected, actual)) failures.push({ path, expected, actual })\n}\n\n/** 일부 비교가 통과하는가 */\nexport function matches(expected: unknown, actual: unknown): boolean {\n const failures: Failure[] = []\n partial(expected, actual, \"\", failures)\n return failures.length === 0\n}\n\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @obov/mext/inspect — inspector 의 프로그램 API (specs/inspector.md). Node.js · Bun 전용 (파일 · 환경변수를 읽는다).
|
|
3
|
+
* mext inspect CLI 가 쓰는 것과 같은 객체: 호출자 프로필 · Inspector (프로필별 호출 · 호출 기록) · 시나리오.
|
|
4
|
+
* @obov/mext (.) 와 mext start · 고객 CLI 는 이 entry 를 import 하지 않는다
|
|
5
|
+
*/
|
|
6
|
+
export { AGENT_SETTLE_MS, AGENT_TIMEOUT_MS, checkAudit, connectAgent, instructAgent, isAgentClient, summarizeAudit } from "./agent.ts";
|
|
7
|
+
export type { AgentClient, AgentEvent, AgentExpect, AgentMessage, AgentObservation, AgentPlugin, AgentPluginContext, AgentToolCall, InstructOptions, } from "./agent.ts";
|
|
8
|
+
export { auditChannel, devAuditSource, resolveAudit } from "./audit.ts";
|
|
9
|
+
export type { AuditAvailability, AuditChannel, AuditSource, DevAuditCredentials, DevAuditSourceOptions } from "./audit.ts";
|
|
10
|
+
export { startUiServer, UI_DEFAULT_PORT, UI_TOKEN_HEADER } from "./ui-server.ts";
|
|
11
|
+
export type { UiServer, UiServerOptions } from "./ui-server.ts";
|
|
12
|
+
export { createInspector, InspectUsageError, outcomeOf, redactValue, resolveTarget } from "./inspector.ts";
|
|
13
|
+
export type { Answers, CallOutcome, CallRecord, ErrorBody, Exchange, InputRecord, InspectCallOptions, Inspector, InspectorEvent, InspectorOptions, InspectTarget, } from "./inspector.ts";
|
|
14
|
+
export { DEFAULT_PROFILE, DEFAULT_TOKEN_ENV, describeProfiles, findProfile, INSPECT_DIR, loadProfiles, parseProfiles, ProfileFileError, PROFILES_FILE, profilesPath, ProfileUnavailableError, redact, REDACTED, resolveProfile, } from "./profiles.ts";
|
|
15
|
+
export type { EnvRef, Profile, ProfileSet, ProfileView, ResolvedProfile } from "./profiles.ts";
|
|
16
|
+
export { appendStep, compare, loadScenario, parseScenario, runScenario, ScenarioFileError, scenarioFiles, SCENARIOS_DIR, stepFromRecord, } from "./scenario.ts";
|
|
17
|
+
export type { AgentStep, CallStep, CommandsStep, Expect, Failure, RunScenarioOptions, Scenario, ScenarioResult, ScenarioStep, StepResult, } from "./scenario.ts";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @obov/mext/inspect — inspector 의 프로그램 API (specs/inspector.md). Node.js · Bun 전용 (파일 · 환경변수를 읽는다).
|
|
3
|
+
* mext inspect CLI 가 쓰는 것과 같은 객체: 호출자 프로필 · Inspector (프로필별 호출 · 호출 기록) · 시나리오.
|
|
4
|
+
* @obov/mext (.) 와 mext start · 고객 CLI 는 이 entry 를 import 하지 않는다
|
|
5
|
+
*/
|
|
6
|
+
export { AGENT_SETTLE_MS, AGENT_TIMEOUT_MS, checkAudit, connectAgent, instructAgent, isAgentClient, summarizeAudit } from "./agent.js";
|
|
7
|
+
export { auditChannel, devAuditSource, resolveAudit } from "./audit.js";
|
|
8
|
+
export { startUiServer, UI_DEFAULT_PORT, UI_TOKEN_HEADER } from "./ui-server.js";
|
|
9
|
+
export { createInspector, InspectUsageError, outcomeOf, redactValue, resolveTarget } from "./inspector.js";
|
|
10
|
+
export { DEFAULT_PROFILE, DEFAULT_TOKEN_ENV, describeProfiles, findProfile, INSPECT_DIR, loadProfiles, parseProfiles, ProfileFileError, PROFILES_FILE, profilesPath, ProfileUnavailableError, redact, REDACTED, resolveProfile, } from "./profiles.js";
|
|
11
|
+
export { appendStep, compare, loadScenario, parseScenario, runScenario, ScenarioFileError, scenarioFiles, SCENARIOS_DIR, stepFromRecord, } from "./scenario.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/inspect/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAYtI,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEvE,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAEhF,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAc1G,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,MAAM,EACN,QAAQ,EACR,cAAc,GACf,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,UAAU,EACV,OAAO,EACP,YAAY,EACZ,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA","sourcesContent":["/**\n * @obov/mext/inspect — inspector 의 프로그램 API (specs/inspector.md). Node.js · Bun 전용 (파일 · 환경변수를 읽는다).\n * mext inspect CLI 가 쓰는 것과 같은 객체: 호출자 프로필 · Inspector (프로필별 호출 · 호출 기록) · 시나리오.\n * @obov/mext (.) 와 mext start · 고객 CLI 는 이 entry 를 import 하지 않는다\n */\nexport { AGENT_SETTLE_MS, AGENT_TIMEOUT_MS, checkAudit, connectAgent, instructAgent, isAgentClient, summarizeAudit } from \"./agent.ts\"\nexport type {\n AgentClient,\n AgentEvent,\n AgentExpect,\n AgentMessage,\n AgentObservation,\n AgentPlugin,\n AgentPluginContext,\n AgentToolCall,\n InstructOptions,\n} from \"./agent.ts\"\nexport { auditChannel, devAuditSource, resolveAudit } from \"./audit.ts\"\nexport type { AuditAvailability, AuditChannel, AuditSource, DevAuditCredentials, DevAuditSourceOptions } from \"./audit.ts\"\nexport { startUiServer, UI_DEFAULT_PORT, UI_TOKEN_HEADER } from \"./ui-server.ts\"\nexport type { UiServer, UiServerOptions } from \"./ui-server.ts\"\nexport { createInspector, InspectUsageError, outcomeOf, redactValue, resolveTarget } from \"./inspector.ts\"\nexport type {\n Answers,\n CallOutcome,\n CallRecord,\n ErrorBody,\n Exchange,\n InputRecord,\n InspectCallOptions,\n Inspector,\n InspectorEvent,\n InspectorOptions,\n InspectTarget,\n} from \"./inspector.ts\"\nexport {\n DEFAULT_PROFILE,\n DEFAULT_TOKEN_ENV,\n describeProfiles,\n findProfile,\n INSPECT_DIR,\n loadProfiles,\n parseProfiles,\n ProfileFileError,\n PROFILES_FILE,\n profilesPath,\n ProfileUnavailableError,\n redact,\n REDACTED,\n resolveProfile,\n} from \"./profiles.ts\"\nexport type { EnvRef, Profile, ProfileSet, ProfileView, ResolvedProfile } from \"./profiles.ts\"\nexport {\n appendStep,\n compare,\n loadScenario,\n parseScenario,\n runScenario,\n ScenarioFileError,\n scenarioFiles,\n SCENARIOS_DIR,\n stepFromRecord,\n} from \"./scenario.ts\"\nexport type {\n AgentStep,\n CallStep,\n CommandsStep,\n Expect,\n Failure,\n RunScenarioOptions,\n Scenario,\n ScenarioResult,\n ScenarioStep,\n StepResult,\n} from \"./scenario.ts\"\n"]}
|