cuelo 0.6.5 → 0.6.6
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 +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/updates/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page.js +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover.html +1 -1
- package/.next/server/app/recover.rsc +2 -2
- package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
- package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +9 -9
- package/.next/server/chunks/1517.js +3 -3
- package/.next/server/chunks/3707.js +1 -1
- 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/app/{layout-1f400c6ff9faef10.js → layout-0cd3ee1b7ad71a56.js} +1 -1
- package/.next/static/chunks/app/{page-73ab54909e09eb66.js → page-42bb9e939eef51e6.js} +3 -3
- package/.next/trace +105 -105
- package/.next/trace-build +1 -1
- package/README.md +4 -0
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +1 -1
- package/Tools/OMP_Global_Config/agent/RULES.md +1 -0
- package/Tools/OMP_Global_Config/agent/extensions/skim.ts +9 -2
- package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +2 -1
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +337 -0
- package/package.json +1 -1
- /package/.next/static/{p4VT4B7cMX4CRW9JBo9-M → 3-TNbqZBhQBsgN3SMAKv1}/_buildManifest.js +0 -0
- /package/.next/static/{p4VT4B7cMX4CRW9JBo9-M → 3-TNbqZBhQBsgN3SMAKv1}/_ssgManifest.js +0 -0
package/.next/trace-build
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"run-webpack","duration":
|
|
1
|
+
[{"name":"run-webpack","duration":91652796,"timestamp":741083,"id":14,"parentId":1,"tags":{},"startTime":1790765555546,"traceId":"c18bf164adcd5868"},{"name":"run-typescript","duration":14133056,"timestamp":92736076,"id":10707,"parentId":1,"tags":{},"startTime":1790765647541,"traceId":"c18bf164adcd5868"},{"name":"static-check","duration":3607011,"timestamp":106953217,"id":10710,"parentId":1,"tags":{},"startTime":1790765661759,"traceId":"c18bf164adcd5868"},{"name":"static-generation","duration":9747103,"timestamp":110660497,"id":10862,"parentId":1,"tags":{},"startTime":1790765665466,"traceId":"c18bf164adcd5868"},{"name":"collect-build-traces","duration":16611498,"timestamp":110624918,"id":10859,"parentId":1,"tags":{},"startTime":1790765665430,"traceId":"c18bf164adcd5868"},{"name":"telemetry-flush","duration":72,"timestamp":127240213,"id":10871,"parentId":1,"tags":{},"startTime":1790765682046,"traceId":"c18bf164adcd5868"},{"name":"next-build","duration":126587215,"timestamp":653094,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1790765555458,"traceId":"c18bf164adcd5868"}]
|
package/README.md
CHANGED
|
@@ -81,6 +81,10 @@ CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규
|
|
|
81
81
|
|
|
82
82
|
- 모델 역할(`default`, `smol`, `slow`, `plan`, `commit`, `task` 등)별 모델 지정·전환 — TUI의 `/model`과 같은 `config.yml`을 씁니다.
|
|
83
83
|
- 입력창의 `/` 명령은 대기 중뿐 아니라 실행 중에도 Enter·Steer·Follow-up으로 실행할 수 있습니다. 내장 명령은 즉시 처리하고, 발견된 확장·스킬·프롬프트 템플릿 명령은 해당 명령 경로로 전달합니다. 사용할 수 없거나 실행 중 허용되지 않는 명령은 안내하며 일반 채팅으로 전송하지 않습니다.
|
|
84
|
+
- Codex 응답이 진행 중일 때 보낸 Steer가 서버의 "unsupported_native_inflight_message"로 거절돼도 대화가 오류로 끝나지 않습니다. 그 메시지는 현재 응답이 끝난 뒤 다음 요청으로 한 번 전달됩니다.
|
|
85
|
+
- 에이전트가 일반 구현 방식을 고르는 질문에 추천안을 붙여 물으면, 2분 동안 답이 없을 때 추천안으로 진행합니다. 질문 창에 그 안내가 표시되고, 옵션을 고르거나 답을 입력하면 대기 시간이 다시 시작됩니다. 자동 선택은 사용자 승인으로 치지 않으며, 배포·삭제·비용·계정·권한 확인 질문은 답할 때까지 기다립니다.
|
|
86
|
+
- Main이 OpenAI 모델에서 Fast를 켜면 새로 만드는 OpenAI 계열 SubAgent도 Fast로 요청합니다. Anthropic·Google SubAgent에는 Fast를 자동으로 넘기지 않고, Fast가 꺼져 있으면 SubAgent도 표준 처리로 요청합니다.
|
|
87
|
+
- 네이티브 창을 다루는 `computer.run`에서 버튼을 한 번 누른 뒤, 원하는 행·문구·값이 나타날 때까지 로컬 읽기로만 다시 확인하고 결과만 돌려줍니다. 기다리는 동안 다시 누르지 않으며, 제한 시간 초과·취소·읽기 오류를 서로 구분해 알립니다. 저장·전송처럼 결과가 남는 클릭의 승인 절차는 바뀌지 않습니다([사용법](./docs/harness.md#typed-judgment-routing-jev)).
|
|
84
88
|
- provider 로그인·API 키, `models.yml`, 플러그인, 스킬을 웹에서 관리합니다.
|
|
85
89
|
- 프로젝트 파일 탐색과 소스·문서·이미지·오디오·PDF 미리보기, Git worktree 전환.
|
|
86
90
|
- 프로젝트 신뢰: 설정에서 프로젝트별 승인을 관리·해제하고, 실행 중에는 작업을 끊지 않고 신뢰 승인을 예약합니다. 신뢰하지 않은 저장소의 확장·훅·도구·MCP는 실행하지 않습니다([docs/project-trust.md](./docs/project-trust.md)).
|
|
@@ -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.6.
|
|
12
|
+
version: '0.6.6',
|
|
13
13
|
distribution: 'cuelo',
|
|
14
14
|
coreVersion: '18.4.4',
|
|
15
15
|
};
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
확인을 요청하지 않는다. 테스트·문서·현재 동작 중 어느 하나도 자동으로 정답이 아니므로 요구와
|
|
36
36
|
충돌하면 실제 동작을 확인한다. 제품 의미가 달라지는데 증거로 정할 수 없거나 권한·외부 승인이
|
|
37
37
|
없는 항목만 사용자 판단으로 올리고, 그 부분이 막혀도 독립적으로 허용된 작업은 계속 수행한다.
|
|
38
|
+
- 그래도 사용자가 골라야 하는 일반 구현 선택을 `ask`로 물을 때는 유효한 `recommended`와 `autoSelectRecommended: true`를 달아 2분 동안 답이 없으면 추천안으로 진행하게 한다. 삭제·배포·비용·계정·권한·provider 안전 확인과 그 밖의 고영향 확인 질문에는 자동선택을 켜지 않고 답을 기다린다. 타임아웃 자동선택은 사용자 승인이 아니므로 승인이 필요한 작업의 근거로 쓰지 않는다.
|
|
38
39
|
- 승인된 목표 안에서 발견한 직접 원인과 그 변경이 만든 회귀는 사용자가 다시 지시하지 않아도
|
|
39
40
|
수정하고 관련 검증까지 마친다. 허용 경계 밖, 다른 소유자의 파일, 원래 완료 조건과 무관한
|
|
40
41
|
정리·기능 추가는 여전히 범위 밖이며 읽을 수 있다는 것이 수정 권한은 아니다.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lstat, open, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
|
-
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext, SessionManager } from "@oh-my-pi/pi-coding-agent";
|
|
4
4
|
// 런타임 패키지는 legacy-pi 확장 loader가 host SDK 경로로 재작성하므로 아래에서 지연 import한다.
|
|
5
5
|
|
|
6
6
|
// Gemini의 큰 context를 무조건 채우지 않는다. 48 KiB/파일, 192 KiB/요청은 대량 문서 조사의
|
|
@@ -156,7 +156,7 @@ async function collect(input: SkimInput, cwd: string, signal: AbortSignal): Prom
|
|
|
156
156
|
return { prompt, notes, count: chunks.length };
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
async function callModel(prompt: string, ctx: ExtensionContext, signal: AbortSignal, requested: typeof GEMINI | typeof DEEPSEEK): Promise<string> {
|
|
159
|
+
export async function callModel(prompt: string, ctx: ExtensionContext, signal: AbortSignal, requested: typeof GEMINI | typeof DEEPSEEK): Promise<string> {
|
|
160
160
|
const [{ findScopedSettings }, { completeSimple }] = await Promise.all([
|
|
161
161
|
import("@oh-my-pi/pi-coding-agent/config/settings"), import("@oh-my-pi/pi-ai"),
|
|
162
162
|
]);
|
|
@@ -174,6 +174,13 @@ async function callModel(prompt: string, ctx: ExtensionContext, signal: AbortSig
|
|
|
174
174
|
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
|
175
175
|
}, { apiKey: ctx.modelRegistry.resolver(model, sessionId), sessionId, maxTokens: requested === DEEPSEEK ? 512 : 1024,
|
|
176
176
|
disableReasoning: true, signal });
|
|
177
|
+
// judge·cache-warm과 같은 대화 밖 비용 장부(`model_usage`)에 응답이 돌아온 시도만 남긴다. 오류·빈 답
|
|
178
|
+
// 응답도 청구된 시도라 먼저 기록하고, 응답 없이 던진 예외는 관측한 usage가 없어 남기지 않는다.
|
|
179
|
+
// ctx 타입은 읽기 전용이지만 런타임 값은 세션의 SessionManager다(extensions/runner.ts).
|
|
180
|
+
(ctx.sessionManager as Partial<Pick<SessionManager, "appendModelUsage">>).appendModelUsage?.({
|
|
181
|
+
purpose: "skim", role: requested === GEMINI ? "skim" : undefined, api: model.api, provider: model.provider,
|
|
182
|
+
model: model.id, usage: response.usage, stopReason: response.stopReason, errorMessage: response.errorMessage,
|
|
183
|
+
}, { sessionId, parentId: ctx.sessionManager.getLeafId() });
|
|
177
184
|
if (response.stopReason !== "stop") throw new Error(response.errorMessage ?? `Model stopped: ${response.stopReason}`);
|
|
178
185
|
const answer = response.content.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
|
|
179
186
|
if (!answer) throw new Error(`${requested}가 빈 답을 반환했습니다.`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- source-fingerprint:
|
|
1
|
+
<!-- source-fingerprint: 393de19755d97ad6 -->
|
|
2
2
|
# Global Rules
|
|
3
3
|
|
|
4
4
|
(Model-facing English copy of the Korean source `RULES.md`.)
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- Deploy confirmation (user, 2026-09-26, 2026-09-29 and 2026-09-30): the home PC (hostname `KYS`) and the office PC (its hostname is in the private deploy table) follow the same procedure. Once that PC's CUELO session has verified a source change and committed and pushed it, watch that commit's GitHub CI until it finishes, report the result, and ask whether to deploy. After the user approves, run `Tools/CUELO_Setup/deploy-live.ps1` and `Tools/OMP_Global_Config/verify.ps1 -StrictRuntime` for that same revision (`Tools/CUELO_Setup/setup.ps1` is the full app installer, not this step). The deploy transaction applies the mirror profile with `Tools/OMP_Global_Config/setup.ps1 -ProfileOnly` before starting the new server, then checks that the new server's `/api/models` role selectors match the mirror `config.yml` `modelRoles` and records it in the receipt `profile`, so do not run ProfileOnly or `CUELO-Restart` separately after a deploy. If the roles differ or could not be observed, deploy-live exits 3 without rolling back; restart as its guidance says and check `/api/models` again. An in-session deploy returns once the worker accepts, so after resuming check yourself that the transaction receipt's `profile.modelRoles.matched` is `true`. If only the profile changed without a deploy, restart the server after ProfileOnly and do the same role check. The live server reads Settings once at startup; file changes or a touch do not reload it. Check the PC with `hostname` first. Do not add automatic deployment such as a scheduled task. Deleting stages or rollbacks, deploying to any PC other than these two, updating the CLI, and anything that incurs cost still need separate approval.
|
|
17
17
|
- CI watching (user, 2026-09-28): when watching a commit's CI, name the workflow you wait on (`gh run list --workflow <name>` or similar) and report the moment that run finishes. Never use "every run for this SHA is completed" as the condition: a release/deploy run triggered by CI success (`workflow_run`) appears seconds later on the same SHA and holds back the CI report. Watch such follow-on runs separately after reporting.
|
|
18
18
|
- Narrow implementation uncertainty using explicit requirements and approved contracts, actual callers and data flow, test/reproduction results, and current behavior and docs. Once evidence settles the direction, proceed and record the basis; do not ask for confirmation on every minor implementation choice. Tests, docs, and current behavior are not automatically right; when they conflict with the requirements, check the actual behavior. Escalate to the user only items that change product meaning and cannot be settled by evidence, or that lack authority or external approval; keep doing independently allowed work even if that part is blocked.
|
|
19
|
+
- When a normal implementation choice still needs the user and you ask it with `ask`, give it a valid `recommended` and `autoSelectRecommended: true` so it proceeds with the recommendation after 2 minutes without an answer. Never enable auto-selection for deletion, deployment, cost, account, permission, provider safety, or other high-impact confirmations; wait for the answer. A timed-out auto-selection is not user approval, so never use it as the basis for an action that needs approval.
|
|
19
20
|
- Direct causes found within the approved goal, and regressions your change created, are fixed and verified without the user asking again. Outside the allowed boundary, other owners' files, and cleanup or features unrelated to the original completion criteria remain out of scope; being able to read something is not permission to modify it.
|
|
20
21
|
- Even before a final action that needs approval, first finish already-approved or reversible preparation and read-only investigation/review so there is a concrete target to review, then ask for approval. This preparation principle never grants or lowers existing prior-approval boundaries, including deletion, deployment, cost, and compatibility breaks.
|
|
21
22
|
- Explicit user instructions take precedence over Skill guidance, but apply within higher-priority system/developer rules and existing approval boundaries. If a Skill makes you ask for approval, stop, or go in a different direction than requested, name that Skill and the relevant guidance exactly and briefly explain why it applies. Do not repeat that explanation for routine Skill use.
|
|
@@ -6787,6 +6787,193 @@ interface ComputerInputTarget {
|
|
|
6787
6787
|
anchor: "- Each window `.ax()` starts a ref generation. Current/previous snapshot refs remain valid; older refs throw `StaleRef`. Re-snapshot; NEVER guess.\n",
|
|
6788
6788
|
patched: "- Each window `.ax()` starts a ref generation. Current/previous snapshot refs remain valid. An older ref is reacquired only when its saved fingerprint (role, name, AutomationId, named parents, position) matches exactly one element; the element's `ref` changes and the action result carries `reacquired`. Otherwise it throws `StaleRef` naming why and the candidates: re-snapshot; NEVER guess.\n- Actions return `{ action, status, suggestedNext, evidence?, escalation?, reacquired? }`. `verified` rests on an AX readback; `unverified` (all pointer/keyboard input) means delivered but unobserved: `reobserve` before relying on it; `suspected_noop` → follow `escalation`. Inside `computer.run`, a thrown action error carries `error.computerAction`.\n",
|
|
6789
6789
|
},
|
|
6790
|
+
{
|
|
6791
|
+
// 2026-09-30 computer 목표 대기(사용자 요청 0.6.6): 행동은 사용자 코드에서 한 번, 목표는 wait(predicate)가 로컬 읽기로만 확인한다.
|
|
6792
|
+
// 이 편집부터 worker.ts 7개는 한 묶음이다. probe 는 run context 를 복제해 goalProbe 를 켜고, 그 signal 은 deadline·wait 종료에도 끝난다.
|
|
6793
|
+
// 간격은 Playwright pollAgainstDeadline 기본값(https://github.com/microsoft/playwright/blob/main/packages/isomorphic/timeoutRunner.ts)을 따른다.
|
|
6794
|
+
file: "src/tools/computer/worker.ts",
|
|
6795
|
+
marker: "import { untilAborted } from \"@oh-my-pi/pi-utils/abortable\";",
|
|
6796
|
+
anchor: `import * as postmortem from "@oh-my-pi/pi-utils/postmortem";`,
|
|
6797
|
+
patched: `import { untilAborted } from "@oh-my-pi/pi-utils/abortable";
|
|
6798
|
+
import * as postmortem from "@oh-my-pi/pi-utils/postmortem";`,
|
|
6799
|
+
},
|
|
6800
|
+
{
|
|
6801
|
+
file: "src/tools/computer/worker.ts",
|
|
6802
|
+
marker: " goalProbe?: boolean;",
|
|
6803
|
+
anchor: `interface ComputerRunContext {
|
|
6804
|
+
signal: AbortSignal;
|
|
6805
|
+
readOnly: boolean;
|
|
6806
|
+
snapshot: ComputerSessionSnapshot;
|
|
6807
|
+
output: RunOutput;
|
|
6808
|
+
screenshots: ComputerScreenshot[];
|
|
6809
|
+
}`,
|
|
6810
|
+
patched: `interface ComputerRunContext {
|
|
6811
|
+
signal: AbortSignal;
|
|
6812
|
+
readOnly: boolean;
|
|
6813
|
+
/** Set while a \`wait(predicate)\` probe runs; \`signal\` then also ends at the wait's deadline and when it settles. */
|
|
6814
|
+
goalProbe?: boolean;
|
|
6815
|
+
snapshot: ComputerSessionSnapshot;
|
|
6816
|
+
output: RunOutput;
|
|
6817
|
+
screenshots: ComputerScreenshot[];
|
|
6818
|
+
}
|
|
6819
|
+
|
|
6820
|
+
/** Pauses between goal probes when \`interval\` is not given: Playwright's \`pollAgainstDeadline\` default; the last one repeats. */
|
|
6821
|
+
const GOAL_POLL_INTERVALS = [100, 250, 500, 1000] as const;
|
|
6822
|
+
|
|
6823
|
+
/** A goal probe only reads: desktop input, clipboard writes, and tool-bridge calls would repeat on every probe. */
|
|
6824
|
+
function goalProbeRefusal(method: string): ToolError {
|
|
6825
|
+
return new ToolError(
|
|
6826
|
+
\`wait(predicate) probe cannot run '\${method}': probes only read desktop state (windows, ax, find, value); act once before wait()\`,
|
|
6827
|
+
);
|
|
6828
|
+
}`,
|
|
6829
|
+
},
|
|
6830
|
+
{
|
|
6831
|
+
file: "src/tools/computer/worker.ts",
|
|
6832
|
+
marker: " if (context.goalProbe) throw goalProbeRefusal(method);",
|
|
6833
|
+
anchor: `function guardRun(context: ComputerRunContext, method: string): void {
|
|
6834
|
+
if (context.readOnly)`,
|
|
6835
|
+
patched: `function guardRun(context: ComputerRunContext, method: string): void {
|
|
6836
|
+
if (context.goalProbe) throw goalProbeRefusal(method);
|
|
6837
|
+
if (context.readOnly)`,
|
|
6838
|
+
},
|
|
6839
|
+
{
|
|
6840
|
+
// probe 안 screenshot 은 로컬 읽기로 허용하되, 기존 silent 경로로 probe 마다 이미지가 붙지 않게 한다.
|
|
6841
|
+
file: "src/tools/computer/worker.ts",
|
|
6842
|
+
marker: " if (!options?.silent && !context.goalProbe) {",
|
|
6843
|
+
anchor: " if (!options?.silent) {",
|
|
6844
|
+
patched: " if (!options?.silent && !context.goalProbe) {",
|
|
6845
|
+
},
|
|
6846
|
+
{
|
|
6847
|
+
file: "src/tools/computer/worker.ts",
|
|
6848
|
+
marker: "this.#waitForGoal(",
|
|
6849
|
+
anchor: ` wait: (msOrPredicate: number | (() => unknown), options?: WaitPredicateOptions): Promise<unknown> => {
|
|
6850
|
+
const resolved =
|
|
6851
|
+
typeof msOrPredicate === "number"
|
|
6852
|
+
? undefined
|
|
6853
|
+
: {
|
|
6854
|
+
timeout: resolvePredicateTimeout(message.timeoutMs, options?.timeout),
|
|
6855
|
+
interval: options?.interval,
|
|
6856
|
+
};
|
|
6857
|
+
return markHandled(waitForRun(msOrPredicate, signal, resolved));
|
|
6858
|
+
},`,
|
|
6859
|
+
patched: ` wait: (msOrPredicate: number | (() => unknown), options?: WaitPredicateOptions): Promise<unknown> => {
|
|
6860
|
+
if (typeof msOrPredicate !== "function") return markHandled(waitForRun(msOrPredicate, signal));
|
|
6861
|
+
return markHandled(
|
|
6862
|
+
this.#waitForGoal(
|
|
6863
|
+
this.#runContexts.getStore() ?? runContext,
|
|
6864
|
+
msOrPredicate,
|
|
6865
|
+
resolvePredicateTimeout(message.timeoutMs, options?.timeout),
|
|
6866
|
+
options?.interval,
|
|
6867
|
+
),
|
|
6868
|
+
);
|
|
6869
|
+
},`,
|
|
6870
|
+
},
|
|
6871
|
+
{
|
|
6872
|
+
file: "src/tools/computer/worker.ts",
|
|
6873
|
+
marker: "if (this.#runContexts.getStore()?.goalProbe) throw goalProbeRefusal(`tool:${name}`);",
|
|
6874
|
+
anchor: ` callTool: (name, args) => {
|
|
6875
|
+
throwIfAborted(active.signal);
|
|
6876
|
+
return this.#callTool(active, name, args);`,
|
|
6877
|
+
patched: ` callTool: (name, args) => {
|
|
6878
|
+
throwIfAborted(active.signal);
|
|
6879
|
+
if (this.#runContexts.getStore()?.goalProbe) throw goalProbeRefusal(\`tool:\${name}\`);
|
|
6880
|
+
return this.#callTool(active, name, args);`,
|
|
6881
|
+
},
|
|
6882
|
+
{
|
|
6883
|
+
file: "src/tools/computer/worker.ts",
|
|
6884
|
+
marker: " async #waitForGoal(",
|
|
6885
|
+
anchor: ` #createDesktopScope(session: NativeDesktopSession): object {`,
|
|
6886
|
+
patched: ` /**
|
|
6887
|
+
* \`wait(predicate)\` for the goal state after an action. The first probe runs at once, then after
|
|
6888
|
+
* Playwright's back-off (or the fixed \`interval\`), each pause capped at the time left; no probe starts
|
|
6889
|
+
* after the deadline. Every probe runs in a derived context whose signal also ends at the deadline and
|
|
6890
|
+
* when the wait settles, and whose input, clipboard writes, and tool calls refuse, so a native read that
|
|
6891
|
+
* returns late cannot continue into further reads or input. Predicate errors propagate on first
|
|
6892
|
+
* occurrence; an unmet deadline is the named timeout; run cancellation stays an abort.
|
|
6893
|
+
*/
|
|
6894
|
+
async #waitForGoal(base: ComputerRunContext, predicate: () => unknown, timeout: number, interval?: number): Promise<unknown> {
|
|
6895
|
+
throwIfAborted(base.signal);
|
|
6896
|
+
const deadline = new AbortController();
|
|
6897
|
+
const timer = setTimeout(
|
|
6898
|
+
() => deadline.abort(postmortem.markExpectedCleanupError(new ToolAbortError("wait(predicate) deadline"))),
|
|
6899
|
+
timeout,
|
|
6900
|
+
);
|
|
6901
|
+
const signal = AbortSignal.any([base.signal, deadline.signal]);
|
|
6902
|
+
const probe: ComputerRunContext = { ...base, signal, goalProbe: true };
|
|
6903
|
+
const started = performance.now();
|
|
6904
|
+
let probes = 0;
|
|
6905
|
+
let probing = false;
|
|
6906
|
+
try {
|
|
6907
|
+
for (let step = 0; performance.now() - started < timeout; step++) {
|
|
6908
|
+
probes++;
|
|
6909
|
+
probing = true;
|
|
6910
|
+
const value = await untilAborted(signal, async () => await this.#runContexts.run(probe, predicate));
|
|
6911
|
+
// A probe that ends past the deadline never counts, even when truthy (a synchronous predicate can outrun the timer).
|
|
6912
|
+
if (performance.now() - started > timeout) break;
|
|
6913
|
+
probing = false;
|
|
6914
|
+
if (value) return value;
|
|
6915
|
+
const pause = interval === undefined ? GOAL_POLL_INTERVALS[Math.min(step, GOAL_POLL_INTERVALS.length - 1)]! : Math.max(interval, 10);
|
|
6916
|
+
const left = Math.max(0, timeout - (performance.now() - started));
|
|
6917
|
+
await untilAborted(signal, async () => await Bun.sleep(Math.min(pause, left)));
|
|
6918
|
+
}
|
|
6919
|
+
} catch (error) {
|
|
6920
|
+
// Only a rejection caused by this wait's own signal is remapped; predicate, native, and permission errors pass through as thrown.
|
|
6921
|
+
const aborted = signal.aborted && error instanceof Error && (error === signal.reason || error.cause === signal.reason);
|
|
6922
|
+
if (!aborted) throw error;
|
|
6923
|
+
throwIfAborted(base.signal);
|
|
6924
|
+
} finally {
|
|
6925
|
+
clearTimeout(timer);
|
|
6926
|
+
deadline.abort(postmortem.markExpectedCleanupError(new ToolAbortError("wait(predicate) ended")));
|
|
6927
|
+
}
|
|
6928
|
+
throw new ToolError(
|
|
6929
|
+
\`wait(predicate) timed out after \${timeout}ms — predicate never returned truthy (\${probes} probe\${probes === 1 ? "" : "s"}\${probing ? "; the probe still running at the deadline was abandoned" : ""})\`,
|
|
6930
|
+
);
|
|
6931
|
+
}
|
|
6932
|
+
|
|
6933
|
+
#createDesktopScope(session: NativeDesktopSession): object {`,
|
|
6934
|
+
},
|
|
6935
|
+
{
|
|
6936
|
+
// 모델이 보는 타입: 목표 대기 규칙.
|
|
6937
|
+
file: "src/tools/computer/declarations.d.ts",
|
|
6938
|
+
marker: "or poll a goal predicate until truthy and resolve with its value.",
|
|
6939
|
+
anchor: ` /** Sleep for milliseconds or poll a predicate until truthy. */
|
|
6940
|
+
readonly wait: (
|
|
6941
|
+
msOrPredicate: number | (() => unknown),
|
|
6942
|
+
options?: {
|
|
6943
|
+
/** Maximum polling time in milliseconds. */
|
|
6944
|
+
timeout?: number;
|
|
6945
|
+
/** Delay between predicate calls in milliseconds. */
|
|
6946
|
+
interval?: number;
|
|
6947
|
+
},
|
|
6948
|
+
) => Promise<unknown>;`,
|
|
6949
|
+
patched: ` /**
|
|
6950
|
+
* Sleep for milliseconds, or poll a goal predicate until truthy and resolve with its value.
|
|
6951
|
+
* Act once before \`wait\`; inside the predicate, desktop input, \`clipboard.write\`, and tool calls throw, and screenshots stay silent.
|
|
6952
|
+
* An unmet goal throws \`wait(predicate) timed out after …\`; predicate errors propagate unretried; cancel stays cancel.
|
|
6953
|
+
*/
|
|
6954
|
+
readonly wait: (
|
|
6955
|
+
msOrPredicate: number | (() => unknown),
|
|
6956
|
+
options?: {
|
|
6957
|
+
/** Maximum polling time in milliseconds (default 30s, kept below the run budget). */
|
|
6958
|
+
timeout?: number;
|
|
6959
|
+
/** Fixed delay between predicate calls in milliseconds; by default 100, 250, 500, then 1000. */
|
|
6960
|
+
interval?: number;
|
|
6961
|
+
},
|
|
6962
|
+
) => Promise<unknown>;`,
|
|
6963
|
+
},
|
|
6964
|
+
{
|
|
6965
|
+
// 모델 지시: 행동은 한 번, 목표는 wait(predicate)로 읽고, 미달이면 다시 누르지 않는다.
|
|
6966
|
+
file: "src/prompts/tools/computer.md",
|
|
6967
|
+
marker: "- Goal after an action: act ONCE",
|
|
6968
|
+
anchor: "Plain data, functions, and `RegExp` values are supported in `args`.\n",
|
|
6969
|
+
patched: "Plain data, functions, and `RegExp` values are supported in `args`.\n- Goal after an action: act ONCE, then `await wait(async () => …read…, { timeout })` for the row, text, or value the user asked for; it resolves with that value. Inside the predicate, desktop input, `clipboard.write`, and tool calls throw and screenshots stay silent; probes back off 100→1000 ms, and a late read cannot continue past the deadline. The predicate is still unsandboxed Bun/Node code. On `wait(predicate) timed out` NEVER re-click, take over, or switch windows: reobserve and report. A button state change alone does not prove a save.\n",
|
|
6970
|
+
},
|
|
6971
|
+
{
|
|
6972
|
+
file: "src/prompts/tools/computer.md",
|
|
6973
|
+
marker: "return await wait(async () => (await orders.find(",
|
|
6974
|
+
anchor: "\treturn await target.ax();\n}, { timeout: 30 });\n",
|
|
6975
|
+
patched: "\treturn await target.ax();\n}, { timeout: 30 });\nawait computer.run(async ({ desktop, wait }) => {\n\tconst orders = await desktop.window({ title: \"Orders\" });\n\tconst [save] = await orders.find({ role: \"button\", title: \"Save\" });\n\tawait save.press();\n\treturn await wait(async () => (await orders.find({ title: \"Saved row 1\" }))[0]?.title, { timeout: 5000 });\n}, { timeout: 30 });\n",
|
|
6976
|
+
},
|
|
6790
6977
|
{
|
|
6791
6978
|
// MCP 선택 연결: 요청별 MCP 선택(opt-in). all이 기본이며 per-request면 시작 시 연결하지 않고 요청마다 확장이 고른다.
|
|
6792
6979
|
file: "src/mcp/settings.ts",
|
|
@@ -7720,6 +7907,156 @@ export function summarizeAutoLearnSaved(messages: readonly AgentMessage[], from:
|
|
|
7720
7907
|
anchor: "remember with `learn` when memory enabled. If nothing worth keeping, do nothing.",
|
|
7721
7908
|
patched: "remember with `learn` when memory enabled. Begin each `learn` memory with one short headline sentence (under 80 characters) that states the lesson, then give the details. If nothing worth keeping, do nothing.",
|
|
7722
7909
|
},
|
|
7910
|
+
// Codex native turn lane 는 실행 중 `response.steer`를 `response.steer.failed`가 아니라 일반
|
|
7911
|
+
// error 프레임(code=unsupported_native_inflight_message)으로 거절한다(2026-09-30 사용자 보고).
|
|
7912
|
+
// 그 프레임은 steer 제출자가 아니라 응답 스트림으로 들어가 진행 중 턴이 죽었다. steer 대기자가
|
|
7913
|
+
// 있을 때만 이 정확한 코드를 거절 ack 로 돌려 기존 claim.reject() → 다음 경계 일반 전달 경로를
|
|
7914
|
+
// 타게 하고, 같은 소켓은 이후 steer 를 전송하지 않는다(새 소켓은 다시 한 번 시도한다).
|
|
7915
|
+
// 대기자가 없거나 다른 코드인 error 프레임은 그대로 스트림에 push 한다.
|
|
7916
|
+
{
|
|
7917
|
+
file: "../pi-ai/src/providers/openai-codex-responses.ts",
|
|
7918
|
+
marker: "#steerUnsupported = false;",
|
|
7919
|
+
anchor: ` /** Steering whose automatic successor the active attach request is reading. */
|
|
7920
|
+
#attachSteerIds?: ReadonlySet<string>;`,
|
|
7921
|
+
patched: ` /** Steering whose automatic successor the active attach request is reading. */
|
|
7922
|
+
#attachSteerIds?: ReadonlySet<string>;
|
|
7923
|
+
/** The server answered a steer with unsupported_native_inflight_message; this socket no longer submits steers. */
|
|
7924
|
+
#steerUnsupported = false;`,
|
|
7925
|
+
},
|
|
7926
|
+
{
|
|
7927
|
+
file: "../pi-ai/src/providers/openai-codex-responses.ts",
|
|
7928
|
+
marker: "this.#steerWaiters.shift()?.resolve({",
|
|
7929
|
+
anchor: ` // Steering acknowledgements belong to the submitter, not to the
|
|
7930
|
+
// response stream they interleave with.
|
|
7931
|
+
if (typeof parsed.type === "string" && parsed.type.startsWith("response.steer.")) {`,
|
|
7932
|
+
patched: ` // The native turn lane refuses steering with a bare error frame. It answers the
|
|
7933
|
+
// pending steer, not the response stream, so settle that waiter as rejected.
|
|
7934
|
+
if (
|
|
7935
|
+
parsed.type === "error" &&
|
|
7936
|
+
parsed.code === "unsupported_native_inflight_message" &&
|
|
7937
|
+
this.#steerWaiters.length > 0
|
|
7938
|
+
) {
|
|
7939
|
+
this.#steerUnsupported = true;
|
|
7940
|
+
this.#steerWaiters.shift()?.resolve({
|
|
7941
|
+
accepted: false,
|
|
7942
|
+
code: parsed.code,
|
|
7943
|
+
message: typeof parsed.message === "string" ? parsed.message : undefined,
|
|
7944
|
+
});
|
|
7945
|
+
return;
|
|
7946
|
+
}
|
|
7947
|
+
// Steering acknowledgements belong to the submitter, not to the
|
|
7948
|
+
// response stream they interleave with.
|
|
7949
|
+
if (typeof parsed.type === "string" && parsed.type.startsWith("response.steer.")) {`,
|
|
7950
|
+
},
|
|
7951
|
+
{
|
|
7952
|
+
file: "../pi-ai/src/providers/openai-codex-responses.ts",
|
|
7953
|
+
marker: "code: \"unsupported_native_inflight_message\", message: undefined",
|
|
7954
|
+
anchor: ` return Promise.reject(new CodexWebSocketTransportError(\`websocket connection is unavailable\`));
|
|
7955
|
+
}
|
|
7956
|
+
const event = { type: "response.steer", previous_response_id: previousResponseId, input };`,
|
|
7957
|
+
patched: ` return Promise.reject(new CodexWebSocketTransportError(\`websocket connection is unavailable\`));
|
|
7958
|
+
}
|
|
7959
|
+
if (this.#steerUnsupported) {
|
|
7960
|
+
return Promise.resolve({ accepted: false, code: "unsupported_native_inflight_message", message: undefined });
|
|
7961
|
+
}
|
|
7962
|
+
const event = { type: "response.steer", previous_response_id: previousResponseId, input };`,
|
|
7963
|
+
},
|
|
7964
|
+
// 2026-09-30 사용자 요청(0.6.6): 일반 구현 선택 질문만 `ask.timeout` 무응답 시 추천안으로 진행한다.
|
|
7965
|
+
// 자동선택은 긍정 opt-in(`autoSelectRecommended: true`)이고 유효한 `recommended`가 필수다. 한 호출의
|
|
7966
|
+
// 모든 질문이 조건을 채울 때만 timeout 을 넘기고, 나머지(기존 호출·승인 질문·plan mode)는 무기한 기다린다.
|
|
7967
|
+
// 타임아웃 결과는 사용자 응답·승인이 아니라고 결과 문구에 적는다. 키워드 판정은 두지 않는다.
|
|
7968
|
+
{
|
|
7969
|
+
file: "src/tools/ask.ts",
|
|
7970
|
+
marker: `"autoSelectRecommended?": arkType("boolean")`,
|
|
7971
|
+
anchor: ` "recommended?": arkType("number").describe("0-based default index"),
|
|
7972
|
+
}).narrow((question, ctx) => {`,
|
|
7973
|
+
patched: ` "recommended?": arkType("number").describe("0-based default index"),
|
|
7974
|
+
"autoSelectRecommended?": arkType("boolean").describe(
|
|
7975
|
+
"opt-in: pick recommended after ask.timeout with no answer; reversible implementation choices only",
|
|
7976
|
+
),
|
|
7977
|
+
}).narrow((question, ctx) => {`,
|
|
7978
|
+
},
|
|
7979
|
+
{
|
|
7980
|
+
file: "src/tools/ask.ts",
|
|
7981
|
+
marker: "never guess the first option on the user's behalf",
|
|
7982
|
+
anchor: ` return [options[recommended]!.label];
|
|
7983
|
+
}
|
|
7984
|
+
return [options[0]!.label];
|
|
7985
|
+
}`,
|
|
7986
|
+
patched: ` return [options[recommended]!.label];
|
|
7987
|
+
}
|
|
7988
|
+
// No valid recommendation: never guess the first option on the user's behalf.
|
|
7989
|
+
return [];
|
|
7990
|
+
}`,
|
|
7991
|
+
},
|
|
7992
|
+
{
|
|
7993
|
+
file: "src/tools/ask.ts",
|
|
7994
|
+
marker: "no user response, not user approval",
|
|
7995
|
+
anchor: ` const suffix = \`\${result.timedOut ? " (auto-selected after timeout)" : ""}\${noteSuffix}\`;`,
|
|
7996
|
+
patched: ` const suffix = \`\${result.timedOut ? " (auto-selected recommended option after timeout; no user response, not user approval)" : ""}\${noteSuffix}\`;`,
|
|
7997
|
+
},
|
|
7998
|
+
{
|
|
7999
|
+
file: "src/tools/ask.ts",
|
|
8000
|
+
marker: "No user response: auto-selected the recommended option after timeout",
|
|
8001
|
+
anchor: ` responseParts.push(result.timedOut ? \`\${selectedText} (auto-selected after timeout)\` : selectedText);`,
|
|
8002
|
+
patched: ` responseParts.push(
|
|
8003
|
+
result.timedOut
|
|
8004
|
+
? \`No user response: auto-selected the recommended option after timeout: \${result.selectedOptions.join(", ")} (not user approval)\`
|
|
8005
|
+
: selectedText,
|
|
8006
|
+
);`,
|
|
8007
|
+
},
|
|
8008
|
+
{
|
|
8009
|
+
file: "src/tools/ask.ts",
|
|
8010
|
+
marker: "Auto-selection is a positive opt-in",
|
|
8011
|
+
anchor: ` const timeout = planModeEnabled ? null : settingsTimeout;`,
|
|
8012
|
+
patched: ` // Auto-selection is a positive opt-in: every question must ask for it and carry a valid
|
|
8013
|
+
// recommendation. Legacy calls, approvals, and questions without one wait indefinitely.
|
|
8014
|
+
const autoSelectable = params.questions.every(
|
|
8015
|
+
q =>
|
|
8016
|
+
q.autoSelectRecommended === true &&
|
|
8017
|
+
typeof q.recommended === "number" &&
|
|
8018
|
+
Number.isInteger(q.recommended) &&
|
|
8019
|
+
q.recommended >= 0 &&
|
|
8020
|
+
q.recommended < q.options.length,
|
|
8021
|
+
);
|
|
8022
|
+
const timeout = planModeEnabled || !autoSelectable ? null : settingsTimeout;`,
|
|
8023
|
+
},
|
|
8024
|
+
{
|
|
8025
|
+
file: "src/prompts/tools/ask.md",
|
|
8026
|
+
marker: "`autoSelectRecommended: true`",
|
|
8027
|
+
anchor: `- \`recommended\` auto-adds " (Recommended)"; \`multi: true\` permits multiple selections.`,
|
|
8028
|
+
patched: `- \`recommended\` auto-adds " (Recommended)"; \`multi: true\` permits multiple selections.
|
|
8029
|
+
- \`autoSelectRecommended: true\` (requires a valid \`recommended\`): if nobody answers before \`ask.timeout\`, the recommended option is picked. Use ONLY for reversible implementation choices; NEVER for deployment, deletion, cost, account/permission, provider safety, or other high-impact confirmations. A timed-out pick is not user approval.`,
|
|
8030
|
+
},
|
|
8031
|
+
// 2026-09-30 사용자 요청(0.6.6): Main 의 Fast(OpenAI `priority`)만 새 child 에 상속한다.
|
|
8032
|
+
// `tier.subagent: inherit` 해석(및 agentServiceTierOverrides 의 inherit)이 부모 map 전체를 넘기면
|
|
8033
|
+
// Main 이 Anthropic Fast 일 때 Sonnet child 까지 `speed: "fast"` 로 과금된다. 상속 결과를
|
|
8034
|
+
// openai=priority 하나로 좁힌다. 명시 concrete tier·override·revive persist 경로는 그대로다.
|
|
8035
|
+
{
|
|
8036
|
+
file: "src/task/executor.ts",
|
|
8037
|
+
marker: "function parentSubagentServiceTiers(",
|
|
8038
|
+
anchor: `function inheritedSubagentServiceTiers(
|
|
8039
|
+
baseSettings: Settings,
|
|
8040
|
+
inheritedServiceTier?: ServiceTierByFamily | null,
|
|
8041
|
+
): ServiceTierByFamily {
|
|
8042
|
+
if (inheritedServiceTier === undefined) {`,
|
|
8043
|
+
patched: `function inheritedSubagentServiceTiers(
|
|
8044
|
+
baseSettings: Settings,
|
|
8045
|
+
inheritedServiceTier?: ServiceTierByFamily | null,
|
|
8046
|
+
): ServiceTierByFamily {
|
|
8047
|
+
// CUELO: inheritance carries only the parent's OpenAI Fast (\`priority\`). Anthropic/Google
|
|
8048
|
+
// tiers and other OpenAI tiers (ultrafast, flex, ...) stay with the parent; an explicit
|
|
8049
|
+
// concrete \`tier.subagent\` or per-agent override still applies on its own path.
|
|
8050
|
+
const parent = parentSubagentServiceTiers(baseSettings, inheritedServiceTier);
|
|
8051
|
+
return parent.openai === "priority" ? { openai: "priority" } : {};
|
|
8052
|
+
}
|
|
8053
|
+
|
|
8054
|
+
function parentSubagentServiceTiers(
|
|
8055
|
+
baseSettings: Settings,
|
|
8056
|
+
inheritedServiceTier?: ServiceTierByFamily | null,
|
|
8057
|
+
): ServiceTierByFamily {
|
|
8058
|
+
if (inheritedServiceTier === undefined) {`,
|
|
8059
|
+
},
|
|
7723
8060
|
];
|
|
7724
8061
|
// EDITS 문자열의 줄 끝을 LF로 통일한다. 이 파일의 작업 사본이 CRLF여도 core 파일(LF)과
|
|
7725
8062
|
// 비교·치환이 어긋나지 않는다. core 파일 자체의 줄 끝은 건드리지 않는다.
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|