cuelo 0.10.0 → 0.10.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 +9 -9
- 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/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/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 +9 -9
- package/.next/server/chunks/1008.js +1 -1
- package/.next/server/chunks/5489.js +2 -2
- package/.next/server/chunks/6429.js +1 -1
- 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-6f450839544bc6b3.js → 9575-9196474bb9e5c752.js} +2 -2
- package/.next/static/chunks/app/{layout-d8c1b10303bcd958.js → layout-713017c65de14601.js} +1 -1
- package/.next/trace +108 -108
- package/.next/trace-build +1 -1
- package/README.md +4 -1
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +3 -3
- package/Tools/OMP_Global_Config/agent/RULES.md +4 -0
- package/Tools/OMP_Global_Config/agent/agents/maker.md +12 -6
- package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +3 -3
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/index.ts +16 -1
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/powershell-syntax.ts +5 -4
- package/Tools/OMP_Global_Config/agent/extensions/draft/markdown.ts +226 -0
- package/Tools/OMP_Global_Config/agent/extensions/draft/ooxml.ts +222 -0
- package/Tools/OMP_Global_Config/agent/extensions/draft/pdf.ts +63 -0
- package/Tools/OMP_Global_Config/agent/extensions/draft/zip.ts +73 -0
- package/Tools/OMP_Global_Config/agent/extensions/draft.ts +211 -0
- package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +1 -1
- package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +16 -47
- package/Tools/OMP_Global_Config/agent/extensions/mcp-selection.ts +32 -8
- package/Tools/OMP_Global_Config/agent/extensions/skim.ts +75 -43
- package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +2 -1
- package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +5 -6
- package/Tools/OMP_Global_Config/agent/rules/omp-harness.md +1 -1
- package/Tools/OMP_Global_Config/agent/rules/subagent.md +18 -14
- package/Tools/OMP_Global_Config/agent/sop/_common.md +4 -4
- package/Tools/OMP_Global_Config/agent/sop/_writer.md +6 -0
- package/Tools/OMP_Global_Config/agent/sop/maker.md +1 -1
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +46 -3
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +182 -0
- package/bin/cuelo.js +5 -0
- package/package.json +7 -7
- /package/.next/static/{MYIe6QFbh4yP3_wc_Fetr → S8tJkE71e2D0DgH4X1s0S}/_buildManifest.js +0 -0
- /package/.next/static/{MYIe6QFbh4yP3_wc_Fetr → S8tJkE71e2D0DgH4X1s0S}/_ssgManifest.js +0 -0
package/.next/trace-build
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"run-webpack","duration":
|
|
1
|
+
[{"name":"run-webpack","duration":114018786,"timestamp":687805,"id":14,"parentId":1,"tags":{},"startTime":1791507921870,"traceId":"6c75260e77b7370d"},{"name":"run-typescript","duration":24733849,"timestamp":115113546,"id":11084,"parentId":1,"tags":{},"startTime":1791508036296,"traceId":"6c75260e77b7370d"},{"name":"static-check","duration":4561356,"timestamp":139975975,"id":11087,"parentId":1,"tags":{},"startTime":1791508061159,"traceId":"6c75260e77b7370d"},{"name":"static-generation","duration":12317561,"timestamp":144649546,"id":11259,"parentId":1,"tags":{},"startTime":1791508065832,"traceId":"6c75260e77b7370d"},{"name":"collect-build-traces","duration":21852595,"timestamp":144608304,"id":11256,"parentId":1,"tags":{},"startTime":1791508065791,"traceId":"6c75260e77b7370d"},{"name":"telemetry-flush","duration":109,"timestamp":166472512,"id":11268,"parentId":1,"tags":{},"startTime":1791508087655,"traceId":"6c75260e77b7370d"},{"name":"next-build","duration":165897246,"timestamp":575399,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1791507921758,"traceId":"6c75260e77b7370d"}]
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Use the same `~/.omp/agent` sessions, credentials and model configuration from t
|
|
|
16
16
|
|
|
17
17
|
**한국어:** CUELO는 omp(oh-my-pi)를 위한 오픈소스 AI 코딩 에이전트 작업 공간이자 하네스입니다. 웹 앱은 omp SDK를 서버 안에서 실행하고, `omp` CLI와 같은 `~/.omp/agent` 디렉터리를 씁니다. 터미널에서 하던 세션을 브라우저에서 이어 가고, 다시 터미널로 돌아가도 기록·계정·모델 설정을 공유합니다.
|
|
18
18
|
|
|
19
|
-
내장 OMP 코어는 **18.8.
|
|
19
|
+
내장 OMP 코어는 **18.8.6**입니다. 별도로 설치한 `omp` CLI의 버전은 웹 업데이트와 독립적이며, 앱에 포함된 CLI를 `omp` 명령으로 사용하는 설치에서는 웹과 같은 코어를 사용합니다.
|
|
20
20
|
|
|
21
21
|
CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규칙·실행 절차에 반영해, 사람이 같은 문제를 다시 지적하지 않아도 다음 작업에서 실수를 줄이는 것입니다. 원인을 확인하고 필요한 부분만 바꾸며, 다음 작업에서 적용과 효과를 확인하는 것까지를 지향합니다. 기록을 저장했다는 이유만으로 자동 개선이 끝났다고 보지는 않습니다.
|
|
22
22
|
|
|
@@ -173,6 +173,9 @@ bun bin/cuelo.js --reset-password # 비밀번호 재설정
|
|
|
173
173
|
|
|
174
174
|
Windows의 WSL Ubuntu에서 실행 중인 CUELO를 갱신하는 동안에는 WSL 연결을 유지하고, 열려 있는 화면에 세션 정지·준비·패키지 교체·확인 단계를 표시합니다. 서버가 내려가 응답하지 않는 동안에는 마지막으로 확인한 단계를 그대로 표시하고 아직 확인하지 못한 단계로 앞서가지 않으며, 갱신이 끝나면 원래 대화로 돌아옵니다. 새 서버가 실행 중이고 응답하면 갱신 완료 화면에서 멈추지 않고 마칩니다. 서비스가 멈춘 것을 확인한 뒤에만 파일과 프로필을 바꾸고, 실패하면 실행 중이던 버전을 그대로 두거나 이전 버전으로 복구합니다. 복구가 끝난 갱신도 완료로 인식합니다.
|
|
175
175
|
|
|
176
|
+
WSL 갱신에서는 시스템 시각이 보정되어도 실행 중인 서버를 같은 서버로 인식해, 진행 중인 대화의 중단·재개 절차를 건너뛰지 않습니다.
|
|
177
|
+
WSL 갱신은 실행 중인 서버와 분리해 새 버전을 미리 빌드한 뒤 교체합니다. 빌드 캐시는 재사용하고 다시 만들 산출물은 복사하지 않아 준비 과정의 디스크 쓰기를 줄입니다.
|
|
178
|
+
|
|
176
179
|
기존 프로필의 하네스·역할 설정은 앱 업데이트만으로 자동 덮어쓰지 않습니다. `0.6.1`의 설정 전환은 [기존 하네스 갱신](./docs/installation.md#061로-기존-하네스-갱신)을 확인하고, 반영 후 CUELO와 `omp`를 다시 시작하세요.
|
|
177
180
|
|
|
178
181
|
```bash
|
|
@@ -9,9 +9,9 @@ const MEMORY_RE = /(const phase1Model = await resolveMemoryModel\(\{[^}]*?fallba
|
|
|
9
9
|
const MEMORY_DONE = /const phase1Model = await resolveMemoryModel\(\{[^}]*?fallbackRole: "smol"/;
|
|
10
10
|
const EXPECTED = {
|
|
11
11
|
name: 'cuelo',
|
|
12
|
-
version: '0.10.
|
|
12
|
+
version: '0.10.2',
|
|
13
13
|
distribution: 'cuelo',
|
|
14
|
-
coreVersion: '18.8.
|
|
14
|
+
coreVersion: '18.8.6',
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// OMP core 18.4.12 `src/sdk.ts`: session-scoped AsyncJobManager for a process
|
|
@@ -177,7 +177,7 @@ function readNativePackage(target) {
|
|
|
177
177
|
throw new Error(`unexpected native package ${packageJson.name}@${packageJson.version}`);
|
|
178
178
|
}
|
|
179
179
|
if (!build || build.distribution !== EXPECTED.distribution || build.coreVersion !== EXPECTED.coreVersion) {
|
|
180
|
-
throw new Error('native cueloBuild metadata does not match cuelo / core 18.8.
|
|
180
|
+
throw new Error('native cueloBuild metadata does not match cuelo / core 18.8.6');
|
|
181
181
|
}
|
|
182
182
|
return packageJson;
|
|
183
183
|
}
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
- 여러 파일을 훑거나 문서·README·저장소 구조를 조사할 때는 먼저 `skim`에 질문한다. 허용 텍스트 파일
|
|
60
60
|
내용은 Google(Gemini), 그 호출이 실패해 한 번 대체되면 B.AI(DeepSeek)로도 전송된다.
|
|
61
61
|
비밀 자료를 대상으로 삼지 말고, 수정할 정확한 줄은 `read`로 확인한다.
|
|
62
|
+
- 문서 초안(md·csv·Excel·Word·PDF 보고서·표)이 필요하면 Main·Maker 모두 먼저 `draft`로 쓴다. Gemini가
|
|
63
|
+
작성해 cwd 안에 저장하고, 실패하면 DeepSeek로 한 번 대체한다(참고 텍스트는 Google 또는 B.AI로 전송).
|
|
64
|
+
만들어진 파일은 직접 열어 검토한 뒤 쓴다. 코드·설정·규칙·`HANDOFF.md`·작업 기록·정확한 줄 수정은 초안이
|
|
65
|
+
아니므로 `edit`/`write`를 쓴다. Gemini 할당량은 같은 Antigravity 계정의 vision·skim과 공유한다.
|
|
62
66
|
- **브라우저는 relay가 기본값이다.** `browser.open`에 `app: { relay: false }`를 빼면 관리형 headless가
|
|
63
67
|
아니라 **사용자의 실제 브라우저**에 탭이 열린다. 닫을 때는 `browser.close({ name })`로 자기 탭만
|
|
64
68
|
닫고 `all`·`kill`·`app.path`는 쓰지 않는다 — 사용자 창까지 닫히거나 기존 프로세스에 붙는다.
|
|
@@ -3,10 +3,10 @@ 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
4
|
model: "@implSonnet"
|
|
5
5
|
thinking-level: auto
|
|
6
|
-
tools: [read, bash, edit, write, grep, glob, skim, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, checkpoint]
|
|
6
|
+
tools: [read, bash, edit, write, grep, glob, skim, draft, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, checkpoint]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
<!-- omp-global-config:generated source-hash=
|
|
9
|
+
<!-- omp-global-config:generated source-hash=29515cbfea01
|
|
10
10
|
이 파일은 patches/build-agents.mjs 가 만든 빌드 산출물이다. 직접 수정하지 마라.
|
|
11
11
|
고칠 곳: agent/sop/maker.md · agent/sop/_writer.md · agent/sop/_common.md
|
|
12
12
|
재생성: node patches/build-agents.mjs 검사: node patches/build-agents.mjs --check
|
|
@@ -26,6 +26,12 @@ is yours end to end.
|
|
|
26
26
|
outside the brief boundary, to production or external processes, or invoking `write_memory` is
|
|
27
27
|
allowed only when the brief names the exact target and action and applicable user approval exists.
|
|
28
28
|
- Use `web_search` to discover current external information, then use `read` on the primary source.
|
|
29
|
+
- When the slice needs a document draft (Markdown, CSV, Excel, Word, PDF report or table), write it
|
|
30
|
+
with `draft(instruction, output, paths?)` first: Gemini Flash writes it (DeepSeek once on
|
|
31
|
+
failure; reference text goes to Google or B.AI) and the tool saves the file inside the cwd.
|
|
32
|
+
Open and review the produced file before you use or hand it over. Code, configuration, rules,
|
|
33
|
+
`HANDOFF.md`, your own `doc/history` record, and exact-line edits are not drafts; keep using
|
|
34
|
+
`edit`/`write` for them.
|
|
29
35
|
- Use `todo` only when the work has three or more steps. If you invoke `checkpoint`, invoke the
|
|
30
36
|
runtime-paired `rewind` before yielding.
|
|
31
37
|
- In that same first investigation, cross-check the brief itself: does changing the assigned
|
|
@@ -260,10 +266,10 @@ is yours end to end.
|
|
|
260
266
|
|
|
261
267
|
## Common Rules
|
|
262
268
|
|
|
263
|
-
- **
|
|
264
|
-
|
|
265
|
-
model IDs, code, commands, paths,
|
|
266
|
-
|
|
269
|
+
- **The task brief and every follow-up instruction from Main are written in English (user decision
|
|
270
|
+
2026-10-09). Every user-visible progress or final prose you write stays Korean, even though the
|
|
271
|
+
brief is English.** Preserve required structural keys/headings, model IDs, code, commands, paths,
|
|
272
|
+
filenames, API names, and original errors verbatim. Hidden reasoning language is not governed.
|
|
267
273
|
- The brief's allowed boundary — the projects, paths, and ownership it names — is absolute. The
|
|
268
274
|
files it points you at are where the investigation starts, not the limit of what you may read
|
|
269
275
|
or change. Inside that boundary you own the direct cause of the assigned problem even when it
|
|
@@ -425,7 +425,7 @@ export function renderCharacterSummonRouting(aliases: CharacterAlias | readonly
|
|
|
425
425
|
`[CharacterSummon] 사용자가 ${alias}을(를) 인라인으로 호출했다.`,
|
|
426
426
|
`먼저 사용자에게 자연스러운 한국어와 ${alias}의 성격·표현으로 ${target.model}을(를) 호출한다고 짧고 자연스럽게 답한다.`,
|
|
427
427
|
"그 다음 첫 관련 행동은 반드시 task child 호출이어야 한다. Main이 대신 답하거나 다른 도구로 우회하지 않는다.",
|
|
428
|
-
"task brief
|
|
428
|
+
"task brief와 후속 지시의 산문은 영어로 쓰고, child가 사용자 화면에 표시하는 진행·최종 산문은 모두 한국어다. 선택된 character voice를 그대로 따르며, `TASK_GUARD`와 필드명, 필요한 heading, 모델 ID, 코드, 명령, 경로, API, 원본 오류는 원문 그대로 둔다.",
|
|
429
429
|
"command guard가 task brief에 선택된 모델과 아래 voice 계약을 넣는다. child는 첫 provider request 전에 그 모델로 세션 한정 전환하고, 이전 voice block을 제거한 뒤 이 block을 effective system prompt에 정확히 한 번 설치한다.",
|
|
430
430
|
renderCharacterVoice(alias),
|
|
431
431
|
"호출된 캐릭터 child의 terminal 발화는 이미 사용자 화면에 표시되는 정본이다. Main은 그 본문을 다시 인용·요약·재집계하지 않고, child가 실패하거나 미완료일 때만 자기 말로 그 상태를 알린다.",
|
|
@@ -455,7 +455,7 @@ function renderMultiCharacterSummonRouting(aliases: readonly CharacterAlias[]):
|
|
|
455
455
|
: "그 다음 첫 관련 행동은 반드시 task child 호출이어야 한다. Main이 대신 답하거나 다른 도구로 우회하지 않는다.",
|
|
456
456
|
"한 번의 task 호출의 tasks[]에 tool-capable 캐릭터마다 task 하나씩을 언급 순서대로 넣는다. 각 task 본문에는 아래 exact marker를 그대로 쓴다. marker가 없거나 pending과 다른 alias이면 command guard가 차단한다. 일반 task를 같은 호출에 섞지 않는다.",
|
|
457
457
|
...capable.map((alias) => `- ${alias}: \`${summonMarkerFor(alias)}\``),
|
|
458
|
-
"task brief
|
|
458
|
+
"task brief와 후속 지시의 산문은 영어로 쓰고, child가 사용자 화면에 표시하는 진행·최종 산문은 모두 한국어다. `TASK_GUARD`와 필드명, 필요한 heading, 모델 ID, 코드, 명령, 경로, API, 원본 오류는 원문 그대로 둔다.",
|
|
459
459
|
);
|
|
460
460
|
}
|
|
461
461
|
for (const alias of webOnly) {
|
|
@@ -565,7 +565,7 @@ export function buildCharacterSummonBrief(brief: string, alias: CharacterAlias):
|
|
|
565
565
|
? ""
|
|
566
566
|
: ` Anthropic OAuth 계정은 stable storage position ${target.oauthPosition}을 먼저 pin한다.`
|
|
567
567
|
}`,
|
|
568
|
-
"- 이 task brief의
|
|
568
|
+
"- 이 task brief의 산문은 영어지만, 사용자 화면에 표시하는 모든 진행·최종 산문은 한국어다. 선택된 character voice를 그대로 따르며, `TASK_GUARD`와 필드명, 필요한 heading, 모델 ID, 코드, 명령, 경로, API, 원본 오류는 원문 그대로 둔다.",
|
|
569
569
|
"- 아래 character voice block을 effective system prompt와 최종 provider payload에 정확히 한 번 설치하고, 이전 character voice block은 제거한다. 고정 catchphrase는 만들지 않는다.",
|
|
570
570
|
renderCharacterVoice(alias),
|
|
571
571
|
"- 이 child의 terminal 발화 자체가 사용자 화면에 표시되는 정본이다. Main에게 다시 전달할 요약을 쓰거나 본문 재인용을 요청하지 말고, 원래 과제에 대한 완전한 응답을 직접 끝낸다.",
|
|
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { isToolCallEventType, type ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
5
|
+
import { resolveToCwd } from "@oh-my-pi/pi-coding-agent/tools/path-utils";
|
|
5
6
|
import {
|
|
6
7
|
CHARACTER_TARGETS,
|
|
7
8
|
malformedSummonMarkerReason,
|
|
@@ -55,6 +56,20 @@ function eventSessionCwd(ctx: { cwd?: string } | undefined): string {
|
|
|
55
56
|
return typeof ctx?.cwd === "string" && ctx.cwd ? resolve(ctx.cwd) : process.cwd();
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* bash 도구의 `cwd` 인자를 코어 bash와 같은 규칙으로 푼다. WSL에서 `E:/x`·`E:\x`는 코어가
|
|
61
|
+
* `/mnt/e/x`에서 실행하므로, `path.resolve`로 `<세션 cwd>/E:/x`를 만들면 없는 경로의 저장소를
|
|
62
|
+
* 증명하지 못해 다른 저장소의 git까지 막는다. 첫 판정 전에 명령을 동기적으로 읽어야 하므로 sync다.
|
|
63
|
+
*/
|
|
64
|
+
function bashToolCwd(sessionCwd: string, inputCwd: string): string {
|
|
65
|
+
try {
|
|
66
|
+
return resolve(resolveToCwd(inputCwd, sessionCwd));
|
|
67
|
+
} catch {
|
|
68
|
+
// 내부 URL 등 코어가 거절한 cwd는 기존 판정으로 넘긴다.
|
|
69
|
+
return resolve(sessionCwd, inputCwd);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
/**
|
|
59
74
|
* terminal id가 가리키는 현재 미보고 entry key를 await 전에 고정한다. 같은 child 이름을 다시 써도
|
|
60
75
|
* reportOwnedChildren에 exact key를 넘기므로 먼저 끝난 capture가 새 항목을 소비하지 않는다.
|
|
@@ -823,7 +838,7 @@ export default function commandGuard(pi: ExtensionAPI): void {
|
|
|
823
838
|
|
|
824
839
|
const sessionCwd = eventSessionCwd(ctx);
|
|
825
840
|
const cwd =
|
|
826
|
-
typeof event.input.cwd === "string" ?
|
|
841
|
+
typeof event.input.cwd === "string" ? bashToolCwd(sessionCwd, event.input.cwd) : sessionCwd;
|
|
827
842
|
const targetDirectories: string[] = [];
|
|
828
843
|
const collectTarget = (directory: string): boolean => {
|
|
829
844
|
targetDirectories.push(directory);
|
|
@@ -18,13 +18,14 @@ const CMDLETS = new Set(
|
|
|
18
18
|
);
|
|
19
19
|
|
|
20
20
|
const GUIDANCE_WINDOWS =
|
|
21
|
-
"PowerShell 로직은 `write`로 .ps1 파일을 만든 뒤 `
|
|
21
|
+
"PowerShell 로직은 `write`로 .ps1 파일을 만든 뒤 PowerShell 7 `pwsh -NoProfile -ExecutionPolicy Bypass -File C:/절대/슬래시/경로.ps1`로 실행하세요(7이 없을 때만 `powershell.exe`).";
|
|
22
22
|
|
|
23
23
|
// WSL의 bash는 진짜 bash라 따옴표·`\$`가 정상 처리되고, Windows 프로그램에는 Windows 경로를 넘겨야 한다.
|
|
24
|
-
//
|
|
24
|
+
// Linux `pwsh`가 아니라 Windows PowerShell 7(`pwsh.exe`)을 부른다. 5.1은 BOM 없는 .ps1을 CP949로 읽는다.
|
|
25
|
+
// 7도 WSL로 넘기는 stdout은 시스템 코드페이지(CP949)라 한글이 깨진다(2026-10-09 7.6.6에서 관측).
|
|
25
26
|
const GUIDANCE_LINUX =
|
|
26
|
-
"PowerShell 로직은 `write`로 .ps1 파일을 만든 뒤 `
|
|
27
|
-
"
|
|
27
|
+
"PowerShell 로직은 `write`로 .ps1 파일을 만든 뒤 Windows PowerShell 7 `pwsh.exe -NoProfile -ExecutionPolicy Bypass -File \"$(wslpath -w /절대/경로.ps1)\"`로 실행하세요(7이 없을 때만 `powershell.exe`). " +
|
|
28
|
+
"한글을 출력하면 .ps1 첫 줄(짧은 `-Command`면 본문 맨 앞)에 `[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)`를 두세요. `powershell.exe`(5.1)로 실행하면 .ps1도 UTF-8 BOM으로 저장해야 합니다. " +
|
|
28
29
|
"짧은 `-Command` 본문은 작은따옴표로 감싸거나 `\\$`로 이스케이프하면 통과합니다.";
|
|
29
30
|
|
|
30
31
|
function reason(subject: string, platform: NodeJS.Platform): string {
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// draft의 docx·pdf 변환이 함께 쓰는 Markdown 부분집합 파서.
|
|
2
|
+
// 지원: 제목(#~######), 문단, **굵게**·*기울임*·`코드`·[링크](url), 글머리·번호 목록(들여쓰기 중첩),
|
|
3
|
+
// GFM 표, 펜스 코드 블록, 인용(>), 가로줄. 그 밖의 문법은 글자 그대로 문단에 남는다.
|
|
4
|
+
|
|
5
|
+
export interface Run { text: string; bold?: boolean; italic?: boolean; code?: boolean; href?: string }
|
|
6
|
+
export interface ListItem { level: number; ordered: boolean; runs: Run[] }
|
|
7
|
+
export type Block =
|
|
8
|
+
| { kind: "heading"; level: number; runs: Run[] }
|
|
9
|
+
| { kind: "paragraph"; runs: Run[] }
|
|
10
|
+
| { kind: "list"; items: ListItem[] }
|
|
11
|
+
| { kind: "table"; header: Run[][]; rows: Run[][][] }
|
|
12
|
+
| { kind: "code"; lines: string[] }
|
|
13
|
+
| { kind: "quote"; runs: Run[] }
|
|
14
|
+
| { kind: "rule" };
|
|
15
|
+
|
|
16
|
+
const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
|
|
17
|
+
const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
18
|
+
const RULE = /^ {0,3}([-*_])(?:\s*\1){2,}\s*$/;
|
|
19
|
+
const ITEM = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/;
|
|
20
|
+
const TABLE_SEPARATOR = /^\s*\|?\s*:?-+:?\s*(?:\|\s*:?-+:?\s*)*\|?\s*$/;
|
|
21
|
+
const QUOTE = /^ {0,3}>\s?(.*)$/;
|
|
22
|
+
const ESCAPABLE = /[\\`*_[\]#|()~>+\-.!{}]/;
|
|
23
|
+
|
|
24
|
+
function startsBlock(line: string, next: string | undefined): boolean {
|
|
25
|
+
return HEADING.test(line) || FENCE.test(line) || RULE.test(line) || ITEM.test(line) || QUOTE.test(line)
|
|
26
|
+
|| (line.includes("|") && next !== undefined && TABLE_SEPARATOR.test(next) && next.includes("-"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 인라인 서식을 run 목록으로 바꾼다. 닫는 표시가 없는 `*`·`**`는 글자로 남긴다. */
|
|
30
|
+
export function parseInline(source: string): Run[] {
|
|
31
|
+
const runs: Run[] = [];
|
|
32
|
+
let bold = false;
|
|
33
|
+
let italic = false;
|
|
34
|
+
let buffer = "";
|
|
35
|
+
const flush = () => {
|
|
36
|
+
if (buffer) runs.push({ text: buffer, ...(bold ? { bold } : {}), ...(italic ? { italic } : {}) });
|
|
37
|
+
buffer = "";
|
|
38
|
+
};
|
|
39
|
+
const src = source.replace(/<br\s*\/?>/gi, "\n");
|
|
40
|
+
for (let i = 0; i < src.length;) {
|
|
41
|
+
const ch = src[i]!;
|
|
42
|
+
if (ch === "\\" && i + 1 < src.length && ESCAPABLE.test(src[i + 1]!)) { buffer += src[i + 1]; i += 2; continue; }
|
|
43
|
+
if (ch === "`") {
|
|
44
|
+
const ticks = /^`+/.exec(src.slice(i))![0];
|
|
45
|
+
const end = src.indexOf(ticks, i + ticks.length);
|
|
46
|
+
if (end > 0) {
|
|
47
|
+
flush();
|
|
48
|
+
runs.push({ text: src.slice(i + ticks.length, end).replace(/^ (.+) $/, "$1"), code: true, ...(bold ? { bold } : {}), ...(italic ? { italic } : {}) });
|
|
49
|
+
i = end + ticks.length;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
buffer += ticks; i += ticks.length; continue;
|
|
53
|
+
}
|
|
54
|
+
if (ch === "[") {
|
|
55
|
+
const link = /^\[([^\]\n]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/.exec(src.slice(i));
|
|
56
|
+
if (link) {
|
|
57
|
+
flush();
|
|
58
|
+
for (const run of parseInline(link[1]!)) runs.push({ ...run, ...(bold ? { bold } : {}), ...(italic ? { italic } : {}), href: link[2]! });
|
|
59
|
+
i += link[0].length;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (src.startsWith("***", i) && ((bold && italic) || src.indexOf("***", i + 3) > 0)) { flush(); bold = !bold; italic = !italic; i += 3; continue; }
|
|
64
|
+
const pair = src.slice(i, i + 2);
|
|
65
|
+
if ((pair === "**" || pair === "__") && (bold || src.indexOf(pair, i + 2) > 0)) { flush(); bold = !bold; i += 2; continue; }
|
|
66
|
+
if (ch === "*" && (italic ? true : src[i + 1] !== " " && src.indexOf("*", i + 1) > 0)) { flush(); italic = !italic; i += 1; continue; }
|
|
67
|
+
buffer += ch;
|
|
68
|
+
i += 1;
|
|
69
|
+
}
|
|
70
|
+
flush();
|
|
71
|
+
return runs;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function splitRow(line: string): string[] {
|
|
75
|
+
let body = line.trim();
|
|
76
|
+
if (body.startsWith("|")) body = body.slice(1);
|
|
77
|
+
if (body.endsWith("|") && !body.endsWith("\\|")) body = body.slice(0, -1);
|
|
78
|
+
const cells: string[] = [];
|
|
79
|
+
let current = "";
|
|
80
|
+
let inCode = false;
|
|
81
|
+
for (let i = 0; i < body.length; i++) {
|
|
82
|
+
const ch = body[i]!;
|
|
83
|
+
if (ch === "\\" && body[i + 1] === "|") { current += "|"; i++; continue; }
|
|
84
|
+
if (ch === "`") inCode = !inCode;
|
|
85
|
+
if (ch === "|" && !inCode) { cells.push(current.trim()); current = ""; continue; }
|
|
86
|
+
current += ch;
|
|
87
|
+
}
|
|
88
|
+
cells.push(current.trim());
|
|
89
|
+
return cells;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function parseMarkdown(source: string): Block[] {
|
|
93
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
94
|
+
const blocks: Block[] = [];
|
|
95
|
+
for (let i = 0; i < lines.length;) {
|
|
96
|
+
const line = lines[i]!;
|
|
97
|
+
if (!line.trim()) { i++; continue; }
|
|
98
|
+
const fence = FENCE.exec(line);
|
|
99
|
+
if (fence) {
|
|
100
|
+
const marker = fence[1]!;
|
|
101
|
+
const body: string[] = [];
|
|
102
|
+
for (i++; i < lines.length && !lines[i]!.trimStart().startsWith(marker); i++) body.push(lines[i]!);
|
|
103
|
+
i++; // 닫는 펜스(없으면 문서 끝)
|
|
104
|
+
blocks.push({ kind: "code", lines: body });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const heading = HEADING.exec(line);
|
|
108
|
+
if (heading) { blocks.push({ kind: "heading", level: heading[1]!.length, runs: parseInline(heading[2]!) }); i++; continue; }
|
|
109
|
+
if (RULE.test(line)) { blocks.push({ kind: "rule" }); i++; continue; }
|
|
110
|
+
if (line.includes("|") && i + 1 < lines.length && TABLE_SEPARATOR.test(lines[i + 1]!) && lines[i + 1]!.includes("-")) {
|
|
111
|
+
const header = splitRow(line);
|
|
112
|
+
const rows: Run[][][] = [];
|
|
113
|
+
for (i += 2; i < lines.length && lines[i]!.trim() && lines[i]!.includes("|"); i++) {
|
|
114
|
+
const cells = splitRow(lines[i]!);
|
|
115
|
+
rows.push(header.map((_, index) => parseInline(cells[index] ?? "")));
|
|
116
|
+
}
|
|
117
|
+
blocks.push({ kind: "table", header: header.map(parseInline), rows });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (QUOTE.test(line)) {
|
|
121
|
+
const body: string[] = [];
|
|
122
|
+
for (; i < lines.length && QUOTE.test(lines[i]!); i++) body.push(QUOTE.exec(lines[i]!)![1]!);
|
|
123
|
+
blocks.push({ kind: "quote", runs: parseInline(body.join("\n").trim()) });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (ITEM.test(line)) {
|
|
127
|
+
const items: ListItem[] = [];
|
|
128
|
+
const indents: number[] = [];
|
|
129
|
+
for (; i < lines.length;) {
|
|
130
|
+
const current = lines[i]!;
|
|
131
|
+
const item = ITEM.exec(current);
|
|
132
|
+
if (item) {
|
|
133
|
+
const indent = item[1]!.replace(/\t/g, " ").length;
|
|
134
|
+
while (indents.length && indents[indents.length - 1]! > indent) indents.pop();
|
|
135
|
+
if (!indents.length || indents[indents.length - 1]! < indent) indents.push(indent);
|
|
136
|
+
items.push({ level: Math.min(indents.length - 1, 2), ordered: /\d/.test(item[2]!), runs: parseInline(item[3]!.replace(/^\[[ xX]\]\s+/, (box) => (/[xX]/.test(box) ? "☑ " : "☐ "))) });
|
|
137
|
+
i++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// 들여 쓴 이어지는 줄은 직전 항목에 붙인다. 빈 줄 뒤에 항목이 오면 같은 목록이 이어진다.
|
|
141
|
+
if (current.trim() && /^\s+/.test(current) && items.length && !startsBlock(current.trim(), lines[i + 1])) {
|
|
142
|
+
const last = items[items.length - 1]!;
|
|
143
|
+
last.runs = [...last.runs, { text: " " }, ...parseInline(current.trim())];
|
|
144
|
+
i++;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!current.trim() && i + 1 < lines.length && ITEM.test(lines[i + 1]!)) { i++; continue; }
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
blocks.push({ kind: "list", items });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const body: string[] = [line.trim()];
|
|
154
|
+
for (i++; i < lines.length && lines[i]!.trim() && !startsBlock(lines[i]!, lines[i + 1]); i++) body.push(lines[i]!.trim());
|
|
155
|
+
blocks.push({ kind: "paragraph", runs: parseInline(body.join(" ")) });
|
|
156
|
+
}
|
|
157
|
+
return blocks;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function plainText(runs: readonly Run[]): string { return runs.map((run) => run.text).join(""); }
|
|
161
|
+
|
|
162
|
+
/** 결과 보고용 개요: 제목 목록, 제목이 없으면 앞 문단 몇 개. */
|
|
163
|
+
export function outline(blocks: readonly Block[], limit = 12): string[] {
|
|
164
|
+
const headings = blocks.filter((block): block is Extract<Block, { kind: "heading" }> => block.kind === "heading")
|
|
165
|
+
.map((block) => `${"#".repeat(block.level)} ${plainText(block.runs)}`);
|
|
166
|
+
if (headings.length) return headings.length > limit ? [...headings.slice(0, limit), `… 제목 ${headings.length - limit}개 더`] : headings;
|
|
167
|
+
return blocks.filter((block): block is Extract<Block, { kind: "paragraph" }> => block.kind === "paragraph")
|
|
168
|
+
.slice(0, 3).map((block) => plainText(block.runs).slice(0, 120));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function escapeHtml(text: string): string {
|
|
172
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function htmlRuns(runs: readonly Run[]): string {
|
|
176
|
+
return runs.map((run) => {
|
|
177
|
+
let html = escapeHtml(run.text).replace(/\n/g, "<br>");
|
|
178
|
+
if (run.code) html = `<code>${html}</code>`;
|
|
179
|
+
if (run.italic) html = `<em>${html}</em>`;
|
|
180
|
+
if (run.bold) html = `<strong>${html}</strong>`;
|
|
181
|
+
if (run.href) html = `<a href="${escapeHtml(run.href)}">${html}</a>`;
|
|
182
|
+
return html;
|
|
183
|
+
}).join("");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function htmlList(items: readonly ListItem[]): string {
|
|
187
|
+
let html = "";
|
|
188
|
+
const open: string[] = [];
|
|
189
|
+
let depth = -1;
|
|
190
|
+
for (const item of items) {
|
|
191
|
+
const tag = item.ordered ? "ol" : "ul";
|
|
192
|
+
while (depth > item.level) { html += `</li></${open.pop()}>`; depth--; }
|
|
193
|
+
if (depth === item.level && open[open.length - 1] !== tag) { html += `</li></${open.pop()}>`; depth--; }
|
|
194
|
+
if (depth === item.level) html += "</li>";
|
|
195
|
+
while (depth < item.level) { html += `<${tag}>`; open.push(tag); depth++; if (depth < item.level) html += "<li>"; }
|
|
196
|
+
html += `<li>${htmlRuns(item.runs)}`;
|
|
197
|
+
}
|
|
198
|
+
while (open.length) html += `</li></${open.pop()}>`;
|
|
199
|
+
return html;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const CSS = `@page { size: A4; margin: 18mm 16mm; }
|
|
203
|
+
body { font-family: "Malgun Gothic", "맑은 고딕", "Apple SD Gothic Neo", "Noto Sans KR", sans-serif; font-size: 10.5pt; line-height: 1.6; color: #111; }
|
|
204
|
+
h1 { font-size: 20pt; border-bottom: 1px solid #999; padding-bottom: 4px; } h2 { font-size: 15pt; } h3 { font-size: 12.5pt; } h4, h5, h6 { font-size: 11pt; }
|
|
205
|
+
h1, h2, h3, h4, h5, h6 { margin: 1.1em 0 0.4em; page-break-after: avoid; }
|
|
206
|
+
table { border-collapse: collapse; width: 100%; margin: 0.6em 0; page-break-inside: auto; } tr { page-break-inside: avoid; }
|
|
207
|
+
th, td { border: 1px solid #888; padding: 4px 6px; text-align: left; vertical-align: top; } th { background: #eee; }
|
|
208
|
+
pre { background: #f4f4f4; padding: 8px; white-space: pre-wrap; word-break: break-all; font-size: 9pt; }
|
|
209
|
+
code { font-family: Consolas, "D2Coding", monospace; } blockquote { margin: 0.6em 0; padding-left: 10px; border-left: 3px solid #bbb; color: #444; }
|
|
210
|
+
hr { border: 0; border-top: 1px solid #aaa; }`;
|
|
211
|
+
|
|
212
|
+
export function toHtml(blocks: readonly Block[], title: string): string {
|
|
213
|
+
const body = blocks.map((block) => {
|
|
214
|
+
switch (block.kind) {
|
|
215
|
+
case "heading": return `<h${block.level}>${htmlRuns(block.runs)}</h${block.level}>`;
|
|
216
|
+
case "paragraph": return `<p>${htmlRuns(block.runs)}</p>`;
|
|
217
|
+
case "list": return htmlList(block.items);
|
|
218
|
+
case "table": return `<table><thead><tr>${block.header.map((cell) => `<th>${htmlRuns(cell)}</th>`).join("")}</tr></thead><tbody>${
|
|
219
|
+
block.rows.map((row) => `<tr>${row.map((cell) => `<td>${htmlRuns(cell)}</td>`).join("")}</tr>`).join("")}</tbody></table>`;
|
|
220
|
+
case "code": return `<pre><code>${escapeHtml(block.lines.join("\n"))}</code></pre>`;
|
|
221
|
+
case "quote": return `<blockquote>${htmlRuns(block.runs)}</blockquote>`;
|
|
222
|
+
case "rule": return "<hr>";
|
|
223
|
+
}
|
|
224
|
+
}).join("\n");
|
|
225
|
+
return `<!doctype html>\n<html lang="ko"><head><meta charset="utf-8"><title>${escapeHtml(title)}</title><style>${CSS}</style></head><body>\n${body}\n</body></html>\n`;
|
|
226
|
+
}
|