ummaya 0.2.3 → 0.2.5

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 (534) hide show
  1. package/README.md +17 -3
  2. package/bin/ummaya +10 -1
  3. package/npm-shrinkwrap.json +253 -2
  4. package/package.json +5 -1
  5. package/prompts/manifest.yaml +2 -2
  6. package/prompts/session_guidance_v1.md +3 -1
  7. package/prompts/system_v1.md +9 -7
  8. package/pyproject.toml +26 -7
  9. package/specs/2803-document-production-hardening/contracts/document-tools.schema.json +1043 -0
  10. package/src/ummaya/_canonical/__init__.py +2 -0
  11. package/src/ummaya/context/builder.py +17 -11
  12. package/src/ummaya/engine/engine.py +30 -113
  13. package/src/ummaya/engine/query.py +20 -0
  14. package/src/ummaya/evidence/__init__.py +44 -0
  15. package/src/ummaya/evidence/__main__.py +7 -0
  16. package/src/ummaya/evidence/dataset_contract.py +193 -0
  17. package/src/ummaya/evidence/document_authoring_cases.py +33 -0
  18. package/src/ummaya/evidence/document_harness.py +313 -0
  19. package/src/ummaya/evidence/document_viewer_ux.py +391 -0
  20. package/src/ummaya/evidence/gates.py +70 -0
  21. package/src/ummaya/evidence/json_types.py +20 -0
  22. package/src/ummaya/evidence/models.py +145 -0
  23. package/src/ummaya/evidence/output_payload.py +89 -0
  24. package/src/ummaya/evidence/payload_documents.py +233 -0
  25. package/src/ummaya/evidence/route_contracts.py +224 -0
  26. package/src/ummaya/evidence/route_helpers.py +150 -0
  27. package/src/ummaya/evidence/runner.py +177 -0
  28. package/src/ummaya/evidence/source_provenance.py +246 -0
  29. package/src/ummaya/evidence/source_provenance_redaction.py +176 -0
  30. package/src/ummaya/evidence/task_registry.py +264 -0
  31. package/src/ummaya/evidence/tool_layer.py +39 -0
  32. package/src/ummaya/evidence/tool_layer_models.py +151 -0
  33. package/src/ummaya/ipc/adapter_manifest_emitter.py +26 -10
  34. package/src/ummaya/ipc/document_intent_normalization.py +185 -0
  35. package/src/ummaya/ipc/frame_schema.py +52 -5
  36. package/src/ummaya/ipc/route_diagnostics.py +73 -0
  37. package/src/ummaya/ipc/stdio.py +2282 -417
  38. package/src/ummaya/llm/client.py +234 -59
  39. package/src/ummaya/llm/config.py +8 -3
  40. package/src/ummaya/llm/reasoning.py +84 -0
  41. package/src/ummaya/primitives/__init__.py +6 -2
  42. package/src/ummaya/primitives/delegation.py +1 -1
  43. package/src/ummaya/primitives/document.py +28 -0
  44. package/src/ummaya/settings.py +0 -3
  45. package/src/ummaya/tools/discovery_bridge.py +34 -2
  46. package/src/ummaya/tools/documents/__init__.py +297 -0
  47. package/src/ummaya/tools/documents/adapter_registry.py +487 -0
  48. package/src/ummaya/tools/documents/archive_container_probe.py +167 -0
  49. package/src/ummaya/tools/documents/artifact_store.py +454 -0
  50. package/src/ummaya/tools/documents/authoring.py +283 -0
  51. package/src/ummaya/tools/documents/baselines.py +114 -0
  52. package/src/ummaya/tools/documents/capability.py +331 -0
  53. package/src/ummaya/tools/documents/contracts.py +112 -0
  54. package/src/ummaya/tools/documents/conversion.py +521 -0
  55. package/src/ummaya/tools/documents/diff.py +275 -0
  56. package/src/ummaya/tools/documents/engines.py +163 -0
  57. package/src/ummaya/tools/documents/evaluation.py +291 -0
  58. package/src/ummaya/tools/documents/explicit_values.py +108 -0
  59. package/src/ummaya/tools/documents/fixtures.py +174 -0
  60. package/src/ummaya/tools/documents/format_completion_audit.py +471 -0
  61. package/src/ummaya/tools/documents/formats/__init__.py +2 -0
  62. package/src/ummaya/tools/documents/formats/archive.py +528 -0
  63. package/src/ummaya/tools/documents/formats/base.py +41 -0
  64. package/src/ummaya/tools/documents/formats/code_file.py +211 -0
  65. package/src/ummaya/tools/documents/formats/data_file.py +272 -0
  66. package/src/ummaya/tools/documents/formats/hwp.py +284 -0
  67. package/src/ummaya/tools/documents/formats/hwpx.py +1837 -0
  68. package/src/ummaya/tools/documents/formats/odf.py +435 -0
  69. package/src/ummaya/tools/documents/formats/ooxml.py +1030 -0
  70. package/src/ummaya/tools/documents/formats/passive.py +766 -0
  71. package/src/ummaya/tools/documents/formats/pdf.py +702 -0
  72. package/src/ummaya/tools/documents/formats/text_web.py +268 -0
  73. package/src/ummaya/tools/documents/hwp_conversion_probe.py +178 -0
  74. package/src/ummaya/tools/documents/hwp_direct_candidate.py +141 -0
  75. package/src/ummaya/tools/documents/inspection.py +289 -0
  76. package/src/ummaya/tools/documents/intake.py +1079 -0
  77. package/src/ummaya/tools/documents/legacy_office_promotion_probe.py +366 -0
  78. package/src/ummaya/tools/documents/models.py +1598 -0
  79. package/src/ummaya/tools/documents/odf_promotion_probe.py +167 -0
  80. package/src/ummaya/tools/documents/orchestrator.py +96 -0
  81. package/src/ummaya/tools/documents/passive_capability_probe.py +251 -0
  82. package/src/ummaya/tools/documents/patch.py +170 -0
  83. package/src/ummaya/tools/documents/pdfa_conformance.py +284 -0
  84. package/src/ummaya/tools/documents/pdfa_promotion_probe.py +198 -0
  85. package/src/ummaya/tools/documents/permissions.py +110 -0
  86. package/src/ummaya/tools/documents/planner.py +616 -0
  87. package/src/ummaya/tools/documents/registry.py +2733 -0
  88. package/src/ummaya/tools/documents/render.py +978 -0
  89. package/src/ummaya/tools/documents/render_comparison.py +113 -0
  90. package/src/ummaya/tools/documents/render_comparison_models.py +74 -0
  91. package/src/ummaya/tools/documents/render_comparison_regions.py +73 -0
  92. package/src/ummaya/tools/documents/render_comparison_style.py +161 -0
  93. package/src/ummaya/tools/documents/reread.py +157 -0
  94. package/src/ummaya/tools/documents/runtime_authoring.py +244 -0
  95. package/src/ummaya/tools/documents/runtime_authoring_bundle.py +76 -0
  96. package/src/ummaya/tools/documents/scorecard.py +184 -0
  97. package/src/ummaya/tools/documents/socratic_planner.py +193 -0
  98. package/src/ummaya/tools/documents/style.py +48 -0
  99. package/src/ummaya/tools/documents/tool_defs.py +523 -0
  100. package/src/ummaya/tools/documents/validate.py +347 -0
  101. package/src/ummaya/tools/executor.py +61 -12
  102. package/src/ummaya/tools/geocoding/kakao_client.py +1 -2
  103. package/src/ummaya/tools/kma/apihub_catalog.py +984 -1
  104. package/src/ummaya/tools/kma/apihub_structured_adapter.py +86 -6
  105. package/src/ummaya/tools/kma/apihub_url_adapter.py +593 -0
  106. package/src/ummaya/tools/kma/apihub_url_catalog.py +296 -0
  107. package/src/ummaya/tools/live_proxy.py +0 -3
  108. package/src/ummaya/tools/location_adapters.py +8 -6
  109. package/src/ummaya/tools/manifest_metadata.py +16 -3
  110. package/src/ummaya/tools/models.py +5 -1
  111. package/src/ummaya/tools/mvp_surface.py +2 -2
  112. package/src/ummaya/tools/nmc/emergency_search.py +8 -6
  113. package/src/ummaya/tools/register_all.py +17 -0
  114. package/src/ummaya/tools/registry.py +10 -1
  115. package/src/ummaya/tools/resolve_location.py +4 -4
  116. package/src/ummaya/tools/routing/__init__.py +59 -0
  117. package/src/ummaya/tools/routing/builder.py +105 -0
  118. package/src/ummaya/tools/routing/cards.py +29 -0
  119. package/src/ummaya/tools/routing/decision_service.py +534 -0
  120. package/src/ummaya/tools/routing/decision_types.py +74 -0
  121. package/src/ummaya/tools/routing/feasibility.py +122 -0
  122. package/src/ummaya/tools/routing/intent.py +17 -0
  123. package/src/ummaya/tools/routing/intent_extractor.py +207 -0
  124. package/src/ummaya/tools/routing/intent_patterns.py +160 -0
  125. package/src/ummaya/tools/routing/intent_public_data.py +150 -0
  126. package/src/ummaya/tools/routing/intent_types.py +48 -0
  127. package/src/ummaya/tools/routing/lint.py +78 -0
  128. package/src/ummaya/tools/routing/metadata.py +174 -0
  129. package/src/ummaya/tools/routing/projection.py +340 -0
  130. package/src/ummaya/tools/routing/retrieval_policy.py +629 -0
  131. package/src/ummaya/tools/routing/schema.py +81 -0
  132. package/src/ummaya/tools/routing/types.py +96 -0
  133. package/src/ummaya/tools/routing_index.py +2 -2
  134. package/src/ummaya/tools/search.py +40 -106
  135. package/src/ummaya/tools/verified_data_go_kr/_manifest.py +115 -25
  136. package/src/ummaya/tools/verified_data_go_kr/airkorea_air_quality.py +109 -4
  137. package/src/ummaya/tools/verified_data_go_kr/nmc_aed_site.py +108 -2
  138. package/src/ummaya/tools/verified_data_go_kr/pps_bid_public_info.py +174 -9
  139. package/src/ummaya/tools/verified_data_go_kr/tago_bus_arrival.py +66 -3
  140. package/src/ummaya/tools/verified_data_go_kr/tago_bus_location.py +12 -2
  141. package/src/ummaya/tools/verified_data_go_kr/tago_bus_route.py +8 -2
  142. package/src/ummaya/tools/verified_data_go_kr/tago_bus_route_station.py +114 -0
  143. package/src/ummaya/tools/verified_data_go_kr/tago_bus_station.py +14 -3
  144. package/src/ummaya/tools/verify_canonical_map.py +21 -0
  145. package/tests/fixtures/documents/public_forms/baselines.yaml +113 -0
  146. package/tui/package.json +1 -2
  147. package/tui/src/.cc-byte-identical-whitelist.yaml +266 -0
  148. package/tui/src/QueryEngine.ts +12 -4
  149. package/tui/src/bridge/inboundAttachments.ts +3 -3
  150. package/tui/src/cli/handlers/auth.ts +4 -13
  151. package/tui/src/cli/handlers/mcp.tsx +3 -3
  152. package/tui/src/cli/print.ts +69 -18
  153. package/tui/src/cli/update.ts +13 -13
  154. package/tui/src/commands/copy/index.ts +1 -1
  155. package/tui/src/commands/cost/cost.ts +2 -2
  156. package/tui/src/commands/init-verifiers.ts +5 -5
  157. package/tui/src/commands/init.ts +30 -30
  158. package/tui/src/commands/insights.ts +44 -44
  159. package/tui/src/commands/install-github-app/install-github-app.tsx +2 -2
  160. package/tui/src/commands/install-github-app/setupGitHubActions.ts +3 -3
  161. package/tui/src/commands/install-github-app/types.ts +8 -30
  162. package/tui/src/commands/install.tsx +5 -5
  163. package/tui/src/commands/mcp/addCommand.ts +5 -5
  164. package/tui/src/commands/mcp/xaaIdpCommand.ts +2 -2
  165. package/tui/src/commands/plugin/ManageMarketplaces.tsx +2 -2
  166. package/tui/src/commands/plugin/types.ts +6 -28
  167. package/tui/src/commands/plugin/unifiedTypes.ts +4 -26
  168. package/tui/src/commands/reasoning/index.ts +13 -0
  169. package/tui/src/commands/reasoning/reasoning.tsx +177 -0
  170. package/tui/src/commands/rename/generateSessionName.ts +1 -1
  171. package/tui/src/commands/thinkback/thinkback.tsx +3 -3
  172. package/tui/src/commands.ts +2 -0
  173. package/tui/src/components/Feedback.tsx +1 -1
  174. package/tui/src/components/LogoV2/EmergencyTip.tsx +11 -2
  175. package/tui/src/components/LogoV2/WelcomeV2.tsx +1 -3
  176. package/tui/src/components/Messages.tsx +2 -1
  177. package/tui/src/components/ScrollKeybindingHandler.tsx +6 -6
  178. package/tui/src/components/Spinner/types.ts +6 -28
  179. package/tui/src/components/Spinner.tsx +2 -2
  180. package/tui/src/components/agents/generateAgent.ts +1 -1
  181. package/tui/src/components/agents/new-agent-creation/types.ts +4 -26
  182. package/tui/src/components/config/EnvSecretIsolatedEditor.tsx +1 -1
  183. package/tui/src/components/design-system/LoadingState.tsx +2 -2
  184. package/tui/src/components/mcp/types.ts +16 -38
  185. package/tui/src/components/messages/AssistantToolUseMessage.tsx +3 -2
  186. package/tui/src/components/messages/UserCrossSessionMessage.ts +16 -4
  187. package/tui/src/components/messages/UserForkBoilerplateMessage.ts +16 -4
  188. package/tui/src/components/messages/UserGitHubWebhookMessage.ts +16 -4
  189. package/tui/src/components/messages/UserToolResultMessage/utils.tsx +3 -2
  190. package/tui/src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.ts +9 -4
  191. package/tui/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.ts +9 -4
  192. package/tui/src/components/primitive/DocumentSocraticReviewBlock.tsx +129 -0
  193. package/tui/src/components/primitive/DocumentToolResultCard.tsx +224 -0
  194. package/tui/src/components/primitive/documentSocraticReview.ts +215 -0
  195. package/tui/src/components/primitive/index.tsx +43 -1
  196. package/tui/src/components/primitive/types.ts +137 -0
  197. package/tui/src/components/ui/option.ts +4 -26
  198. package/tui/src/constants/common.ts +0 -2
  199. package/tui/src/constants/prompts.ts +4 -3
  200. package/tui/src/constants/querySource.ts +4 -26
  201. package/tui/src/entrypoints/sdk/controlTypes.ts +26 -48
  202. package/tui/src/entrypoints/sdk/coreTypes.generated.ts +3 -25
  203. package/tui/src/entrypoints/sdk/runtimeTypes.ts +38 -60
  204. package/tui/src/entrypoints/sdk/sdkUtilityTypes.ts +4 -26
  205. package/tui/src/entrypoints/sdk/settingsTypes.generated.ts +3 -25
  206. package/tui/src/entrypoints/sdk/toolTypes.ts +3 -25
  207. package/tui/src/hooks/toolPermission/handlers/interactiveHandler.ts +10 -0
  208. package/tui/src/hooks/useApiKeyVerification.ts +1 -1
  209. package/tui/src/hooks/useVirtualScroll.ts +1 -1
  210. package/tui/src/ink/ink.tsx +33 -14
  211. package/tui/src/ink/reconciler.ts +2 -3
  212. package/tui/src/ink/render-to-screen.ts +30 -10
  213. package/tui/src/ipc/bridge.ts +62 -15
  214. package/tui/src/ipc/bridgeSingleton.ts +5 -1
  215. package/tui/src/ipc/codec.ts +29 -3
  216. package/tui/src/ipc/frames.generated.ts +407 -312
  217. package/tui/src/ipc/llmClient.ts +279 -76
  218. package/tui/src/ipc/llmTypes.ts +16 -1
  219. package/tui/src/ipc/schema/frame.schema.json +1 -3475
  220. package/tui/src/keybindings/defaultBindings.ts +4 -0
  221. package/tui/src/main.tsx +32 -11
  222. package/tui/src/native-ts/file-index/index.ts +33 -3
  223. package/tui/src/observability/surface.ts +2 -2
  224. package/tui/src/probes/toolRegistryProbe.tsx +3 -1
  225. package/tui/src/projectOnboardingState.ts +7 -6
  226. package/tui/src/query/chatMessageTypes.ts +18 -0
  227. package/tui/src/query/chatMessagesBuilder.ts +1 -1
  228. package/tui/src/query/deps.ts +1 -1
  229. package/tui/src/query/messageGuards.ts +106 -0
  230. package/tui/src/query/publicDataTerminalRepair.ts +384 -0
  231. package/tui/src/query/run.ts +1075 -0
  232. package/tui/src/query/supportBoundary.ts +168 -0
  233. package/tui/src/query/toolResultErrors.ts +103 -0
  234. package/tui/src/query/toolRunner.ts +687 -0
  235. package/tui/src/query/unavailableToolRepair.ts +118 -0
  236. package/tui/src/query.ts +9 -1721
  237. package/tui/src/screens/REPL.tsx +42 -31
  238. package/tui/src/services/api/adapterManifest.ts +4 -0
  239. package/tui/src/services/api/backendChat/events.ts +117 -0
  240. package/tui/src/services/api/backendChat/finalMessage.ts +40 -0
  241. package/tui/src/services/api/backendChat/frame.ts +9 -0
  242. package/tui/src/services/api/backendChat/streaming.ts +430 -0
  243. package/tui/src/services/api/backendChat/types.ts +62 -0
  244. package/tui/src/services/api/backendChat.ts +1 -0
  245. package/tui/src/services/api/client.ts +98 -14
  246. package/tui/src/services/api/errorUtils.ts +5 -5
  247. package/tui/src/services/api/errors.ts +1 -1
  248. package/tui/src/services/api/logging.ts +1 -1
  249. package/tui/src/services/api/ummaya/evidence.ts +194 -0
  250. package/tui/src/services/api/ummaya/messages.ts +255 -0
  251. package/tui/src/services/api/ummaya/nonStreaming.ts +66 -0
  252. package/tui/src/services/api/ummaya/provider.ts +200 -0
  253. package/tui/src/services/api/ummaya/reasoning.ts +24 -0
  254. package/tui/src/services/api/ummaya/request.ts +200 -0
  255. package/tui/src/services/api/ummaya/selectionContext.ts +240 -0
  256. package/tui/src/services/api/ummaya/streaming.ts +365 -0
  257. package/tui/src/services/api/ummaya/streamingPayload.ts +129 -0
  258. package/tui/src/services/api/ummaya/streamingReader.ts +40 -0
  259. package/tui/src/services/api/ummaya/toolSelection.ts +217 -0
  260. package/tui/src/services/api/ummaya/types.ts +110 -0
  261. package/tui/src/services/api/ummaya/usage.ts +30 -0
  262. package/tui/src/services/api/ummaya.ts +26 -364
  263. package/tui/src/services/api/withRetry.ts +1 -1
  264. package/tui/src/services/awaySummary.ts +2 -2
  265. package/tui/src/services/claudeAiLimits.ts +1 -1
  266. package/tui/src/services/compact/autoCompact.ts +1 -1
  267. package/tui/src/services/compact/compact.ts +1 -1
  268. package/tui/src/services/lsp/types.ts +8 -30
  269. package/tui/src/services/tips/types.ts +6 -28
  270. package/tui/src/services/tokenEstimation.ts +1 -1
  271. package/tui/src/services/toolRegistry/bootGuard.ts +5 -5
  272. package/tui/src/services/toolUseSummary/toolUseSummaryGenerator.ts +1 -1
  273. package/tui/src/services/tools/toolExecution.ts +94 -1
  274. package/tui/src/skills/bundled/stuck.ts +12 -12
  275. package/tui/src/state/AppStateStore.ts +7 -0
  276. package/tui/src/store/pendingPermissionSlot.ts +1 -1
  277. package/tui/src/store/session-store.ts +10 -36
  278. package/tui/src/stubs/any-stub.ts +15 -10
  279. package/tui/src/stubs/color-diff-napi.ts +37 -23
  280. package/tui/src/stubs/globals.d.ts +3 -3
  281. package/tui/src/stubs/macro-preload.ts +23 -12
  282. package/tui/src/tools/AdapterTool/AdapterTool.ts +1239 -163
  283. package/tui/src/tools/AdapterTool/routeDiagnostics.ts +75 -0
  284. package/tui/src/tools/AgentTool/AgentTool.tsx +84 -1371
  285. package/tui/src/tools/AgentTool/agentToolHandoff.ts +114 -0
  286. package/tui/src/tools/AgentTool/agentToolPartialResult.ts +16 -0
  287. package/tui/src/tools/AgentTool/agentToolProgress.ts +32 -0
  288. package/tui/src/tools/AgentTool/agentToolResolver.ts +161 -0
  289. package/tui/src/tools/AgentTool/agentToolResult.ts +163 -0
  290. package/tui/src/tools/AgentTool/agentToolUtils.ts +14 -686
  291. package/tui/src/tools/AgentTool/asyncAgentLifecycle.ts +208 -0
  292. package/tui/src/tools/AgentTool/asyncLifecycle.ts +153 -0
  293. package/tui/src/tools/AgentTool/backgroundedCompletion.ts +126 -0
  294. package/tui/src/tools/AgentTool/backgroundedLifecycle.ts +174 -0
  295. package/tui/src/tools/AgentTool/foregroundBackground.ts +83 -0
  296. package/tui/src/tools/AgentTool/foregroundDrain.tsx +133 -0
  297. package/tui/src/tools/AgentTool/foregroundFinalize.ts +98 -0
  298. package/tui/src/tools/AgentTool/foregroundLifecycle.tsx +237 -0
  299. package/tui/src/tools/AgentTool/foregroundProgress.tsx +169 -0
  300. package/tui/src/tools/AgentTool/foregroundTask.ts +89 -0
  301. package/tui/src/tools/AgentTool/forkSubagent.ts +1 -12
  302. package/tui/src/tools/AgentTool/forkSubagentGate.ts +34 -0
  303. package/tui/src/tools/AgentTool/launchRouting.ts +203 -0
  304. package/tui/src/tools/AgentTool/lifecycle.ts +244 -0
  305. package/tui/src/tools/AgentTool/mcpRouting.ts +73 -0
  306. package/tui/src/tools/AgentTool/orchestrationSupport.ts +70 -0
  307. package/tui/src/tools/AgentTool/permissions.ts +39 -0
  308. package/tui/src/tools/AgentTool/promptSetup.ts +181 -0
  309. package/tui/src/tools/AgentTool/remoteRouting.ts +62 -0
  310. package/tui/src/tools/AgentTool/resultMapping.ts +116 -0
  311. package/tui/src/tools/AgentTool/resumeAgent.ts +39 -107
  312. package/tui/src/tools/AgentTool/resumeAgentHelpers.ts +140 -0
  313. package/tui/src/tools/AgentTool/runAgent.ts +1 -1
  314. package/tui/src/tools/AgentTool/runtimeConfig.ts +57 -0
  315. package/tui/src/tools/AgentTool/schemas.ts +196 -0
  316. package/tui/src/tools/AgentTool/sourceVerificationPropagation.ts +263 -0
  317. package/tui/src/tools/AgentTool/worktreeLifecycle.ts +105 -0
  318. package/tui/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx +174 -202
  319. package/tui/src/tools/BashTool/BashTool.tsx +71 -1072
  320. package/tui/src/tools/BashTool/bashCommandHelpers.ts +12 -12
  321. package/tui/src/tools/BashTool/bashPermissions/astPreflight.ts +173 -0
  322. package/tui/src/tools/BashTool/bashPermissions/classifierChecks.ts +199 -0
  323. package/tui/src/tools/BashTool/bashPermissions/compoundGuards.ts +53 -0
  324. package/tui/src/tools/BashTool/bashPermissions/constants.ts +99 -0
  325. package/tui/src/tools/BashTool/bashPermissions/index.ts +38 -0
  326. package/tui/src/tools/BashTool/bashPermissions/legacyMisparsing.ts +62 -0
  327. package/tui/src/tools/BashTool/bashPermissions/main.ts +135 -0
  328. package/tui/src/tools/BashTool/bashPermissions/normalizedCommands.ts +33 -0
  329. package/tui/src/tools/BashTool/bashPermissions/operatorFlow.ts +98 -0
  330. package/tui/src/tools/BashTool/bashPermissions/permissionChecks.ts +200 -0
  331. package/tui/src/tools/BashTool/bashPermissions/prefixSuggestions.ts +88 -0
  332. package/tui/src/tools/BashTool/bashPermissions/promptClassifierRules.ts +125 -0
  333. package/tui/src/tools/BashTool/bashPermissions/ruleDelegates.ts +19 -0
  334. package/tui/src/tools/BashTool/bashPermissions/ruleMatching.ts +145 -0
  335. package/tui/src/tools/BashTool/bashPermissions/sandboxAutoAllow.ts +75 -0
  336. package/tui/src/tools/BashTool/bashPermissions/subcommandFlow.ts +205 -0
  337. package/tui/src/tools/BashTool/bashPermissions/subcommandGuards.ts +73 -0
  338. package/tui/src/tools/BashTool/bashPermissions/subcommandResultHelpers.ts +116 -0
  339. package/tui/src/tools/BashTool/bashPermissions/types.ts +26 -0
  340. package/tui/src/tools/BashTool/bashPermissions/wrapperStripping.ts +139 -0
  341. package/tui/src/tools/BashTool/bashPermissions.ts +26 -2621
  342. package/tui/src/tools/BashTool/call.ts +202 -0
  343. package/tui/src/tools/BashTool/callLoader.ts +35 -0
  344. package/tui/src/tools/BashTool/commandClassification.ts +151 -0
  345. package/tui/src/tools/BashTool/commandClassificationLoader.ts +40 -0
  346. package/tui/src/tools/BashTool/cwdReset.ts +33 -0
  347. package/tui/src/tools/BashTool/lineTruncation.ts +11 -0
  348. package/tui/src/tools/BashTool/modeValidation.ts +13 -1
  349. package/tui/src/tools/BashTool/outputPersistence.ts +42 -0
  350. package/tui/src/tools/BashTool/permissionClassification.ts +66 -0
  351. package/tui/src/tools/BashTool/permissionLoader.ts +44 -0
  352. package/tui/src/tools/BashTool/resultLoader.ts +29 -0
  353. package/tui/src/tools/BashTool/resultMapping.ts +83 -0
  354. package/tui/src/tools/BashTool/sandboxPolicy.ts +79 -0
  355. package/tui/src/tools/BashTool/schemas.ts +65 -0
  356. package/tui/src/tools/BashTool/sedEditExecution.ts +59 -0
  357. package/tui/src/tools/BashTool/shellExecution.tsx +245 -0
  358. package/tui/src/tools/BashTool/shellOutputUtils.ts +85 -0
  359. package/tui/src/tools/BashTool/shellPermissionGauntlet.ts +97 -0
  360. package/tui/src/tools/BashTool/uiLoader.ts +37 -0
  361. package/tui/src/tools/BriefTool/upload.ts +1 -1
  362. package/tui/src/tools/CalculatorTool/parser.ts +2 -2
  363. package/tui/src/tools/DocumentPrimitive/DocumentPrimitive.ts +262 -0
  364. package/tui/src/tools/DocumentPrimitive/dispatchNormalization.ts +270 -0
  365. package/tui/src/tools/DocumentPrimitive/documentDestinationPath.ts +18 -0
  366. package/tui/src/tools/DocumentPrimitive/documentMutationGuard.ts +22 -0
  367. package/tui/src/tools/DocumentPrimitive/documentPatchNormalization.ts +248 -0
  368. package/tui/src/tools/DocumentPrimitive/documentSourceVerification.ts +245 -0
  369. package/tui/src/tools/DocumentPrimitive/documentSourceVerificationFields.ts +103 -0
  370. package/tui/src/tools/DocumentPrimitive/modelVisibleOutput.ts +40 -0
  371. package/tui/src/tools/DocumentPrimitive/prompt.ts +35 -0
  372. package/tui/src/tools/FileEditTool/FileEditTool.ts +9 -507
  373. package/tui/src/tools/FileEditTool/call.ts +228 -0
  374. package/tui/src/tools/FileEditTool/validateInput.ts +196 -0
  375. package/tui/src/tools/FileReadTool/imageProcessor.ts +13 -0
  376. package/tui/src/tools/FileWriteTool/FileWriteTool.ts +7 -300
  377. package/tui/src/tools/FileWriteTool/call.ts +223 -0
  378. package/tui/src/tools/FileWriteTool/validateInput.ts +80 -0
  379. package/tui/src/tools/ListMcpResourcesTool/ListMcpResourcesTool.ts +19 -3
  380. package/tui/src/tools/LookupPrimitive/LookupPrimitive.ts +48 -29
  381. package/tui/src/tools/LookupPrimitive/prompt.ts +6 -7
  382. package/tui/src/tools/MCPTool/trustPolicy.ts +118 -0
  383. package/tui/src/tools/McpAuthTool/McpAuthTool.ts +21 -3
  384. package/tui/src/tools/NotebookEditTool/NotebookEditTool.ts +7 -326
  385. package/tui/src/tools/NotebookEditTool/call.ts +254 -0
  386. package/tui/src/tools/NotebookEditTool/notebookModel.ts +51 -0
  387. package/tui/src/tools/NotebookEditTool/validateInput.ts +142 -0
  388. package/tui/src/tools/PowerShellTool/PowerShellTool.tsx +46 -937
  389. package/tui/src/tools/PowerShellTool/acceptEditsCommandValidation.ts +162 -0
  390. package/tui/src/tools/PowerShellTool/call.ts +179 -0
  391. package/tui/src/tools/PowerShellTool/callLoader.ts +37 -0
  392. package/tui/src/tools/PowerShellTool/commandClassification.ts +86 -0
  393. package/tui/src/tools/PowerShellTool/modeValidation.ts +25 -332
  394. package/tui/src/tools/PowerShellTool/outputPersistence.ts +42 -0
  395. package/tui/src/tools/PowerShellTool/permissionClassification.ts +28 -0
  396. package/tui/src/tools/PowerShellTool/resultLoader.ts +31 -0
  397. package/tui/src/tools/PowerShellTool/resultMapping.ts +75 -0
  398. package/tui/src/tools/PowerShellTool/schemas.ts +40 -0
  399. package/tui/src/tools/PowerShellTool/shellExecution.tsx +258 -0
  400. package/tui/src/tools/PowerShellTool/symlinkModeValidation.ts +44 -0
  401. package/tui/src/tools/PowerShellTool/uiLoader.ts +37 -0
  402. package/tui/src/tools/PowerShellTool/validation.ts +39 -0
  403. package/tui/src/tools/ReadMcpResourceTool/ReadMcpResourceTool.ts +19 -3
  404. package/tui/src/tools/ResolveLocationPrimitive/ResolveLocationPrimitive.ts +30 -19
  405. package/tui/src/tools/ResolveLocationPrimitive/prompt.ts +2 -6
  406. package/tui/src/tools/SkillTool/SkillTool.ts +2 -2
  407. package/tui/src/tools/SubmitPrimitive/SubmitPrimitive.ts +51 -18
  408. package/tui/src/tools/TaskCreateTool/TaskCreateTool.ts +16 -2
  409. package/tui/src/tools/TaskGetTool/TaskGetTool.ts +23 -3
  410. package/tui/src/tools/TaskListTool/TaskListTool.ts +22 -4
  411. package/tui/src/tools/TaskOutputTool/TaskOutputTool.tsx +46 -547
  412. package/tui/src/tools/TaskOutputTool/lookup.ts +216 -0
  413. package/tui/src/tools/TaskOutputTool/render.tsx +257 -0
  414. package/tui/src/tools/TaskOutputTool/schemas.ts +55 -0
  415. package/tui/src/tools/TaskOutputTool/serialization.ts +36 -0
  416. package/tui/src/tools/TaskStopTool/TaskStopTool.ts +10 -0
  417. package/tui/src/tools/TaskUpdateTool/TaskUpdateTool.ts +14 -364
  418. package/tui/src/tools/TaskUpdateTool/completion.ts +62 -0
  419. package/tui/src/tools/TaskUpdateTool/schemas.ts +62 -0
  420. package/tui/src/tools/TaskUpdateTool/serialization.ts +46 -0
  421. package/tui/src/tools/TaskUpdateTool/statusUpdate.ts +247 -0
  422. package/tui/src/tools/TodoWriteTool/TodoWriteTool.ts +21 -2
  423. package/tui/src/tools/ToolSearchTool/ToolSearchTool.ts +21 -302
  424. package/tui/src/tools/ToolSearchTool/ccSupportTools.ts +223 -0
  425. package/tui/src/tools/ToolSearchTool/descriptionCache.ts +50 -0
  426. package/tui/src/tools/ToolSearchTool/keywordSearch.ts +216 -0
  427. package/tui/src/tools/ToolSearchTool/prompt.ts +10 -4
  428. package/tui/src/tools/ToolSearchTool/resultMapping.ts +30 -0
  429. package/tui/src/tools/ToolSearchTool/schemas.ts +30 -0
  430. package/tui/src/tools/ToolSearchTool/searchPool.ts +47 -0
  431. package/tui/src/tools/ToolSearchTool/supportIntentHints.ts +140 -0
  432. package/tui/src/tools/TranslateTool/TranslateTool.ts +1 -1
  433. package/tui/src/tools/VerifyPrimitive/VerifyPrimitive.ts +27 -10
  434. package/tui/src/tools/WebFetchTool/WebFetchTool.ts +43 -138
  435. package/tui/src/tools/WebFetchTool/call.ts +227 -0
  436. package/tui/src/tools/WebFetchTool/resolvedAddressSafety.ts +78 -0
  437. package/tui/src/tools/WebFetchTool/sourceVerification.ts +204 -0
  438. package/tui/src/tools/WebFetchTool/types.ts +23 -0
  439. package/tui/src/tools/WebFetchTool/urlSafety.ts +181 -0
  440. package/tui/src/tools/WebFetchTool/utils.ts +1 -1
  441. package/tui/src/tools/WebSearchTool/UI.tsx +0 -1
  442. package/tui/src/tools/WebSearchTool/WebSearchTool.ts +9 -313
  443. package/tui/src/tools/WebSearchTool/call.ts +33 -0
  444. package/tui/src/tools/WebSearchTool/responseMapping.ts +190 -0
  445. package/tui/src/tools/WebSearchTool/resultBlock.ts +47 -0
  446. package/tui/src/tools/WebSearchTool/schemas.ts +47 -0
  447. package/tui/src/tools/WebSearchTool/toolSchema.ts +12 -0
  448. package/tui/src/tools/WorkspaceToolAdapter/WorkspaceToolAdapter.ts +79 -0
  449. package/tui/src/tools/WorkspaceToolAdapter/allowedRootPolicy.ts +85 -0
  450. package/tui/src/tools/WorkspaceToolAdapter/documentFormatGuards.ts +73 -0
  451. package/tui/src/tools/WorkspaceToolAdapter/inputNormalization.ts +105 -0
  452. package/tui/src/tools/WorkspaceToolAdapter/mcpExposurePolicy.ts +64 -0
  453. package/tui/src/tools/WorkspaceToolAdapter/toolDefFactory.ts +215 -0
  454. package/tui/src/tools/WorkspaceToolAdapter/toolNames.ts +6 -0
  455. package/tui/src/tools/WorkspaceToolAdapter/workspacePolicy.ts +15 -0
  456. package/tui/src/tools/_shared/citizenUserText.ts +49 -0
  457. package/tui/src/tools/_shared/dispatchPrimitive.ts +6 -6
  458. package/tui/src/tools/_shared/documentChangeToPatch.ts +125 -0
  459. package/tui/src/tools/_shared/documentDispatchArguments.ts +87 -0
  460. package/tui/src/tools/_shared/documentPrimitiveTimeout.ts +13 -0
  461. package/tui/src/tools/_shared/documentToolResultRender.ts +98 -0
  462. package/tui/src/tools/_shared/locationInputRepair.ts +112 -0
  463. package/tui/src/tools/_shared/pendingCallRegistry.ts +1 -6
  464. package/tui/src/tools/_shared/rootPrimitiveInput.ts +68 -0
  465. package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPatterns.ts +58 -0
  466. package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPrompt.ts +271 -0
  467. package/tui/src/tools/_shared/toolChoiceRepair/documentRepair.ts +452 -0
  468. package/tui/src/tools/_shared/toolChoiceRepair/messageAccess.ts +80 -0
  469. package/tui/src/tools/_shared/toolChoiceRepair/publicDataRepair.ts +92 -0
  470. package/tui/src/tools/_shared/toolChoiceRepair/supportRepair.ts +135 -0
  471. package/tui/src/tools/_shared/toolChoiceRepair.ts +61 -0
  472. package/tui/src/tools/shared/mockDisclaimer.ts +1 -1
  473. package/tui/src/tools.ts +39 -190
  474. package/tui/src/types/fileSuggestion.ts +4 -26
  475. package/tui/src/types/generated/events_mono/claude_code/v1/claude_code_internal_event.ts +186 -148
  476. package/tui/src/types/generated/events_mono/common/v1/auth.ts +25 -11
  477. package/tui/src/types/generated/events_mono/growthbook/v1/growthbook_experiment_event.ts +47 -30
  478. package/tui/src/types/generated/google/protobuf/timestamp.ts +21 -7
  479. package/tui/src/types/message.ts +80 -102
  480. package/tui/src/types/messageQueueTypes.ts +6 -28
  481. package/tui/src/types/notebook.ts +16 -38
  482. package/tui/src/types/statusLine.ts +4 -26
  483. package/tui/src/types/tools.ts +24 -46
  484. package/tui/src/types/utils.ts +6 -28
  485. package/tui/src/upstreamproxy/relay.ts +7 -3
  486. package/tui/src/upstreamproxy/upstreamproxy.ts +1 -1
  487. package/tui/src/utils/assistantMessageFactories.ts +9 -3
  488. package/tui/src/utils/attachments.ts +1 -1
  489. package/tui/src/utils/auth.ts +129 -139
  490. package/tui/src/utils/bash/ast.ts +23 -23
  491. package/tui/src/utils/bash/bashParser.ts +5 -5
  492. package/tui/src/utils/billing.ts +1 -1
  493. package/tui/src/utils/collapseReadSearch.ts +3 -3
  494. package/tui/src/utils/cronTasks.ts +1 -1
  495. package/tui/src/utils/execFileNoThrow.ts +1 -1
  496. package/tui/src/utils/filePersistence/types.ts +16 -38
  497. package/tui/src/utils/forkedAgent.ts +1 -1
  498. package/tui/src/utils/gracefulShutdown.ts +4 -4
  499. package/tui/src/utils/heapDumpService.ts +12 -8
  500. package/tui/src/utils/hooks/apiQueryHookHelper.ts +1 -1
  501. package/tui/src/utils/hooks/execPromptHook.ts +1 -1
  502. package/tui/src/utils/hooks/skillImprovement.ts +1 -1
  503. package/tui/src/utils/kExaoneReasoning.ts +138 -0
  504. package/tui/src/utils/mcp/dateTimeParser.ts +1 -1
  505. package/tui/src/utils/messages.ts +19 -0
  506. package/tui/src/utils/migrateSessions.ts +3 -3
  507. package/tui/src/utils/model/model.ts +6 -6
  508. package/tui/src/utils/multiToolLayout.ts +13 -0
  509. package/tui/src/utils/permissions/yoloClassifier.ts +1 -1
  510. package/tui/src/utils/plugins/headlessPluginInstall.ts +1 -1
  511. package/tui/src/utils/plugins/mcpPluginIntegration.ts +1 -1
  512. package/tui/src/utils/plugins/mcpbHandler.ts +1 -1
  513. package/tui/src/utils/plugins/pluginLoader.ts +8 -8
  514. package/tui/src/utils/processUserInput/processSlashCommand.tsx +2 -2
  515. package/tui/src/utils/processUserInput/processUserInput.ts +26 -0
  516. package/tui/src/utils/protectedNamespace.ts +5 -3
  517. package/tui/src/utils/rawJsonToolCall.ts +242 -0
  518. package/tui/src/utils/ripgrep.ts +16 -7
  519. package/tui/src/utils/sessionTitle.ts +1 -1
  520. package/tui/src/utils/settings/applySettingsChange.ts +4 -0
  521. package/tui/src/utils/settings/permissionValidation.ts +14 -2
  522. package/tui/src/utils/settings/types.ts +9 -3
  523. package/tui/src/utils/shell/prefix.ts +1 -1
  524. package/tui/src/utils/sideQuery.ts +1 -1
  525. package/tui/src/utils/stats.ts +1 -1
  526. package/tui/src/utils/systemThemeWatcher.ts +13 -3
  527. package/tui/src/utils/teleport.tsx +1 -1
  528. package/uv.lock +394 -22
  529. package/assets/copilot-gate-logo.svg +0 -58
  530. package/assets/govon-logo.svg +0 -40
  531. package/src/ummaya/eval/__init__.py +0 -5
  532. package/src/ummaya/eval/retrieval.py +0 -713
  533. package/tui/src/services/api/claude.ts +0 -3510
  534. package/tui/src/utils/messageStream.ts +0 -186
@@ -1,4 +1,8 @@
1
1
  import { z } from 'zod/v4'
2
+ import { randomUUID } from 'node:crypto'
3
+ import { existsSync } from 'node:fs'
4
+ import { homedir } from 'node:os'
5
+ import { basename, join } from 'node:path'
2
6
  import {
3
7
  buildTool,
4
8
  type Tool,
@@ -14,16 +18,77 @@ import {
14
18
  import { getOrCreateUmmayaBridge } from '../../ipc/bridgeSingleton.js'
15
19
  import { getOrCreatePendingCallRegistry } from '../../ipc/pendingCallSingleton.js'
16
20
  import { dispatchPrimitive } from '../_shared/dispatchPrimitive.js'
21
+ import {
22
+ applyDocumentVisualRenderGateToOutput,
23
+ extractDocumentToolResultPayload,
24
+ isDocumentVisualRenderFailedOutput,
25
+ renderDocumentToolResultIfPresent,
26
+ } from '../_shared/documentToolResultRender.js'
17
27
  import { LookupPrimitive } from '../LookupPrimitive/LookupPrimitive.js'
18
28
  import { ResolveLocationPrimitive } from '../ResolveLocationPrimitive/ResolveLocationPrimitive.js'
19
29
  import { SubmitPrimitive } from '../SubmitPrimitive/SubmitPrimitive.js'
20
30
  import { VerifyPrimitive } from '../VerifyPrimitive/VerifyPrimitive.js'
31
+ import { DocumentPrimitive } from '../DocumentPrimitive/DocumentPrimitive.js'
32
+ import { resolveDocumentPrimitiveTimeoutMs } from '../_shared/documentPrimitiveTimeout.js'
21
33
 
22
- type AdapterPrimitive = 'find' | 'locate' | 'send' | 'check'
34
+ type AdapterPrimitive = 'find' | 'locate' | 'send' | 'check' | 'document'
23
35
 
24
36
  type InputSchema = z.ZodType<{ [key: string]: unknown }>
25
37
 
26
- const ROOT_PRIMITIVE_TOOL_NAMES = new Set(['locate', 'find', 'check', 'send'])
38
+ const ROOT_PRIMITIVE_TOOL_NAMES = new Set([
39
+ 'locate',
40
+ 'find',
41
+ 'check',
42
+ 'send',
43
+ 'document',
44
+ ])
45
+ const DOCUMENT_TOOL_NAMES = new Set([
46
+ 'document',
47
+ 'document_inspect',
48
+ 'document_extract',
49
+ 'document_form_schema',
50
+ 'document_copy_for_edit',
51
+ 'document_apply_fill',
52
+ 'document_apply_style',
53
+ 'document_render',
54
+ 'document_validate_public_form',
55
+ 'document_save',
56
+ ])
57
+ const DOCUMENT_MUTATION_TOOL_NAMES = new Set([
58
+ 'document_apply_fill',
59
+ 'document_apply_style',
60
+ ])
61
+ const DOCUMENT_ARTIFACT_FOLLOWUP_TOOL_NAMES = new Set([
62
+ 'document_apply_fill',
63
+ 'document_apply_style',
64
+ 'document_render',
65
+ 'document_validate_public_form',
66
+ 'document_save',
67
+ ])
68
+ // Purely mechanical pipeline steps that carry no user-meaningful change — only
69
+ // these are hidden on success. Substantive mutations (apply_fill / apply_style)
70
+ // now render their inline structural diff immediately (per-mutation trigger),
71
+ // the same way Claude Code shows a diff the moment an edit lands. See
72
+ // specs/2802-public-doc-harness/deep-research-migration-document-render.md.
73
+ const MECHANICAL_DOCUMENT_TOOL_NAMES = new Set(['document_copy_for_edit'])
74
+ const SAFE_DOCUMENT_ARTIFACT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/u
75
+ const EXPLICIT_DOCUMENT_ARTIFACT_MARKER_RE =
76
+ /(?:^|[\s"'`(])(?:artifact_id|artifact\s*id|artifact|아티팩트)\s*[:=]?\s*([A-Za-z0-9][A-Za-z0-9_.-]{0,127})(?=$|[^A-Za-z0-9_.-])/iu
77
+ const EXPLICIT_DOCUMENT_ARTIFACT_PREFIX_RE =
78
+ /(?:^|[\s"'`(])((?:source|working|derivative|render|export|viewport)-[A-Za-z0-9][A-Za-z0-9_.-]{0,127})(?=$|[^A-Za-z0-9_.-])/u
79
+ const DOCUMENT_FORMAT_RE = /\b(?:hwpx|hwp|docx|pdf|xlsx|pptx)\b/iu
80
+ const DOWNLOADS_FOLDER_PATH_RE = /(?:^|[\\/])Downloads$/u
81
+ const DOWNLOADS_PATH_SEGMENT_RE = /(?:^|[\\/])Downloads[\\/](?<tail>.+)$/iu
82
+ const DOCUMENT_EXTENSION_RE = /\.(?:hwpx|hwp|docx|pdf|xlsx|pptx)$/iu
83
+ const DOCUMENT_EXTENSION_TRAILING_PUNCT_RE =
84
+ /(\.(?:hwpx|hwp|docx|pdf|xlsx|pptx))[.。.]+$/iu
85
+ const EXPLICIT_LOCAL_DOCUMENT_PATH_RE =
86
+ /(?:^|[\s"'`(])(?<path>(?:~|\/|\.{1,2}\/|[A-Za-z]:\\)[^\n\r"'`]*?\.(?:hwpx|hwp|docx|pdf|xlsx|pptx))(?=$|[\s"'`),,。]|[가-힣])/giu
87
+ const HWPX_TEXT_TARGET_ALIAS_RE =
88
+ /^\/?hwp(?:x)?[-_/]text(?:[-_](?<indexA>\d+)|\[(?<indexB>\d+)\])(?:\/text\(\))?$/iu
89
+ const HWPX_TEXT_TARGET_RE = /^\/hwpx\/text\[\d+\]$/u
90
+ const HWPX_BLOCK_TABLE_CELL_TARGET_RE =
91
+ /^\/hwpx\/\[(?<tableId>hwpx-table-\d{3})\]\/cells\[(?<row>\d+)\]\[(?<column>\d+)\]$/u
27
92
 
28
93
  const fallbackInputSchema = z.object({}).passthrough() as InputSchema
29
94
 
@@ -37,6 +102,680 @@ function asJsonObject(value: unknown): JsonObject {
37
102
  return isJsonObject(value) ? value : {}
38
103
  }
39
104
 
105
+ function documentToolUseAction(toolId: string): string {
106
+ switch (toolId) {
107
+ case 'document':
108
+ return 'Prepare document workflow'
109
+ case 'document_inspect':
110
+ return 'Inspect document form'
111
+ case 'document_extract':
112
+ return 'Read document content'
113
+ case 'document_form_schema':
114
+ return 'Map document fields'
115
+ case 'document_apply_fill':
116
+ return 'Fill document fields'
117
+ case 'document_apply_style':
118
+ return 'Apply document formatting'
119
+ case 'document_render':
120
+ return 'Render document diff'
121
+ case 'document_validate_public_form':
122
+ return 'Validate public-form rules'
123
+ case 'document_save':
124
+ return 'Save document'
125
+ default:
126
+ return 'Process document'
127
+ }
128
+ }
129
+
130
+ function documentToolUseTarget(input: Record<string, unknown>): string | undefined {
131
+ const document = asJsonObject(input.document)
132
+ const path =
133
+ typeof document.path === 'string'
134
+ ? document.path
135
+ : typeof input.path === 'string'
136
+ ? input.path
137
+ : undefined
138
+ if (path !== undefined && path.trim()) {
139
+ return basename(path)
140
+ }
141
+ if (
142
+ typeof document.artifact_id === 'string' ||
143
+ typeof input.artifact_id === 'string'
144
+ ) {
145
+ return 'current document'
146
+ }
147
+ return undefined
148
+ }
149
+
150
+ function renderDocumentToolUseMessage(
151
+ toolId: string,
152
+ input: Record<string, unknown>,
153
+ ): string | null {
154
+ if (MECHANICAL_DOCUMENT_TOOL_NAMES.has(toolId)) return null
155
+ const action = documentToolUseAction(toolId)
156
+ const target = documentToolUseTarget(input)
157
+ return target === undefined ? action : `${action}: ${target}`
158
+ }
159
+
160
+ function messageInnerRecord(message: unknown): JsonObject {
161
+ return asJsonObject(asJsonObject(message).message)
162
+ }
163
+
164
+ function messageRole(message: unknown): string | undefined {
165
+ const inner = messageInnerRecord(message)
166
+ const outer = asJsonObject(message)
167
+ if (typeof inner.role === 'string') return inner.role
168
+ if (typeof outer.role === 'string') return outer.role
169
+ return typeof outer.type === 'string' ? outer.type : undefined
170
+ }
171
+
172
+ function messageContent(message: unknown): unknown {
173
+ const inner = messageInnerRecord(message)
174
+ return inner.content ?? asJsonObject(message).content
175
+ }
176
+
177
+ function isToolResultContent(content: unknown): boolean {
178
+ if (!Array.isArray(content)) return false
179
+ return content.some(block => asJsonObject(block).type === 'tool_result')
180
+ }
181
+
182
+ function textFromMessageContent(content: unknown): string {
183
+ if (typeof content === 'string') return content
184
+ if (!Array.isArray(content)) return ''
185
+ return content
186
+ .map(block => {
187
+ if (typeof block === 'string') return block
188
+ const record = asJsonObject(block)
189
+ if (record.type === 'tool_result') return ''
190
+ if (typeof record.text === 'string') return record.text
191
+ if (typeof record.content === 'string') return record.content
192
+ return ''
193
+ })
194
+ .filter(Boolean)
195
+ .join('\n')
196
+ }
197
+
198
+ function latestPlainUserText(messages: readonly unknown[]): string {
199
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
200
+ const content = messageContent(messages[idx])
201
+ if (messageRole(messages[idx]) !== 'user' || isToolResultContent(content)) {
202
+ continue
203
+ }
204
+ const text = textFromMessageContent(content).trim()
205
+ if (text) return text
206
+ }
207
+ return ''
208
+ }
209
+
210
+ function safeDocumentArtifactId(value: unknown): string | undefined {
211
+ if (typeof value !== 'string') return undefined
212
+ const candidate = value.trim()
213
+ return SAFE_DOCUMENT_ARTIFACT_ID_RE.test(candidate) ? candidate : undefined
214
+ }
215
+
216
+ function explicitDocumentArtifactIdFromText(text: string): string | undefined {
217
+ const marked = EXPLICIT_DOCUMENT_ARTIFACT_MARKER_RE.exec(text)?.[1]
218
+ const markedArtifactId = safeDocumentArtifactId(marked)
219
+ if (markedArtifactId) return markedArtifactId
220
+ const prefixed = EXPLICIT_DOCUMENT_ARTIFACT_PREFIX_RE.exec(text)?.[1]
221
+ return safeDocumentArtifactId(prefixed)
222
+ }
223
+
224
+ function parseJsonObject(value: unknown): JsonObject | undefined {
225
+ if (isJsonObject(value)) return value
226
+ if (typeof value !== 'string' || !value.trim()) return undefined
227
+ try {
228
+ return asJsonObject(JSON.parse(value))
229
+ } catch {
230
+ return undefined
231
+ }
232
+ }
233
+
234
+ function documentToolResultPayload(message: unknown): JsonObject | undefined {
235
+ const directResult = asJsonObject(asJsonObject(message).toolUseResult).result
236
+ if (isJsonObject(directResult)) return directResult
237
+
238
+ const content = messageContent(message)
239
+ if (!Array.isArray(content)) return undefined
240
+ for (const block of content) {
241
+ const record = asJsonObject(block)
242
+ if (record.type !== 'tool_result') continue
243
+ const parsed = parseJsonObject(record.content)
244
+ const nestedResult = asJsonObject(parsed).result
245
+ if (isJsonObject(nestedResult)) return nestedResult
246
+ }
247
+ return undefined
248
+ }
249
+
250
+ function latestDocumentArtifactRef(
251
+ messages: readonly unknown[],
252
+ options: {
253
+ toolIds: ReadonlySet<string>
254
+ artifactPrefix: string
255
+ },
256
+ ): string | undefined {
257
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
258
+ const payload = documentToolResultPayload(messages[idx])
259
+ if (!payload) continue
260
+ if (
261
+ typeof payload.tool_id !== 'string' ||
262
+ !options.toolIds.has(payload.tool_id)
263
+ ) continue
264
+ if (payload.status !== 'ok') continue
265
+ const refs = Array.isArray(payload.artifact_refs) ? payload.artifact_refs : []
266
+ for (let refIdx = refs.length - 1; refIdx >= 0; refIdx -= 1) {
267
+ const ref = safeDocumentArtifactId(refs[refIdx])
268
+ if (ref?.startsWith(options.artifactPrefix)) return ref
269
+ }
270
+ }
271
+ return undefined
272
+ }
273
+
274
+ function latestMutableDocumentArtifactRef(messages: readonly unknown[]): string | undefined {
275
+ return (
276
+ latestDocumentArtifactRef(messages, {
277
+ toolIds: DOCUMENT_MUTATION_TOOL_NAMES,
278
+ artifactPrefix: 'derivative-',
279
+ }) ??
280
+ latestDocumentArtifactRef(messages, {
281
+ toolIds: new Set([
282
+ 'document_copy_for_edit',
283
+ 'document_apply_fill',
284
+ 'document_apply_style',
285
+ ]),
286
+ artifactPrefix: 'working-',
287
+ })
288
+ )
289
+ }
290
+
291
+ function shouldHideSuccessfulIntermediateDocumentResult(output: unknown): boolean {
292
+ const payload = extractDocumentToolResultPayload(output)
293
+ return (
294
+ payload !== null &&
295
+ payload.status === 'ok' &&
296
+ typeof payload.tool_id === 'string' &&
297
+ MECHANICAL_DOCUMENT_TOOL_NAMES.has(payload.tool_id)
298
+ )
299
+ }
300
+
301
+ function documentCorrelationId(value: unknown): string {
302
+ return typeof value === 'string' && value.trim()
303
+ ? value.trim()
304
+ : `document-render-${randomUUID()}`
305
+ }
306
+
307
+ function documentFormatFromText(text: string): string | undefined {
308
+ return DOCUMENT_FORMAT_RE.exec(text)?.[0]?.toLowerCase()
309
+ }
310
+
311
+ function documentFormatFromPath(path: string): string | undefined {
312
+ const match = /\.(hwpx|hwp|docx|pdf|xlsx|pptx)$/iu.exec(path.trim())
313
+ return match?.[1]?.toLowerCase()
314
+ }
315
+
316
+ function inferredDownloadsDocumentPath(text: string): string | undefined {
317
+ if (!/(다운로드\s*폴더|downloads)/iu.test(text)) return undefined
318
+ const formatMatch = DOCUMENT_FORMAT_RE.exec(text)
319
+ const format = formatMatch?.[0]?.toLowerCase()
320
+ if (!format || formatMatch === null) return undefined
321
+ const beforeFormat = text.slice(0, formatMatch.index).trim()
322
+ const nameMatch =
323
+ /(?:다운로드\s*폴더|downloads)(?:에)?\s*(?:있는|의)?\s*(?<name>.+)$/iu.exec(
324
+ beforeFormat,
325
+ )
326
+ const rawName = nameMatch?.groups?.name?.trim()
327
+ if (!rawName) return undefined
328
+ const fileName = rawName
329
+ .replace(/[\\/:*?"<>|]/gu, ' ')
330
+ .replace(/\s+/gu, ' ')
331
+ .replace(/[.。.]+$/gu, '')
332
+ .trim()
333
+ if (!fileName) return undefined
334
+ return join(homedir(), 'Downloads', `${fileName}.${format}`)
335
+ }
336
+
337
+ function isDownloadsFolderLikePath(value: unknown): boolean {
338
+ if (typeof value !== 'string') return false
339
+ const normalized = value
340
+ .trim()
341
+ .replace(/^['"`]+|['"`]+$/gu, '')
342
+ .replace(/[\\/]+$/u, '')
343
+ .replace(/\.$/u, '')
344
+ return DOWNLOADS_FOLDER_PATH_RE.test(normalized)
345
+ }
346
+
347
+ function normalizeDownloadsDocumentPath(value: unknown): string | undefined {
348
+ if (typeof value !== 'string') return undefined
349
+ const cleaned = value
350
+ .trim()
351
+ .replace(/^['"`]+|['"`]+$/gu, '')
352
+ .replace(DOCUMENT_EXTENSION_TRAILING_PUNCT_RE, '$1')
353
+ if (!cleaned) return undefined
354
+ const downloadsPathMatch = DOWNLOADS_PATH_SEGMENT_RE.exec(cleaned)
355
+ if (DOCUMENT_EXTENSION_RE.test(cleaned) && downloadsPathMatch) {
356
+ const homeDownloads = `${homedir()}/Downloads/`
357
+ if (cleaned.startsWith(homeDownloads)) return cleaned
358
+ const tail = downloadsPathMatch.groups?.tail
359
+ if (!tail || tail.includes('..')) return undefined
360
+ const parts = tail.split(/[\\/]+/u).filter(Boolean)
361
+ if (parts.length === 0) return undefined
362
+ return join(homedir(), 'Downloads', ...parts)
363
+ }
364
+ return undefined
365
+ }
366
+
367
+ function cleanUserDocumentPath(value: string): string {
368
+ return value
369
+ .trim()
370
+ .replace(/^['"`]+|['"`]+$/gu, '')
371
+ .replace(DOCUMENT_EXTENSION_TRAILING_PUNCT_RE, '$1')
372
+ .replace(/^~/u, homedir())
373
+ }
374
+
375
+ function existingUserDocumentPathsFromText(text: string): string[] {
376
+ const paths: string[] = []
377
+ for (const match of text.matchAll(EXPLICIT_LOCAL_DOCUMENT_PATH_RE)) {
378
+ const rawPath = match.groups?.path
379
+ if (typeof rawPath !== 'string') continue
380
+ const candidate = cleanUserDocumentPath(rawPath)
381
+ if (!candidate || !DOCUMENT_EXTENSION_RE.test(candidate)) continue
382
+ if (!existsSync(candidate)) continue
383
+ if (!paths.includes(candidate)) paths.push(candidate)
384
+ }
385
+ return paths
386
+ }
387
+
388
+ function normalizeExactUserDocumentPathInput(
389
+ input: Record<string, unknown>,
390
+ document: JsonObject,
391
+ messages: readonly unknown[],
392
+ ): Record<string, unknown> | undefined {
393
+ if (document.artifact_id !== undefined) return undefined
394
+ const userPaths = existingUserDocumentPathsFromText(latestPlainUserText(messages))
395
+ if (userPaths.length !== 1) return undefined
396
+ const lockedPath = userPaths[0]
397
+ if (lockedPath === undefined) return undefined
398
+
399
+ const currentPath =
400
+ typeof document.path === 'string' ? cleanUserDocumentPath(document.path) : undefined
401
+ if (currentPath !== undefined && existsSync(currentPath)) return undefined
402
+ if (
403
+ currentPath !== undefined &&
404
+ basename(currentPath) !== basename(lockedPath)
405
+ ) {
406
+ return undefined
407
+ }
408
+
409
+ return {
410
+ ...input,
411
+ correlation_id: documentCorrelationId(input.correlation_id),
412
+ document: {
413
+ ...document,
414
+ path: lockedPath,
415
+ expected_format:
416
+ documentFormatFromPath(lockedPath) ??
417
+ document.expected_format ??
418
+ input.expected_format ??
419
+ documentFormatFromText(latestPlainUserText(messages)),
420
+ },
421
+ }
422
+ }
423
+
424
+ function normalizeDocumentInspectPathInput(
425
+ toolId: string,
426
+ input: Record<string, unknown>,
427
+ document: JsonObject,
428
+ messages: readonly unknown[],
429
+ ): Record<string, unknown> | undefined {
430
+ if (toolId !== 'document_inspect' && toolId !== 'document') return undefined
431
+ const path = document.path ?? input.path
432
+ const userText = latestPlainUserText(messages)
433
+ const cleanedPath = typeof path === 'string' ? cleanUserDocumentPath(path) : undefined
434
+ if (
435
+ cleanedPath !== undefined &&
436
+ DOCUMENT_EXTENSION_RE.test(cleanedPath) &&
437
+ existsSync(cleanedPath)
438
+ ) {
439
+ return undefined
440
+ }
441
+ const normalizedDownloadsPath = normalizeDownloadsDocumentPath(path)
442
+ const inferredPath = isDownloadsFolderLikePath(path)
443
+ ? inferredDownloadsDocumentPath(userText)
444
+ : undefined
445
+ const inferredDownloadsPath = inferredDownloadsDocumentPath(userText)
446
+ const shouldPreferUserTextDownloadsPath =
447
+ normalizedDownloadsPath !== undefined &&
448
+ inferredDownloadsPath !== undefined &&
449
+ normalizedDownloadsPath !== inferredDownloadsPath
450
+ const normalizedPath =
451
+ (shouldPreferUserTextDownloadsPath ? inferredDownloadsPath : undefined) ??
452
+ (normalizedDownloadsPath !== undefined && existsSync(normalizedDownloadsPath)
453
+ ? normalizedDownloadsPath
454
+ : undefined) ??
455
+ (inferredPath !== undefined && existsSync(inferredPath) ? inferredPath : undefined) ??
456
+ (inferredDownloadsPath !== undefined && existsSync(inferredDownloadsPath)
457
+ ? inferredDownloadsPath
458
+ : undefined) ??
459
+ inferredDownloadsPath ??
460
+ normalizedDownloadsPath ??
461
+ inferredPath
462
+ if (!normalizedPath) return undefined
463
+ return {
464
+ ...input,
465
+ correlation_id: documentCorrelationId(input.correlation_id),
466
+ document: {
467
+ ...document,
468
+ path: normalizedPath,
469
+ expected_format:
470
+ documentFormatFromPath(normalizedPath) ??
471
+ document.expected_format ??
472
+ input.expected_format ??
473
+ documentFormatFromText(userText),
474
+ },
475
+ }
476
+ }
477
+
478
+ function normalizeDocumentPathExpectedFormatInput(
479
+ input: Record<string, unknown>,
480
+ document: JsonObject,
481
+ ): Record<string, unknown> | undefined {
482
+ if (document.artifact_id !== undefined) return undefined
483
+ if (typeof document.path !== 'string') return undefined
484
+ const normalizedPath = cleanUserDocumentPath(document.path)
485
+ const pathFormat = documentFormatFromPath(normalizedPath)
486
+ if (pathFormat === undefined) return undefined
487
+ const currentFormat =
488
+ typeof document.expected_format === 'string'
489
+ ? document.expected_format.toLowerCase()
490
+ : undefined
491
+ if (currentFormat === pathFormat && document.path === normalizedPath) {
492
+ return undefined
493
+ }
494
+ return {
495
+ ...input,
496
+ correlation_id: documentCorrelationId(input.correlation_id),
497
+ document: {
498
+ ...document,
499
+ path: normalizedPath,
500
+ expected_format: pathFormat,
501
+ },
502
+ }
503
+ }
504
+
505
+ function normalizeHwpxTextTargetPath(value: unknown): string | undefined {
506
+ if (typeof value !== 'string') return undefined
507
+ const targetPath = value.trim()
508
+ const match = HWPX_TEXT_TARGET_ALIAS_RE.exec(targetPath)
509
+ const rawIndex = match?.groups?.indexA ?? match?.groups?.indexB
510
+ if (!rawIndex) return undefined
511
+ return `/hwpx/text[${Number(rawIndex)}]`
512
+ }
513
+
514
+ function latestDocumentFieldPaths(messages: readonly unknown[]): Set<string> | undefined {
515
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
516
+ const payload = documentToolResultPayload(messages[idx])
517
+ const extraction = asJsonObject(payload?.extraction)
518
+ const fields = Array.isArray(extraction.fields) ? extraction.fields : []
519
+ const paths = new Set<string>()
520
+ for (const field of fields) {
521
+ const path = asJsonObject(field).path
522
+ if (typeof path === 'string' && path.trim()) {
523
+ paths.add(path.trim())
524
+ }
525
+ }
526
+ if (paths.size > 0) return paths
527
+ }
528
+ return undefined
529
+ }
530
+
531
+ function latestDocumentTableCellFieldAliases(
532
+ messages: readonly unknown[],
533
+ ): Map<string, string> | undefined {
534
+ for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
535
+ const payload = documentToolResultPayload(messages[idx])
536
+ const extraction = asJsonObject(payload?.extraction)
537
+ const tables = Array.isArray(extraction.tables) ? extraction.tables : []
538
+ const aliases = new Map<string, string>()
539
+ for (const tableValue of tables) {
540
+ const table = asJsonObject(tableValue)
541
+ const blockId = table.block_id
542
+ const cells = Array.isArray(table.cells) ? table.cells : []
543
+ if (typeof blockId !== 'string' || !blockId.trim()) continue
544
+ for (const cellValue of cells) {
545
+ const cell = asJsonObject(cellValue)
546
+ const rowIndex = cell.row_index
547
+ const columnIndex = cell.column_index
548
+ const fieldPath = cell.field_path
549
+ if (
550
+ typeof rowIndex !== 'number' ||
551
+ typeof columnIndex !== 'number' ||
552
+ typeof fieldPath !== 'string' ||
553
+ !fieldPath.trim()
554
+ ) {
555
+ continue
556
+ }
557
+ aliases.set(
558
+ `/hwpx/[${blockId.trim()}]/cells[${rowIndex}][${columnIndex}]`,
559
+ fieldPath.trim(),
560
+ )
561
+ }
562
+ }
563
+ if (aliases.size > 0) return aliases
564
+ }
565
+ return undefined
566
+ }
567
+
568
+ function normalizeHwpxTableCellTargetPath(
569
+ value: unknown,
570
+ aliases: ReadonlyMap<string, string> | undefined,
571
+ ): string | undefined {
572
+ if (typeof value !== 'string') return undefined
573
+ const targetPath = value.trim()
574
+ if (!HWPX_BLOCK_TABLE_CELL_TARGET_RE.test(targetPath)) return undefined
575
+ return aliases?.get(targetPath)
576
+ }
577
+
578
+ function normalizeDocumentPatchTargetPaths(
579
+ input: Record<string, unknown>,
580
+ messages: readonly unknown[],
581
+ ): Record<string, unknown> {
582
+ const patches = input.patches
583
+ if (!Array.isArray(patches)) return input
584
+
585
+ const fieldPaths = latestDocumentFieldPaths(messages)
586
+ const tableCellAliases = latestDocumentTableCellFieldAliases(messages)
587
+ let changed = false
588
+ const normalizedPatches = patches.flatMap(patch => {
589
+ if (!isJsonObject(patch)) return patch
590
+ const rawTargetPath =
591
+ typeof patch.target_path === 'string' ? patch.target_path.trim() : patch.target_path
592
+ const isHwpxTableCellTarget =
593
+ typeof rawTargetPath === 'string' && HWPX_BLOCK_TABLE_CELL_TARGET_RE.test(rawTargetPath)
594
+ const normalizedTargetPath =
595
+ normalizeHwpxTextTargetPath(patch.target_path) ??
596
+ normalizeHwpxTableCellTargetPath(patch.target_path, tableCellAliases)
597
+ const targetPath =
598
+ normalizedTargetPath ?? (
599
+ typeof rawTargetPath === 'string' ? rawTargetPath : patch.target_path
600
+ )
601
+ if (
602
+ isHwpxTableCellTarget &&
603
+ tableCellAliases !== undefined &&
604
+ normalizedTargetPath === undefined
605
+ ) {
606
+ changed = true
607
+ return []
608
+ }
609
+ if (
610
+ typeof targetPath === 'string' &&
611
+ fieldPaths !== undefined &&
612
+ HWPX_TEXT_TARGET_RE.test(targetPath) &&
613
+ !fieldPaths.has(targetPath)
614
+ ) {
615
+ changed = true
616
+ return []
617
+ }
618
+ if (
619
+ normalizedTargetPath === undefined ||
620
+ normalizedTargetPath === patch.target_path
621
+ ) {
622
+ return patch
623
+ }
624
+ changed = true
625
+ return {
626
+ ...patch,
627
+ target_path: normalizedTargetPath,
628
+ }
629
+ })
630
+
631
+ if (normalizedPatches.length === 0) return input
632
+ return changed ? { ...input, patches: normalizedPatches } : input
633
+ }
634
+
635
+ function normalizeDocumentPrimitiveInstructionInput(
636
+ toolId: string,
637
+ input: Record<string, unknown>,
638
+ messages: readonly unknown[],
639
+ ): Record<string, unknown> {
640
+ if (toolId !== 'document') return input
641
+ if (Array.isArray(input.patches) && input.patches.length > 0) return input
642
+
643
+ const userText = latestPlainUserText(messages)
644
+ if (!userText) return input
645
+
646
+ const instruction =
647
+ typeof input.instruction === 'string' ? input.instruction.trim() : ''
648
+ if (instruction.includes(userText)) return input
649
+
650
+ return {
651
+ ...input,
652
+ instruction: instruction
653
+ ? `${instruction}\n\nOriginal user request:\n${userText}`
654
+ : userText,
655
+ }
656
+ }
657
+
658
+ export function normalizeExplicitDocumentArtifactInput(
659
+ toolId: string,
660
+ input: Record<string, unknown>,
661
+ messages: readonly unknown[],
662
+ ): Record<string, unknown> {
663
+ if (!DOCUMENT_TOOL_NAMES.has(toolId)) return input
664
+
665
+ const instructionInput = normalizeDocumentPrimitiveInstructionInput(
666
+ toolId,
667
+ input,
668
+ messages,
669
+ )
670
+ const normalizedPatchInput = normalizeDocumentPatchTargetPaths(
671
+ instructionInput,
672
+ messages,
673
+ )
674
+ const { artifact_id: topLevelArtifactIdRaw, ...withoutTopLevelArtifactId } =
675
+ normalizedPatchInput
676
+ const document = asJsonObject(normalizedPatchInput.document)
677
+ const normalizedExactUserPathInput = normalizeExactUserDocumentPathInput(
678
+ normalizedPatchInput,
679
+ document,
680
+ messages,
681
+ )
682
+ if (normalizedExactUserPathInput) return normalizedExactUserPathInput
683
+ const normalizedInspectPathInput = normalizeDocumentInspectPathInput(
684
+ toolId,
685
+ normalizedPatchInput,
686
+ document,
687
+ messages,
688
+ )
689
+ if (normalizedInspectPathInput) return normalizedInspectPathInput
690
+ const { path: _documentPath, ...documentWithoutPath } = document
691
+ const existingDocumentArtifactId = safeDocumentArtifactId(document.artifact_id)
692
+ const inputArtifactId =
693
+ existingDocumentArtifactId ?? safeDocumentArtifactId(topLevelArtifactIdRaw)
694
+ const hasExtractionArtifactId = inputArtifactId?.startsWith('document-intake-') === true
695
+
696
+ if (DOCUMENT_ARTIFACT_FOLLOWUP_TOOL_NAMES.has(toolId)) {
697
+ const artifactId = latestMutableDocumentArtifactRef(messages)
698
+ if (
699
+ artifactId &&
700
+ (
701
+ document.path !== undefined ||
702
+ existingDocumentArtifactId?.startsWith('source-') ||
703
+ hasExtractionArtifactId
704
+ )
705
+ ) {
706
+ return {
707
+ ...withoutTopLevelArtifactId,
708
+ correlation_id: documentCorrelationId(input.correlation_id),
709
+ document: {
710
+ ...documentWithoutPath,
711
+ artifact_id: artifactId,
712
+ },
713
+ }
714
+ }
715
+ }
716
+
717
+ if (toolId === 'document_copy_for_edit' && hasExtractionArtifactId) {
718
+ const sourceArtifactId = latestDocumentArtifactRef(messages, {
719
+ toolIds: new Set(['document_inspect', 'document_extract', 'document_form_schema']),
720
+ artifactPrefix: 'source-',
721
+ })
722
+ if (sourceArtifactId) {
723
+ return {
724
+ ...withoutTopLevelArtifactId,
725
+ correlation_id: documentCorrelationId(input.correlation_id),
726
+ document: {
727
+ ...documentWithoutPath,
728
+ artifact_id: sourceArtifactId,
729
+ },
730
+ }
731
+ }
732
+ }
733
+
734
+ if (existingDocumentArtifactId) {
735
+ return topLevelArtifactIdRaw === undefined
736
+ ? { ...normalizedPatchInput, document: documentWithoutPath }
737
+ : { ...withoutTopLevelArtifactId, document: documentWithoutPath }
738
+ }
739
+
740
+ if (toolId === 'document_copy_for_edit' && document.path !== undefined) {
741
+ const sourceArtifactId = latestDocumentArtifactRef(messages, {
742
+ toolIds: new Set(['document_inspect', 'document_extract', 'document_form_schema']),
743
+ artifactPrefix: 'source-',
744
+ })
745
+ if (sourceArtifactId) {
746
+ return {
747
+ ...withoutTopLevelArtifactId,
748
+ correlation_id: documentCorrelationId(input.correlation_id),
749
+ document: {
750
+ ...documentWithoutPath,
751
+ artifact_id: sourceArtifactId,
752
+ },
753
+ }
754
+ }
755
+ }
756
+
757
+ const topLevelArtifactId = safeDocumentArtifactId(topLevelArtifactIdRaw)
758
+ const currentTextArtifactId = explicitDocumentArtifactIdFromText(
759
+ latestPlainUserText(messages),
760
+ )
761
+ const artifactId = topLevelArtifactId ?? currentTextArtifactId
762
+ if (!artifactId) {
763
+ return (
764
+ normalizeDocumentPathExpectedFormatInput(normalizedPatchInput, document) ??
765
+ normalizedPatchInput
766
+ )
767
+ }
768
+
769
+ return {
770
+ ...withoutTopLevelArtifactId,
771
+ correlation_id: documentCorrelationId(input.correlation_id),
772
+ document: {
773
+ ...documentWithoutPath,
774
+ artifact_id: artifactId,
775
+ },
776
+ }
777
+ }
778
+
40
779
  function asStringArray(value: unknown): string[] {
41
780
  return Array.isArray(value)
42
781
  ? value.filter((item): item is string => typeof item === 'string')
@@ -197,6 +936,8 @@ function primitiveToolFor(primitive: AdapterPrimitive): Tool {
197
936
  return ResolveLocationPrimitive as Tool
198
937
  case 'send':
199
938
  return SubmitPrimitive as Tool
939
+ case 'document':
940
+ return DocumentPrimitive as Tool
200
941
  case 'check':
201
942
  return VerifyPrimitive as Tool
202
943
  case 'find':
@@ -212,6 +953,22 @@ function rootInputFor(entry: AdapterManifestEntry, input: Record<string, unknown
212
953
  }
213
954
  }
214
955
 
956
+ function concreteAdapterCallInputFor(
957
+ entry: AdapterManifestEntry,
958
+ input: Record<string, unknown>,
959
+ ): Record<string, unknown> {
960
+ if (input.tool_id !== entry.tool_id) return input
961
+ const params = input.params
962
+ return isJsonObject(params) ? params : input
963
+ }
964
+
965
+ function validateAdapterContractInput(
966
+ _toolId: string,
967
+ _input: Record<string, unknown>,
968
+ ) {
969
+ return undefined
970
+ }
971
+
215
972
  export function isAdapterToolName(name: string): boolean {
216
973
  return resolveAdapter(name) !== undefined
217
974
  }
@@ -233,219 +990,487 @@ function searchTokens(text: string): string[] {
233
990
  return text.toLowerCase().match(/[\p{L}\p{N}_-]+/gu) ?? []
234
991
  }
235
992
 
236
- function expandedQueryTokens(query: string): Set<string> {
237
- const tokens = new Set(searchTokens(query))
238
- const compact = query.toLowerCase()
239
- if (/[날씨기상비강수기온습도풍속예보관측실황]/u.test(compact)) {
240
- for (const token of [
241
- '날씨',
242
- '기상',
243
- '현재',
244
- '관측',
245
- '실황',
246
- 'weather',
247
- 'current',
248
- 'observation',
249
- 'forecast',
250
- 'temperature',
251
- 'precipitation',
252
- 'humidity',
253
- 'wind',
254
- ]) {
255
- tokens.add(token)
256
- }
993
+ const KOREAN_TRAILING_PARTICLES = [
994
+ '으로부터',
995
+ '에서부터',
996
+ '에게서',
997
+ '한테서',
998
+ '까지',
999
+ '부터',
1000
+ '으로',
1001
+ '에서',
1002
+ '에게',
1003
+ '한테',
1004
+ '처럼',
1005
+ '보다',
1006
+ '하고',
1007
+ '이며',
1008
+ '이고',
1009
+ '',
1010
+ '',
1011
+ '과',
1012
+ '은',
1013
+ '는',
1014
+ '이',
1015
+ '가',
1016
+ '을',
1017
+ '를',
1018
+ '의',
1019
+ '에',
1020
+ '도',
1021
+ '만',
1022
+ ] as const
1023
+
1024
+ const LOW_SIGNAL_DISCOVERY_TOKENS = new Set([
1025
+ '지금',
1026
+ '현재',
1027
+ '오늘',
1028
+ '내일',
1029
+ '모레',
1030
+ '이번',
1031
+ '근처',
1032
+ '주변',
1033
+ '어디',
1034
+ '어떻게',
1035
+ 'now',
1036
+ 'current',
1037
+ 'today',
1038
+ 'tomorrow',
1039
+ 'latest',
1040
+ 'nearby',
1041
+ 'realtime',
1042
+ 'real-time',
1043
+ ])
1044
+
1045
+ const KMA_LIFESTYLE_WEATHER_RE =
1046
+ /(날씨|현재\s*기상|실황|관측|예보|기온|습도|풍속|지금\s*비|비\s*(?:와|오|올|내리)|우산|강수|소나기|산책|퇴근|current\s+weather|forecast|rain|umbrella|precipitation|temperature)/iu
1047
+ const HEALTHCARE_RE =
1048
+ /(응급|응급실|응급의료|야간\s*진료|야간진료|병원|의원|의료기관|진료\s*가능|\bemergency\b|\ber\b|\bhospital\b|\bclinic\b)/iu
1049
+ const AIR_QUALITY_RE =
1050
+ /(미세먼지|초미세먼지|초미세|대기질|대기오염|공기질|마스크|pm\s*2\.?5|pm\s*10|air\s*korea|airkorea|air\s*quality|airquality)/iu
1051
+ const KMA_ANALYSIS_RE =
1052
+ /(분석자료|이미\s*분석|고해상도\s*격자|객관분석|AWS\s*객관|지도\s*자료|일기도|분석일기도|비구름|바람\s*흐름|날씨\s*흐름|전국\s*날씨|synoptic|weather\s*chart|objective\s*analysis|high[-\s]?resolution|grid)/iu
1053
+ const AIRPORT_AVIATION_RE =
1054
+ /(AMOS|METAR|SPECI|RVR|항공기상|공항기상|활주로|runway|aviation|비행기|항공편|비행편|이륙|착륙|결항|지연|운항|뜰\s*만|뜨나|뜰\s*수|flight|take\s*off|landing|delay|cancel)/iu
1055
+ const POI_LOCATION_RE =
1056
+ /(근처|주변|주위|인근|가까운|우리\s*동네|여기|이\s*근처|현재\s*위치|내\s*위치|역|터미널|공항|캠퍼스|대학교|대학|해수욕장|시장|공원|랜드마크)/iu
1057
+ const ADMIN_LOCATION_RE =
1058
+ /(?:[가-힣]{2,}(?:시|군|구|동|읍|면)\b|[가-힣0-9]{2,}(?<!으)(?:로|길)\b)/iu
1059
+ const COORDINATE_PAIR_RE =
1060
+ /[+-]?\d{1,2}(?:\.\d+)?\s*,\s*[+-]?\d{2,3}(?:\.\d+)?/u
1061
+ const PRIOR_LOCATION_CONTEXT_RE = /\[prior_location_context\]/u
1062
+ const GOV24_RE = /(정부24|gov24|주민등록등본|등본|증명서|민원)/iu
1063
+ const GOV24_READ_ONLY_RE = /(가능\s*여부|준비물|확인|조회|안내|알려)/iu
1064
+ const GOV24_ACTION_RE = /(신청|진행|제출|접수|발급\s*신청|apply|submit|issue)/iu
1065
+ const WELFARE_RE =
1066
+ /(생활비|기초생활|주거급여|긴급복지|저소득|차상위|복지혜택|지원금|진료비\s*바우처|출산휴가|임신|아동수당|첫만남이용권)/iu
1067
+ const CIVIL_BIRTH_HANDOFF_RE =
1068
+ /(출생신고|아기가\s*태어|아동수당|첫만남이용권|피부양자\s*등록)/iu
1069
+ const UTILITY_RE = /(전기|수도|도시가스|요금|자동이체|공과금|고지서|납부)/iu
1070
+ const HOUSING_HANDOFF_RE =
1071
+ /(생애최초\s*주택구입|주택구입|대출|취득세|등기|전입)/iu
1072
+ const CIVIL_DEATH_RE = /(사망|돌아가|장례|유족|상속|재산|국민연금)/iu
1073
+
1074
+ const LOCATION_TOOL_IDS = new Set([
1075
+ 'locate',
1076
+ 'kakao_address_search',
1077
+ 'kakao_keyword_search',
1078
+ 'kakao_coord_to_region',
1079
+ 'juso_adm_cd_lookup',
1080
+ 'sgis_adm_cd_lookup',
1081
+ ])
1082
+ const KMA_LIFESTYLE_WEATHER_TOOL_IDS = new Set([
1083
+ 'kma_current_observation',
1084
+ 'kma_ultra_short_term_forecast',
1085
+ 'kma_short_term_forecast',
1086
+ 'kma_forecast_fetch',
1087
+ ])
1088
+ const EMERGENCY_TOOL_IDS = new Set([
1089
+ 'nmc_emergency_search',
1090
+ 'nmc_aed_site_locate',
1091
+ 'hira_hospital_search',
1092
+ 'hira_medical_institution_detail',
1093
+ ])
1094
+ const GOV24_LOOKUP_TOOL_IDS = new Set(['mock_lookup_module_gov24_certificate'])
1095
+ const GOV24_ACTION_TOOL_IDS = new Set([
1096
+ 'mock_lookup_module_gov24_certificate',
1097
+ 'mock_verify_module_simple_auth',
1098
+ 'mock_verify_ganpyeon_injeung',
1099
+ 'mock_verify_mobile_id',
1100
+ 'mock_submit_module_gov24_minwon',
1101
+ ])
1102
+ const WELFARE_TOOL_IDS = new Set([
1103
+ 'mohw_welfare_eligibility_search',
1104
+ 'mock_welfare_application_submit_v1',
1105
+ ])
1106
+ const UTILITY_TOOL_IDS = new Set([
1107
+ 'kepco_contract_power_usage',
1108
+ 'mock_kftc_opengiro_bill_send_v1',
1109
+ 'mock_kftc_opengiro_payment_send_v1',
1110
+ ])
1111
+ const CIVIL_DEATH_TOOL_IDS = new Set([
1112
+ 'bfc_funeral_area_fee',
1113
+ 'reb_real_estate_stat_table',
1114
+ 'mohw_welfare_eligibility_search',
1115
+ ])
1116
+
1117
+ type ProviderRoutingIntent = {
1118
+ readonly hasCoordinateLocationAnchor: boolean
1119
+ readonly hasAdminLocationAnchor: boolean
1120
+ readonly hasPriorLocationContext: boolean
1121
+ readonly hasLocationAnchor: boolean
1122
+ readonly hasLifestyleWeather: boolean
1123
+ readonly hasEmergencyMedical: boolean
1124
+ readonly hasGov24ReadOnly: boolean
1125
+ readonly hasGov24Action: boolean
1126
+ readonly hasWelfare: boolean
1127
+ readonly hasCivilBirthHandoff: boolean
1128
+ readonly hasUtility: boolean
1129
+ readonly hasHousingHandoff: boolean
1130
+ readonly hasCivilDeath: boolean
1131
+ }
1132
+
1133
+ type AdapterSelectionOptions = {
1134
+ readonly hasCurrentTurnLocationContext?: boolean
1135
+ }
1136
+
1137
+ function hasHangul(text: string): boolean {
1138
+ return /\p{Script=Hangul}/u.test(text)
1139
+ }
1140
+
1141
+ function extractProviderRoutingIntent(query: string): ProviderRoutingIntent {
1142
+ const hasEmergencyMedical = HEALTHCARE_RE.test(query)
1143
+ const hasGov24 = GOV24_RE.test(query)
1144
+ const hasGov24Action = hasGov24 && GOV24_ACTION_RE.test(query)
1145
+ const hasCoordinateLocationAnchor = COORDINATE_PAIR_RE.test(query)
1146
+ const hasAdminLocationAnchor = ADMIN_LOCATION_RE.test(query)
1147
+ const hasPoiLocationAnchor = POI_LOCATION_RE.test(query)
1148
+ const hasPriorLocationContext = PRIOR_LOCATION_CONTEXT_RE.test(query)
1149
+ return {
1150
+ hasCoordinateLocationAnchor,
1151
+ hasAdminLocationAnchor,
1152
+ hasPriorLocationContext,
1153
+ hasLocationAnchor:
1154
+ hasCoordinateLocationAnchor ||
1155
+ hasPoiLocationAnchor ||
1156
+ hasAdminLocationAnchor ||
1157
+ hasPriorLocationContext,
1158
+ hasLifestyleWeather:
1159
+ KMA_LIFESTYLE_WEATHER_RE.test(query) &&
1160
+ !hasEmergencyMedical &&
1161
+ !AIR_QUALITY_RE.test(query) &&
1162
+ !KMA_ANALYSIS_RE.test(query) &&
1163
+ !AIRPORT_AVIATION_RE.test(query),
1164
+ hasEmergencyMedical,
1165
+ hasGov24ReadOnly:
1166
+ hasGov24 &&
1167
+ GOV24_READ_ONLY_RE.test(query) &&
1168
+ !hasGov24Action,
1169
+ hasGov24Action,
1170
+ hasWelfare: WELFARE_RE.test(query),
1171
+ hasCivilBirthHandoff: CIVIL_BIRTH_HANDOFF_RE.test(query),
1172
+ hasUtility: UTILITY_RE.test(query),
1173
+ hasHousingHandoff: HOUSING_HANDOFF_RE.test(query),
1174
+ hasCivilDeath: CIVIL_DEATH_RE.test(query),
257
1175
  }
258
- if (/(응급|응급실|er|emergency)/u.test(compact)) {
259
- for (const token of [
260
- '응급',
261
- '응급실',
262
- '응급의료',
263
- '응급의료센터',
264
- '실시간',
265
- '병상',
266
- '야간',
267
- 'emergency',
268
- 'room',
269
- 'er',
270
- 'nmc',
271
- ]) {
272
- tokens.add(token)
273
- }
1176
+ }
1177
+
1178
+ function addSetValues(target: Set<string>, values: ReadonlySet<string>): void {
1179
+ for (const value of values) target.add(value)
1180
+ }
1181
+
1182
+ function restrictiveToolIdsForIntent(
1183
+ intent: ProviderRoutingIntent,
1184
+ options: AdapterSelectionOptions = {},
1185
+ ): Set<string> | undefined {
1186
+ const allowed = new Set<string>()
1187
+ let restrictive = false
1188
+
1189
+ if (intent.hasGov24ReadOnly) {
1190
+ restrictive = true
1191
+ addSetValues(allowed, GOV24_LOOKUP_TOOL_IDS)
1192
+ } else if (intent.hasGov24Action) {
1193
+ restrictive = true
1194
+ addSetValues(allowed, GOV24_ACTION_TOOL_IDS)
274
1195
  }
275
- if (/(병원|의료|진료|약국|hospital|clinic|medical)/u.test(compact)) {
276
- for (const token of [
277
- '병원',
278
- '의료기관',
279
- '진료',
280
- '진료과목',
281
- '야간',
282
- '약국',
283
- 'hospital',
284
- 'clinic',
285
- 'medical',
286
- 'nearby',
287
- ]) {
288
- tokens.add(token)
1196
+
1197
+ if (intent.hasLifestyleWeather) {
1198
+ restrictive = true
1199
+ if (
1200
+ options.hasCurrentTurnLocationContext !== true &&
1201
+ !intent.hasPriorLocationContext
1202
+ ) {
1203
+ addSetValues(allowed, LOCATION_TOOL_IDS)
289
1204
  }
1205
+ addSetValues(allowed, KMA_LIFESTYLE_WEATHER_TOOL_IDS)
290
1206
  }
291
- if (/(aed|자동심장|심장충격|제세동)/u.test(compact)) {
292
- for (const token of ['aed', '자동심장충격기', '자동제세동기', '심장충격기', '위치']) {
293
- tokens.add(token)
1207
+
1208
+ if (intent.hasEmergencyMedical) {
1209
+ restrictive = true
1210
+ if (
1211
+ options.hasCurrentTurnLocationContext !== true &&
1212
+ !intent.hasPriorLocationContext &&
1213
+ intent.hasLocationAnchor
1214
+ ) {
1215
+ addSetValues(allowed, LOCATION_TOOL_IDS)
294
1216
  }
295
- }
296
- if (/(미세먼지|초미세|대기질|공기질|airquality|air quality)/u.test(compact)) {
297
- for (const token of ['미세먼지', '대기질', '대기오염', 'airkorea', 'air', 'quality']) {
298
- tokens.add(token)
1217
+ if (
1218
+ options.hasCurrentTurnLocationContext === true ||
1219
+ intent.hasPriorLocationContext ||
1220
+ intent.hasCoordinateLocationAnchor ||
1221
+ intent.hasAdminLocationAnchor
1222
+ ) {
1223
+ addSetValues(allowed, EMERGENCY_TOOL_IDS)
299
1224
  }
300
1225
  }
301
- if (/(법률|변호사|무료상담|상담)/u.test(compact)) {
302
- for (const token of ['법률', '변호사', '마을변호사', '상담', 'legal', 'lawyer']) {
303
- tokens.add(token)
304
- }
1226
+
1227
+ if (intent.hasWelfare) {
1228
+ restrictive = true
1229
+ addSetValues(allowed, WELFARE_TOOL_IDS)
305
1230
  }
306
- if (/(장례|화장|봉안|장사|funeral)/u.test(compact)) {
307
- for (const token of ['장례', '장례식장', '시설사용료', 'funeral', 'fee']) {
308
- tokens.add(token)
309
- }
1231
+
1232
+ if (intent.hasCivilBirthHandoff) {
1233
+ restrictive = true
310
1234
  }
311
- if (/(취업|채용|공고|공무원|job|recruit)/u.test(compact)) {
312
- for (const token of ['취업', '채용', '공고', '공무원', 'public', 'job']) {
313
- tokens.add(token)
314
- }
1235
+
1236
+ if (intent.hasUtility) {
1237
+ restrictive = true
1238
+ addSetValues(allowed, UTILITY_TOOL_IDS)
315
1239
  }
316
- if (/(대학|등록금|유학생|tuition|university)/u.test(compact)) {
317
- for (const token of ['대학', '등록금', '유학생', '대학알리미', 'tuition', 'university']) {
318
- tokens.add(token)
319
- }
1240
+
1241
+ if (intent.hasHousingHandoff) {
1242
+ restrictive = true
320
1243
  }
321
- if (/(전력|전기|한전|계약종별|power|kepco)/u.test(compact)) {
322
- for (const token of ['전력', '전기사용량', '계약종별', '한전', 'kepco', 'power', 'usage']) {
323
- tokens.add(token)
324
- }
1244
+
1245
+ if (intent.hasCivilDeath) {
1246
+ restrictive = true
1247
+ addSetValues(allowed, CIVIL_DEATH_TOOL_IDS)
325
1248
  }
326
- if (/(특보|예비특보|경보|주의보|태풍|warning|alert)/u.test(compact)) {
327
- for (const token of ['특보', '예비특보', '경보', '주의보', '기상청', 'weather', 'alert']) {
328
- tokens.add(token)
329
- }
1249
+
1250
+ return restrictive ? allowed : undefined
1251
+ }
1252
+
1253
+ function routingIntentBoostForTool(
1254
+ toolId: string,
1255
+ intent: ProviderRoutingIntent,
1256
+ ): number {
1257
+ if (intent.hasGov24ReadOnly && GOV24_LOOKUP_TOOL_IDS.has(toolId)) return 1200
1258
+ if (intent.hasGov24Action && GOV24_ACTION_TOOL_IDS.has(toolId)) return 1000
1259
+ if (intent.hasLifestyleWeather) {
1260
+ if (toolId === 'kakao_keyword_search') return 1100
1261
+ if (toolId === 'kakao_address_search') return 1000
1262
+ if (toolId === 'kma_current_observation') return 900
1263
+ if (toolId === 'kma_ultra_short_term_forecast') return 800
1264
+ if (toolId === 'kma_short_term_forecast') return 650
1265
+ if (LOCATION_TOOL_IDS.has(toolId)) return 260
330
1266
  }
331
- if (/(교통사고|사고\s*위험|사고다발|위험\s*(구간|도로|지점)|어린이보호구역|보호구역|도로\s*구간|accident|hazard|hotspot)/u.test(compact)) {
332
- for (const token of [
333
- '교통사고',
334
- '사고',
335
- '위험',
336
- '위험지점',
337
- '사고다발',
338
- '사고다발구역',
339
- '어린이보호구역',
340
- '행정동코드',
341
- 'koroad',
342
- 'accident',
343
- 'hazard',
344
- 'hotspot',
345
- ]) {
346
- tokens.add(token)
347
- }
1267
+ if (intent.hasEmergencyMedical && intent.hasLocationAnchor) {
1268
+ if (toolId === 'nmc_emergency_search') return 1200
1269
+ if (toolId === 'nmc_aed_site_locate') return 950
1270
+ if (toolId === 'kakao_keyword_search') return 900
1271
+ if (toolId === 'kakao_address_search') return 800
1272
+ if (toolId === 'kakao_coord_to_region') return 500
1273
+ if (toolId === 'hira_hospital_search') return 250
1274
+ if (toolId === 'hira_medical_institution_detail') return 200
1275
+ if (LOCATION_TOOL_IDS.has(toolId)) return 300
348
1276
  }
349
- if (/(주소|위치|좌표|행정|[가-힣]+(시|군|구|동|읍|면|로|길))/u.test(compact)) {
350
- for (const token of [
351
- 'locate',
352
- '위치',
353
- '주소',
354
- '좌표',
355
- '행정동',
356
- '법정동',
357
- 'geocode',
358
- 'address',
359
- 'kakao',
360
- ]) {
361
- tokens.add(token)
362
- }
1277
+ if (intent.hasWelfare && WELFARE_TOOL_IDS.has(toolId)) return 1000
1278
+ if (intent.hasUtility && UTILITY_TOOL_IDS.has(toolId)) return 1000
1279
+ if (intent.hasCivilDeath && CIVIL_DEATH_TOOL_IDS.has(toolId)) return 1000
1280
+ return 0
1281
+ }
1282
+
1283
+ function koreanParticleStrippedVariants(token: string): string[] {
1284
+ if (!hasHangul(token)) return []
1285
+ const variants: string[] = []
1286
+ let current = token
1287
+ for (let i = 0; i < 2; i += 1) {
1288
+ const nextSuffix = KOREAN_TRAILING_PARTICLES.find(
1289
+ suffix => current.length > suffix.length + 1 && current.endsWith(suffix),
1290
+ )
1291
+ if (!nextSuffix) break
1292
+ current = current.slice(0, -nextSuffix.length)
1293
+ variants.push(current)
363
1294
  }
364
- if (/(근처|주변|인근|가까운|역|터미널|공항|캠퍼스|대학교|대학|해수욕장|시장|공원|랜드마크|nearby|around)/u.test(compact)) {
365
- for (const token of [
366
- '장소',
367
- '키워드',
368
- 'poi',
369
- '랜드마크',
370
- '역',
371
- 'keyword',
372
- 'station',
373
- 'place',
374
- ]) {
375
- tokens.add(token)
1295
+ return variants
1296
+ }
1297
+
1298
+ function expandedTokensForText(text: string): Set<string> {
1299
+ const tokens = new Set<string>()
1300
+ for (const token of searchTokens(text)) {
1301
+ tokens.add(token)
1302
+ for (const variant of koreanParticleStrippedVariants(token)) {
1303
+ tokens.add(variant)
376
1304
  }
377
1305
  }
378
1306
  return tokens
379
1307
  }
380
1308
 
381
- type ScoredAdapterEntry = {
382
- entry: AdapterManifestEntry
383
- score: number
1309
+ function expandedQueryTokens(query: string): Set<string> {
1310
+ return expandedTokensForText(query)
1311
+ }
1312
+
1313
+ function isUsefulDiscoveryToken(token: string): boolean {
1314
+ const compact = token.replace(/[_-]/gu, '')
1315
+ if (compact.length === 0) return false
1316
+ if (LOW_SIGNAL_DISCOVERY_TOKENS.has(token)) return false
1317
+ if (hasHangul(compact)) return compact.length >= 2
1318
+ if (compact === 'er') return true
1319
+ return compact.length >= 3
1320
+ }
1321
+
1322
+ function isSingleHangulPlaceSuffixMatch(
1323
+ fieldToken: string,
1324
+ queryToken: string,
1325
+ ): boolean {
1326
+ return (
1327
+ hasHangul(fieldToken) &&
1328
+ fieldToken.length === 1 &&
1329
+ hasHangul(queryToken) &&
1330
+ queryToken.length >= 3 &&
1331
+ queryToken.endsWith(fieldToken)
1332
+ )
1333
+ }
1334
+
1335
+ function fieldMatchesToken(
1336
+ fieldTokens: Set<string>,
1337
+ fieldText: string,
1338
+ queryToken: string,
1339
+ ): boolean {
1340
+ if (fieldTokens.has(queryToken) || fieldText.includes(queryToken)) {
1341
+ return true
1342
+ }
1343
+ if (!isUsefulDiscoveryToken(queryToken)) return false
1344
+ for (const fieldToken of fieldTokens) {
1345
+ if (isSingleHangulPlaceSuffixMatch(fieldToken, queryToken)) {
1346
+ return true
1347
+ }
1348
+ if (!isUsefulDiscoveryToken(fieldToken)) continue
1349
+ if (fieldToken.includes(queryToken) || queryToken.includes(fieldToken)) {
1350
+ return true
1351
+ }
1352
+ }
1353
+ return false
1354
+ }
1355
+
1356
+ function requiredInputFieldsFor(entry: AdapterManifestEntry): string[] {
1357
+ return asStringArray(asJsonObject(entry.input_schema_json).required)
384
1358
  }
385
1359
 
386
- function queryExplicitlyMentionsCoordinates(query: string): boolean {
387
- return /좌표|위도|경도|\blat\b|\blon\b|\blongitude\b|\blatitude\b|wgs84|coord|coord2region|reverse geocode|q0|q1/i.test(query)
1360
+ function isOpaqueProviderIdentifierField(fieldName: string): boolean {
1361
+ return (
1362
+ fieldName === 'ykiho' ||
1363
+ fieldName === 'id' ||
1364
+ fieldName.endsWith('_id')
1365
+ )
388
1366
  }
389
1367
 
390
- function isReverseGeocodeAdapter(toolId: string): boolean {
391
- return toolId === 'kakao_coord_to_region' || toolId === 'sgis_adm_cd_lookup'
1368
+ function isOpaqueIdentifierOnlyInitialCandidate(
1369
+ entry: AdapterManifestEntry,
1370
+ queryTokens: Set<string>,
1371
+ query: string,
1372
+ ): boolean {
1373
+ const requiredFields = requiredInputFieldsFor(entry)
1374
+ if (requiredFields.length === 0) return false
1375
+ if (!requiredFields.every(isOpaqueProviderIdentifierField)) return false
1376
+
1377
+ const normalizedQuery = query.toLowerCase()
1378
+ if (normalizedQuery.includes(entry.tool_id.toLowerCase())) return false
1379
+ return !requiredFields.some(fieldName => queryTokens.has(fieldName.toLowerCase()))
392
1380
  }
393
1381
 
394
- function queryTargetsKoroadHazardDataset(query: string): boolean {
395
- return /(사고\s*위험|위험\s*(구간|도로|지점)|도로\s*구간|어린이보호구역|보호구역|스쿨존|행정동코드|adm_cd|hazard|hotspot)/iu.test(query)
1382
+ type AdapterScore = {
1383
+ score: number
1384
+ qualifyingDiscoveryMatches: number
1385
+ }
1386
+
1387
+ type ScoredAdapterEntry = {
1388
+ entry: AdapterManifestEntry
1389
+ score: number
396
1390
  }
397
1391
 
398
1392
  function scoreAdapterEntry(
399
1393
  entry: AdapterManifestEntry,
400
1394
  queryTokens: Set<string>,
401
1395
  query: string,
402
- ): number {
1396
+ ): AdapterScore {
1397
+ const toolId = entry.tool_id.toLowerCase()
1398
+ const name = entry.name.toLowerCase()
403
1399
  const searchHint = entry.search_hint.toLowerCase()
404
1400
  const description = (entry.llm_description ?? '').toLowerCase()
405
1401
  const haystack = [
406
- entry.tool_id,
407
- entry.name,
1402
+ toolId,
1403
+ name,
408
1404
  entry.primitive,
409
1405
  searchHint,
410
1406
  description,
411
1407
  ].join(' ').toLowerCase()
412
- const hintTokens = new Set(searchTokens(searchHint))
1408
+ const toolIdTokens = expandedTokensForText(toolId)
1409
+ const nameTokens = expandedTokensForText(name)
1410
+ const hintTokens = expandedTokensForText(searchHint)
413
1411
  let score = 0
1412
+ let qualifyingDiscoveryMatches = 0
414
1413
  for (const token of queryTokens) {
415
1414
  if (!token) continue
416
- if (entry.tool_id.toLowerCase().includes(token)) score += 12
417
- if (hintTokens.has(token)) score += 8
418
- else if (searchHint.includes(token)) score += 4
1415
+ let matchedDiscovery = false
1416
+ if (fieldMatchesToken(toolIdTokens, toolId, token)) {
1417
+ score += 12
1418
+ matchedDiscovery = true
1419
+ }
1420
+ if (fieldMatchesToken(hintTokens, searchHint, token)) {
1421
+ score += 8
1422
+ matchedDiscovery = true
1423
+ }
1424
+ if (fieldMatchesToken(nameTokens, name, token)) {
1425
+ score += 4
1426
+ matchedDiscovery = true
1427
+ }
419
1428
  if (description.includes(token)) score += 2
420
1429
  if (haystack.includes(token)) score += 1
1430
+ if (matchedDiscovery && isUsefulDiscoveryToken(token)) {
1431
+ qualifyingDiscoveryMatches += 1
1432
+ }
421
1433
  }
422
- if (
423
- isReverseGeocodeAdapter(entry.tool_id) &&
424
- !queryExplicitlyMentionsCoordinates(query)
425
- ) {
426
- score = Math.max(0, score - 24)
427
- }
428
- if (queryTargetsKoroadHazardDataset(query)) {
429
- if (entry.tool_id === 'koroad_accident_hazard_search') score += 32
430
- if (entry.tool_id === 'koroad_accident_search') score = 0
1434
+ if (query.toLowerCase().includes(toolId)) {
1435
+ score += 1000
1436
+ qualifyingDiscoveryMatches += 1
431
1437
  }
432
- return score
1438
+ return { score, qualifyingDiscoveryMatches }
433
1439
  }
434
1440
 
435
1441
  export function selectTopKAdapterToolNamesForQuery(
436
1442
  query: string,
437
1443
  maxResults = 5,
1444
+ options: AdapterSelectionOptions = {},
438
1445
  ): string[] {
439
1446
  const normalizedQuery = query.trim()
440
1447
  if (!normalizedQuery || maxResults <= 0) return []
441
1448
  const queryTokens = expandedQueryTokens(normalizedQuery)
1449
+ const routingIntent = extractProviderRoutingIntent(normalizedQuery)
1450
+ const restrictiveToolIds = restrictiveToolIdsForIntent(routingIntent, options)
442
1451
  const ranked = listAdapters()
443
1452
  .filter(entry => !ROOT_PRIMITIVE_TOOL_NAMES.has(entry.tool_id))
444
- .map(entry => ({
445
- entry,
446
- score: scoreAdapterEntry(entry, queryTokens, normalizedQuery),
447
- }))
448
- .filter(candidate => candidate.score > 0)
1453
+ .map(entry => {
1454
+ const result = scoreAdapterEntry(entry, queryTokens, normalizedQuery)
1455
+ const routingBoost = routingIntentBoostForTool(entry.tool_id, routingIntent)
1456
+ return {
1457
+ entry,
1458
+ score: result.score + routingBoost,
1459
+ qualifyingDiscoveryMatches:
1460
+ result.qualifyingDiscoveryMatches + (routingBoost > 0 ? 1 : 0),
1461
+ }
1462
+ })
1463
+ .filter(candidate =>
1464
+ (restrictiveToolIds === undefined ||
1465
+ restrictiveToolIds.has(candidate.entry.tool_id)) &&
1466
+ candidate.score > 0 &&
1467
+ candidate.qualifyingDiscoveryMatches > 0 &&
1468
+ !isOpaqueIdentifierOnlyInitialCandidate(
1469
+ candidate.entry,
1470
+ queryTokens,
1471
+ normalizedQuery,
1472
+ )
1473
+ )
449
1474
  .sort((a, b) => {
450
1475
  if (b.score !== a.score) return b.score - a.score
451
1476
  return a.entry.tool_id.localeCompare(b.entry.tool_id)
@@ -486,6 +1511,8 @@ function buildAdapterTool(entry: AdapterManifestEntry): Tool {
486
1511
  const primitiveTool = primitiveToolFor(primitive)
487
1512
  const adapterInputSchema = inputSchemaFor(entry)
488
1513
  const adapterInputJSONSchema = inputJSONSchemaFor(entry)
1514
+ const directCheckAdapterRequiresPermission =
1515
+ primitive === 'check' && !ROOT_PRIMITIVE_TOOL_NAMES.has(entry.tool_id)
489
1516
 
490
1517
  return buildTool({
491
1518
  name: entry.tool_id,
@@ -516,13 +1543,19 @@ function buildAdapterTool(entry: AdapterManifestEntry): Tool {
516
1543
  },
517
1544
 
518
1545
  isReadOnly(input) {
1546
+ if (directCheckAdapterRequiresPermission) return false
519
1547
  return primitiveTool.isReadOnly(rootInputFor(entry, input))
520
1548
  },
521
1549
 
522
1550
  isDestructive(input) {
1551
+ if (directCheckAdapterRequiresPermission) return true
523
1552
  return primitiveTool.isDestructive?.(rootInputFor(entry, input)) ?? false
524
1553
  },
525
1554
 
1555
+ async checkPermissions(input, context) {
1556
+ return primitiveTool.checkPermissions(rootInputFor(entry, input), context)
1557
+ },
1558
+
526
1559
  async description() {
527
1560
  return entry.name
528
1561
  },
@@ -536,7 +1569,7 @@ function buildAdapterTool(entry: AdapterManifestEntry): Tool {
536
1569
  ].join('\n\n')
537
1570
  },
538
1571
 
539
- async validateInput(input) {
1572
+ async validateInput(input, context) {
540
1573
  if (!resolveAdapter(entry.tool_id)) {
541
1574
  return {
542
1575
  result: false as const,
@@ -551,29 +1584,64 @@ function buildAdapterTool(entry: AdapterManifestEntry): Tool {
551
1584
  errorCode: 1,
552
1585
  }
553
1586
  }
1587
+ const contractInput = validateAdapterContractInput(
1588
+ entry.tool_id,
1589
+ input as Record<string, unknown>,
1590
+ )
1591
+ if (contractInput) return contractInput
554
1592
  return { result: true as const }
555
1593
  },
556
1594
 
557
1595
  async call(input, context) {
558
- return dispatchPrimitive({
1596
+ const normalizedDocumentInput = normalizeExplicitDocumentArtifactInput(
1597
+ entry.tool_id,
1598
+ input,
1599
+ context.messages,
1600
+ )
1601
+ const adapterCallInput = concreteAdapterCallInputFor(
1602
+ entry,
1603
+ normalizedDocumentInput,
1604
+ )
1605
+ const result = await dispatchPrimitive({
559
1606
  primitive,
560
1607
  toolName: entry.tool_id,
561
- args: input,
1608
+ args: adapterCallInput,
562
1609
  context,
563
1610
  registry: getOrCreatePendingCallRegistry(),
564
1611
  bridge: getOrCreateUmmayaBridge(),
1612
+ timeoutMs:
1613
+ primitive === 'document'
1614
+ ? resolveDocumentPrimitiveTimeoutMs()
1615
+ : undefined,
565
1616
  })
1617
+ return {
1618
+ ...result,
1619
+ data: applyDocumentVisualRenderGateToOutput(result.data),
1620
+ }
566
1621
  },
567
1622
 
568
1623
  userFacingName(input) {
1624
+ if (DOCUMENT_TOOL_NAMES.has(entry.tool_id)) {
1625
+ return 'Document'
1626
+ }
569
1627
  return primitiveTool.userFacingName(rootInputFor(entry, input ?? {}))
570
1628
  },
571
1629
 
572
1630
  mapToolResultToToolResultBlockParam(output, toolUseID) {
573
- return primitiveTool.mapToolResultToToolResultBlockParam(output, toolUseID)
1631
+ const gatedOutput = applyDocumentVisualRenderGateToOutput(output)
1632
+ const block = primitiveTool.mapToolResultToToolResultBlockParam(gatedOutput, toolUseID)
1633
+ return isDocumentVisualRenderFailedOutput(gatedOutput)
1634
+ ? { ...block, is_error: true }
1635
+ : block
574
1636
  },
575
1637
 
576
1638
  renderToolUseMessage(input, options) {
1639
+ if (DOCUMENT_TOOL_NAMES.has(entry.tool_id)) {
1640
+ return renderDocumentToolUseMessage(
1641
+ entry.tool_id,
1642
+ input as Record<string, unknown>,
1643
+ )
1644
+ }
577
1645
  const rendered = primitiveTool.renderToolUseMessage(
578
1646
  rootInputFor(entry, input),
579
1647
  options,
@@ -582,8 +1650,16 @@ function buildAdapterTool(entry: AdapterManifestEntry): Tool {
582
1650
  },
583
1651
 
584
1652
  renderToolResultMessage(output, progressMessagesForMessage, options) {
1653
+ const gatedOutput = applyDocumentVisualRenderGateToOutput(output)
1654
+ if (shouldHideSuccessfulIntermediateDocumentResult(gatedOutput)) {
1655
+ return null
1656
+ }
1657
+ const documentResult = renderDocumentToolResultIfPresent(gatedOutput, options)
1658
+ if (documentResult !== null) {
1659
+ return documentResult
1660
+ }
585
1661
  return primitiveTool.renderToolResultMessage?.(
586
- output,
1662
+ gatedOutput,
587
1663
  progressMessagesForMessage,
588
1664
  options,
589
1665
  ) ?? null