cuelo 0.10.49 → 0.10.51

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.
Files changed (66) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +10 -10
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.js +1 -1
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +1 -1
  18. package/.next/server/app/_not-found.rsc +2 -2
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/.next/server/app/api/updates/route.js +1 -1
  26. package/.next/server/app/index.html +1 -1
  27. package/.next/server/app/index.rsc +5 -5
  28. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  29. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  30. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  31. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  32. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  33. package/.next/server/app/page.js +5 -5
  34. package/.next/server/app/page_client-reference-manifest.js +1 -1
  35. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/recover.html +1 -1
  37. package/.next/server/app/recover.rsc +2 -2
  38. package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
  39. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  40. package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
  41. package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
  42. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  43. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  44. package/.next/server/app-paths-manifest.json +10 -10
  45. package/.next/server/middleware-build-manifest.js +1 -1
  46. package/.next/server/pages/404.html +1 -1
  47. package/.next/server/pages/500.html +1 -1
  48. package/.next/server/server-reference-manifest.json +1 -1
  49. package/.next/static/chunks/app/{layout-9489cd70db38d552.js → layout-6378f788fcd6dbfc.js} +1 -1
  50. package/.next/static/chunks/app/{page-c7c715cf078c3418.js → page-883fc62b01e4fd6c.js} +5 -5
  51. package/.next/static/css/17b6a202abbcac12.css +1 -0
  52. package/.next/trace +107 -107
  53. package/.next/trace-build +1 -1
  54. package/README.md +1 -1
  55. package/Tools/CUELO_Setup/files/native-runtime-patch.js +1 -1
  56. package/Tools/OMP_Global_Config/agent/agents/maker.md +2 -2
  57. package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +1 -1
  58. package/Tools/OMP_Global_Config/agent/rules/meta-harness.md +1 -1
  59. package/Tools/OMP_Global_Config/agent/sop/maker.md +1 -1
  60. package/Tools/OMP_Global_Config/agent/tools/windows-control/control.ps1 +200 -0
  61. package/Tools/OMP_Global_Config/agent/tools/windows-control/index.ts +255 -0
  62. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +9 -0
  63. package/package.json +1 -1
  64. package/.next/static/css/b7e1817e3c1a4c20.css +0 -1
  65. /package/.next/static/{5gOfHmA_lDytew7Oyx7wS → GzkGqyCey-ntAId1o1zDM}/_buildManifest.js +0 -0
  66. /package/.next/static/{5gOfHmA_lDytew7Oyx7wS → GzkGqyCey-ntAId1o1zDM}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-webpack","duration":77684283,"timestamp":619091,"id":14,"parentId":1,"tags":{},"startTime":1792536213341,"traceId":"10062087fa58dc8a"},{"name":"run-typescript","duration":13674772,"timestamp":78682976,"id":10997,"parentId":1,"tags":{},"startTime":1792536291406,"traceId":"10062087fa58dc8a"},{"name":"static-check","duration":4262935,"timestamp":92449601,"id":11000,"parentId":1,"tags":{},"startTime":1792536305173,"traceId":"10062087fa58dc8a"},{"name":"static-generation","duration":7399136,"timestamp":96788483,"id":11172,"parentId":1,"tags":{},"startTime":1792536309512,"traceId":"10062087fa58dc8a"},{"name":"collect-build-traces","duration":15441300,"timestamp":96772023,"id":11169,"parentId":1,"tags":{},"startTime":1792536309495,"traceId":"10062087fa58dc8a"},{"name":"telemetry-flush","duration":46,"timestamp":112216686,"id":11181,"parentId":1,"tags":{},"startTime":1792536324940,"traceId":"10062087fa58dc8a"},{"name":"next-build","duration":111682196,"timestamp":534554,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1792536213256,"traceId":"10062087fa58dc8a"}]
1
+ [{"name":"run-webpack","duration":120469305,"timestamp":629337,"id":14,"parentId":1,"tags":{},"startTime":1792541415273,"traceId":"919daa10af2300e0"},{"name":"run-typescript","duration":18298348,"timestamp":121494803,"id":10997,"parentId":1,"tags":{},"startTime":1792541536133,"traceId":"919daa10af2300e0"},{"name":"static-check","duration":4164604,"timestamp":139898059,"id":11000,"parentId":1,"tags":{},"startTime":1792541554536,"traceId":"919daa10af2300e0"},{"name":"static-generation","duration":13138046,"timestamp":144184003,"id":11172,"parentId":1,"tags":{},"startTime":1792541558822,"traceId":"919daa10af2300e0"},{"name":"collect-build-traces","duration":22233918,"timestamp":144144315,"id":11169,"parentId":1,"tags":{},"startTime":1792541558782,"traceId":"919daa10af2300e0"},{"name":"telemetry-flush","duration":113,"timestamp":166383698,"id":11181,"parentId":1,"tags":{},"startTime":1792541581020,"traceId":"919daa10af2300e0"},{"name":"next-build","duration":165862435,"timestamp":521419,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1792541415165,"traceId":"919daa10af2300e0"}]
package/README.md CHANGED
@@ -98,7 +98,7 @@ Anthropic 구독 계정이 모두 주간 한도에 닿으면, 미리 둔 예비
98
98
  - 서브에이전트를 발주한 답변 안에 접힌 카드가 붙어, 맡긴 일마다 실행 상태와 Main의 검수 판정을 한 줄로 보여 줍니다. 펼치면 Maker별 시도가 재작업 순서대로 묶여 실행 상태·검사 수·판정과 함께 나오고, 지금 세션에서 실행한 Maker는 「자세히」로 바뀐 파일·검사·판정 사유를 연 뒤 뒤로 돌아올 수 있습니다. 이전 실행의 기록만 남은 Maker는 확인할 수 없다고 표시합니다. 실행이 끝났어도 Main이 판정을 남기지 않았으면 「검수 미기록」으로 표시하며, 끝난 것을 통과로 치지 않습니다. 서브에이전트가 실행 중이거나 판정을 기다리는 동안에는 입력창 위에 한 줄 요약이 고정되고, 누르면 그 카드로 스크롤해 강조합니다.
99
99
  - 실행 중인 서브에이전트는 패널 카드와 답변 카드 상세에 지금 단계(조사 › 구현 › 검증 › 보고)와 경과 시간이 몇 초마다 갱신되어 보입니다. 같은 모델의 과거 실행 가운데 지금보다 오래 걸린 실행들을 근거로 남은 시간 범위와 대략의 진행률을 함께 보여 주며, 표본 수도 표시합니다. 할 일 목록을 쓰는 서브에이전트는 그 진행 기준 추정을 따로 덧붙입니다. 과거 기록이 부족하면 남은 시간은 숨기고, 과거 실행 대부분보다 길어지면 그렇다고 알립니다. 추정이지 약속이 아닙니다.
100
100
  - 대화가 압축돼도 서브에이전트 카드·진행줄과 할 일 목록은 현재 분기의 기록을 이어서 보여 줍니다. 압축으로 원래 답변이 가려진 카드는 압축 지점에서 다시 열 수 있으며, 새로고침해도 복원됩니다. 이후 실행 상태와 검수 판정은 새 관측에 맞춰 갱신됩니다.
101
- - 끝난 턴의 마지막 답변 아래에 변경 파일·검사·실패·미확인 요약이 붙습니다. 펼치면 Main과 서브에이전트의 작업, 검사·리뷰와 재수정 흐름을 그래프로 보고 노드에서 관련 기록·변경 파일·문서를 열 수 있습니다. 넓은 카드에서는 단계가 상자로 한 줄에 놓이고, 카드 폭이 740px 이하이면 일어난 순서대로 적는 세로 레일로 바뀝니다. 실제 진행 중에는 지금 단계의 진행 표시만 움직이고, 완료되면 결과만 정지한 상태로 남습니다. 서브에이전트 카드를 펼치면 조사·구현·검증·보고·Main 판정 흐름을 같은 그래프로 봅니다. 업무 종류와 동시 실행은 기록으로 확인된 경우에만 표시합니다. `.omp` 작업 폴더나 시스템 임시 폴더에 쓴 임시 파일은 변경 파일로 세지 않습니다.
101
+ - 끝난 턴의 마지막 답변 아래에 변경 파일·검사·실패·미확인 요약이 붙습니다. 펼치면 Main과 서브에이전트의 작업, 검사·리뷰와 재수정 흐름을 그래프로 보고 노드에서 관련 기록·변경 파일·문서를 열 수 있습니다. 넓은 카드에서는 단계가 상자로 한 줄에 놓이고, 카드 폭이 740px 이하이면 일어난 순서대로 적는 세로 레일로 바뀝니다. 실제 진행 중에는 지금 단계의 진행 표시만 움직이고, 완료되면 결과만 정지한 상태로 남습니다. 끝난 그래프의 「재생」을 누르면 실제로 거친 단계와 서브에이전트 갈래, 되돌아온 재수정, 변경 결과를 차례로 다시 밝힌 뒤 정지한 그래프로 돌아갑니다. 재생은 기록된 순서만 보여 주며 실제 걸린 시간에 비례하지 않습니다. 서브에이전트 카드를 펼치면 조사·구현·검증·보고·Main 판정 흐름을 같은 그래프로 봅니다. 업무 종류와 동시 실행은 기록으로 확인된 경우에만 표시합니다. `.omp` 작업 폴더나 시스템 임시 폴더에 쓴 임시 파일은 변경 파일로 세지 않습니다.
102
102
  - 검사 후 다시 수정한 작업은 이전 통과 결과를 현재 검증으로 표시하지 않습니다. 확인되지 않은 검사·복합 명령은 「판정 미확인」으로, 답을 읽지 못한 교차리뷰는 「리뷰 불가」로 구분합니다. 실행 중 끼어든 지시가 같은 작업을 이어 가면 이전 작업 흐름을 함께 보여 줍니다. 비공개 파일만 게시해 공개 CI가 생기지 않은 것으로 확인되면 CI는 「필요 없음」으로 표시하고, CI·배포가 없던 턴은 끝난 뒤 그 단계를 「필요 없음」으로 닫습니다.
103
103
  - 실행 중에 보낸 입력은 「지금 지시」와 「완료 후 요청」으로 나뉘어, 접수·전달 대기·대화에 들어감 단계를 따로 보여 줍니다. 전달을 확인하지 못하면 그렇다고 표시합니다. 스크린샷만 보낸 입력도 「이미지 N개」로 표시합니다. 기본 목록에는 최근 4개만 보이고, 「이전 N개 더 보기」로 나머지를 펼칠 수 있습니다. 실제 대기 중인 입력은 목록에서 숨겨져도 유지되며, 펼친 목록에서 거둘 수 있습니다.
104
104
  - 자식 에이전트의 발화가 해당 턴 아래 대화 흐름에 끼어들어 누가 무슨 말을 했는지 이어서 읽힙니다.
@@ -9,7 +9,7 @@ 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.49',
12
+ version: '0.10.51',
13
13
  distribution: 'cuelo',
14
14
  coreVersion: '18.12.3',
15
15
  };
@@ -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, draft, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, windows_build, checkpoint]
6
+ tools: [read, bash, edit, write, grep, glob, skim, draft, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, windows_build, windows_control, checkpoint]
7
7
  ---
8
8
 
9
- <!-- omp-global-config:generated source-hash=3357fab21461
9
+ <!-- omp-global-config:generated source-hash=cf0f7c8e6905
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
@@ -851,7 +851,7 @@
851
851
  "codeAndPolicyReviewOwner": "main",
852
852
  "designJudgment": "main-or-the-owning-maker-reads-the-design-rules-and-skills-there-is-no-separate-design-role",
853
853
  "repositoryAccess": "owner-writable-inside-its-brief-boundary-only",
854
- "tools": ["read", "bash", "edit", "write", "grep", "glob", "skim", "draft", "lsp", "eval", "generate_image", "ast_grep", "ast_edit", "debug", "todo", "web_search", "windows_build", "checkpoint"],
854
+ "tools": ["read", "bash", "edit", "write", "grep", "glob", "skim", "draft", "lsp", "eval", "generate_image", "ast_grep", "ast_edit", "debug", "todo", "web_search", "windows_build", "windows_control", "checkpoint"],
855
855
  "runtimeAutoPair": "checkpoint-in-frontmatter-causes-core-createTools-to-add-rewind-to-the-restricted-session-tool-surface-rewind-is-intentionally-absent-from-frontmatter",
856
856
  "execution": "async",
857
857
  "broadValidation": "the-owner-runs-the-ordinary-project-checks-its-slice-needs-and-skips-unrelated-project-wide-suites",
@@ -14,7 +14,7 @@ globs: ["Tools/OMP_Global_Config/**"]
14
14
  발주 때 Main이 고른 `tasks[].model`의 `:auto` selector가 child auto를 켜고, 실제 단계는 그 모델의 전체 범위에서 child가 고른다.
15
15
  모델·fallback은 `modelRoles`, 모델 분류·추론은 `routing.modelSelection`·`routing.effortSelection`이 정한다.
16
16
  - frontmatter 도구는 `read, bash, edit, write, grep, glob, skim, draft, lsp, eval,
17
- generate_image, ast_grep, ast_edit, debug, todo, web_search, windows_build, checkpoint`다. core 18.3.0에는 `hub`가
17
+ generate_image, ast_grep, ast_edit, debug, todo, web_search, windows_build, windows_control, checkpoint`다. core 18.3.0에는 `hub`가
18
18
  없고(목록에 남겨도 조용히 버려진다) `wait`는 top-level 전용이라 Maker(depth 1)에 넣어도 생기지 않으므로 둘 다 적지 않는다.
19
19
  restricted session에서 `checkpoint`를 명시하면 core `createTools`가 `rewind`를 안전 쌍으로
20
20
  자동 추가하므로 frontmatter에는 `rewind`를 중복하지 않지만 실제 runtime에는 둘 다 노출된다.
@@ -2,7 +2,7 @@
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
4
  model: "@implSonnet"
5
- tools: [read, bash, edit, write, grep, glob, skim, draft, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, windows_build, checkpoint]
5
+ tools: [read, bash, edit, write, grep, glob, skim, draft, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, windows_build, windows_control, checkpoint]
6
6
  ---
7
7
 
8
8
  You are the delegated implementation Maker on the default `modelRoles.implSonnet` slot. A delegated slice
@@ -0,0 +1,200 @@
1
+ param([Parameter(Mandatory = $true)][string]$Request)
2
+ # windows_control 도구의 Windows 쪽 본체. 요청은 base64(UTF-8 JSON) 한 인자로 받는다(WSL 인자 따옴표 문제를 피한다).
3
+ # 클래식 Win32 컨트롤(WinForms·VB6·MFC)의 자식 창에 SendMessageTimeout으로 WM_SETTEXT·BM_CLICK을 보낸다.
4
+ # 전면 창·포커스는 바꾸지 않는다(SetForegroundWindow·SetFocus·SendInput을 쓰지 않는다).
5
+ [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
6
+ $ErrorActionPreference = 'Stop'
7
+
8
+ Add-Type -TypeDefinition @"
9
+ using System;
10
+ using System.Text;
11
+ using System.Collections.Generic;
12
+ using System.Runtime.InteropServices;
13
+
14
+ public class CueloChild {
15
+ public long Hwnd;
16
+ public string ClassName;
17
+ public string Text;
18
+ public int ControlId;
19
+ public bool Visible;
20
+ public bool Enabled;
21
+ public int Check;
22
+ }
23
+
24
+ public static class CueloWin {
25
+ private delegate bool EnumProc(IntPtr h, IntPtr l);
26
+ [DllImport("user32.dll")] private static extern bool EnumChildWindows(IntPtr p, EnumProc f, IntPtr l);
27
+ [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassName(IntPtr h, StringBuilder s, int n);
28
+ [DllImport("user32.dll")] private static extern int GetDlgCtrlID(IntPtr h);
29
+ [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr h);
30
+ [DllImport("user32.dll")] private static extern bool IsWindowEnabled(IntPtr h);
31
+ [DllImport("user32.dll")] private static extern bool IsWindow(IntPtr h);
32
+ [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr h, int index);
33
+ [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
34
+ [DllImport("user32.dll", SetLastError = true)]
35
+ private static extern IntPtr SendMessageTimeout(IntPtr h, uint m, IntPtr w, IntPtr l, uint flags, uint timeout, out IntPtr result);
36
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SendMessageTimeoutW")]
37
+ private static extern IntPtr SendMessageTimeoutText(IntPtr h, uint m, IntPtr w, StringBuilder l, uint flags, uint timeout, out IntPtr result);
38
+ [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SendMessageTimeoutW")]
39
+ private static extern IntPtr SendMessageTimeoutString(IntPtr h, uint m, IntPtr w, string l, uint flags, uint timeout, out IntPtr result);
40
+
41
+ private const uint SMTO_ABORTIFHUNG = 2;
42
+ private const uint WM_SETTEXT = 0x000C, WM_GETTEXT = 0x000D, WM_GETTEXTLENGTH = 0x000E;
43
+ private const uint BM_GETCHECK = 0x00F0, BM_CLICK = 0x00F5;
44
+ private const int GWL_STYLE = -16;
45
+
46
+ public static bool Exists(long hwnd) { return IsWindow(new IntPtr(hwnd)); }
47
+
48
+ public static string ReadText(IntPtr h) {
49
+ IntPtr length;
50
+ if (SendMessageTimeout(h, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, 1000, out length) == IntPtr.Zero) return null;
51
+ int n = (int)length.ToInt64();
52
+ if (n < 0) n = 0;
53
+ if (n > 65535) n = 65535;
54
+ StringBuilder buffer = new StringBuilder(n + 1);
55
+ IntPtr copied;
56
+ if (SendMessageTimeoutText(h, WM_GETTEXT, new IntPtr(n + 1), buffer, SMTO_ABORTIFHUNG, 1000, out copied) == IntPtr.Zero) return null;
57
+ return buffer.ToString();
58
+ }
59
+
60
+ // Check state only for check-box and radio-button styles; push buttons report -1.
61
+ private static int CheckState(IntPtr h, string className) {
62
+ if (className.IndexOf("BUTTON", StringComparison.OrdinalIgnoreCase) < 0) return -1;
63
+ int kind = GetWindowLong(h, GWL_STYLE) & 0xF;
64
+ if (kind != 2 && kind != 3 && kind != 4 && kind != 5 && kind != 6 && kind != 9) return -1;
65
+ IntPtr state;
66
+ if (SendMessageTimeout(h, BM_GETCHECK, IntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, 1000, out state) == IntPtr.Zero) return -1;
67
+ return (int)state.ToInt64();
68
+ }
69
+
70
+ public static CueloChild Describe(IntPtr h) {
71
+ StringBuilder cls = new StringBuilder(256);
72
+ GetClassName(h, cls, 256);
73
+ CueloChild child = new CueloChild();
74
+ child.Hwnd = h.ToInt64();
75
+ child.ClassName = cls.ToString();
76
+ child.Text = ReadText(h);
77
+ child.ControlId = GetDlgCtrlID(h);
78
+ child.Visible = IsWindowVisible(h);
79
+ child.Enabled = IsWindowEnabled(h);
80
+ child.Check = CheckState(h, child.ClassName);
81
+ return child;
82
+ }
83
+
84
+ public static List<CueloChild> Children(long top) {
85
+ List<CueloChild> list = new List<CueloChild>();
86
+ EnumChildWindows(new IntPtr(top), delegate (IntPtr h, IntPtr l) { list.Add(Describe(h)); return true; }, IntPtr.Zero);
87
+ return list;
88
+ }
89
+
90
+ // Returns 0 when delivered; otherwise the Win32 error (1460 = timeout, e.g. a modal dialog is still open; 5 = elevated target).
91
+ public static int SetText(long hwnd, string value) {
92
+ IntPtr result;
93
+ if (SendMessageTimeoutString(new IntPtr(hwnd), WM_SETTEXT, IntPtr.Zero, value, SMTO_ABORTIFHUNG, 2000, out result) != IntPtr.Zero) return 0;
94
+ int error = Marshal.GetLastWin32Error();
95
+ return error == 0 ? -1 : error;
96
+ }
97
+
98
+ public static int Click(long hwnd, uint timeoutMs) {
99
+ IntPtr result;
100
+ if (SendMessageTimeout(new IntPtr(hwnd), BM_CLICK, IntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, timeoutMs, out result) != IntPtr.Zero) return 0;
101
+ int error = Marshal.GetLastWin32Error();
102
+ return error == 0 ? -1 : error;
103
+ }
104
+ }
105
+ "@
106
+
107
+ function Write-Result($value) { $value | ConvertTo-Json -Depth 5 -Compress }
108
+
109
+ function Get-Changes($before, $after) {
110
+ $old = @{}
111
+ foreach ($child in $before) { $old[$child.Hwnd] = $child }
112
+ $changes = @()
113
+ foreach ($child in $after) {
114
+ $prev = $old[$child.Hwnd]
115
+ if ($null -eq $prev) {
116
+ $changes += [ordered]@{ hwnd = $child.Hwnd; className = $child.ClassName; added = $true; text = $child.Text }
117
+ continue
118
+ }
119
+ if ($prev.Text -ne $child.Text -or $prev.Check -ne $child.Check -or $prev.Enabled -ne $child.Enabled -or $prev.Visible -ne $child.Visible) {
120
+ $changes += [ordered]@{
121
+ hwnd = $child.Hwnd; className = $child.ClassName
122
+ textBefore = $prev.Text; textAfter = $child.Text
123
+ checkBefore = $prev.Check; checkAfter = $child.Check
124
+ enabledAfter = $child.Enabled; visibleAfter = $child.Visible
125
+ }
126
+ }
127
+ }
128
+ # 사라진 컨트롤도 변화다(창은 남았는데 버튼이 자식을 없앤 경우).
129
+ $now = @{}
130
+ foreach ($child in $after) { $now[$child.Hwnd] = $true }
131
+ foreach ($child in $before) {
132
+ if (-not $now.ContainsKey($child.Hwnd)) {
133
+ $changes += [ordered]@{ hwnd = $child.Hwnd; className = $child.ClassName; removed = $true; text = $child.Text }
134
+ }
135
+ }
136
+ , $changes
137
+ }
138
+
139
+ try {
140
+ $req = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Request)) | ConvertFrom-Json
141
+ $window = [long]$req.window
142
+ $foregroundBefore = [CueloWin]::GetForegroundWindow().ToInt64()
143
+ if (-not [CueloWin]::Exists($window)) {
144
+ Write-Result ([ordered]@{ ok = $false; error = 'window-not-found'; window = $window }); exit 0
145
+ }
146
+ $before = [CueloWin]::Children($window)
147
+ if ($req.action -eq 'list') {
148
+ $foregroundAfter = [CueloWin]::GetForegroundWindow().ToInt64()
149
+ Write-Result ([ordered]@{ ok = $true; action = 'list'; window = $window; foregroundUnchanged = ($foregroundBefore -eq $foregroundAfter); controls = $before }); exit 0
150
+ }
151
+
152
+ $found = @($before | Where-Object {
153
+ ($null -eq $req.text -or $_.Text -ceq [string]$req.text) -and
154
+ ($null -eq $req.className -or $_.ClassName.IndexOf([string]$req.className, [StringComparison]::OrdinalIgnoreCase) -ge 0)
155
+ })
156
+ if ($found.Count -eq 0) {
157
+ Write-Result ([ordered]@{ ok = $false; error = 'control-not-found'; window = $window; controls = $before }); exit 0
158
+ }
159
+ if ($null -ne $req.index) {
160
+ $index = [int]$req.index
161
+ if ($index -lt 0 -or $index -ge $found.Count) {
162
+ Write-Result ([ordered]@{ ok = $false; error = 'index-out-of-range'; window = $window; matches = $found }); exit 0
163
+ }
164
+ $target = $found[$index]
165
+ } elseif ($found.Count -gt 1) {
166
+ Write-Result ([ordered]@{ ok = $false; error = 'ambiguous'; window = $window; matches = $found }); exit 0
167
+ } else {
168
+ $target = $found[0]
169
+ }
170
+
171
+ # 바꾸는 동작은 비활성 컨트롤에 보내지 않는다(사용자가 막아 둔 입력칸·버튼이다).
172
+ if ($req.action -ne 'read' -and -not $target.Enabled) {
173
+ Write-Result ([ordered]@{ ok = $false; error = 'control-disabled'; window = $window; target = $target }); exit 0
174
+ }
175
+ $code = 0
176
+ $started = [Diagnostics.Stopwatch]::StartNew()
177
+ switch ($req.action) {
178
+ 'setText' { $code = [CueloWin]::SetText($target.Hwnd, [string]$req.value) }
179
+ 'click' { $code = [CueloWin]::Click($target.Hwnd, [uint32]$req.timeoutMs) }
180
+ 'read' { }
181
+ default { throw "unknown action $($req.action)" }
182
+ }
183
+ $started.Stop()
184
+ $after = if ([CueloWin]::Exists($window)) { [CueloWin]::Children($window) } else { @() }
185
+ $foregroundAfter = [CueloWin]::GetForegroundWindow().ToInt64()
186
+ Write-Result ([ordered]@{
187
+ ok = ($code -eq 0)
188
+ action = $req.action
189
+ window = $window
190
+ windowClosed = -not [CueloWin]::Exists($window)
191
+ target = $target
192
+ targetAfter = ($after | Where-Object { $_.Hwnd -eq $target.Hwnd } | Select-Object -First 1)
193
+ win32Error = $code
194
+ elapsedMs = $started.ElapsedMilliseconds
195
+ foregroundUnchanged = ($foregroundBefore -eq $foregroundAfter)
196
+ changes = (Get-Changes $before $after)
197
+ })
198
+ } catch {
199
+ Write-Result ([ordered]@{ ok = $false; error = 'script-error'; message = $_.Exception.Message })
200
+ }
@@ -0,0 +1,255 @@
1
+ import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
2
+ import { posix } from "node:path";
3
+
4
+ // 2026-10-21 사무실 실측(doc/history/2026/10/21-computer-background): WinForms에서 computer의 UIA press·setValue는
5
+ // 효과는 있지만 사용자 전면 창을 가져가고, 게시된 win.click은 오류 없이 무시됐다. 같은 컨트롤 HWND에
6
+ // SendMessage WM_SETTEXT·BM_CLICK을 보내면 전면 창을 지키며 25~45ms에 동작했다. 이 도구가 그 경로다.
7
+
8
+ export type ControlAction = "list" | "read" | "setText" | "click";
9
+
10
+ export interface WindowsControlParams {
11
+ window: string | number;
12
+ action: ControlAction;
13
+ text?: string;
14
+ className?: string;
15
+ index?: number;
16
+ value?: string;
17
+ timeoutMs?: number;
18
+ }
19
+
20
+ export interface ExecResult { code: number; stdout: string; stderr?: string }
21
+ export type Exec = (command: string, args: string[], options: { cwd: string; timeout?: number }) => Promise<ExecResult>;
22
+
23
+ export interface ControlDeps {
24
+ exec: Exec;
25
+ cwd: string;
26
+ /** WSL이면 스크립트 경로를 wslpath -w로 바꾼다. */
27
+ wsl: boolean;
28
+ scriptPath: string;
29
+ }
30
+
31
+ export interface ChildControl {
32
+ Hwnd: number;
33
+ ClassName: string;
34
+ Text: string | null;
35
+ ControlId: number;
36
+ Visible: boolean;
37
+ Enabled: boolean;
38
+ /** -1: 상태를 읽을 수 없음(일반 버튼, WinForms처럼 직접 그리는 체크박스), 0: 해제, 1: 체크, 2: 불확정 */
39
+ Check: number;
40
+ }
41
+
42
+ export interface ControlChange {
43
+ hwnd: number;
44
+ className: string;
45
+ added?: boolean;
46
+ removed?: boolean;
47
+ text?: string | null;
48
+ textBefore?: string | null;
49
+ textAfter?: string | null;
50
+ checkBefore?: number;
51
+ checkAfter?: number;
52
+ enabledAfter?: boolean;
53
+ visibleAfter?: boolean;
54
+ }
55
+
56
+ export interface ControlResult {
57
+ ok: boolean;
58
+ error?: string;
59
+ message?: string;
60
+ action?: ControlAction;
61
+ window?: number;
62
+ windowClosed?: boolean;
63
+ target?: ChildControl;
64
+ targetAfter?: ChildControl | null;
65
+ win32Error?: number;
66
+ elapsedMs?: number;
67
+ foregroundUnchanged?: boolean;
68
+ changes?: ControlChange[] | ControlChange | null;
69
+ controls?: ChildControl[] | ChildControl | null;
70
+ matches?: ChildControl[] | ChildControl | null;
71
+ shell?: string;
72
+ }
73
+
74
+ const DEFAULT_CLICK_TIMEOUT_MS = 2000;
75
+ const MAX_LISTED = 40;
76
+ const WIN32_TIMEOUT = 1460;
77
+ const WIN32_ACCESS_DENIED = 5;
78
+
79
+ /** 요청을 검증해 스크립트로 넘길 JSON으로 만든다. 잘못된 요청은 Windows를 부르기 전에 거절한다. */
80
+ export function buildRequest(params: WindowsControlParams): Record<string, unknown> {
81
+ const window = Number(params.window);
82
+ if (!Number.isSafeInteger(window) || window <= 0) {
83
+ throw new Error(`windows_control: window는 computer.window()의 id(양의 정수 HWND)여야 한다: ${String(params.window)}`);
84
+ }
85
+ const needsTarget = params.action !== "list";
86
+ if (needsTarget && params.text === undefined && params.className === undefined) {
87
+ throw new Error("windows_control: list 말고는 text 또는 className으로 대상 컨트롤을 지정해야 한다");
88
+ }
89
+ if (params.action === "setText" && params.value === undefined) {
90
+ throw new Error("windows_control: setText에는 value가 필요하다");
91
+ }
92
+ if (params.index !== undefined && (!Number.isInteger(params.index) || params.index < 0)) {
93
+ throw new Error(`windows_control: index는 0 이상의 정수여야 한다: ${params.index}`);
94
+ }
95
+ const timeoutMs = Math.max(100, Math.min(30_000, Math.trunc(params.timeoutMs ?? DEFAULT_CLICK_TIMEOUT_MS)));
96
+ return {
97
+ window,
98
+ action: params.action,
99
+ ...(params.text !== undefined ? { text: params.text } : {}),
100
+ ...(params.className !== undefined ? { className: params.className } : {}),
101
+ ...(params.index !== undefined ? { index: params.index } : {}),
102
+ ...(params.value !== undefined ? { value: params.value } : {}),
103
+ timeoutMs,
104
+ };
105
+ }
106
+
107
+ export async function runWindowsControl(params: WindowsControlParams, deps: ControlDeps): Promise<ControlResult> {
108
+ const request = buildRequest(params);
109
+ let script = deps.scriptPath;
110
+ if (deps.wsl) {
111
+ const converted = await deps.exec("wslpath", ["-w", deps.scriptPath], { cwd: deps.cwd });
112
+ script = converted.stdout.trim();
113
+ if (converted.code !== 0 || !script) throw new Error(`windows_control: wslpath 변환 실패: ${converted.stderr ?? ""}`.trim());
114
+ }
115
+ const encoded = Buffer.from(JSON.stringify(request), "utf8").toString("base64");
116
+ const args = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, "-Request", encoded];
117
+ // 사용자 결정(10-09): Windows PowerShell 7(pwsh)을 먼저 쓰고, 없을 때만 5.1로 간다.
118
+ // "없음"은 pi.exec가 실행 파일을 찾지 못해 code ENOENT로 throw한 경우뿐이다. pwsh가 실행돼서 실패한 경우
119
+ // (어떤 종료 코드든)는 같은 요청을 다른 셸로 다시 보내지 않는다. 버튼을 두 번 누를 수 있어서다.
120
+ let shell = "pwsh.exe";
121
+ let result: ExecResult;
122
+ try {
123
+ result = await deps.exec(shell, args, { cwd: deps.cwd, timeout: 60_000 });
124
+ } catch (error) {
125
+ if ((error as NodeJS.ErrnoException | undefined)?.code !== "ENOENT") throw error;
126
+ shell = "powershell.exe";
127
+ result = await deps.exec(shell, args, { cwd: deps.cwd, timeout: 60_000 });
128
+ }
129
+ const line = result.stdout.split(/\r?\n/).map((text) => text.trim()).filter((text) => text.startsWith("{")).pop();
130
+ if (!line) {
131
+ const detail = [result.stderr, result.stdout].filter(Boolean).join("\n").trim().slice(0, 600);
132
+ throw new Error(`windows_control: ${shell} exit ${result.code}, 결과 JSON 없음${detail ? `: ${detail}` : ""}`);
133
+ }
134
+ return { ...(JSON.parse(line) as ControlResult), shell };
135
+ }
136
+
137
+ /** ConvertTo-Json은 원소가 하나인 배열을 객체로 내보낸다. */
138
+ function asList<T>(value: T[] | T | null | undefined): T[] {
139
+ if (value === null || value === undefined) return [];
140
+ return Array.isArray(value) ? value : [value];
141
+ }
142
+
143
+ function quote(text: string | null | undefined): string {
144
+ if (text === null || text === undefined) return "(읽기 실패)";
145
+ const flat = text.replace(/\r?\n/g, "⏎");
146
+ return JSON.stringify(flat.length > 80 ? `${flat.slice(0, 80)}…` : flat);
147
+ }
148
+
149
+ function describe(child: ChildControl): string {
150
+ const flags = [child.Visible ? "" : "숨김", child.Enabled ? "" : "비활성", child.Check >= 0 ? `check=${child.Check}` : ""].filter(Boolean);
151
+ return `${child.ClassName} ${quote(child.Text)} hwnd=${child.Hwnd}${child.ControlId ? ` id=${child.ControlId}` : ""}${flags.length ? ` (${flags.join(", ")})` : ""}`;
152
+ }
153
+
154
+ function describeChange(change: ControlChange): string {
155
+ if (change.added) return ` + ${change.className} hwnd=${change.hwnd} 새로 생김 ${quote(change.text)}`;
156
+ if (change.removed) return ` - ${change.className} hwnd=${change.hwnd} 사라짐 ${quote(change.text)}`;
157
+ const parts: string[] = [];
158
+ if (change.textBefore !== change.textAfter) parts.push(`${quote(change.textBefore)} → ${quote(change.textAfter)}`);
159
+ if (change.checkBefore !== change.checkAfter) parts.push(`check ${change.checkBefore} → ${change.checkAfter}`);
160
+ if (parts.length === 0) parts.push(`enabled=${change.enabledAfter} visible=${change.visibleAfter}`);
161
+ return ` ${change.className} hwnd=${change.hwnd}: ${parts.join(", ")}`;
162
+ }
163
+
164
+ function listBlock(title: string, children: ChildControl[]): string[] {
165
+ const shown = children.slice(0, MAX_LISTED).map((child) => ` ${describe(child)}`);
166
+ const more = children.length > MAX_LISTED ? [` … ${children.length - MAX_LISTED}개 더`] : [];
167
+ return [`${title} (${children.length}개):`, ...shown, ...more];
168
+ }
169
+
170
+ export function formatControlResult(result: ControlResult): string {
171
+ const lines: string[] = [];
172
+ if (!result.ok && result.error) {
173
+ switch (result.error) {
174
+ case "window-not-found":
175
+ return `windows_control: 창 hwnd=${result.window}가 없다. computer.window()로 id를 다시 확인한다.`;
176
+ case "control-not-found":
177
+ return [`windows_control: 조건에 맞는 컨트롤이 없다. 아무것도 보내지 않았다.`, ...listBlock("창의 컨트롤", asList(result.controls))].join("\n");
178
+ case "ambiguous":
179
+ return [`windows_control: 조건에 맞는 컨트롤이 여러 개다. index로 하나를 고른다. 아무것도 보내지 않았다.`, ...listBlock("후보(index 순서)", asList(result.matches))].join("\n");
180
+ case "index-out-of-range":
181
+ return [`windows_control: index가 후보 수를 넘는다. 아무것도 보내지 않았다.`, ...listBlock("후보(index 순서)", asList(result.matches))].join("\n");
182
+ case "control-disabled":
183
+ return `windows_control: 대상이 비활성이라 아무것도 보내지 않았다: ${result.target ? describe(result.target) : ""}`;
184
+ case "script-error":
185
+ return `windows_control: Windows 스크립트 오류: ${result.message ?? ""}`;
186
+ }
187
+ }
188
+ const foreground = result.foregroundUnchanged
189
+ ? "전면 창 유지."
190
+ : "주의: 호출 사이 전면 창이 바뀌었다(이 호출 때문인지 사용자 조작인지 이 결과로는 구분할 수 없다).";
191
+ if (result.action === "list") {
192
+ lines.push(...listBlock(`창 hwnd=${result.window}의 자식 컨트롤`, asList(result.controls)), foreground);
193
+ return lines.join("\n");
194
+ }
195
+ const target = result.target ? describe(result.target) : "(대상 미상)";
196
+ const verb = result.action === "click" ? "BM_CLICK" : result.action === "setText" ? "WM_SETTEXT" : "읽기";
197
+ if (result.ok) {
198
+ lines.push(`${verb} ${result.action === "read" ? "완료" : "전달"}: ${target}${result.elapsedMs !== undefined && result.action !== "read" ? ` (${result.elapsedMs}ms)` : ""}`);
199
+ } else if (result.win32Error === WIN32_TIMEOUT) {
200
+ lines.push(`${verb} 전달됐지만 처리기가 ${result.action === "click" ? "timeoutMs" : "2초"} 안에 끝나지 않았다(모달 창이 열렸을 수 있다): ${target}`);
201
+ } else if (result.win32Error === WIN32_ACCESS_DENIED) {
202
+ lines.push(`${verb} 거부됨(Win32 5): 대상 프로그램이 관리자 권한으로 실행 중이다. 아무 효과도 없다고 본다: ${target}`);
203
+ } else {
204
+ lines.push(`${verb} 실패(Win32 ${result.win32Error}): ${target}`);
205
+ }
206
+ if (result.action !== "read" && !result.windowClosed) {
207
+ lines.push(result.targetAfter ? `대상 지금: ${describe(result.targetAfter)}` : "대상 컨트롤이 사라졌다.");
208
+ }
209
+ if (result.windowClosed) lines.push("창이 닫혔다.");
210
+ const changes = asList(result.changes);
211
+ if (result.action !== "read" && !result.windowClosed) {
212
+ if (changes.length > 0) lines.push("창 안 컨트롤 변화:", ...changes.map(describeChange));
213
+ else lines.push("창 안 컨트롤의 텍스트·체크·활성 변화 없음. 효과는 AX나 화면으로 확인한다(새 창이 열렸으면 computer.windows()).");
214
+ }
215
+ lines.push(foreground);
216
+ return lines.join("\n");
217
+ }
218
+
219
+ const factory: CustomToolFactory = (pi) => ({
220
+ name: "windows_control",
221
+ label: "Windows Control",
222
+ loadMode: "essential",
223
+ description:
224
+ "Drive classic Win32 controls (WinForms, VB6, MFC: buttons, check boxes, text boxes) in a Windows window WITHOUT taking the user's foreground or focus. "
225
+ + "Sends `WM_SETTEXT` / `BM_CLICK` with SendMessageTimeout to the control's own child window, then reads every child control again and reports what changed (text, check state, enabled) and whether the foreground window stayed the same. "
226
+ + "Use it instead of computer `el.press()`/`el.setValue()` (which bring the window to the front on Windows) or `win.click` (silently ignored by WinForms buttons). "
227
+ + "`window` is the id from `computer.window()` (the top-level HWND). `list` shows the child controls; other actions pick one control by exact `text` and/or `className` substring, with `index` when several match (ambiguous picks are refused, nothing sent). "
228
+ + "Not for WPF, WinUI, UWP, web/Chromium or custom-drawn controls (they have no child window per control). "
229
+ + "Same authorization as computer input: only windows the user asked you to operate; confirm before consequential buttons (save, delete, send, start a machine).",
230
+ parameters: pi.zod.object({
231
+ window: pi.zod.union([pi.zod.string(), pi.zod.number()]).describe("Top-level window id from computer.window() (HWND)"),
232
+ action: pi.zod.enum(["list", "read", "setText", "click"]).describe("list child controls, read one, set its text, or click it"),
233
+ text: pi.zod.string().optional().describe("Exact current text/caption of the control, e.g. a button label"),
234
+ className: pi.zod.string().optional().describe("Case-insensitive substring of the Win32 class, e.g. EDIT, BUTTON, STATIC"),
235
+ index: pi.zod.number().optional().describe("0-based pick among several matches, in list order"),
236
+ value: pi.zod.string().optional().describe("New text for setText"),
237
+ timeoutMs: pi.zod.number().optional().describe("How long click waits for the button handler (default 2000; a modal dialog keeps it running)"),
238
+ }),
239
+
240
+ async execute(_toolCallId, params, _onUpdate, _ctx, signal) {
241
+ const exec: Exec = (command, args, options) => pi.exec(command, args, { ...options, signal });
242
+ const result = await runWindowsControl(params as WindowsControlParams, {
243
+ exec,
244
+ cwd: pi.cwd,
245
+ wsl: process.platform === "linux" && Boolean(process.env.WSL_DISTRO_NAME),
246
+ scriptPath: posix.join(import.meta.dir, "control.ps1"),
247
+ });
248
+ return {
249
+ content: [{ type: "text", text: formatControlResult(result) }],
250
+ details: result,
251
+ };
252
+ },
253
+ });
254
+
255
+ export default factory;
@@ -2295,6 +2295,15 @@ function createRunPageScope(page: Page, restoreInterception: () => Promise<void>
2295
2295
  legacyPatched: "- 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.",
2296
2296
  patched: "- After `BackgroundUnavailable`, follow the setting the error names. Under `takeover`, a per-call `takeover: true` needs no confirmation; use it instead of `computer.control.acquire`. Under `refuse` nothing was sent, and `takeover: true`, `raise()` and `control.acquire` are refused too: drive web pages through `browser` (relay) and controls through AX, or hand that step to the user. Acquired control requires live human confirmation; acquire only on user request and release it when finished.\n- Check the outcome in the same eval cell as the action: `wait()` for exact AX/DOM text or properties first. Only when the wording varies, pass the relevant AX text (not a screenshot) to `judge(state, {outcome: {type: \"choice\", instructions, criteria: {done, not_done, unknown}}})` instead of spending a model turn; treat `unknown` or low confidence as not done. Never let `judge()` decide focus, window or process identity, permissions, or whether input may be sent; those come from the OS and tool results.",
2297
2297
  },
2298
+ {
2299
+ // 2026-10-21 사무실 실측(doc/history/2026/10/21-computer-background): WinForms에서 AX press·setValue는 refuse에서도
2300
+ // 창을 앞으로 가져왔고, 게시된 win.click은 오류 없이 무시됐다(status unverified). 한 동작이 0.1~0.4초인데
2301
+ // 체감 지연은 동작 사이 모델 턴(p50 6.5~8.5초)이라, 한 셀 묶음과 windows_control 도구를 안내한다.
2302
+ file: "src/prompts/system/computer-use.md",
2303
+ marker: "Classic Win32 windows (WinForms, VB6, MFC)",
2304
+ anchor: "- Use AX for exposed semantic controls; use window screenshots and pixels for canvas/custom-drawn surfaces. Group predictable actions with verification in `computer.run`.",
2305
+ patched: "- Use AX for exposed semantic controls; use window screenshots and pixels for canvas/custom-drawn surfaces. Group predictable actions with verification in `computer.run`: one cell per act-wait-check sequence, not one model turn per click; each action takes well under a second, the turns between them are what the user waits for.\n- Classic Win32 windows (WinForms, VB6, MFC) on Windows: AX `press()`/`setValue()` on a window that is not in front either bring it to the foreground (even under `refuse`) or, on newer cores, fail with `BackgroundUnavailable`; a posted `win.click` on their buttons is silently ignored (`unverified`, no error). When the `windows_control` tool is available, use it for their buttons, check boxes and text boxes: it keeps the user's foreground and works while the window is covered. Treat any `unverified` action as not done until AX or the tool's readback shows the change.",
2306
+ },
2298
2307
  {
2299
2308
  // 빚의 정체(어느 steer 메시지인가)를 표현하는 최소 타입.
2300
2309
  file: "src/session/irc-bridge.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cuelo",
3
- "version": "0.10.49",
3
+ "version": "0.10.51",
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": {