cuelo 0.7.1 → 0.8.0

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 (186) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -8
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +27 -3
  5. package/.next/react-loadable-manifest.json +13 -3
  6. package/.next/required-server-files.js +2 -2
  7. package/.next/required-server-files.json +2 -2
  8. package/.next/routes-manifest.json +6 -0
  9. package/.next/server/app/_global-error/page.js +3 -3
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/server/app/_not-found/page.js +2 -2
  20. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/server/app/_not-found.html +1 -1
  22. package/.next/server/app/_not-found.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  24. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  26. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/api/agent/[id]/account/route.js +1 -1
  30. package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
  31. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  32. package/.next/server/app/api/agent/[id]/route.js +1 -1
  33. package/.next/server/app/api/agent/new/route.js +1 -1
  34. package/.next/server/app/api/agent/running/route.js +1 -1
  35. package/.next/server/app/api/attachments/route.js +2 -2
  36. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  37. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  38. package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
  39. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  40. package/.next/server/app/api/auth/providers/route.js +1 -1
  41. package/.next/server/app/api/cwd/browse/route.js +1 -1
  42. package/.next/server/app/api/cwd/validate/route.js +1 -1
  43. package/.next/server/app/api/default-cwd/route.js +1 -1
  44. package/.next/server/app/api/display-settings/route.js +1 -1
  45. package/.next/server/app/api/document-text/route.js +2 -2
  46. package/.next/server/app/api/experiments/route.js +1 -1
  47. package/.next/server/app/api/file-index/route.js +1 -1
  48. package/.next/server/app/api/files/[...path]/route.js +1 -1
  49. package/.next/server/app/api/git/diff/route.js +1 -1
  50. package/.next/server/app/api/git/status/route.js +2 -2
  51. package/.next/server/app/api/gpt6/handles/route.js +1 -1
  52. package/.next/server/app/api/gpt6/reply/route.js +1 -1
  53. package/.next/server/app/api/gpt6/token/route.js +1 -1
  54. package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
  55. package/.next/server/app/api/home/route.js +1 -1
  56. package/.next/server/app/api/live/call/[callId]/route.js +1 -1
  57. package/.next/server/app/api/live/character-voice/route.js +1 -1
  58. package/.next/server/app/api/live/events/route.js +2 -2
  59. package/.next/server/app/api/live/offer/route.js +1 -1
  60. package/.next/server/app/api/live/voices/route.js +1 -1
  61. package/.next/server/app/api/lounge/events/route.js +2 -2
  62. package/.next/server/app/api/lounge/route.js +1 -1
  63. package/.next/server/app/api/mcp/route.js +2 -2
  64. package/.next/server/app/api/model-roles/route.js +1 -1
  65. package/.next/server/app/api/models/route.js +1 -1
  66. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  67. package/.next/server/app/api/models-config/discover/route.js +1 -1
  68. package/.next/server/app/api/models-config/route.js +3 -3
  69. package/.next/server/app/api/models-config/test/route.js +1 -1
  70. package/.next/server/app/api/plugins/route.js +1 -1
  71. package/.next/server/app/api/project-trust/route.js +1 -1
  72. package/.next/server/app/api/projects/route.js +2 -2
  73. package/.next/server/app/api/runs/[runId]/route.js +1 -1
  74. package/.next/server/app/api/runs/route.js +1 -1
  75. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  76. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  77. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
  78. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  79. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  80. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  81. package/.next/server/app/api/sessions/route.js +1 -1
  82. package/.next/server/app/api/sessions/search/route.js +1 -1
  83. package/.next/server/app/api/settings/route.js +1 -1
  84. package/.next/server/app/api/sidecars/[service]/[...path]/route.js +1 -1
  85. package/.next/server/app/api/skills/check/route.js +1 -1
  86. package/.next/server/app/api/skills/install/route.js +2 -2
  87. package/.next/server/app/api/skills/route.js +2 -2
  88. package/.next/server/app/api/skills/search/route.js +1 -1
  89. package/.next/server/app/api/skills/update/route.js +1 -1
  90. package/.next/server/app/api/theme/route.js +1 -1
  91. package/.next/server/app/api/tool-image/route.js +1 -1
  92. package/.next/server/app/api/update-maintenance/route.js +2 -2
  93. package/.next/server/app/api/updates/route.js +1 -1
  94. package/.next/server/app/api/web-access/recovery/route.js +1 -1
  95. package/.next/server/app/api/web-access/route.js +1 -1
  96. package/.next/server/app/api/web6/consults/route.js +1 -1
  97. package/.next/server/app/api/worktrees/route.js +1 -1
  98. package/.next/server/app/favicon.ico/route.js +1 -1
  99. package/.next/server/app/index.html +1 -1
  100. package/.next/server/app/index.rsc +7 -6
  101. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -4
  102. package/.next/server/app/index.segments/_full.segment.rsc +7 -6
  103. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  104. package/.next/server/app/index.segments/_index.segment.rsc +3 -3
  105. package/.next/server/app/index.segments/_tree.segment.rsc +4 -3
  106. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  107. package/.next/server/app/office-preview/page.js +2 -0
  108. package/.next/server/app/office-preview/page.js.nft.json +1 -0
  109. package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -0
  110. package/.next/server/app/office-preview.html +1 -0
  111. package/.next/server/app/office-preview.meta +16 -0
  112. package/.next/server/app/office-preview.rsc +20 -0
  113. package/.next/server/app/office-preview.segments/_full.segment.rsc +20 -0
  114. package/.next/server/app/office-preview.segments/_head.segment.rsc +6 -0
  115. package/.next/server/app/office-preview.segments/_index.segment.rsc +7 -0
  116. package/.next/server/app/office-preview.segments/_tree.segment.rsc +4 -0
  117. package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +7 -0
  118. package/.next/server/app/office-preview.segments/office-preview.segment.rsc +5 -0
  119. package/.next/server/app/page.js +19 -19
  120. package/.next/server/app/page.js.nft.json +1 -1
  121. package/.next/server/app/page_client-reference-manifest.js +1 -1
  122. package/.next/server/app/recover/page.js +2 -2
  123. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/recover.html +1 -1
  125. package/.next/server/app/recover.rsc +3 -3
  126. package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
  127. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  128. package/.next/server/app/recover.segments/_index.segment.rsc +3 -3
  129. package/.next/server/app/recover.segments/_tree.segment.rsc +2 -2
  130. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  131. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  132. package/.next/server/app-paths-manifest.json +9 -8
  133. package/.next/server/chunks/205.js +3 -2
  134. package/.next/server/chunks/424.js +1 -1
  135. package/.next/server/chunks/5582.js +2 -2
  136. package/.next/server/chunks/58.js +353 -0
  137. package/.next/server/chunks/6429.js +1 -1
  138. package/.next/server/chunks/7362.js +1 -0
  139. package/.next/server/middleware-build-manifest.js +1 -1
  140. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  141. package/.next/server/pages/404.html +1 -1
  142. package/.next/server/pages/500.html +1 -1
  143. package/.next/server/server-reference-manifest.json +1 -1
  144. package/.next/static/chunks/1937.8900983e70e8e9dc.js +1 -0
  145. package/.next/static/chunks/{3308.34ea31e24088c85f.js → 3308.8b94b38809d1b8b6.js} +1 -1
  146. package/.next/static/chunks/4857.cb0735ea310a39f0.js +1 -0
  147. package/.next/static/chunks/52733464.85a1551044fef357.js +1 -0
  148. package/.next/static/chunks/6110-681afd060dde5270.js +1 -0
  149. package/.next/static/chunks/9304.1f940d490ea5c972.js +1 -0
  150. package/.next/static/chunks/app/{layout-ca001f1e875e854c.js → layout-6aa3580989aedade.js} +1 -1
  151. package/.next/static/chunks/app/office-preview/page-2151040877dfb7cd.js +1 -0
  152. package/.next/static/chunks/app/page-f6f024bab16a7232.js +22 -0
  153. package/.next/static/chunks/b536a0f1.f7e390612229faa9.js +342 -0
  154. package/.next/static/chunks/bd904a5c.b74776e0b23d0607.js +1 -0
  155. package/.next/static/chunks/f5ef5e1c.14b6095722ff4f0b.js +12 -0
  156. package/.next/static/chunks/{framework-32a7466d5cae4a81.js → framework-e405bc515d75023a.js} +1 -1
  157. package/.next/static/chunks/webpack-93d544f82f48c3fd.js +1 -0
  158. package/.next/static/css/67b6e19482963224.css +1 -0
  159. package/.next/static/css/829f44afcc3302e9.css +1 -0
  160. package/.next/static/css/abd3d00cca9457f7.css +1 -0
  161. package/.next/static/vaxcaBF-y6afdFHIrgeuN/_buildManifest.js +1 -0
  162. package/.next/trace +106 -105
  163. package/.next/trace-build +1 -1
  164. package/.next/types/app/office-preview/page.ts +87 -0
  165. package/.next/types/routes.d.ts +2 -1
  166. package/.next/types/validator.ts +9 -0
  167. package/README.md +10 -4
  168. package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
  169. package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +2 -2
  170. package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +22 -11
  171. package/Tools/OMP_Global_Config/agent/extensions/korean-reply-guard.ts +174 -0
  172. package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +43 -16
  173. package/Tools/OMP_Global_Config/agent/rules/verdict.md +3 -0
  174. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +45 -5
  175. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +315 -40
  176. package/install.mjs +6 -2
  177. package/package.json +10 -7
  178. package/public/office3d/ASSETS.md +8 -0
  179. package/.next/static/chunks/2619.9aac8983f30c7c8a.js +0 -1
  180. package/.next/static/chunks/52733464.3870f99cc446f9ff.js +0 -1
  181. package/.next/static/chunks/app/page-ce1990eb55a57ad6.js +0 -22
  182. package/.next/static/chunks/webpack-13c58b226bf07c7e.js +0 -1
  183. package/.next/static/css/bb6b220f400250d8.css +0 -1
  184. package/.next/static/css/d0ec5531aef136d6.css +0 -1
  185. package/.next/static/vl7SKOdL2Zksd2mJ3tjOQ/_buildManifest.js +0 -1
  186. /package/.next/static/{vl7SKOdL2Zksd2mJ3tjOQ → vaxcaBF-y6afdFHIrgeuN}/_ssgManifest.js +0 -0
@@ -53,6 +53,17 @@ function resolveTarget() {
53
53
  return found ?? seen[seen.length - 1];
54
54
  }
55
55
 
56
+ /**
57
+ * #310(Opus 5.5 Claude API 구운 models.json 행)의 18.4.6 후보. 18.4.10 후보는 이 문자열에서 만든다(EDITS 의
58
+ * pi-catalog models.json 항목 주석). 18.4.6 라이브 적용본이 그대로 applied 여야 하므로 바꾸지 않는다.
59
+ */
60
+ const OPUS55_ROW_1846 = {
61
+ file: "../pi-catalog/src/models.json",
62
+ 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',
63
+ anchor: '"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},"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":false',
64
+ 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',
65
+ };
66
+
56
67
  /** 각 항목: 원본 앵커를 찾아 patched 로 바꾼다. marker 가 있으면 이미 적용된 것으로 본다. */
57
68
  const EDITS = [
58
69
  {
@@ -1587,15 +1598,29 @@ function resolveSubagentRetryFallbackCandidates(
1587
1598
  {
1588
1599
  // 승인 후보로 돌았어도 세션의 첫 model_change 는 요청 selector 를 함께 남겨야
1589
1600
  // 한다(코어가 대체한 셀렉터만 남기면 요청과 실제가 구분되지 않는다).
1601
+ // 18.4.10(#14040)은 옵션 빌더 함수를 `sessionSpec: SubagentSessionSpec = { options: { … } }`로 바꿔 한 단계 더
1602
+ // 들여쓴다. 같은 options 객체가 spawn·revive 모두에 쓰이는 점은 그대로다. alternate는 18.4.6 형태다. 두 후보의
1603
+ // marker는 줄 앞 개행과 들여쓰기까지 넣어 서로의 결과에 들어 있지 않게 했다.
1590
1604
  file: "src/task/executor.ts",
1591
- marker: "modelPatternRequested: modelPatterns.length > 0",
1592
- anchor: ` modelPatternAuthFallback:
1605
+ marker: "\n\t\t\t\t\tmodelPatternRequested: modelPatterns.length > 0",
1606
+ anchor: ` modelPatternAuthFallback:
1607
+ model || modelOverride === undefined ? undefined : options.parentActiveModelPattern,`,
1608
+ patched: ` modelPatternAuthFallback:
1609
+ model || modelOverride === undefined ? undefined : options.parentActiveModelPattern,
1610
+ modelPatternAuthFallbackUsed:
1611
+ authFallbackUsed || approvedModelSelection?.substituted === true || undefined,
1612
+ modelPatternRequested: modelPatterns.length > 0 ? modelPatterns.join(", ") : undefined,`,
1613
+ alternates: [{
1614
+ file: "src/task/executor.ts",
1615
+ marker: "\n\t\t\t\tmodelPatternRequested: modelPatterns.length > 0",
1616
+ anchor: ` modelPatternAuthFallback:
1593
1617
  model || modelOverride === undefined ? undefined : options.parentActiveModelPattern,`,
1594
- patched: ` modelPatternAuthFallback:
1618
+ patched: ` modelPatternAuthFallback:
1595
1619
  model || modelOverride === undefined ? undefined : options.parentActiveModelPattern,
1596
1620
  modelPatternAuthFallbackUsed:
1597
1621
  authFallbackUsed || approvedModelSelection?.substituted === true || undefined,
1598
1622
  modelPatternRequested: modelPatterns.length > 0 ? modelPatterns.join(", ") : undefined,`,
1623
+ }],
1599
1624
  },
1600
1625
  {
1601
1626
  // 대체된 모델로 시작한 세션을 transcript 에서 구분할 수 있게 남긴다. 요청 selector
@@ -2996,6 +3021,79 @@ import { isUnexpectedSocketCloseMessage } from "@oh-my-pi/pi-utils/fetch-retry";
2996
3021
  : {}),
2997
3022
  };`,
2998
3023
  },
3024
+ // 18.4.9(#14001)는 같은 계정 재기록의 persisted row 쓰기를 건너뛴다(#persistedSticky: type·credentialId·explicit·60초).
3025
+ // 이 비교에 exactLabel이 없으면 같은 계정에서 summon label만 바뀐 pin(일반 명시 pin → exact summon, 또는 그 반대)이
3026
+ // DB 행에 반영되지 않아, 다른 프로세스·재시작이 옛 정체성으로 복원한다(RIN exact pin이 사라져 sibling 회전 가능).
3027
+ // 아래 세 항목이 그 비교·기록·복원에 exactLabel을 넣는다. 검증: core-character-affinity-test.ts [7].
3028
+ // 18.4.6 은 record 마다 행을 다시 쓰므로(dedupe 없음) 할 일이 없다: alternate는 그 판의 매번-쓰기 줄 no-op이다.
3029
+ {
3030
+ file: "../pi-ai/src/auth/affinity.ts",
3031
+ marker: "a label-only change must rewrite the row",
3032
+ anchor: "\texplicit: boolean;\n\tlastUsedAtMs: number;\n};\n",
3033
+ patched: "\texplicit: boolean;\n\tlastUsedAtMs: number;\n\t/** CUELO: exact summon label stored in the row; a label-only change must rewrite the row. */\n\texactLabel: string | undefined;\n};\n",
3034
+ alternates: [{
3035
+ file: "../pi-ai/src/auth/affinity.ts",
3036
+ marker: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3037
+ anchor: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3038
+ patched: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3039
+ }],
3040
+ },
3041
+ {
3042
+ file: "../pi-ai/src/auth/affinity.ts",
3043
+ marker: "\t\t\tpersisted.exactLabel === keptExactLabel &&\n",
3044
+ anchor: ` persisted.explicit === isExplicit &&
3045
+ Math.abs(nowMs - persisted.lastUsedAtMs) < SESSION_STICKY_PERSIST_INTERVAL_MS
3046
+ ) {
3047
+ return;
3048
+ }
3049
+ try {
3050
+ this.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);
3051
+ this.#persistedSticky.set(cacheKey, {
3052
+ type,
3053
+ credentialId,
3054
+ explicit: isExplicit,
3055
+ lastUsedAtMs: nowMs,
3056
+ });`,
3057
+ patched: ` persisted.explicit === isExplicit &&
3058
+ persisted.exactLabel === keptExactLabel &&
3059
+ Math.abs(nowMs - persisted.lastUsedAtMs) < SESSION_STICKY_PERSIST_INTERVAL_MS
3060
+ ) {
3061
+ return;
3062
+ }
3063
+ try {
3064
+ this.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);
3065
+ this.#persistedSticky.set(cacheKey, {
3066
+ type,
3067
+ credentialId,
3068
+ explicit: isExplicit,
3069
+ lastUsedAtMs: nowMs,
3070
+ exactLabel: keptExactLabel,
3071
+ });`,
3072
+ alternates: [{
3073
+ file: "../pi-ai/src/auth/affinity.ts",
3074
+ marker: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3075
+ anchor: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3076
+ patched: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3077
+ }],
3078
+ },
3079
+ {
3080
+ // 복원한 행을 dedupe 기준으로 삼을 때도 그 행의 label을 같이 기억한다(바로 위 sessionVal은 위 항목이 trim한 값).
3081
+ file: "../pi-ai/src/auth/affinity.ts",
3082
+ marker: "\t\t\t\t\t\texactLabel: sessionVal.exactLabel,\n",
3083
+ anchor: ` explicit: val.explicit === true,
3084
+ lastUsedAtMs: val.lastUsedAtMs,
3085
+ });`,
3086
+ patched: ` explicit: val.explicit === true,
3087
+ lastUsedAtMs: val.lastUsedAtMs,
3088
+ exactLabel: sessionVal.exactLabel,
3089
+ });`,
3090
+ alternates: [{
3091
+ file: "../pi-ai/src/auth/affinity.ts",
3092
+ marker: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3093
+ anchor: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3094
+ patched: "\t\t\t\t// Expires in 30 days\n\t\t\t\tconst expiresAtSec = Math.floor(nowMs / 1000) + 30 * 24 * 60 * 60;\n\t\t\t\tthis.#store.setCache(cacheKey, JSON.stringify(sessionCredential), expiresAtSec);\n",
3095
+ }],
3096
+ },
2999
3097
  {
3000
3098
  file: "../pi-ai/src/auth/affinity.ts",
3001
3099
  marker: "exactLabel(provider: string, sessionId: string | undefined): string | undefined {",
@@ -3198,24 +3296,27 @@ import { resolveUsedFraction } from "../usage";`,
3198
3296
  patched: "import {\n\torderUsageRankedCandidates,\n\tPRIMARY_WINDOW_HOT_FRACTION,\n\tplanPriority,\n",
3199
3297
  },
3200
3298
  {
3201
- // 2026-09-29 사용자 정책: Anthropic 새/cold 재선택은 RIN(저장 순서 0)을 먼저 쓰고, RIN 이 주간 한도의
3202
- // 하루 구간(사용량 패널 paceOf 와 같은 기준: 지난 날 수 + 1 일치)을 넘으면 MIO 로 넘긴다. 날이 바뀌어
3203
- // 허용치가 늘면 다시 RIN 이다. 둘 다 넘었으면 덜 넘은 쪽. 옛 "주간 reset 이 빠른 계정 우선"(2026-09-26)을
3204
- // 대체한다(legacyPatched). 사용량 그림이 완전하고 건강한 후보만, upstream ranking 에서 이미 차지한
3205
- // 자리들 안에서만 재배열한다. unknown·partial·blocked·reserve·plan 부적격·5h hot 후보는 upstream 자리
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 선언 한 줄이다.
3299
+ // 2026-10-02 사용자 정책: "리셋이 얼마 안 남았는데 한도가 많이 남은 계정을 적극 쓴다". Anthropic 새/cold
3300
+ // 선택에서 오늘 몫(사용량 패널 paceOf 와 같은 기준: (지난 날 수 + 1) / 창 일수) 안쪽 계정끼리는 저장
3301
+ // 순서 대신 7d required drain(남은 비율 ÷ 리셋까지 남은 시간, upstream windowRequiredDrain 그대로)이 큰
3302
+ // 계정을 먼저 쓴다. 몫을 넘은 계정은 몫 안쪽 계정 뒤(페이스 보호), 둘 다 넘었으면 덜 넘은 쪽, 동률은 저장
3303
+ // 순서(위치 0 먼저). 2026-09-29 "몫 안쪽은 RIN(저장 순서 0) 먼저" 적용본을 대체한다(legacyPatched).
3304
+ // 사용량 그림이 완전하고 건강한 후보만, upstream ranking 에서 이미 차지한 자리들 안에서만 재배열한다.
3305
+ // unknown·partial·blocked·reserve·plan 부적격·5h hot 후보는 upstream 자리 그대로다. exact summon 은 이
3306
+ // 앞에서 return 하고 warm/explicit pin·plan pin 재승격은 이 뒤에 온다(warm pin 은 shouldRank=false).
3307
+ // 재배열 대상은 한도 미도달·측정 완료(used<1) 후보뿐이라 18.4.5 spent allowance 후보는 upstream 자리 그대로다.
3308
+ // 앵커는 18.4.4~18.4.6 모두 한 번 있는 preflightFailures 선언 한 줄이다. 검증: core-account-order-test.ts.
3210
3309
  file: "../pi-ai/src/auth/select.ts",
3211
- marker: "// Prefer RIN (first Anthropic account) while it stays inside its daily slice of the weekly quota.",
3310
+ marker: "// Prefer the Anthropic account whose unused weekly quota expires soonest, inside its daily slice.",
3212
3311
  anchor: "\t\tconst preflightFailures = new Set<OAuthCandidate>();",
3213
- legacyPatched: ` // Prefer the healthy Anthropic account whose weekly window resets first.
3312
+ legacyPatched: ` // Prefer RIN (first Anthropic account) while it stays inside its daily slice of the weekly quota.
3214
3313
  // Only candidates with a complete, healthy usage picture move, and only among the slots they
3215
3314
  // already hold in the upstream ranking: unknown, partial, blocked, reserve, plan-ineligible or
3216
- // 5h-hot candidates keep their place. Ties: larger remaining weekly quota, then upstream order.
3315
+ // 5h-hot candidates keep their place. Daily slice = (elapsed whole days + 1) / window days.
3316
+ // Inside the slice: storage order. Over it: smaller overshoot first, then storage order.
3217
3317
  if (provider === "anthropic" && shouldRank && strategy) {
3218
3318
  const nowMs = Date.now();
3319
+ const dayMs = 86_400_000;
3219
3320
  const weekly = candidates.map(candidate => {
3220
3321
  const usage = candidate.usage;
3221
3322
  if (!candidate.usageChecked || !usage || candidate.inReserve === true) return undefined;
@@ -3245,17 +3346,21 @@ import { resolveUsedFraction } from "../usage";`,
3245
3346
  return undefined;
3246
3347
  }
3247
3348
  const resetAt = secondary.window?.resetsAt;
3349
+ const durationMs = secondary.window?.durationMs;
3248
3350
  const used = resolveUsedFraction(secondary);
3249
3351
  if (typeof resetAt !== "number" || !Number.isFinite(resetAt) || resetAt <= nowMs) return undefined;
3352
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < dayMs) return undefined;
3250
3353
  if (typeof used !== "number" || !Number.isFinite(used)) return undefined;
3251
- return { resetAt, remaining: 1 - used };
3354
+ const elapsedMs = Math.min(durationMs, Math.max(0, durationMs - (resetAt - nowMs)));
3355
+ const allowance = Math.min(1, ((Math.floor(elapsedMs / dayMs) + 1) * dayMs) / durationMs);
3356
+ return { overshoot: Math.max(0, used - allowance), index: candidate.selection.index };
3252
3357
  });
3253
3358
  const slots = candidates.flatMap((_candidate, pos) => (weekly[pos] ? [pos] : []));
3254
3359
  const preferred = [...slots]
3255
3360
  .sort((left, right) => {
3256
- const leftWeekly = weekly[left]!;
3257
- const rightWeekly = weekly[right]!;
3258
- return leftWeekly.resetAt - rightWeekly.resetAt || rightWeekly.remaining - leftWeekly.remaining || left - right;
3361
+ const a = weekly[left]!;
3362
+ const b = weekly[right]!;
3363
+ return Number(a.overshoot > 0) - Number(b.overshoot > 0) || a.overshoot - b.overshoot || a.index - b.index;
3259
3364
  })
3260
3365
  .map(pos => candidates[pos]!);
3261
3366
  slots.forEach((pos, order) => {
@@ -3263,11 +3368,12 @@ import { resolveUsedFraction } from "../usage";`,
3263
3368
  });
3264
3369
  }
3265
3370
  const preflightFailures = new Set<OAuthCandidate>();`,
3266
- patched: ` // Prefer RIN (first Anthropic account) while it stays inside its daily slice of the weekly quota.
3371
+ patched: ` // Prefer the Anthropic account whose unused weekly quota expires soonest, inside its daily slice.
3267
3372
  // Only candidates with a complete, healthy usage picture move, and only among the slots they
3268
3373
  // already hold in the upstream ranking: unknown, partial, blocked, reserve, plan-ineligible or
3269
3374
  // 5h-hot candidates keep their place. Daily slice = (elapsed whole days + 1) / window days.
3270
- // Inside the slice: storage order. Over it: smaller overshoot first, then storage order.
3375
+ // Inside the slice: larger weekly required drain (upstream windowRequiredDrain) first.
3376
+ // Over it: after every in-slice account, smaller overshoot first. Ties: storage order.
3271
3377
  if (provider === "anthropic" && shouldRank && strategy) {
3272
3378
  const nowMs = Date.now();
3273
3379
  const dayMs = 86_400_000;
@@ -3307,14 +3413,23 @@ import { resolveUsedFraction } from "../usage";`,
3307
3413
  if (typeof used !== "number" || !Number.isFinite(used)) return undefined;
3308
3414
  const elapsedMs = Math.min(durationMs, Math.max(0, durationMs - (resetAt - nowMs)));
3309
3415
  const allowance = Math.min(1, ((Math.floor(elapsedMs / dayMs) + 1) * dayMs) / durationMs);
3310
- return { overshoot: Math.max(0, used - allowance), index: candidate.selection.index };
3416
+ return {
3417
+ overshoot: Math.max(0, used - allowance),
3418
+ drain: windowRequiredDrain(secondary, nowMs, strategy.windowDefaults.secondaryMs),
3419
+ index: candidate.selection.index,
3420
+ };
3311
3421
  });
3312
3422
  const slots = candidates.flatMap((_candidate, pos) => (weekly[pos] ? [pos] : []));
3313
3423
  const preferred = [...slots]
3314
3424
  .sort((left, right) => {
3315
3425
  const a = weekly[left]!;
3316
3426
  const b = weekly[right]!;
3317
- return Number(a.overshoot > 0) - Number(b.overshoot > 0) || a.overshoot - b.overshoot || a.index - b.index;
3427
+ return (
3428
+ Number(a.overshoot > 0) - Number(b.overshoot > 0) ||
3429
+ a.overshoot - b.overshoot ||
3430
+ (a.overshoot > 0 ? 0 : b.drain - a.drain) ||
3431
+ a.index - b.index
3432
+ );
3318
3433
  })
3319
3434
  .map(pos => candidates[pos]!);
3320
3435
  slots.forEach((pos, order) => {
@@ -3323,6 +3438,64 @@ import { resolveUsedFraction } from "../usage";`,
3323
3438
  }
3324
3439
  const preflightFailures = new Set<OAuthCandidate>();`,
3325
3440
  },
3441
+ // 2026-10-02 사용자 정책: summon marker 없이 띄운 일반 maker child 는 부모 세션의 Anthropic 계정 고정(warm 자동
3442
+ // pin·교체 exact pin 모두)을 물려받지 않고 위 순서 규칙으로 새로 고른다. 실제 경로: task 도구(task/index.ts) →
3443
+ // runStructuredSubagent(structured-subagent.ts: credentialSourceSessionId = 부모 agent.sessionId) → runSubprocess
3444
+ // → createAgentSession(sdk.ts) → authStorage.sessions.inherit. ‘린/미오 호출’ summon child 는 상속 뒤 character-voice
3445
+ // before_agent_start 가 지정 위치 exact pin 을 다시 걸므로 그대로 둔다. 다른 provider·maker 아닌 child 상속은 그대로.
3446
+ // 아래 네 파일 다섯 항목: inherit 의 provider 제외 인자, 그 타입, sdk 옵션·전달, executor 의 maker 판정.
3447
+ // 검증: core-account-order-test.ts [상속].
3448
+ {
3449
+ file: "../pi-ai/src/auth/affinity.ts",
3450
+ marker: "\tinherit(sourceSessionId: string, targetSessionId: string, skipProviders?: readonly string[]): number {",
3451
+ anchor: ` inherit(sourceSessionId: string, targetSessionId: string): number {
3452
+ if (!sourceSessionId || !targetSessionId || sourceSessionId === targetSessionId) return 0;
3453
+ let inherited = 0;
3454
+ for (const provider of this.#pool.providers()) {
3455
+ const credential = this.get(provider, sourceSessionId);
3456
+ if (!credential) continue;`,
3457
+ patched: ` inherit(sourceSessionId: string, targetSessionId: string, skipProviders?: readonly string[]): number {
3458
+ if (!sourceSessionId || !targetSessionId || sourceSessionId === targetSessionId) return 0;
3459
+ let inherited = 0;
3460
+ for (const provider of this.#pool.providers()) {
3461
+ if (skipProviders?.includes(provider)) continue;
3462
+ const credential = this.get(provider, sourceSessionId);
3463
+ if (!credential) continue;`,
3464
+ },
3465
+ {
3466
+ file: "../pi-ai/src/auth/types.ts",
3467
+ marker: "\tinherit(sourceSessionId: string, targetSessionId: string, skipProviders?: readonly string[]): number;",
3468
+ anchor: "\tinherit(sourceSessionId: string, targetSessionId: string): number;",
3469
+ patched: ` /** \`skipProviders\`: providers whose affinity the target must choose fresh instead of copying. */
3470
+ inherit(sourceSessionId: string, targetSessionId: string, skipProviders?: readonly string[]): number;`,
3471
+ },
3472
+ {
3473
+ file: "src/sdk.ts",
3474
+ marker: "\tcredentialInheritSkipProviders?: readonly string[];",
3475
+ anchor: "\tcredentialSourceSessionId?: string;\n\n\t/** Model to use. Default: from settings, else first available */",
3476
+ patched: ` credentialSourceSessionId?: string;
3477
+ /** Providers whose {@link credentialSourceSessionId} affinity is not copied; the child selects them fresh. */
3478
+ credentialInheritSkipProviders?: readonly string[];
3479
+
3480
+ /** Model to use. Default: from settings, else first available */`,
3481
+ },
3482
+ {
3483
+ file: "src/sdk.ts",
3484
+ marker: "options.credentialSourceSessionId, providerSessionId, options.credentialInheritSkipProviders);",
3485
+ anchor: "\t\tmodelRegistry.authStorage.sessions.inherit(options.credentialSourceSessionId, providerSessionId);",
3486
+ patched: ` modelRegistry.authStorage.sessions.inherit(
3487
+ options.credentialSourceSessionId, providerSessionId, options.credentialInheritSkipProviders);`,
3488
+ },
3489
+ {
3490
+ file: "src/task/executor.ts",
3491
+ marker: "\t\t\t\tcredentialInheritSkipProviders:",
3492
+ anchor: "\t\t\t\tcredentialSourceSessionId: options.credentialSourceSessionId,\n",
3493
+ patched: ` credentialSourceSessionId: options.credentialSourceSessionId,
3494
+ // A plain maker picks its own Anthropic account; a character summon re-pins its exact one.
3495
+ credentialInheritSkipProviders:
3496
+ agent.name === "maker" && !/\\[character-summon\\s/u.test(task) ? ["anthropic"] : undefined,
3497
+ `,
3498
+ },
3326
3499
  {
3327
3500
  // exact OAuth 가 해석되지 않으면 login API 키·18.4.5 config fallback 키(#13815)·env 키로 조용히 넘어가지 않는다.
3328
3501
  // 18.4.5는 resolveOAuth 성공 분기에 oauthIdentity 전달을 넣어 옛 한 덩어리 앵커가 사라졌다. 라벨은
@@ -8138,21 +8311,32 @@ function parentSubagentServiceTiers(
8138
8311
  // controls가 둘 다 참일 때만 나간다(anthropic.ts prefixMismatchBehavior). models.yml은 thinking override의
8139
8312
  // prefixBinding을 스키마에서 조용히 버리고 공개 설치에는 없으므로 설정으로 켜지 않는다.
8140
8313
  {
8314
+ // 18.4.10(#14019)은 이 행에 prefixBinding을 넣었지만 binding controls는 false로 남긴다. 18.4.10 후보는 binding controls만 켠다.
8315
+ // 그 결과가 18.4.6 적용본과 바이트까지 같으면 두 후보가 함께 applied(ambiguous)이고, 문맥으로도 가를 수 없다(가장 가까운
8316
+ // 18.4.6↔18.4.10 차이가 앵커 앞 53K자·뒤 249K자). 그래서 18.4.10 결과만 `": true"` 공백으로 구분한다. JSON.parse는 이 공백을 무시한다.
8141
8317
  file: "../pi-catalog/src/models.json",
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',
8143
- anchor: '"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},"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":false',
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',
8145
- },
8146
- {
8147
- // 모든 provider의 Opus 5.5 규칙 계보에 prefixBinding(Sonnet 5.5 규칙과 같은 모양). 18.4.6은 kdl 한 줄 이동으로
8148
- // source가 kdl:61이고 규칙 본문은 같다. alternate는 18.4.4·18.4.5 공통 kdl:60 앵커다. 두 후보의 marker는 source가
8149
- // 달라 서로의 결과·순정본에 들어 있지 않다. 18.4.4 적용본은 kdl:60 patched 바로 뒤에 binding controls 규칙이 붙어
8150
- // 있어 alternate marker가 그대로 성립한다.
8318
+ marker: OPUS55_ROW_1846.marker.replace('"supportsThinkingBindingControls":true', '"supportsThinkingBindingControls": true'),
8319
+ anchor: OPUS55_ROW_1846.anchor.replace('"supportsDisplay":true}', '"supportsDisplay":true,"prefixBinding":true}'),
8320
+ patched: OPUS55_ROW_1846.patched.replace('"supportsThinkingBindingControls":true', '"supportsThinkingBindingControls": true'),
8321
+ alternates: [OPUS55_ROW_1846],
8322
+ },
8323
+ {
8324
+ // 모든 provider의 Opus 5.5 규칙 계보에 prefixBinding(Sonnet 5.5 규칙과 같은 모양). 18.4.10은 upstream #14019가 kdl:61에
8325
+ // 같은 thinking.prefixBinding을 넣었으므로 할 일이 없다(본 후보: 그 upstream 줄의 no-op). 18.4.6 적용 결과와 18.4.10
8326
+ // 순정의 kdl:61 규칙은 바이트까지 같아서, 두 후보 모두 바로 뒤 Sonnet 5.5 규칙의 source(18.4.6 kdl:70, 18.4.10 kdl:77)까지
8327
+ // 앵커에 넣어 가른다. 그러지 않으면 18.4.10 순정이 18.4.6 후보로 applied가 되고 --revert가 upstream prefixBinding을 지운다.
8328
+ // alternate는 18.4.6(kdl:61)과 18.4.4·18.4.5 공통 kdl:60 앵커다. 18.4.4 적용본은 kdl:60 patched 바로 뒤에 binding
8329
+ // controls 규칙이 붙어 있어 kdl:60 marker가 그대로 성립한다.
8151
8330
  file: "../pi-catalog/src/compat/rules.json",
8152
- marker: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}}',
8153
- anchor: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false}}',
8154
- patched: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}}',
8331
+ marker: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:77",',
8332
+ anchor: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:77",',
8333
+ patched: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:77",',
8155
8334
  alternates: [{
8335
+ file: "../pi-catalog/src/compat/rules.json",
8336
+ marker: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:70",',
8337
+ anchor: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false}},{"source":"classes/anthropic.kdl:70",',
8338
+ patched: '{"source":"classes/anthropic.kdl:61","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false},"thinking":{"prefixBinding":true}},{"source":"classes/anthropic.kdl:70",',
8339
+ }, {
8156
8340
  file: "../pi-catalog/src/compat/rules.json",
8157
8341
  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}}',
8158
8342
  anchor: '{"source":"classes/anthropic.kdl:60","class":"anthropic","family":"opus","revision":[{"op":">=","revision":"5.5.0"},{"op":"<","revision":"6.0.0"}],"wire":{"supportsForcedToolChoice":false}}',
@@ -8161,16 +8345,23 @@ function parentSubagentServiceTiers(
8161
8345
  },
8162
8346
  {
8163
8347
  // binding controls 규칙은 그 버전의 upstream Sonnet 5.5 binding 규칙 바로 뒤에 같은 provider 범위로 둔다.
8164
- // 18.4.6: kdl:129, alternate 18.4.5: kdl:128(둘 다 anthropic·cloudflare, 규칙 본문 동일). alternate 18.4.4:
8165
- // kdl:106(anthropic·cloudflare·vertex). 18.4.6·18.4.5 후보는 추가 규칙이 같아 marker를 앞 Sonnet 규칙까지 포함한
8348
+ // 18.4.10: kdl:139(#13996이 Vertex를 뺀 supportsPerMessageEffort가 같은 Sonnet 규칙에 붙었다. provider 범위는 그대로
8349
+ // anthropic·cloudflare이고 추가하는 Opus 규칙 본문도 같다). alternate 18.4.6: kdl:129, 18.4.5: kdl:128(둘 다 anthropic·cloudflare,
8350
+ // 규칙 본문 동일). alternate 18.4.4:
8351
+ // kdl:106(anthropic·cloudflare·vertex). 18.4.10·18.4.6·18.4.5 후보는 추가 규칙이 같아 marker를 앞 Sonnet 규칙까지 포함한
8166
8352
  // patched 전체로 둔다(같은 marker면 둘 다 applied라 ambiguous가 된다). 18.4.4 라이브 적용본은 옛 위치(kdl:60 뒤)에
8167
8353
  // 같은 규칙이 있어 그 alternate marker로 applied이다. 그 적용본의 --revert는 patched가 연속으로 없으므로 엔진의
8168
8354
  // 백업 경로(~/.omp/core-patch-backup)를 쓴다.
8169
8355
  file: "../pi-catalog/src/compat/rules.json",
8170
- marker: '{"source":"classes/anthropic.kdl:129","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}}',
8171
- anchor: '{"source":"classes/anthropic.kdl:129","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true}}',
8172
- patched: '{"source":"classes/anthropic.kdl:129","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}}',
8356
+ marker: '{"source":"classes/anthropic.kdl:139","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true,"supportsPerMessageEffort":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}}',
8357
+ anchor: '{"source":"classes/anthropic.kdl:139","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true,"supportsPerMessageEffort":true}}',
8358
+ patched: '{"source":"classes/anthropic.kdl:139","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true,"supportsPerMessageEffort":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}}',
8173
8359
  alternates: [{
8360
+ file: "../pi-catalog/src/compat/rules.json",
8361
+ marker: '{"source":"classes/anthropic.kdl:129","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}}',
8362
+ anchor: '{"source":"classes/anthropic.kdl:129","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true}}',
8363
+ patched: '{"source":"classes/anthropic.kdl:129","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}}',
8364
+ }, {
8174
8365
  file: "../pi-catalog/src/compat/rules.json",
8175
8366
  marker: '{"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}}',
8176
8367
  anchor: '{"source":"classes/anthropic.kdl:128","class":"anthropic","providers":["anthropic","cloudflare-ai-gateway"],"family":"sonnet","revision":[{"op":">=","revision":"5.5.0"}],"wire":{"supportsThinkingBindingControls":true}}',
@@ -8354,6 +8545,14 @@ function parentSubagentServiceTiers(
8354
8545
  marker: 'import { createRatchetPrelude } from "./ratchet/prelude.ts";',
8355
8546
  anchor: 'import { createRatchetPrelude } from "./ratchet/prelude";',
8356
8547
  patched: 'import { createRatchetPrelude } from "./ratchet/prelude.ts";',
8548
+ // 18.4.10: upstream #14027/#14029가 모듈을 `ratchet/prelude-definition.ts`로 개명해 같은 stem 충돌이 없어졌다(RETIRE).
8549
+ // 그 upstream import 줄 자체의 no-op이다. 18.4.6(KYS·라이브)은 위 본 후보가 계속 필요하다.
8550
+ alternates: [{
8551
+ file: "src/sdk.ts",
8552
+ marker: 'import { createRatchetPrelude } from "./ratchet/prelude-definition";',
8553
+ anchor: 'import { createRatchetPrelude } from "./ratchet/prelude-definition";',
8554
+ patched: 'import { createRatchetPrelude } from "./ratchet/prelude-definition";',
8555
+ }],
8357
8556
  },
8358
8557
  // P55. genuine 사용자 steer 가 진행 중 모델 요청을 끝까지 기다리지 않게 한다(사용자 결정: 즉시 반영, 재추론 사용량 감수).
8359
8558
  // 2026-10-01 사용자 관측: Astra(xhigh) 4분짜리 추론 요청 중 넣은 steer 가 그 요청이 끝날 때까지(98.8s·169.6s)
@@ -8697,6 +8896,82 @@ class HarmonyLeakInterruption extends Error {`,
8697
8896
  anchor: "\t\t\tdurationMs: job.durationMs,\n\t\t\t...(job.meta ? { meta: job.meta } : {}),\n",
8698
8897
  patched: "\t\t\tdurationMs: job.durationMs,\n\t\t\t...(job.status ? { status: job.status } : {}),\n\t\t\t...(job.meta ? { meta: job.meta } : {}),\n",
8699
8898
  },
8899
+ // 이슈 #10: extension 의 getAsyncJobSnapshot 은 인자를 받지 않고(ExtensionContext 타입·runner·sdk adapter) 기본 recent 5개만
8900
+ // 돌려줘, 같은 owner 의 더 최근 job 5개에 밀린 취소 실행의 종료 근거(같은 실행의 cancelled·endTime)를 볼 수 없었다. options 를
8901
+ // 끝까지 전달하고, `jobIds` 를 준 호출에만 그 id 들의 exact row 를 `jobs` 로 더한다. 기존 owner filter·foreground 제외를 그대로
8902
+ // 쓰고 getJob O(k) 조회라 정렬·전체 복사가 없다. jobIds 가 없으면 반환 형태와 기본 recent 5 는 그대로다.
8903
+ {
8904
+ file: "src/session/agent-session-types.ts",
8905
+ marker: "export interface AsyncJobSnapshotOptions {",
8906
+ anchor: "/** Snapshot of running, recent, and pending-delivery asynchronous jobs. */\nexport interface AsyncJobSnapshot {\n\trunning: AsyncJobSnapshotItem[];\n\trecent: AsyncJobSnapshotItem[];\n\tdelivery: AsyncJobDeliveryState;\n}",
8907
+ patched: "/** Options for an {@link AsyncJobSnapshot} read. */\nexport interface AsyncJobSnapshotOptions {\n\t/** Settled rows listed in `recent`, newest first (default 5). */\n\trecentLimit?: number;\n\t/**\n\t * Exact job ids to look up regardless of the `recent` window. Rows this session owns and lists (same owner\n\t * filter; foreground-backed jobs stay hidden) are returned in `jobs`; unknown, evicted or foreign ids are omitted.\n\t */\n\tjobIds?: readonly string[];\n}\n\n/** Snapshot of running, recent, and pending-delivery asynchronous jobs. */\nexport interface AsyncJobSnapshot {\n\trunning: AsyncJobSnapshotItem[];\n\trecent: AsyncJobSnapshotItem[];\n\tdelivery: AsyncJobDeliveryState;\n\t/** Present only when `jobIds` was requested: the exact rows found for those ids, in request order. */\n\tjobs?: AsyncJobSnapshotItem[];\n}",
8908
+ },
8909
+ {
8910
+ file: "src/session/agent-session.ts",
8911
+ marker: "\tAsyncJobSnapshotOptions,\n\tCommandMetadataChangedListener,",
8912
+ anchor: "\tAsyncJobSnapshot,\n\tCommandMetadataChangedListener,",
8913
+ patched: "\tAsyncJobSnapshot,\n\tAsyncJobSnapshotOptions,\n\tCommandMetadataChangedListener,",
8914
+ },
8915
+ {
8916
+ file: "src/session/agent-session.ts",
8917
+ marker: "\tgetAsyncJobSnapshot(options?: AsyncJobSnapshotOptions): AsyncJobSnapshot | null {",
8918
+ anchor: "\tgetAsyncJobSnapshot(options?: { recentLimit?: number }): AsyncJobSnapshot | null {",
8919
+ patched: "\tgetAsyncJobSnapshot(options?: AsyncJobSnapshotOptions): AsyncJobSnapshot | null {",
8920
+ },
8921
+ {
8922
+ file: "src/session/agent-session.ts",
8923
+ marker: "\t\treturn { running, recent, delivery, jobs };",
8924
+ anchor: "\t\tconst delivery = manager.getDeliveryState(ownerFilter);\n\t\treturn { running, recent, delivery };",
8925
+ patched: "\t\tconst delivery = manager.getDeliveryState(ownerFilter);\n\t\tif (!options?.jobIds) return { running, recent, delivery };\n\t\t// CUELO #10: exact lookup independent of the display-sized recent window, with the same owner and foreground rules.\n\t\tconst jobs = options.jobIds.flatMap(id => {\n\t\t\tconst job = manager.getJob(id);\n\t\t\tif (!job || job.foreground || (ownerFilter && job.ownerId !== ownerFilter.ownerId)) return [];\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tid: job.id,\n\t\t\t\t\ttype: job.type,\n\t\t\t\t\tstatus: job.status,\n\t\t\t\t\tlabel: job.label,\n\t\t\t\t\tstartTime: job.startTime,\n\t\t\t\t\tendTime: job.endTime,\n\t\t\t\t\tagentId: job.agentId,\n\t\t\t\t},\n\t\t\t];\n\t\t});\n\t\treturn { running, recent, delivery, jobs };",
8926
+ },
8927
+ {
8928
+ file: "src/session/agent-session.ts",
8929
+ marker: "\t\t\tgetAsyncJobSnapshot: options => this.getAsyncJobSnapshot(options),",
8930
+ anchor: "\t\t\tgetAsyncJobSnapshot: () => this.getAsyncJobSnapshot(),",
8931
+ patched: "\t\t\tgetAsyncJobSnapshot: options => this.getAsyncJobSnapshot(options),",
8932
+ },
8933
+ {
8934
+ file: "src/extensibility/extensions/types.ts",
8935
+ marker: "import type { AsyncJobSnapshot, AsyncJobSnapshotOptions, SendUserMessageOptions } from \"../../session/agent-session\";",
8936
+ anchor: "import type { AsyncJobSnapshot, SendUserMessageOptions } from \"../../session/agent-session\";",
8937
+ patched: "import type { AsyncJobSnapshot, AsyncJobSnapshotOptions, SendUserMessageOptions } from \"../../session/agent-session\";",
8938
+ },
8939
+ {
8940
+ file: "src/extensibility/extensions/types.ts",
8941
+ marker: "\tgetAsyncJobSnapshot(options?: AsyncJobSnapshotOptions): AsyncJobSnapshot | null;",
8942
+ anchor: "\t/** Get a read-only snapshot of async jobs owned by this session. */\n\tgetAsyncJobSnapshot(): AsyncJobSnapshot | null;",
8943
+ patched: "\t/** Get a read-only snapshot of async jobs owned by this session; `options.jobIds` adds exact rows for those ids. */\n\tgetAsyncJobSnapshot(options?: AsyncJobSnapshotOptions): AsyncJobSnapshot | null;",
8944
+ },
8945
+ {
8946
+ file: "src/extensibility/extensions/runner.ts",
8947
+ marker: "import type { AsyncJobSnapshot, AsyncJobSnapshotOptions } from \"../../session/agent-session\";",
8948
+ anchor: "import type { AsyncJobSnapshot } from \"../../session/agent-session\";",
8949
+ patched: "import type { AsyncJobSnapshot, AsyncJobSnapshotOptions } from \"../../session/agent-session\";",
8950
+ },
8951
+ {
8952
+ file: "src/extensibility/extensions/runner.ts",
8953
+ marker: "\t#getAsyncJobSnapshotFn: (options?: AsyncJobSnapshotOptions) => AsyncJobSnapshot | null = () => null;",
8954
+ anchor: "\t#getAsyncJobSnapshotFn: () => AsyncJobSnapshot | null = () => null;",
8955
+ patched: "\t#getAsyncJobSnapshotFn: (options?: AsyncJobSnapshotOptions) => AsyncJobSnapshot | null = () => null;",
8956
+ },
8957
+ {
8958
+ file: "src/extensibility/extensions/runner.ts",
8959
+ marker: "\t\tgetAsyncJobSnapshot?: (options?: AsyncJobSnapshotOptions) => AsyncJobSnapshot | null,",
8960
+ anchor: "\t\tgetAsyncJobSnapshot?: () => AsyncJobSnapshot | null,",
8961
+ patched: "\t\tgetAsyncJobSnapshot?: (options?: AsyncJobSnapshotOptions) => AsyncJobSnapshot | null,",
8962
+ },
8963
+ {
8964
+ file: "src/extensibility/extensions/runner.ts",
8965
+ marker: "\t\t\tgetAsyncJobSnapshot: options => this.#getAsyncJobSnapshotFn(options),",
8966
+ anchor: "\t\t\tgetAsyncJobSnapshot: () => this.#getAsyncJobSnapshotFn(),",
8967
+ patched: "\t\t\tgetAsyncJobSnapshot: options => this.#getAsyncJobSnapshotFn(options),",
8968
+ },
8969
+ {
8970
+ file: "src/sdk.ts",
8971
+ marker: "\t\t\toptions => (hasSession ? session.getAsyncJobSnapshot(options) : null),",
8972
+ anchor: "\t\t\t() => (hasSession ? session.getAsyncJobSnapshot() : null),",
8973
+ patched: "\t\t\toptions => (hasSession ? session.getAsyncJobSnapshot(options) : null),",
8974
+ },
8700
8975
  ];
8701
8976
  // EDITS 문자열의 줄 끝을 LF로 통일한다. 이 파일의 작업 사본이 CRLF여도 core 파일(LF)과
8702
8977
  // 비교·치환이 어긋나지 않는다. core 파일 자체의 줄 끝은 건드리지 않는다.
package/install.mjs CHANGED
@@ -22,7 +22,11 @@ import { fileURLToPath } from "node:url";
22
22
  const ROOT = path.dirname(fileURLToPath(import.meta.url));
23
23
  const require = createRequire(path.join(ROOT, "package.json"));
24
24
 
25
- /** The model slots the public harness routes Makers to; `--model` sets omp's core `default` separately. */
25
+ /**
26
+ * The model slots the public harness routes Makers to; `--model` sets omp's core `default` separately.
27
+ * Same list as `lib/harness-roles.ts`, which the Model roles panel lists as "not set" until chosen;
28
+ * this file runs before dependencies exist, so `install.test.mjs` keeps the two equal.
29
+ */
26
30
  export const HARNESS_ROLES = [
27
31
  "implSonnet",
28
32
  "implOpus",
@@ -274,7 +278,7 @@ function setup(options) {
274
278
  if (roles.written) console.log(`Profile config: wrote ${roles.configPath}.`);
275
279
  if (roles.ignored.length > 0) console.log(`Profile config: ${roles.configPath} already exists and was not changed; ignored ${roles.ignored.join(", ")}.`);
276
280
  if (roles.missing.length > 0) {
277
- console.log(`Model roles not set: ${roles.missing.join(", ")}. Choose your own models in CUELO Settings > Model roles.`);
281
+ console.log(`Model roles not set: ${roles.missing.join(", ")}. They are listed as not set in CUELO Settings > Models > Model roles; choose your own models there.`);
278
282
  }
279
283
  console.log("Next: run `node install.mjs start`, open the address it prints, and sign in to your providers under Settings > Models.");
280
284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cuelo",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
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": {
@@ -30,7 +30,7 @@
30
30
  "license": "MIT",
31
31
  "cueloBuild": {
32
32
  "distribution": "cuelo",
33
- "coreVersion": "18.4.6"
33
+ "coreVersion": "18.4.10"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=22.19.0",
@@ -88,11 +88,11 @@
88
88
  "test:coverage": "bun test --coverage ./app ./components ./hooks ./lib ./public ./bin ./install.test.mjs"
89
89
  },
90
90
  "dependencies": {
91
- "@oh-my-pi/pi-agent-core": "18.4.6",
92
- "@oh-my-pi/pi-ai": "18.4.6",
93
- "@oh-my-pi/pi-coding-agent": "18.4.6",
94
- "@oh-my-pi/pi-tui": "18.4.6",
95
- "@oh-my-pi/pi-utils": "18.4.6",
91
+ "@oh-my-pi/pi-agent-core": "18.4.10",
92
+ "@oh-my-pi/pi-ai": "18.4.10",
93
+ "@oh-my-pi/pi-coding-agent": "18.4.10",
94
+ "@oh-my-pi/pi-tui": "18.4.10",
95
+ "@oh-my-pi/pi-utils": "18.4.10",
96
96
  "@seed-design/css": "2.8.0",
97
97
  "@seed-design/react": "2.4.1",
98
98
  "@seed-design/tailwind4-theme": "2.4.0",
@@ -109,6 +109,7 @@
109
109
  },
110
110
  "devDependencies": {
111
111
  "@lobehub/icons": "^5.16.0",
112
+ "@react-three/fiber": "^9.8.1",
112
113
  "@tailwindcss/postcss": "^4.3.3",
113
114
  "@types/bun": "^1.4.0",
114
115
  "@types/js-yaml": "^4.0.9",
@@ -116,6 +117,7 @@
116
117
  "@types/react": "^19.2.18",
117
118
  "@types/react-dom": "^19.2.5",
118
119
  "@types/react-syntax-highlighter": "^15.5.13",
120
+ "@types/three": "^0.186.0",
119
121
  "eslint": "^9.39.5",
120
122
  "eslint-config-next": "16.2.12",
121
123
  "jiti": "^2.7.0",
@@ -131,6 +133,7 @@
131
133
  "remark-gfm": "^4.0.1",
132
134
  "remark-math": "^6.0.0",
133
135
  "tailwindcss": "^4.3.3",
136
+ "three": "^0.186.1",
134
137
  "typescript": "^5.9.3"
135
138
  }
136
139
  }
@@ -0,0 +1,8 @@
1
+ # Office 3D assets
2
+
3
+ 오피스 보기의 3D 사무실(`components/office/OfficeScene3D.tsx`)은 내려받은 모델·텍스처·애니메이션 파일을 쓰지 않는다.
4
+ 방(바닥·벽·책상·의자·노트북·소파)과 일곱 캐릭터는 모두 코드에서 three.js 기본 도형으로 만든다. 유료 자산은 쓰지 않는다.
5
+
6
+ - 캐릭터 겉모습과 동작(대기 숨쉬기·걷기·앉아 일하기·손짓·쉬기)은 `components/office/OfficeCharacter.ts` 한 곳에 있다.
7
+ 각 캐릭터의 머리색·머리 모양·옷 색은 같은 저장소의 계정 아바타(`public/avatars/`)를 보고 정했다.
8
+ - 이전에 쓰던 three.js 예제 로봇(`RobotExpressive.glb`, CC0 1.0)은 더 쓰지 않아 지웠다.