@n2world/orchestrator 1.1.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/agent-os-rd.d.ts +100 -0
- package/dist/agent-os-rd.js +258 -0
- package/dist/audit-store.d.ts +14 -0
- package/dist/audit-store.js +107 -0
- package/dist/beta-runner.d.ts +95 -0
- package/dist/beta-runner.js +251 -0
- package/dist/beta.d.ts +102 -0
- package/dist/beta.js +180 -0
- package/dist/browser-agent.d.ts +90 -0
- package/dist/browser-agent.js +223 -0
- package/dist/channel-gateway.d.ts +74 -0
- package/dist/channel-gateway.js +270 -0
- package/dist/channels.d.ts +120 -0
- package/dist/channels.js +432 -0
- package/dist/chat-store.d.ts +29 -0
- package/dist/chat-store.js +120 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +607 -0
- package/dist/command-screen.d.ts +12 -0
- package/dist/command-screen.js +44 -0
- package/dist/commit-gate.d.ts +98 -0
- package/dist/commit-gate.js +258 -0
- package/dist/companion-api.d.ts +37 -0
- package/dist/companion-api.js +101 -0
- package/dist/conversation-graph.d.ts +39 -0
- package/dist/conversation-graph.js +92 -0
- package/dist/cost-estimator.d.ts +27 -0
- package/dist/cost-estimator.js +42 -0
- package/dist/cron-runner.d.ts +31 -0
- package/dist/cron-runner.js +46 -0
- package/dist/dashboard/chat.html +326 -0
- package/dist/dashboard/dental.html +58 -0
- package/dist/dashboard/freebie.png +0 -0
- package/dist/dashboard/icon-192.png +0 -0
- package/dist/dashboard/index.html +892 -0
- package/dist/dashboard/manifest.json +15 -0
- package/dist/dashboard/service-worker.js +28 -0
- package/dist/dashboard-server.d.ts +37 -0
- package/dist/dashboard-server.js +457 -0
- package/dist/dental-intake-service.d.ts +37 -0
- package/dist/dental-intake-service.js +61 -0
- package/dist/dental-metrics.d.ts +25 -0
- package/dist/dental-metrics.js +37 -0
- package/dist/docking.d.ts +36 -0
- package/dist/docking.js +73 -0
- package/dist/finance-mcts-candidate.d.ts +37 -0
- package/dist/finance-mcts-candidate.js +106 -0
- package/dist/finance-regulation-kr.d.ts +33 -0
- package/dist/finance-regulation-kr.js +104 -0
- package/dist/finance-workflow.d.ts +135 -0
- package/dist/finance-workflow.js +242 -0
- package/dist/gateway.d.ts +18 -0
- package/dist/gateway.js +123 -0
- package/dist/governance.d.ts +39 -0
- package/dist/governance.js +48 -0
- package/dist/governed-executor.d.ts +31 -0
- package/dist/governed-executor.js +63 -0
- package/dist/governed-llm.d.ts +41 -0
- package/dist/governed-llm.js +83 -0
- package/dist/gpu-bridge.d.ts +16 -0
- package/dist/gpu-bridge.js +53 -0
- package/dist/health.d.ts +47 -0
- package/dist/health.js +66 -0
- package/dist/identity-link.d.ts +32 -0
- package/dist/identity-link.js +98 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +417 -0
- package/dist/integrations/emr-adapter.d.ts +41 -0
- package/dist/integrations/emr-adapter.js +63 -0
- package/dist/kakao-oauth.d.ts +16 -0
- package/dist/kakao-oauth.js +87 -0
- package/dist/knowledge-graph.d.ts +53 -0
- package/dist/knowledge-graph.js +156 -0
- package/dist/llm.d.ts +65 -0
- package/dist/llm.js +357 -0
- package/dist/mcp-client-guard.d.ts +32 -0
- package/dist/mcp-client-guard.js +179 -0
- package/dist/mcp-macaroon.d.ts +75 -0
- package/dist/mcp-macaroon.js +161 -0
- package/dist/mcts-kernel-bridge.d.ts +36 -0
- package/dist/mcts-kernel-bridge.js +99 -0
- package/dist/mcts-prior.d.ts +79 -0
- package/dist/mcts-prior.js +170 -0
- package/dist/model-router.d.ts +51 -0
- package/dist/model-router.js +75 -0
- package/dist/multi-axis-lift.d.ts +43 -0
- package/dist/multi-axis-lift.js +141 -0
- package/dist/net-guard.d.ts +39 -0
- package/dist/net-guard.js +141 -0
- package/dist/onboarding.d.ts +38 -0
- package/dist/onboarding.js +94 -0
- package/dist/oracle-anchored-search.d.ts +25 -0
- package/dist/oracle-anchored-search.js +50 -0
- package/dist/oracle.d.ts +22 -0
- package/dist/oracle.js +116 -0
- package/dist/p6-governance.d.ts +150 -0
- package/dist/p6-governance.js +252 -0
- package/dist/pairing.d.ts +22 -0
- package/dist/pairing.js +81 -0
- package/dist/personalization.d.ts +35 -0
- package/dist/personalization.js +73 -0
- package/dist/pglite-hnsw-bridge.d.ts +118 -0
- package/dist/pglite-hnsw-bridge.js +311 -0
- package/dist/pglite-store.d.ts +59 -0
- package/dist/pglite-store.js +180 -0
- package/dist/playbook.d.ts +79 -0
- package/dist/playbook.js +83 -0
- package/dist/playbooks/dental-intake.d.ts +20 -0
- package/dist/playbooks/dental-intake.js +112 -0
- package/dist/predictive-agent.d.ts +157 -0
- package/dist/predictive-agent.js +535 -0
- package/dist/prompt-optimizer.d.ts +18 -0
- package/dist/prompt-optimizer.js +104 -0
- package/dist/rate-limiter.d.ts +25 -0
- package/dist/rate-limiter.js +75 -0
- package/dist/safety-anneal.d.ts +83 -0
- package/dist/safety-anneal.js +153 -0
- package/dist/sandbox-controller.d.ts +12 -0
- package/dist/sandbox-controller.js +95 -0
- package/dist/satisfaction-metrics.d.ts +26 -0
- package/dist/satisfaction-metrics.js +61 -0
- package/dist/sensor-bridge.d.ts +53 -0
- package/dist/sensor-bridge.js +133 -0
- package/dist/session-repair.d.ts +27 -0
- package/dist/session-repair.js +66 -0
- package/dist/slack-finance-intake.d.ts +42 -0
- package/dist/slack-finance-intake.js +122 -0
- package/dist/symbolic-dynamics.d.ts +113 -0
- package/dist/symbolic-dynamics.js +420 -0
- package/dist/telemetry.d.ts +19 -0
- package/dist/telemetry.js +68 -0
- package/dist/text-embedding.d.ts +6 -0
- package/dist/text-embedding.js +42 -0
- package/dist/tier-classifier.d.ts +20 -0
- package/dist/tier-classifier.js +58 -0
- package/dist/tier-guard.d.ts +36 -0
- package/dist/tier-guard.js +56 -0
- package/dist/tui.d.ts +9 -0
- package/dist/tui.js +214 -0
- package/dist/update-security.d.ts +31 -0
- package/dist/update-security.js +112 -0
- package/dist/v-calibration.d.ts +16 -0
- package/dist/v-calibration.js +42 -0
- package/dist/value-calibration.d.ts +41 -0
- package/dist/value-calibration.js +133 -0
- package/dist/value-head.d.ts +20 -0
- package/dist/value-head.js +91 -0
- package/dist/wal-buffer.d.ts +23 -0
- package/dist/wal-buffer.js +144 -0
- package/dist/wiki-synthesizer.d.ts +80 -0
- package/dist/wiki-synthesizer.js +0 -0
- package/dist/worker-agent.d.ts +10 -0
- package/dist/worker-agent.js +19 -0
- package/package.json +65 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// P2 — 자가연결 지식 그래프 파서 + 관계 평가 (계획서 v2.4 §4.5)
|
|
4
|
+
// ----------------------------------------------------------------------------
|
|
5
|
+
// 축적된 지식(.md) 항목 간 관계를 추출해 그래프로 잇는다. 세 종류의 관계:
|
|
6
|
+
// - reference : 명시적 참조 — `[[id]]` 위키링크 · 마크다운 링크 · 다른 항목 파일명 언급
|
|
7
|
+
// - dependency : 의존 — 의존 큐(먼저 주입/선행/requires/depends…)와 함께 다른 항목 언급
|
|
8
|
+
// - similarity : 유사 — 해시 임베딩 코사인 유사도 ≥ 임계값(상위 K)
|
|
9
|
+
//
|
|
10
|
+
// 정직 고지(구현강도 상한): reference/dependency 는 **명시적 신호(파일명/위키링크/큐 단어)** 에
|
|
11
|
+
// 기반한 규칙 추출이다(LLM 의미 추론 아님). 깔끔히 구조화된 지식(파일명 상호참조)에서 정확하며,
|
|
12
|
+
// 자유 산문 일반으로 일반화하지 않는다. similarity 는 P1 과 동일한 **얕은 해시 임베딩** 기반.
|
|
13
|
+
// 평가는 합성 코퍼스의 동어반복을 막기 위해 **실제 골드셋과 분리 보고**한다(§2.3).
|
|
14
|
+
// ============================================================================
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.parseKnowledgeGraph = parseKnowledgeGraph;
|
|
17
|
+
exports.evaluateRelations = evaluateRelations;
|
|
18
|
+
const text_embedding_1 = require("./text-embedding");
|
|
19
|
+
// 의존 큐(한/영). 라인 단위로 이 단어와 다른 항목 언급이 함께 있으면 dependency.
|
|
20
|
+
const DEPENDENCY_CUE = /(먼저\s*(주입|읽|통독|실행|선행)|선행\s*조건|필수\s*선행|prerequisite|pre-?req|\brequires?\b|\bdepends?\s+on\b|의존(?:한다|성|함)?|에\s*의존|기반(?:으로|한다))/i;
|
|
21
|
+
const WIKILINK = /\[\[([^\]\[]+)\]\]/g;
|
|
22
|
+
const MD_LINK = /\]\(([^)]+)\)/g;
|
|
23
|
+
function escapeRegExp(s) {
|
|
24
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
25
|
+
}
|
|
26
|
+
/** id 후보 해석: 정확 id, 확장자 보정(.md), basename 일치를 시도. 없으면 null. */
|
|
27
|
+
function resolveId(mention, idSet) {
|
|
28
|
+
const m = mention.trim();
|
|
29
|
+
if (idSet.has(m))
|
|
30
|
+
return m;
|
|
31
|
+
if (idSet.has(m + '.md'))
|
|
32
|
+
return m + '.md';
|
|
33
|
+
const base = m.split(/[\\/]/).pop() ?? m;
|
|
34
|
+
if (idSet.has(base))
|
|
35
|
+
return base;
|
|
36
|
+
if (idSet.has(base + '.md'))
|
|
37
|
+
return base + '.md';
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 지식 항목 배열에서 관계 그래프를 추출한다. 자기참조(X→X)는 제외, 같은 (source,target,type)는 1회.
|
|
42
|
+
*/
|
|
43
|
+
function parseKnowledgeGraph(items, opts = {}) {
|
|
44
|
+
const threshold = opts.similarityThreshold ?? 0.5;
|
|
45
|
+
const topK = opts.similarityTopK ?? 3;
|
|
46
|
+
const includeSim = opts.includeSimilarity ?? true;
|
|
47
|
+
const nodes = items.map((it) => it.id);
|
|
48
|
+
const idSet = new Set(nodes);
|
|
49
|
+
// 파일명 언급 탐지를 위해 id 별 정규식(자기 자신 제외는 호출부에서).
|
|
50
|
+
const idPatterns = nodes.map((id) => ({ id, re: new RegExp(escapeRegExp(id), 'i') }));
|
|
51
|
+
const seen = new Set(); // dedupe key: src→tgt|type
|
|
52
|
+
const edges = [];
|
|
53
|
+
const addEdge = (source, target, type, weight) => {
|
|
54
|
+
if (source === target)
|
|
55
|
+
return;
|
|
56
|
+
const key = `${source}->${target}|${type}`;
|
|
57
|
+
if (seen.has(key))
|
|
58
|
+
return;
|
|
59
|
+
seen.add(key);
|
|
60
|
+
edges.push({ source, target, type, weight });
|
|
61
|
+
};
|
|
62
|
+
// ── reference / dependency: 라인 단위 명시 신호 ──
|
|
63
|
+
for (const item of items) {
|
|
64
|
+
const lines = item.content.split(/\r?\n/);
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const isDep = DEPENDENCY_CUE.test(line);
|
|
67
|
+
// 위키링크 [[X]] → reference(큐 있으면 dependency)
|
|
68
|
+
for (const m of line.matchAll(WIKILINK)) {
|
|
69
|
+
const tgt = resolveId(m[1], idSet);
|
|
70
|
+
if (tgt)
|
|
71
|
+
addEdge(item.id, tgt, isDep ? 'dependency' : 'reference', 1);
|
|
72
|
+
}
|
|
73
|
+
// 마크다운 링크 ](X)
|
|
74
|
+
for (const m of line.matchAll(MD_LINK)) {
|
|
75
|
+
const tgt = resolveId(m[1], idSet);
|
|
76
|
+
if (tgt)
|
|
77
|
+
addEdge(item.id, tgt, isDep ? 'dependency' : 'reference', 1);
|
|
78
|
+
}
|
|
79
|
+
// 파일명(=다른 항목 id) 직접 언급
|
|
80
|
+
for (const { id, re } of idPatterns) {
|
|
81
|
+
if (id === item.id)
|
|
82
|
+
continue;
|
|
83
|
+
if (re.test(line))
|
|
84
|
+
addEdge(item.id, id, isDep ? 'dependency' : 'reference', 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ── similarity: 해시 임베딩 코사인 상위 K (무방향, canonical src<tgt) ──
|
|
89
|
+
if (includeSim && items.length > 1) {
|
|
90
|
+
const embs = items.map((it) => (0, text_embedding_1.textEmbedding)(it.content));
|
|
91
|
+
for (let i = 0; i < items.length; i++) {
|
|
92
|
+
const sims = [];
|
|
93
|
+
for (let j = 0; j < items.length; j++) {
|
|
94
|
+
if (i === j)
|
|
95
|
+
continue;
|
|
96
|
+
const sim = (0, text_embedding_1.cosineSimilarity)(embs[i], embs[j]);
|
|
97
|
+
if (sim >= threshold)
|
|
98
|
+
sims.push({ j, sim });
|
|
99
|
+
}
|
|
100
|
+
sims.sort((a, b) => b.sim - a.sim);
|
|
101
|
+
for (const { j, sim } of sims.slice(0, topK)) {
|
|
102
|
+
const a = items[i].id;
|
|
103
|
+
const b = items[j].id;
|
|
104
|
+
const [s, t] = a < b ? [a, b] : [b, a]; // canonical 무방향
|
|
105
|
+
addEdge(s, t, 'similarity', +sim.toFixed(4));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { nodes, edges };
|
|
110
|
+
}
|
|
111
|
+
function ratio(num, den) {
|
|
112
|
+
return den > 0 ? +(num / den).toFixed(4) : null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 예측 엣지 집합을 골드 라벨로 채점. 라벨된 (source,target,type) 쌍만 평가한다
|
|
116
|
+
* (열린세계 페널티 방지 — 라벨 외 엣지는 무시, 라벨된 음성으로 precision 측정).
|
|
117
|
+
* @param type 평가할 관계 종류(생략 시 'all' — 라벨 전체).
|
|
118
|
+
*/
|
|
119
|
+
function evaluateRelations(edges, gold, type) {
|
|
120
|
+
const predicted = new Set(edges.map((e) => `${e.source}->${e.target}|${e.type}`));
|
|
121
|
+
const labels = type ? gold.filter((g) => g.type === type) : gold;
|
|
122
|
+
let tp = 0, fp = 0, fn = 0, tn = 0, positives = 0;
|
|
123
|
+
for (const g of labels) {
|
|
124
|
+
const isPred = predicted.has(`${g.source}->${g.target}|${g.type}`);
|
|
125
|
+
if (g.present) {
|
|
126
|
+
positives++;
|
|
127
|
+
if (isPred)
|
|
128
|
+
tp++;
|
|
129
|
+
else
|
|
130
|
+
fn++;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
if (isPred)
|
|
134
|
+
fp++;
|
|
135
|
+
else
|
|
136
|
+
tn++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const precision = ratio(tp, tp + fp);
|
|
140
|
+
const recall = ratio(tp, tp + fn);
|
|
141
|
+
const f1 = precision !== null && recall !== null && precision + recall > 0
|
|
142
|
+
? +((2 * precision * recall) / (precision + recall)).toFixed(4)
|
|
143
|
+
: null;
|
|
144
|
+
return {
|
|
145
|
+
type: type ?? 'all',
|
|
146
|
+
labeled: labels.length,
|
|
147
|
+
positives,
|
|
148
|
+
tp,
|
|
149
|
+
fp,
|
|
150
|
+
fn,
|
|
151
|
+
tn,
|
|
152
|
+
precision,
|
|
153
|
+
recall,
|
|
154
|
+
f1,
|
|
155
|
+
};
|
|
156
|
+
}
|
package/dist/llm.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
type ProviderId = 'anthropic' | 'google';
|
|
2
|
+
export declare function askAgent(userInput: string, runShellCommand: (cmd: string) => Promise<string>): Promise<string>;
|
|
3
|
+
/** 대화 메시지 한 턴(역할 + 텍스트). askAgentChat 가 히스토리로 유지한다. */
|
|
4
|
+
export interface ChatTurn {
|
|
5
|
+
role: 'user' | 'assistant';
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `askAgent` 의 **멀티턴(대화)** 버전. 호출자가 전체 히스토리를 넘기면 그 맥락 위에서 답한다.
|
|
10
|
+
* 도구(executeTerminalCommand)는 동일하게 제공된다(샌드박스 실행). 단발 askAgent 와 달리
|
|
11
|
+
* 이전 대화를 기억하므로 CLI `chat` 같은 연속 대화에 쓴다. 키 미설정 시 안내 문자열을 반환.
|
|
12
|
+
*/
|
|
13
|
+
/** 선택적 브라우저 도구 브리지(BrowserAgent.act 형태). 주어지면 controlBrowser 도구가 추가된다. */
|
|
14
|
+
export type BrowserBridge = (action: {
|
|
15
|
+
verb: string;
|
|
16
|
+
url?: string;
|
|
17
|
+
ref?: string;
|
|
18
|
+
text?: string;
|
|
19
|
+
query?: string;
|
|
20
|
+
}) => Promise<string>;
|
|
21
|
+
export declare function askAgentChat(history: ChatTurn[], runShellCommand: (cmd: string) => Promise<string>, opts?: {
|
|
22
|
+
browser?: BrowserBridge;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* 도구 없는 단발 텍스트 생성(구조화 후보 생성 등에 사용). 제공자 추상화 위에서 동작.
|
|
26
|
+
* 키 미설정 시 명확히 throw 한다.
|
|
27
|
+
*/
|
|
28
|
+
export declare function llmComplete(prompt: string, system?: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* `llmComplete` 와 동일 경로지만 **제공자 실 토큰 usage** 도 반환한다(P3 토큰 절감 실측용).
|
|
31
|
+
* 새 제공자 경로를 만들지 않고 기존 generateText 경로를 재사용한다(단순화). 키 미설정 시 throw.
|
|
32
|
+
*/
|
|
33
|
+
export declare function llmCompleteUsage(prompt: string, system?: string): Promise<{
|
|
34
|
+
text: string;
|
|
35
|
+
usage: {
|
|
36
|
+
inputTokens: number | null;
|
|
37
|
+
outputTokens: number | null;
|
|
38
|
+
} | null;
|
|
39
|
+
provider: ProviderId;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* 비전 그라운딩(#2 폴백) — 스크린샷에서 설명에 해당하는 요소의 정규화 좌표(0~1)를 찾는다.
|
|
43
|
+
* 멀티모달 모델(Gemini/Claude) 사용. 못 찾거나 키 없으면 null. 비용이 크므로 DOM(#3) 실패 시만.
|
|
44
|
+
*/
|
|
45
|
+
export declare function locateOnScreen(screenshotBase64: string, description: string): Promise<{
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
} | null>;
|
|
49
|
+
/** 현재 활성 제공자/모델 정보 (대시보드·진단용). */
|
|
50
|
+
export declare function activeLlmInfo(): {
|
|
51
|
+
provider: ProviderId | null;
|
|
52
|
+
model: string | null;
|
|
53
|
+
};
|
|
54
|
+
export type LlmFetch = (url: string, init: any) => Promise<{
|
|
55
|
+
ok: boolean;
|
|
56
|
+
status: number;
|
|
57
|
+
json: () => Promise<any>;
|
|
58
|
+
}>;
|
|
59
|
+
/** 로컬(Ollama) 모델이 설정되었는가. OLLAMA_MODEL 명시 시에만 true(정직). */
|
|
60
|
+
export declare function localLlmConfigured(): boolean;
|
|
61
|
+
/** 로컬(Ollama) 멀티턴 호출. 미설정/미가동 시 명확히 throw(가짜 성공 금지). */
|
|
62
|
+
export declare function localChat(history: ChatTurn[], system?: string, fetchFn?: LlmFetch): Promise<string>;
|
|
63
|
+
/** 외부 제공자 식별(없으면 null). Tier 라우팅에서 외부 가용성 판단에 사용. */
|
|
64
|
+
export declare function externalProviderId(): ProviderId | null;
|
|
65
|
+
export {};
|
package/dist/llm.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.askAgent = askAgent;
|
|
37
|
+
exports.askAgentChat = askAgentChat;
|
|
38
|
+
exports.llmComplete = llmComplete;
|
|
39
|
+
exports.llmCompleteUsage = llmCompleteUsage;
|
|
40
|
+
exports.locateOnScreen = locateOnScreen;
|
|
41
|
+
exports.activeLlmInfo = activeLlmInfo;
|
|
42
|
+
exports.localLlmConfigured = localLlmConfigured;
|
|
43
|
+
exports.localChat = localChat;
|
|
44
|
+
exports.externalProviderId = externalProviderId;
|
|
45
|
+
const ai_1 = require("ai");
|
|
46
|
+
const zod_1 = require("zod");
|
|
47
|
+
const dotenv = __importStar(require("dotenv"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
// Load env variables from multiple potential locations
|
|
50
|
+
dotenv.config();
|
|
51
|
+
dotenv.config({ path: path.join(__dirname, '..', '.env') });
|
|
52
|
+
dotenv.config({ path: path.join(__dirname, '..', '..', '..', '.env') });
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// 제공자 추상화 (Provider Abstraction)
|
|
55
|
+
// ----------------------------------------------------------------------------
|
|
56
|
+
// llm.ts 는 더 이상 특정 제공자(@ai-sdk/google)에 직접 종속되지 않는다.
|
|
57
|
+
// LLM_PROVIDER 환경변수(anthropic|google) 또는 존재하는 키로 자동 선택하며,
|
|
58
|
+
// Vercel AI SDK 의 제공자-중립 generateText() 인터페이스 위에서 동작한다.
|
|
59
|
+
// 제공자 패키지는 "동적 import" 하므로, 한쪽만 설치돼 있어도 빌드가 깨지지 않는다.
|
|
60
|
+
// ============================================================================
|
|
61
|
+
const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY;
|
|
62
|
+
const GEMINI_KEY = process.env.GEMINI_API_KEY;
|
|
63
|
+
const AGENT_NAME = process.env.AGENT_NAME || 'Ethan';
|
|
64
|
+
function isUsable(key) {
|
|
65
|
+
return !!key && key.trim() !== '' && !key.startsWith('YOUR_');
|
|
66
|
+
}
|
|
67
|
+
/** LLM_PROVIDER 우선, 없으면 사용 가능한 키 기준으로 제공자 결정. */
|
|
68
|
+
function resolveProvider() {
|
|
69
|
+
const explicit = (process.env.LLM_PROVIDER || '').toLowerCase();
|
|
70
|
+
if (explicit === 'anthropic' && isUsable(ANTHROPIC_KEY))
|
|
71
|
+
return 'anthropic';
|
|
72
|
+
if (explicit === 'google' && isUsable(GEMINI_KEY))
|
|
73
|
+
return 'google';
|
|
74
|
+
// 명시값이 없거나 해당 키가 없으면 가용한 것으로 폴백 (Anthropic 우선)
|
|
75
|
+
if (isUsable(ANTHROPIC_KEY))
|
|
76
|
+
return 'anthropic';
|
|
77
|
+
if (isUsable(GEMINI_KEY))
|
|
78
|
+
return 'google';
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
/** 선택된 제공자의 모델 객체를 동적 로드해 반환. */
|
|
82
|
+
async function resolveModel(provider) {
|
|
83
|
+
// 간접 지정자(변수)로 동적 import → 미설치 제공자 패키지가 있어도 tsc 빌드가 깨지지 않는다.
|
|
84
|
+
if (provider === 'anthropic') {
|
|
85
|
+
const spec = '@ai-sdk/anthropic';
|
|
86
|
+
const { createAnthropic } = (await import(spec));
|
|
87
|
+
const anthropic = createAnthropic({ apiKey: ANTHROPIC_KEY });
|
|
88
|
+
return anthropic(process.env.ANTHROPIC_MODEL || 'claude-opus-4-8');
|
|
89
|
+
}
|
|
90
|
+
const spec = '@ai-sdk/google';
|
|
91
|
+
const { createGoogleGenerativeAI } = (await import(spec));
|
|
92
|
+
const google = createGoogleGenerativeAI({ apiKey: GEMINI_KEY });
|
|
93
|
+
return google(process.env.GEMINI_MODEL || 'gemini-flash-latest');
|
|
94
|
+
}
|
|
95
|
+
function systemPrompt() {
|
|
96
|
+
return `당신은 n2world (AnyWorld) 에이전트의 인공지능 두뇌인 '${AGENT_NAME}'입니다.
|
|
97
|
+
사용자가 한글로 자연어 지시를 내리면, 필요에 따라 제공된 도구(executeTerminalCommand)를 사용하여 샌드박스 내부 터미널 명령어를 실행하고 결과를 분석하여 임무를 완수해야 합니다.
|
|
98
|
+
중요: executeTerminalCommand 도구를 호출할 때는 반드시 'command' 인자 값에 실행할 터미널 명령어를 구체적인 문자열(예: 'dir', 'n2world bench' 등)로 지정해야 합니다. 절대 빈 문자열이나 빈 객체로 호출하지 마십시오.
|
|
99
|
+
마지막에 수행한 작업의 결과를 깔끔한 한글 요약본으로 보고해 주세요.`;
|
|
100
|
+
}
|
|
101
|
+
/** 도구 호출 멀티스텝 상한. 행동형 과제(파일·디렉터리 생성 등)가 최종 요약까지 끝낼 여유를 준다. */
|
|
102
|
+
const MAX_STEPS = 12;
|
|
103
|
+
/**
|
|
104
|
+
* generateText 결과에서 **항상 비어 있지 않은** 최종 텍스트를 만든다.
|
|
105
|
+
* 빈 응답 원인(제1계명 정직 진단): 모델이 도구 호출로 단계를 소진하고 마지막 단계가 도구 호출이면
|
|
106
|
+
* `result.text` 가 ''(빈 문자열)가 된다(finishReason='tool-calls'). 아래 3단으로 방어한다:
|
|
107
|
+
* 1) 단계들에서 마지막 비지 않은 텍스트 회수
|
|
108
|
+
* 2) 도구 없이 "작업 요약" 한 번 더 생성(도구 메시지 맥락 전달)
|
|
109
|
+
* 3) 그래도 비면 사용자에게 다음 행동을 안내하는 비-빈 폴백(빈 말풍선 금지)
|
|
110
|
+
*/
|
|
111
|
+
async function finalizeText(result, model) {
|
|
112
|
+
if (result?.text && result.text.trim())
|
|
113
|
+
return result.text;
|
|
114
|
+
const steps = result?.steps ?? [];
|
|
115
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
116
|
+
if (steps[i]?.text && String(steps[i].text).trim())
|
|
117
|
+
return steps[i].text;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const prior = result?.response?.messages ?? [];
|
|
121
|
+
const followup = await (0, ai_1.generateText)({
|
|
122
|
+
model,
|
|
123
|
+
messages: [
|
|
124
|
+
...prior,
|
|
125
|
+
{ role: 'user', content: '방금 수행한 작업의 결과를 한국어로 간결히 요약해 줘. 도구는 더 호출하지 말고 텍스트로만 답해.' },
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
if (followup?.text && followup.text.trim())
|
|
129
|
+
return followup.text;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
/* 요약 생성 실패 → 폴백 */
|
|
133
|
+
}
|
|
134
|
+
return '여러 단계로 작업을 수행했지만 최종 요약 생성에 실패했습니다(단계 한도/도구 호출 종료). "방금 한 작업 요약해줘" 또는 "계속"이라고 입력하시면 이어서 정리하겠습니다.';
|
|
135
|
+
}
|
|
136
|
+
async function askAgent(userInput, runShellCommand) {
|
|
137
|
+
const provider = resolveProvider();
|
|
138
|
+
if (!provider) {
|
|
139
|
+
return `[알림] LLM API 키가 설정되지 않았습니다.
|
|
140
|
+
프로젝트 루트의 \`.env\` 파일에 \`ANTHROPIC_API_KEY\`(Claude) 또는 \`GEMINI_API_KEY\`(Gemini) 중 하나 이상을 입력해 주세요.
|
|
141
|
+
(\`.env.example\` 을 복사해 사용하세요.)
|
|
142
|
+
|
|
143
|
+
* 현재 입력된 명령어: "${userInput}"`;
|
|
144
|
+
}
|
|
145
|
+
let model;
|
|
146
|
+
try {
|
|
147
|
+
model = await resolveModel(provider);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return `[에러] '${provider}' 제공자 패키지를 불러오지 못했습니다. 'npm install' 로 의존성을 설치했는지 확인하세요: ${err.message}`;
|
|
151
|
+
}
|
|
152
|
+
const messages = [{ role: 'user', content: userInput }];
|
|
153
|
+
try {
|
|
154
|
+
const result = await (0, ai_1.generateText)({
|
|
155
|
+
model,
|
|
156
|
+
system: systemPrompt(),
|
|
157
|
+
messages,
|
|
158
|
+
tools: {
|
|
159
|
+
executeTerminalCommand: {
|
|
160
|
+
description: '노트북의 격리된 보안 샌드박스 내부에서 쉘 명령어를 실행합니다.',
|
|
161
|
+
// AI SDK v6: 스키마 키는 inputSchema(과거 parameters). 잘못된 키면 스키마가 모델에
|
|
162
|
+
// 전달되지 않아 command 가 undefined 로 호출된다(빈 응답·단계 낭비의 원인이었음).
|
|
163
|
+
inputSchema: zod_1.z.object({
|
|
164
|
+
command: zod_1.z.string().describe('실행할 터미널 명령어 (예: dir, n2world bench, node -v 등)'),
|
|
165
|
+
}),
|
|
166
|
+
execute: async (args) => {
|
|
167
|
+
console.log(`[${AGENT_NAME} Tool Call] Received args:`, JSON.stringify(args));
|
|
168
|
+
let command = args?.command;
|
|
169
|
+
if (!command || command.trim() === '') {
|
|
170
|
+
command = 'dir';
|
|
171
|
+
}
|
|
172
|
+
console.log(`[${AGENT_NAME} Tool Call] Command: ${command}`);
|
|
173
|
+
return await runShellCommand(command);
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
stopWhen: (0, ai_1.stepCountIs)(MAX_STEPS),
|
|
178
|
+
});
|
|
179
|
+
return await finalizeText(result, model);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
return `[에러] ${provider} LLM API 호출 중 오류가 발생했습니다: ${err.message}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function askAgentChat(history, runShellCommand, opts = {}) {
|
|
186
|
+
const provider = resolveProvider();
|
|
187
|
+
if (!provider) {
|
|
188
|
+
return `[알림] LLM API 키가 설정되지 않았습니다. 프로젝트 루트 \`.env\` 에 ANTHROPIC_API_KEY 또는 GEMINI_API_KEY 를 입력하세요(.env.example 참고).`;
|
|
189
|
+
}
|
|
190
|
+
let model;
|
|
191
|
+
try {
|
|
192
|
+
model = await resolveModel(provider);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
return `[에러] '${provider}' 제공자 패키지를 불러오지 못했습니다('npm install' 확인): ${err.message}`;
|
|
196
|
+
}
|
|
197
|
+
const tools = {
|
|
198
|
+
executeTerminalCommand: {
|
|
199
|
+
description: '노트북의 격리된 보안 샌드박스 내부에서 쉘 명령어를 실행합니다.',
|
|
200
|
+
// AI SDK v6: inputSchema(과거 parameters). 잘못된 키면 command 가 undefined 로 옴.
|
|
201
|
+
inputSchema: zod_1.z.object({
|
|
202
|
+
command: zod_1.z.string().describe('실행할 터미널 명령어 (예: dir, node -v 등)'),
|
|
203
|
+
}),
|
|
204
|
+
execute: async (args) => {
|
|
205
|
+
let command = args?.command;
|
|
206
|
+
if (!command || command.trim() === '')
|
|
207
|
+
command = 'dir';
|
|
208
|
+
return await runShellCommand(command);
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
// 선택적 브라우저 제어 도구(의미 DOM #3 + 자동화 #1). 보안 스크린은 브리지(BrowserAgent) 내부에서 적용.
|
|
213
|
+
if (opts.browser) {
|
|
214
|
+
tools.controlBrowser = {
|
|
215
|
+
description: '웹브라우저를 제어한다. verb: goto(url 이동)·read(현재 페이지의 [ref] 요소 목록)·click(ref)·type(ref,text)·extract(본문 텍스트)·back·' +
|
|
216
|
+
'vclick(비전 폴백 — read 로 안 잡히는 캔버스/이미지 요소를 query 설명으로 클릭). ' +
|
|
217
|
+
'먼저 goto/read 로 요소 ref 를 얻어 click/type 하고, read 에 없을 때만 vclick(query) 를 쓴다. 결제·구매·삭제·로그인 제출은 차단된다.',
|
|
218
|
+
inputSchema: zod_1.z.object({
|
|
219
|
+
verb: zod_1.z.enum(['goto', 'read', 'click', 'type', 'extract', 'back', 'vclick']),
|
|
220
|
+
url: zod_1.z.string().optional(),
|
|
221
|
+
ref: zod_1.z.string().optional(),
|
|
222
|
+
text: zod_1.z.string().optional(),
|
|
223
|
+
query: zod_1.z.string().optional(),
|
|
224
|
+
}),
|
|
225
|
+
execute: async (args) => opts.browser(args),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const result = await (0, ai_1.generateText)({
|
|
230
|
+
model,
|
|
231
|
+
system: systemPrompt(),
|
|
232
|
+
messages: history.map((t) => ({ role: t.role, content: t.content })),
|
|
233
|
+
tools,
|
|
234
|
+
stopWhen: (0, ai_1.stepCountIs)(MAX_STEPS),
|
|
235
|
+
});
|
|
236
|
+
return await finalizeText(result, model);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
return `[에러] ${provider} LLM API 호출 중 오류: ${err.message}`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 도구 없는 단발 텍스트 생성(구조화 후보 생성 등에 사용). 제공자 추상화 위에서 동작.
|
|
244
|
+
* 키 미설정 시 명확히 throw 한다.
|
|
245
|
+
*/
|
|
246
|
+
async function llmComplete(prompt, system) {
|
|
247
|
+
const provider = resolveProvider();
|
|
248
|
+
if (!provider) {
|
|
249
|
+
throw new Error('LLM API 키 미설정(.env 의 ANTHROPIC_API_KEY 또는 GEMINI_API_KEY).');
|
|
250
|
+
}
|
|
251
|
+
const model = await resolveModel(provider);
|
|
252
|
+
const result = await (0, ai_1.generateText)({
|
|
253
|
+
model,
|
|
254
|
+
system: system ?? '당신은 정확한 코드 수정 후보를 생성하는 도우미입니다.',
|
|
255
|
+
messages: [{ role: 'user', content: prompt }],
|
|
256
|
+
});
|
|
257
|
+
return result.text;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* `llmComplete` 와 동일 경로지만 **제공자 실 토큰 usage** 도 반환한다(P3 토큰 절감 실측용).
|
|
261
|
+
* 새 제공자 경로를 만들지 않고 기존 generateText 경로를 재사용한다(단순화). 키 미설정 시 throw.
|
|
262
|
+
*/
|
|
263
|
+
async function llmCompleteUsage(prompt, system) {
|
|
264
|
+
const provider = resolveProvider();
|
|
265
|
+
if (!provider) {
|
|
266
|
+
throw new Error('LLM API 키 미설정(.env 의 ANTHROPIC_API_KEY 또는 GEMINI_API_KEY).');
|
|
267
|
+
}
|
|
268
|
+
const model = await resolveModel(provider);
|
|
269
|
+
const result = await (0, ai_1.generateText)({
|
|
270
|
+
model,
|
|
271
|
+
system: system ?? '당신은 지식을 압축·합성하는 도우미입니다.',
|
|
272
|
+
messages: [{ role: 'user', content: prompt }],
|
|
273
|
+
});
|
|
274
|
+
const u = result.usage ?? null;
|
|
275
|
+
const usage = u
|
|
276
|
+
? { inputTokens: u.inputTokens ?? u.promptTokens ?? null, outputTokens: u.outputTokens ?? u.completionTokens ?? null }
|
|
277
|
+
: null;
|
|
278
|
+
return { text: result.text, usage, provider };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 비전 그라운딩(#2 폴백) — 스크린샷에서 설명에 해당하는 요소의 정규화 좌표(0~1)를 찾는다.
|
|
282
|
+
* 멀티모달 모델(Gemini/Claude) 사용. 못 찾거나 키 없으면 null. 비용이 크므로 DOM(#3) 실패 시만.
|
|
283
|
+
*/
|
|
284
|
+
async function locateOnScreen(screenshotBase64, description) {
|
|
285
|
+
const provider = resolveProvider();
|
|
286
|
+
if (!provider)
|
|
287
|
+
return null;
|
|
288
|
+
let model;
|
|
289
|
+
try {
|
|
290
|
+
model = await resolveModel(provider);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const prompt = `이 스크린샷에서 다음 UI 요소의 **중심 좌표**를 찾아줘: "${description}".\n` +
|
|
296
|
+
`좌표는 이미지 좌상단(0,0)~우하단(1,1) 정규화 비율로. 반드시 JSON 한 줄로만 답해:\n` +
|
|
297
|
+
`{"found":true,"x":0.0~1.0,"y":0.0~1.0} 또는 {"found":false}`;
|
|
298
|
+
try {
|
|
299
|
+
const result = await (0, ai_1.generateText)({
|
|
300
|
+
model,
|
|
301
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: prompt }, { type: 'image', image: Buffer.from(screenshotBase64, 'base64') }] }],
|
|
302
|
+
});
|
|
303
|
+
const m = (result.text || '').match(/\{[\s\S]*\}/);
|
|
304
|
+
if (!m)
|
|
305
|
+
return null;
|
|
306
|
+
const o = JSON.parse(m[0]);
|
|
307
|
+
if (!o.found || typeof o.x !== 'number' || typeof o.y !== 'number')
|
|
308
|
+
return null;
|
|
309
|
+
return { x: o.x, y: o.y };
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** 현재 활성 제공자/모델 정보 (대시보드·진단용). */
|
|
316
|
+
function activeLlmInfo() {
|
|
317
|
+
const provider = resolveProvider();
|
|
318
|
+
if (provider === 'anthropic')
|
|
319
|
+
return { provider, model: process.env.ANTHROPIC_MODEL || 'claude-opus-4-8' };
|
|
320
|
+
if (provider === 'google')
|
|
321
|
+
return { provider, model: process.env.GEMINI_MODEL || 'gemini-flash-latest' };
|
|
322
|
+
return { provider: null, model: null };
|
|
323
|
+
}
|
|
324
|
+
/** 로컬(Ollama) 모델이 설정되었는가. OLLAMA_MODEL 명시 시에만 true(정직). */
|
|
325
|
+
function localLlmConfigured() {
|
|
326
|
+
return !!(process.env.OLLAMA_MODEL && process.env.OLLAMA_MODEL.trim());
|
|
327
|
+
}
|
|
328
|
+
/** 로컬(Ollama) 멀티턴 호출. 미설정/미가동 시 명확히 throw(가짜 성공 금지). */
|
|
329
|
+
async function localChat(history, system, fetchFn) {
|
|
330
|
+
const model = (process.env.OLLAMA_MODEL || '').trim();
|
|
331
|
+
if (!model)
|
|
332
|
+
throw new Error('로컬 모델 미설정(OLLAMA_MODEL). Tier-0 로컬 처리를 위해 ollama 모델을 설정하세요.');
|
|
333
|
+
const base = (process.env.OLLAMA_BASE_URL || 'http://127.0.0.1:11434').replace(/\/$/, '');
|
|
334
|
+
const f = fetchFn ?? (globalThis.fetch);
|
|
335
|
+
if (!f)
|
|
336
|
+
throw new Error('global fetch 없음(Node 18+ 필요).');
|
|
337
|
+
const messages = [
|
|
338
|
+
...(system ? [{ role: 'system', content: system }] : []),
|
|
339
|
+
...history.map((t) => ({ role: t.role, content: t.content })),
|
|
340
|
+
];
|
|
341
|
+
const res = await f(`${base}/api/chat`, {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
headers: { 'Content-Type': 'application/json' },
|
|
344
|
+
body: JSON.stringify({ model, messages, stream: false }),
|
|
345
|
+
});
|
|
346
|
+
if (!res.ok)
|
|
347
|
+
throw new Error(`Ollama 호출 실패: HTTP ${res.status} (ollama 실행/모델 수신 확인)`);
|
|
348
|
+
const body = await res.json();
|
|
349
|
+
const text = body?.message?.content;
|
|
350
|
+
if (typeof text !== 'string')
|
|
351
|
+
throw new Error('Ollama 응답 형식 오류(message.content 없음)');
|
|
352
|
+
return text;
|
|
353
|
+
}
|
|
354
|
+
/** 외부 제공자 식별(없으면 null). Tier 라우팅에서 외부 가용성 판단에 사용. */
|
|
355
|
+
function externalProviderId() {
|
|
356
|
+
return resolveProvider();
|
|
357
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare function isSafeMcpUrl(url: string, allowHosts: string[]): {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
reason: string;
|
|
4
|
+
};
|
|
5
|
+
/** M2 — IP 가 사설/루프백/링크로컬/메타데이터/유니크로컬인가(SSRF 표적). */
|
|
6
|
+
export declare function isPrivateIp(ip: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* M2 — DNS 리바인딩 방지: 호스트명을 *실제 resolve* 한 뒤 모든 IP가 공인인지 검사.
|
|
9
|
+
* 허용/공개 호스트명이 사설 IP로 가리켜도 차단. 외부 연결 직전 호출.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isSafeResolvedUrl(url: string, allowHosts: string[], resolver?: (host: string) => Promise<string[]>): Promise<{
|
|
12
|
+
ok: boolean;
|
|
13
|
+
reason: string;
|
|
14
|
+
}>;
|
|
15
|
+
export interface SanitizeResult {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
/** 스키마 검증 통과 데이터(통과 시). */
|
|
18
|
+
data?: unknown;
|
|
19
|
+
/** 탐지된 인젝션/위험 사유. */
|
|
20
|
+
flags: string[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* MCP 도구 출력 검증: (1) 기대 스키마 검증, (2) 프롬프트 인젝션 패턴 플래그.
|
|
24
|
+
* 인젝션 패턴이 있으면 ok=false(에이전트 컨텍스트로 환류 금지).
|
|
25
|
+
*/
|
|
26
|
+
export declare function sanitizeToolOutput(raw: string, isValid: (o: any) => boolean): SanitizeResult;
|
|
27
|
+
/** 에이전트 생성 캔버스용 엄격 CSP — 인라인·eval·임의 네트워크 차단(허용 출처만). */
|
|
28
|
+
export declare function a2uiCsp(allowConnect?: string[]): string;
|
|
29
|
+
/** iframe sandbox 속성 — 동일출처/팝업/탑네비 차단(스크립트만 허용). */
|
|
30
|
+
export declare const A2UI_SANDBOX = "allow-scripts";
|
|
31
|
+
/** provenance 배너 마크업 — 사용자에게 "에이전트 생성" 출처를 명시(기만 방지). */
|
|
32
|
+
export declare function provenanceBanner(): string;
|