cuelo 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -8
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +27 -3
  5. package/.next/react-loadable-manifest.json +13 -3
  6. package/.next/required-server-files.js +2 -2
  7. package/.next/required-server-files.json +2 -2
  8. package/.next/routes-manifest.json +6 -0
  9. package/.next/server/app/_global-error/page.js +3 -3
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/server/app/_not-found/page.js +2 -2
  20. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/server/app/_not-found.html +1 -1
  22. package/.next/server/app/_not-found.rsc +3 -3
  23. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  24. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  26. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  28. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  29. package/.next/server/app/api/agent/[id]/account/route.js +1 -1
  30. package/.next/server/app/api/agent/[id]/bash-output/route.js +1 -1
  31. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  32. package/.next/server/app/api/agent/[id]/route.js +1 -1
  33. package/.next/server/app/api/agent/new/route.js +1 -1
  34. package/.next/server/app/api/agent/running/route.js +1 -1
  35. package/.next/server/app/api/attachments/route.js +2 -2
  36. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  37. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  38. package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
  39. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  40. package/.next/server/app/api/auth/providers/route.js +1 -1
  41. package/.next/server/app/api/cwd/browse/route.js +1 -1
  42. package/.next/server/app/api/cwd/validate/route.js +1 -1
  43. package/.next/server/app/api/default-cwd/route.js +1 -1
  44. package/.next/server/app/api/display-settings/route.js +1 -1
  45. package/.next/server/app/api/document-text/route.js +2 -2
  46. package/.next/server/app/api/experiments/route.js +1 -1
  47. package/.next/server/app/api/file-index/route.js +1 -1
  48. package/.next/server/app/api/files/[...path]/route.js +1 -1
  49. package/.next/server/app/api/git/diff/route.js +1 -1
  50. package/.next/server/app/api/git/status/route.js +2 -2
  51. package/.next/server/app/api/gpt6/handles/route.js +1 -1
  52. package/.next/server/app/api/gpt6/reply/route.js +1 -1
  53. package/.next/server/app/api/gpt6/token/route.js +1 -1
  54. package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
  55. package/.next/server/app/api/home/route.js +1 -1
  56. package/.next/server/app/api/live/call/[callId]/route.js +1 -1
  57. package/.next/server/app/api/live/character-voice/route.js +1 -1
  58. package/.next/server/app/api/live/events/route.js +2 -2
  59. package/.next/server/app/api/live/offer/route.js +1 -1
  60. package/.next/server/app/api/live/voices/route.js +1 -1
  61. package/.next/server/app/api/lounge/events/route.js +2 -2
  62. package/.next/server/app/api/lounge/route.js +1 -1
  63. package/.next/server/app/api/mcp/route.js +2 -2
  64. package/.next/server/app/api/model-roles/route.js +1 -1
  65. package/.next/server/app/api/models/route.js +1 -1
  66. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  67. package/.next/server/app/api/models-config/discover/route.js +1 -1
  68. package/.next/server/app/api/models-config/route.js +3 -3
  69. package/.next/server/app/api/models-config/test/route.js +1 -1
  70. package/.next/server/app/api/plugins/route.js +1 -1
  71. package/.next/server/app/api/project-trust/route.js +1 -1
  72. package/.next/server/app/api/projects/route.js +2 -2
  73. package/.next/server/app/api/runs/[runId]/route.js +1 -1
  74. package/.next/server/app/api/runs/route.js +1 -1
  75. package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
  76. package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
  77. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +1 -1
  78. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  79. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  80. package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
  81. package/.next/server/app/api/sessions/route.js +1 -1
  82. package/.next/server/app/api/sessions/search/route.js +1 -1
  83. package/.next/server/app/api/settings/route.js +1 -1
  84. package/.next/server/app/api/sidecars/[service]/[...path]/route.js +1 -1
  85. package/.next/server/app/api/skills/check/route.js +1 -1
  86. package/.next/server/app/api/skills/install/route.js +2 -2
  87. package/.next/server/app/api/skills/route.js +2 -2
  88. package/.next/server/app/api/skills/search/route.js +1 -1
  89. package/.next/server/app/api/skills/update/route.js +1 -1
  90. package/.next/server/app/api/theme/route.js +1 -1
  91. package/.next/server/app/api/tool-image/route.js +1 -1
  92. package/.next/server/app/api/update-maintenance/route.js +2 -2
  93. package/.next/server/app/api/updates/route.js +1 -1
  94. package/.next/server/app/api/web-access/recovery/route.js +1 -1
  95. package/.next/server/app/api/web-access/route.js +1 -1
  96. package/.next/server/app/api/web6/consults/route.js +1 -1
  97. package/.next/server/app/api/worktrees/route.js +1 -1
  98. package/.next/server/app/favicon.ico/route.js +1 -1
  99. package/.next/server/app/index.html +1 -1
  100. package/.next/server/app/index.rsc +7 -6
  101. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -4
  102. package/.next/server/app/index.segments/_full.segment.rsc +7 -6
  103. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  104. package/.next/server/app/index.segments/_index.segment.rsc +3 -3
  105. package/.next/server/app/index.segments/_tree.segment.rsc +4 -3
  106. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  107. package/.next/server/app/office-preview/page.js +2 -0
  108. package/.next/server/app/office-preview/page.js.nft.json +1 -0
  109. package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -0
  110. package/.next/server/app/office-preview.html +1 -0
  111. package/.next/server/app/office-preview.meta +16 -0
  112. package/.next/server/app/office-preview.rsc +20 -0
  113. package/.next/server/app/office-preview.segments/_full.segment.rsc +20 -0
  114. package/.next/server/app/office-preview.segments/_head.segment.rsc +6 -0
  115. package/.next/server/app/office-preview.segments/_index.segment.rsc +7 -0
  116. package/.next/server/app/office-preview.segments/_tree.segment.rsc +4 -0
  117. package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +7 -0
  118. package/.next/server/app/office-preview.segments/office-preview.segment.rsc +5 -0
  119. package/.next/server/app/page.js +19 -19
  120. package/.next/server/app/page.js.nft.json +1 -1
  121. package/.next/server/app/page_client-reference-manifest.js +1 -1
  122. package/.next/server/app/recover/page.js +2 -2
  123. package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
  124. package/.next/server/app/recover.html +1 -1
  125. package/.next/server/app/recover.rsc +3 -3
  126. package/.next/server/app/recover.segments/_full.segment.rsc +3 -3
  127. package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
  128. package/.next/server/app/recover.segments/_index.segment.rsc +3 -3
  129. package/.next/server/app/recover.segments/_tree.segment.rsc +2 -2
  130. package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
  131. package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
  132. package/.next/server/app-paths-manifest.json +9 -8
  133. package/.next/server/chunks/205.js +3 -2
  134. package/.next/server/chunks/424.js +1 -1
  135. package/.next/server/chunks/5582.js +2 -2
  136. package/.next/server/chunks/58.js +353 -0
  137. package/.next/server/chunks/6429.js +1 -1
  138. package/.next/server/chunks/7362.js +1 -0
  139. package/.next/server/middleware-build-manifest.js +1 -1
  140. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  141. package/.next/server/pages/404.html +1 -1
  142. package/.next/server/pages/500.html +1 -1
  143. package/.next/server/server-reference-manifest.json +1 -1
  144. package/.next/static/chunks/1937.8900983e70e8e9dc.js +1 -0
  145. package/.next/static/chunks/{3308.34ea31e24088c85f.js → 3308.8b94b38809d1b8b6.js} +1 -1
  146. package/.next/static/chunks/4857.cb0735ea310a39f0.js +1 -0
  147. package/.next/static/chunks/52733464.85a1551044fef357.js +1 -0
  148. package/.next/static/chunks/6110-681afd060dde5270.js +1 -0
  149. package/.next/static/chunks/9304.1f940d490ea5c972.js +1 -0
  150. package/.next/static/chunks/app/{layout-ca001f1e875e854c.js → layout-6aa3580989aedade.js} +1 -1
  151. package/.next/static/chunks/app/office-preview/page-2151040877dfb7cd.js +1 -0
  152. package/.next/static/chunks/app/page-f6f024bab16a7232.js +22 -0
  153. package/.next/static/chunks/b536a0f1.f7e390612229faa9.js +342 -0
  154. package/.next/static/chunks/bd904a5c.b74776e0b23d0607.js +1 -0
  155. package/.next/static/chunks/f5ef5e1c.14b6095722ff4f0b.js +12 -0
  156. package/.next/static/chunks/{framework-32a7466d5cae4a81.js → framework-e405bc515d75023a.js} +1 -1
  157. package/.next/static/chunks/webpack-93d544f82f48c3fd.js +1 -0
  158. package/.next/static/css/67b6e19482963224.css +1 -0
  159. package/.next/static/css/829f44afcc3302e9.css +1 -0
  160. package/.next/static/css/abd3d00cca9457f7.css +1 -0
  161. package/.next/static/vaxcaBF-y6afdFHIrgeuN/_buildManifest.js +1 -0
  162. package/.next/trace +106 -105
  163. package/.next/trace-build +1 -1
  164. package/.next/types/app/office-preview/page.ts +87 -0
  165. package/.next/types/routes.d.ts +2 -1
  166. package/.next/types/validator.ts +9 -0
  167. package/README.md +10 -4
  168. package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
  169. package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +2 -2
  170. package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +22 -11
  171. package/Tools/OMP_Global_Config/agent/extensions/korean-reply-guard.ts +174 -0
  172. package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +43 -16
  173. package/Tools/OMP_Global_Config/agent/rules/verdict.md +3 -0
  174. package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +45 -5
  175. package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +315 -40
  176. package/install.mjs +6 -2
  177. package/package.json +10 -7
  178. package/public/office3d/ASSETS.md +8 -0
  179. package/.next/static/chunks/2619.9aac8983f30c7c8a.js +0 -1
  180. package/.next/static/chunks/52733464.3870f99cc446f9ff.js +0 -1
  181. package/.next/static/chunks/app/page-ce1990eb55a57ad6.js +0 -22
  182. package/.next/static/chunks/webpack-13c58b226bf07c7e.js +0 -1
  183. package/.next/static/css/bb6b220f400250d8.css +0 -1
  184. package/.next/static/css/d0ec5531aef136d6.css +0 -1
  185. package/.next/static/vl7SKOdL2Zksd2mJ3tjOQ/_buildManifest.js +0 -1
  186. /package/.next/static/{vl7SKOdL2Zksd2mJ3tjOQ → vaxcaBF-y6afdFHIrgeuN}/_ssgManifest.js +0 -0
@@ -85,6 +85,8 @@ export interface RoutingDeps {
85
85
  candidates?: (ctx: ExtensionContext, policy: RoutingPolicy) => Promise<Candidate[]>;
86
86
  /** 후보 provider별 계정 잔량. 생략하면 CUELO usage 사이드카(`OMP_USAGE_PORT`, 기본 30142)를 읽는다. */
87
87
  quota?: (providers: string[], signal?: AbortSignal) => Promise<QuotaSnapshot>;
88
+ /** 준비 단계 진단용 단조 시계(ms). 생략하면 performance.now. */
89
+ clock?: () => number;
88
90
  /** 발주 이력. 생략하면 기록·history 요약을 하지 않는다. 실패는 발주를 막지 않는다. */
89
91
  ledger?: RoutingLedger;
90
92
  }
@@ -540,10 +542,11 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
540
542
  // 검사를 통과한 발주의 이력 초안. spawn 성공(noteSpawned) 때만 identity와 함께 ledger에 남긴다.
541
543
  // 새 초안은 항상 소유 계약을 싣는다. 원장의 과거 row만 ownership이 없을 수 있다.
542
544
  type DispatchDraft = Omit<DispatchRecord, "ts" | "ownership" | keyof AttemptIdentity> & { ownership: DispatchOwnership };
543
- const pendingDispatches = new Map<string, DispatchDraft>();
544
- // 검사를 통과했지만 spawn 결과를 아직 받지 못한 task 호출의 소유 예약(toolCallId → 초안). 별도 task 호출의 admission이
545
- // 이 예약과 충돌을 검사한다. spawn 관측(noteSpawned)은 실제 owner 목록으로 넘기며 지우고, spawn 없이 끝난 호출은 releaseCall이 지운다.
546
- const reservations = new Map<string, { key: string; draft: DispatchDraft }[]>();
545
+ // 검사를 통과했지만 spawn 결과를 아직 받지 못한 task 호출의 실행 초안(toolCallId → 그 호출 tasks[]의 itemIndex별 초안).
546
+ // 초안은 이 호출 실행에만 속한다. 같은 이름·같은 계약의 다른 호출도 자기 초안을 갖고, 준비 판단(bindings)은 그대로 재사용한다.
547
+ // 별도 task 호출의 admission이 이 예약과 충돌을 검사한다. spawn 관측(noteSpawned)은 자기 초안만 소비해 실제 owner 목록으로
548
+ // 넘기며 지우고, spawn 없이 끝난 호출은 releaseCall이 자기 예약만 지운다.
549
+ const reservations = new Map<string, { itemIndex: number; key: string; draft: DispatchDraft }[]>();
547
550
  // 거절 진단 전용 색인. 준비된 계약을 이름별로 시간순으로 모아 어긋난 필드를 지목하며,
548
551
  // 통과·차단 판정에는 관여하지 않는다.
549
552
  const preparedContracts = new Map<string, DispatchContract[]>();
@@ -555,7 +558,6 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
555
558
  const reset = () => {
556
559
  generation++;
557
560
  bindings.clear();
558
- pendingDispatches.clear();
559
561
  reservations.clear();
560
562
  preparedContracts.clear();
561
563
  decisionCache.clear();
@@ -730,11 +732,19 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
730
732
  throw new Error("같은 task 계약에 더 최신 facts 준비가 있어 이전 라우팅 판단은 폐기되었습니다.");
731
733
  }
732
734
  };
735
+ const clock = deps.clock ?? (() => performance.now());
736
+ const startedAt = clock();
737
+ const elapsed = (from: number, to: number) => Math.max(0, Math.round(to - from));
738
+ // 고정 필드 진단. 숫자만 담으며 task 수와 무관하다. jevStarted는 이 호출이 실제로 시작한 judge 요청 수이고,
739
+ // 분류별 건수와 합산해 해석하지 않는다(한 요청이 decision·placement를 함께 판단할 수 있다).
740
+ const counts = { jevStarted: 0, decisionNew: 0, decisionReused: 0, placementNew: 0, placementReused: 0 };
733
741
  let quotaAbort: AbortController | undefined;
734
742
  try {
735
743
  const ownersByTask = contracts.map((contract) => relevantOwners(contract, allOwners));
736
744
  const refreshAttempts: RefreshAttempts = new Map();
745
+ const candidatesStartedAt = clock();
737
746
  const { available: candidates, unavailable: unavailableCandidates } = await candidateSet(ctx, current, refreshAttempts);
747
+ const candidatesDoneAt = clock();
738
748
  requireLatest();
739
749
  // 잔량은 시각마다 바뀌므로 revision·identity·Jev state 밖의 참고 정보로만 붙인다.
740
750
  // Jev 판단과 함께 시작하고, 이번 계정 잔량 조회가 끝난 뒤 배정 결과를 반환한다.
@@ -760,6 +770,7 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
760
770
  reason: error instanceof Error ? error.message : String(error),
761
771
  });
762
772
  }
773
+ const judgeStartedAt = clock();
763
774
  const publications = await Promise.all(tasks.map(async (task, index) => {
764
775
  const contract = contracts[index]!;
765
776
  const owners = ownersByTask[index]!;
@@ -773,8 +784,14 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
773
784
  let decisionPending = decisionCache.get(decisionKey);
774
785
  let placementPending = placementCache.get(placementKey);
775
786
 
787
+ // 소유자가 없으면 placement 판단 자체가 없으므로 어느 쪽으로도 세지 않는다. 새 decision 판단은 placement를 함께 덮어쓴다.
788
+ if (decisionPending) counts.decisionReused += 1;
789
+ if (decisionPending && placementPending && owners.length > 0) counts.placementReused += 1;
776
790
  if (!decisionPending) {
777
791
  const fullQuestions = { ...baseQuestions, ...ownerQuestions };
792
+ counts.jevStarted += 1;
793
+ counts.decisionNew += 1;
794
+ if (owners.length > 0) counts.placementNew += 1;
778
795
  const fullPending = judge(ctx, routingState(task.assessment, owners), fullQuestions, signal);
779
796
  decisionPending = cacheJudgment(
780
797
  decisionCache,
@@ -795,6 +812,8 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
795
812
  );
796
813
  }
797
814
  } else if (!placementPending && owners.length > 0) {
815
+ counts.jevStarted += 1;
816
+ counts.placementNew += 1;
798
817
  placementPending = cacheJudgment(
799
818
  placementCache,
800
819
  placementKey,
@@ -841,7 +860,9 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
841
860
  },
842
861
  };
843
862
  }));
863
+ const quotaWaitStartedAt = clock();
844
864
  const quota = await quotaPending;
865
+ const quotaDoneAt = clock();
845
866
  requireLatest();
846
867
  const allocation = normalAllocation(current, candidates, quota);
847
868
  // 이력은 advisory다. revision·identity·Jev state 밖에 두며 읽기 실패는 빈 이력이다.
@@ -870,7 +891,15 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
870
891
  const name = tasks[index]!.name.trim();
871
892
  preparedContracts.set(name, [...(preparedContracts.get(name) ?? []), contracts[index]!]);
872
893
  });
894
+ const diagnostics = {
895
+ totalMs: elapsed(startedAt, quotaDoneAt),
896
+ candidatesMs: elapsed(candidatesStartedAt, candidatesDoneAt),
897
+ judgeWaitMs: elapsed(judgeStartedAt, quotaWaitStartedAt),
898
+ quotaExtraWaitMs: elapsed(quotaWaitStartedAt, quotaDoneAt),
899
+ ...counts,
900
+ };
873
901
  return {
902
+ diagnostics,
874
903
  candidates,
875
904
  unavailableCandidates,
876
905
  quota,
@@ -923,7 +952,7 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
923
952
  candidateLock ??= { workClass: contract.workClass, primaryDeliverable: contract.primaryDeliverable };
924
953
  return contract;
925
954
  });
926
- const drafts: [string, DispatchDraft][] = [];
955
+ const drafts: [number, DispatchDraft][] = [];
927
956
  for (const [itemIndex, item] of tasks.entries()) {
928
957
  const requested = typeof item.model === "string" ? item.model : "";
929
958
  const task = typeof item.task === "string" ? item.task : "";
@@ -994,7 +1023,7 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
994
1023
  const recommended = candidates.findIndex((candidate) => candidate.profile === profile);
995
1024
  const recommendedCandidate = candidates[recommended];
996
1025
  const workClass = answers?.workClass?.choice ?? null;
997
- drafts.push([key, {
1026
+ drafts.push([itemIndex, {
998
1027
  type: "dispatch",
999
1028
  name,
1000
1029
  workClass,
@@ -1048,8 +1077,7 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
1048
1077
  reason: `다른 task 호출이 발주 승인을 받아 spawn 결과를 기다리는 공유 작업공간 OWNED_PATHS와 겹칩니다: ${reserved.slice(0, DIAGNOSTIC_LIST_LIMIT).join("; ")}. 이 호출은 발주하지 않았습니다. 그 호출의 결과가 돌아온 뒤 현재 owner를 확인해 다시 내거나, 경로를 나누거나 isolated로 발주하세요.`,
1049
1078
  };
1050
1079
  }
1051
- for (const [key, draft] of drafts) pendingDispatches.set(key, draft);
1052
- if (callId && drafts.length > 0) reservations.set(callId, drafts.map(([key, draft]) => ({ key, draft })));
1080
+ if (callId && drafts.length > 0) reservations.set(callId, drafts.map(([itemIndex, draft]) => ({ itemIndex, key: briefKey(draft.name, contracts[itemIndex]!), draft })));
1053
1081
  return canonicalInput === input ? undefined : { input: canonicalInput };
1054
1082
  }
1055
1083
 
@@ -1058,9 +1086,6 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
1058
1086
  * tool_execution_end만 내고, task 실행 오류는 isError tool_result를 낸다. 다른 호출의 예약과 이미 이관된 owner는 건드리지 않는다.
1059
1087
  */
1060
1088
  function releaseCall(callId: string): void {
1061
- for (const { key, draft } of reservations.get(callId) ?? []) {
1062
- if (pendingDispatches.get(key) === draft) pendingDispatches.delete(key);
1063
- }
1064
1089
  reservations.delete(callId);
1065
1090
  }
1066
1091
 
@@ -1081,16 +1106,16 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
1081
1106
  // 원장에서 읽으므로 reload 뒤에도 연결이 유지되고, 관측되지 않은 준비만으로는 연결하지 않는다.
1082
1107
  const known = assignmentsByName(deps.ledger?.read() ?? [], sessionId);
1083
1108
  let candidateLock = contractLock;
1109
+ const reserved = reservations.get(callId) ?? [];
1084
1110
  for (const [index, item] of tasks.entries()) {
1085
1111
  const task = typeof item.task === "string" ? item.task : "";
1086
1112
  const contract = dispatchContract(task, candidateLock);
1087
1113
  if (!contract) continue;
1088
1114
  candidateLock ??= { workClass: contract.workClass, primaryDeliverable: contract.primaryDeliverable };
1089
1115
  const name = String(item.name ?? "").trim();
1090
- const key = briefKey(name, contract);
1091
- const draft = pendingDispatches.get(key);
1116
+ // 이 호출이 admission에서 받은 그 항목의 초안만 소비한다. 같은 이름·같은 계약의 다른 호출 초안은 그 호출의 것이다.
1117
+ const draft = reserved.find((entry) => entry.itemIndex === index && entry.key === briefKey(name, contract))?.draft;
1092
1118
  if (!draft) continue;
1093
- pendingDispatches.delete(key);
1094
1119
  // 다른 guard가 막은 뒤 다른 selector로 다시 낸 발주는 그 호출의 beforeTask 초안만 맞는다.
1095
1120
  if (item.model !== `${draft.chosenModel}:${draft.chosenEffort}`) continue;
1096
1121
  const linked = sessionId && contract.findingId !== null ? known.get(name) : undefined;
@@ -1131,7 +1156,9 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
1131
1156
  const request = params as { context: string; tasks: RouteTask[] };
1132
1157
  const batch = await prepareBatch(request.context, request.tasks, ctx, signal, String(callId ?? ""));
1133
1158
  // 준비 handle(plan)은 발주 identity가 아니므로(routing_verdict는 spawn identity) 모델 문맥에는 싣지 않고 details에만 둔다.
1134
- const view = { ...batch, routes: batch.routes.map(({ plan: _plan, ...route }) => route) };
1159
+ // 준비 진단(diagnostics)도 details 전용이다. 모델 문맥에는 싣지 않는다.
1160
+ const { diagnostics: _diagnostics, ...shared } = batch;
1161
+ const view = { ...shared, routes: batch.routes.map(({ plan: _plan, ...route }) => route) };
1135
1162
  return { content: [{ type: "text", text: JSON.stringify(view) }], details: batch };
1136
1163
  },
1137
1164
  });
@@ -174,6 +174,9 @@ Main은 근거가 있는 finding을 각각 다음 중 하나로 닫는다.
174
174
  어긴 실행 실수면 기존 규칙 적용을 교정하고, 규칙·Skill 누락일 때만 해당 정본을 보완한다.
175
175
  미확정 가설이나 실패율 임계값으로 규칙을 자동 생성·변경하지 않는다. `learn`은 Main 전용이다.
176
176
  Maker는 종료 보고에 교훈 후보를 싣고, Main이 저장·기존 교훈 연결·기각을 정한다.
177
+ 저장 범위도 Main이 직접 정한다. 여러 프로젝트에 적용되는 사용자 선호·작업 운영 원칙은
178
+ `global`, 특정 저장소의 구현·경로·환경에 종속된 사실은 `project`로 저장한다. 사용자가
179
+ 범위를 다시 지정하게 하지 않으며, 이미 있는 교훈은 중복 저장보다 연결·수정을 우선한다.
177
180
  전역 `autolearn`도 켜져 있다. 도구 호출 8회 이상 턴이 끝나면 core의 별도 capture Agent가
178
181
  (`learn`·`manage_skill`만 가진 채) 교훈을 따로 저장하고, 저장한 경우에만 대화에
179
182
  `[교훈 자동 저장]`(`autolearn-saved`) 메시지를 남긴다. 이건 보조일 뿐 Main의 직접 저장 책임은 그대로다.
@@ -1,6 +1,6 @@
1
1
  import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
2
2
  import { createHash } from "node:crypto";
3
- import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import { access, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -28,6 +28,41 @@ const manifestInstructions = `From cwd Tools/CUELO_Setup, run:
28
28
  node files/source-build-helper.js create-manifest ../.. files/runtime-integrity.json --output files/source-integrity.json
29
29
  node files/source-build-helper.js verify-source ../.. files/source-integrity.json files/runtime-integrity.json
30
30
  Then include Tools/CUELO_Setup/files/source-integrity.json in the same commit.`;
31
+ const memorySyncScript = "Tools/OMP_Global_Config/memory-sync/sync.ts";
32
+ const memoryTransport = "Tools/OMP_Global_Config/memory-sync/memories.jsonl";
33
+
34
+ type Exec = (command: string, args: string[], options: { cwd: string }) => Promise<{ code: number; stdout: string; stderr?: string }>;
35
+
36
+ /**
37
+ * 기억 PC 간 동기화의 export를 커밋 경로에 붙인다. 원래 hook은 손으로 돌리는 export.ps1 끝뿐이라
38
+ * 평소 커밋·배포로는 한 번도 내보내지지 않았다(2026-10-02). 같은 저장소 커밋이면 먼저 export하고,
39
+ * 바뀐 transport 파일을 커밋 목록에 더한다. 실패는 커밋을 막지 않고 결과 문구로 알린다.
40
+ */
41
+ async function exportMemories(cwd: string, files: string[], exec: Exec): Promise<{ file?: string; note?: string }> {
42
+ const top = await exec("git", ["-C", cwd, "rev-parse", "--show-toplevel"], { cwd });
43
+ if (top.code !== 0) return {};
44
+ const root = resolve(top.stdout.trim());
45
+ try {
46
+ await access(join(root, ...memorySyncScript.split("/")));
47
+ } catch {
48
+ return {};
49
+ }
50
+ const inRoot = (path: string) => {
51
+ const rel = relative(root, resolve(cwd, path));
52
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
53
+ };
54
+ if (!files.every(inRoot)) return {};
55
+ const transport = relative(cwd, join(root, ...memoryTransport.split("/"))).split(sep).join("/");
56
+ if (transport.startsWith("../")) return { note: "기억 export 생략: 현재 cwd 밖의 파일이라 이 커밋에 넣을 수 없다." };
57
+ if (files.some((file) => file.replace(/\\/g, "/").toLowerCase() === transport.toLowerCase())) return {};
58
+ const exported = await exec("bun", [join(root, ...memorySyncScript.split("/")), "export", root], { cwd: root });
59
+ if (exported.code !== 0) {
60
+ const reason = (exported.stderr || exported.stdout).trim().split(/\r?\n/).at(-1) || `exit ${exported.code}`;
61
+ return { note: `기억 export 실패(커밋은 진행): ${reason}` };
62
+ }
63
+ const status = await exec("git", ["-C", root, "status", "--porcelain", "--", memoryTransport], { cwd: root });
64
+ return status.code === 0 && status.stdout.trim() ? { file: transport } : {};
65
+ }
31
66
 
32
67
  function isSourcePath(path: string, packagedTools: string[]): boolean {
33
68
  if (path.split("/").some((segment) => generatedSourceSegments[segment] || segment.endsWith(".tsbuildinfo"))) return false;
@@ -137,13 +172,16 @@ const factory: CustomToolFactory = (pi) => ({
137
172
  }),
138
173
 
139
174
  async execute(_toolCallId, params, onUpdate, _ctx, signal) {
140
- const files = params.files.map((value) => value.trim()).filter(Boolean);
175
+ const requested = params.files.map((value) => value.trim()).filter(Boolean);
141
176
  const message = params.message.trim();
142
- if (files.length === 0) throw new Error("git_finalize requires at least one exact file path.");
177
+ if (requested.length === 0) throw new Error("git_finalize requires at least one exact file path.");
143
178
  if (!message) throw new Error("git_finalize requires a non-empty commit message.");
144
179
  if (process.platform !== "win32") throw new Error("git_finalize currently requires Windows PowerShell.");
145
180
 
146
- await checkSourceManifest(pi.cwd, files, (command, args, options) => pi.exec(command, args, { ...options, signal }));
181
+ const exec: Exec = (command, args, options) => pi.exec(command, args, { ...options, signal });
182
+ await checkSourceManifest(pi.cwd, requested, exec);
183
+ const memory = await exportMemories(pi.cwd, requested, exec);
184
+ const files = memory.file ? [...requested, memory.file] : requested;
147
185
 
148
186
  onUpdate?.({
149
187
  content: [{ type: "text", text: "Repository finalizer lock을 획득하고 변경 경계를 확인하는 중입니다." }],
@@ -168,7 +206,9 @@ const factory: CustomToolFactory = (pi) => ({
168
206
  content: [
169
207
  {
170
208
  type: "text",
171
- text: `Committed and pushed ${parsed.commitSha} to ${parsed.remote} ${parsed.upstreamRef}.`,
209
+ text: `Committed and pushed ${parsed.commitSha} to ${parsed.remote} ${parsed.upstreamRef}.`
210
+ + (memory.file ? ` 기억 transport(${memory.file})를 함께 커밋했다.` : "")
211
+ + (memory.note ? ` ${memory.note}` : ""),
172
212
  },
173
213
  ],
174
214
  details: parsed,