cuelo 0.6.7 → 0.6.9
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 +11 -11
- 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/sessions/search/route.js +1 -1
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page.js +9 -9
- 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 +11 -11
- package/.next/server/chunks/5582.js +2 -2
- package/.next/server/chunks/6429.js +3 -3
- 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-e2710f3d550fa365.js → layout-0082ed8a34057701.js} +1 -1
- package/.next/static/chunks/app/page-ca1d4378b3ad4549.js +22 -0
- package/.next/trace +105 -105
- package/.next/trace-build +1 -1
- package/README.md +12 -7
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
- package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +55 -13
- package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +57 -12
- package/Tools/OMP_Global_Config/agent/extensions/lib/routing-ledger.ts +36 -7
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +571 -53
- package/bin/prepare-runtime.js +37 -6
- package/install.mjs +3 -1
- package/package.json +8 -7
- package/.next/static/chunks/app/page-6fbdefb38e5ea22f.js +0 -22
- /package/.next/static/{ZtmM1pqseDsjZJNTAi2i3 → 4swMbGViH4DcOvsX9-ZTC}/_buildManifest.js +0 -0
- /package/.next/static/{ZtmM1pqseDsjZJNTAi2i3 → 4swMbGViH4DcOvsX9-ZTC}/_ssgManifest.js +0 -0
|
@@ -1792,31 +1792,13 @@ function resolveSubagentRetryFallbackCandidates(
|
|
|
1792
1792
|
},
|
|
1793
1793
|
{
|
|
1794
1794
|
// BAI root: executor 가 요청 패턴의 provider 를 파싱하려면 parseModelString 이
|
|
1795
|
-
// 필요하다. 18.2.
|
|
1796
|
-
// 18.
|
|
1797
|
-
//
|
|
1795
|
+
// 필요하다. 18.2.5부터 그 함수는 pi-tui/overlays/model-selector 에 있으므로 그 import 한 줄에 얹는다.
|
|
1796
|
+
// 18.4.5는 이 줄과 model-resolver import 사이에 render-utils·edit import 를 끼워 넣었다. 그래서 앵커를
|
|
1797
|
+
// 이 한 줄로 좁힌다(18.4.4·18.4.5 모두 파일 안에 정확히 한 번, 18.4.4 적용본의 patched 도 그대로 성립).
|
|
1798
1798
|
file: "src/task/executor.ts",
|
|
1799
1799
|
marker: `import { formatModelSelectorValue, parseModelString } from "@oh-my-pi/pi-tui/overlays/model-selector";`,
|
|
1800
|
-
anchor: `import { formatModelSelectorValue } from "@oh-my-pi/pi-tui/overlays/model-selector"
|
|
1801
|
-
import {
|
|
1802
|
-
formatModelStringWithRouting,
|
|
1803
|
-
resolveAgentAdvisorSelection,
|
|
1804
|
-
resolveAgentPrewalkPattern,
|
|
1805
|
-
resolveConfiguredModelPatterns,
|
|
1806
|
-
resolveExplicitModelRole,
|
|
1807
|
-
resolveModelOverride,
|
|
1808
|
-
resolveModelOverrideWithAuthFallback,
|
|
1809
|
-
} from "../config/model-resolver";`,
|
|
1810
|
-
patched: `import { formatModelSelectorValue, parseModelString } from "@oh-my-pi/pi-tui/overlays/model-selector";
|
|
1811
|
-
import {
|
|
1812
|
-
formatModelStringWithRouting,
|
|
1813
|
-
resolveAgentAdvisorSelection,
|
|
1814
|
-
resolveAgentPrewalkPattern,
|
|
1815
|
-
resolveConfiguredModelPatterns,
|
|
1816
|
-
resolveExplicitModelRole,
|
|
1817
|
-
resolveModelOverride,
|
|
1818
|
-
resolveModelOverrideWithAuthFallback,
|
|
1819
|
-
} from "../config/model-resolver";`,
|
|
1800
|
+
anchor: `import { formatModelSelectorValue } from "@oh-my-pi/pi-tui/overlays/model-selector";`,
|
|
1801
|
+
patched: `import { formatModelSelectorValue, parseModelString } from "@oh-my-pi/pi-tui/overlays/model-selector";`,
|
|
1820
1802
|
},
|
|
1821
1803
|
{
|
|
1822
1804
|
// 미해결 subagent 모델 요청의 scoped discovery 복구 헬퍼. sdk.ts
|
|
@@ -3157,12 +3139,14 @@ import { resolveUsedFraction } from "../usage";`,
|
|
|
3157
3139
|
// ranking 을 계산하기 전에 끊어 sibling usage 조회조차 하지 않는다. 알려진 block 은
|
|
3158
3140
|
// provider 요청 전에 실패하고(allowBlocked:false), definitive 실패도 다른 행으로 다시
|
|
3159
3141
|
// 고르지 않는다(allowFallback:false). auth retry step (b)의 forceRefresh 는 같은 행만 다시 민다.
|
|
3142
|
+
// 18.4.5(#13889)는 shouldRank 앞에 spent-allowance 판정(rankDespitePin·sessionPreferredUsage)을 넣어
|
|
3143
|
+
// 옛 앵커 `...sessionPinIsExplicit));` 가 사라졌다. exact pin 은 늘 explicit(pin())이라 그 판정은
|
|
3144
|
+
// usage 를 조회하지 않고, 자동 pin 의 spent 계정 전환은 upstream 그대로 둔다. 앵커는 두 버전 모두
|
|
3145
|
+
// shouldRank 바로 뒤에 있는 "When ranking" 주석 한 줄이다(18.4.4 적용본의 patched 도 그대로 성립).
|
|
3160
3146
|
file: "../pi-ai/src/auth/select.ts",
|
|
3161
3147
|
marker: "// Exact character summons never enter the ranked sibling candidate pool.",
|
|
3162
|
-
anchor: `
|
|
3163
|
-
//
|
|
3164
|
-
patched: ` (policyReserveEnabled && !sessionPinIsExplicit));
|
|
3165
|
-
// Exact character summons never enter the ranked sibling candidate pool.
|
|
3148
|
+
anchor: ` // When ranking, seed the pinned credential first in the evaluation order so it wins genuine`,
|
|
3149
|
+
patched: ` // Exact character summons never enter the ranked sibling candidate pool.
|
|
3166
3150
|
// Known blocks fail before a provider request; a request-time usage error
|
|
3167
3151
|
// is handled by TurnRecovery without credential or model fallback.
|
|
3168
3152
|
if (sessionCredential?.type === "oauth" && sessionCredential.exactLabel) {
|
|
@@ -3220,11 +3204,13 @@ import { resolveUsedFraction } from "../usage";`,
|
|
|
3220
3204
|
// 대체한다(legacyPatched). 사용량 그림이 완전하고 건강한 후보만, upstream ranking 에서 이미 차지한
|
|
3221
3205
|
// 자리들 안에서만 재배열한다. unknown·partial·blocked·reserve·plan 부적격·5h hot 후보는 upstream 자리
|
|
3222
3206
|
// 그대로다. exact summon 은 이 앞에서 return 하고 warm/explicit pin·plan pin 재승격은 이 뒤에 온다.
|
|
3207
|
+
// 18.4.5(#13889)는 비ranking 후보 목록 끝을 `}));` 에서 삼항 map 으로 바꾸고 ranking 비교기에 spent
|
|
3208
|
+
// allowance 를 reserve 보다 앞에 넣었다. 재배열 대상은 한도 미도달·측정 완료(used<1) 후보뿐이라 spent
|
|
3209
|
+
// 후보는 upstream 자리 그대로다. 앵커는 두 버전 모두 한 번 있는 preflightFailures 선언 한 줄이다.
|
|
3223
3210
|
file: "../pi-ai/src/auth/select.ts",
|
|
3224
3211
|
marker: "// Prefer RIN (first Anthropic account) while it stays inside its daily slice of the weekly quota.",
|
|
3225
|
-
anchor: "\t\
|
|
3226
|
-
legacyPatched: `
|
|
3227
|
-
// Prefer the healthy Anthropic account whose weekly window resets first.
|
|
3212
|
+
anchor: "\t\tconst preflightFailures = new Set<OAuthCandidate>();",
|
|
3213
|
+
legacyPatched: ` // Prefer the healthy Anthropic account whose weekly window resets first.
|
|
3228
3214
|
// Only candidates with a complete, healthy usage picture move, and only among the slots they
|
|
3229
3215
|
// already hold in the upstream ranking: unknown, partial, blocked, reserve, plan-ineligible or
|
|
3230
3216
|
// 5h-hot candidates keep their place. Ties: larger remaining weekly quota, then upstream order.
|
|
@@ -3277,8 +3263,7 @@ import { resolveUsedFraction } from "../usage";`,
|
|
|
3277
3263
|
});
|
|
3278
3264
|
}
|
|
3279
3265
|
const preflightFailures = new Set<OAuthCandidate>();`,
|
|
3280
|
-
patched: `
|
|
3281
|
-
// Prefer RIN (first Anthropic account) while it stays inside its daily slice of the weekly quota.
|
|
3266
|
+
patched: ` // Prefer RIN (first Anthropic account) while it stays inside its daily slice of the weekly quota.
|
|
3282
3267
|
// Only candidates with a complete, healthy usage picture move, and only among the slots they
|
|
3283
3268
|
// already hold in the upstream ranking: unknown, partial, blocked, reserve, plan-ineligible or
|
|
3284
3269
|
// 5h-hot candidates keep their place. Daily slice = (elapsed whole days + 1) / window days.
|
|
@@ -3339,19 +3324,22 @@ import { resolveUsedFraction } from "../usage";`,
|
|
|
3339
3324
|
const preflightFailures = new Set<OAuthCandidate>();`,
|
|
3340
3325
|
},
|
|
3341
3326
|
{
|
|
3342
|
-
// exact OAuth 가 해석되지 않으면 login API 키·env 키로 조용히 넘어가지 않는다.
|
|
3327
|
+
// exact OAuth 가 해석되지 않으면 login API 키·18.4.5 config fallback 키(#13815)·env 키로 조용히 넘어가지 않는다.
|
|
3328
|
+
// 18.4.5는 resolveOAuth 성공 분기에 oauthIdentity 전달을 넣어 옛 한 덩어리 앵커가 사라졌다. 라벨은
|
|
3329
|
+
// resolveOAuth 전에 읽고(이 항목), 거절은 성공 분기 바로 뒤·login/fallback/env 조회 앞에 둔다(다음 항목).
|
|
3330
|
+
// 두 앵커 모두 18.4.4·18.4.5에 정확히 한 번 있고 18.4.4 적용본의 patched 도 그대로 성립한다.
|
|
3331
|
+
file: "../pi-ai/src/auth/cascade.ts",
|
|
3332
|
+
marker: "const exactOAuthLabel = this.#deps.affinity.exactLabel(provider, sessionId);",
|
|
3333
|
+
anchor: ` const oauthResolved = await this.#deps.selector.resolveOAuth(provider, sessionId, options);`,
|
|
3334
|
+
patched: ` const exactOAuthLabel = this.#deps.affinity.exactLabel(provider, sessionId);
|
|
3335
|
+
const oauthResolved = await this.#deps.selector.resolveOAuth(provider, sessionId, options);`,
|
|
3336
|
+
},
|
|
3337
|
+
{
|
|
3343
3338
|
file: "../pi-ai/src/auth/cascade.ts",
|
|
3344
3339
|
marker: "의 지정 OAuth 계정이 현재 사용할 수 없습니다.`);",
|
|
3345
|
-
anchor: `
|
|
3346
|
-
if (oauthResolved) {
|
|
3347
|
-
if (oauthResolved.credentialId !== undefined) onCredentialId?.(oauthResolved.credentialId);
|
|
3348
|
-
return oauthResolved.apiKey;
|
|
3340
|
+
anchor: ` return oauthResolved.apiKey;
|
|
3349
3341
|
}`,
|
|
3350
|
-
patched: `
|
|
3351
|
-
const oauthResolved = await this.#deps.selector.resolveOAuth(provider, sessionId, options);
|
|
3352
|
-
if (oauthResolved) {
|
|
3353
|
-
if (oauthResolved.credentialId !== undefined) onCredentialId?.(oauthResolved.credentialId);
|
|
3354
|
-
return oauthResolved.apiKey;
|
|
3342
|
+
patched: ` return oauthResolved.apiKey;
|
|
3355
3343
|
}
|
|
3356
3344
|
if (exactOAuthLabel) {
|
|
3357
3345
|
throw new Error(\`[CharacterSummonRuntime] \${exactOAuthLabel}의 지정 OAuth 계정이 현재 사용할 수 없습니다.\`);
|
|
@@ -4003,19 +3991,21 @@ export const cfgJudgmentProvider = register({
|
|
|
4003
3991
|
// 사용량 패널이 두 계정을 섞어 보인다(2026-09-28 실장애). 요청을 처리한 행 id 는 resolver 경로가
|
|
4004
3992
|
// 이미 알고 있으므로(stream.ts runAttempt) 응답 메타데이터로 실어 그 행에 귀속한다. 행을 모르는
|
|
4005
3993
|
// 호출자(정적 키 등)는 기존 세션 fallback 을 그대로 쓴다. 회귀: patches/core-usage-header-attribution-test.ts
|
|
3994
|
+
// 18.4.5는 attemptOptions 에 oauthIdentity 를 더해 그 줄이 버전마다 다르다. 그래서 바로 다음 줄(두 버전 공통,
|
|
3995
|
+
// 정확히 한 번) 앞에 끼운다. upstream 의 oauthIdentity 전달은 그대로 둔다.
|
|
4006
3996
|
{
|
|
4007
3997
|
file: "../pi-ai/src/stream.ts",
|
|
4008
3998
|
marker: "// Response headers carry this attempt's account quota.",
|
|
4009
|
-
anchor: ` const
|
|
4010
|
-
patched: `
|
|
4011
|
-
// Response headers carry this attempt's account quota. Name the row that
|
|
3999
|
+
anchor: ` const inner = streamSimpleRequest(model, context, attemptOptions);`,
|
|
4000
|
+
patched: ` // Response headers carry this attempt's account quota. Name the row that
|
|
4012
4001
|
// actually sent it so usage ingest never re-resolves the session's current
|
|
4013
4002
|
// account, which a mid-request pin switch may already have moved.
|
|
4014
4003
|
const onResponse = requestOptions?.onResponse;
|
|
4015
4004
|
if (credentialId !== undefined && onResponse) {
|
|
4016
4005
|
attemptOptions.onResponse = (response, responseModel, responseSignal) =>
|
|
4017
4006
|
onResponse({ ...response, metadata: { ...response.metadata, credentialId } }, responseModel, responseSignal);
|
|
4018
|
-
}
|
|
4007
|
+
}
|
|
4008
|
+
const inner = streamSimpleRequest(model, context, attemptOptions);`,
|
|
4019
4009
|
},
|
|
4020
4010
|
{
|
|
4021
4011
|
file: "src/session/session-stats.ts",
|
|
@@ -4505,8 +4495,11 @@ function resolveExactWeb6Candidate(session: ToolSession): CompletionCandidate[]
|
|
|
4505
4495
|
// TodoTracker.`,
|
|
4506
4496
|
},
|
|
4507
4497
|
{
|
|
4498
|
+
// 18.4.5(#3821 subagent live preview)는 #cancelObserverUiSyncTimer 끝에 #cancelSubagentPreviewTick() 을 더했다.
|
|
4499
|
+
// lifecycle 이 TodoTracker 를 바꾸지 않게 reconcile 경로만 걷어내고, upstream 의 preview tick 취소는 그대로 둔다
|
|
4500
|
+
// (alternate). 두 후보의 marker 는 각자의 cancel 함수 전체라 서로의 결과·순정본에 들어 있지 않다.
|
|
4508
4501
|
file: "src/modes/interactive-mode.ts",
|
|
4509
|
-
marker: "
|
|
4502
|
+
marker: "\t#cancelObserverUiSyncTimer(): void {\n\t\tif (this.#observerUiSyncTimer) {\n\t\t\tclearTimeout(this.#observerUiSyncTimer);\n\t\t\tthis.#observerUiSyncTimer = undefined;\n\t\t}\n\t}",
|
|
4510
4503
|
anchor: ` #scheduleObserverUiSync(kind: SessionObserverChangeKind): void {
|
|
4511
4504
|
if (kind !== "progress") {
|
|
4512
4505
|
this.#observerUiSyncNeedsTodoReconcile = true;
|
|
@@ -4563,6 +4556,68 @@ function resolveExactWeb6Candidate(session: ToolSession): CompletionCandidate[]
|
|
|
4563
4556
|
this.#observerUiSyncTimer = undefined;
|
|
4564
4557
|
}
|
|
4565
4558
|
}`,
|
|
4559
|
+
alternates: [{
|
|
4560
|
+
file: "src/modes/interactive-mode.ts",
|
|
4561
|
+
marker: "\t#cancelObserverUiSyncTimer(): void {\n\t\tif (this.#observerUiSyncTimer) {\n\t\t\tclearTimeout(this.#observerUiSyncTimer);\n\t\t\tthis.#observerUiSyncTimer = undefined;\n\t\t}\n\t\tthis.#cancelSubagentPreviewTick();\n\t}",
|
|
4562
|
+
anchor: ` #scheduleObserverUiSync(kind: SessionObserverChangeKind): void {
|
|
4563
|
+
if (kind !== "progress") {
|
|
4564
|
+
this.#observerUiSyncNeedsTodoReconcile = true;
|
|
4565
|
+
}
|
|
4566
|
+
if (this.#observerUiSyncTimer) return;
|
|
4567
|
+
this.#observerUiSyncTimer = setTimeout(() => {
|
|
4568
|
+
this.#observerUiSyncTimer = undefined;
|
|
4569
|
+
this.#flushObserverUiSync();
|
|
4570
|
+
}, SUBAGENT_OBSERVER_UI_COALESCE_MS);
|
|
4571
|
+
this.#observerUiSyncTimer.unref?.();
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4574
|
+
#flushObserverUiSync(): void {
|
|
4575
|
+
this.syncRunningSubagentBadge({ requestRender: false });
|
|
4576
|
+
if (this.#observerUiSyncNeedsTodoReconcile) {
|
|
4577
|
+
this.#observerUiSyncNeedsTodoReconcile = false;
|
|
4578
|
+
this.#reconcileTodosWithSubagents();
|
|
4579
|
+
}
|
|
4580
|
+
this.#syncTodoHudState(this.#todoPhasesOwner ?? this.session);
|
|
4581
|
+
this.#renderTodoList();
|
|
4582
|
+
this.#renderSubagentList();
|
|
4583
|
+
this.ui.requestRender();
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
#cancelObserverUiSyncTimer(): void {
|
|
4587
|
+
if (this.#observerUiSyncTimer) {
|
|
4588
|
+
clearTimeout(this.#observerUiSyncTimer);
|
|
4589
|
+
this.#observerUiSyncTimer = undefined;
|
|
4590
|
+
}
|
|
4591
|
+
this.#observerUiSyncNeedsTodoReconcile = false;
|
|
4592
|
+
this.#cancelSubagentPreviewTick();
|
|
4593
|
+
}`,
|
|
4594
|
+
patched: ` #scheduleObserverUiSync(_kind: SessionObserverChangeKind): void {
|
|
4595
|
+
// Lifecycle changes only refresh observer and TODO presentation. They do
|
|
4596
|
+
// not constitute Main acceptance and therefore never change TodoTracker.
|
|
4597
|
+
if (this.#observerUiSyncTimer) return;
|
|
4598
|
+
this.#observerUiSyncTimer = setTimeout(() => {
|
|
4599
|
+
this.#observerUiSyncTimer = undefined;
|
|
4600
|
+
this.#flushObserverUiSync();
|
|
4601
|
+
}, SUBAGENT_OBSERVER_UI_COALESCE_MS);
|
|
4602
|
+
this.#observerUiSyncTimer.unref?.();
|
|
4603
|
+
}
|
|
4604
|
+
|
|
4605
|
+
#flushObserverUiSync(): void {
|
|
4606
|
+
this.syncRunningSubagentBadge({ requestRender: false });
|
|
4607
|
+
this.#syncTodoHudState(this.#todoPhasesOwner ?? this.session);
|
|
4608
|
+
this.#renderTodoList();
|
|
4609
|
+
this.#renderSubagentList();
|
|
4610
|
+
this.ui.requestRender();
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
#cancelObserverUiSyncTimer(): void {
|
|
4614
|
+
if (this.#observerUiSyncTimer) {
|
|
4615
|
+
clearTimeout(this.#observerUiSyncTimer);
|
|
4616
|
+
this.#observerUiSyncTimer = undefined;
|
|
4617
|
+
}
|
|
4618
|
+
this.#cancelSubagentPreviewTick();
|
|
4619
|
+
}`,
|
|
4620
|
+
}],
|
|
4566
4621
|
},
|
|
4567
4622
|
{
|
|
4568
4623
|
// 2026-09-21 실사용: 사용자는 `mnemopi.autoRetain: false` 로 자동 턴 저장을 껐는데도
|
|
@@ -8076,9 +8131,12 @@ function parentSubagentServiceTiers(
|
|
|
8076
8131
|
// Opus 5.5도 Fable 5.1·Sonnet 5.5처럼 thinking 서명을 앞선 system·tools·messages에 묶는다고 한다. 18.4.x
|
|
8077
8132
|
// catalog는 Opus 5.5에 thinking-prefix-binding을 빠뜨려, prefix가 바뀐 요청이 drop_block 없이 나가고 신규
|
|
8078
8133
|
// 계정에서는 400 뒤 재시도로만 복구된다. 내장 모델은 구워진 models.json 값을 그대로 쓰고, 규칙으로 새로 만드는
|
|
8079
|
-
// 모델(discovery 등)은 rules.json을 쓰므로 두 곳에 prefixBinding과 binding controls(beta
|
|
8080
|
-
//
|
|
8081
|
-
//
|
|
8134
|
+
// 모델(discovery 등)은 rules.json을 쓰므로 두 곳에 prefixBinding과 binding controls(beta)를 같이 채운다.
|
|
8135
|
+
// binding controls의 provider 범위는 그 버전 upstream Sonnet 5.5 규칙과 같게 둔다: 18.4.4는 Claude API·
|
|
8136
|
+
// Cloudflare·Vertex, 18.4.5는 Vertex가 `thinking.adaptive.block_binding: Extra inputs are not permitted`(400)로
|
|
8137
|
+
// 거절해 Claude API·Cloudflare만(classes/anthropic.kdl:128). wire의 block_binding은 prefixBinding과 binding
|
|
8138
|
+
// controls가 둘 다 참일 때만 나간다(anthropic.ts prefixMismatchBehavior). models.yml은 thinking override의
|
|
8139
|
+
// prefixBinding을 스키마에서 조용히 버리고 공개 설치에는 없으므로 설정으로 켜지 않는다.
|
|
8082
8140
|
{
|
|
8083
8141
|
file: "../pi-catalog/src/models.json",
|
|
8084
8142
|
marker: '"supportsDisplay":true,"prefixBinding":true},"identity":{"class":"anthropic","family":"opus","revision":"5.5.0"},"requiresGlyphTokenization":true,"tokenizer":"claude-v5","supportsComputerUse":false,"compat":{"officialEndpoint":true,"signingEndpoint":true,"supportsContextManagement":true,"supportsServerCompaction":true,"firstPartyProvider":true,"supportsOutputEffort":true,"disableStrictTools":false,"disableAdaptiveThinking":false,"allowAnthropicHeaderOverrides":false,"supportsEagerToolInputStreaming":true,"supportsLongCacheRetention":true,"supportsMidConversationSystem":true,"supportsTurnScopedSystem":true,"supportsMidConversationToolChanges":true,"supportsPerMessageEffort":true,"supportsThinkingBindingControls":true',
|
|
@@ -8086,12 +8144,30 @@ function parentSubagentServiceTiers(
|
|
|
8086
8144
|
patched: '"claude-opus-5-5":{"id":"claude-opus-5-5","name":"Claude Opus 5.5","api":"anthropic-messages","provider":"anthropic","baseUrl":"https://api.anthropic.com","reasoning":true,"input":["text","image"],"cost":{"input":4,"output":20,"cacheRead":0.2,"cacheWrite":5},"contextWindow":1000000,"maxTokens":128000,"int":57.6,"tps":95.2,"thinking":{"mode":"anthropic-adaptive","efforts":["low","medium","high","xhigh","max"],"supportsDisplay":true,"prefixBinding":true},"identity":{"class":"anthropic","family":"opus","revision":"5.5.0"},"requiresGlyphTokenization":true,"tokenizer":"claude-v5","supportsComputerUse":false,"compat":{"officialEndpoint":true,"signingEndpoint":true,"supportsContextManagement":true,"supportsServerCompaction":true,"firstPartyProvider":true,"supportsOutputEffort":true,"disableStrictTools":false,"disableAdaptiveThinking":false,"allowAnthropicHeaderOverrides":false,"supportsEagerToolInputStreaming":true,"supportsLongCacheRetention":true,"supportsMidConversationSystem":true,"supportsTurnScopedSystem":true,"supportsMidConversationToolChanges":true,"supportsPerMessageEffort":true,"supportsThinkingBindingControls":true',
|
|
8087
8145
|
},
|
|
8088
8146
|
{
|
|
8147
|
+
// 모든 provider의 Opus 5.5 규칙 계보에 prefixBinding(Sonnet 5.5 kdl:69와 같은 모양). 18.4.4·18.4.5 공통 앵커.
|
|
8148
|
+
// 18.4.4 적용본은 이 patched 바로 뒤에 binding controls 규칙이 붙어 있어 marker가 그대로 성립한다.
|
|
8089
8149
|
file: "../pi-catalog/src/compat/rules.json",
|
|
8090
|
-
marker: '{"source":"
|
|
8150
|
+
marker: '{"source":"classes/anthropic.kdl:60","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}}',
|
|
8091
8151
|
anchor: '{"source":"classes/anthropic.kdl:60","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false}}',
|
|
8092
|
-
patched: '{"source":"classes/anthropic.kdl:60","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}}
|
|
8152
|
+
patched: '{"source":"classes/anthropic.kdl:60","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}}',
|
|
8153
|
+
},
|
|
8154
|
+
{
|
|
8155
|
+
// binding controls 규칙은 그 버전의 upstream Sonnet 5.5 binding 규칙 바로 뒤에 같은 provider 범위로 둔다.
|
|
8156
|
+
// 18.4.5: kdl:128(anthropic·cloudflare). alternate 18.4.4: kdl:106(anthropic·cloudflare·vertex).
|
|
8157
|
+
// 18.4.4 라이브 적용본은 옛 위치(kdl:60 뒤)에 같은 규칙이 있어 alternate marker로 applied이다. 그 적용본의
|
|
8158
|
+
// --revert는 patched가 연속으로 없으므로 엔진의 백업 경로(~/.omp/core-patch-backup)를 쓴다.
|
|
8159
|
+
file: "../pi-catalog/src/compat/rules.json",
|
|
8160
|
+
marker: '{"source":"cuelo:opus-5.5-thinking-binding","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
|
|
8161
|
+
anchor: '{"source":"classes/anthropic.kdl:128","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true}}',
|
|
8162
|
+
patched: '{"source":"classes/anthropic.kdl:128","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true}},{"source":"cuelo:opus-5.5-thinking-binding","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
|
|
8163
|
+
alternates: [{
|
|
8164
|
+
file: "../pi-catalog/src/compat/rules.json",
|
|
8165
|
+
marker: '{"source":"cuelo:opus-5.5-thinking-binding","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway","google-vertex"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
|
|
8166
|
+
anchor: '{"source":"classes/anthropic.kdl:106","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway","google-vertex"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsMidConversationSystem":true,"supportsMidConversationToolChanges":true,"supportsTurnScopedSystem":true,"supportsPerMessageEffort":true,"supportsThinkingBindingControls":true}}',
|
|
8167
|
+
patched: '{"source":"classes/anthropic.kdl:106","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway","google-vertex"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsMidConversationSystem":true,"supportsMidConversationToolChanges":true,"supportsTurnScopedSystem":true,"supportsPerMessageEffort":true,"supportsThinkingBindingControls":true}},{"source":"cuelo:opus-5.5-thinking-binding","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway","google-vertex"],"family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsThinkingBindingControls":true}}',
|
|
8168
|
+
}],
|
|
8093
8169
|
},
|
|
8094
|
-
// 위
|
|
8170
|
+
// 위 세 항목과 짝: modelOverrides의 thinking은 buildModel이 규칙으로 채운 thinking을 통째로 덮어써서, override가
|
|
8095
8171
|
// 있는 Opus 5.5(그리고 upstream의 Sonnet 5.5·Fable 5.1)에서 prefixBinding이 사라진다. override 스키마는 이 필드를
|
|
8096
8172
|
// 받지 않으므로, override가 정하지 않았으면 모델 계보가 정한 값을 보존한다.
|
|
8097
8173
|
{
|
|
@@ -8100,6 +8176,448 @@ function parentSubagentServiceTiers(
|
|
|
8100
8176
|
anchor: "\tif (patch.thinking !== undefined && built.thinking !== undefined) {\n\t\t// Config-authored capability metadata owns the explicit surface; build\n\t\t// first so non-reasoning and wire-disabled models still suppress it.\n\t\tbuilt.thinking = patch.thinking;\n\t}\n",
|
|
8101
8177
|
patched: "\tif (patch.thinking !== undefined && built.thinking !== undefined) {\n\t\t// Config-authored capability metadata owns the explicit surface; build\n\t\t// first so non-reasoning and wire-disabled models still suppress it.\n\t\t// CUELO: prefix binding is model lineage, not config surface.\n\t\tbuilt.thinking =\n\t\t\tpatch.thinking.prefixBinding === undefined && built.thinking.prefixBinding === true\n\t\t\t\t? { ...patch.thinking, prefixBinding: true }\n\t\t\t\t: patch.thinking;\n\t}\n",
|
|
8102
8178
|
},
|
|
8179
|
+
// 「Auto, 최대 X」(CUELO 0.6.8): ModelControls의 session 상한은 생성 때 한 번만 정해지고 setter가 없다.
|
|
8180
|
+
// 상한을 바꿀 수 있게 하되, 생성 때 받은 상한(task.maxEffort spawn 상한)은 넓힐 수 없는 하한으로 남긴다 —
|
|
8181
|
+
// 유효 상한은 둘 중 낮은 쪽이다. auto는 상한 전 결과를 기억해 두어 상한을 올리면 그 결과로 돌아간다.
|
|
8182
|
+
// 검증: patches/core-thinking-ceiling-test.ts(미패치 FAIL → 패치 PASS).
|
|
8183
|
+
{
|
|
8184
|
+
file: "src/session/model-controls.ts",
|
|
8185
|
+
marker: "readonly #spawnThinkingLevelCeiling: Effort | undefined;",
|
|
8186
|
+
anchor: ` /** Hard per-session effort ceiling (e.g. a task spawn's \`task.maxEffort\` cap); recovery paths re-clamp to it. */
|
|
8187
|
+
readonly #thinkingLevelCeiling: Effort | undefined;`,
|
|
8188
|
+
patched: ` /** Hard per-session effort ceiling (e.g. a task spawn's \`task.maxEffort\` cap); recovery paths re-clamp to it. */
|
|
8189
|
+
#thinkingLevelCeiling: Effort | undefined;
|
|
8190
|
+
/** CUELO: construction-time (task spawn) ceiling; setThinkingLevelCeiling never widens past it. */
|
|
8191
|
+
readonly #spawnThinkingLevelCeiling: Effort | undefined;
|
|
8192
|
+
/** CUELO: auto's last resolution before the ceiling, so a raised ceiling can restore it. */
|
|
8193
|
+
#autoUncappedLevel: Effort | undefined;`,
|
|
8194
|
+
},
|
|
8195
|
+
{
|
|
8196
|
+
file: "src/session/model-controls.ts",
|
|
8197
|
+
marker: "this.#spawnThinkingLevelCeiling = options.thinkingLevelCeiling;",
|
|
8198
|
+
anchor: "\t\tthis.#thinkingLevelCeiling = options.thinkingLevelCeiling;\n",
|
|
8199
|
+
patched: "\t\tthis.#thinkingLevelCeiling = options.thinkingLevelCeiling;\n\t\tthis.#spawnThinkingLevelCeiling = options.thinkingLevelCeiling;\n",
|
|
8200
|
+
},
|
|
8201
|
+
{
|
|
8202
|
+
file: "src/session/model-controls.ts",
|
|
8203
|
+
marker: "setThinkingLevelCeiling(ceiling: Effort | undefined, record: boolean = true): void {",
|
|
8204
|
+
anchor: ` /** Hard per-session effort ceiling every thinking-level change is clamped to. */
|
|
8205
|
+
get thinkingLevelCeiling(): Effort | undefined {
|
|
8206
|
+
return this.#thinkingLevelCeiling;
|
|
8207
|
+
}
|
|
8208
|
+
`,
|
|
8209
|
+
patched: ` /** Hard per-session effort ceiling every thinking-level change is clamped to. */
|
|
8210
|
+
get thinkingLevelCeiling(): Effort | undefined {
|
|
8211
|
+
return this.#thinkingLevelCeiling;
|
|
8212
|
+
}
|
|
8213
|
+
|
|
8214
|
+
/**
|
|
8215
|
+
* CUELO: replace the user effort ceiling ("auto, but at most X"); \`undefined\` clears it.
|
|
8216
|
+
* The construction-time (task spawn) ceiling stays a hard bound: the effective ceiling is the
|
|
8217
|
+
* lower of the two, so this path never widens a spawn cap. The current level is re-clamped;
|
|
8218
|
+
* while \`auto\` is configured the resolved level is re-clamped from its pre-ceiling
|
|
8219
|
+
* resolution and \`auto\` stays configured. \`record: false\` (startup restore) applies the
|
|
8220
|
+
* ceiling without appending a transcript entry.
|
|
8221
|
+
*/
|
|
8222
|
+
setThinkingLevelCeiling(ceiling: Effort | undefined, record: boolean = true): void {
|
|
8223
|
+
const spawn = this.#spawnThinkingLevelCeiling;
|
|
8224
|
+
this.#thinkingLevelCeiling =
|
|
8225
|
+
ceiling !== undefined &&
|
|
8226
|
+
(spawn === undefined || THINKING_EFFORTS.indexOf(ceiling) < THINKING_EFFORTS.indexOf(spawn))
|
|
8227
|
+
? ceiling
|
|
8228
|
+
: spawn;
|
|
8229
|
+
const model = this.#model;
|
|
8230
|
+
const previous = this.#thinkingLevel;
|
|
8231
|
+
let next: ThinkingLevel | undefined;
|
|
8232
|
+
if (this.#autoThinking) {
|
|
8233
|
+
const base =
|
|
8234
|
+
this.#autoResolvedLevel === undefined
|
|
8235
|
+
? resolveProvisionalAutoLevel(model)
|
|
8236
|
+
: (this.#autoUncappedLevel ?? this.#autoResolvedLevel);
|
|
8237
|
+
const capped = clampThinkingLevelToCeiling(model, base, this.#thinkingLevelCeiling);
|
|
8238
|
+
if (capped === undefined) return;
|
|
8239
|
+
if (this.#autoResolvedLevel !== undefined) this.#autoResolvedLevel = capped;
|
|
8240
|
+
next = capped;
|
|
8241
|
+
} else {
|
|
8242
|
+
next = resolveThinkingLevelForModel(
|
|
8243
|
+
model,
|
|
8244
|
+
clampThinkingLevelToCeiling(model, previous, this.#thinkingLevelCeiling),
|
|
8245
|
+
);
|
|
8246
|
+
}
|
|
8247
|
+
if (next === previous) return;
|
|
8248
|
+
this.#thinkingLevel = next;
|
|
8249
|
+
this.#applyThinkingLevelToAgent(next);
|
|
8250
|
+
if (record) {
|
|
8251
|
+
this.#host.clearInheritedProviderPromptCacheKey();
|
|
8252
|
+
this.#host.sessionManager.appendThinkingLevelChange(next, this.configuredThinkingLevel());
|
|
8253
|
+
}
|
|
8254
|
+
this.#host.emit(
|
|
8255
|
+
this.#autoThinking
|
|
8256
|
+
? { type: "thinking_level_changed", thinkingLevel: next, configured: AUTO_THINKING }
|
|
8257
|
+
: { type: "thinking_level_changed", thinkingLevel: next },
|
|
8258
|
+
);
|
|
8259
|
+
}
|
|
8260
|
+
`,
|
|
8261
|
+
},
|
|
8262
|
+
{
|
|
8263
|
+
// floor 패치가 바꾼 clamp 블록 바로 뒤의 원본 줄에 붙인다(그 항목의 marker·앵커와 겹치지 않는다).
|
|
8264
|
+
file: "src/session/model-controls.ts",
|
|
8265
|
+
marker: "// CUELO: remember the pre-ceiling resolution so raising the ceiling can restore it.",
|
|
8266
|
+
anchor: ` if (effort === undefined) return;
|
|
8267
|
+
const shouldPersistResolution = this.#thinkingLevel !== effort;
|
|
8268
|
+
this.#autoResolvedLevel = effort;`,
|
|
8269
|
+
patched: ` if (effort === undefined) return;
|
|
8270
|
+
// CUELO: remember the pre-ceiling resolution so raising the ceiling can restore it.
|
|
8271
|
+
this.#autoUncappedLevel = raiseToAutoThinkingFloor(
|
|
8272
|
+
model,
|
|
8273
|
+
resolved ??
|
|
8274
|
+
(this.#autoResolvedLevel === undefined ? undefined : (this.#autoUncappedLevel ?? this.#autoResolvedLevel)) ??
|
|
8275
|
+
resolveProvisionalAutoLevel(model),
|
|
8276
|
+
this.#host.settings,
|
|
8277
|
+
);
|
|
8278
|
+
const shouldPersistResolution = this.#thinkingLevel !== effort;
|
|
8279
|
+
this.#autoResolvedLevel = effort;`,
|
|
8280
|
+
},
|
|
8281
|
+
{
|
|
8282
|
+
file: "src/session/agent-session.ts",
|
|
8283
|
+
marker: "\tsetThinkingLevelCeiling(ceiling: Effort | undefined, record: boolean = true): void {\n\t\tthis.#models.setThinkingLevelCeiling(ceiling, record);",
|
|
8284
|
+
anchor: ` /** Selects the session thinking level and optionally persists it as the default. */
|
|
8285
|
+
setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist: boolean = false): void {
|
|
8286
|
+
this.#models.setThinkingLevel(level, persist);
|
|
8287
|
+
}
|
|
8288
|
+
`,
|
|
8289
|
+
patched: ` /** Selects the session thinking level and optionally persists it as the default. */
|
|
8290
|
+
setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist: boolean = false): void {
|
|
8291
|
+
this.#models.setThinkingLevel(level, persist);
|
|
8292
|
+
}
|
|
8293
|
+
|
|
8294
|
+
/** CUELO: replaces the user effort ceiling ("auto, but at most X"); a spawn ceiling still bounds it. */
|
|
8295
|
+
setThinkingLevelCeiling(ceiling: Effort | undefined, record: boolean = true): void {
|
|
8296
|
+
this.#models.setThinkingLevelCeiling(ceiling, record);
|
|
8297
|
+
}
|
|
8298
|
+
|
|
8299
|
+
/** CUELO: effective effort ceiling, the lower of the spawn ceiling and the user ceiling. */
|
|
8300
|
+
get thinkingLevelCeiling(): Effort | undefined {
|
|
8301
|
+
return this.#models.thinkingLevelCeiling;
|
|
8302
|
+
}
|
|
8303
|
+
`,
|
|
8304
|
+
},
|
|
8305
|
+
// HTML export(세션 안 `/export`, CLI `--export`, CUELO 웹 `app/api/sessions/[id]/export`)는 세션 폴더의 `*.jsonl`을
|
|
8306
|
+
// 전부 subagent 기록으로 싣는다. advisor는 같은 폴더에 `__advisor.jsonl`·`__advisor.<slug>.jsonl`(subagent advisor는
|
|
8307
|
+
// `<SubId>/__advisor.jsonl`)로 자기 프롬프트·검토를 남기므로, 내보낸 HTML에 advisor 내부 기록이 그대로 들어갔다
|
|
8308
|
+
// (18.4.5; upstream #13908은 같은 경계에서 isAdvisorTranscriptName으로 거르지만 미머지). 수집 경계에서 advisor
|
|
8309
|
+
// 파일만 건너뛴다. 진짜 subagent·중첩 subagent·부모 대화(부모에게 전달된 advisor 메시지 포함)는 그대로이고,
|
|
8310
|
+
// 파일은 읽기만 한다. 웹 route는 번들 `dist/cli.js --export`를 먼저 실행하므로 번들의 같은 함수에도 같은 조건을
|
|
8311
|
+
// 넣는다(min 식별자까지 정확히 일치하는 18.4.5 앵커, 버전이 바뀌면 조용히 어긋나지 않고 anchor-lost).
|
|
8312
|
+
// 회귀: patches/core-export-advisor-test.ts(실제 SDK exportFromFile과 bun dist/cli.js --export의 HTML).
|
|
8313
|
+
{
|
|
8314
|
+
file: "src/export/html/index.ts",
|
|
8315
|
+
marker: 'import { isAdvisorTranscriptName } from "../../advisor/transcript-recorder";',
|
|
8316
|
+
anchor: 'import type { SessionEntry, SessionHeader } from "../../session/session-entries";',
|
|
8317
|
+
patched: 'import { isAdvisorTranscriptName } from "../../advisor/transcript-recorder";\nimport type { SessionEntry, SessionHeader } from "../../session/session-entries";',
|
|
8318
|
+
},
|
|
8319
|
+
{
|
|
8320
|
+
file: "src/export/html/index.ts",
|
|
8321
|
+
marker: ' // Advisor transcripts share this directory but are the advisor\'s own prompts and reviews, not subagents.\n',
|
|
8322
|
+
anchor: ' if (!name.endsWith(".jsonl") || name.includes(".bak")) continue;\n',
|
|
8323
|
+
patched: ' if (!name.endsWith(".jsonl") || name.includes(".bak")) continue;\n // Advisor transcripts share this directory but are the advisor\'s own prompts and reviews, not subagents.\n if (isAdvisorTranscriptName(name)) continue;\n',
|
|
8324
|
+
},
|
|
8325
|
+
{
|
|
8326
|
+
file: "dist/cli.js",
|
|
8327
|
+
marker: 'if(!o.endsWith(".jsonl")||o.includes(".bak")||o==="__advisor.jsonl"||o.startsWith("__advisor.")&&o.endsWith(".jsonl"))continue;let r=o.slice(0,-6),',
|
|
8328
|
+
anchor: 'if(!o.endsWith(".jsonl")||o.includes(".bak"))continue;let r=o.slice(0,-6),',
|
|
8329
|
+
patched: 'if(!o.endsWith(".jsonl")||o.includes(".bak")||o==="__advisor.jsonl"||o.startsWith("__advisor.")&&o.endsWith(".jsonl"))continue;let r=o.slice(0,-6),',
|
|
8330
|
+
},
|
|
8331
|
+
// 18.4.5 는 /ratchet 을 새로 넣으면서 `src/ratchet/prelude.ts`(코드)와 `prelude.js`(eval 텍스트 자산)를 같은 stem 으로
|
|
8332
|
+
// 두고 sdk.ts 가 `./ratchet/prelude` 로 import 한다. Bun 1.4.2 는 node_modules 안의 확장자 없는 import 를 .js 부터
|
|
8333
|
+
// 해석하므로, npm 으로 설치해 src 를 import 하는 소비자(CUELO 웹의 SDK)에서는 sdk.ts 로드가
|
|
8334
|
+
// `Export named 'createRatchetPrelude' not found in module ...ratchet\prelude.js` 로 실패한다(저장소 경로·번들은 정상).
|
|
8335
|
+
// 이 import 만 .ts 로 명시한다. 동작은 upstream 이 의도한 그대로다. 18.4.5 전용이다: 18.4.4 에는 ratchet 이 없으므로
|
|
8336
|
+
// 18.4.4 설치본에서는 이 항목이 anchor-lost 로 적용 전에 실패한다(다른 판을 조용히 건너뛰는 no-op 후보를 두지 않는다).
|
|
8337
|
+
{
|
|
8338
|
+
file: "src/sdk.ts",
|
|
8339
|
+
marker: 'import { createRatchetPrelude } from "./ratchet/prelude.ts";',
|
|
8340
|
+
anchor: 'import { createRatchetPrelude } from "./ratchet/prelude";',
|
|
8341
|
+
patched: 'import { createRatchetPrelude } from "./ratchet/prelude.ts";',
|
|
8342
|
+
},
|
|
8343
|
+
// P55. genuine 사용자 steer 가 아직 보이는 출력(text·toolCall)이 없는 진행 중 모델 요청을 끝까지 기다리지 않게 한다.
|
|
8344
|
+
// 2026-10-01 사용자 관측: Astra(xhigh) 4분짜리 추론 요청 중 넣은 steer 가 그 요청이 끝날 때까지(98.8s·169.6s)
|
|
8345
|
+
// 큐에 머물렀고, Opus 는 바로 개입되는 것처럼 보였다. Agent.steer 는 큐에만 넣고 모델 스트림은 끊지 않으며
|
|
8346
|
+
// (도구 실행만 interrupt), Codex native turn lane 은 `response.steer` 를 거절(P48 sticky)하고 SSE·Anthropic 은
|
|
8347
|
+
// live 주입이 없다. 그래서 "보이는 출력이 아직 없음 + live 채널 미부착이거나 claim reject·defer" 일 때만
|
|
8348
|
+
// 요청 전용 AbortSignal(promptToolAbortController 와 같이 provider signal 에만 병합)로 그 provider 호출을 끊고,
|
|
8349
|
+
// partial 은 message_end 없이 버린다(context·persist·replay 에 남지 않음, 웹은 다음 message_start 가 live
|
|
8350
|
+
// bubble 을 교체). 같은 run·같은 turn 안에서 steering 을 경계 주입해 새 요청을 보낸다. session/agent/loop abort·
|
|
8351
|
+
// goal pause 는 건드리지 않는다. 이미 text·toolCall 이 보였으면 기존대로 boundary 처리(2026-09-20 "보이던 답 끊김"
|
|
8352
|
+
// 계약, core-steer-stream-test.ts). native accept·내부 IRC/advisory/custom/agent steer·follow-up 은 대상이 아니다.
|
|
8353
|
+
// 회귀: patches/core-steer-stream-test.ts(실제 Agent 루프 + Codex/SSE wire fixture).
|
|
8354
|
+
{
|
|
8355
|
+
file: "../pi-agent-core/src/agent-loop.ts",
|
|
8356
|
+
marker: "class SteeringRestartInterruption extends Error {",
|
|
8357
|
+
anchor: "class HarmonyLeakInterruption extends Error {",
|
|
8358
|
+
patched: `/** CUELO P55: race token for a request-only restart requested by genuine user steering. */
|
|
8359
|
+
const STEER_RESTART = Symbol("steer-restart");
|
|
8360
|
+
|
|
8361
|
+
/**
|
|
8362
|
+
* CUELO P55: marks the last listener snapshot of a partial discarded for a steering restart.
|
|
8363
|
+
* Agent#runLoop treats a marked partial as absent, so an exception before the next message
|
|
8364
|
+
* start never commits it. Symbol keys stay out of JSON, structured clones and provider payloads.
|
|
8365
|
+
*/
|
|
8366
|
+
export const STEER_DISCARDED_PARTIAL = Symbol("pi-agent-core.steer-discarded-partial");
|
|
8367
|
+
|
|
8368
|
+
/**
|
|
8369
|
+
* CUELO P55: genuine user steering ended the in-flight request before it produced visible
|
|
8370
|
+
* output. The partial was discarded (never committed, persisted or replayed); the loop delivers
|
|
8371
|
+
* the steering at this boundary and re-requests in the same run.
|
|
8372
|
+
*/
|
|
8373
|
+
class SteeringRestartInterruption extends Error {
|
|
8374
|
+
constructor() {
|
|
8375
|
+
super("User steering restarted the in-flight model request");
|
|
8376
|
+
this.name = "SteeringRestartInterruption";
|
|
8377
|
+
}
|
|
8378
|
+
}
|
|
8379
|
+
|
|
8380
|
+
/** CUELO P55: same predicate as \`hasNewUserSteering\`: user-typed, not agent-attributed, not synthetic. */
|
|
8381
|
+
function isGenuineUserSteer(message: AgentMessage): boolean {
|
|
8382
|
+
return (
|
|
8383
|
+
message.role === "user" &&
|
|
8384
|
+
("attribution" in message ? message.attribution !== "agent" : true) &&
|
|
8385
|
+
!("synthetic" in message && message.synthetic === true)
|
|
8386
|
+
);
|
|
8387
|
+
}
|
|
8388
|
+
|
|
8389
|
+
/** CUELO P55: text or a tool call already reached listeners; such a partial is never discarded for steering. */
|
|
8390
|
+
function hasVisibleAssistantOutput(message: AssistantMessage | null): boolean {
|
|
8391
|
+
return (
|
|
8392
|
+
message?.content.some(
|
|
8393
|
+
block => block.type === "toolCall" || (block.type === "text" && block.text.trim().length > 0),
|
|
8394
|
+
) === true
|
|
8395
|
+
);
|
|
8396
|
+
}
|
|
8397
|
+
|
|
8398
|
+
class HarmonyLeakInterruption extends Error {`,
|
|
8399
|
+
},
|
|
8400
|
+
{
|
|
8401
|
+
file: "../pi-agent-core/src/agent-loop.ts",
|
|
8402
|
+
marker: "if (steerRestartController) providerAbortSignals.push(steerRestartController.signal);",
|
|
8403
|
+
anchor: ` const providerAbortSignals: AbortSignal[] = [];
|
|
8404
|
+
if (requestSignal) providerAbortSignals.push(requestSignal);
|
|
8405
|
+
if (promptToolAbortController) providerAbortSignals.push(promptToolAbortController.signal);`,
|
|
8406
|
+
patched: ` // CUELO P55: request-only cancel for genuine user steering. Provider signal ONLY (like
|
|
8407
|
+
// promptToolAbortController), so it never trips the loop's external-abort handling.
|
|
8408
|
+
const steerRestartController = config.onUserSteeringQueued ? new AbortController() : undefined;
|
|
8409
|
+
const providerAbortSignals: AbortSignal[] = [];
|
|
8410
|
+
if (requestSignal) providerAbortSignals.push(requestSignal);
|
|
8411
|
+
if (promptToolAbortController) providerAbortSignals.push(promptToolAbortController.signal);
|
|
8412
|
+
if (steerRestartController) providerAbortSignals.push(steerRestartController.signal);`,
|
|
8413
|
+
},
|
|
8414
|
+
{
|
|
8415
|
+
file: "../pi-agent-core/src/agent-loop.ts",
|
|
8416
|
+
marker: "const discardForSteerRestart = async (): Promise<never> => {",
|
|
8417
|
+
anchor: ` detachAbortListener = () => requestSignal.removeEventListener("abort", onAbort);
|
|
8418
|
+
}
|
|
8419
|
+
|
|
8420
|
+
try {
|
|
8421
|
+
while (true) {
|
|
8422
|
+
let next: IteratorResult<AssistantMessageEvent>;
|
|
8423
|
+
if (abortRacePromise) {
|
|
8424
|
+
const result = await Promise.race([responseIterator.next(), abortRacePromise]);
|
|
8425
|
+
if (result === ABORTED) {
|
|
8426
|
+
return await finishAbortedStream();
|
|
8427
|
+
}
|
|
8428
|
+
next = result;
|
|
8429
|
+
} else {
|
|
8430
|
+
next = await responseIterator.next();
|
|
8431
|
+
}
|
|
8432
|
+
if (next.done) {
|
|
8433
|
+
providerStreamSettled = true;
|
|
8434
|
+
break;
|
|
8435
|
+
}
|
|
8436
|
+
|
|
8437
|
+
const event = next.value;
|
|
8438
|
+
if (event.type === "done" || event.type === "error") {`,
|
|
8439
|
+
patched: ` detachAbortListener = () => requestSignal.removeEventListener("abort", onAbort);
|
|
8440
|
+
}
|
|
8441
|
+
|
|
8442
|
+
// CUELO P55: genuine user steering ends this request only while nothing visible streamed
|
|
8443
|
+
// (no text, no tool call) and the provider cannot take it live: no pump attached to the
|
|
8444
|
+
// channel (SSE, non-steering providers, before response.created) or the pump's claim was
|
|
8445
|
+
// rejected/deferred (Codex native lane, P48). An accepted live steer keeps the request.
|
|
8446
|
+
const steerChannel = providerCall.liveSteering;
|
|
8447
|
+
let steerRestartClosed = false;
|
|
8448
|
+
const requestSteerRestart = (): void => {
|
|
8449
|
+
if (!steerRestartController || steerRestartClosed || steerRestartController.signal.aborted) return;
|
|
8450
|
+
if (hasVisibleAssistantOutput(partialMessage)) return;
|
|
8451
|
+
if (steerChannel?.attached && steerChannel.deferred.length === 0) return;
|
|
8452
|
+
steerRestartController.abort();
|
|
8453
|
+
};
|
|
8454
|
+
const detachUserSteeringRestart = steerRestartController
|
|
8455
|
+
? config.onUserSteeringQueued?.(requestSteerRestart)
|
|
8456
|
+
: undefined;
|
|
8457
|
+
if (steerRestartController && steerChannel) {
|
|
8458
|
+
steerChannel.onDeferred = () => {
|
|
8459
|
+
if (steerChannel.deferred.some(isGenuineUserSteer) || hasNewUserSteering?.() === true) {
|
|
8460
|
+
requestSteerRestart();
|
|
8461
|
+
}
|
|
8462
|
+
};
|
|
8463
|
+
}
|
|
8464
|
+
const detachSteerRestart = (): void => {
|
|
8465
|
+
detachUserSteeringRestart?.();
|
|
8466
|
+
if (steerChannel) steerChannel.onDeferred = undefined;
|
|
8467
|
+
};
|
|
8468
|
+
let steerRestartRace: Promise<typeof STEER_RESTART> | undefined;
|
|
8469
|
+
if (steerRestartController) {
|
|
8470
|
+
const { promise, resolve } = Promise.withResolvers<typeof STEER_RESTART>();
|
|
8471
|
+
steerRestartController.signal.addEventListener("abort", () => resolve(STEER_RESTART), { once: true });
|
|
8472
|
+
steerRestartRace = promise;
|
|
8473
|
+
}
|
|
8474
|
+
// Drop the partial without message_end: listeners never receive it as a message, so it is
|
|
8475
|
+
// not committed, persisted or replayed (the next message_start replaces the live bubble).
|
|
8476
|
+
const discardForSteerRestart = async (): Promise<never> => {
|
|
8477
|
+
steerRestartClosed = true;
|
|
8478
|
+
try {
|
|
8479
|
+
const cleanup = responseIterator.return?.();
|
|
8480
|
+
if (cleanup) void cleanup.catch(() => {});
|
|
8481
|
+
} catch {
|
|
8482
|
+
// Provider cancellation failures cannot resurrect the discarded partial.
|
|
8483
|
+
}
|
|
8484
|
+
await speculationCoordinator?.discardAll("user steering restarted the request", "discarded");
|
|
8485
|
+
speculationSettled = true;
|
|
8486
|
+
if (addedPartial) {
|
|
8487
|
+
context.messages.pop();
|
|
8488
|
+
addedPartial = false;
|
|
8489
|
+
}
|
|
8490
|
+
// Listeners last saw \`turnSnapshot\`; mark it so Agent#runLoop never resurrects it.
|
|
8491
|
+
if (turnSnapshot) Reflect.set(turnSnapshot, STEER_DISCARDED_PARTIAL, true);
|
|
8492
|
+
throw new SteeringRestartInterruption();
|
|
8493
|
+
};
|
|
8494
|
+
if (hasNewUserSteering?.() === true) requestSteerRestart();
|
|
8495
|
+
|
|
8496
|
+
try {
|
|
8497
|
+
while (true) {
|
|
8498
|
+
let next: IteratorResult<AssistantMessageEvent>;
|
|
8499
|
+
if (abortRacePromise || steerRestartRace) {
|
|
8500
|
+
const pendingNext = responseIterator.next();
|
|
8501
|
+
const result = await (steerRestartRace
|
|
8502
|
+
? abortRacePromise
|
|
8503
|
+
? Promise.race([pendingNext, abortRacePromise, steerRestartRace])
|
|
8504
|
+
: Promise.race([pendingNext, steerRestartRace])
|
|
8505
|
+
: Promise.race([pendingNext, abortRacePromise as Promise<typeof ABORTED>]));
|
|
8506
|
+
if (result === ABORTED) {
|
|
8507
|
+
return await finishAbortedStream();
|
|
8508
|
+
}
|
|
8509
|
+
// A chunk that raced the restart is dropped unseen: nothing visible had streamed.
|
|
8510
|
+
if (result === STEER_RESTART || steerRestartController?.signal.aborted) {
|
|
8511
|
+
if (requestSignal?.aborted) return await finishAbortedStream();
|
|
8512
|
+
return await discardForSteerRestart();
|
|
8513
|
+
}
|
|
8514
|
+
next = result;
|
|
8515
|
+
} else {
|
|
8516
|
+
next = await responseIterator.next();
|
|
8517
|
+
}
|
|
8518
|
+
if (next.done) {
|
|
8519
|
+
providerStreamSettled = true;
|
|
8520
|
+
break;
|
|
8521
|
+
}
|
|
8522
|
+
|
|
8523
|
+
const event = next.value;
|
|
8524
|
+
if (event.type === "done" || event.type === "error") {
|
|
8525
|
+
// The request finished on its own; steering now waits for the normal boundary.
|
|
8526
|
+
steerRestartClosed = true;`,
|
|
8527
|
+
},
|
|
8528
|
+
{
|
|
8529
|
+
file: "../pi-agent-core/src/agent-loop.ts",
|
|
8530
|
+
marker: " detachSteerRestart();\n",
|
|
8531
|
+
anchor: ` cancelArgStreams();
|
|
8532
|
+
if (!providerStreamSettled) {`,
|
|
8533
|
+
patched: ` cancelArgStreams();
|
|
8534
|
+
detachSteerRestart();
|
|
8535
|
+
if (!providerStreamSettled) {`,
|
|
8536
|
+
},
|
|
8537
|
+
{
|
|
8538
|
+
file: "../pi-agent-core/src/agent-loop.ts",
|
|
8539
|
+
marker: "if (steerRestartController?.signal.aborted && !requestSignal?.aborted) await discardForSteerRestart();",
|
|
8540
|
+
anchor: ` try {
|
|
8541
|
+
let trailing = await response.result();`,
|
|
8542
|
+
patched: ` // CUELO P55: the iterator ended after a restart was requested; discard instead of finalizing.
|
|
8543
|
+
if (steerRestartController?.signal.aborted && !requestSignal?.aborted) await discardForSteerRestart();
|
|
8544
|
+
steerRestartClosed = true;
|
|
8545
|
+
try {
|
|
8546
|
+
let trailing = await response.result();`,
|
|
8547
|
+
},
|
|
8548
|
+
{
|
|
8549
|
+
file: "../pi-agent-core/src/agent-loop.ts",
|
|
8550
|
+
marker: "if (err instanceof SteeringRestartInterruption) {",
|
|
8551
|
+
anchor: " if (!(err instanceof HarmonyLeakInterruption)) throw err;",
|
|
8552
|
+
patched: ` if (err instanceof SteeringRestartInterruption) {
|
|
8553
|
+
// CUELO P55: the discarded partial never reached context or listeners. Deliver the
|
|
8554
|
+
// steering (plus anything the live channel took for the aborted response) at this
|
|
8555
|
+
// boundary and re-request inside the same turn and run; run/goal state is untouched.
|
|
8556
|
+
const channel = preparedProviderCall.liveSteering;
|
|
8557
|
+
const taken = channel ? [...channel.accepted.splice(0), ...channel.deferred.splice(0)] : [];
|
|
8558
|
+
const steering = [...taken, ...((await config.getSteeringMessages?.(signal)) || [])];
|
|
8559
|
+
for (const queued of steering) {
|
|
8560
|
+
currentContext.messages.push(queued);
|
|
8561
|
+
newMessages.push(queued);
|
|
8562
|
+
(queued as CommittableAsideMessage)[ASIDE_MESSAGE_COMMIT]?.();
|
|
8563
|
+
}
|
|
8564
|
+
emitInputMessages(stream, steering);
|
|
8565
|
+
hasMoreToolCalls = true;
|
|
8566
|
+
continue;
|
|
8567
|
+
}
|
|
8568
|
+
if (!(err instanceof HarmonyLeakInterruption)) throw err;`,
|
|
8569
|
+
},
|
|
8570
|
+
{
|
|
8571
|
+
file: "../pi-agent-core/src/live-steering.ts",
|
|
8572
|
+
marker: " attached = false;\n",
|
|
8573
|
+
anchor: " readonly #queue: LiveSteeringQueue;\n",
|
|
8574
|
+
patched: ` /** CUELO P55: a provider pump pulls from this channel (set on its first wait). */
|
|
8575
|
+
attached = false;
|
|
8576
|
+
/** CUELO P55: notified after input is deferred to the boundary (claim rejected or not convertible). */
|
|
8577
|
+
onDeferred: (() => void) | undefined;
|
|
8578
|
+
readonly #queue: LiveSteeringQueue;
|
|
8579
|
+
`,
|
|
8580
|
+
},
|
|
8581
|
+
{
|
|
8582
|
+
file: "../pi-agent-core/src/live-steering.ts",
|
|
8583
|
+
marker: " this.attached = true;\n",
|
|
8584
|
+
anchor: " wait(signal: AbortSignal): Promise<void> {\n",
|
|
8585
|
+
patched: " wait(signal: AbortSignal): Promise<void> {\n this.attached = true;\n",
|
|
8586
|
+
},
|
|
8587
|
+
{
|
|
8588
|
+
file: "../pi-agent-core/src/live-steering.ts",
|
|
8589
|
+
marker: " this.deferred.push(...messages);\n this.onDeferred?.();\n return undefined;",
|
|
8590
|
+
anchor: " this.deferred.push(...messages);\n return undefined;",
|
|
8591
|
+
patched: " this.deferred.push(...messages);\n this.onDeferred?.();\n return undefined;",
|
|
8592
|
+
},
|
|
8593
|
+
{
|
|
8594
|
+
file: "../pi-agent-core/src/live-steering.ts",
|
|
8595
|
+
marker: " this.deferred.push(...messages);\n this.onDeferred?.();\n },",
|
|
8596
|
+
anchor: " this.deferred.push(...messages);\n },",
|
|
8597
|
+
patched: " this.deferred.push(...messages);\n this.onDeferred?.();\n },",
|
|
8598
|
+
},
|
|
8599
|
+
// P55 r2: 버린 partial 은 Agent#runLoop 의 지역 `partial` 에 마지막 snapshot 으로 남는다(message_end 만 비운다).
|
|
8600
|
+
// 경계의 steering dequeue 가 던지거나(예: AgentSession usage preflight 의 "Usage preflight cancelled") 비어 있고
|
|
8601
|
+
// 다음 요청이 start 전에 실패하면, 아래 두 소비 경계가 그것을 error 메시지로 commit 해 signature 째 persist 됐다.
|
|
8602
|
+
// 표시된 snapshot 을 없는 것으로 취급한다. 원래 오류 문구와 stopReason 은 그대로 남는다.
|
|
8603
|
+
{
|
|
8604
|
+
file: "../pi-agent-core/src/agent.ts",
|
|
8605
|
+
marker: " STEER_DISCARDED_PARTIAL,\n",
|
|
8606
|
+
anchor: " steeringQueueState,\n unpairedToolCallTail,\n} from \"./agent-loop\";",
|
|
8607
|
+
patched: " STEER_DISCARDED_PARTIAL,\n steeringQueueState,\n unpairedToolCallTail,\n} from \"./agent-loop\";",
|
|
8608
|
+
},
|
|
8609
|
+
{
|
|
8610
|
+
file: "../pi-agent-core/src/agent.ts",
|
|
8611
|
+
marker: " if (partial && Reflect.get(partial, STEER_DISCARDED_PARTIAL) === true) partial = null;\n",
|
|
8612
|
+
anchor: " // Handle any remaining partial message\n",
|
|
8613
|
+
patched: " // Handle any remaining partial message\n // CUELO P55: a partial discarded for a steering restart never becomes a message.\n if (partial && Reflect.get(partial, STEER_DISCARDED_PARTIAL) === true) partial = null;\n",
|
|
8614
|
+
},
|
|
8615
|
+
{
|
|
8616
|
+
file: "../pi-agent-core/src/agent.ts",
|
|
8617
|
+
marker: "Reflect.get(partial, STEER_DISCARDED_PARTIAL) !== true ? partial : undefined;",
|
|
8618
|
+
anchor: " const assistantPartial = partial?.role === \"assistant\" ? partial : undefined;",
|
|
8619
|
+
patched: " // CUELO P55: a partial discarded for a steering restart is not this error's content.\n const assistantPartial =\n partial?.role === \"assistant\" && Reflect.get(partial, STEER_DISCARDED_PARTIAL) !== true ? partial : undefined;",
|
|
8620
|
+
},
|
|
8103
8621
|
];
|
|
8104
8622
|
// EDITS 문자열의 줄 끝을 LF로 통일한다. 이 파일의 작업 사본이 CRLF여도 core 파일(LF)과
|
|
8105
8623
|
// 비교·치환이 어긋나지 않는다. core 파일 자체의 줄 끝은 건드리지 않는다.
|