cuelo 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -9
  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 +2 -2
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/.next/server/app/api/agent/[id]/account/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/account/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  28. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  29. package/.next/server/app/api/agent/[id]/route.js +1 -1
  30. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  31. package/.next/server/app/api/agent/new/route.js +1 -1
  32. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  33. package/.next/server/app/api/agent/running/route.js +1 -1
  34. package/.next/server/app/api/agent/running/route.js.nft.json +1 -1
  35. package/.next/server/app/api/gpt6/handles/route.js +1 -1
  36. package/.next/server/app/api/gpt6/handles/route.js.nft.json +1 -1
  37. package/.next/server/app/api/gpt6/reply/route.js +1 -1
  38. package/.next/server/app/api/gpt6/reply/route.js.nft.json +1 -1
  39. package/.next/server/app/api/gpt6/token/route.js +1 -1
  40. package/.next/server/app/api/gpt6/token/route.js.nft.json +1 -1
  41. package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
  42. package/.next/server/app/api/gpt6/web6-session/route.js.nft.json +1 -1
  43. package/.next/server/app/api/live/call/[callId]/route.js +1 -1
  44. package/.next/server/app/api/live/call/[callId]/route.js.nft.json +1 -1
  45. package/.next/server/app/api/live/events/route.js +2 -2
  46. package/.next/server/app/api/live/events/route.js.nft.json +1 -1
  47. package/.next/server/app/api/live/offer/route.js +1 -1
  48. package/.next/server/app/api/live/offer/route.js.nft.json +1 -1
  49. package/.next/server/app/api/live/voices/route.js +1 -1
  50. package/.next/server/app/api/live/voices/route.js.nft.json +1 -1
  51. package/.next/server/app/api/lounge/events/route.js +2 -2
  52. package/.next/server/app/api/lounge/events/route.js.nft.json +1 -1
  53. package/.next/server/app/api/lounge/route.js +1 -1
  54. package/.next/server/app/api/lounge/route.js.nft.json +1 -1
  55. package/.next/server/app/api/project-trust/route.js +1 -1
  56. package/.next/server/app/api/project-trust/route.js.nft.json +1 -1
  57. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  58. package/.next/server/app/api/sessions/[id]/auto-name/route.js.nft.json +1 -1
  59. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  60. package/.next/server/app/api/sessions/[id]/context/route.js.nft.json +1 -1
  61. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  62. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  63. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  64. package/.next/server/app/api/sessions/[id]/state/route.js.nft.json +1 -1
  65. package/.next/server/app/api/sessions/route.js +1 -1
  66. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  67. package/.next/server/app/api/tool-image/route.js +1 -1
  68. package/.next/server/app/api/tool-image/route.js.nft.json +1 -1
  69. package/.next/server/app/api/update-maintenance/route.js +3 -3
  70. package/.next/server/app/api/update-maintenance/route.js.nft.json +1 -1
  71. package/.next/server/app/api/updates/route.js +1 -1
  72. package/.next/server/app/index.html +1 -1
  73. package/.next/server/app/index.rsc +4 -4
  74. package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
  75. package/.next/server/app/index.segments/_full.segment.rsc +4 -4
  76. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  77. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  78. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  79. package/.next/server/app/page.js +4 -4
  80. package/.next/server/app/page_client-reference-manifest.js +1 -1
  81. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/recover.html +1 -1
  83. package/.next/server/app/recover.rsc +2 -2
  84. package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
  85. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  86. package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
  87. package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
  88. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  89. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  90. package/.next/server/app-paths-manifest.json +9 -9
  91. package/.next/server/chunks/1517.js +3 -3
  92. package/.next/server/chunks/3707.js +2 -2
  93. package/.next/server/chunks/6429.js +3 -3
  94. package/.next/server/middleware-build-manifest.js +1 -1
  95. package/.next/server/pages/404.html +1 -1
  96. package/.next/server/pages/500.html +1 -1
  97. package/.next/server/server-reference-manifest.json +1 -1
  98. package/.next/static/chunks/app/{layout-4e015d86866518f5.js → layout-0cd3ee1b7ad71a56.js} +1 -1
  99. package/.next/static/chunks/app/page-42bb9e939eef51e6.js +22 -0
  100. package/.next/trace +105 -105
  101. package/.next/trace-build +1 -1
  102. package/README.md +9 -2
  103. package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
  104. package/Tools/CUELO_Setup/files/usage-server.js +18 -1
  105. package/Tools/OMP_Global_Config/agent/RULES.md +2 -1
  106. package/Tools/OMP_Global_Config/agent/agents/maker.md +25 -42
  107. package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +7 -3
  108. package/Tools/OMP_Global_Config/agent/extensions/skim.ts +9 -2
  109. package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +3 -2
  110. package/Tools/OMP_Global_Config/agent/sop/_common.md +14 -23
  111. package/Tools/OMP_Global_Config/agent/sop/_writer.md +10 -18
  112. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +585 -8
  113. package/package.json +7 -7
  114. package/.next/static/chunks/app/page-1933216030ad4353.js +0 -22
  115. /package/.next/static/{jnfhHGBsVK-Y8Lt6wq9XJ → 3-TNbqZBhQBsgN3SMAKv1}/_buildManifest.js +0 -0
  116. /package/.next/static/{jnfhHGBsVK-Y8Lt6wq9XJ → 3-TNbqZBhQBsgN3SMAKv1}/_ssgManifest.js +0 -0
@@ -341,7 +341,7 @@ const EDITS = [
341
341
  },
342
342
  {
343
343
  file: "../pi-agent-core/src/agent.ts",
344
- marker: "for (const listener of this.#userSteeringListeners) listener();",
344
+ marker: "for (const listener of this.#userSteeringListeners) listener();\n\t\t}\n\t\tthis.#notifySteeringWaiters();\n\t}",
345
345
  anchor: ` steer(m: AgentMessage) {
346
346
  this.#steeringQueue.push(m);
347
347
  this.#notifySteeringWaiters();
@@ -353,6 +353,25 @@ const EDITS = [
353
353
  }
354
354
  this.#notifySteeringWaiters();
355
355
  }`,
356
+ // 18.4.4(#11872): steer() 가 큐 mutator 마다 `#emitQueueChanged()` 를 정확히 한 번 부른다. 사용자 steer 리스너는
357
+ // 큐 push 직후·notifyWaiters 이전이라는 위치만 그대로 두고 upstream 의 emit 은 건드리지 않는다.
358
+ alternates: [{
359
+ file: "../pi-agent-core/src/agent.ts",
360
+ marker: "for (const listener of this.#userSteeringListeners) listener();\n\t\t}\n\t\tthis.#notifySteeringWaiters();\n\t\tthis.#emitQueueChanged();",
361
+ anchor: ` steer(m: AgentMessage) {
362
+ this.#steeringQueue.push(m);
363
+ this.#notifySteeringWaiters();
364
+ this.#emitQueueChanged();
365
+ }`,
366
+ patched: ` steer(m: AgentMessage) {
367
+ this.#steeringQueue.push(m);
368
+ if (m.role === "user" && ("attribution" in m ? m.attribution !== "agent" : true) && !("synthetic" in m && m.synthetic === true)) {
369
+ for (const listener of this.#userSteeringListeners) listener();
370
+ }
371
+ this.#notifySteeringWaiters();
372
+ this.#emitQueueChanged();
373
+ }`,
374
+ }],
356
375
  },
357
376
  {
358
377
  file: "../pi-agent-core/src/agent-loop.ts",
@@ -5308,6 +5327,12 @@ export class LearnTool implements AgentTool<LearnSchema> {`,
5308
5327
  marker: "// HANSE: env without a service name exports into the command\n\t\t\tpendingNotices.push(",
5309
5328
  anchor: "\t\t} else if (ready !== undefined || env !== undefined) {\n\t\t\t// Nothing can honour ready/env without a service to attach them to;\n\t\t\t// running the command the caller did ask for beats failing the call.\n\t\t\tconst ignored = [ready && \"ready\", env && \"env\"].filter(Boolean).join(\" and \");\n\t\t\tpendingNotices.push(`Ignored ${ignored}: service-only, and no service name was given.`);\n",
5310
5329
  patched: "\t\t} else if (ready !== undefined) {\n\t\t\t// HANSE: env without a service name exports into the command\n\t\t\tpendingNotices.push(\"Ignored ready: service-only, and no service name was given.\");\n",
5330
+ }, {
5331
+ // 18.4.4: upstream 이 env 파라미터를 지우면서 알림은 ready 만 남았다. 이미 우리가 바라는 형태라 적용할 것이 없다.
5332
+ file: "src/tools/bash.ts",
5333
+ marker: "\t\t} else if (ready !== undefined) {\n\t\t\t// Nothing can honour ready without a service to attach it to;\n",
5334
+ anchor: "\t\t} else if (ready !== undefined) {\n\t\t\t// Nothing can honour ready without a service to attach it to;\n",
5335
+ patched: "\t\t} else if (ready !== undefined) {\n\t\t\t// Nothing can honour ready without a service to attach it to;\n",
5311
5336
  }],
5312
5337
  },
5313
5338
  {
@@ -5316,8 +5341,8 @@ export class LearnTool implements AgentTool<LearnSchema> {`,
5316
5341
  marker: "// HANSE: non-service env becomes a leading export",
5317
5342
  anchor: "\t\tinvalidateGithubCacheForBashCommand(command);\n",
5318
5343
  patched: ` invalidateGithubCacheForBashCommand(command);
5319
- // HANSE: non-service env becomes a leading export
5320
- if (name === undefined && env) {
5344
+ // HANSE: non-service env becomes a leading export (18.4.4: service env too — upstream dropped launch env)
5345
+ if (env) {
5321
5346
  const exports: string[] = [];
5322
5347
  for (const [key, value] of Object.entries(env)) {
5323
5348
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new ToolError("Invalid env name: " + key);
@@ -5327,11 +5352,94 @@ export class LearnTool implements AgentTool<LearnSchema> {`,
5327
5352
  }
5328
5353
  `,
5329
5354
  },
5355
+ // 18.4.4 는 bash 도구의 `env` 파라미터를 통째로 지웠다(pi-coding-agent CHANGELOG "Removed"). env 는 사용자의 기존 도구
5356
+ // 계약이라 upstream 삭제 hunk 를 되돌린다(아래 여섯 항목). 각 항목의 본 후보는 18.4.4 원문에 `// HANSE: env restored` 를
5357
+ // 붙여 복원하고, alternate 는 env 가 원래 있는 18.4.3 원문의 no-op 이다. 18.4.3 에서 --revert 가 upstream 의
5358
+ // env 코드를 지우지 않도록 두 후보의 marker 가 서로의 결과에 들어 있지 않게 나눴다.
5359
+ // service 는 launch 가 env 를 받지 않으므로 위 export 항목이 service·일반 명령 모두 명령 앞 export 로 넣는다.
5360
+ {
5361
+ file: "src/tools/bash.ts",
5362
+ marker: "\t\"env?\": type.record(\"string\", \"string\"), // HANSE: env restored\n});\n\nconst bashSchemaWithAsyncAndService = type({",
5363
+ anchor: "\t}),\n});\n\nconst bashSchemaWithAsyncAndService = type({",
5364
+ patched: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"), // HANSE: env restored\n});\n\nconst bashSchemaWithAsyncAndService = type({",
5365
+ alternates: [{
5366
+ file: "src/tools/bash.ts",
5367
+ marker: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"),\n});\n\nconst bashSchemaWithAsyncAndService = type({",
5368
+ anchor: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"),\n});\n\nconst bashSchemaWithAsyncAndService = type({",
5369
+ patched: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"),\n});\n\nconst bashSchemaWithAsyncAndService = type({",
5370
+ }],
5371
+ },
5372
+ {
5373
+ file: "src/tools/bash.ts",
5374
+ marker: "\t\"env?\": type.record(\"string\", \"string\"), // HANSE: env restored\n});\n\ntype BashToolSchema =",
5375
+ anchor: "\t}),\n});\n\ntype BashToolSchema =",
5376
+ patched: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"), // HANSE: env restored\n});\n\ntype BashToolSchema =",
5377
+ alternates: [{
5378
+ file: "src/tools/bash.ts",
5379
+ marker: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"),\n});\n\ntype BashToolSchema =",
5380
+ anchor: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"),\n});\n\ntype BashToolSchema =",
5381
+ patched: "\t}),\n\t\"env?\": type.record(\"string\", \"string\"),\n});\n\ntype BashToolSchema =",
5382
+ }],
5383
+ },
5384
+ {
5385
+ file: "src/tools/bash.ts",
5386
+ marker: "\tenv?: Record<string, string>; // HANSE: env restored\n",
5387
+ anchor: "\tready?: ServiceReady;\n\tasync?: boolean;",
5388
+ patched: "\tready?: ServiceReady;\n\tenv?: Record<string, string>; // HANSE: env restored\n\tasync?: boolean;",
5389
+ alternates: [{
5390
+ file: "src/tools/bash.ts",
5391
+ marker: "\tready?: ServiceReady;\n\tenv?: Record<string, string>;\n\tasync?: boolean;",
5392
+ anchor: "\tready?: ServiceReady;\n\tenv?: Record<string, string>;\n\tasync?: boolean;",
5393
+ patched: "\tready?: ServiceReady;\n\tenv?: Record<string, string>;\n\tasync?: boolean;",
5394
+ }],
5395
+ },
5396
+ {
5397
+ file: "src/tools/bash.ts",
5398
+ marker: "/** HANSE: env restored. Drops a record with no keys",
5399
+ anchor: "\treturn { log, host, port, timeout };\n}\n\nexport interface BashToolOptions {}",
5400
+ patched: "\treturn { log, host, port, timeout };\n}\n\n/** HANSE: env restored. Drops a record with no keys: `env: {}` sets nothing, so it requests nothing. */\nfunction nonEmptyRecord(record: Record<string, string> | undefined): Record<string, string> | undefined {\n\tif (!record) return undefined;\n\tfor (const _key in record) return record;\n\treturn undefined;\n}\n\nexport interface BashToolOptions {}",
5401
+ alternates: [{
5402
+ file: "src/tools/bash.ts",
5403
+ marker: "/** Drops a record with no keys: `env: {}` sets nothing, so it requests nothing. */\nfunction nonEmptyRecord(record: Record<string, string> | undefined)",
5404
+ anchor: "/** Drops a record with no keys: `env: {}` sets nothing, so it requests nothing. */\nfunction nonEmptyRecord(record: Record<string, string> | undefined)",
5405
+ patched: "/** Drops a record with no keys: `env: {}` sets nothing, so it requests nothing. */\nfunction nonEmptyRecord(record: Record<string, string> | undefined)",
5406
+ }],
5407
+ },
5408
+ {
5409
+ file: "src/tools/bash.ts",
5410
+ marker: "\t\t\tenv: rawEnv, // HANSE: env restored\n",
5411
+ anchor: "\t\t\tready: rawReady,\n\t\t\tasync: rawAsync,",
5412
+ patched: "\t\t\tready: rawReady,\n\t\t\tenv: rawEnv, // HANSE: env restored\n\t\t\tasync: rawAsync,",
5413
+ alternates: [{
5414
+ file: "src/tools/bash.ts",
5415
+ marker: "\t\t\tready: rawReady,\n\t\t\tenv: rawEnv,\n\t\t\tasync: rawAsync,",
5416
+ anchor: "\t\t\tready: rawReady,\n\t\t\tenv: rawEnv,\n\t\t\tasync: rawAsync,",
5417
+ patched: "\t\t\tready: rawReady,\n\t\t\tenv: rawEnv,\n\t\t\tasync: rawAsync,",
5418
+ }],
5419
+ },
5420
+ {
5421
+ file: "src/tools/bash.ts",
5422
+ marker: "\t\tconst env = nonEmptyRecord(rawEnv); // HANSE: env restored\n",
5423
+ anchor: "\t\tconst ready = normalizeReady(rawReady);\n\t\tconst asyncRequested = rawAsync === true;\n",
5424
+ patched: "\t\tconst ready = normalizeReady(rawReady);\n\t\tconst env = nonEmptyRecord(rawEnv); // HANSE: env restored\n\t\tconst asyncRequested = rawAsync === true;\n",
5425
+ alternates: [{
5426
+ file: "src/tools/bash.ts",
5427
+ marker: "\t\tconst ready = normalizeReady(rawReady);\n\t\tconst env = nonEmptyRecord(rawEnv);\n",
5428
+ anchor: "\t\tconst ready = normalizeReady(rawReady);\n\t\tconst env = nonEmptyRecord(rawEnv);\n",
5429
+ patched: "\t\tconst ready = normalizeReady(rawReady);\n\t\tconst env = nonEmptyRecord(rawEnv);\n",
5430
+ }],
5431
+ },
5330
5432
  {
5331
5433
  file: "src/prompts/tools/bash.md",
5332
5434
  marker: "ready requires name; no async/timeout. env adds variables (without name: exported before the command)",
5333
5435
  anchor: "unique name; ready/env require name; no async/timeout. env adds variables;",
5334
5436
  patched: "unique name; ready requires name; no async/timeout. env adds variables (without name: exported before the command);",
5437
+ alternates: [{
5438
+ file: "src/prompts/tools/bash.md",
5439
+ marker: "env adds variables (exported before the command)",
5440
+ anchor: "unique name; ready requires name; no async/timeout; pty defaults true.",
5441
+ patched: "unique name; ready requires name; no async/timeout. env adds variables (exported before the command); pty defaults true.",
5442
+ }],
5335
5443
  },
5336
5444
  {
5337
5445
  // 같은 집계: `todo` append 에 phase 를 빠뜨리면 거절됐다(사흘간 9건). phase 가 없으면 아직 안 끝난
@@ -5589,9 +5697,15 @@ function raiseToAutoThinkingFloor(model: Model, level: Effort | undefined, setti
5589
5697
  // 조건이 하나도 걸리지 않아 모든 창이 일치했고("multiple windows match 65822"), `title: /정규식/`은
5590
5698
  // 안내 없이 TypeError로 죽었다. 필터 값은 문자열만 받고, 아니면 무엇이 틀렸는지 말하는 ToolError를 낸다.
5591
5699
  file: "src/tools/computer/worker.ts",
5592
- marker: "function assertWindowFilter(filter: unknown)",
5700
+ // 18.4.3: matchesFilter 가 `window.id === filter.id` 라 숫자 id 필터는 어떤 창과도 일치하지 않는다.
5701
+ // 그래서 이 버전에서는 숫자 id 필터도 거절하는 원래 검사를 유지한다(아래 alternate 는 18.4.4 전용).
5702
+ marker: "\"exact window id\" : \"case-insensitive substring\"",
5593
5703
  anchor: `function matchesFilter(window: DesktopWindow, filter?: WindowFilter): boolean {
5594
- if (!filter) return true;`,
5704
+ if (!filter) return true;
5705
+ const app = filter.app?.toLocaleLowerCase();
5706
+ const title = filter.title?.toLocaleLowerCase();
5707
+ return (
5708
+ (filter.id === undefined || window.id === filter.id) &&`,
5595
5709
  patched: `/** A filter is a plain \`{ id?, app?, title? }\` of strings; anything else used to match every window. */
5596
5710
  function assertWindowFilter(filter: unknown): asserts filter is WindowFilter | undefined {
5597
5711
  if (filter === undefined) return;
@@ -5608,7 +5722,45 @@ function assertWindowFilter(filter: unknown): asserts filter is WindowFilter | u
5608
5722
  }
5609
5723
 
5610
5724
  function matchesFilter(window: DesktopWindow, filter?: WindowFilter): boolean {
5611
- if (!filter) return true;`,
5725
+ if (!filter) return true;
5726
+ const app = filter.app?.toLocaleLowerCase();
5727
+ const title = filter.title?.toLocaleLowerCase();
5728
+ return (
5729
+ (filter.id === undefined || window.id === filter.id) &&`,
5730
+ // 18.4.4(#13649): matchesFilter 가 `String(filter.id)` 로 정규화한다. 안전한 정수 id 는 문자열 id 와 같은 창이라
5731
+ // 허용하고, 그 밖의 값(정규식·객체·소수 등)만 거절한다.
5732
+ alternates: [{
5733
+ file: "src/tools/computer/worker.ts",
5734
+ marker: "Number.isSafeInteger(value)) continue;",
5735
+ anchor: `function matchesFilter(window: DesktopWindow, filter?: WindowFilter): boolean {
5736
+ if (!filter) return true;
5737
+ const app = filter.app?.toLocaleLowerCase();
5738
+ const title = filter.title?.toLocaleLowerCase();
5739
+ return (
5740
+ (filter.id === undefined || window.id === String(filter.id)) &&`,
5741
+ patched: `/** A filter is a plain \`{ id?, app?, title? }\`; string app/title and a string or integer id. Anything else used to match every window. */
5742
+ function assertWindowFilter(filter: unknown): asserts filter is WindowFilter | undefined {
5743
+ if (filter === undefined) return;
5744
+ if (filter === null || typeof filter !== "object" || Array.isArray(filter)) {
5745
+ throw new ToolError(\`window filter must be an object like { app?, title?, id? }; got \${filter === null ? "null" : typeof filter}\`);
5746
+ }
5747
+ for (const key of ["id", "app", "title"] as const) {
5748
+ const value = (filter as Record<string, unknown>)[key];
5749
+ if (key === "id" && typeof value === "number" && Number.isSafeInteger(value)) continue;
5750
+ if (value !== undefined && typeof value !== "string") {
5751
+ const got = value instanceof RegExp ? \`RegExp \${value}\` : typeof value;
5752
+ throw new ToolError(\`window filter \${key} must be a string (\${key === "id" ? "exact window id, or an integer" : "case-insensitive substring"}); got \${got}\`);
5753
+ }
5754
+ }
5755
+ }
5756
+
5757
+ function matchesFilter(window: DesktopWindow, filter?: WindowFilter): boolean {
5758
+ if (!filter) return true;
5759
+ const app = filter.app?.toLocaleLowerCase();
5760
+ const title = filter.title?.toLocaleLowerCase();
5761
+ return (
5762
+ (filter.id === undefined || window.id === String(filter.id)) &&`,
5763
+ }],
5612
5764
  },
5613
5765
  {
5614
5766
  // 숫자 id는 창 id(숫자 문자열)와 뜻이 하나뿐이라 문자열로 바꿔 id로 찾는다. 그 밖의 값은 위에서 거절한다.
@@ -5626,6 +5778,23 @@ function matchesFilter(window: DesktopWindow, filter?: WindowFilter): boolean {
5626
5778
  typeof byId === "string"
5627
5779
  ? windows.filter(window => window.id === byId)
5628
5780
  : windows.filter(window => matchesFilter(window, byId));`,
5781
+ // 18.4.4: upstream(#13649)이 숫자 id 를 `String(selector)` 로 직접 푼다. 숫자 변환은 upstream 것을 쓰고, 우리는
5782
+ // 객체 필터(null·배열·정규식 값)를 거절하는 검사만 남긴다.
5783
+ alternates: [{
5784
+ file: "src/tools/computer/worker.ts",
5785
+ marker: "if (typeof selector !== \"string\" && typeof selector !== \"number\") assertWindowFilter(selector);",
5786
+ anchor: ` const windows = await nativeCall(signal, () => session.listWindows());
5787
+ const matches =
5788
+ typeof selector === "string" || typeof selector === "number"
5789
+ ? windows.filter(window => window.id === String(selector))
5790
+ : windows.filter(window => matchesFilter(window, selector));`,
5791
+ patched: ` if (typeof selector !== "string" && typeof selector !== "number") assertWindowFilter(selector);
5792
+ const windows = await nativeCall(signal, () => session.listWindows());
5793
+ const matches =
5794
+ typeof selector === "string" || typeof selector === "number"
5795
+ ? windows.filter(window => window.id === String(selector))
5796
+ : windows.filter(window => matchesFilter(window, selector));`,
5797
+ }],
5629
5798
  },
5630
5799
  {
5631
5800
  file: "src/tools/computer/worker.ts",
@@ -6618,6 +6787,193 @@ interface ComputerInputTarget {
6618
6787
  anchor: "- Each window `.ax()` starts a ref generation. Current/previous snapshot refs remain valid; older refs throw `StaleRef`. Re-snapshot; NEVER guess.\n",
6619
6788
  patched: "- Each window `.ax()` starts a ref generation. Current/previous snapshot refs remain valid. An older ref is reacquired only when its saved fingerprint (role, name, AutomationId, named parents, position) matches exactly one element; the element's `ref` changes and the action result carries `reacquired`. Otherwise it throws `StaleRef` naming why and the candidates: re-snapshot; NEVER guess.\n- Actions return `{ action, status, suggestedNext, evidence?, escalation?, reacquired? }`. `verified` rests on an AX readback; `unverified` (all pointer/keyboard input) means delivered but unobserved: `reobserve` before relying on it; `suspected_noop` → follow `escalation`. Inside `computer.run`, a thrown action error carries `error.computerAction`.\n",
6620
6789
  },
6790
+ {
6791
+ // 2026-09-30 computer 목표 대기(사용자 요청 0.6.6): 행동은 사용자 코드에서 한 번, 목표는 wait(predicate)가 로컬 읽기로만 확인한다.
6792
+ // 이 편집부터 worker.ts 7개는 한 묶음이다. probe 는 run context 를 복제해 goalProbe 를 켜고, 그 signal 은 deadline·wait 종료에도 끝난다.
6793
+ // 간격은 Playwright pollAgainstDeadline 기본값(https://github.com/microsoft/playwright/blob/main/packages/isomorphic/timeoutRunner.ts)을 따른다.
6794
+ file: "src/tools/computer/worker.ts",
6795
+ marker: "import { untilAborted } from \"@oh-my-pi/pi-utils/abortable\";",
6796
+ anchor: `import * as postmortem from "@oh-my-pi/pi-utils/postmortem";`,
6797
+ patched: `import { untilAborted } from "@oh-my-pi/pi-utils/abortable";
6798
+ import * as postmortem from "@oh-my-pi/pi-utils/postmortem";`,
6799
+ },
6800
+ {
6801
+ file: "src/tools/computer/worker.ts",
6802
+ marker: " goalProbe?: boolean;",
6803
+ anchor: `interface ComputerRunContext {
6804
+ signal: AbortSignal;
6805
+ readOnly: boolean;
6806
+ snapshot: ComputerSessionSnapshot;
6807
+ output: RunOutput;
6808
+ screenshots: ComputerScreenshot[];
6809
+ }`,
6810
+ patched: `interface ComputerRunContext {
6811
+ signal: AbortSignal;
6812
+ readOnly: boolean;
6813
+ /** Set while a \`wait(predicate)\` probe runs; \`signal\` then also ends at the wait's deadline and when it settles. */
6814
+ goalProbe?: boolean;
6815
+ snapshot: ComputerSessionSnapshot;
6816
+ output: RunOutput;
6817
+ screenshots: ComputerScreenshot[];
6818
+ }
6819
+
6820
+ /** Pauses between goal probes when \`interval\` is not given: Playwright's \`pollAgainstDeadline\` default; the last one repeats. */
6821
+ const GOAL_POLL_INTERVALS = [100, 250, 500, 1000] as const;
6822
+
6823
+ /** A goal probe only reads: desktop input, clipboard writes, and tool-bridge calls would repeat on every probe. */
6824
+ function goalProbeRefusal(method: string): ToolError {
6825
+ return new ToolError(
6826
+ \`wait(predicate) probe cannot run '\${method}': probes only read desktop state (windows, ax, find, value); act once before wait()\`,
6827
+ );
6828
+ }`,
6829
+ },
6830
+ {
6831
+ file: "src/tools/computer/worker.ts",
6832
+ marker: " if (context.goalProbe) throw goalProbeRefusal(method);",
6833
+ anchor: `function guardRun(context: ComputerRunContext, method: string): void {
6834
+ if (context.readOnly)`,
6835
+ patched: `function guardRun(context: ComputerRunContext, method: string): void {
6836
+ if (context.goalProbe) throw goalProbeRefusal(method);
6837
+ if (context.readOnly)`,
6838
+ },
6839
+ {
6840
+ // probe 안 screenshot 은 로컬 읽기로 허용하되, 기존 silent 경로로 probe 마다 이미지가 붙지 않게 한다.
6841
+ file: "src/tools/computer/worker.ts",
6842
+ marker: " if (!options?.silent && !context.goalProbe) {",
6843
+ anchor: " if (!options?.silent) {",
6844
+ patched: " if (!options?.silent && !context.goalProbe) {",
6845
+ },
6846
+ {
6847
+ file: "src/tools/computer/worker.ts",
6848
+ marker: "this.#waitForGoal(",
6849
+ anchor: ` wait: (msOrPredicate: number | (() => unknown), options?: WaitPredicateOptions): Promise<unknown> => {
6850
+ const resolved =
6851
+ typeof msOrPredicate === "number"
6852
+ ? undefined
6853
+ : {
6854
+ timeout: resolvePredicateTimeout(message.timeoutMs, options?.timeout),
6855
+ interval: options?.interval,
6856
+ };
6857
+ return markHandled(waitForRun(msOrPredicate, signal, resolved));
6858
+ },`,
6859
+ patched: ` wait: (msOrPredicate: number | (() => unknown), options?: WaitPredicateOptions): Promise<unknown> => {
6860
+ if (typeof msOrPredicate !== "function") return markHandled(waitForRun(msOrPredicate, signal));
6861
+ return markHandled(
6862
+ this.#waitForGoal(
6863
+ this.#runContexts.getStore() ?? runContext,
6864
+ msOrPredicate,
6865
+ resolvePredicateTimeout(message.timeoutMs, options?.timeout),
6866
+ options?.interval,
6867
+ ),
6868
+ );
6869
+ },`,
6870
+ },
6871
+ {
6872
+ file: "src/tools/computer/worker.ts",
6873
+ marker: "if (this.#runContexts.getStore()?.goalProbe) throw goalProbeRefusal(`tool:${name}`);",
6874
+ anchor: ` callTool: (name, args) => {
6875
+ throwIfAborted(active.signal);
6876
+ return this.#callTool(active, name, args);`,
6877
+ patched: ` callTool: (name, args) => {
6878
+ throwIfAborted(active.signal);
6879
+ if (this.#runContexts.getStore()?.goalProbe) throw goalProbeRefusal(\`tool:\${name}\`);
6880
+ return this.#callTool(active, name, args);`,
6881
+ },
6882
+ {
6883
+ file: "src/tools/computer/worker.ts",
6884
+ marker: " async #waitForGoal(",
6885
+ anchor: ` #createDesktopScope(session: NativeDesktopSession): object {`,
6886
+ patched: ` /**
6887
+ * \`wait(predicate)\` for the goal state after an action. The first probe runs at once, then after
6888
+ * Playwright's back-off (or the fixed \`interval\`), each pause capped at the time left; no probe starts
6889
+ * after the deadline. Every probe runs in a derived context whose signal also ends at the deadline and
6890
+ * when the wait settles, and whose input, clipboard writes, and tool calls refuse, so a native read that
6891
+ * returns late cannot continue into further reads or input. Predicate errors propagate on first
6892
+ * occurrence; an unmet deadline is the named timeout; run cancellation stays an abort.
6893
+ */
6894
+ async #waitForGoal(base: ComputerRunContext, predicate: () => unknown, timeout: number, interval?: number): Promise<unknown> {
6895
+ throwIfAborted(base.signal);
6896
+ const deadline = new AbortController();
6897
+ const timer = setTimeout(
6898
+ () => deadline.abort(postmortem.markExpectedCleanupError(new ToolAbortError("wait(predicate) deadline"))),
6899
+ timeout,
6900
+ );
6901
+ const signal = AbortSignal.any([base.signal, deadline.signal]);
6902
+ const probe: ComputerRunContext = { ...base, signal, goalProbe: true };
6903
+ const started = performance.now();
6904
+ let probes = 0;
6905
+ let probing = false;
6906
+ try {
6907
+ for (let step = 0; performance.now() - started < timeout; step++) {
6908
+ probes++;
6909
+ probing = true;
6910
+ const value = await untilAborted(signal, async () => await this.#runContexts.run(probe, predicate));
6911
+ // A probe that ends past the deadline never counts, even when truthy (a synchronous predicate can outrun the timer).
6912
+ if (performance.now() - started > timeout) break;
6913
+ probing = false;
6914
+ if (value) return value;
6915
+ const pause = interval === undefined ? GOAL_POLL_INTERVALS[Math.min(step, GOAL_POLL_INTERVALS.length - 1)]! : Math.max(interval, 10);
6916
+ const left = Math.max(0, timeout - (performance.now() - started));
6917
+ await untilAborted(signal, async () => await Bun.sleep(Math.min(pause, left)));
6918
+ }
6919
+ } catch (error) {
6920
+ // Only a rejection caused by this wait's own signal is remapped; predicate, native, and permission errors pass through as thrown.
6921
+ const aborted = signal.aborted && error instanceof Error && (error === signal.reason || error.cause === signal.reason);
6922
+ if (!aborted) throw error;
6923
+ throwIfAborted(base.signal);
6924
+ } finally {
6925
+ clearTimeout(timer);
6926
+ deadline.abort(postmortem.markExpectedCleanupError(new ToolAbortError("wait(predicate) ended")));
6927
+ }
6928
+ throw new ToolError(
6929
+ \`wait(predicate) timed out after \${timeout}ms — predicate never returned truthy (\${probes} probe\${probes === 1 ? "" : "s"}\${probing ? "; the probe still running at the deadline was abandoned" : ""})\`,
6930
+ );
6931
+ }
6932
+
6933
+ #createDesktopScope(session: NativeDesktopSession): object {`,
6934
+ },
6935
+ {
6936
+ // 모델이 보는 타입: 목표 대기 규칙.
6937
+ file: "src/tools/computer/declarations.d.ts",
6938
+ marker: "or poll a goal predicate until truthy and resolve with its value.",
6939
+ anchor: ` /** Sleep for milliseconds or poll a predicate until truthy. */
6940
+ readonly wait: (
6941
+ msOrPredicate: number | (() => unknown),
6942
+ options?: {
6943
+ /** Maximum polling time in milliseconds. */
6944
+ timeout?: number;
6945
+ /** Delay between predicate calls in milliseconds. */
6946
+ interval?: number;
6947
+ },
6948
+ ) => Promise<unknown>;`,
6949
+ patched: ` /**
6950
+ * Sleep for milliseconds, or poll a goal predicate until truthy and resolve with its value.
6951
+ * Act once before \`wait\`; inside the predicate, desktop input, \`clipboard.write\`, and tool calls throw, and screenshots stay silent.
6952
+ * An unmet goal throws \`wait(predicate) timed out after …\`; predicate errors propagate unretried; cancel stays cancel.
6953
+ */
6954
+ readonly wait: (
6955
+ msOrPredicate: number | (() => unknown),
6956
+ options?: {
6957
+ /** Maximum polling time in milliseconds (default 30s, kept below the run budget). */
6958
+ timeout?: number;
6959
+ /** Fixed delay between predicate calls in milliseconds; by default 100, 250, 500, then 1000. */
6960
+ interval?: number;
6961
+ },
6962
+ ) => Promise<unknown>;`,
6963
+ },
6964
+ {
6965
+ // 모델 지시: 행동은 한 번, 목표는 wait(predicate)로 읽고, 미달이면 다시 누르지 않는다.
6966
+ file: "src/prompts/tools/computer.md",
6967
+ marker: "- Goal after an action: act ONCE",
6968
+ anchor: "Plain data, functions, and `RegExp` values are supported in `args`.\n",
6969
+ patched: "Plain data, functions, and `RegExp` values are supported in `args`.\n- Goal after an action: act ONCE, then `await wait(async () => …read…, { timeout })` for the row, text, or value the user asked for; it resolves with that value. Inside the predicate, desktop input, `clipboard.write`, and tool calls throw and screenshots stay silent; probes back off 100→1000 ms, and a late read cannot continue past the deadline. The predicate is still unsandboxed Bun/Node code. On `wait(predicate) timed out` NEVER re-click, take over, or switch windows: reobserve and report. A button state change alone does not prove a save.\n",
6970
+ },
6971
+ {
6972
+ file: "src/prompts/tools/computer.md",
6973
+ marker: "return await wait(async () => (await orders.find(",
6974
+ anchor: "\treturn await target.ax();\n}, { timeout: 30 });\n",
6975
+ patched: "\treturn await target.ax();\n}, { timeout: 30 });\nawait computer.run(async ({ desktop, wait }) => {\n\tconst orders = await desktop.window({ title: \"Orders\" });\n\tconst [save] = await orders.find({ role: \"button\", title: \"Save\" });\n\tawait save.press();\n\treturn await wait(async () => (await orders.find({ title: \"Saved row 1\" }))[0]?.title, { timeout: 5000 });\n}, { timeout: 30 });\n",
6976
+ },
6621
6977
  {
6622
6978
  // MCP 선택 연결: 요청별 MCP 선택(opt-in). all이 기본이며 per-request면 시작 시 연결하지 않고 요청마다 확장이 고른다.
6623
6979
  file: "src/mcp/settings.ts",
@@ -7406,8 +7762,10 @@ export interface McpSelectEventResult {
7406
7762
  {
7407
7763
  // 교훈 자동 저장 표시(2026-09-29 사용자 지적 "교훈 저장이 안 뜬다"): autolearn capture Agent는 본 대화와
7408
7764
  // 분리돼 저장해도 화면·다음 턴 문맥에 아무것도 남지 않았다. 성공한 learn·manage_skill만 요약해 돌려준다.
7765
+ // r3(2026-09-30 사용자 지적 "... 로 잘리는 것보다 몇 줄 요약이 낫다"): 160자 절단 대신 교훈의 첫 문장(=capture가
7766
+ // 맨 앞에 쓰는 제목, 아래 nudge 항목)만 보여 주고, 그마저 120자를 넘으면 단어 경계에서 줄인다. legacyPatched는 r1 적용본.
7409
7767
  file: "src/sdk.ts",
7410
- marker: `export function summarizeAutoLearnSaved(`,
7768
+ marker: ` if (first.length <= 120) return first;`,
7411
7769
  anchor: ` createSessionId?: () => string;
7412
7770
  }
7413
7771
 
@@ -7417,6 +7775,53 @@ export interface McpSelectEventResult {
7417
7775
  onCaptured?: (saved: string[]) => void;
7418
7776
  }
7419
7777
 
7778
+ /**
7779
+ * Summarize a capture run's successful \`learn\`/\`manage_skill\` calls. Each call is paired with its
7780
+ * result, so a rejected or failed save is never reported as stored.
7781
+ */
7782
+ export function summarizeAutoLearnSaved(messages: readonly AgentMessage[], from: number): string[] {
7783
+ const text = (value: unknown): string => (typeof value === "string" ? value.replace(/\\s+/g, " ").trim() : "");
7784
+ const calls = new Map<string, { name: string; args: Record<string, unknown> }>();
7785
+ const saved: string[] = [];
7786
+ for (const message of messages.slice(from)) {
7787
+ if (message.role === "assistant") {
7788
+ for (const block of message.content) {
7789
+ if (block.type === "toolCall") {
7790
+ calls.set(block.id, { name: block.name, args: (block.arguments ?? {}) as Record<string, unknown> });
7791
+ }
7792
+ }
7793
+ continue;
7794
+ }
7795
+ if (message.role !== "toolResult" || message.isError) continue;
7796
+ const call = calls.get(message.toolCallId);
7797
+ if (!call) continue;
7798
+ if (call.name === "learn") {
7799
+ const memory = text(call.args.memory);
7800
+ const skill = call.args.skill as { action?: unknown; name?: unknown } | undefined;
7801
+ const skillNote = skill ? " (스킬 " + text(skill.action) + " " + text(skill.name) + ")" : "";
7802
+ saved.push("교훈: " + autoLearnHeadline(memory) + skillNote);
7803
+ } else if (call.name === "manage_skill") {
7804
+ saved.push("스킬 " + text(call.args.action) + ": " + text(call.args.name));
7805
+ }
7806
+ }
7807
+ return saved;
7808
+ }
7809
+
7810
+ /** The notice headline: the lesson's first sentence, shortened at a word boundary past 120 chars. */
7811
+ function autoLearnHeadline(memory: string): string {
7812
+ const first = /^.+?[.!?](?=\\s|$)/.exec(memory)?.[0] ?? memory;
7813
+ if (first.length <= 120) return first;
7814
+ const cut = first.slice(0, 120);
7815
+ const space = cut.lastIndexOf(" ");
7816
+ return (space > 60 ? cut.slice(0, space) : cut).replace(/[\\s,;:(]+$/, "") + "…";
7817
+ }
7818
+
7819
+ /** Build a private capture runner over a detached message snapshot and provider session. */`,
7820
+ legacyPatched: ` createSessionId?: () => string;
7821
+ /** Receives the capture run's successful \`learn\`/\`manage_skill\` saves so the session can show them. */
7822
+ onCaptured?: (saved: string[]) => void;
7823
+ }
7824
+
7420
7825
  /**
7421
7826
  * Summarize a capture run's successful \`learn\`/\`manage_skill\` calls. Each call is paired with its
7422
7827
  * result, so a rejected or failed save is never reported as stored.
@@ -7464,11 +7869,25 @@ export function summarizeAutoLearnSaved(messages: readonly AgentMessage[], from:
7464
7869
  },
7465
7870
  {
7466
7871
  // 저장 결과를 보이는 custom 메시지로 남긴다. idle이면 턴을 시작하지 않고 붙이고, 진행 중이면 aside로 끼운다.
7872
+ // r2: 머리글을 "[교훈 자동 저장] N건"으로 줄이고 3건까지만 보인다. legacyPatched는 r1 적용본.
7467
7873
  file: "src/sdk.ts",
7468
- marker: ` { customType: "autolearn-saved", content, display: true, attribution: "agent" },`,
7874
+ marker: ` const content = ["[교훈 자동 저장] " + saved.length + "건", ...shown].join("\\n");`,
7469
7875
  anchor: ` const runAutoLearnCapture = createAutoLearnCaptureRunner({
7470
7876
  sourceAgent: agent,`,
7471
7877
  patched: ` const runAutoLearnCapture = createAutoLearnCaptureRunner({
7878
+ sourceAgent: agent,
7879
+ onCaptured: saved => {
7880
+ const shown = saved.slice(0, 3).map(line => "- " + line);
7881
+ if (saved.length > 3) shown.push("- 외 " + (saved.length - 3) + "건");
7882
+ const content = ["[교훈 자동 저장] " + saved.length + "건", ...shown].join("\\n");
7883
+ void session
7884
+ .sendCustomMessage(
7885
+ { customType: "autolearn-saved", content, display: true, attribution: "agent" },
7886
+ session.isStreaming ? { deliverAs: "aside" } : undefined,
7887
+ )
7888
+ .catch(error => logger.warn("Failed to show auto-learn capture result", { error: String(error) }));
7889
+ },`,
7890
+ legacyPatched: ` const runAutoLearnCapture = createAutoLearnCaptureRunner({
7472
7891
  sourceAgent: agent,
7473
7892
  onCaptured: saved => {
7474
7893
  const content = ["[교훈 자동 저장] 이번 턴이 끝난 뒤 별도 capture가 저장했다.", ...saved.map(line => "- " + line)].join("\\n");
@@ -7480,6 +7899,164 @@ export function summarizeAutoLearnSaved(messages: readonly AgentMessage[], from:
7480
7899
  .catch(error => logger.warn("Failed to show auto-learn capture result", { error: String(error) }));
7481
7900
  },`,
7482
7901
  },
7902
+ {
7903
+ // capture가 교훈 맨 앞에 짧은 제목 한 문장을 쓰게 한다(2026-09-30). 알림은 그 첫 문장을 보여 주므로,
7904
+ // 긴 설명형 첫 문장이 잘려 "…"로 끝나지 않는다. 기억 검색에도 제목이 앞에 오는 편이 낫다.
7905
+ file: "src/prompts/system/autolearn-nudge-autocontinue.md",
7906
+ marker: "Begin each `learn` memory with one short headline sentence",
7907
+ anchor: "remember with `learn` when memory enabled. If nothing worth keeping, do nothing.",
7908
+ patched: "remember with `learn` when memory enabled. Begin each `learn` memory with one short headline sentence (under 80 characters) that states the lesson, then give the details. If nothing worth keeping, do nothing.",
7909
+ },
7910
+ // Codex native turn lane 는 실행 중 `response.steer`를 `response.steer.failed`가 아니라 일반
7911
+ // error 프레임(code=unsupported_native_inflight_message)으로 거절한다(2026-09-30 사용자 보고).
7912
+ // 그 프레임은 steer 제출자가 아니라 응답 스트림으로 들어가 진행 중 턴이 죽었다. steer 대기자가
7913
+ // 있을 때만 이 정확한 코드를 거절 ack 로 돌려 기존 claim.reject() → 다음 경계 일반 전달 경로를
7914
+ // 타게 하고, 같은 소켓은 이후 steer 를 전송하지 않는다(새 소켓은 다시 한 번 시도한다).
7915
+ // 대기자가 없거나 다른 코드인 error 프레임은 그대로 스트림에 push 한다.
7916
+ {
7917
+ file: "../pi-ai/src/providers/openai-codex-responses.ts",
7918
+ marker: "#steerUnsupported = false;",
7919
+ anchor: ` /** Steering whose automatic successor the active attach request is reading. */
7920
+ #attachSteerIds?: ReadonlySet<string>;`,
7921
+ patched: ` /** Steering whose automatic successor the active attach request is reading. */
7922
+ #attachSteerIds?: ReadonlySet<string>;
7923
+ /** The server answered a steer with unsupported_native_inflight_message; this socket no longer submits steers. */
7924
+ #steerUnsupported = false;`,
7925
+ },
7926
+ {
7927
+ file: "../pi-ai/src/providers/openai-codex-responses.ts",
7928
+ marker: "this.#steerWaiters.shift()?.resolve({",
7929
+ anchor: ` // Steering acknowledgements belong to the submitter, not to the
7930
+ // response stream they interleave with.
7931
+ if (typeof parsed.type === "string" && parsed.type.startsWith("response.steer.")) {`,
7932
+ patched: ` // The native turn lane refuses steering with a bare error frame. It answers the
7933
+ // pending steer, not the response stream, so settle that waiter as rejected.
7934
+ if (
7935
+ parsed.type === "error" &&
7936
+ parsed.code === "unsupported_native_inflight_message" &&
7937
+ this.#steerWaiters.length > 0
7938
+ ) {
7939
+ this.#steerUnsupported = true;
7940
+ this.#steerWaiters.shift()?.resolve({
7941
+ accepted: false,
7942
+ code: parsed.code,
7943
+ message: typeof parsed.message === "string" ? parsed.message : undefined,
7944
+ });
7945
+ return;
7946
+ }
7947
+ // Steering acknowledgements belong to the submitter, not to the
7948
+ // response stream they interleave with.
7949
+ if (typeof parsed.type === "string" && parsed.type.startsWith("response.steer.")) {`,
7950
+ },
7951
+ {
7952
+ file: "../pi-ai/src/providers/openai-codex-responses.ts",
7953
+ marker: "code: \"unsupported_native_inflight_message\", message: undefined",
7954
+ anchor: ` return Promise.reject(new CodexWebSocketTransportError(\`websocket connection is unavailable\`));
7955
+ }
7956
+ const event = { type: "response.steer", previous_response_id: previousResponseId, input };`,
7957
+ patched: ` return Promise.reject(new CodexWebSocketTransportError(\`websocket connection is unavailable\`));
7958
+ }
7959
+ if (this.#steerUnsupported) {
7960
+ return Promise.resolve({ accepted: false, code: "unsupported_native_inflight_message", message: undefined });
7961
+ }
7962
+ const event = { type: "response.steer", previous_response_id: previousResponseId, input };`,
7963
+ },
7964
+ // 2026-09-30 사용자 요청(0.6.6): 일반 구현 선택 질문만 `ask.timeout` 무응답 시 추천안으로 진행한다.
7965
+ // 자동선택은 긍정 opt-in(`autoSelectRecommended: true`)이고 유효한 `recommended`가 필수다. 한 호출의
7966
+ // 모든 질문이 조건을 채울 때만 timeout 을 넘기고, 나머지(기존 호출·승인 질문·plan mode)는 무기한 기다린다.
7967
+ // 타임아웃 결과는 사용자 응답·승인이 아니라고 결과 문구에 적는다. 키워드 판정은 두지 않는다.
7968
+ {
7969
+ file: "src/tools/ask.ts",
7970
+ marker: `"autoSelectRecommended?": arkType("boolean")`,
7971
+ anchor: ` "recommended?": arkType("number").describe("0-based default index"),
7972
+ }).narrow((question, ctx) => {`,
7973
+ patched: ` "recommended?": arkType("number").describe("0-based default index"),
7974
+ "autoSelectRecommended?": arkType("boolean").describe(
7975
+ "opt-in: pick recommended after ask.timeout with no answer; reversible implementation choices only",
7976
+ ),
7977
+ }).narrow((question, ctx) => {`,
7978
+ },
7979
+ {
7980
+ file: "src/tools/ask.ts",
7981
+ marker: "never guess the first option on the user's behalf",
7982
+ anchor: ` return [options[recommended]!.label];
7983
+ }
7984
+ return [options[0]!.label];
7985
+ }`,
7986
+ patched: ` return [options[recommended]!.label];
7987
+ }
7988
+ // No valid recommendation: never guess the first option on the user's behalf.
7989
+ return [];
7990
+ }`,
7991
+ },
7992
+ {
7993
+ file: "src/tools/ask.ts",
7994
+ marker: "no user response, not user approval",
7995
+ anchor: ` const suffix = \`\${result.timedOut ? " (auto-selected after timeout)" : ""}\${noteSuffix}\`;`,
7996
+ patched: ` const suffix = \`\${result.timedOut ? " (auto-selected recommended option after timeout; no user response, not user approval)" : ""}\${noteSuffix}\`;`,
7997
+ },
7998
+ {
7999
+ file: "src/tools/ask.ts",
8000
+ marker: "No user response: auto-selected the recommended option after timeout",
8001
+ anchor: ` responseParts.push(result.timedOut ? \`\${selectedText} (auto-selected after timeout)\` : selectedText);`,
8002
+ patched: ` responseParts.push(
8003
+ result.timedOut
8004
+ ? \`No user response: auto-selected the recommended option after timeout: \${result.selectedOptions.join(", ")} (not user approval)\`
8005
+ : selectedText,
8006
+ );`,
8007
+ },
8008
+ {
8009
+ file: "src/tools/ask.ts",
8010
+ marker: "Auto-selection is a positive opt-in",
8011
+ anchor: ` const timeout = planModeEnabled ? null : settingsTimeout;`,
8012
+ patched: ` // Auto-selection is a positive opt-in: every question must ask for it and carry a valid
8013
+ // recommendation. Legacy calls, approvals, and questions without one wait indefinitely.
8014
+ const autoSelectable = params.questions.every(
8015
+ q =>
8016
+ q.autoSelectRecommended === true &&
8017
+ typeof q.recommended === "number" &&
8018
+ Number.isInteger(q.recommended) &&
8019
+ q.recommended >= 0 &&
8020
+ q.recommended < q.options.length,
8021
+ );
8022
+ const timeout = planModeEnabled || !autoSelectable ? null : settingsTimeout;`,
8023
+ },
8024
+ {
8025
+ file: "src/prompts/tools/ask.md",
8026
+ marker: "`autoSelectRecommended: true`",
8027
+ anchor: `- \`recommended\` auto-adds " (Recommended)"; \`multi: true\` permits multiple selections.`,
8028
+ patched: `- \`recommended\` auto-adds " (Recommended)"; \`multi: true\` permits multiple selections.
8029
+ - \`autoSelectRecommended: true\` (requires a valid \`recommended\`): if nobody answers before \`ask.timeout\`, the recommended option is picked. Use ONLY for reversible implementation choices; NEVER for deployment, deletion, cost, account/permission, provider safety, or other high-impact confirmations. A timed-out pick is not user approval.`,
8030
+ },
8031
+ // 2026-09-30 사용자 요청(0.6.6): Main 의 Fast(OpenAI `priority`)만 새 child 에 상속한다.
8032
+ // `tier.subagent: inherit` 해석(및 agentServiceTierOverrides 의 inherit)이 부모 map 전체를 넘기면
8033
+ // Main 이 Anthropic Fast 일 때 Sonnet child 까지 `speed: "fast"` 로 과금된다. 상속 결과를
8034
+ // openai=priority 하나로 좁힌다. 명시 concrete tier·override·revive persist 경로는 그대로다.
8035
+ {
8036
+ file: "src/task/executor.ts",
8037
+ marker: "function parentSubagentServiceTiers(",
8038
+ anchor: `function inheritedSubagentServiceTiers(
8039
+ baseSettings: Settings,
8040
+ inheritedServiceTier?: ServiceTierByFamily | null,
8041
+ ): ServiceTierByFamily {
8042
+ if (inheritedServiceTier === undefined) {`,
8043
+ patched: `function inheritedSubagentServiceTiers(
8044
+ baseSettings: Settings,
8045
+ inheritedServiceTier?: ServiceTierByFamily | null,
8046
+ ): ServiceTierByFamily {
8047
+ // CUELO: inheritance carries only the parent's OpenAI Fast (\`priority\`). Anthropic/Google
8048
+ // tiers and other OpenAI tiers (ultrafast, flex, ...) stay with the parent; an explicit
8049
+ // concrete \`tier.subagent\` or per-agent override still applies on its own path.
8050
+ const parent = parentSubagentServiceTiers(baseSettings, inheritedServiceTier);
8051
+ return parent.openai === "priority" ? { openai: "priority" } : {};
8052
+ }
8053
+
8054
+ function parentSubagentServiceTiers(
8055
+ baseSettings: Settings,
8056
+ inheritedServiceTier?: ServiceTierByFamily | null,
8057
+ ): ServiceTierByFamily {
8058
+ if (inheritedServiceTier === undefined) {`,
8059
+ },
7483
8060
  ];
7484
8061
  // EDITS 문자열의 줄 끝을 LF로 통일한다. 이 파일의 작업 사본이 CRLF여도 core 파일(LF)과
7485
8062
  // 비교·치환이 어긋나지 않는다. core 파일 자체의 줄 끝은 건드리지 않는다.