cuelo 0.10.2 → 0.10.4
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 +15 -15
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +1 -1
- package/.next/required-server-files.json +1 -1
- 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/agent/[id]/account/route.js +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +1 -1
- package/.next/server/app/api/default-cwd/route.js +1 -1
- package/.next/server/app/api/display-settings/route.js +1 -1
- package/.next/server/app/api/file-index/route.js +1 -1
- package/.next/server/app/api/files/[...path]/route.js +1 -1
- package/.next/server/app/api/git/diff/route.js +2 -2
- package/.next/server/app/api/git/status/route.js +2 -2
- package/.next/server/app/api/gpt6/handles/route.js +1 -1
- package/.next/server/app/api/gpt6/reply/route.js +1 -1
- package/.next/server/app/api/gpt6/token/route.js +1 -1
- package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
- package/.next/server/app/api/live/call/[callId]/route.js +1 -1
- package/.next/server/app/api/live/character-voice/route.js +1 -1
- package/.next/server/app/api/live/events/route.js +1 -1
- package/.next/server/app/api/live/offer/route.js +1 -1
- package/.next/server/app/api/live/voices/route.js +1 -1
- package/.next/server/app/api/lounge/events/route.js +1 -1
- package/.next/server/app/api/lounge/route.js +1 -1
- package/.next/server/app/api/mcp/route.js +2 -2
- package/.next/server/app/api/model-roles/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/projects/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/settings/route.js +1 -1
- package/.next/server/app/api/skills/check/route.js +1 -1
- package/.next/server/app/api/skills/install/route.js +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/update/route.js +1 -1
- package/.next/server/app/api/theme/route.js +1 -1
- package/.next/server/app/api/tool-image/route.js +1 -1
- package/.next/server/app/api/update-maintenance/route.js +1 -1
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/api/web6/consults/route.js +1 -1
- package/.next/server/app/api/worktrees/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/office/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview.html +1 -1
- package/.next/server/app/office-preview.rsc +2 -2
- package/.next/server/app/office-preview.segments/_full.segment.rsc +2 -2
- package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/_index.segment.rsc +2 -2
- package/.next/server/app/office-preview.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
- package/.next/server/app/office.html +1 -1
- package/.next/server/app/office.rsc +4 -4
- package/.next/server/app/office.segments/_full.segment.rsc +4 -4
- package/.next/server/app/office.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office.segments/_index.segment.rsc +2 -2
- package/.next/server/app/office.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office.segments/office/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/office.segments/office.segment.rsc +1 -1
- 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 +15 -15
- package/.next/server/chunks/1008.js +3 -3
- package/.next/server/chunks/5489.js +2 -2
- package/.next/server/chunks/5582.js +2 -2
- 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/{9575-9196474bb9e5c752.js → 9575-c7186831a3bfe17c.js} +2 -2
- package/.next/static/chunks/app/{layout-713017c65de14601.js → layout-bfc26328305a8fac.js} +1 -1
- package/.next/trace +108 -108
- package/.next/trace-build +1 -1
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +1 -1
- package/Tools/OMP_Global_Config/agent/agents/maker.md +14 -11
- package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +1 -1
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/index.ts +12 -4
- package/Tools/OMP_Global_Config/agent/extensions/korean-reply-guard.ts +2 -2
- package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +2 -2
- package/Tools/OMP_Global_Config/agent/rules/meta-harness.md +2 -2
- package/Tools/OMP_Global_Config/agent/rules/subagent.md +5 -3
- package/Tools/OMP_Global_Config/agent/sop/_common.md +12 -9
- package/Tools/OMP_Global_Config/agent/sop/maker.md +1 -1
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +7 -28
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/wsl-git.ts +96 -0
- package/Tools/OMP_Global_Config/agent/tools/windows-build/index.ts +330 -0
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +133 -0
- package/package.json +3 -2
- /package/.next/static/{S8tJkE71e2D0DgH4X1s0S → HCr7GGX3_oM-hKknNcPSW}/_buildManifest.js +0 -0
- /package/.next/static/{S8tJkE71e2D0DgH4X1s0S → HCr7GGX3_oM-hKknNcPSW}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
|
|
2
|
+
import { access, mkdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { posix, win32 } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* WSL(또는 Windows)에서 Windows 전용 프로젝트(VB.NET/.NET Framework WinForms, C# 솔루션)를 MSBuild로 빌드한다.
|
|
7
|
+
* 빌드만 한다: 산출물 실행·복사·배포를 하지 않고 프로젝트 파일은 읽기만 한다.
|
|
8
|
+
* 콘솔 출력은 PowerShell 5.1/콘솔 코드페이지(CP949)에 따라 깨질 수 있어 쓰지 않고(-noconlog),
|
|
9
|
+
* MSBuild 파일 로거가 UTF-8로 쓴 로그에서 오류·경고를 읽는다.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface WindowsBuildParams {
|
|
13
|
+
project: string;
|
|
14
|
+
configuration?: string;
|
|
15
|
+
platform?: string;
|
|
16
|
+
target?: string;
|
|
17
|
+
msbuildProperties?: Record<string, string>;
|
|
18
|
+
restore?: boolean;
|
|
19
|
+
maxErrors?: number;
|
|
20
|
+
timeoutSeconds?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ExecResult { code: number; stdout: string; stderr?: string; killed?: boolean }
|
|
24
|
+
export type Exec = (command: string, args: string[], options: { cwd: string; timeout?: number }) => Promise<ExecResult>;
|
|
25
|
+
|
|
26
|
+
export interface BuildDeps {
|
|
27
|
+
exec: Exec;
|
|
28
|
+
cwd: string;
|
|
29
|
+
/** WSL이면 경로를 wslpath로 바꾼다. Windows 네이티브면 변환 없이 그대로 쓴다. */
|
|
30
|
+
wsl: boolean;
|
|
31
|
+
exists: (path: string) => Promise<boolean>;
|
|
32
|
+
mkdirp: (path: string) => Promise<void>;
|
|
33
|
+
readText: (path: string) => Promise<string | undefined>;
|
|
34
|
+
env: (name: string) => string | undefined;
|
|
35
|
+
now: () => number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BuildDiagnostics {
|
|
39
|
+
errorCount: number;
|
|
40
|
+
warningCount: number;
|
|
41
|
+
errors: string[];
|
|
42
|
+
warnings: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BuildResult extends BuildDiagnostics {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
exitCode: number;
|
|
48
|
+
killed: boolean;
|
|
49
|
+
project: string;
|
|
50
|
+
msbuildPath: string;
|
|
51
|
+
msbuildVersion: string;
|
|
52
|
+
logPath: string;
|
|
53
|
+
logPathPosix: string;
|
|
54
|
+
logMissing: boolean;
|
|
55
|
+
consoleTail?: string[];
|
|
56
|
+
durationMs: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const DEFAULT_MAX_ERRORS = 20;
|
|
60
|
+
const MAX_WARNING_LINES = 5;
|
|
61
|
+
const DEFAULT_TIMEOUT_SECONDS = 600;
|
|
62
|
+
const MAX_TIMEOUT_SECONDS = 3600;
|
|
63
|
+
const PROJECT_EXTENSION = /\.(sln|slnx|\w*proj)$/i;
|
|
64
|
+
const DRIVE_PATH = /^[A-Za-z]:[\\/]/;
|
|
65
|
+
const WSL_DRIVE_PATH = /^\/mnt\/[a-z]\//i;
|
|
66
|
+
const VSWHERE_RELATIVE = "Microsoft Visual Studio\\Installer\\vswhere.exe";
|
|
67
|
+
const DEFAULT_PROGRAM_FILES_X86 = "C:\\Program Files (x86)";
|
|
68
|
+
// 빌드만 한다: 실행·배포 계열 target과 빌드 전후 명령·시작 프로그램 속성은 거부한다.
|
|
69
|
+
const DENIED_TARGETS: Record<string, true> = { run: true, deploy: true, publish: true, install: true };
|
|
70
|
+
const DENIED_PROPERTIES: Record<string, true> = {
|
|
71
|
+
postbuildevent: true, prebuildevent: true, startprogram: true, startaction: true, deployonbuild: true, deploytarget: true,
|
|
72
|
+
};
|
|
73
|
+
const DIAGNOSTIC_LINE = /^\s*(?:(?<origin>.*?):\s+)?(?<severity>error|warning)(?:\s+(?<code>[A-Za-z]+\d+))?\s*:\s*(?<message>.*?)\s*$/i;
|
|
74
|
+
|
|
75
|
+
export interface ProjectInput { windows?: string; posix?: string }
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 프로젝트 경로를 받아 드라이브 위 Windows 경로 또는 `/mnt/<드라이브>/` POSIX 경로로 정규화한다.
|
|
79
|
+
* WSL 안(ext4)·UNC 경로는 MSBuild가 안정적으로 열지 못하므로 거부한다. 파일 존재는 여기서 보지 않는다.
|
|
80
|
+
*/
|
|
81
|
+
export function normalizeProjectInput(input: string, cwd: string, platform: NodeJS.Platform = process.platform): ProjectInput {
|
|
82
|
+
const value = input.trim().replace(/^"(.*)"$/, "$1");
|
|
83
|
+
if (!value) throw new Error("windows_build: project 경로가 비어 있다.");
|
|
84
|
+
let result: ProjectInput;
|
|
85
|
+
if (DRIVE_PATH.test(value)) {
|
|
86
|
+
result = { windows: win32.normalize(value.replace(/\//g, "\\")) };
|
|
87
|
+
} else if (/^\\\\/.test(value)) {
|
|
88
|
+
throw new Error(`windows_build: UNC 경로는 지원하지 않는다: ${value}`);
|
|
89
|
+
} else if (platform === "win32") {
|
|
90
|
+
result = { windows: win32.resolve(cwd, value) };
|
|
91
|
+
} else {
|
|
92
|
+
const resolved = posix.resolve(cwd, value);
|
|
93
|
+
if (!WSL_DRIVE_PATH.test(resolved)) {
|
|
94
|
+
throw new Error(`windows_build: /mnt/<드라이브>/ 아래 Windows 드라이브 프로젝트만 빌드한다(받은 경로: ${resolved}).`);
|
|
95
|
+
}
|
|
96
|
+
result = { posix: resolved };
|
|
97
|
+
}
|
|
98
|
+
const name = result.windows ?? result.posix!;
|
|
99
|
+
if (!PROJECT_EXTENSION.test(name)) {
|
|
100
|
+
throw new Error(`windows_build: project는 .sln/.slnx 또는 *proj 파일이어야 한다: ${name}`);
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function validateToken(label: string, value: string, pattern: RegExp): string {
|
|
106
|
+
const trimmed = value.trim();
|
|
107
|
+
if (!trimmed || !pattern.test(trimmed)) throw new Error(`windows_build: ${label} 값이 올바르지 않다: ${JSON.stringify(value)}`);
|
|
108
|
+
return trimmed;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** MSBuild 명령줄 인자를 만든다. project·logFile은 이미 Windows 경로여야 한다. */
|
|
112
|
+
export function buildMsbuildArgs(params: Omit<WindowsBuildParams, "project">, project: string, logFile: string): string[] {
|
|
113
|
+
if (logFile.includes(";")) throw new Error(`windows_build: 로그 경로에 ';'가 있어 MSBuild 파일 로거에 넘길 수 없다: ${logFile}`);
|
|
114
|
+
const args = ["-nologo", "-m", "-nodeReuse:false", "-noconlog", `-flp:logfile=${logFile};encoding=UTF-8;verbosity=minimal`];
|
|
115
|
+
if (params.restore) args.push("-restore");
|
|
116
|
+
const properties: Array<[string, string]> = [];
|
|
117
|
+
if (params.configuration !== undefined) properties.push(["Configuration", validateToken("configuration", params.configuration, /^[\w .+-]+$/)]);
|
|
118
|
+
if (params.platform !== undefined) properties.push(["Platform", validateToken("platform", params.platform, /^[\w .+-]+$/)]);
|
|
119
|
+
for (const [name, value] of Object.entries(params.msbuildProperties ?? {})) {
|
|
120
|
+
if (!/^[A-Za-z_][\w.]*$/.test(name)) throw new Error(`windows_build: 속성 이름이 올바르지 않다: ${JSON.stringify(name)}`);
|
|
121
|
+
if (DENIED_PROPERTIES[name.toLowerCase()]) throw new Error(`windows_build: 빌드만 허용하므로 속성 ${name}은 쓸 수 없다.`);
|
|
122
|
+
if (/[\r\n\0]/.test(value)) throw new Error(`windows_build: 속성 ${name} 값에 줄바꿈이 있다.`);
|
|
123
|
+
properties.push([name, value]);
|
|
124
|
+
}
|
|
125
|
+
for (const [name, value] of properties) args.push(`-p:${name}=${value}`);
|
|
126
|
+
if (params.target !== undefined) {
|
|
127
|
+
const targets = params.target.split(/[;,]/).map((item) => item.trim()).filter(Boolean);
|
|
128
|
+
if (targets.length === 0) throw new Error("windows_build: target이 비어 있다.");
|
|
129
|
+
for (const item of targets) {
|
|
130
|
+
if (!/^[A-Za-z_][\w.-]*$/.test(item)) throw new Error(`windows_build: target 이름이 올바르지 않다: ${JSON.stringify(item)}`);
|
|
131
|
+
if (DENIED_TARGETS[item.toLowerCase()]) throw new Error(`windows_build: 빌드만 허용하므로 target ${item}은 쓸 수 없다.`);
|
|
132
|
+
}
|
|
133
|
+
args.push(`-t:${targets.join(";")}`);
|
|
134
|
+
}
|
|
135
|
+
args.push(project);
|
|
136
|
+
return args;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* MSBuild 로그·콘솔 텍스트에서 `origin: error CODE: message` / `warning` 줄을 모은다.
|
|
141
|
+
* 같은 줄이 요약부에 다시 나오므로 줄 전체가 같으면 한 번만 센다. 진단 접두어(error/warning)는 현지화되지 않아
|
|
142
|
+
* 한국어 MSBuild 로그에서도 같다. 요약의 "N 오류" 문구는 현지화되므로 쓰지 않는다.
|
|
143
|
+
*/
|
|
144
|
+
export function parseBuildLog(text: string, maxErrors = DEFAULT_MAX_ERRORS): BuildDiagnostics {
|
|
145
|
+
const errors = new Set<string>();
|
|
146
|
+
const warnings = new Set<string>();
|
|
147
|
+
for (const raw of text.replace(/^\uFEFF/, "").split(/\r?\n/)) {
|
|
148
|
+
const match = DIAGNOSTIC_LINE.exec(raw);
|
|
149
|
+
if (!match?.groups) continue;
|
|
150
|
+
(match.groups.severity.toLowerCase() === "error" ? errors : warnings).add(raw.trim());
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
errorCount: errors.size,
|
|
154
|
+
warningCount: warnings.size,
|
|
155
|
+
errors: [...errors].slice(0, maxErrors),
|
|
156
|
+
warnings: [...warnings].slice(0, Math.min(maxErrors, MAX_WARNING_LINES)),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** vswhere 출력에서 첫 MSBuild.exe 경로를 고른다. 없으면 undefined. */
|
|
161
|
+
export function parseVswhereOutput(stdout: string): string | undefined {
|
|
162
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).find((line) => /msbuild\.exe$/i.test(line));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function nonEmptyLines(text: string): string[] {
|
|
166
|
+
return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function describeFailure(label: string, result: ExecResult): string {
|
|
170
|
+
return `${label} (exit ${result.code}): ${(result.stderr || result.stdout || "").trim() || "출력 없음"}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function runWindowsBuild(params: WindowsBuildParams, deps: BuildDeps): Promise<BuildResult> {
|
|
174
|
+
const { exec, cwd } = deps;
|
|
175
|
+
const maxErrors = Math.max(1, Math.min(200, Math.trunc(params.maxErrors ?? DEFAULT_MAX_ERRORS)));
|
|
176
|
+
const timeoutSeconds = Math.max(10, Math.min(MAX_TIMEOUT_SECONDS, Math.trunc(params.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS)));
|
|
177
|
+
const input = normalizeProjectInput(params.project, cwd, deps.wsl ? "linux" : "win32");
|
|
178
|
+
|
|
179
|
+
const convert = async (flag: "-w" | "-u", path: string): Promise<string> => {
|
|
180
|
+
if (!deps.wsl) return path;
|
|
181
|
+
const result = await exec("wslpath", [flag, path], { cwd });
|
|
182
|
+
const converted = result.stdout.trim();
|
|
183
|
+
if (result.code !== 0 || !converted) throw new Error(describeFailure(`windows_build: wslpath ${flag} ${path} 변환 실패`, result));
|
|
184
|
+
return converted;
|
|
185
|
+
};
|
|
186
|
+
const toWindows = (path: string) => convert("-w", path);
|
|
187
|
+
const toPosix = (path: string) => convert("-u", path);
|
|
188
|
+
|
|
189
|
+
const projectWindows = input.windows ?? await toWindows(input.posix!);
|
|
190
|
+
const projectPosix = input.posix ?? await toPosix(input.windows!);
|
|
191
|
+
if (!(await deps.exists(projectPosix))) throw new Error(`windows_build: 프로젝트 파일이 없다: ${projectPosix}`);
|
|
192
|
+
const projectDir = deps.wsl ? posix.dirname(projectPosix) : win32.dirname(projectWindows);
|
|
193
|
+
|
|
194
|
+
// Windows 쪽 TEMP와 Program Files (x86). cmd를 UTF-8 코드페이지로 바꿔 한국어 사용자 이름 경로도 깨지지 않게 읽는다.
|
|
195
|
+
let tempDir: string | undefined;
|
|
196
|
+
let programFilesX86: string | undefined;
|
|
197
|
+
if (deps.wsl) {
|
|
198
|
+
const result = await exec("cmd.exe", ["/c", "chcp 65001>nul&echo %TEMP%&echo %ProgramFiles(x86)%"], { cwd: projectDir });
|
|
199
|
+
const lines = nonEmptyLines(result.stdout);
|
|
200
|
+
if (result.code === 0 && DRIVE_PATH.test(lines[0] ?? "")) tempDir = lines[0];
|
|
201
|
+
if (result.code === 0 && DRIVE_PATH.test(lines[1] ?? "")) programFilesX86 = lines[1];
|
|
202
|
+
} else {
|
|
203
|
+
tempDir = deps.env("TEMP");
|
|
204
|
+
programFilesX86 = deps.env("ProgramFiles(x86)");
|
|
205
|
+
}
|
|
206
|
+
if (!tempDir) throw new Error("windows_build: Windows TEMP 폴더를 알아내지 못했다.");
|
|
207
|
+
programFilesX86 ??= DEFAULT_PROGRAM_FILES_X86;
|
|
208
|
+
|
|
209
|
+
const vswhereWindows = win32.join(programFilesX86, VSWHERE_RELATIVE);
|
|
210
|
+
const vswherePosix = await toPosix(vswhereWindows);
|
|
211
|
+
if (!(await deps.exists(vswherePosix))) {
|
|
212
|
+
throw new Error(`windows_build: MSBuild를 찾지 못했다. vswhere.exe가 없다(${vswhereWindows}). Visual Studio 또는 Build Tools(MSBuild 구성 요소)를 설치해야 한다.`);
|
|
213
|
+
}
|
|
214
|
+
const found = await exec(
|
|
215
|
+
vswherePosix,
|
|
216
|
+
["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild", "-find", "MSBuild\\**\\Bin\\MSBuild.exe", "-utf8"],
|
|
217
|
+
{ cwd: projectDir },
|
|
218
|
+
);
|
|
219
|
+
const msbuildWindows = found.code === 0 ? parseVswhereOutput(found.stdout) : undefined;
|
|
220
|
+
if (!msbuildWindows) {
|
|
221
|
+
throw new Error(`windows_build: MSBuild를 찾지 못했다. vswhere가 MSBuild 구성 요소가 있는 설치를 찾지 못했다${found.code === 0 ? "" : ` (exit ${found.code})`}. Visual Studio 또는 Build Tools(MSBuild 구성 요소)를 설치해야 한다.`);
|
|
222
|
+
}
|
|
223
|
+
const msbuildPosix = await toPosix(msbuildWindows);
|
|
224
|
+
if (!(await deps.exists(msbuildPosix))) throw new Error(`windows_build: vswhere가 알려준 MSBuild가 없다: ${msbuildWindows}`);
|
|
225
|
+
const versionResult = await exec(msbuildPosix, ["-version", "-nologo"], { cwd: projectDir });
|
|
226
|
+
const msbuildVersion = nonEmptyLines(versionResult.stdout).at(-1) ?? "unknown";
|
|
227
|
+
|
|
228
|
+
const logDirWindows = win32.join(tempDir, "omp-windows-build");
|
|
229
|
+
const logDirPosix = await toPosix(logDirWindows);
|
|
230
|
+
await deps.mkdirp(logDirPosix);
|
|
231
|
+
const stamp = new Date(deps.now()).toISOString().replace(/\D/g, "").slice(0, 14);
|
|
232
|
+
const logName = `build-${stamp}-${Math.random().toString(36).slice(2, 8)}.log`;
|
|
233
|
+
const logPath = win32.join(logDirWindows, logName);
|
|
234
|
+
const logPathPosix = deps.wsl ? posix.join(logDirPosix, logName) : logPath;
|
|
235
|
+
|
|
236
|
+
const args = buildMsbuildArgs(params, projectWindows, logPath);
|
|
237
|
+
const started = deps.now();
|
|
238
|
+
const run = await exec(msbuildPosix, args, { cwd: projectDir, timeout: timeoutSeconds * 1000 });
|
|
239
|
+
const durationMs = deps.now() - started;
|
|
240
|
+
|
|
241
|
+
const logText = await deps.readText(logPathPosix);
|
|
242
|
+
const consoleText = `${run.stdout}\n${run.stderr ?? ""}`;
|
|
243
|
+
// 로그가 없으면 MSBuild가 빌드 전에 멈춘 것(MSB1009 프로젝트 없음 등)이라 콘솔 출력에서 읽는다.
|
|
244
|
+
const diagnostics = parseBuildLog(logText ?? consoleText, maxErrors);
|
|
245
|
+
const failed = run.code !== 0;
|
|
246
|
+
return {
|
|
247
|
+
ok: !failed,
|
|
248
|
+
exitCode: run.code,
|
|
249
|
+
killed: Boolean(run.killed),
|
|
250
|
+
project: projectWindows,
|
|
251
|
+
msbuildPath: msbuildWindows,
|
|
252
|
+
msbuildVersion,
|
|
253
|
+
logPath,
|
|
254
|
+
logPathPosix,
|
|
255
|
+
logMissing: logText === undefined,
|
|
256
|
+
...diagnostics,
|
|
257
|
+
...(failed && diagnostics.errorCount === 0 ? { consoleTail: nonEmptyLines(consoleText).slice(-15) } : {}),
|
|
258
|
+
durationMs,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function formatBuildResult(result: BuildResult, maxErrors = DEFAULT_MAX_ERRORS): string {
|
|
263
|
+
const lines: string[] = [];
|
|
264
|
+
const seconds = (result.durationMs / 1000).toFixed(1);
|
|
265
|
+
const status = result.ok ? "빌드 성공" : result.killed ? "빌드 중단(시간 초과 또는 취소)" : "빌드 실패";
|
|
266
|
+
lines.push(`${status} — exit ${result.exitCode}, ${seconds}s`);
|
|
267
|
+
lines.push(`프로젝트: ${result.project}`);
|
|
268
|
+
lines.push(`MSBuild: ${result.msbuildPath} (${result.msbuildVersion})`);
|
|
269
|
+
lines.push(`오류 ${result.errorCount}개 · 경고 ${result.warningCount}개`);
|
|
270
|
+
if (result.errors.length > 0) {
|
|
271
|
+
lines.push("오류:", ...result.errors.map((line) => ` ${line}`));
|
|
272
|
+
if (result.errorCount > result.errors.length) lines.push(` … 외 ${result.errorCount - result.errors.length}개(전체는 로그 참고, 최대 ${maxErrors}줄 표시)`);
|
|
273
|
+
}
|
|
274
|
+
if (result.warnings.length > 0) {
|
|
275
|
+
lines.push("경고(앞 일부):", ...result.warnings.map((line) => ` ${line}`));
|
|
276
|
+
}
|
|
277
|
+
if (result.consoleTail?.length) lines.push("MSBuild 콘솔 끝부분:", ...result.consoleTail.map((line) => ` ${line}`));
|
|
278
|
+
lines.push(`로그: ${result.logPath}${result.logMissing ? " (생성되지 않음)" : ""}`);
|
|
279
|
+
if (result.logPathPosix !== result.logPath) lines.push(`로그(POSIX): ${result.logPathPosix}`);
|
|
280
|
+
return lines.join("\n");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const defaultDeps = (cwd: string, exec: Exec): BuildDeps => ({
|
|
284
|
+
exec,
|
|
285
|
+
cwd,
|
|
286
|
+
wsl: process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME),
|
|
287
|
+
exists: (path) => access(path).then(() => true, () => false),
|
|
288
|
+
mkdirp: async (path) => { await mkdir(path, { recursive: true }); },
|
|
289
|
+
readText: (path) => readFile(path, "utf8").then((text) => text, () => undefined),
|
|
290
|
+
env: (name) => process.env[name],
|
|
291
|
+
now: () => Date.now(),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const factory: CustomToolFactory = (pi) => ({
|
|
295
|
+
name: "windows_build",
|
|
296
|
+
label: "Windows Build",
|
|
297
|
+
loadMode: "essential",
|
|
298
|
+
description:
|
|
299
|
+
"Build a Windows-only project (.sln/.slnx/*proj, e.g. VB.NET or C# WinForms on a /mnt/<drive> or X:\\ path) with Windows MSBuild from WSL. "
|
|
300
|
+
+ "Locates MSBuild through vswhere.exe, converts paths with wslpath, runs `MSBuild -nologo -m` with a UTF-8 file logger, and returns exit code, MSBuild path/version, error/warning counts, the first error lines and the log path. "
|
|
301
|
+
+ "Build only: never runs the built program, never touches devices, never deploys or copies outputs; Run/Deploy/Publish/Install targets and Pre/PostBuildEvent properties are rejected. "
|
|
302
|
+
+ "`restore: true` adds -restore (NuGet network access); default is off. Build writes bin/obj inside the project folder as a normal build does.",
|
|
303
|
+
parameters: pi.zod.object({
|
|
304
|
+
project: pi.zod.string().describe("Solution or project file: /mnt/<drive>/... or X:\\... (relative paths resolve from the session cwd)"),
|
|
305
|
+
configuration: pi.zod.string().optional().describe("Configuration property, e.g. Debug or Release"),
|
|
306
|
+
platform: pi.zod.string().optional().describe("Platform property, e.g. x86, x64, Any CPU"),
|
|
307
|
+
target: pi.zod.string().optional().describe("MSBuild target(s), e.g. Rebuild or Clean;Build. Run/Deploy/Publish/Install are rejected"),
|
|
308
|
+
// Not named `properties`: the core wire-schema pass turns the empty `properties` map of a
|
|
309
|
+
// parameter with that name into `true`, and Anthropic/OpenAI then reject the whole request.
|
|
310
|
+
msbuildProperties: pi.zod.record(pi.zod.string()).optional().describe("Extra -p:Name=Value properties"),
|
|
311
|
+
restore: pi.zod.boolean().optional().describe("Add -restore before building (default false)"),
|
|
312
|
+
maxErrors: pi.zod.number().optional().describe("Number of error lines to return (default 20, max 200)"),
|
|
313
|
+
timeoutSeconds: pi.zod.number().optional().describe("Build time limit in seconds (default 600, max 3600)"),
|
|
314
|
+
}),
|
|
315
|
+
|
|
316
|
+
async execute(_toolCallId, params, onUpdate, _ctx, signal) {
|
|
317
|
+
const exec: Exec = (command, args, options) => pi.exec(command, args, { ...options, signal });
|
|
318
|
+
onUpdate?.({
|
|
319
|
+
content: [{ type: "text", text: "MSBuild를 찾아 빌드하는 중입니다." }],
|
|
320
|
+
details: { phase: "build", project: params.project },
|
|
321
|
+
});
|
|
322
|
+
const result = await runWindowsBuild(params as WindowsBuildParams, defaultDeps(pi.cwd, exec));
|
|
323
|
+
return {
|
|
324
|
+
content: [{ type: "text", text: formatBuildResult(result, Math.max(1, Math.min(200, Math.trunc(params.maxErrors ?? DEFAULT_MAX_ERRORS)))) }],
|
|
325
|
+
details: result,
|
|
326
|
+
};
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
export default factory;
|
|
@@ -1262,6 +1262,21 @@ Project-wide validation is the main agent's job, run once after all subagents la
|
|
|
1262
1262
|
anchor: `- \`app.relay: true\`: drive the user's Chrome through the omp relay. \`app.target\` selects a tab by URL/title substring; without it, the visible tab is adopted. Opening with \`url\` navigates that adopted tab.`,
|
|
1263
1263
|
patched: `- \`app.relay: true\`: drive the user's Chrome through the omp relay. \`app.target\` selects a tab by URL/title substring; without it, the relay opens a new tab for this \`name\` and leaves the user's tabs alone. Reopening the same \`name\` reuses that tab; closing it releases the tab without closing the page.`,
|
|
1264
1264
|
},
|
|
1265
|
+
{
|
|
1266
|
+
// 2026-10-09 사용자 결정: computer 승인 창은 control.acquire에서만 뜬다. 모델이
|
|
1267
|
+
// BackgroundUnavailable을 우회하려고 acquire를 불러 매번 승인을 묻게 됐다(Windows 시절
|
|
1268
|
+
// 세션은 동작별 takeover만 썼다). 승인 게이트는 그대로 두고, 동작별 takeover를 먼저 쓰게 안내한다.
|
|
1269
|
+
file: "src/prompts/tools/computer.md",
|
|
1270
|
+
marker: "NEVER acquire control to work around",
|
|
1271
|
+
anchor: "- `computer.control.acquire({reason})` requires live human confirmation for task-scoped foreground control;",
|
|
1272
|
+
patched: "- NEVER acquire control to work around `BackgroundUnavailable` or a background no-op: retry that one call with `{ takeover: true }`, which needs no confirmation. Acquire only when the user asks for sustained foreground control.\n- `computer.control.acquire({reason})` requires live human confirmation for task-scoped foreground control;",
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
file: "src/prompts/system/computer-use.md",
|
|
1276
|
+
marker: "Per-call `takeover: true`",
|
|
1277
|
+
anchor: "- Task-scoped control requires live human confirmation; release it when finished.",
|
|
1278
|
+
patched: "- Per-call `takeover: true` after `BackgroundUnavailable` needs no confirmation; use it instead of `computer.control.acquire`. Acquired control requires live human confirmation; acquire only on user request and release it when finished.",
|
|
1279
|
+
},
|
|
1265
1280
|
{
|
|
1266
1281
|
// 빚의 정체(어느 steer 메시지인가)를 표현하는 최소 타입.
|
|
1267
1282
|
file: "src/session/irc-bridge.ts",
|
|
@@ -4737,6 +4752,95 @@ class LocalTextBackend implements TextBackend {`,
|
|
|
4737
4752
|
patched: `import type { Settings } from "../config/settings";
|
|
4738
4753
|
import { cfgJudgmentProvider } from "../config/model-settings";`,
|
|
4739
4754
|
},
|
|
4755
|
+
{
|
|
4756
|
+
// 2026-10-09 WSL 회귀 감사 3.5: JEV `find` 판정이 typesafe 경로에서 23.8% 400("Invalid decision request.
|
|
4757
|
+
// Send only model, state, and bounded typed questions; ...")으로 실패하고 openrouter 로 넘어갔다. typesafe
|
|
4758
|
+
// provider 는 Experiential Labs gateway(models.yml)로 가고, 그 decoder 는 요청당 질문을 32개까지만 받는다
|
|
4759
|
+
// (experientiallabs/experiential exp/runtime/gateway/decisions_contracts.py MAX_DECISION_QUESTIONS = 32).
|
|
4760
|
+
// find 는 한 요청에 최대 64(NAME_BATCH·DIR_BATCH)·48(SKETCH_CARDS_MAX)개를 보낸다. 세션 기록에서 400 62건은
|
|
4761
|
+
// 전부 35문항 이상, 성공 184건은 전부 30문항 이하였다. 질문은 같은 state 에 대해 서로 독립으로 판정되므로
|
|
4762
|
+
// 32개씩 나눠 같은 state 로 병렬 전송하고 응답을 합친다. jfind 배치 상수와 벤치마크한 state 배치는 그대로다.
|
|
4763
|
+
// 32개 이하 요청은 본문 바이트가 그대로이고, openrouter-decisions 경로(61문항도 받았다)는 나누지 않는다.
|
|
4764
|
+
// 검증: core-typesafe-split-test.ts.
|
|
4765
|
+
file: "../pi-ai/src/judgment/typesafe.ts",
|
|
4766
|
+
marker: "const TYPESAFE_MAX_QUESTIONS = 32;",
|
|
4767
|
+
anchor: "\treturn Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_MAX_MS);\n}\n\nexport class TypeSafeJudge implements Judge {\n",
|
|
4768
|
+
patched: [
|
|
4769
|
+
"\treturn Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_MAX_MS);",
|
|
4770
|
+
"}",
|
|
4771
|
+
"",
|
|
4772
|
+
"/**",
|
|
4773
|
+
" * CUELO: questions per request on the `typesafe` route. CUELO points that route at the Experiential Labs",
|
|
4774
|
+
" * gateway, whose decoder admits at most 32 questions (`MAX_DECISION_QUESTIONS`) and answers a larger set",
|
|
4775
|
+
" * with 400 \"Invalid decision request\". Questions are judged independently against the shared state, so a",
|
|
4776
|
+
" * larger set goes out as parts that each repeat the state.",
|
|
4777
|
+
" */",
|
|
4778
|
+
"const TYPESAFE_MAX_QUESTIONS = 32;",
|
|
4779
|
+
"",
|
|
4780
|
+
"/** `questions` itself when it has at most `max` entries, else parts of at most `max` in insertion order. */",
|
|
4781
|
+
"function questionParts(questions: Questions, max: number): Questions[] {",
|
|
4782
|
+
"\tconst ids = Object.keys(questions);",
|
|
4783
|
+
"\tif (ids.length <= max) return [questions];",
|
|
4784
|
+
"\tconst parts: Questions[] = [];",
|
|
4785
|
+
"\tfor (let start = 0; start < ids.length; start += max) {",
|
|
4786
|
+
"\t\tconst part: Questions = {};",
|
|
4787
|
+
"\t\tfor (const id of ids.slice(start, start + max)) part[id] = questions[id]!;",
|
|
4788
|
+
"\t\tparts.push(part);",
|
|
4789
|
+
"\t}",
|
|
4790
|
+
"\treturn parts;",
|
|
4791
|
+
"}",
|
|
4792
|
+
"",
|
|
4793
|
+
"/** The parts' responses as one: answers merged, token counts and billed cost summed where reported. */",
|
|
4794
|
+
"function mergeResponses(responses: SystemOneResponse[]): SystemOneResponse {",
|
|
4795
|
+
"\tif (responses.length === 1) return responses[0]!;",
|
|
4796
|
+
"\tconst total = (pick: (usage: SystemOneResponse[\"usage\"]) => number | undefined): number | undefined => {",
|
|
4797
|
+
"\t\tlet sum: number | undefined;",
|
|
4798
|
+
"\t\tfor (const response of responses) {",
|
|
4799
|
+
"\t\t\tconst value = pick(response.usage);",
|
|
4800
|
+
"\t\t\tif (value !== undefined) sum = (sum ?? 0) + value;",
|
|
4801
|
+
"\t\t}",
|
|
4802
|
+
"\t\treturn sum;",
|
|
4803
|
+
"\t};",
|
|
4804
|
+
"\treturn {",
|
|
4805
|
+
"\t\tmodel: responses[0]!.model,",
|
|
4806
|
+
"\t\tanswers: Object.assign({}, ...responses.map(response => response.answers)),",
|
|
4807
|
+
"\t\tusage: {",
|
|
4808
|
+
"\t\t\tinput_tokens: total(usage => usage.input_tokens),",
|
|
4809
|
+
"\t\t\toutput_tokens: total(usage => usage.output_tokens),",
|
|
4810
|
+
"\t\t\tcost: total(usage => usage.cost),",
|
|
4811
|
+
"\t\t},",
|
|
4812
|
+
"\t};",
|
|
4813
|
+
"}",
|
|
4814
|
+
"",
|
|
4815
|
+
"export class TypeSafeJudge implements Judge {",
|
|
4816
|
+
"",
|
|
4817
|
+
].join("\n"),
|
|
4818
|
+
},
|
|
4819
|
+
{
|
|
4820
|
+
// 위 항목과 짝: judge 가 질문 묶음을 나눠 보내고 응답을 합친다. 나누지 않으면 parts 는 request.questions
|
|
4821
|
+
// 그 객체 하나라 본문이 원래 식 `{ state, model, questions }` 과 바이트까지 같다.
|
|
4822
|
+
file: "../pi-ai/src/judgment/typesafe.ts",
|
|
4823
|
+
marker: "// CUELO: past the typesafe question bound",
|
|
4824
|
+
anchor: "\t\tconst body = JSON.stringify({ state: request.state, model: this.model, questions: request.questions });\n\t\tconst signal = options?.signal;\n\t\tconst response = await withAuth(\n\t\t\tthis.#apiKey,\n\t\t\tkey => this.#attempt<SystemOneResponse>(JUDGMENT_ROUTES[this.api], body, key, signal),\n\t\t\t{ signal },\n\t\t);\n",
|
|
4825
|
+
patched: [
|
|
4826
|
+
"\t\tconst signal = options?.signal;",
|
|
4827
|
+
"\t\t// CUELO: past the typesafe question bound the parts run in parallel and merge into one response.",
|
|
4828
|
+
"\t\tconst parts = questionParts(request.questions, this.api === TYPESAFE_PROVIDER ? TYPESAFE_MAX_QUESTIONS : Infinity);",
|
|
4829
|
+
"\t\tconst response = mergeResponses(",
|
|
4830
|
+
"\t\t\tawait Promise.all(",
|
|
4831
|
+
"\t\t\t\tparts.map(questions => {",
|
|
4832
|
+
"\t\t\t\t\tconst body = JSON.stringify({ state: request.state, model: this.model, questions });",
|
|
4833
|
+
"\t\t\t\t\treturn withAuth(",
|
|
4834
|
+
"\t\t\t\t\t\tthis.#apiKey,",
|
|
4835
|
+
"\t\t\t\t\t\tkey => this.#attempt<SystemOneResponse>(JUDGMENT_ROUTES[this.api], body, key, signal),",
|
|
4836
|
+
"\t\t\t\t\t\t{ signal },",
|
|
4837
|
+
"\t\t\t\t\t);",
|
|
4838
|
+
"\t\t\t\t}),",
|
|
4839
|
+
"\t\t\t),",
|
|
4840
|
+
"\t\t);",
|
|
4841
|
+
"",
|
|
4842
|
+
].join("\n"),
|
|
4843
|
+
},
|
|
4740
4844
|
{
|
|
4741
4845
|
// 18.2.7 에는 `providers.judgmentProvider` schema 항목 자체가 없다(legacy key 목록에만
|
|
4742
4846
|
// 남아 migration 이 소비한다). 이 빌드의 로컬 선택 설정으로 최소 enum 만 되살린다:
|
|
@@ -6070,6 +6174,35 @@ export function hostNextServerEnvUnsets(
|
|
|
6070
6174
|
pty: ptyRequest,
|
|
6071
6175
|
`,
|
|
6072
6176
|
},
|
|
6177
|
+
{
|
|
6178
|
+
// 2026-10-09 WSL 회귀 감사 3.3: WSL bash 도구에서 wsl.exe 자체 메시지가 UTF-16LE 로 나와 NUL·U+FFFD 로 깨졌다.
|
|
6179
|
+
// bash 출력은 pi-natives(Rust)가 손실 UTF-8 로 풀어 문자열로 넘기므로 JS 에서는 원래 바이트를 되살릴 수 없다.
|
|
6180
|
+
// wsl.exe 는 WSL_UTF8=1 이면 UTF-8 로 쓰지만, Linux 변수는 WSLENV 에 이름이 있어야 Windows 프로세스에 닿는다
|
|
6181
|
+
// (실측: `WSL_UTF8=1 wsl.exe --version` 은 그대로 UTF-16LE, `WSLENV=WSL_UTF8` 을 더하면 UTF-8, `WSL_UTF8/u` 는 UTF-16LE).
|
|
6182
|
+
// linux 이고 WSL_DISTRO_NAME 이 있으면 기존 WSLENV 항목은 남긴 채 WSL_UTF8 을 한 번 덧붙인다. 서버 env 나 호출자가
|
|
6183
|
+
// 정한 WSL_UTF8 은 그 값을 그대로 넘기고, 없을 때만 1 로 둔다(서비스 env 가 WSL_UTF8=1 만 정해도 이 경로로 닿는다).
|
|
6184
|
+
// 호출자가 넘긴 WSLENV 는 그대로 이긴다. 비WSL·win32 경로는 그대로다.
|
|
6185
|
+
// PowerShell 등의 CP949 콘솔 출력은 범위 밖이다(Main 결정: iconv 안내 유지). 검증: core-wsl-utf8-env-test.ts.
|
|
6186
|
+
file: "src/exec/non-interactive-env.ts",
|
|
6187
|
+
marker: "// CUELO: on WSL, wsl.exe writes its own messages as UTF-16LE",
|
|
6188
|
+
anchor: "\tif (platform !== \"win32\") {\n\t\treturn overrides ? { ...base, ...overrides } : base;\n\t}\n",
|
|
6189
|
+
patched: [
|
|
6190
|
+
"\tif (platform !== \"win32\") {",
|
|
6191
|
+
"\t\t// CUELO: on WSL, wsl.exe writes its own messages as UTF-16LE, which the bash tool shows as NUL/U+FFFD",
|
|
6192
|
+
"\t\t// mojibake, unless WSL_UTF8=1 reaches it. Windows processes see only the Linux variables WSLENV names, so",
|
|
6193
|
+
"\t\t// WSL_UTF8 is listed there; a value the server env or the caller already set is forwarded as is.",
|
|
6194
|
+
"\t\t// A caller WSLENV still wins.",
|
|
6195
|
+
"\t\tif (platform === \"linux\" && baseEnv.WSL_DISTRO_NAME) {",
|
|
6196
|
+
"\t\t\tconst shared = (baseEnv.WSLENV ?? \"\").split(\":\").filter(name => name.length > 0);",
|
|
6197
|
+
"\t\t\tif (!shared.some(name => name.split(\"/\")[0] === \"WSL_UTF8\")) shared.push(\"WSL_UTF8\");",
|
|
6198
|
+
"\t\t\tconst utf8 = hasEnvValue(baseEnv, \"WSL_UTF8\", platform) ? {} : { WSL_UTF8: \"1\" };",
|
|
6199
|
+
"\t\t\treturn { ...base, ...utf8, WSLENV: shared.join(\":\"), ...overrides };",
|
|
6200
|
+
"\t\t}",
|
|
6201
|
+
"\t\treturn overrides ? { ...base, ...overrides } : base;",
|
|
6202
|
+
"\t}",
|
|
6203
|
+
"",
|
|
6204
|
+
].join("\n"),
|
|
6205
|
+
},
|
|
6073
6206
|
{
|
|
6074
6207
|
// 2026-09-29: 이름 있는 bash service 는 bash-executor 를 거치지 않는다. daemon broker 가
|
|
6075
6208
|
// `workerEnvFromParent(spec.env)` 로 띄우는데, 이 함수는 broker 자신의 env 위에 overlay 를 덧씌우기만
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cuelo",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "CUELO — open-source AI coding agent workspace and harness for omp (oh-my-pi)",
|
|
5
5
|
"homepage": "https://github.com/gim47656-ship-it/CUELO#readme",
|
|
6
6
|
"repository": {
|
|
@@ -139,6 +139,7 @@
|
|
|
139
139
|
"remark-math": "^6.0.0",
|
|
140
140
|
"tailwindcss": "^4.3.3",
|
|
141
141
|
"three": "^0.186.1",
|
|
142
|
-
"typescript": "^5.9.3"
|
|
142
|
+
"typescript": "^5.9.3",
|
|
143
|
+
"typescript-language-server": "^6.0.1"
|
|
143
144
|
}
|
|
144
145
|
}
|
|
File without changes
|
|
File without changes
|