cuelo 0.8.3 → 0.9.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 (88) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +6 -6
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.js +2 -2
  6. package/.next/required-server-files.json +2 -2
  7. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +1 -1
  18. package/.next/server/app/_not-found.rsc +3 -3
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  20. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next/server/app/api/updates/route.js +1 -1
  26. package/.next/server/app/index.html +1 -1
  27. package/.next/server/app/index.rsc +5 -5
  28. package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
  29. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  30. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  31. package/.next/server/app/index.segments/_index.segment.rsc +3 -3
  32. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  33. package/.next/server/app/office/page_client-reference-manifest.js +1 -1
  34. package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
  35. package/.next/server/app/office-preview.html +1 -1
  36. package/.next/server/app/office-preview.rsc +3 -3
  37. package/.next/server/app/office-preview.segments/_full.segment.rsc +3 -3
  38. package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
  39. package/.next/server/app/office-preview.segments/_index.segment.rsc +3 -3
  40. package/.next/server/app/office-preview.segments/_tree.segment.rsc +2 -2
  41. package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +1 -1
  42. package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
  43. package/.next/server/app/office.html +1 -1
  44. package/.next/server/app/office.rsc +5 -5
  45. package/.next/server/app/office.segments/_full.segment.rsc +5 -5
  46. package/.next/server/app/office.segments/_head.segment.rsc +1 -1
  47. package/.next/server/app/office.segments/_index.segment.rsc +3 -3
  48. package/.next/server/app/office.segments/_tree.segment.rsc +2 -2
  49. package/.next/server/app/office.segments/office/__PAGE__.segment.rsc +3 -3
  50. package/.next/server/app/office.segments/office.segment.rsc +1 -1
  51. package/.next/server/app/page_client-reference-manifest.js +1 -1
  52. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  53. package/.next/server/app/recover.html +1 -1
  54. package/.next/server/app/recover.rsc +3 -3
  55. package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
  56. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  57. package/.next/server/app/recover.segments/_index.segment.rsc +3 -3
  58. package/.next/server/app/recover.segments/_tree.segment.rsc +2 -2
  59. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  60. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  61. package/.next/server/app-paths-manifest.json +6 -6
  62. package/.next/server/chunks/5489.js +3 -3
  63. package/.next/server/chunks/7362.js +1 -1
  64. package/.next/server/middleware-build-manifest.js +1 -1
  65. package/.next/server/pages/404.html +1 -1
  66. package/.next/server/pages/500.html +1 -1
  67. package/.next/server/server-reference-manifest.json +1 -1
  68. package/.next/static/chunks/6110-dffa6a7b1ada31e6.js +1 -0
  69. package/.next/static/chunks/9575-09e3873e0ee5ecfc.js +22 -0
  70. package/.next/static/chunks/app/{layout-427af3ea4f595064.js → layout-3adbfd44301cba82.js} +1 -1
  71. package/.next/static/css/{67b6e19482963224.css → 1a4cea7126dfb757.css} +1 -1
  72. package/.next/trace +107 -107
  73. package/.next/trace-build +1 -1
  74. package/README.md +3 -1
  75. package/Tools/CUELO_Setup/files/native-runtime-patch.js +3 -3
  76. package/Tools/CUELO_Setup/files/usage-server.js +56 -3
  77. package/Tools/OMP_Global_Config/agent/AGENTS.md +2 -0
  78. package/Tools/OMP_Global_Config/agent/agents/maker.md +3 -1
  79. package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +3 -1
  80. package/Tools/OMP_Global_Config/agent/rules/omp-harness.md +5 -0
  81. package/Tools/OMP_Global_Config/agent/rules/subagent.md +15 -0
  82. package/Tools/OMP_Global_Config/agent/sop/_writer.md +2 -0
  83. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +215 -0
  84. package/package.json +7 -7
  85. package/.next/static/chunks/6110-46b1b16915b39e5b.js +0 -1
  86. package/.next/static/chunks/9575-7c8d686e116aef58.js +0 -22
  87. /package/.next/static/{A6e8vaM_XsMnaMJbdHUo9 → do0UuZKXEeG2rQnEL-f1I}/_buildManifest.js +0 -0
  88. /package/.next/static/{A6e8vaM_XsMnaMJbdHUo9 → do0UuZKXEeG2rQnEL-f1I}/_ssgManifest.js +0 -0
package/.next/trace-build CHANGED
@@ -1 +1 @@
1
- [{"name":"run-webpack","duration":125300122,"timestamp":763339,"id":14,"parentId":1,"tags":{},"startTime":1791052269546,"traceId":"8b61a1aaeb5cf6ef"},{"name":"run-typescript","duration":24695083,"timestamp":126471168,"id":10956,"parentId":1,"tags":{},"startTime":1791052395255,"traceId":"8b61a1aaeb5cf6ef"},{"name":"static-check","duration":5325952,"timestamp":151300620,"id":10959,"parentId":1,"tags":{},"startTime":1791052420085,"traceId":"8b61a1aaeb5cf6ef"},{"name":"static-generation","duration":14494904,"timestamp":156762435,"id":11123,"parentId":1,"tags":{},"startTime":1791052425547,"traceId":"8b61a1aaeb5cf6ef"},{"name":"collect-build-traces","duration":23345648,"timestamp":156700225,"id":11120,"parentId":1,"tags":{},"startTime":1791052425484,"traceId":"8b61a1aaeb5cf6ef"},{"name":"telemetry-flush","duration":81,"timestamp":180050336,"id":11132,"parentId":1,"tags":{},"startTime":1791052448835,"traceId":"8b61a1aaeb5cf6ef"},{"name":"next-build","duration":179422470,"timestamp":627973,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1791052269410,"traceId":"8b61a1aaeb5cf6ef"}]
1
+ [{"name":"run-webpack","duration":69994735,"timestamp":644316,"id":14,"parentId":1,"tags":{},"startTime":1791076938405,"traceId":"3405c6181116366c"},{"name":"run-typescript","duration":12356324,"timestamp":70958136,"id":10956,"parentId":1,"tags":{},"startTime":1791077008718,"traceId":"3405c6181116366c"},{"name":"static-check","duration":3718671,"timestamp":83683029,"id":10959,"parentId":1,"tags":{},"startTime":1791077021443,"traceId":"3405c6181116366c"},{"name":"static-generation","duration":5699088,"timestamp":87477820,"id":11123,"parentId":1,"tags":{},"startTime":1791077025238,"traceId":"3405c6181116366c"},{"name":"collect-build-traces","duration":13451425,"timestamp":87457076,"id":11120,"parentId":1,"tags":{},"startTime":1791077025217,"traceId":"3405c6181116366c"},{"name":"telemetry-flush","duration":62,"timestamp":100912460,"id":11132,"parentId":1,"tags":{},"startTime":1791077038672,"traceId":"3405c6181116366c"},{"name":"next-build","duration":100339762,"timestamp":572780,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1791076938333,"traceId":"3405c6181116366c"}]
package/README.md CHANGED
@@ -16,7 +16,7 @@ Use the same `~/.omp/agent` sessions, credentials and model configuration from t
16
16
 
17
17
  **한국어:** CUELO는 omp(oh-my-pi)를 위한 오픈소스 AI 코딩 에이전트 작업 공간이자 하네스입니다. 웹 앱은 omp SDK를 서버 안에서 실행하고, `omp` CLI와 같은 `~/.omp/agent` 디렉터리를 씁니다. 터미널에서 하던 세션을 브라우저에서 이어 가고, 다시 터미널로 돌아가도 기록·계정·모델 설정을 공유합니다.
18
18
 
19
- 내장 OMP 코어는 **18.5.1**입니다. 추론 강도를 바꾸거나 서브에이전트를 다시 이어도 예비 모델 설정을 유지하며, 별도로 설치한 `omp` CLI의 버전은 웹 업데이트와 독립적입니다.
19
+ 내장 OMP 코어는 **18.6.0**입니다. 추론 강도를 바꾸거나 서브에이전트를 다시 이어도 예비 모델 설정을 유지하며, 별도로 설치한 `omp` CLI의 버전은 웹 업데이트와 독립적입니다.
20
20
 
21
21
  CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규칙·실행 절차에 반영해, 사람이 같은 문제를 다시 지적하지 않아도 다음 작업에서 실수를 줄이는 것입니다. 원인을 확인하고 필요한 부분만 바꾸며, 다음 작업에서 적용과 효과를 확인하는 것까지를 지향합니다. 기록을 저장했다는 이유만으로 자동 개선이 끝났다고 보지는 않습니다.
22
22
 
@@ -54,6 +54,8 @@ CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규
54
54
 
55
55
  - 계정별 사용량 카드: 한도 창, 이번 구간 실측, 리셋 권장을 한 화면에 보여 줍니다. 기록이 없으면 숫자를 지어내지 않고 「기록 부족」으로 표시합니다.
56
56
  - 계정은 이메일 대신 캐릭터 얼굴과 별칭으로 구별합니다. 주소를 상시 노출하지 않기 위해서입니다.
57
+ - 사이드바 아래 사용량에서 「순서 편집」을 누르면 계정마다 위로·아래로 버튼이 생깁니다. 한 계정의 한도는 함께 움직이고, 고른 순서는 이 브라우저에 저장돼 새로고침해도 유지됩니다. 순서는 화면 표시에만 적용되며, 처음 보는 계정은 맨 뒤에 붙습니다.
58
+ - 로그인이 만료되거나 쓸 수 없게 된 계정도 사용량에서 사라지지 않고 이유와 함께 남습니다. 다시 로그인해야 풀리는 계정에만 「재로그인」 버튼이 붙으며, 누르면 Models의 해당 제공자 로그인 화면이 대상 계정(얼굴·별칭·가린 주소)과 함께 열립니다. 로그인은 그 화면에서 직접 시작하고, 끝나면 사용량을 다시 읽어 같은 계정이 돌아왔을 때만 복구됐다고 알립니다. 다른 계정으로 로그인하면 원래 계정의 오류는 그대로 남습니다. 직접 꺼 둔 계정은 지금처럼 목록에 나오지 않습니다.
57
59
  - 효율 패널(Run X-Ray · Experiment Lab): `~/.omp/stats.db`와 세션 기록에 이미 쌓인 값만 읽어 실행별 병목과 구성 비교를 보여 줍니다.
58
60
 
59
61
  **여러 에이전트와 함께 일하기**
@@ -9,9 +9,9 @@ const MEMORY_RE = /(const phase1Model = await resolveMemoryModel\(\{[^}]*?fallba
9
9
  const MEMORY_DONE = /const phase1Model = await resolveMemoryModel\(\{[^}]*?fallbackRole: "smol"/;
10
10
  const EXPECTED = {
11
11
  name: 'cuelo',
12
- version: '0.8.3',
12
+ version: '0.9.0',
13
13
  distribution: 'cuelo',
14
- coreVersion: '18.5.1',
14
+ coreVersion: '18.6.0',
15
15
  };
16
16
 
17
17
  // OMP core 18.4.12 `src/sdk.ts`: session-scoped AsyncJobManager for a process
@@ -177,7 +177,7 @@ function readNativePackage(target) {
177
177
  throw new Error(`unexpected native package ${packageJson.name}@${packageJson.version}`);
178
178
  }
179
179
  if (!build || build.distribution !== EXPECTED.distribution || build.coreVersion !== EXPECTED.coreVersion) {
180
- throw new Error('native cueloBuild metadata does not match cuelo / core 18.5.1');
180
+ throw new Error('native cueloBuild metadata does not match cuelo / core 18.6.0');
181
181
  }
182
182
  return packageJson;
183
183
  }
@@ -171,6 +171,19 @@ function controlError(status, code, outcomeUnknown = false) {
171
171
  return Object.assign(new Error(code), { status, code, outcomeUnknown });
172
172
  }
173
173
 
174
+ // 인증 실패 tombstone의 사유를 화면용 코드로 줄인다. core의 원문 사유(provider 응답 조각)는 내보내지 않는다.
175
+ // 자리를 지키는 tombstone은 모두 인증 실패이므로 다시 로그인해야 쓸 수 있다.
176
+ function authError(cause) {
177
+ const text = String(cause ?? '');
178
+ if (/invalid_grant|refresh token (?:expired|not found|invalid)/i.test(text)) {
179
+ return { code: 'refresh_expired', message: '로그인이 만료되었습니다. 다시 로그인하세요.', reloginRequired: true };
180
+ }
181
+ if (/invalidated|revoked|unauthorized|\b401\b/i.test(text)) {
182
+ return { code: 'token_revoked', message: '인증이 취소되었습니다. 다시 로그인하세요.', reloginRequired: true };
183
+ }
184
+ return { code: 'auth_failed', message: '인증에 실패해 이 계정을 쓸 수 없습니다. 다시 로그인하세요.', reloginRequired: true };
185
+ }
186
+
174
187
  // 동일 storage의 reload/cache도 함께 직렬화한다. 외부 CLI의 mutation까지 잠그지는 않는다.
175
188
  function createControlFacade(getControl, invalidate = invalidateUsage) {
176
189
  let tail = Promise.resolve();
@@ -204,6 +217,17 @@ function createControlFacade(getControl, invalidate = invalidateUsage) {
204
217
  await storage.reload();
205
218
  const entries = storage.credentials.snapshot().credentials;
206
219
  const blocks = storage.blocks.list(entries.map(entry => entry.id));
220
+ // 계정 자리(oauthPosition)는 core가 정한다. 인증 실패로 비활성화된 계정도 재로그인 전까지 자기 자리를
221
+ // 지키므로, 그 tombstone을 오류 행으로 함께 낸다. 자리 API가 없는 core에서는 자리·오류 행을 내지 않는다.
222
+ const hasSeats = typeof storage.credentials.oauthSeatIds === 'function';
223
+ const tombstones = hasSeats && typeof storage.credentials.listDisabled === 'function'
224
+ ? await storage.credentials.listDisabled() : [];
225
+ const seats = new Map();
226
+ const seatOf = (provider, id) => {
227
+ if (!seats.has(provider)) seats.set(provider, storage.credentials.oauthSeatIds(provider));
228
+ const position = seats.get(provider).indexOf(id);
229
+ return position >= 0 ? { oauthPosition: position } : {};
230
+ };
207
231
  const result = entries.map(entry => {
208
232
  const providerKey = `${entry.provider}:${entry.credential.type}`;
209
233
  const own = blocks.filter(block => block.credentialId === entry.id);
@@ -216,8 +240,19 @@ function createControlFacade(getControl, invalidate = invalidateUsage) {
216
240
  ...pick(entry.credential, IDENTITY_KEYS),
217
241
  disabled: !!manual,
218
242
  autoBlockedUntilMs: auto.length ? Math.max(...auto.map(block => block.blockedUntilMs)) : null,
243
+ ...(hasSeats && entry.credential.type === 'oauth' ? seatOf(entry.provider, entry.id) : {}),
219
244
  };
220
245
  });
246
+ for (const tombstone of tombstones) {
247
+ if (tombstone.type !== 'oauth') continue;
248
+ const seat = seatOf(tombstone.provider, tombstone.id);
249
+ // 자리가 없는 tombstone은 교체·로그아웃으로 자리를 반납한 행이다. 사용자에게 할 일이 없다.
250
+ if (seat.oauthPosition === undefined) continue;
251
+ result.push({
252
+ credentialId: tombstone.id, provider: tombstone.provider, credentialType: 'oauth',
253
+ ...pick(tombstone, IDENTITY_KEYS), ...seat, authError: authError(tombstone.cause),
254
+ });
255
+ }
221
256
  if (currentEpoch !== epoch) return credentials(true);
222
257
  snapshot = { at: Date.now(), credentials: result };
223
258
  return result;
@@ -283,14 +318,16 @@ function createControlFacade(getControl, invalidate = invalidateUsage) {
283
318
  }
284
319
  if (startEpoch !== epoch) return enrich(raw);
285
320
  const joined = new Set();
321
+ // 인증 실패 행은 사용량 보고서와 결합하지 않는다. 같은 identity의 활성 계정 보고서를 가져가면 안 된다.
322
+ const usable = all.filter(credential => !credential.authError);
286
323
  for (const report of usage.reports) {
287
- const credential = matchCredential(report, all);
324
+ const credential = matchCredential(report, usable);
288
325
  if (!credential) {
289
326
  report.accountRole = 'usage-only';
290
327
  if (RESET_PROVIDERS.includes(report.provider)) report.savedReset = savedReset(undefined, Date.now(), report.provider);
291
328
  continue;
292
329
  }
293
- Object.assign(report, pick(credential, ['credentialId', 'disabled', 'autoBlockedUntilMs']));
330
+ Object.assign(report, pick(credential, ['credentialId', 'disabled', 'autoBlockedUntilMs', 'oauthPosition']));
294
331
  joined.add(credential.credentialId);
295
332
  if (RESET_PROVIDERS.includes(report.provider)) {
296
333
  const credits = creditsByProvider.get(report.provider);
@@ -301,11 +338,21 @@ function createControlFacade(getControl, invalidate = invalidateUsage) {
301
338
  }
302
339
  for (const credential of all) {
303
340
  if (joined.has(credential.credentialId) || !control().storage.usage.providerFor(credential.provider)) continue;
341
+ if (credential.authError) {
342
+ // 수동 OFF·쿨다운 상태와 섞지 않는다. 그 id의 block은 재로그인 뒤 다시 보인다.
343
+ usage.reports.push({
344
+ provider: credential.provider, fetchedAt: 0, limits: [],
345
+ accountRole: 'auth-error',
346
+ metadata: pick(credential, IDENTITY_KEYS),
347
+ ...pick(credential, ['credentialId', 'oauthPosition', 'authError']),
348
+ });
349
+ continue;
350
+ }
304
351
  usage.reports.push({
305
352
  provider: credential.provider, fetchedAt: 0, limits: [],
306
353
  accountRole: 'control-only',
307
354
  metadata: pick(credential, IDENTITY_KEYS),
308
- ...pick(credential, ['credentialId', 'disabled', 'autoBlockedUntilMs']),
355
+ ...pick(credential, ['credentialId', 'disabled', 'autoBlockedUntilMs', 'oauthPosition']),
309
356
  ...(RESET_PROVIDERS.includes(credential.provider) ? { savedReset: savedReset(
310
357
  creditsByProvider.get(credential.provider)?.statuses.find(status => status.credentialId === credential.credentialId),
311
358
  creditsByProvider.get(credential.provider)?.at, credential.provider) } : {}),
@@ -318,9 +365,15 @@ function createControlFacade(getControl, invalidate = invalidateUsage) {
318
365
  delete report.credentialId;
319
366
  delete report.disabled;
320
367
  delete report.autoBlockedUntilMs;
368
+ delete report.oauthPosition;
321
369
  if (RESET_PROVIDERS.includes(report.provider)) report.savedReset = savedReset(undefined, Date.now(), report.provider);
322
370
  }
323
371
  }
372
+ // 화면이 순서를 기억할 안정 키. credential id(없으면 provider 계정 id)로 만들고 email은 쓰지 않는다.
373
+ for (const report of usage.reports) {
374
+ if (Number.isSafeInteger(report.credentialId) && report.credentialId > 0) report.accountKey = `${report.provider}:${report.credentialId}`;
375
+ else if (report.metadata?.accountId) report.accountKey = `${report.provider}:acct:${report.metadata.accountId}`;
376
+ }
324
377
  return usage;
325
378
  }
326
379
  function setDisabled(id, disabled) {
@@ -71,6 +71,8 @@ Main은 확정 delta와 그 증거를 누적 검수하며, 최종 수용은 수
71
71
 
72
72
  Main은 사용자 요구·수용 조건·보존 동작·승인·관측 경로를 먼저 확정한다. 위임 브리프와 첫 조사에서 요구를 대조하는 절차는 `rule://verdict`·`rule://subagent` 및 `harness-policy.json` `briefContextRelay`·`routing.dispatchAssumptionCheck`를 따른다. `task` 발주 시 `TASK_GUARD` 뒤의 공유 메타 `TASK_TITLE`·`TODO_TASKS`도 `rule://task-guard`에 따라 전달한다. Skill은 설치만으로 열지 않고 이번 작업에 필요하거나 사용자가 요청했을 때만 연다. UI 작업은 `rule://frontend`를 따른다. 사용자가 명시 요청하지 않으면 `goal` 도구에 `token_budget`을 임의 설정하지 않는다.
73
73
 
74
+ Main이 활용한 관련 Skill을 Maker에게 넘기고 적용 여부를 확인하는 절차는 `rule://subagent` 「Skill 인계」를 따른다. 사용자가 다시 요청해야 발동하는 절차가 아니다.
75
+
74
76
  `systematic-debugging`·`verification-before-completion`처럼 절차만 담은 Skill은 원인을 모르는 버그, 두 번째 실패, 완료 판정이 모호할 때만 연다. 위치와 수정 방향이 과제에 이미 나온 수정은 기본 검증 절차로 충분하므로 선독하지 않는다.
75
77
 
76
78
  요청 한 번마다 전체 컨텍스트를 다시 싣는다. 서로 의존하지 않는 읽기·검색은 한 턴에 묶고, 수정 전에는 고칠 구간과 그 호출부·테스트를 함께 읽어 한 번에 맞게 고친다. edit이 "본 적 없는 줄"로 거절되면 안내대로 같은 edit을 그대로 다시 보내고 재읽기를 하지 않는다.
@@ -6,7 +6,7 @@ thinking-level: medium
6
6
  tools: [read, bash, edit, write, grep, glob, skim, lsp, eval, generate_image, ast_grep, ast_edit, debug, todo, web_search, checkpoint]
7
7
  ---
8
8
 
9
- <!-- omp-global-config:generated source-hash=4e5719c2a636
9
+ <!-- omp-global-config:generated source-hash=816f54313240
10
10
  이 파일은 patches/build-agents.mjs 가 만든 빌드 산출물이다. 직접 수정하지 마라.
11
11
  고칠 곳: agent/sop/maker.md · agent/sop/_writer.md · agent/sop/_common.md
12
12
  재생성: node patches/build-agents.mjs 검사: node patches/build-agents.mjs --check
@@ -76,6 +76,8 @@ is yours end to end.
76
76
  - Answer every finding that names your change with the fix you made and the raw output of the check
77
77
  that now covers it, keeping the same finding id — no separate messenger or new reporting stage.
78
78
  Never weaken an expectation, relax a fixture, or hide a failure to make validation pass.
79
+ - Follow `rule://subagent` 「Skill 인계」 for relevant skills handed over in the brief:
80
+ required direct reading before implementation, reuse of known locators, and reconsultation when unclear.
79
81
  - 관련 Skill을 먼저 읽고 이번 조치에 적용되는 주의사항만 이름·절/locator·위험 요약으로 추린다.
80
82
  pre-retry advisory를 보충하다가(Common Rules) 검증 약화나 Skill 충돌·적용 불가가 나오면 문제 된
81
83
  조치만 보류하고 기존 blocker/조향 DM으로 원문 오류 locator·Skill locator·제안 조치·보존할 수용
@@ -1,4 +1,4 @@
1
- <!-- source-fingerprint: 94b703423427496b -->
1
+ <!-- source-fingerprint: a3671e216eec0e9 -->
2
2
  # Global Agent Instructions
3
3
 
4
4
  (Model-facing English copy of the Korean source `AGENTS.md`. All user-facing prose stays Korean.)
@@ -74,6 +74,8 @@ Use Main-only `wait` (subagents have none) only at real dependency/synthesis bar
74
74
 
75
75
  Main first fixes user requirements, acceptance criteria, behavior to preserve, approvals, and the observation path. Checking the requirements in the delegation brief and first investigation: `rule://verdict`·`rule://subagent` and `harness-policy.json` `briefContextRelay`·`routing.dispatchAssumptionCheck`. When dispatching `task`, also pass the shared metadata `TASK_TITLE`·`TODO_TASKS` after `TASK_GUARD` per `rule://task-guard`. Open a Skill only when this task needs it or the user asks, not because it is installed. UI work follows `rule://frontend`. Never set `token_budget` on the `goal` tool unless the user explicitly asks.
76
76
 
77
+ Follow `rule://subagent` 「Skill 인계」 to hand the relevant Skills Main used to the Maker and verify their application. This applies without another user reminder.
78
+
77
79
  Open procedure-only Skills such as `systematic-debugging`·`verification-before-completion` only for a bug of unknown cause, a second failure, or an ambiguous completion verdict. When the task already names the location and fix direction, the default verification procedure suffices; do not pre-read them.
78
80
 
79
81
  Every request resends the full context. Batch independent reads/searches in one turn; before editing, read the section to change together with its callers and tests so the edit lands in one pass. If an edit is rejected for touching lines "never displayed", resend the same edit as instructed without re-reading.
@@ -53,6 +53,11 @@ globs: ["Tools/OMP_Global_Config/**", "Tools/CUELO_Setup/**"]
53
53
  목표는 최신판 추종이 아니라 upstream 개선을 가져오면서 기존 해결책과 고유 작업 계약을 보존하고,
54
54
  upstream이 해결한 workaround를 제거해 patch debt를 줄이는 것이다.
55
55
 
56
+ 사용자가 “최신으로 올려줘”라고만 해도 내장 코어의 새 API·상태·데이터 흐름에 맞춘 호출부 이관,
57
+ 기존 패치의 KEEP/ADAPT/RETIRE/BLOCK 판정, 회귀·실행 검증을 요청에 포함한다. 충돌 조정은 별도
58
+ 재지시를 기다리지 않는다. CLI 실행 파일 교체와 CUELO 내장 코어 이식의 완료 증거는 구분한다.
59
+ 이 기본 범위가 배포·삭제·비용·인증 변경의 별도 승인 경계를 낮추지는 않는다.
60
+
56
61
  ### 판단 정본과 upstream 조사
57
62
 
58
63
  현재 설치 버전만으로 판단하지 않는다. 다음 미러와 실제 Known-Good runtime을 먼저 대조한다.
@@ -295,6 +295,21 @@ Task Guard lock·budget·소유권·`FINDING_ID`, exit status, 파일·권한·
295
295
  - 조각 간 인터페이스·시그니처·파일 소유권·출력 형식은 배치 `context`에 미리 확정한다.
296
296
  - 짧은 결정·변경분과 artifact 링크로 충분하면 전체 대화나 상류 보고서를 복사하지 않는다.
297
297
 
298
+ ### Skill 인계
299
+
300
+ - Main은 자신이 읽고 활용한 Skill 중 맡길 조각과 관련된 것만 브리프에 인계한다. `skill://이름`과
301
+ 필요한 절·문서 locator, 적용한 판단, 아직 불명확한 점을 짧게 적는다. 원문 전체나 설치된 Skill
302
+ 목록을 복사하지 않는다.
303
+ - 현재 규칙·작업에 따라 필수인 참조와 선택적인 참고를 구분한다. Maker는 필수 Skill을 관련
304
+ 작업에 착수하기 전에 직접 읽는다. Main의 요약·원문을 읽었다는 설명은 Maker의 직접 참조를
305
+ 대체하지 않는다. 같은 조각에서 이미 읽은 원문과 locator는 재사용한다.
306
+ - 적용 방법이 헷갈리거나 Main의 요약과 현재 코드·규칙이 어긋나면 해당 Skill의 필요한 절을
307
+ 다시 확인한다. “헷갈릴 때 참조”는 필수 선독을 생략하는 조건이 아니다. 원문을 확인해도
308
+ 승인·보존 계약과 충돌하면 그 지점만 Main에게 근거와 함께 알리고 독립 작업은 계속한다.
309
+ - Maker는 적용한 판단과 해당 변경·검증 근거를 기존 보고에 연결한다. Main은 그 근거로
310
+ 적용 여부를 확인하며, 브리프에 이름을 적었다는 사실만으로 사용했다고 보고하지 않는다.
311
+ 이 절은 새 자동 차단 gate나 관계없는 Skill까지 읽는 의무를 만들지 않는다.
312
+
298
313
  ## 병렬과 대기
299
314
 
300
315
  - `task.maxConcurrency`는 ceiling이다. ready인 진짜 독립 조각을 빈 슬롯까지 `tasks[]` feed로
@@ -59,6 +59,8 @@
59
59
  - Answer every finding that names your change with the fix you made and the raw output of the check
60
60
  that now covers it, keeping the same finding id — no separate messenger or new reporting stage.
61
61
  Never weaken an expectation, relax a fixture, or hide a failure to make validation pass.
62
+ - Follow `rule://subagent` 「Skill 인계」 for relevant skills handed over in the brief:
63
+ required direct reading before implementation, reuse of known locators, and reconsultation when unclear.
62
64
  - 관련 Skill을 먼저 읽고 이번 조치에 적용되는 주의사항만 이름·절/locator·위험 요약으로 추린다.
63
65
  pre-retry advisory를 보충하다가(Common Rules) 검증 약화나 Skill 충돌·적용 불가가 나오면 문제 된
64
66
  조치만 보류하고 기존 blocker/조향 DM으로 원문 오류 locator·Skill locator·제안 조치·보존할 수용
@@ -8810,6 +8810,12 @@ function parentSubagentServiceTiers(
8810
8810
  marker: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||ERe(i))continue;let a=i.slice(0,-6),',
8811
8811
  anchor: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||ERe(i))continue;let a=i.slice(0,-6),',
8812
8812
  patched: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||ERe(i))continue;let a=i.slice(0,-6),',
8813
+ }, {
8814
+ // 18.6.0 번들의 같은 upstream 조건(_Re = isAdvisorTranscriptName) no-op. 이름만 바뀌었다.
8815
+ file: "dist/cli.js",
8816
+ marker: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||_Re(i))continue;let a=i.slice(0,-6),',
8817
+ anchor: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||_Re(i))continue;let a=i.slice(0,-6),',
8818
+ patched: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||_Re(i))continue;let a=i.slice(0,-6),',
8813
8819
  }],
8814
8820
  },
8815
8821
  // 18.4.5 는 /ratchet 을 새로 넣으면서 `src/ratchet/prelude.ts`(코드)와 `prelude.js`(eval 텍스트 자산)를 같은 stem 으로
@@ -9341,6 +9347,215 @@ class HarmonyLeakInterruption extends Error {`,
9341
9347
  anchor: "\t\tconst attribution = options?.attribution ?? \"user\";\n",
9342
9348
  patched: "\t\tconst attribution = options?.attribution ?? \"user\";\n\t\tif (mode !== \"aside\") this.#raiseAutoThinkingForQueuedInput(text, attribution);\n",
9343
9349
  },
9350
+ // CUELO 계정 자리(2026-10-04). upstream oauth.accounts()의 position은 활성 credential 배열 index라서,
9351
+ // 한 계정이 인증 실패로 비활성화되면 뒤 계정이 앞 자리로 당겨진다(RIN 자리 0이 비면 MIO 계정이 RIN이 된다).
9352
+ // 재로그인도 새 행을 만들고 tombstone을 지우므로 [11, 12]처럼 순서가 뒤집힌다. 자리 = (활성 OAuth id ∪
9353
+ // 인증 실패로 자리를 지키는 tombstone id) 오름차순 index로 바꾸고, 같은 identity의 재로그인은 그
9354
+ // tombstone 행을 같은 id로 되살린다. 로그아웃(deleted by user)·교체(replaced by)는 자리를 반납한다.
9355
+ // accounts()는 계속 활성 계정만 돌려주므로 position과 배열 index가 다를 수 있다.
9356
+ {
9357
+ file: "../pi-ai/src/auth/sqlite-credential-store.ts",
9358
+ marker: "function holdsOAuthSeat(row: AuthRow): boolean {",
9359
+ anchor: "\nfunction matchesReplacementCredential(\n",
9360
+ patched: `
9361
+ /**
9362
+ * CUELO: a tombstone that keeps its account seat (\`oauth.accounts\` position) — an OAuth row
9363
+ * disabled by an auth failure. Replacement and logout tombstones give their seat up.
9364
+ */
9365
+ function holdsOAuthSeat(row: AuthRow): boolean {
9366
+ return (
9367
+ row.credential_type !== "api_key" &&
9368
+ row.disabled_cause !== null &&
9369
+ !/^(replaced by|deleted by user)/i.test(row.disabled_cause)
9370
+ );
9371
+ }
9372
+
9373
+ function matchesReplacementCredential(
9374
+ `,
9375
+ },
9376
+ {
9377
+ file: "../pi-ai/src/auth/sqlite-credential-store.ts",
9378
+ marker: "\t\t\t// CUELO: an auth-failed account that logs in again revives its own tombstone in place",
9379
+ anchor: "\t\t\t\tthis.#deleteStmt.run(\"replaced by newer credential\", row.id);\n\t\t\t}\n\n\t\t\tif (targetId === null) {\n\t\t\t\tconst row = this.#insertStmt.get(\n",
9380
+ patched: `\t\t\t\tthis.#deleteStmt.run("replaced by newer credential", row.id);
9381
+ \t\t\t}
9382
+
9383
+ \t\t\t// CUELO: an auth-failed account that logs in again revives its own tombstone in place
9384
+ \t\t\t// instead of getting a new row, so its credential id, account seat and the session pins
9385
+ \t\t\t// that name it survive re-authentication. Upstream's replacement matcher decides identity:
9386
+ \t\t\t// another member of the same org never claims the row. Blocks on the id stay as they were.
9387
+ \t\t\tif (targetId === null && item.type === "oauth") {
9388
+ \t\t\t\tfor (const row of this.#listDisabledByProviderStmt.all(providerName) as AuthRow[]) {
9389
+ \t\t\t\t\tif (!holdsOAuthSeat(row)) continue;
9390
+ \t\t\t\t\tconst identityKey = resolveRowCredentialIdentityKey(providerName, row);
9391
+ \t\t\t\t\tif (!matchesReplacementCredential(providerName, deserializeCredential(row), identityKey, item)) continue;
9392
+ \t\t\t\t\tconst revived = this.#db
9393
+ \t\t\t\t\t\t.query(
9394
+ \t\t\t\t\t\t\t\`UPDATE auth_credentials SET credential_type = ?, data = ?, identity_key = ?, disabled_cause = NULL, updated_at = \${SQLITE_NOW_EPOCH} WHERE id = ? AND disabled_cause IS NOT NULL\`,
9395
+ \t\t\t\t\t\t)
9396
+ \t\t\t\t\t\t.run(serialized.credentialType, serialized.data, serialized.identityKey, row.id) as { changes: number };
9397
+ \t\t\t\t\tif (revived.changes > 0) {
9398
+ \t\t\t\t\t\ttargetId = row.id;
9399
+ \t\t\t\t\t\tbreak;
9400
+ \t\t\t\t\t}
9401
+ \t\t\t\t}
9402
+ \t\t\t}
9403
+
9404
+ \t\t\tif (targetId === null) {
9405
+ \t\t\t\tconst row = this.#insertStmt.get(
9406
+ `,
9407
+ },
9408
+ {
9409
+ file: "../pi-ai/src/auth/sqlite-credential-store.ts",
9410
+ marker: "\tlistOAuthSeatHolderIds(provider: string): number[] {",
9411
+ anchor: "\tasync listDisabledCredentials(provider?: string): Promise<DisabledCredentialSummary[]> {\n",
9412
+ patched: `\t/**
9413
+ \t * CUELO: ids of \`provider\` tombstones that keep their account seat — auth failures whose
9414
+ \t * identity no active row has taken over (the superseded-tombstone purge's own rule).
9415
+ \t */
9416
+ \tlistOAuthSeatHolderIds(provider: string): number[] {
9417
+ \t\tconst active: AuthCredential[] = [];
9418
+ \t\tfor (const row of this.#listActiveByProviderStmt.all(provider) as AuthRow[]) {
9419
+ \t\t\tconst credential = deserializeCredential(row);
9420
+ \t\t\tif (credential?.type === "oauth") active.push(credential);
9421
+ \t\t}
9422
+ \t\tconst ids: number[] = [];
9423
+ \t\tfor (const row of this.#listDisabledByProviderStmt.all(provider) as AuthRow[]) {
9424
+ \t\t\tif (!holdsOAuthSeat(row)) continue;
9425
+ \t\t\tconst credential = deserializeCredential(row);
9426
+ \t\t\tif (credential === null) continue;
9427
+ \t\t\tconst identityKey = resolveRowCredentialIdentityKey(provider, row);
9428
+ \t\t\tif (active.some(current => matchesReplacementCredential(provider, credential, identityKey, current))) continue;
9429
+ \t\t\tids.push(row.id);
9430
+ \t\t}
9431
+ \t\treturn ids;
9432
+ \t}
9433
+
9434
+ \tasync listDisabledCredentials(provider?: string): Promise<DisabledCredentialSummary[]> {
9435
+ `,
9436
+ },
9437
+ {
9438
+ file: "../pi-ai/src/auth/store.ts",
9439
+ marker: "\tlistOAuthSeatHolderIds?(provider: string): number[];",
9440
+ anchor: "\tlistDisabledCredentials?(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;\n",
9441
+ patched: "\tlistDisabledCredentials?(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;\n\t/** CUELO: ids of tombstones that keep their account seat. Local stores only; others keep upstream's compacted order. */\n\tlistOAuthSeatHolderIds?(provider: string): number[];\n",
9442
+ },
9443
+ {
9444
+ file: "../pi-ai/src/auth/pool.ts",
9445
+ marker: "\toauthSeatIds(provider: string): number[] {",
9446
+ anchor: "\t/**\n\t * Disabled credential tombstones for display surfaces (`omp usage`,\n\t * broker `GET /v1/credentials/disabled`). Empty when the backing store\n\t * keeps no tombstones or the remote broker predates the endpoint.\n\t */\n\tasync listDisabled(",
9447
+ patched: `\t/**
9448
+ \t * CUELO: the account seats of \`provider\` — active OAuth rows plus tombstones that keep their
9449
+ \t * seat, by id. \`oauth.accounts\` reports an account's index here as its \`position\`, so an
9450
+ \t * auth-failed account leaves its seat empty instead of shifting every later account.
9451
+ \t */
9452
+ \toauthSeatIds(provider: string): number[] {
9453
+ \t\tconst ids = new Set<number>();
9454
+ \t\tfor (const entry of this.entries(provider)) if (entry.credential.type === "oauth") ids.add(entry.id);
9455
+ \t\tfor (const id of this.#store.listOAuthSeatHolderIds?.(provider) ?? []) ids.add(id);
9456
+ \t\treturn [...ids].sort((a, b) => a - b);
9457
+ \t}
9458
+
9459
+ \t/**
9460
+ \t * Disabled credential tombstones for display surfaces (\`omp usage\`,
9461
+ \t * broker \`GET /v1/credentials/disabled\`). Empty when the backing store
9462
+ \t * keeps no tombstones or the remote broker predates the endpoint.
9463
+ \t */
9464
+ \tasync listDisabled(`,
9465
+ },
9466
+ {
9467
+ file: "../pi-ai/src/auth/types.ts",
9468
+ marker: "\toauthSeatIds(provider: string): number[];",
9469
+ anchor: "\tlistDisabled(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;\n",
9470
+ patched: "\tlistDisabled(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;\n\t/** CUELO: account seats by id — active OAuth rows plus auth-failed tombstones awaiting re-login. A seat's index is `OAuthAccountSummary.position`. */\n\toauthSeatIds(provider: string): number[];\n",
9471
+ },
9472
+ {
9473
+ file: "../pi-ai/src/auth/types.ts",
9474
+ marker: " * Returned by {@link AuthStorage.oauth.accounts}; `position` (0-based) is the\n * account seat.",
9475
+ anchor: " * Returned by {@link AuthStorage.oauth.accounts}; `position` (0-based) is the\n * selector accepted by {@link AuthStorage.oauth.accessById}.\n",
9476
+ patched: " * Returned by {@link AuthStorage.oauth.accounts}; `position` (0-based) is the\n * account seat. CUELO: an auth-failed account keeps its seat until it logs in again, so\n * positions can have gaps — select by `position` or `credentialId`, never by array index.\n",
9477
+ },
9478
+ {
9479
+ file: "../pi-ai/src/auth/types.ts",
9480
+ marker: "\t * order, WITHOUT refreshing any token. Each account's `position` (0-based) is its\n\t * account seat",
9481
+ anchor: "\t * order, WITHOUT refreshing any token. The array position (0-based) is the\n\t * selector accepted by {@link AuthStorage.oauth.accessById}; a \"pick the Nth\n\t * account\" UI should render `position + 1`.\n",
9482
+ patched: "\t * order, WITHOUT refreshing any token. Each account's `position` (0-based) is its\n\t * account seat; a \"pick the Nth account\" UI should render `position + 1`. CUELO: an\n\t * auth-failed account keeps its seat empty, so positions can have gaps.\n",
9483
+ },
9484
+ {
9485
+ file: "../pi-ai/src/auth/oauth.ts",
9486
+ marker: "\t * order, WITHOUT refreshing any token. Each account's `position` (0-based) is its\n\t * account seat",
9487
+ anchor: "\t * order, WITHOUT refreshing any token. The array position (0-based) is the\n\t * selector displayed by a \"pick the Nth account\" UI as `position + 1`.\n",
9488
+ patched: "\t * order, WITHOUT refreshing any token. Each account's `position` (0-based) is its\n\t * account seat, displayed by a \"pick the Nth account\" UI as `position + 1`. CUELO: an\n\t * auth-failed account keeps its seat empty, so positions can have gaps.\n",
9489
+ },
9490
+ {
9491
+ file: "../pi-ai/src/auth/oauth.ts",
9492
+ marker: "\t\tconst seats = this.#deps.pool.oauthSeatIds(provider);",
9493
+ anchor: "\t\treturn this.#getStoredOAuthSelections(provider).map((selection, position) => {\n\t\t\tconst active = selection.credentialId === activeCredentialId;\n\t\t\treturn {\n\t\t\t\tposition,\n",
9494
+ patched: `\t\t// CUELO: \`position\` is the account seat (CredentialPool.oauthSeatIds), not the index in this
9495
+ \t\t// active-only list, so a later account is never renumbered into an auth-failed account's seat.
9496
+ \t\tconst seats = this.#deps.pool.oauthSeatIds(provider);
9497
+ \t\treturn this.#getStoredOAuthSelections(provider).map(selection => {
9498
+ \t\t\tconst active = selection.credentialId === activeCredentialId;
9499
+ \t\t\treturn {
9500
+ \t\t\t\tposition: seats.indexOf(selection.credentialId),
9501
+ `,
9502
+ },
9503
+ {
9504
+ // 같은 org의 다른 사람 tombstone을 활성 계정이 숨기던 판정. upstream 주석("neither email nor
9505
+ // accountId contradicts")대로 email·accountId가 다르면 org가 같아도 다른 identity다.
9506
+ file: "src/cli/usage-cli.ts",
9507
+ marker: "\t\tif (summaryEmail && accountEmail) return summaryEmail === accountEmail;",
9508
+ anchor: "\t\tif (summaryEmail && accountEmail && summaryEmail === accountEmail) return true;\n\t\tif (summaryAccountId && accountAccountId && summaryAccountId === accountAccountId) return true;\n",
9509
+ patched: "\t\t// CUELO: a differing email or accountId is another person even inside one org (or a shared\n\t\t// Codex workspace id), so that member's auth failure stays visible.\n\t\tif (summaryEmail && accountEmail) return summaryEmail === accountEmail;\n\t\tif (summaryAccountId && accountAccountId) return summaryAccountId === accountAccountId;\n",
9510
+ },
9511
+ {
9512
+ // --list 가 position + 1 로 번호를 보여 주므로 --account 도 같은 번호(자리)로 고른다.
9513
+ file: "src/commands/token.ts",
9514
+ marker: "\t\t\t\tconst selected = n === undefined ? undefined : accounts.find(acct => acct.position === n - 1);",
9515
+ anchor: `\t\t\t\tconst n = flags.account;
9516
+ \t\t\t\tif (n === undefined || n < 1 || n > accounts.length) {
9517
+ \t\t\t\t\tprocess.stderr.write(
9518
+ \t\t\t\t\t\t\`\${chalk.red(\`Invalid --account \${n ?? "(missing)"}.\`)} Provider "\${providerName}" has \${accounts.length} OAuth account(s) (1-\${accounts.length}).\\n\`,
9519
+ \t\t\t\t\t);
9520
+ \t\t\t\t\tprocess.exitCode = 1;
9521
+ \t\t\t\t\treturn;
9522
+ \t\t\t\t}
9523
+ \t\t\t\tconst resolution = managedMcpOAuth
9524
+ \t\t\t\t\t? await resolveManagedMcpOAuthToken(authStorage, provider, {
9525
+ \t\t\t\t\t\t\tcredentialId: accounts[n - 1]?.credentialId,
9526
+ \t\t\t\t\t\t\tforceRefresh: flags["force-refresh"],
9527
+ \t\t\t\t\t\t})
9528
+ \t\t\t\t\t: await authStorage.oauth.accessById(provider, accounts[n - 1]!.credentialId, {
9529
+ `,
9530
+ patched: `\t\t\t\tconst n = flags.account;
9531
+ \t\t\t\t// CUELO: account numbers are seats (\`position + 1\`, as --list prints them); an auth-failed
9532
+ \t\t\t\t// account leaves its number empty instead of shifting the others.
9533
+ \t\t\t\tconst selected = n === undefined ? undefined : accounts.find(acct => acct.position === n - 1);
9534
+ \t\t\t\tif (n === undefined || selected === undefined) {
9535
+ \t\t\t\t\tprocess.stderr.write(
9536
+ \t\t\t\t\t\t\`\${chalk.red(\`Invalid --account \${n ?? "(missing)"}.\`)} Provider "\${providerName}" has OAuth account(s) \${accounts.map(acct => acct.position + 1).join(", ")}.\\n\`,
9537
+ \t\t\t\t\t);
9538
+ \t\t\t\t\tprocess.exitCode = 1;
9539
+ \t\t\t\t\treturn;
9540
+ \t\t\t\t}
9541
+ \t\t\t\tconst resolution = managedMcpOAuth
9542
+ \t\t\t\t\t? await resolveManagedMcpOAuthToken(authStorage, provider, {
9543
+ \t\t\t\t\t\t\tcredentialId: selected.credentialId,
9544
+ \t\t\t\t\t\t\tforceRefresh: flags["force-refresh"],
9545
+ \t\t\t\t\t\t})
9546
+ \t\t\t\t\t: await authStorage.oauth.accessById(provider, selected.credentialId, {
9547
+ `,
9548
+ },
9549
+ {
9550
+ // 첫 줄이 read 예산보다 크면 hashline 모드는 그 줄을 한 바이트도 내지 않고 거부 문구만 낸다. 그런데
9551
+ // upstream은 preview 크기를 전달량으로 보고해 "[Showing line 1 (partial, 150.0KB of 195.3KB)]"처럼
9552
+ // 모델이 받은 적 없는 150KB를 받았다고 말한다(2026-10-04 실측). 전달량은 본문에 실제로 실린 것만 센다.
9553
+ // :raw·hashline을 끈 모드의 preview, 재개 offset 미제공, 수집 예산은 그대로다.
9554
+ file: "src/tools/read.ts",
9555
+ marker: "\t\t\t\t\t// CUELO: hashline mode refuses the oversized line outright",
9556
+ anchor: "\t\t\t\t\tconst previewBytes = firstLineExceedsLimit ? (firstLinePreview?.bytes ?? 0) : 0;\n",
9557
+ patched: "\t\t\t\t\t// CUELO: hashline mode refuses the oversized line outright and delivers none of it, so its\n\t\t\t\t\t// notice must report 0 bytes shown, not the preview it never rendered.\n\t\t\t\t\tconst previewBytes =\n\t\t\t\t\t\tfirstLineExceedsLimit && (rawSelector || !displayMode.hashLines) ? (firstLinePreview?.bytes ?? 0) : 0;\n",
9558
+ },
9344
9559
  ];
9345
9560
  // EDITS 문자열의 줄 끝을 LF로 통일한다. 이 파일의 작업 사본이 CRLF여도 core 파일(LF)과
9346
9561
  // 비교·치환이 어긋나지 않는다. core 파일 자체의 줄 끝은 건드리지 않는다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cuelo",
3
- "version": "0.8.3",
3
+ "version": "0.9.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.5.1"
33
+ "coreVersion": "18.6.0"
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.5.1",
92
- "@oh-my-pi/pi-ai": "18.5.1",
93
- "@oh-my-pi/pi-coding-agent": "18.5.1",
94
- "@oh-my-pi/pi-tui": "18.5.1",
95
- "@oh-my-pi/pi-utils": "18.5.1",
91
+ "@oh-my-pi/pi-agent-core": "18.6.0",
92
+ "@oh-my-pi/pi-ai": "18.6.0",
93
+ "@oh-my-pi/pi-coding-agent": "18.6.0",
94
+ "@oh-my-pi/pi-tui": "18.6.0",
95
+ "@oh-my-pi/pi-utils": "18.6.0",
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",