cuelo 0.8.0 → 0.8.2

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 (94) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/react-loadable-manifest.json +3 -3
  6. package/.next/required-server-files.js +2 -2
  7. package/.next/required-server-files.json +2 -2
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  23. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/server/app/api/updates/route.js +1 -1
  27. package/.next/server/app/index.html +1 -1
  28. package/.next/server/app/index.rsc +6 -6
  29. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
  30. package/.next/server/app/index.segments/_full.segment.rsc +6 -6
  31. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  32. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  33. package/.next/server/app/index.segments/_tree.segment.rsc +3 -3
  34. package/.next/server/app/office-preview/page.js +2 -2
  35. package/.next/server/app/office-preview/page.js.nft.json +1 -1
  36. package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
  37. package/.next/server/app/office-preview.html +1 -1
  38. package/.next/server/app/office-preview.rsc +3 -3
  39. package/.next/server/app/office-preview.segments/_full.segment.rsc +3 -3
  40. package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
  41. package/.next/server/app/office-preview.segments/_index.segment.rsc +2 -2
  42. package/.next/server/app/office-preview.segments/_tree.segment.rsc +2 -2
  43. package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +2 -2
  44. package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
  45. package/.next/server/app/page.js +17 -17
  46. package/.next/server/app/page.js.nft.json +1 -1
  47. package/.next/server/app/page_client-reference-manifest.js +1 -1
  48. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  49. package/.next/server/app/recover.html +1 -1
  50. package/.next/server/app/recover.rsc +2 -2
  51. package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
  52. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  53. package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
  54. package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
  55. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  56. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  57. package/.next/server/app-paths-manifest.json +11 -11
  58. package/.next/server/chunks/1933.js +353 -0
  59. package/.next/server/chunks/2607.js +1 -0
  60. package/.next/server/chunks/6429.js +2 -2
  61. package/.next/server/middleware-build-manifest.js +1 -1
  62. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  63. package/.next/server/pages/404.html +1 -1
  64. package/.next/server/pages/500.html +1 -1
  65. package/.next/server/server-reference-manifest.json +1 -1
  66. package/.next/static/chunks/8637.5c189b15878e878b.js +1 -0
  67. package/.next/static/chunks/9703.36cc00cc10e10f45.js +1 -0
  68. package/.next/static/chunks/9889-4e472951b0a45120.js +1 -0
  69. package/.next/static/chunks/app/{layout-6aa3580989aedade.js → layout-78440a2c8d3cd1c7.js} +1 -1
  70. package/.next/static/chunks/app/office-preview/page-68828399eb58177b.js +1 -0
  71. package/.next/static/chunks/app/page-a4c4acecc034872f.js +22 -0
  72. package/.next/static/chunks/webpack-aba44445f0ef2ed9.js +1 -0
  73. package/.next/static/css/aab5fc8ab1b53780.css +1 -0
  74. package/.next/static/css/d6462eb5a4d2cb8c.css +1 -0
  75. package/.next/trace +107 -106
  76. package/.next/trace-build +1 -1
  77. package/README.md +3 -3
  78. package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
  79. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +404 -390
  80. package/package.json +7 -7
  81. package/public/office3d/ASSETS.md +51 -4
  82. package/public/office3d/office-kit.glb +0 -0
  83. package/.next/server/chunks/58.js +0 -353
  84. package/.next/server/chunks/7362.js +0 -1
  85. package/.next/static/chunks/1937.8900983e70e8e9dc.js +0 -1
  86. package/.next/static/chunks/4857.cb0735ea310a39f0.js +0 -1
  87. package/.next/static/chunks/6110-681afd060dde5270.js +0 -1
  88. package/.next/static/chunks/app/office-preview/page-2151040877dfb7cd.js +0 -1
  89. package/.next/static/chunks/app/page-f6f024bab16a7232.js +0 -22
  90. package/.next/static/chunks/webpack-93d544f82f48c3fd.js +0 -1
  91. package/.next/static/css/829f44afcc3302e9.css +0 -1
  92. package/.next/static/css/abd3d00cca9457f7.css +0 -1
  93. /package/.next/static/{vaxcaBF-y6afdFHIrgeuN → 3xAqEONTnox8I-4Te8HCx}/_buildManifest.js +0 -0
  94. /package/.next/static/{vaxcaBF-y6afdFHIrgeuN → 3xAqEONTnox8I-4Te8HCx}/_ssgManifest.js +0 -0
@@ -64,6 +64,20 @@ const OPUS55_ROW_1846 = {
64
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
65
  };
66
66
 
67
+ /**
68
+ * #310의 18.4.10 후보. binding controls만 켜고 18.4.6 결과와 `": true"` 공백으로 구분한다(EDITS 항목 주석). marker는
69
+ * patched 전체다: 18.4.12 후보와는 행 앞쪽 측정값 tps로만 갈리므로 tps 뒤 조각을 marker로 쓰면 둘 다 applied가 된다.
70
+ */
71
+ const OPUS55_TPS_18410 = '"tps":95.2,';
72
+ const OPUS55_TPS_18412 = '"tps":92.9,';
73
+ const OPUS55_ROW_18410_PATCHED = OPUS55_ROW_1846.patched.replace('"supportsThinkingBindingControls":true', '"supportsThinkingBindingControls": true');
74
+ const OPUS55_ROW_18410 = {
75
+ file: "../pi-catalog/src/models.json",
76
+ marker: OPUS55_ROW_18410_PATCHED,
77
+ anchor: OPUS55_ROW_1846.anchor.replace('"supportsDisplay":true}', '"supportsDisplay":true,"prefixBinding":true}'),
78
+ patched: OPUS55_ROW_18410_PATCHED,
79
+ };
80
+
67
81
  /** 각 항목: 원본 앵커를 찾아 patched 로 바꾼다. marker 가 있으면 이미 적용된 것으로 본다. */
68
82
  const EDITS = [
69
83
  {
@@ -454,6 +468,136 @@ const EDITS = [
454
468
  anchor: " Every task MUST skip build/lint/tests/formatters mid-flight; run once afterward.",
455
469
  patched: " Validation ownership: each task owner MUST run the smallest meaningful validation for its settled changes; do not blanket-ban independent checks. Defer only checks that depend on another writer's unfinished changes or concurrently mutate the same build output, cache, database, or profile; name one owner and run each shared check once after convergence, reusing valid results instead of repeating them in Main.",
456
470
  },
471
+ {
472
+ // 18.4.12(upstream "subagents skip their own builds, tests, and smoke runs")는 subagent 렌더에서 # 5. Verify 전체를
473
+ // "NEVER verify your changes … unless your assignment explicitly instructs it" Hand-off 로 바꿨다. CUELO 계약은
474
+ // Maker 가 자기 변경을 집중 검사로 증명·보고하고 무거운 통합 검사는 Main 에 exact command 로 넘기는 것이다
475
+ // (agent/sop/_writer.md, rule://subagent 「검증 소유권」). 그래서 subagent 에도 Verify 본문(UI·버그·테스트 규칙)을
476
+ // 그대로 두고 subagent 줄만 앞에 붙인다. 프로젝트 전체 검사 금지는 이 템플릿에 worktree 정보가 없어 여기 두지 않고
477
+ // subagent-system-prompt.md 의 `{{#unless worktree}}` # Validation 이 맡는다(18.4.10 의미). 18.4.10 에는 subagent
478
+ // 분기가 없어 할 일이 없다: alternate 는 그 판 Verify 머리의 no-op 이고, 18.4.12 결과와 순정 어디에도 없는 문맥이다.
479
+ file: "src/prompts/system/system-prompt.md",
480
+ marker: "Scoped proof of your own change is yours:",
481
+ anchor: `{{#if subagent}}
482
+ # 5. Hand-off
483
+ Main agent verifies once after all subagents land; parallel runs storm the CPU and trip on siblings' half-finished edits.
484
+ - NEVER verify your changes (builds, tests, linters, formatters, smoke runs) unless your assignment explicitly instructs it.
485
+ - Changes complete → yield; name the checks main agent should run.
486
+ {{else}}
487
+ # 5. Verify
488
+ Non-trivial work: NEVER yield without a smoke run: run the thing, exercise the changed path, observe the result. Tests alone are not proof.
489
+ - Investigation: run it; output proves it; no tests.
490
+ - UI: verify actual surface.
491
+ {{#if browserEnabled}}
492
+ - Web: \`browser.open\` tab, direct helpers for actions, \`tab.run\` for custom JS; visual proof; \`tab.close\`. No tests unless existing suite breaks.
493
+ {{/if}}
494
+ {{#if computerEnabled}}
495
+ - Native desktop: JS/Python eval \`computer\` helpers; fresh screenshot/accessibility proof.
496
+ {{/if}}
497
+ - TUI/CLI: launch actual program; observe interaction/output/state.
498
+ {{#ifAny (not browserEnabled) (not computerEnabled)}}
499
+ - No runtime for changed surface: throwaway script/smoke test; report visual limit.
500
+ {{/ifAny}}
501
+ - Bug: reproduce before; confirm after. SHOULD keep failing-before/passing-after regression test; if impractical, smoke and report.
502
+ - Feature/API: update broken contract tests; prove new behavior via throwaway script. New test ONLY for uncertain edge or user request.
503
+ - Permanent tests MUST catch plausible consumer-visible bugs: behavior, boundaries, invariants, transitions, precedence, errors. Follow conventions; deterministic, isolated, full-suite-safe.
504
+ - NEVER test wiring/copies/forwarding/mock echoes/source text/incidental defaults, tautologies, bare not-throw, non-empty/length-grew, duplicate same-path rows. Use throwaway scripts.
505
+ - Existing wording/implementation/incidental-behavior tests: MUST delete, NEVER re-pin regardless of author.
506
+ {{/if}}
507
+
508
+ # 6. Cleanup
509
+ {{#if subagent}}Permanent{{else}}After smoke proof: permanent{{/if}} fix/feature`,
510
+ patched: `# 5. Verify
511
+ {{#if subagent}}
512
+ Scoped proof of your own change is yours: run the single test file, targeted repro, or smoke run below that exercises the changed path, and report each command, exit status, and observed result. A check you cannot run here, or a heavy integration check main agent owns → yield its exact command for main agent to run.
513
+ {{/if}}
514
+ Non-trivial work: NEVER yield without a smoke run: run the thing, exercise the changed path, observe the result. Tests alone are not proof.
515
+ - Investigation: run it; output proves it; no tests.
516
+ - UI: verify actual surface.
517
+ {{#if browserEnabled}}
518
+ - Web: \`browser.open\` tab, direct helpers for actions, \`tab.run\` for custom JS; visual proof; \`tab.close\`. No tests unless existing suite breaks.
519
+ {{/if}}
520
+ {{#if computerEnabled}}
521
+ - Native desktop: JS/Python eval \`computer\` helpers; fresh screenshot/accessibility proof.
522
+ {{/if}}
523
+ - TUI/CLI: launch actual program; observe interaction/output/state.
524
+ {{#ifAny (not browserEnabled) (not computerEnabled)}}
525
+ - No runtime for changed surface: throwaway script/smoke test; report visual limit.
526
+ {{/ifAny}}
527
+ - Bug: reproduce before; confirm after. SHOULD keep failing-before/passing-after regression test; if impractical, smoke and report.
528
+ - Feature/API: update broken contract tests; prove new behavior via throwaway script. New test ONLY for uncertain edge or user request.
529
+ - Permanent tests MUST catch plausible consumer-visible bugs: behavior, boundaries, invariants, transitions, precedence, errors. Follow conventions; deterministic, isolated, full-suite-safe.
530
+ - NEVER test wiring/copies/forwarding/mock echoes/source text/incidental defaults, tautologies, bare not-throw, non-empty/length-grew, duplicate same-path rows. Use throwaway scripts.
531
+ - Existing wording/implementation/incidental-behavior tests: MUST delete, NEVER re-pin regardless of author.
532
+
533
+ # 6. Cleanup
534
+ After smoke proof: permanent fix/feature`,
535
+ alternates: [{
536
+ file: "src/prompts/system/system-prompt.md",
537
+ marker: "code made obsolete by cutover is in scope.{{/has}}\n\n# 5. Verify\nNon-trivial work:",
538
+ anchor: "code made obsolete by cutover is in scope.{{/has}}\n\n# 5. Verify\nNon-trivial work:",
539
+ patched: "code made obsolete by cutover is in scope.{{/has}}\n\n# 5. Verify\nNon-trivial work:",
540
+ }],
541
+ },
542
+ {
543
+ // 같은 18.4.12 변경이 project-prompt.md <critical> 의 검증 의무 줄을 subagent 에서 "verification is main agent's job.
544
+ // NEVER run it yourself …"로 바꿨다. 의무 줄을 모두에게 되돌리고 subagent 범위(집중 증명·exit status 보고·못 돌린
545
+ // 검사의 exact command)만 덧붙인다. 18.4.10 alternate 는 그 판 의무 줄(바로 뒤 </critical>)의 no-op 이다.
546
+ file: "src/prompts/system/project-prompt.md",
547
+ marker: "- As a subagent, that proof is a scoped check of your own change;",
548
+ anchor: `{{#if subagent}}
549
+ - Changes complete → yield; verification is main agent's job. NEVER run it yourself unless your assignment explicitly instructs it.
550
+ {{else}}
551
+ - Before yielding, MUST verify significant behavioral changes: run the specific test, command, or scenario covering the change.
552
+ {{/if}}`,
553
+ patched: `- Before yielding, MUST verify significant behavioral changes: run the specific test, command, or scenario covering the change.
554
+ {{#if subagent}}
555
+ - As a subagent, that proof is a scoped check of your own change; report its command and exit status, and yield the exact command for any check you cannot run so main agent runs it.
556
+ {{/if}}`,
557
+ alternates: [{
558
+ file: "src/prompts/system/project-prompt.md",
559
+ marker: "- Before yielding, MUST verify significant behavioral changes: run the specific test, command, or scenario covering the change.\n</critical>",
560
+ anchor: "- Before yielding, MUST verify significant behavioral changes: run the specific test, command, or scenario covering the change.\n</critical>",
561
+ patched: "- Before yielding, MUST verify significant behavioral changes: run the specific test, command, or scenario covering the change.\n</critical>",
562
+ }],
563
+ },
564
+ {
565
+ // 18.4.11 은 subagent-system-prompt.md 의 # Validation 절을 지웠다. 18.4.10 과 같은 자리·같은 `{{#unless worktree}}`
566
+ // 조건으로 되돌린다: 작업 트리를 형제와 공유할 때만 프로젝트 전체 빌드·포매터·린터·전체 스위트를 금지한다(형제의 미완
567
+ // 편집·CPU 경합). 집중 증명은 "fine"(허용)이 아니라 요구로 적는다 — 18.4.12 의 다른 두 템플릿과 같은 계약이고,
568
+ // 18.4.10 순정 문장과 구분돼 그 판에서 applied 로 보이지 않는다. 18.4.10 alternate 는 그 판 절 끝의 no-op 이다.
569
+ file: "src/prompts/system/subagent-system-prompt.md",
570
+ marker: "Scoped proof of your own change (single test file, targeted repro, smoke run) is still required: run it and report the result.",
571
+ anchor: "You are operating on a piece of work assigned to you by the main agent.\n\n{{#if worktree}}",
572
+ patched: `You are operating on a piece of work assigned to you by the main agent.
573
+
574
+ {{#unless worktree}}
575
+ # Validation
576
+ Project-wide validation is the main agent's job, run once after all subagents land. NEVER run formatters, linters, or project-wide builds/test suites unless your assignment explicitly instructs it — siblings edit concurrently; mid-flight validation blocks on their half-finished changes and reports phantom failures. Scoped proof of your own change (single test file, targeted repro, smoke run) is still required: run it and report the result.
577
+ {{/unless}}
578
+
579
+ {{#if worktree}}`,
580
+ alternates: [{
581
+ file: "src/prompts/system/subagent-system-prompt.md",
582
+ marker: "Scoped proof of your own change (single test file, targeted repro, smoke run) is fine.\n{{/unless}}",
583
+ anchor: "Scoped proof of your own change (single test file, targeted repro, smoke run) is fine.\n{{/unless}}",
584
+ patched: "Scoped proof of your own change (single test file, targeted repro, smoke run) is fine.\n{{/unless}}",
585
+ }],
586
+ },
587
+ {
588
+ // 18.4.11 은 같은 파일 Completion 의 "investigate, edit, run, verify." 에서 run·verify 를 뺐다. 실행·검증을 되살리되
589
+ // 범위를 집중 검사로 적는다(18.4.10 문장과도 구분된다). 18.4.10 alternate 는 그 판 문장의 no-op 이다.
590
+ file: "src/prompts/system/subagent-system-prompt.md",
591
+ marker: "you MUST continue with another tool call — investigate, edit, run scoped checks, verify.",
592
+ anchor: "you MUST continue with another tool call — investigate, edit. Save narrative",
593
+ patched: "you MUST continue with another tool call — investigate, edit, run scoped checks, verify. Save narrative",
594
+ alternates: [{
595
+ file: "src/prompts/system/subagent-system-prompt.md",
596
+ marker: "you MUST continue with another tool call — investigate, edit, run, verify. Save narrative",
597
+ anchor: "you MUST continue with another tool call — investigate, edit, run, verify. Save narrative",
598
+ patched: "you MUST continue with another tool call — investigate, edit, run, verify. Save narrative",
599
+ }],
600
+ },
457
601
  {
458
602
  // 2026-09-15 실환경: 사용자가 보낸 steer 가 ask 시작 12.7초 전에 큐에
459
603
  // 들어왔지만, ask 가 24분 timeout 을 전부 기다린 뒤에야 interrupt_skipped
@@ -2063,373 +2207,6 @@ function evalRejectionResult(error: unknown): AgentToolResult<EvalToolDetails |
2063
2207
  return result;
2064
2208
  });`,
2065
2209
  },
2066
- // ---- task maker external runtime bridge (Codex app-server / Claude Code CLI) ----
2067
- // 분기는 코어가 provider/model/auth fallback 을 최종 승인한 뒤에만 일어난다.
2068
- // 두 env 중 하나라도 없거나 maker 이외 agent 면 기존 createAgentSession 경로를 그대로 탄다.
2069
- {
2070
- file: "src/task/executor.ts",
2071
- marker: 'import { pathToFileURL } from "node:url";',
2072
- anchor: `import path from "node:path";`,
2073
- patched: `import path from "node:path";
2074
- import { pathToFileURL } from "node:url";`,
2075
- },
2076
- {
2077
- file: "src/task/executor.ts",
2078
- marker: `type ExternalMakerEngine = "codex" | "claude";`,
2079
- anchor: `function normalizeModelPatterns(value: string | string[] | undefined): string[] {
2080
- if (!value) return [];
2081
- if (Array.isArray(value)) {
2082
- return value.map(entry => entry.trim()).filter(Boolean);
2083
- }
2084
- return value
2085
- .split(",")
2086
- .map(entry => entry.trim())
2087
- .filter(Boolean);
2088
- }`,
2089
- patched: `function normalizeModelPatterns(value: string | string[] | undefined): string[] {
2090
- if (!value) return [];
2091
- if (Array.isArray(value)) {
2092
- return value.map(entry => entry.trim()).filter(Boolean);
2093
- }
2094
- return value
2095
- .split(",")
2096
- .map(entry => entry.trim())
2097
- .filter(Boolean);
2098
- }
2099
-
2100
- type ExternalMakerEngine = "codex" | "claude";
2101
- type ExternalMakerEvent =
2102
- | { type: "session_started"; sessionId: string; engine: ExternalMakerEngine }
2103
- | { type: "text_delta"; text: string }
2104
- | { type: "reasoning_delta"; text: string }
2105
- | { type: "tool_started"; id: string; name: string; input: unknown }
2106
- | { type: "tool_completed"; id: string; output: string; isError: boolean }
2107
- | { type: "file_changed"; path: string }
2108
- | { type: "usage"; input: number; cachedInput: number; cacheWrite: number; output: number }
2109
- | { type: "turn_completed"; stopReason: "stop" | "aborted" | "error"; text: string }
2110
- | { type: "error"; message: string };
2111
-
2112
- interface ExternalMakerSession {
2113
- readonly id: string;
2114
- readonly engine: ExternalMakerEngine;
2115
- prompt(message: string): Promise<void>;
2116
- abort(): Promise<void>;
2117
- onEvent(handler: (event: ExternalMakerEvent) => void): () => void;
2118
- dispose(): Promise<void>;
2119
- }
2120
-
2121
- interface ExternalMakerRuntime {
2122
- createSession(options: {
2123
- cwd: string;
2124
- model?: string;
2125
- thinking?: string;
2126
- }): Promise<ExternalMakerSession>;
2127
- }
2128
-
2129
- function resolveExternalMakerEngine(
2130
- agentName: string,
2131
- model: Model<Api> | undefined,
2132
- ): { engine: ExternalMakerEngine; runtimeDir: string } | undefined {
2133
- if (agentName !== "maker" || !model) return undefined;
2134
- const configured = process.env.CUELO_MAKER_ENGINES?.trim();
2135
- const runtimeDir = process.env.CUELO_RUNTIME_DIR?.trim();
2136
- if (!configured || !runtimeDir) return undefined;
2137
- const engines = new Set(configured.split(",").map(value => value.trim()).filter(Boolean));
2138
- const engine =
2139
- model.provider === "openai-codex" && engines.has("codex")
2140
- ? "codex"
2141
- : model.provider === "anthropic" && engines.has("claude")
2142
- ? "claude"
2143
- : undefined;
2144
- if (!engine) return undefined;
2145
- if (!path.isAbsolute(runtimeDir)) {
2146
- throw new Error("CUELO_RUNTIME_DIR must be an absolute path when an external maker engine is selected");
2147
- }
2148
- return { engine, runtimeDir };
2149
- }
2150
-
2151
- async function loadExternalMakerRuntime(
2152
- engine: ExternalMakerEngine,
2153
- runtimeDir: string,
2154
- ): Promise<ExternalMakerRuntime> {
2155
- const moduleUrl = pathToFileURL(path.join(runtimeDir, "index.ts")).href;
2156
- const runtimeModule = (await import(moduleUrl)) as {
2157
- getRuntime?: (selected: ExternalMakerEngine) => Promise<ExternalMakerRuntime>;
2158
- };
2159
- if (typeof runtimeModule.getRuntime !== "function") {
2160
- throw new Error(\`CUELO runtime module does not export getRuntime(): \${moduleUrl}\`);
2161
- }
2162
- return runtimeModule.getRuntime(engine);
2163
- }`,
2164
- },
2165
- {
2166
- file: "src/task/executor.ts",
2167
- marker: "acceptExternalEvent(event: ExternalMakerEvent, engine: ExternalMakerEngine): void;",
2168
- anchor: ` /** Subscribe the monitor to a session's events. Returns the unsubscribe function. */
2169
- attach(session: AgentSession): () => void;`,
2170
- patched: ` /** Subscribe the monitor to a session's events. Returns the unsubscribe function. */
2171
- attach(session: AgentSession): () => void;
2172
- /** Translate one optional-runtime event into the existing task progress/result stream. */
2173
- acceptExternalEvent(event: ExternalMakerEvent, engine: ExternalMakerEngine): void;`,
2174
- },
2175
- {
2176
- file: "src/task/executor.ts",
2177
- marker: "let hasExternalUsage = false;",
2178
- anchor: ` const outputChunks: string[] = [];
2179
- const finalOutputChunks: string[] = [];`,
2180
- patched: ` const outputChunks: string[] = [];
2181
- const finalOutputChunks: string[] = [];
2182
- const externalToolNames = new Map<string, string>();
2183
- let externalStreamText = "";
2184
- let hasExternalUsage = false;
2185
- const externalUsage = { input: 0, cachedInput: 0, cacheWrite: 0, output: 0 };`,
2186
- },
2187
- {
2188
- file: "src/task/executor.ts",
2189
- marker: "const acceptExternalEvent = (event: ExternalMakerEvent, engine: ExternalMakerEngine): void => {",
2190
- anchor: ` const publishAdvisorState = (session: AgentSession | null): void => {`,
2191
- patched: ` const acceptExternalEvent = (event: ExternalMakerEvent, engine: ExternalMakerEngine): void => {
2192
- const forward = (mapped: AgentEvent): void => {
2193
- emitSubagentEvent(mapped);
2194
- processEvent(mapped);
2195
- };
2196
- switch (event.type) {
2197
- case "session_started":
2198
- emitSubagentEvent({ type: "agent_start" } as AgentEvent);
2199
- break;
2200
- case "text_delta": {
2201
- externalStreamText += event.text;
2202
- forward({
2203
- type: "message_update",
2204
- message: { role: "assistant", content: [] },
2205
- assistantMessageEvent: { type: "text_delta", delta: event.text },
2206
- } as unknown as AgentEvent);
2207
- break;
2208
- }
2209
- case "reasoning_delta":
2210
- forward({
2211
- type: "message_update",
2212
- message: { role: "assistant", content: [] },
2213
- assistantMessageEvent: { type: "thinking_delta", delta: event.text },
2214
- } as unknown as AgentEvent);
2215
- break;
2216
- case "tool_started":
2217
- externalToolNames.set(event.id, event.name);
2218
- forward({
2219
- type: "tool_execution_start",
2220
- toolCallId: event.id,
2221
- toolName: event.name,
2222
- args: isRecord(event.input) ? event.input : {},
2223
- } as unknown as AgentEvent);
2224
- break;
2225
- case "tool_completed": {
2226
- const toolName = externalToolNames.get(event.id) ?? "external-tool";
2227
- externalToolNames.delete(event.id);
2228
- forward({
2229
- type: "tool_execution_end",
2230
- toolCallId: event.id,
2231
- toolName,
2232
- args: {},
2233
- result: { content: [{ type: "text", text: event.output }], details: {} },
2234
- isError: event.isError,
2235
- } as unknown as AgentEvent);
2236
- break;
2237
- }
2238
- case "file_changed":
2239
- progress.lastIntent = \`changed \${event.path}\`;
2240
- scheduleProgress();
2241
- break;
2242
- case "usage":
2243
- hasExternalUsage = true;
2244
- externalUsage.input += event.input;
2245
- externalUsage.cachedInput += event.cachedInput;
2246
- externalUsage.cacheWrite += event.cacheWrite;
2247
- externalUsage.output += event.output;
2248
- break;
2249
- case "turn_completed": {
2250
- const report = event.text.trim() ? event.text : externalStreamText;
2251
- const finalText = \`[external-engine: \${engine}]\${report ? \`\\n\\n\${report}\` : ""}\`;
2252
- const usage = hasExternalUsage
2253
- ? {
2254
- input: externalUsage.input,
2255
- output: externalUsage.output,
2256
- cacheRead: externalUsage.cachedInput,
2257
- cacheWrite: externalUsage.cacheWrite,
2258
- totalTokens:
2259
- externalUsage.input +
2260
- externalUsage.cachedInput +
2261
- externalUsage.cacheWrite +
2262
- externalUsage.output,
2263
- reasoningTokens: 0,
2264
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
2265
- }
2266
- : undefined;
2267
- const message = {
2268
- role: "assistant",
2269
- content: [{ type: "text", text: finalText }],
2270
- ...(usage ? { usage } : {}),
2271
- };
2272
- forward({ type: "message_end", message } as unknown as AgentEvent);
2273
- forward({ type: "agent_end", messages: [message] } as unknown as AgentEvent);
2274
- if (event.stopReason === "stop") {
2275
- recordExtractedToolData("yield", { useLastTurn: true });
2276
- }
2277
- break;
2278
- }
2279
- case "error":
2280
- progress.lastIntent = "external-engine error";
2281
- scheduleProgress(true);
2282
- break;
2283
- }
2284
- };
2285
-
2286
- const publishAdvisorState = (session: AgentSession | null): void => {`,
2287
- },
2288
- {
2289
- file: "src/task/executor.ts",
2290
- marker: "\t\tacceptExternalEvent,",
2291
- anchor: ` attach,
2292
- captureSalvage,`,
2293
- patched: ` attach,
2294
- acceptExternalEvent,
2295
- captureSalvage,`,
2296
- },
2297
- {
2298
- file: "src/task/executor.ts",
2299
- marker: "Running maker with external engine",
2300
- anchor: ` const sessionManagerPromise = sessionFile
2301
- ? SessionManager.open(sessionFile, undefined, undefined, {
2302
- initialCwd: effectiveCwd,
2303
- parentSession: options.sessionFile ?? undefined,
2304
- suppressBreadcrumb: true,
2305
- })
2306
- : Promise.resolve(SessionManager.inMemory(effectiveCwd));`,
2307
- patched: ` const externalSelection = resolveExternalMakerEngine(agent.name, model);
2308
- if (externalSelection) {
2309
- const { engine, runtimeDir } = externalSelection;
2310
- progress.resolvedModelRoute = \`external-engine: \${engine}\`;
2311
- logger.info("Running maker with external engine", {
2312
- id,
2313
- engine,
2314
- provider: model?.provider,
2315
- model: model?.id,
2316
- });
2317
- emitSubagentFrame(options.eventBus, options.subagentEventBus, TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
2318
- id,
2319
- agent: agent.name,
2320
- parentToolCallId: options.parentToolCallId,
2321
- detached: options.detached,
2322
- agentSource: agent.source,
2323
- description: options.description,
2324
- status: "started" as const,
2325
- sessionFile: subtaskSessionFile,
2326
- index,
2327
- });
2328
- const runtime = await awaitAbortable(loadExternalMakerRuntime(engine, runtimeDir));
2329
- const sessionPromise = runtime.createSession({
2330
- cwd: effectiveCwd,
2331
- model: model?.id,
2332
- thinking: effectiveThinkingLevel,
2333
- });
2334
- let externalSession: ExternalMakerSession;
2335
- try {
2336
- externalSession = await awaitAbortable(sessionPromise);
2337
- } catch (err) {
2338
- if (abortSignal.aborted) void sessionPromise.then(session => session.dispose()).catch(() => {});
2339
- throw err;
2340
- }
2341
- let stopReason: "stop" | "aborted" | "error" | undefined;
2342
- let externalError: string | undefined;
2343
- const unsubscribeExternal = externalSession.onEvent(event => {
2344
- if (event.type === "turn_completed") stopReason = event.stopReason;
2345
- if (event.type === "error") externalError = event.message;
2346
- monitor.acceptExternalEvent(event, engine);
2347
- });
2348
- const onExternalAbort = () => {
2349
- void externalSession.abort().catch(err => {
2350
- logger.debug("External maker abort failed", {
2351
- id,
2352
- engine,
2353
- error: err instanceof Error ? err.message : String(err),
2354
- });
2355
- });
2356
- };
2357
- abortSignal.addEventListener("abort", onExternalAbort, { once: true });
2358
- try {
2359
- const externalSystemPrompt = prompt.render(subagentSystemPromptTemplate, {
2360
- agent: agent.systemPrompt,
2361
- context: options.context?.trim() ?? "",
2362
- planReference: options.planReference?.content ?? "",
2363
- planReferencePath: options.planReference?.path ?? "",
2364
- worktree: worktree ?? "",
2365
- outputSchema: undefined,
2366
- outputSchemaOverridesAgent: false,
2367
- workPoolYieldItems: [],
2368
- ircPeers: [],
2369
- ircParkedCount: 0,
2370
- ircOmittedCount: 0,
2371
- ircSelfId: "",
2372
- });
2373
- const externalPrompt =
2374
- \`\${externalSystemPrompt}\\n\\n§ External engine completion\\n\` +
2375
- "Complete the assignment and return the final report as ordinary text in this turn. " +
2376
- "The bridge normalizes that text into the parent task result; the OMP yield tool is unavailable.\\n\\n" +
2377
- \`§ Assignment\\n\${task}\`;
2378
- readyAt = performance.now();
2379
- await awaitAbortable(externalSession.prompt(externalPrompt));
2380
- if (abortSignal.aborted || stopReason === "aborted") {
2381
- return {
2382
- exitCode: 1,
2383
- aborted: true,
2384
- abortReason: monitor.resolveAbortReasonText(),
2385
- durationMs: Date.now() - startTime,
2386
- };
2387
- }
2388
- if (stopReason !== "stop") {
2389
- throw new Error(externalError ?? \`External maker engine \${engine} ended without a successful turn\`);
2390
- }
2391
- return { exitCode: 0, durationMs: Date.now() - startTime };
2392
- } finally {
2393
- abortSignal.removeEventListener("abort", onExternalAbort);
2394
- unsubscribeExternal();
2395
- await externalSession.dispose();
2396
- }
2397
- }
2398
-
2399
- const sessionManagerPromise = sessionFile
2400
- ? SessionManager.open(sessionFile, undefined, undefined, {
2401
- initialCwd: effectiveCwd,
2402
- parentSession: options.sessionFile ?? undefined,
2403
- suppressBreadcrumb: true,
2404
- })
2405
- : Promise.resolve(SessionManager.inMemory(effectiveCwd));`,
2406
- },
2407
- {
2408
- file: "src/task/executor.ts",
2409
- marker: 'const externalEngineResult = progress.resolvedModelRoute?.startsWith("external-engine:") === true;',
2410
- anchor: ` // Breadcrumb the synchronous yield-payload shaping (O(rawOutput)) so a block
2411
- // here is attributed to this subagent rather than logged as "unknown".
2412
- pushLoopPhase(\`subagent:\${id}\`);
2413
- let finalized: FinalizeSubprocessOutputResult;`,
2414
- patched: ` // Breadcrumb the synchronous yield-payload shaping (O(rawOutput)) so a block
2415
- // here is attributed to this subagent rather than logged as "unknown".
2416
- pushLoopPhase(\`subagent:\${id}\`);
2417
- const externalEngineResult = progress.resolvedModelRoute?.startsWith("external-engine:") === true;
2418
- let finalized: FinalizeSubprocessOutputResult;`,
2419
- },
2420
- {
2421
- file: "src/task/executor.ts",
2422
- marker: 'outputSchemaSource: externalEngineResult ? "none" : args.outputSchemaSource,',
2423
- anchor: ` outputSchema: args.outputSchema,
2424
- outputSchemaMode: args.outputSchemaMode,
2425
- outputSchemaSource: args.outputSchemaSource,`,
2426
- patched: ` // Optional runtimes return one ordinary-text turn and do not expose the
2427
- // core yield tool. Keep their result on the normal SingleResult path,
2428
- // but do not claim caller-schema validation that never ran.
2429
- outputSchema: externalEngineResult ? undefined : args.outputSchema,
2430
- outputSchemaMode: externalEngineResult ? undefined : args.outputSchemaMode,
2431
- outputSchemaSource: externalEngineResult ? "none" : args.outputSchemaSource,`,
2432
- },
2433
2210
  // ---- task per-item model selector (Arena hypothesis makers) ----
2434
2211
  // 네이티브 task wire 계약에는 per-spawn 모델 선택기가 없고 모델은 agent 정의에서만
2435
2212
  // 왔다. StructuredSubagentRequest.model → resolveEffectiveSubagentPolicy의
@@ -2438,19 +2215,37 @@ async function loadExternalMakerRuntime(
2438
2215
  // 인증/approved-fallback/재시도 체인·task-guard 상한은 건드리지 않는다.
2439
2216
  // 잘못된 값은 기존 fall-through(다음 소스로 전이)를 따르므로, 엄격한 거부는
2440
2217
  // 호출자(Arena 런타임의 사전 해석)가 맡는다.
2218
+ // 18.5.0 RETIRE: upstream 이 같은 per-call model selector 를 넣었다(string | string[], batch 최상위는 never,
2219
+ // 해석 실패 시 대체 금지; pi-tui task.ts:2156·2185, types.ts 각 schema, index.ts:306·330·820·1631). 아래 11항목은
2220
+ // 18.4.12 본 후보를 유지하고, 18.5.0 에는 upstream 줄 자체의 no-op 후보를 둔다. 본 후보 앵커는 18.5.0 이 바로 뒤에
2221
+ // 넣은 upstream 줄과 겹치지 않는 범위까지 넓혀 새 판 순정에서 성립하지 않게 한다. index.ts 의 preflight·launch 두
2222
+ // 줄은 18.5.0 순정과 18.4.12 적용본이 국소 문맥까지 같아 같은 파일의 18.5.0 전용 import(invalidModelSelectorReason)
2223
+ // 유무로 가른다. Maker 단일 모델은 maker-routing guard(`typeof item.model === "string"`)가 계속 막는다.
2441
2224
  {
2442
2225
  // 18.2.5: upstream 이 TaskItem 을 @oh-my-pi/pi-tui 로 옮기고 effort 타입을 인라인했다.
2443
2226
  file: "../pi-tui/src/tools/task.ts",
2444
2227
  marker: "Standard model selector for this spawn (item)",
2445
- anchor: "\t/** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */\n\teffort?: \"lo\" | \"med\" | \"hi\";",
2446
- patched: "\t/** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/**\n\t * Standard model selector for this spawn (item), e.g. \"provider/model-id:max\".\n\t * Forwards to the existing subagent model resolution (`requestModel` first,\n\t * then `task.agentModelOverrides`, then the agent definition); omitted\n\t * preserves existing behavior. Unresolvable values fall through to the next\n\t * source per existing precedence, so callers needing strictness must\n\t * pre-resolve. A `:level` suffix pins exact thinking effort unless\n\t * per-spawn `effort` overrides it.\n\t */\n\tmodel?: string;",
2228
+ anchor: "\t/** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Caller-provided output schema;",
2229
+ patched: "\t/** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/**\n\t * Standard model selector for this spawn (item), e.g. \"provider/model-id:max\".\n\t * Forwards to the existing subagent model resolution (`requestModel` first,\n\t * then `task.agentModelOverrides`, then the agent definition); omitted\n\t * preserves existing behavior. Unresolvable values fall through to the next\n\t * source per existing precedence, so callers needing strictness must\n\t * pre-resolve. A `:level` suffix pins exact thinking effort unless\n\t * per-spawn `effort` overrides it.\n\t */\n\tmodel?: string;\n\t/** Caller-provided output schema;",
2230
+ alternates: [{
2231
+ file: "../pi-tui/src/tools/task.ts",
2232
+ marker: "\t/** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Per-spawn model selector or ordered selector array; overrides agent and settings preferences. */\n\tmodel?: string | string[];",
2233
+ anchor: "\t/** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Per-spawn model selector or ordered selector array; overrides agent and settings preferences. */\n\tmodel?: string | string[];",
2234
+ patched: "\t/** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Per-spawn model selector or ordered selector array; overrides agent and settings preferences. */\n\tmodel?: string | string[];",
2235
+ }],
2447
2236
  },
2448
2237
  {
2449
2238
  // 18.2.5: upstream 이 TaskParams 를 @oh-my-pi/pi-tui 로 옮기고 effort 타입을 인라인했다.
2450
2239
  file: "../pi-tui/src/tools/task.ts",
2451
2240
  marker: "Standard model selector for this spawn (flat form)",
2452
- anchor: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";",
2453
- patched: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/**\n\t * Standard model selector for this spawn (flat form), e.g. \"provider/model-id:max\".\n\t * Same forwarding and fall-through semantics as the batch item field.\n\t */\n\tmodel?: string;",
2241
+ anchor: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Caller-provided output schema;",
2242
+ patched: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/**\n\t * Standard model selector for this spawn (flat form), e.g. \"provider/model-id:max\".\n\t * Same forwarding and fall-through semantics as the batch item field.\n\t */\n\tmodel?: string;\n\t/** Caller-provided output schema;",
2243
+ alternates: [{
2244
+ file: "../pi-tui/src/tools/task.ts",
2245
+ marker: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Per-spawn model selector or ordered selector array; overrides agent and settings preferences. */\n\tmodel?: string | string[];",
2246
+ anchor: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Per-spawn model selector or ordered selector array; overrides agent and settings preferences. */\n\tmodel?: string | string[];",
2247
+ patched: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/** Per-spawn model selector or ordered selector array; overrides agent and settings preferences. */\n\tmodel?: string | string[];",
2248
+ }],
2454
2249
  },
2455
2250
  {
2456
2251
  // 18.3.4는 모든 task schema에 필수 `solutionSpace: "string"` 을 `task` 바로 뒤에 넣었다
@@ -2503,6 +2298,27 @@ const taskItemSchemaIsolated = type({
2503
2298
  "isolated?": "boolean",
2504
2299
  "+": "delete",
2505
2300
  });`,
2301
+ alternates: [{
2302
+ file: "src/task/types.ts",
2303
+ marker: `export const taskItemSchema = type({
2304
+ "name?": "string",
2305
+ agent: "string = 'task'",
2306
+ task: "string",
2307
+ solutionSpace: "string",
2308
+ "model?": "string | string[]",`,
2309
+ anchor: `export const taskItemSchema = type({
2310
+ "name?": "string",
2311
+ agent: "string = 'task'",
2312
+ task: "string",
2313
+ solutionSpace: "string",
2314
+ "model?": "string | string[]",`,
2315
+ patched: `export const taskItemSchema = type({
2316
+ "name?": "string",
2317
+ agent: "string = 'task'",
2318
+ task: "string",
2319
+ solutionSpace: "string",
2320
+ "model?": "string | string[]",`,
2321
+ }],
2506
2322
  },
2507
2323
  {
2508
2324
  file: "src/task/types.ts",
@@ -2525,6 +2341,27 @@ const taskItemSchemaIsolated = type({
2525
2341
  solutionSpace: "string",
2526
2342
  "model?": "string",
2527
2343
  "outputSchema?": outputSchemaInputSchema,`,
2344
+ alternates: [{
2345
+ file: "src/task/types.ts",
2346
+ marker: `export const taskSchema = type({
2347
+ "name?": "string",
2348
+ agent: "string = 'task'",
2349
+ task: "string",
2350
+ solutionSpace: "string",
2351
+ "model?": "string | string[]",`,
2352
+ anchor: `export const taskSchema = type({
2353
+ "name?": "string",
2354
+ agent: "string = 'task'",
2355
+ task: "string",
2356
+ solutionSpace: "string",
2357
+ "model?": "string | string[]",`,
2358
+ patched: `export const taskSchema = type({
2359
+ "name?": "string",
2360
+ agent: "string = 'task'",
2361
+ task: "string",
2362
+ solutionSpace: "string",
2363
+ "model?": "string | string[]",`,
2364
+ }],
2528
2365
  },
2529
2366
  {
2530
2367
  file: "src/task/types.ts",
@@ -2547,6 +2384,27 @@ const taskItemSchemaIsolated = type({
2547
2384
  solutionSpace: "string",
2548
2385
  "model?": "string",
2549
2386
  "outputSchema?": outputSchemaInputSchema,`,
2387
+ alternates: [{
2388
+ file: "src/task/types.ts",
2389
+ marker: `const taskSchemaNoIsolation = type({
2390
+ "name?": "string",
2391
+ agent: "string = 'task'",
2392
+ task: "string",
2393
+ solutionSpace: "string",
2394
+ "model?": "string | string[]",`,
2395
+ anchor: `const taskSchemaNoIsolation = type({
2396
+ "name?": "string",
2397
+ agent: "string = 'task'",
2398
+ task: "string",
2399
+ solutionSpace: "string",
2400
+ "model?": "string | string[]",`,
2401
+ patched: `const taskSchemaNoIsolation = type({
2402
+ "name?": "string",
2403
+ agent: "string = 'task'",
2404
+ task: "string",
2405
+ solutionSpace: "string",
2406
+ "model?": "string | string[]",`,
2407
+ }],
2550
2408
  },
2551
2409
  {
2552
2410
  file: "src/task/types.ts",
@@ -2585,6 +2443,18 @@ const taskItemSchemaIsolated = type({
2585
2443
  "isolated?": "boolean",
2586
2444
  "+": "delete",
2587
2445
  });`,
2446
+ alternates: [{
2447
+ file: "src/task/types.ts",
2448
+ marker: ` solutionSpace: "string",
2449
+ ...effortField,
2450
+ "model?": "string | string[]",`,
2451
+ anchor: ` solutionSpace: "string",
2452
+ ...effortField,
2453
+ "model?": "string | string[]",`,
2454
+ patched: ` solutionSpace: "string",
2455
+ ...effortField,
2456
+ "model?": "string | string[]",`,
2457
+ }],
2588
2458
  },
2589
2459
  {
2590
2460
  file: "src/task/types.ts",
@@ -2618,23 +2488,92 @@ const taskItemSchemaIsolated = type({
2618
2488
  ...toolsField,
2619
2489
  "+": "delete",
2620
2490
  });`,
2491
+ alternates: [{
2492
+ file: "src/task/types.ts",
2493
+ marker: ` solutionSpace: "string",
2494
+ ...effortField,
2495
+ "model?": "string | string[]",`,
2496
+ anchor: ` solutionSpace: "string",
2497
+ ...effortField,
2498
+ "model?": "string | string[]",`,
2499
+ patched: ` solutionSpace: "string",
2500
+ ...effortField,
2501
+ "model?": "string | string[]",`,
2502
+ }],
2621
2503
  },
2622
2504
  {
2623
2505
  file: "src/task/index.ts",
2624
- marker: "item.model = params.model;",
2625
- anchor: ` const item: TaskItem = { name: params.name, agent: params.agent, task: params.task };`,
2626
- patched: ` const item: TaskItem = { name: params.name, agent: params.agent, task: params.task };
2506
+ marker: ` const item: TaskItem = { name: params.name, agent: params.agent, task: params.task };
2627
2507
  if ("model" in params) item.model = params.model;`,
2508
+ anchor: ` const item: TaskItem = { name: params.name, agent: params.agent, task: params.task };
2509
+ if ("solutionSpace" in params) item.solutionSpace = params.solutionSpace;
2510
+ if ("outputSchema" in params) item.outputSchema = params.outputSchema;
2511
+ if ("schemaMode" in params) item.schemaMode = params.schemaMode;
2512
+ if ("tools" in params) item.tools = params.tools;
2513
+ if ("effort" in params) item.effort = params.effort;
2514
+ if ("isolated" in params) item.isolated = params.isolated;`,
2515
+ patched: ` const item: TaskItem = { name: params.name, agent: params.agent, task: params.task };
2516
+ if ("model" in params) item.model = params.model;
2517
+ if ("solutionSpace" in params) item.solutionSpace = params.solutionSpace;
2518
+ if ("outputSchema" in params) item.outputSchema = params.outputSchema;
2519
+ if ("schemaMode" in params) item.schemaMode = params.schemaMode;
2520
+ if ("tools" in params) item.tools = params.tools;
2521
+ if ("effort" in params) item.effort = params.effort;
2522
+ if ("isolated" in params) item.isolated = params.isolated;`,
2523
+ alternates: [{
2524
+ file: "src/task/index.ts",
2525
+ marker: ` if ("effort" in params) item.effort = params.effort;
2526
+ if ("model" in params) item.model = params.model;
2527
+ if ("isolated" in params) item.isolated = params.isolated;`,
2528
+ anchor: ` if ("effort" in params) item.effort = params.effort;
2529
+ if ("model" in params) item.model = params.model;
2530
+ if ("isolated" in params) item.isolated = params.isolated;`,
2531
+ patched: ` if ("effort" in params) item.effort = params.effort;
2532
+ if ("model" in params) item.model = params.model;
2533
+ if ("isolated" in params) item.isolated = params.isolated;`,
2534
+ }],
2628
2535
  },
2629
2536
  {
2630
2537
  file: "src/task/index.ts",
2631
- marker: "spawn.model = item.model;",
2632
- anchor: ` const spawn: TaskParams = { agent: item.agent?.trim() || defaultAgent };`,
2633
- patched: ` const spawn: TaskParams = { agent: item.agent?.trim() || defaultAgent };
2538
+ marker: ` const spawn: TaskParams = { agent: item.agent?.trim() || defaultAgent };
2634
2539
  if ("model" in item) spawn.model = item.model;`,
2540
+ anchor: ` const spawn: TaskParams = { agent: item.agent?.trim() || defaultAgent };
2541
+ if (item.name !== undefined) spawn.name = item.name;
2542
+ if (item.task !== undefined) spawn.task = item.task;
2543
+ if (item.solutionSpace !== undefined) spawn.solutionSpace = item.solutionSpace;
2544
+ if (params.context !== undefined) spawn.context = params.context;
2545
+ if ("outputSchema" in item) spawn.outputSchema = item.outputSchema;
2546
+ if ("schemaMode" in item) spawn.schemaMode = item.schemaMode;
2547
+ if ("tools" in item) spawn.tools = item.tools;
2548
+ if ("effort" in item) spawn.effort = item.effort;
2549
+ if (item.isolated !== undefined) {`,
2550
+ patched: ` const spawn: TaskParams = { agent: item.agent?.trim() || defaultAgent };
2551
+ if ("model" in item) spawn.model = item.model;
2552
+ if (item.name !== undefined) spawn.name = item.name;
2553
+ if (item.task !== undefined) spawn.task = item.task;
2554
+ if (item.solutionSpace !== undefined) spawn.solutionSpace = item.solutionSpace;
2555
+ if (params.context !== undefined) spawn.context = params.context;
2556
+ if ("outputSchema" in item) spawn.outputSchema = item.outputSchema;
2557
+ if ("schemaMode" in item) spawn.schemaMode = item.schemaMode;
2558
+ if ("tools" in item) spawn.tools = item.tools;
2559
+ if ("effort" in item) spawn.effort = item.effort;
2560
+ if (item.isolated !== undefined) {`,
2561
+ alternates: [{
2562
+ file: "src/task/index.ts",
2563
+ marker: ` if ("effort" in item) spawn.effort = item.effort;
2564
+ if ("model" in item) spawn.model = item.model;
2565
+ if (item.isolated !== undefined) {`,
2566
+ anchor: ` if ("effort" in item) spawn.effort = item.effort;
2567
+ if ("model" in item) spawn.model = item.model;
2568
+ if (item.isolated !== undefined) {`,
2569
+ patched: ` if ("effort" in item) spawn.effort = item.effort;
2570
+ if ("model" in item) spawn.model = item.model;
2571
+ if (item.isolated !== undefined) {`,
2572
+ }],
2635
2573
  },
2636
2574
  {
2637
2575
  file: "src/task/index.ts",
2576
+ excludes: "invalidModelSelectorReason",
2638
2577
  marker: `...(params.model !== undefined ? { model: params.model } : {}),
2639
2578
  ...("isolated" in params`,
2640
2579
  anchor: ` ...(params.effort !== undefined ? { effort: params.effort } : {}),
@@ -2642,9 +2581,23 @@ const taskItemSchemaIsolated = type({
2642
2581
  patched: ` ...(params.effort !== undefined ? { effort: params.effort } : {}),
2643
2582
  ...(params.model !== undefined ? { model: params.model } : {}),
2644
2583
  ...("isolated" in params ? { isolation: { requested: params.isolated } } : {}),`,
2584
+ alternates: [{
2585
+ file: "src/task/index.ts",
2586
+ requires: "invalidModelSelectorReason",
2587
+ marker: ` ...(params.effort !== undefined ? { effort: params.effort } : {}),
2588
+ ...(params.model !== undefined ? { model: params.model } : {}),
2589
+ ...("isolated" in params ? { isolation: { requested: params.isolated } } : {}),`,
2590
+ anchor: ` ...(params.effort !== undefined ? { effort: params.effort } : {}),
2591
+ ...(params.model !== undefined ? { model: params.model } : {}),
2592
+ ...("isolated" in params ? { isolation: { requested: params.isolated } } : {}),`,
2593
+ patched: ` ...(params.effort !== undefined ? { effort: params.effort } : {}),
2594
+ ...(params.model !== undefined ? { model: params.model } : {}),
2595
+ ...("isolated" in params ? { isolation: { requested: params.isolated } } : {}),`,
2596
+ }],
2645
2597
  },
2646
2598
  {
2647
2599
  file: "src/task/index.ts",
2600
+ excludes: "invalidModelSelectorReason",
2648
2601
  marker: `...(params.model !== undefined ? { model: params.model } : {}),
2649
2602
  ...(params.tools?.length`,
2650
2603
  // 18.3.4는 effort 다음 줄에 `solutionSpace: params.solutionSpace,` 를 넣었다(index.ts:1506).
@@ -2655,6 +2608,19 @@ const taskItemSchemaIsolated = type({
2655
2608
  solutionSpace: params.solutionSpace,
2656
2609
  ...(params.model !== undefined ? { model: params.model } : {}),
2657
2610
  ...(params.tools?.length`,
2611
+ alternates: [{
2612
+ file: "src/task/index.ts",
2613
+ requires: "invalidModelSelectorReason",
2614
+ marker: ` solutionSpace: params.solutionSpace,
2615
+ ...(params.model !== undefined ? { model: params.model } : {}),
2616
+ ...(params.tools?.length`,
2617
+ anchor: ` solutionSpace: params.solutionSpace,
2618
+ ...(params.model !== undefined ? { model: params.model } : {}),
2619
+ ...(params.tools?.length`,
2620
+ patched: ` solutionSpace: params.solutionSpace,
2621
+ ...(params.model !== undefined ? { model: params.model } : {}),
2622
+ ...(params.tools?.length`,
2623
+ }],
2658
2624
  },
2659
2625
  {
2660
2626
  // 18.3.3 BLOCK: upstream 은 `task`·`bash` 를 가진 SubAgent 정의에 `wait` 를 자동으로 더한다
@@ -2730,7 +2696,8 @@ import { isUnexpectedSocketCloseMessage } from "@oh-my-pi/pi-utils/fetch-retry";
2730
2696
  },
2731
2697
  {
2732
2698
  file: "src/session/turn-recovery.ts",
2733
- marker: `!baiTransportSameModelRetry`,
2699
+ marker: `!(retryBudgetExhausted && classifierRefusal) &&
2700
+ !baiTransportSameModelRetry`,
2734
2701
  anchor: ` if (
2735
2702
  allowModelFallback &&
2736
2703
  retrySettings.modelFallback &&
@@ -2748,6 +2715,23 @@ import { isUnexpectedSocketCloseMessage } from "@oh-my-pi/pi-utils/fetch-retry";
2748
2715
  !baiTransportSameModelRetry
2749
2716
  ) {
2750
2717
  `,
2718
+ // 18.5.0: upstream 이 같은 조건 끝에 첫 시도·스트리밍 진행이 있는 socket drop 의 같은 모델 1회 재시도를 넣었다
2719
+ // (turn-recovery.ts:1623-1636·2630). 합집합으로 둔다: upstream 조건은 그대로 두고 b-ai 전송 장애(2회차 이후,
2720
+ // 내용 없는 끊김, stream read 오류)만 뒤에 덧붙인다.
2721
+ alternates: [{
2722
+ file: "src/session/turn-recovery.ts",
2723
+ marker: `!this.#isFirstAttemptMidStreamSocketDrop(message, id, retryBudgetExhausted) &&
2724
+ !baiTransportSameModelRetry`,
2725
+ anchor: ` !(retryBudgetExhausted && classifierRefusal) &&
2726
+ !this.#isFirstAttemptMidStreamSocketDrop(message, id, retryBudgetExhausted)
2727
+ ) {
2728
+ `,
2729
+ patched: ` !(retryBudgetExhausted && classifierRefusal) &&
2730
+ !this.#isFirstAttemptMidStreamSocketDrop(message, id, retryBudgetExhausted) &&
2731
+ !baiTransportSameModelRetry
2732
+ ) {
2733
+ `,
2734
+ }],
2751
2735
  },
2752
2736
  {
2753
2737
  file: "src/registry/agent-lifecycle.ts",
@@ -7618,10 +7602,10 @@ export interface MCPDeferredServer {
7618
7602
  // MCP 선택 연결: 매 사용자 요청의 system prompt 전에 mcp_select를 보내 고른 deferred 서버만 연결하고 노출 subset을 적용한다. 실패는 새 연결 0.
7619
7603
  file: "src/session/agent-session.ts",
7620
7604
  marker: `import type { MCPManager } from "../mcp/manager";`,
7621
- anchor: `} from "@oh-my-pi/pi-utils";
7622
- import type { AdvisorConfig } from "@oh-my-pi/pi-tui/overlays/advisor-config";`,
7623
- patched: `} from "@oh-my-pi/pi-utils";
7624
- import type { MCPManager } from "../mcp/manager";
7605
+ // 18.5.0: upstream 이 pi-utils import 블록 바로 뒤에 `@oh-my-pi/pi-utils/ar` import 를 넣어 두 줄 앵커가 끊겼다.
7606
+ // AdvisorConfig import 한 줄(두 판 모두 파일 안 1회)만 잡는다. 적용 결과 바이트는 18.4.12 와 같다.
7607
+ anchor: `import type { AdvisorConfig } from "@oh-my-pi/pi-tui/overlays/advisor-config";`,
7608
+ patched: `import type { MCPManager } from "../mcp/manager";
7625
7609
  import type { AdvisorConfig } from "@oh-my-pi/pi-tui/overlays/advisor-config";`,
7626
7610
  },
7627
7611
  {
@@ -8314,11 +8298,14 @@ function parentSubagentServiceTiers(
8314
8298
  // 18.4.10(#14019)은 이 행에 prefixBinding을 넣었지만 binding controls는 false로 남긴다. 18.4.10 후보는 binding controls만 켠다.
8315
8299
  // 그 결과가 18.4.6 적용본과 바이트까지 같으면 두 후보가 함께 applied(ambiguous)이고, 문맥으로도 가를 수 없다(가장 가까운
8316
8300
  // 18.4.6↔18.4.10 차이가 앵커 앞 53K자·뒤 249K자). 그래서 18.4.10 결과만 `": true"` 공백으로 구분한다. JSON.parse는 이 공백을 무시한다.
8301
+ // 18.4.12: 같은 행의 측정값 tps가 95.2→92.9로 바뀌어 18.4.10 앵커가 사라졌다. thinking(prefixBinding true)·compat(binding
8302
+ // controls false)는 18.4.10과 같으므로 본 후보는 18.4.10 후보에서 tps만 바꾼다. 두 판 결과는 tps 뒤가 같아 예전 marker(tps
8303
+ // 뒤 조각)로는 둘 다 applied(ambiguous)가 되므로, 18.4.12·18.4.10 후보 모두 tps를 포함한 patched 전체를 marker로 쓴다.
8317
8304
  file: "../pi-catalog/src/models.json",
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],
8305
+ marker: OPUS55_ROW_18410.patched.replace(OPUS55_TPS_18410, OPUS55_TPS_18412),
8306
+ anchor: OPUS55_ROW_18410.anchor.replace(OPUS55_TPS_18410, OPUS55_TPS_18412),
8307
+ patched: OPUS55_ROW_18410.patched.replace(OPUS55_TPS_18410, OPUS55_TPS_18412),
8308
+ alternates: [OPUS55_ROW_18410, OPUS55_ROW_1846],
8322
8309
  },
8323
8310
  {
8324
8311
  // 모든 provider의 Opus 5.5 규칙 계보에 prefixBinding(Sonnet 5.5 규칙과 같은 모양). 18.4.10은 upstream #14019가 kdl:61에
@@ -8516,23 +8503,45 @@ function parentSubagentServiceTiers(
8516
8503
  // 파일은 읽기만 한다. 웹 route는 번들 `dist/cli.js --export`를 먼저 실행하므로 번들의 같은 함수에도 같은 조건을
8517
8504
  // 넣는다(min 식별자까지 정확히 일치하는 18.4.5 앵커, 버전이 바뀌면 조용히 어긋나지 않고 anchor-lost).
8518
8505
  // 회귀: patches/core-export-advisor-test.ts(실제 SDK exportFromFile과 bun dist/cli.js --export의 HTML).
8506
+ // 18.5.0: upstream 이 같은 수집을 `session/sub-sessions.ts` 로 옮기고 그 루프에서 isAdvisorTranscriptName 으로
8507
+ // 거른다(sub-sessions.ts:11·61, 번들도 같은 조건). 세 항목 모두 그 upstream 줄의 no-op 후보를 둔다(RETIRE).
8508
+ // 본 후보 앵커는 18.5.0 에서 지워진 loadEntriesFromFile import 를 함께 잡아 새 판 순정에서 성립하지 않게 한다.
8519
8509
  {
8520
8510
  file: "src/export/html/index.ts",
8521
8511
  marker: 'import { isAdvisorTranscriptName } from "../../advisor/transcript-recorder";',
8522
- anchor: 'import type { SessionEntry, SessionHeader } from "../../session/session-entries";',
8523
- patched: 'import { isAdvisorTranscriptName } from "../../advisor/transcript-recorder";\nimport type { SessionEntry, SessionHeader } from "../../session/session-entries";',
8512
+ anchor: 'import type { SessionEntry, SessionHeader } from "../../session/session-entries";\nimport { loadEntriesFromFile } from "../../session/session-loader";',
8513
+ patched: 'import { isAdvisorTranscriptName } from "../../advisor/transcript-recorder";\nimport type { SessionEntry, SessionHeader } from "../../session/session-entries";\nimport { loadEntriesFromFile } from "../../session/session-loader";',
8514
+ alternates: [{
8515
+ file: "src/session/sub-sessions.ts",
8516
+ marker: 'import { isAdvisorTranscriptName } from "../advisor/transcript-recorder";',
8517
+ anchor: 'import { isAdvisorTranscriptName } from "../advisor/transcript-recorder";',
8518
+ patched: 'import { isAdvisorTranscriptName } from "../advisor/transcript-recorder";',
8519
+ }],
8524
8520
  },
8525
8521
  {
8526
8522
  file: "src/export/html/index.ts",
8527
8523
  marker: ' // Advisor transcripts share this directory but are the advisor\'s own prompts and reviews, not subagents.\n',
8528
8524
  anchor: ' if (!name.endsWith(".jsonl") || name.includes(".bak")) continue;\n',
8529
8525
  patched: ' if (!name.endsWith(".jsonl") || name.includes(".bak")) continue;\n // Advisor transcripts share this directory but are the advisor\'s own prompts and reviews, not subagents.\n if (isAdvisorTranscriptName(name)) continue;\n',
8526
+ alternates: [{
8527
+ file: "src/session/sub-sessions.ts",
8528
+ marker: ' if (!name.endsWith(".jsonl") || name.includes(".bak") || isAdvisorTranscriptName(name)) continue;\n',
8529
+ anchor: ' if (!name.endsWith(".jsonl") || name.includes(".bak") || isAdvisorTranscriptName(name)) continue;\n',
8530
+ patched: ' if (!name.endsWith(".jsonl") || name.includes(".bak") || isAdvisorTranscriptName(name)) continue;\n',
8531
+ }],
8530
8532
  },
8531
8533
  {
8532
8534
  file: "dist/cli.js",
8533
8535
  marker: 'if(!o.endsWith(".jsonl")||o.includes(".bak")||o==="__advisor.jsonl"||o.startsWith("__advisor.")&&o.endsWith(".jsonl"))continue;let r=o.slice(0,-6),',
8534
8536
  anchor: 'if(!o.endsWith(".jsonl")||o.includes(".bak"))continue;let r=o.slice(0,-6),',
8535
8537
  patched: 'if(!o.endsWith(".jsonl")||o.includes(".bak")||o==="__advisor.jsonl"||o.startsWith("__advisor.")&&o.endsWith(".jsonl"))continue;let r=o.slice(0,-6),',
8538
+ // 18.5.0 번들의 upstream 조건(XTe = isAdvisorTranscriptName) 자체의 no-op.
8539
+ alternates: [{
8540
+ file: "dist/cli.js",
8541
+ marker: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||XTe(i))continue;let a=i.slice(0,-6),',
8542
+ anchor: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||XTe(i))continue;let a=i.slice(0,-6),',
8543
+ patched: 'if(!i.endsWith(".jsonl")||i.includes(".bak")||XTe(i))continue;let a=i.slice(0,-6),',
8544
+ }],
8536
8545
  },
8537
8546
  // 18.4.5 는 /ratchet 을 새로 넣으면서 `src/ratchet/prelude.ts`(코드)와 `prelude.js`(eval 텍스트 자산)를 같은 stem 으로
8538
8547
  // 두고 sdk.ts 가 `./ratchet/prelude` 로 import 한다. Bun 1.4.2 는 node_modules 안의 확장자 없는 import 를 .js 부터
@@ -8977,20 +8986,23 @@ class HarmonyLeakInterruption extends Error {`,
8977
8986
  // 비교·치환이 어긋나지 않는다. core 파일 자체의 줄 끝은 건드리지 않는다.
8978
8987
  for (const entry of EDITS) {
8979
8988
  for (const candidate of [entry, ...(entry.alternates ?? [])]) {
8980
- for (const key of ["anchor", "marker", "patched", "legacyPatched"]) {
8989
+ for (const key of ["anchor", "marker", "patched", "legacyPatched", "requires", "excludes"]) {
8981
8990
  if (typeof candidate[key] === "string") candidate[key] = candidate[key].replaceAll("\r\n", "\n");
8982
8991
  }
8983
8992
  }
8984
8993
  }
8985
8994
 
8986
8995
 
8987
-
8988
8996
  /**
8989
8997
  * 항목 하나를 이 설치에서 성립하는 후보로 좁힌다. 후보는 항목 자신과 `alternates` 이고 각 후보는
8990
8998
  * file·anchor·marker·patched 묶음이다. upstream 이 그 조각을 다른 모듈이나 다른 패키지로 옮기면
8991
8999
  * 옛 설치와 새 설치에서 앵커의 파일과 문구가 갈라지므로, 같은 목적을 양쪽 설치에 그대로 적용하려면
8992
9000
  * 후보가 필요하다(18.2.5의 pi-coding-agent → pi-tui 이동). 성립하는 후보는 정확히 하나여야 한다.
8993
9001
  * 둘 이상이면 어느 쪽이 정본인지 알 수 없으므로 한쪽을 조용히 고르지 않고 실패한다.
9002
+ * 선택 키 `requires`·`excludes` 는 같은 파일 안에 그 문자열이 있어야/없어야 후보로 본다. 옛 판의 적용본과
9003
+ * 새 판의 순정이 국소 문맥까지 바이트가 같을 때만 쓴다(18.5.0 task/index.ts 의 model 전달: upstream 이 우리
9004
+ * 패치와 같은 줄을 넣어 marker 만으로는 "옛 판 적용본"과 "새 판 순정"을 가를 수 없고, 잘못 가르면 --revert 가
9005
+ * upstream 줄을 지운다). 조건은 marker·anchor 판별 전에 적용하고, 남은 후보 수 규칙은 그대로다.
8994
9006
  */
8995
9007
  function resolveEdit(entry, target) {
8996
9008
  const candidates = [entry, ...(entry.alternates ?? [])];
@@ -8999,6 +9011,8 @@ function resolveEdit(entry, target) {
8999
9011
  const live = present
9000
9012
  .map(candidate => {
9001
9013
  const text = readFileSync(join(target, candidate.file), "utf8");
9014
+ if (candidate.requires !== undefined && !text.includes(candidate.requires)) return undefined;
9015
+ if (candidate.excludes !== undefined && text.includes(candidate.excludes)) return undefined;
9002
9016
  if (candidate.legacyPatched && text.includes(candidate.legacyPatched)) return { ...candidate, path: join(target, candidate.file), text, status: "legacy" };
9003
9017
  if (text.includes(candidate.marker)) return { ...candidate, path: join(target, candidate.file), text, status: "applied" };
9004
9018
  if (text.includes(candidate.anchor)) return { ...candidate, path: join(target, candidate.file), text, status: "appliable" };