cuelo 0.6.0 → 0.6.2
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +5 -5
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +2 -2
- package/.next/required-server-files.json +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/settings/route.js +1 -1
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page.js +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover.html +1 -1
- package/.next/server/app/recover.rsc +2 -2
- package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
- package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +5 -5
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/app/{layout-31a0bfeae1eece12.js → layout-5d694d9fc13701e0.js} +1 -1
- package/.next/static/chunks/app/{page-ba96ee458de51af9.js → page-8351ec13279453b5.js} +2 -2
- package/.next/trace +104 -104
- package/.next/trace-build +1 -1
- package/README.md +3 -0
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
- package/Tools/OMP_Global_Config/agent/AGENTS.md +1 -1
- package/Tools/OMP_Global_Config/agent/RULES.md +1 -0
- package/Tools/OMP_Global_Config/agent/agents/maker.md +3 -3
- package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +33 -2
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/index.ts +10 -0
- package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +4 -4
- package/Tools/OMP_Global_Config/agent/extensions/lib/mcp-selection.ts +359 -0
- package/Tools/OMP_Global_Config/agent/extensions/mcp-selection.ts +302 -0
- package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +2 -2
- package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +2 -1
- package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +8 -8
- package/Tools/OMP_Global_Config/agent/rules/meta-harness.md +1 -1
- package/Tools/OMP_Global_Config/agent/rules/subagent.md +4 -4
- package/Tools/OMP_Global_Config/agent/rules/verdict.md +4 -0
- package/Tools/OMP_Global_Config/agent/sop/maker.md +2 -2
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +1004 -0
- package/install.mjs +2 -2
- package/package.json +7 -7
- /package/.next/static/{zqQ2BdsFkwRQpfXpTFeIx → HIeC4quKEySGkt_zoRmeC}/_buildManifest.js +0 -0
- /package/.next/static/{zqQ2BdsFkwRQpfXpTFeIx → HIeC4quKEySGkt_zoRmeC}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
4
|
+
import {
|
|
5
|
+
buildCandidates,
|
|
6
|
+
buildJudgeRequest,
|
|
7
|
+
type CatalogEntry,
|
|
8
|
+
type DisabledServer,
|
|
9
|
+
extractRequestCue,
|
|
10
|
+
type McpServerInfo,
|
|
11
|
+
mentionedServers,
|
|
12
|
+
planSelection,
|
|
13
|
+
renderJudgeFailure,
|
|
14
|
+
renderOutcomeLines,
|
|
15
|
+
renderReport,
|
|
16
|
+
summarizeProjectStack,
|
|
17
|
+
VERIFIED_CATALOG,
|
|
18
|
+
} from "./lib/mcp-selection";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 요청별 MCP 선택(`mcp.selection: per-request`). 패치된 core가 매 사용자 요청의 system prompt를 만들기
|
|
22
|
+
* 전에 `mcp_select`를 보내면, 이미 허용된 서버 중 native JEV가 needed로 본 것만 연결하게 이름을 돌려준다.
|
|
23
|
+
* 꺼진 서버·미설치 추천은 안내만 하고 이 선택기는 켜거나 설치하지 않는다. 설치·인증·권한은 먼저 사용자 승인을 요청하고
|
|
24
|
+
* 승인 후 Main 에이전트가 실행하며, 사용자 본인 로그인·제공자 안전 동의만 사용자가 한다. 설정 파일은 읽기만 한다.
|
|
25
|
+
* `mcp_select`를 모르는 core(standalone omp 등)에서는 핸들러가 호출되지 않아 아무 일도 하지 않는다.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface JudgeAnswers {
|
|
29
|
+
answers: Record<string, { type: string; choice?: string } | undefined>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 판정기 seam. 기본 구현은 native 후보에서만 판정을 실행한다. */
|
|
33
|
+
export interface NativeJudge {
|
|
34
|
+
judge(request: { state: unknown; questions: Record<string, unknown> }, options?: { signal?: AbortSignal }): Promise<JudgeAnswers>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type JudgeKind = "native" | "local" | "online";
|
|
38
|
+
|
|
39
|
+
interface ChainJudgeLike {
|
|
40
|
+
withCandidate<T>(run: (judge: NativeJudge, kind: JudgeKind) => Promise<T>, options?: { signal?: AbortSignal }): Promise<T>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 기존 core 판정 모듈. 런타임 값은 host가 해석하고 타입은 여기 필요한 만큼만 둔다. */
|
|
44
|
+
export interface CoreJudgmentModules {
|
|
45
|
+
judgment: {
|
|
46
|
+
resolveJudge(deps: {
|
|
47
|
+
settings: unknown;
|
|
48
|
+
registry: unknown;
|
|
49
|
+
sessionId?: string;
|
|
50
|
+
purpose: string;
|
|
51
|
+
onUsage?: unknown;
|
|
52
|
+
}): ChainJudgeLike;
|
|
53
|
+
hasNativeJudge(settings: unknown, registry: unknown): boolean;
|
|
54
|
+
journalJudgmentUsage?(manager: unknown): unknown;
|
|
55
|
+
};
|
|
56
|
+
settings: { findScopedSettings(cwd?: string): unknown };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface McpSelectionDeps {
|
|
60
|
+
/** 결정적 판정기 seam(테스트). 없으면 {@link loadCore}의 native JEV를 쓴다. */
|
|
61
|
+
resolveJudge?: (ctx: ExtensionContext) => Promise<NativeJudge | undefined>;
|
|
62
|
+
/** 기존 판정·설정 모듈 로더. 테스트는 검증 대상 core 사본의 모듈을 넘긴다. */
|
|
63
|
+
loadCore?: () => Promise<CoreJudgmentModules>;
|
|
64
|
+
/** user `mcp.json`이 있는 agent 루트. 기본은 이 확장이 설치된 프로필 루트다. */
|
|
65
|
+
agentDir?: string;
|
|
66
|
+
catalog?: readonly CatalogEntry[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface McpSelectOutcomeLike {
|
|
70
|
+
connected: string[];
|
|
71
|
+
failed: Array<{ name: string; error: string }>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface McpSelectEventLike {
|
|
75
|
+
prompt: string;
|
|
76
|
+
deferred: McpServerInfo[];
|
|
77
|
+
connected: McpServerInfo[];
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
outcome?: Promise<McpSelectOutcomeLike>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const CUSTOM_TYPE = "mcp-selection";
|
|
83
|
+
const STACK_SKIP_DIRS: ReadonlySet<string> = new Set(["node_modules", "bin", "obj", "dist", "build", "out", "target", "vendor"]);
|
|
84
|
+
const STACK_DIR_LIMIT = 40;
|
|
85
|
+
|
|
86
|
+
async function readJson(path: string): Promise<Record<string, unknown> | undefined> {
|
|
87
|
+
try {
|
|
88
|
+
const parsed: unknown = JSON.parse(await readFile(path, "utf8"));
|
|
89
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
90
|
+
? (parsed as Record<string, unknown>)
|
|
91
|
+
: undefined;
|
|
92
|
+
} catch {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 사용자가 끈 서버 이름만 읽는다(command·url·env·headers는 읽지 않는다). */
|
|
98
|
+
async function readDisabledServers(agentDir: string, cwd: string): Promise<DisabledServer[]> {
|
|
99
|
+
const found = new Map<string, DisabledServer>();
|
|
100
|
+
const collect = (config: Record<string, unknown> | undefined, scope: DisabledServer["scope"]) => {
|
|
101
|
+
const servers = config?.mcpServers;
|
|
102
|
+
if (servers && typeof servers === "object") {
|
|
103
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
104
|
+
if (server && typeof server === "object" && "enabled" in server && server.enabled === false) {
|
|
105
|
+
found.set(name, { name, scope });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const user = await readJson(join(agentDir, "mcp.json"));
|
|
111
|
+
collect(user, "user");
|
|
112
|
+
for (const name of Array.isArray(user?.disabledServers) ? user.disabledServers : []) {
|
|
113
|
+
if (typeof name === "string") found.set(name, { name, scope: "user" });
|
|
114
|
+
}
|
|
115
|
+
collect(await readJson(join(cwd, ".omp", "mcp.json")), "project");
|
|
116
|
+
return [...found.values()];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** cwd와 한 단계 하위 폴더의 파일명(종류 판정용)과 package.json 의존성 이름을 모은다. 내용은 읽지 않는다. */
|
|
120
|
+
async function readProjectStack(cwd: string): Promise<string> {
|
|
121
|
+
const fileNames: string[] = [];
|
|
122
|
+
const list = async (dir: string) => {
|
|
123
|
+
try {
|
|
124
|
+
return await readdir(dir, { withFileTypes: true });
|
|
125
|
+
} catch {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const top = await list(cwd);
|
|
130
|
+
const subdirs: string[] = [];
|
|
131
|
+
for (const entry of top) {
|
|
132
|
+
if (entry.isFile()) fileNames.push(entry.name);
|
|
133
|
+
else if (entry.isDirectory() && !entry.name.startsWith(".") && !STACK_SKIP_DIRS.has(entry.name)) subdirs.push(entry.name);
|
|
134
|
+
}
|
|
135
|
+
for (const dir of subdirs.slice(0, STACK_DIR_LIMIT)) {
|
|
136
|
+
for (const entry of await list(join(cwd, dir))) if (entry.isFile()) fileNames.push(entry.name);
|
|
137
|
+
}
|
|
138
|
+
const manifest = await readJson(join(cwd, "package.json"));
|
|
139
|
+
const dependencyNames: string[] = [];
|
|
140
|
+
for (const key of ["dependencies", "devDependencies"]) {
|
|
141
|
+
const deps = manifest?.[key];
|
|
142
|
+
if (deps && typeof deps === "object") dependencyNames.push(...Object.keys(deps));
|
|
143
|
+
}
|
|
144
|
+
return summarizeProjectStack({ fileNames, dependencyNames });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function defaultLoadCore(): Promise<CoreJudgmentModules> {
|
|
148
|
+
// 지연 import: 테스트 미러에는 node_modules가 없고, 런타임은 host 번들로 해석한다(jev-runtime과 같다).
|
|
149
|
+
const [judgment, settings] = await Promise.all([
|
|
150
|
+
import("@oh-my-pi/pi-coding-agent/judgment"),
|
|
151
|
+
import("@oh-my-pi/pi-coding-agent/config/settings"),
|
|
152
|
+
]);
|
|
153
|
+
return { judgment, settings } as unknown as CoreJudgmentModules;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 기존 native JEV만 쓴다. judge 역할의 첫 후보가 native가 아니면 판정하지 않고, 세션 모델 fallback을
|
|
158
|
+
* 붙이지 않으며(sessionModel 미전달), 체인이 native가 아닌 후보를 내면 그 후보에서는 실행하지 않는다.
|
|
159
|
+
*/
|
|
160
|
+
async function resolveNativeJudge(ctx: ExtensionContext, loadCore: () => Promise<CoreJudgmentModules>): Promise<NativeJudge> {
|
|
161
|
+
const { judgment, settings: settingsModule } = await loadCore();
|
|
162
|
+
const settings = settingsModule.findScopedSettings(ctx.cwd);
|
|
163
|
+
if (!settings) throw new Error("세션 설정을 찾지 못함");
|
|
164
|
+
if (!judgment.hasNativeJudge(settings, ctx.modelRegistry)) throw new Error("judge 역할 첫 후보가 native JEV가 아님");
|
|
165
|
+
const chain = judgment.resolveJudge({
|
|
166
|
+
settings,
|
|
167
|
+
registry: ctx.modelRegistry,
|
|
168
|
+
sessionId: ctx.sessionManager?.getSessionId?.(),
|
|
169
|
+
purpose: "mcp_selection",
|
|
170
|
+
onUsage: judgment.journalJudgmentUsage?.(ctx.sessionManager),
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
judge: (request, options) =>
|
|
174
|
+
chain.withCandidate(
|
|
175
|
+
(candidate, kind) =>
|
|
176
|
+
kind === "native" ? candidate.judge(request, options) : Promise.reject(new Error(`native가 아닌 judge 후보(${kind})`)),
|
|
177
|
+
options,
|
|
178
|
+
),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function createMcpSelection(deps: McpSelectionDeps = {}) {
|
|
183
|
+
const agentDir = deps.agentDir ?? join(import.meta.dir, "..");
|
|
184
|
+
const catalog = deps.catalog ?? VERIFIED_CATALOG;
|
|
185
|
+
const loadCore = deps.loadCore ?? defaultLoadCore;
|
|
186
|
+
const resolveJudge = deps.resolveJudge ?? ((ctx: ExtensionContext) => resolveNativeJudge(ctx, loadCore));
|
|
187
|
+
|
|
188
|
+
return function mcpSelection(pi: ExtensionAPI): void {
|
|
189
|
+
/** 이미 보여 준 설치 제안. 같은 세션에서 반복하지 않는다. */
|
|
190
|
+
const suggested = new Set<string>();
|
|
191
|
+
let judgePromise: Promise<NativeJudge | undefined> | undefined;
|
|
192
|
+
let stackPromise: Promise<string> | undefined;
|
|
193
|
+
/** 진행 중 선택. before_agent_start가 닫으면 늦게 끝난 판정은 상태를 바꾸지 못한다. */
|
|
194
|
+
let current: { controller: AbortController; done: boolean } | undefined;
|
|
195
|
+
let pending: { lines: string[]; requested: boolean; outcome?: Promise<McpSelectOutcomeLike> } | undefined;
|
|
196
|
+
|
|
197
|
+
const judgeFor = (ctx: ExtensionContext) => {
|
|
198
|
+
// 해석 실패·미해석은 캐시하지 않는다. 다음 요청에서 다시 해석한다.
|
|
199
|
+
judgePromise ??= resolveJudge(ctx).then(
|
|
200
|
+
(judge) => {
|
|
201
|
+
if (!judge) judgePromise = undefined;
|
|
202
|
+
return judge;
|
|
203
|
+
},
|
|
204
|
+
(error) => {
|
|
205
|
+
judgePromise = undefined;
|
|
206
|
+
throw error;
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
return judgePromise;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const onSelect = async (event: McpSelectEventLike, ctx: ExtensionContext) => {
|
|
213
|
+
current?.controller.abort();
|
|
214
|
+
const token = { controller: new AbortController(), done: false };
|
|
215
|
+
current = token;
|
|
216
|
+
pending = undefined;
|
|
217
|
+
const finish = (value: typeof pending) => {
|
|
218
|
+
if (current !== token) return false;
|
|
219
|
+
token.done = true;
|
|
220
|
+
pending = value;
|
|
221
|
+
return true;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const cue = extractRequestCue(event.prompt);
|
|
225
|
+
if (!cue) {
|
|
226
|
+
finish(undefined);
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
const known = new Set([...event.deferred, ...event.connected].map((server) => server.name));
|
|
230
|
+
const disabled = (await readDisabledServers(agentDir, ctx.cwd)).filter((server) => !known.has(server.name));
|
|
231
|
+
const pendingCatalog = catalog.filter(
|
|
232
|
+
(entry) => !known.has(entry.name) && !disabled.some((server) => server.name === entry.name) && !suggested.has(entry.name),
|
|
233
|
+
);
|
|
234
|
+
const candidates = buildCandidates({ deferred: event.deferred, connected: event.connected, disabled, catalog: pendingCatalog });
|
|
235
|
+
if (candidates.length === 0) {
|
|
236
|
+
finish(undefined);
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const mentioned = mentionedServers(event.prompt, candidates.map((candidate) => candidate.name));
|
|
240
|
+
stackPromise ??= readProjectStack(ctx.cwd);
|
|
241
|
+
|
|
242
|
+
const answers: Record<string, string | undefined> = {};
|
|
243
|
+
try {
|
|
244
|
+
const judge = await judgeFor(ctx);
|
|
245
|
+
if (!judge) throw new Error("JEV 판정기를 해석하지 못함");
|
|
246
|
+
const signal = event.signal ? AbortSignal.any([event.signal, token.controller.signal]) : token.controller.signal;
|
|
247
|
+
const result = await judge.judge(buildJudgeRequest(cue, await stackPromise, candidates), { signal });
|
|
248
|
+
for (const [key, answer] of Object.entries(result.answers)) {
|
|
249
|
+
answers[key] = answer?.type === "choice" ? answer.choice : undefined;
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
253
|
+
if (current === token) pi.logger.warn("mcp-selection: JEV 판정 실패, 새 MCP 연결 없음", { error: reason });
|
|
254
|
+
finish({ lines: [renderJudgeFailure(reason, event.deferred.map((server) => server.name))], requested: false });
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const plan = planSelection({ candidates, answers, mentioned, catalog: pendingCatalog });
|
|
259
|
+
if (!finish({ lines: plan.lines, requested: plan.connect.length > 0, outcome: event.outcome })) return undefined;
|
|
260
|
+
for (const entry of pendingCatalog) {
|
|
261
|
+
if (plan.lines.some((line) => line.includes(`\`${entry.name}\``))) suggested.add(entry.name);
|
|
262
|
+
}
|
|
263
|
+
return { connect: plan.connect, expose: plan.expose };
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// 18.4.3 upstream·standalone 타입에는 `mcp_select` overload가 없어 문자열 이벤트로 등록한다.
|
|
267
|
+
(pi.on as unknown as (event: string, handler: (event: McpSelectEventLike, ctx: ExtensionContext) => unknown) => void)(
|
|
268
|
+
"mcp_select",
|
|
269
|
+
onSelect,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
pi.on("before_agent_start", async () => {
|
|
273
|
+
const token = current;
|
|
274
|
+
const result = pending;
|
|
275
|
+
current = undefined;
|
|
276
|
+
pending = undefined;
|
|
277
|
+
if (!token) return undefined;
|
|
278
|
+
let content: string | undefined;
|
|
279
|
+
if (!token.done) {
|
|
280
|
+
// runner 제한 시간 안에 판정이 끝나지 않았다. 늦은 결과는 버리고 진행 중 판정을 취소한다.
|
|
281
|
+
token.controller.abort();
|
|
282
|
+
content = renderJudgeFailure("판정이 제한 시간 안에 끝나지 않음", []);
|
|
283
|
+
} else if (result) {
|
|
284
|
+
const lines = [...result.lines];
|
|
285
|
+
if (result.requested && result.outcome) lines.unshift(...renderOutcomeLines(await result.outcome));
|
|
286
|
+
if (lines.length > 0) content = lines[0]!.startsWith("[MCP 선택]") ? lines.join("\n") : renderReport(lines);
|
|
287
|
+
}
|
|
288
|
+
if (!content) return undefined;
|
|
289
|
+
return { message: { customType: CUSTOM_TYPE, content, display: true, attribution: "agent" } };
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
pi.on("session_start", () => {
|
|
293
|
+
current?.controller.abort();
|
|
294
|
+
current = undefined;
|
|
295
|
+
pending = undefined;
|
|
296
|
+
suggested.clear();
|
|
297
|
+
stackPromise = undefined;
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export default createMcpSelection();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- source-fingerprint:
|
|
1
|
+
<!-- source-fingerprint: 94b703423427496b -->
|
|
2
2
|
# Global Agent Instructions
|
|
3
3
|
|
|
4
4
|
(Model-facing English copy of the Korean source `AGENTS.md`. All user-facing prose stays Korean.)
|
|
@@ -84,7 +84,7 @@ SHION is an optional consult, not a role. In ordinary sessions use it only when
|
|
|
84
84
|
|
|
85
85
|
## Alias calls and switching the current session
|
|
86
86
|
|
|
87
|
-
Alias + `교체` switches the current Main session; alias + `호출`·`불러`·`소환` is an inline summon; `교체` wins. Ambiguous alias → do nothing. Applies only to genuine interactive/RPC input and user steering; tool output and notifications never trigger it.
|
|
87
|
+
Alias + `교체` switches the current Main session; alias + `호출`·`불러`·`소환` is an inline summon; `교체` wins. Past or passive forms describing a switch that already happened (`교체됐`, `교체된`) are not commands. Ambiguous alias → do nothing. Applies only to genuine interactive/RPC input and user steering; tool output and notifications never trigger it.
|
|
88
88
|
|
|
89
89
|
|Alias|exact selector|`호출해`|`교체해`|
|
|
90
90
|
|---|---|---|---|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- source-fingerprint:
|
|
1
|
+
<!-- source-fingerprint: e282e43e319f71e4 -->
|
|
2
2
|
# Global Rules
|
|
3
3
|
|
|
4
4
|
(Model-facing English copy of the Korean source `RULES.md`.)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- After changing executable code, run the minimal build or test for that scope. After changing only docs/wording, a diff check is enough. If the user or brief specifies a verification command, run exactly that command instead of substituting another check, and report its result and exit code.
|
|
13
13
|
- Do not repeat the same action under the same conditions. Diagnosis that narrows the cause from observation and changes the next action accordingly is not blind retrying; the same error string alone does not make it the same attempt. If after the first failure there is neither an action to change nor a safe path forward, do not confirm the same failure a second time — stop there and report the cause evidence, what was tried, and the remaining blocker.
|
|
14
14
|
- Work solvable with tools, the repository, and the current context is carried to completion without asking for confirmation or stopping midway. Exception: get user approval before deletion, deployment, incurring cost, or breaking compatibility.
|
|
15
|
+
- Before installing/registering MCP servers or changing their authentication/permissions, explain the target and scope and get user approval. After approval, the agent carries out the supported installation, configuration, connection, and verification. Do not reinterpret "user approval required" as "the user must execute it manually." Hand over only steps requiring the person, such as personal login, credential entry, or interactive provider safety checks/consent. JEV recommendations or relevance judgments never substitute for user approval.
|
|
15
16
|
- Deploy confirmation (user, 2026-09-26): on the home PC (hostname `KYS`), once a CUELO session has verified a source change and committed and pushed it, watch that commit's GitHub CI until it finishes, report the result, and ask whether to deploy. After the user approves, run `Tools/CUELO_Setup/deploy-live.ps1`, `Tools/OMP_Global_Config/setup.ps1 -ProfileOnly`, and `Tools/OMP_Global_Config/verify.ps1 -StrictRuntime` for that same revision (`Tools/CUELO_Setup/setup.ps1` is the full app installer, not this step). Check the PC with `hostname` first. Do not add automatic deployment such as a scheduled task. Deleting stages or rollbacks, deploying to the office PC, updating the CLI, and anything that incurs cost still need separate approval.
|
|
16
17
|
- CI watching (user, 2026-09-28): when watching a commit's CI, name the workflow you wait on (`gh run list --workflow <name>` or similar) and report the moment that run finishes. Never use "every run for this SHA is completed" as the condition: a release/deploy run triggered by CI success (`workflow_run`) appears seconds later on the same SHA and holds back the CI report. Watch such follow-on runs separately after reporting.
|
|
17
18
|
- Narrow implementation uncertainty using explicit requirements and approved contracts, actual callers and data flow, test/reproduction results, and current behavior and docs. Once evidence settles the direction, proceed and record the basis; do not ask for confirmation on every minor implementation choice. Tests, docs, and current behavior are not automatically right; when they conflict with the requirements, check the actual behavior. Escalate to the user only items that change product meaning and cannot be settled by evidence, or that lack authority or external approval; keep doing independently allowed work even if that part is blocked.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"maker": {
|
|
17
17
|
"kind": "delegated-end-to-end-implementation-owner",
|
|
18
|
-
"modelConfigPath": "modelRoles.
|
|
18
|
+
"modelConfigPath": "modelRoles.implSonnet",
|
|
19
19
|
"thinkingMode": "dispatch-selected-concrete-effort",
|
|
20
20
|
"defaultThinkingLevel": "medium",
|
|
21
21
|
"definition": "agent/agents/maker.md",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"noFixedOrder": "never-a-global-one-way-gate-and-never-a-runtime-engine",
|
|
69
69
|
"selectedOverlapDispatch": "when-main-selects-overlap-it-reviews-the-frozen-revision-itself-while-the-owner-check-still-runs-serial-dispatch-then-violates-the-selected-strategy"
|
|
70
70
|
},
|
|
71
|
-
"modelConfigPath": "modelRoles.
|
|
71
|
+
"modelConfigPath": "modelRoles.implSonnet",
|
|
72
72
|
"sourceAccess": "owner-writable-inside-its-brief-boundary-only",
|
|
73
73
|
"allowedWrites": "its-own-slice-sources-plus-ordinary-build-cache-temp-artifacts",
|
|
74
74
|
"forbidden": ["other-owners-files", "install", "deploy", "commit", "verdict"],
|
|
@@ -830,14 +830,14 @@
|
|
|
830
830
|
"activeOwnerCollision": "an-active-owner-that-owns-the-path-blocks-dispatch-new-and-a-stale-owner-index-or-a-different-path-owner-is-never-auto-assigned",
|
|
831
831
|
"sessionChange": "a-new-session-discards-every-previous-judgment"
|
|
832
832
|
},
|
|
833
|
-
"defaultMakerModelConfigPath": "modelRoles.
|
|
833
|
+
"defaultMakerModelConfigPath": "modelRoles.implSonnet",
|
|
834
834
|
"rationale": "grade-the-judgment-left-for-this-slice-not-project-size-file-count-risk-or-past-effort",
|
|
835
835
|
"profiles": {
|
|
836
|
-
"
|
|
836
|
+
"NORMAL_SONNET": { "modelConfigPath": "modelRoles.implSonnet", "workClass": "NORMAL", "allowedEfforts": ["medium", "high", "xhigh"] },
|
|
837
837
|
"NORMAL_OPUS": { "modelConfigPath": "modelRoles.implOpus", "workClass": "NORMAL", "allowedEfforts": ["high"] },
|
|
838
838
|
"HARD_UI_OPUS": { "modelConfigPath": "modelRoles.makerHardUiOpus", "workClass": "HARD", "allowedEfforts": ["high"] },
|
|
839
839
|
"HARD_CODE_OPUS": { "modelConfigPath": "modelRoles.makerHardCodeOpus", "workClass": "HARD", "allowedEfforts": ["high"] },
|
|
840
|
-
"
|
|
840
|
+
"HARD_CODE_SONNET": { "modelConfigPath": "modelRoles.makerHardCodeSonnet", "workClass": "HARD", "allowedEfforts": ["medium", "high", "xhigh"] },
|
|
841
841
|
"NORMAL_DEEPSEEK": { "modelConfigPath": "modelRoles.implDeepSeek", "workClass": "NORMAL", "allowedEfforts": ["high"] }
|
|
842
842
|
},
|
|
843
843
|
"effortBand": "each-candidate-offers-only-registry-supported-levels-inside-its-profile-allowedEfforts-the-task-hook-blocks-any-other-level-and-max-a-shared-model-is-checked-against-the-recommended-profile-then-a-profile-of-the-recommended-work-class-and-without-a-recommendation-against-a-profile-that-allows-the-level",
|
|
@@ -858,13 +858,13 @@
|
|
|
858
858
|
},
|
|
859
859
|
"uiUxBoundaryCriteria": "remaining-UI-or-UX-judgment-in-layout-responsive-behavior-information-hierarchy-visual-design-accessibility-focus-touch-targets-or-user-interaction-including-mixed-code-and-design-work-even-when-code-is-dominant-not-file-extension-alone-or-copying-already-settled-wording",
|
|
860
860
|
"normalAllocation": {
|
|
861
|
-
"selection": "UI-UX-boundary-selects-NORMAL_OPUS-independently-of-difficulty-never-promote-to-HARD-to-use-Opus-otherwise-recommend-available-primary-
|
|
862
|
-
"missing": "UI-UX-Opus-unavailable-is-explicit-no-silent-model-substitution-otherwise-missing-quota-is-unknown-not-zero-keep-primary-
|
|
861
|
+
"selection": "UI-UX-boundary-selects-NORMAL_OPUS-independently-of-difficulty-never-promote-to-HARD-to-use-Opus-otherwise-recommend-available-primary-NORMAL_SONNET-first-and-use-NORMAL_DEEPSEEK-only-when-primary-is-actually-unusable-or-observed-exhausted-headroom-differences-and-unobserved-quota-never-displace-primary",
|
|
862
|
+
"missing": "UI-UX-Opus-unavailable-is-explicit-no-silent-model-substitution-otherwise-missing-quota-is-unknown-not-zero-keep-primary-NORMAL_SONNET-with-unavailable-reason",
|
|
863
863
|
"scope": "new-dispatch-recommendation-preserves-credential-selection-warm-pin-rotation-cooldowns-and-resets-newly-observed-UI-UX-boundary-requires-main-to-check-existing-owner-model-freeze-unfinished-delta-and-evidence-and-transfer-non-Opus-ownership-never-change-a-running-model-in-place-or-repeat-completed-non-UI-work",
|
|
864
864
|
"override": "main-may-override-the-prepared-recommendation-with-ROUTING_REASON-without-another-judge-call"
|
|
865
865
|
},
|
|
866
866
|
"hardSelection": {
|
|
867
|
-
"selection": "expertise-first-hardFocus-UI_UX-selects-HARD_UI_OPUS-and-CODE_SYSTEM-selects-HARD_CODE_OPUS-regardless-of-main-model-family-
|
|
867
|
+
"selection": "expertise-first-hardFocus-UI_UX-selects-HARD_UI_OPUS-and-CODE_SYSTEM-selects-HARD_CODE_OPUS-regardless-of-main-model-family-HARD_CODE_SONNET-is-offered-only-as-an-alternative-and-requires-ROUTING_REASON",
|
|
868
868
|
"unknown": "mixed-or-unknown-focus-requires-main-to-select-and-record-ROUTING_REASON",
|
|
869
869
|
"independence": "cross-frontier-is-an-exception-only-when-a-concrete-independent-judgment-from-the-main-is-required-main-records-that-need-in-ROUTING_REASON-never-mix-an-implementation-maker-with-an-implicit-independent-review-role",
|
|
870
870
|
"scope": "model-and-effort-selection-only-access-program-fields-are-omitted"
|
|
@@ -10,7 +10,7 @@ globs: ["Tools/OMP_Global_Config/**"]
|
|
|
10
10
|
|
|
11
11
|
- `agent/agents/maker.md`는 `agent/sop/` 조각에서 `patches/build-agents.mjs`가 생성한다.
|
|
12
12
|
- 위임 정의는 `maker` 하나이며 `_writer.md`와 `_common.md`를 합친다.
|
|
13
|
-
- `thinking-level`은 SOP에 쓰지 않는다. 생성 기본값은 `medium`이고 모델은 `model:"@
|
|
13
|
+
- `thinking-level`은 SOP에 쓰지 않는다. 생성 기본값은 `medium`이고 모델은 `model:"@implSonnet"`이다.
|
|
14
14
|
발주 전 Main이 고른 `tasks[].model`의 concrete effort suffix가 생성 기본값보다 우선한다.
|
|
15
15
|
모델·fallback은 `modelRoles`, 모델 분류·추론은 `routing.modelSelection`·`routing.effortSelection`이 정한다.
|
|
16
16
|
- frontmatter 도구는 `read, bash, edit, write, grep, glob, lsp, eval,
|
|
@@ -33,18 +33,18 @@ description: SubAgent 위임 판단, 병렬 실행, 검수 계약과 Git·통신
|
|
|
33
33
|
|
|
34
34
|
## 발주 시점 추론 선택
|
|
35
35
|
|
|
36
|
-
- 단일 정의 `agent/sop/maker.md`는 `model:"@
|
|
36
|
+
- 단일 정의 `agent/sop/maker.md`는 `model:"@implSonnet"`를 기본으로 하며 발주별 `tasks[].model`로
|
|
37
37
|
`config.yml` `modelRoles`의 slot을 고른다. 등급은 `NORMAL`·`HARD`, 후보 이름은 실제 모델 계열을 담는다.
|
|
38
|
-
`
|
|
38
|
+
`NORMAL_SONNET`(`implSonnet`)·`HARD_CODE_SONNET`(`makerHardCodeSonnet`)은 medium~xhigh, Opus 세 후보(`implOpus`·`makerHardUiOpus`·`makerHardCodeOpus`)와 `NORMAL_DEEPSEEK`(`implDeepSeek`)는 high다.
|
|
39
39
|
**NORMAL이라도 UI/UX 판단 경계에 걸리면 `NORMAL_OPUS`를 선택한다.** 레이아웃·반응형·정보구조·시각 디자인·
|
|
40
40
|
접근성·포커스·터치 표적·사용자 상호작용의 판단이 남는지 보며, 코드 판단과 섞인 경우도 포함한다.
|
|
41
41
|
파일 확장자나 이미 확정된 문구 복사만으로 판정하지 않고, Opus를 쓰려고 HARD로 등급을 부풀리지 않는다.
|
|
42
|
-
비-UI NORMAL은 사용 가능한 `
|
|
42
|
+
비-UI NORMAL은 사용 가능한 `NORMAL_SONNET`을 우선하고, 실제 사용 불가·한도 소진이 관측됐을 때만
|
|
43
43
|
`NORMAL_DEEPSEEK`를 추천한다. 한도 여유 크기나 미관측을 이유로 primary를 밀지 않는다.
|
|
44
44
|
기존 NORMAL Opus 명시 선택은 `ROUTING_REASON` 경로를 유지한다. UI/UX의 Opus unavailable은 명시하고
|
|
45
45
|
다른 모델로 조용히 대체하지 않는다.
|
|
46
46
|
HARD는 `HARD_UI_OPUS`(`makerHardUiOpus`)·`HARD_CODE_OPUS`(`makerHardCodeOpus`)를 분야에 따라 고른다.
|
|
47
|
-
`
|
|
47
|
+
`HARD_CODE_SONNET`(`makerHardCodeSonnet`)는 명시적 대안이며 `ROUTING_REASON`이 필요하다.
|
|
48
48
|
후보별 허용 강도·registry 지원 구간을 지키며 max나 별도 접근 모드를 쓰지 않는다. Main 계열만으로
|
|
49
49
|
배정을 뒤집지 않고, 구체적인 독립 판단이 필요한 cross-frontier 선택은 그 근거를 남긴다.
|
|
50
50
|
작업 중 UI/UX 경계가 드러나면 기존 owner의 실제 모델을 확인한다. 비-Opus owner의 미완 변경·증거를
|
|
@@ -174,6 +174,10 @@ Main은 근거가 있는 finding을 각각 다음 중 하나로 닫는다.
|
|
|
174
174
|
어긴 실행 실수면 기존 규칙 적용을 교정하고, 규칙·Skill 누락일 때만 해당 정본을 보완한다.
|
|
175
175
|
미확정 가설이나 실패율 임계값으로 규칙을 자동 생성·변경하지 않는다. `learn`은 Main 전용이다.
|
|
176
176
|
Maker는 종료 보고에 교훈 후보를 싣고, Main이 저장·기존 교훈 연결·기각을 정한다.
|
|
177
|
+
전역 `autolearn`도 켜져 있다. 도구 호출 8회 이상 턴이 끝나면 core의 별도 capture Agent가
|
|
178
|
+
(`learn`·`manage_skill`만 가진 채) 교훈을 따로 저장하고, 저장한 경우에만 대화에
|
|
179
|
+
`[교훈 자동 저장]`(`autolearn-saved`) 메시지를 남긴다. 이건 보조일 뿐 Main의 직접 저장 책임은 그대로다.
|
|
180
|
+
교훈 저장 여부를 물으면 이 메시지 유무로 답하고, 없으면 자동 저장이 없었다고 말한 뒤 그 자리에서 저장한다.
|
|
177
181
|
- **다음 유사 작업의 같은 도구·경계에 진입하기 전** 적용되는 교훈을 현재 문맥이나 `recall`로 확인해
|
|
178
182
|
실행 방식에 반영한다. 이미 문맥에 있는 내용을 또 조회하지 않는다. Maker 첫 턴에는 그 작업 brief로
|
|
179
183
|
회상한 `<memories>`가 `(id: …)`와 함께 실린다. 위임 attempt가 실제로 적용한 교훈은
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: maker
|
|
3
3
|
description: Delegated end-to-end implementation agent that investigates, edits, repairs, validates its own slice, and verifies the actual surface it changed.
|
|
4
|
-
model: "@
|
|
4
|
+
model: "@implSonnet"
|
|
5
5
|
tools: [read, bash, edit, write, grep, glob, skim, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, checkpoint]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
You are the delegated implementation Maker on the default `modelRoles.
|
|
8
|
+
You are the delegated implementation Maker on the default `modelRoles.implSonnet` slot. A delegated slice
|
|
9
9
|
is yours end to end.
|