ummaya 0.2.4 → 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 (477) hide show
  1. package/README.md +15 -2
  2. package/bin/ummaya +10 -1
  3. package/npm-shrinkwrap.json +253 -2
  4. package/package.json +5 -1
  5. package/prompts/manifest.yaml +1 -1
  6. package/prompts/system_v1.md +1 -0
  7. package/pyproject.toml +26 -2
  8. package/specs/2803-document-production-hardening/contracts/document-tools.schema.json +1043 -0
  9. package/src/ummaya/_canonical/__init__.py +2 -0
  10. package/src/ummaya/engine/engine.py +29 -132
  11. package/src/ummaya/evidence/__init__.py +21 -2
  12. package/src/ummaya/evidence/dataset_contract.py +193 -0
  13. package/src/ummaya/evidence/document_authoring_cases.py +33 -0
  14. package/src/ummaya/evidence/document_harness.py +313 -0
  15. package/src/ummaya/evidence/document_viewer_ux.py +391 -0
  16. package/src/ummaya/evidence/gates.py +70 -0
  17. package/src/ummaya/evidence/json_types.py +20 -0
  18. package/src/ummaya/evidence/models.py +88 -1
  19. package/src/ummaya/evidence/output_payload.py +89 -0
  20. package/src/ummaya/evidence/payload_documents.py +233 -0
  21. package/src/ummaya/evidence/route_contracts.py +224 -0
  22. package/src/ummaya/evidence/route_helpers.py +150 -0
  23. package/src/ummaya/evidence/runner.py +81 -212
  24. package/src/ummaya/evidence/source_provenance.py +246 -0
  25. package/src/ummaya/evidence/source_provenance_redaction.py +176 -0
  26. package/src/ummaya/evidence/tool_layer.py +39 -0
  27. package/src/ummaya/evidence/tool_layer_models.py +151 -0
  28. package/src/ummaya/ipc/adapter_manifest_emitter.py +26 -10
  29. package/src/ummaya/ipc/document_intent_normalization.py +185 -0
  30. package/src/ummaya/ipc/frame_schema.py +5 -5
  31. package/src/ummaya/ipc/route_diagnostics.py +73 -0
  32. package/src/ummaya/ipc/stdio.py +1109 -477
  33. package/src/ummaya/llm/client.py +102 -3
  34. package/src/ummaya/llm/config.py +8 -3
  35. package/src/ummaya/primitives/__init__.py +6 -2
  36. package/src/ummaya/primitives/delegation.py +1 -1
  37. package/src/ummaya/primitives/document.py +28 -0
  38. package/src/ummaya/settings.py +0 -3
  39. package/src/ummaya/tools/discovery_bridge.py +17 -1
  40. package/src/ummaya/tools/documents/__init__.py +297 -0
  41. package/src/ummaya/tools/documents/adapter_registry.py +487 -0
  42. package/src/ummaya/tools/documents/archive_container_probe.py +167 -0
  43. package/src/ummaya/tools/documents/artifact_store.py +454 -0
  44. package/src/ummaya/tools/documents/authoring.py +283 -0
  45. package/src/ummaya/tools/documents/baselines.py +114 -0
  46. package/src/ummaya/tools/documents/capability.py +331 -0
  47. package/src/ummaya/tools/documents/contracts.py +112 -0
  48. package/src/ummaya/tools/documents/conversion.py +521 -0
  49. package/src/ummaya/tools/documents/diff.py +275 -0
  50. package/src/ummaya/tools/documents/engines.py +163 -0
  51. package/src/ummaya/tools/documents/evaluation.py +291 -0
  52. package/src/ummaya/tools/documents/explicit_values.py +108 -0
  53. package/src/ummaya/tools/documents/fixtures.py +174 -0
  54. package/src/ummaya/tools/documents/format_completion_audit.py +471 -0
  55. package/src/ummaya/tools/documents/formats/__init__.py +2 -0
  56. package/src/ummaya/tools/documents/formats/archive.py +528 -0
  57. package/src/ummaya/tools/documents/formats/base.py +41 -0
  58. package/src/ummaya/tools/documents/formats/code_file.py +211 -0
  59. package/src/ummaya/tools/documents/formats/data_file.py +272 -0
  60. package/src/ummaya/tools/documents/formats/hwp.py +284 -0
  61. package/src/ummaya/tools/documents/formats/hwpx.py +1837 -0
  62. package/src/ummaya/tools/documents/formats/odf.py +435 -0
  63. package/src/ummaya/tools/documents/formats/ooxml.py +1030 -0
  64. package/src/ummaya/tools/documents/formats/passive.py +766 -0
  65. package/src/ummaya/tools/documents/formats/pdf.py +702 -0
  66. package/src/ummaya/tools/documents/formats/text_web.py +268 -0
  67. package/src/ummaya/tools/documents/hwp_conversion_probe.py +178 -0
  68. package/src/ummaya/tools/documents/hwp_direct_candidate.py +141 -0
  69. package/src/ummaya/tools/documents/inspection.py +289 -0
  70. package/src/ummaya/tools/documents/intake.py +1079 -0
  71. package/src/ummaya/tools/documents/legacy_office_promotion_probe.py +366 -0
  72. package/src/ummaya/tools/documents/models.py +1598 -0
  73. package/src/ummaya/tools/documents/odf_promotion_probe.py +167 -0
  74. package/src/ummaya/tools/documents/orchestrator.py +96 -0
  75. package/src/ummaya/tools/documents/passive_capability_probe.py +251 -0
  76. package/src/ummaya/tools/documents/patch.py +170 -0
  77. package/src/ummaya/tools/documents/pdfa_conformance.py +284 -0
  78. package/src/ummaya/tools/documents/pdfa_promotion_probe.py +198 -0
  79. package/src/ummaya/tools/documents/permissions.py +110 -0
  80. package/src/ummaya/tools/documents/planner.py +616 -0
  81. package/src/ummaya/tools/documents/registry.py +2733 -0
  82. package/src/ummaya/tools/documents/render.py +978 -0
  83. package/src/ummaya/tools/documents/render_comparison.py +113 -0
  84. package/src/ummaya/tools/documents/render_comparison_models.py +74 -0
  85. package/src/ummaya/tools/documents/render_comparison_regions.py +73 -0
  86. package/src/ummaya/tools/documents/render_comparison_style.py +161 -0
  87. package/src/ummaya/tools/documents/reread.py +157 -0
  88. package/src/ummaya/tools/documents/runtime_authoring.py +244 -0
  89. package/src/ummaya/tools/documents/runtime_authoring_bundle.py +76 -0
  90. package/src/ummaya/tools/documents/scorecard.py +184 -0
  91. package/src/ummaya/tools/documents/socratic_planner.py +193 -0
  92. package/src/ummaya/tools/documents/style.py +48 -0
  93. package/src/ummaya/tools/documents/tool_defs.py +523 -0
  94. package/src/ummaya/tools/documents/validate.py +347 -0
  95. package/src/ummaya/tools/executor.py +29 -0
  96. package/src/ummaya/tools/live_proxy.py +0 -3
  97. package/src/ummaya/tools/models.py +5 -1
  98. package/src/ummaya/tools/register_all.py +8 -0
  99. package/src/ummaya/tools/registry.py +10 -1
  100. package/src/ummaya/tools/routing/__init__.py +59 -0
  101. package/src/ummaya/tools/routing/builder.py +105 -0
  102. package/src/ummaya/tools/routing/cards.py +29 -0
  103. package/src/ummaya/tools/routing/decision_service.py +534 -0
  104. package/src/ummaya/tools/routing/decision_types.py +74 -0
  105. package/src/ummaya/tools/routing/feasibility.py +122 -0
  106. package/src/ummaya/tools/routing/intent.py +17 -0
  107. package/src/ummaya/tools/routing/intent_extractor.py +207 -0
  108. package/src/ummaya/tools/routing/intent_patterns.py +160 -0
  109. package/src/ummaya/tools/routing/intent_public_data.py +150 -0
  110. package/src/ummaya/tools/routing/intent_types.py +48 -0
  111. package/src/ummaya/tools/routing/lint.py +78 -0
  112. package/src/ummaya/tools/routing/metadata.py +174 -0
  113. package/src/ummaya/tools/routing/projection.py +340 -0
  114. package/src/ummaya/tools/routing/retrieval_policy.py +629 -0
  115. package/src/ummaya/tools/routing/schema.py +81 -0
  116. package/src/ummaya/tools/routing/types.py +96 -0
  117. package/src/ummaya/tools/routing_index.py +2 -2
  118. package/src/ummaya/tools/search.py +34 -746
  119. package/tests/fixtures/documents/public_forms/baselines.yaml +113 -0
  120. package/tui/package.json +1 -1
  121. package/tui/src/.cc-byte-identical-whitelist.yaml +266 -0
  122. package/tui/src/QueryEngine.ts +12 -8
  123. package/tui/src/bridge/inboundAttachments.ts +3 -3
  124. package/tui/src/cli/handlers/auth.ts +3 -12
  125. package/tui/src/cli/print.ts +7 -7
  126. package/tui/src/commands/insights.ts +1 -1
  127. package/tui/src/commands/install-github-app/types.ts +8 -30
  128. package/tui/src/commands/plugin/types.ts +6 -28
  129. package/tui/src/commands/plugin/unifiedTypes.ts +4 -26
  130. package/tui/src/commands/rename/generateSessionName.ts +1 -1
  131. package/tui/src/components/Feedback.tsx +1 -1
  132. package/tui/src/components/LogoV2/EmergencyTip.tsx +11 -2
  133. package/tui/src/components/LogoV2/WelcomeV2.tsx +1 -3
  134. package/tui/src/components/ScrollKeybindingHandler.tsx +6 -6
  135. package/tui/src/components/Spinner/types.ts +6 -28
  136. package/tui/src/components/agents/generateAgent.ts +1 -1
  137. package/tui/src/components/agents/new-agent-creation/types.ts +4 -26
  138. package/tui/src/components/config/EnvSecretIsolatedEditor.tsx +1 -1
  139. package/tui/src/components/mcp/types.ts +16 -38
  140. package/tui/src/components/messages/AssistantToolUseMessage.tsx +3 -2
  141. package/tui/src/components/messages/UserCrossSessionMessage.ts +16 -4
  142. package/tui/src/components/messages/UserForkBoilerplateMessage.ts +16 -4
  143. package/tui/src/components/messages/UserGitHubWebhookMessage.ts +16 -4
  144. package/tui/src/components/messages/UserToolResultMessage/utils.tsx +3 -2
  145. package/tui/src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.ts +9 -4
  146. package/tui/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.ts +9 -4
  147. package/tui/src/components/primitive/DocumentSocraticReviewBlock.tsx +129 -0
  148. package/tui/src/components/primitive/DocumentToolResultCard.tsx +224 -0
  149. package/tui/src/components/primitive/documentSocraticReview.ts +215 -0
  150. package/tui/src/components/primitive/index.tsx +43 -1
  151. package/tui/src/components/primitive/types.ts +137 -0
  152. package/tui/src/components/ui/option.ts +4 -26
  153. package/tui/src/constants/common.ts +0 -2
  154. package/tui/src/constants/prompts.ts +4 -3
  155. package/tui/src/constants/querySource.ts +4 -26
  156. package/tui/src/entrypoints/sdk/controlTypes.ts +26 -48
  157. package/tui/src/entrypoints/sdk/coreTypes.generated.ts +3 -25
  158. package/tui/src/entrypoints/sdk/runtimeTypes.ts +38 -60
  159. package/tui/src/entrypoints/sdk/sdkUtilityTypes.ts +4 -26
  160. package/tui/src/entrypoints/sdk/settingsTypes.generated.ts +3 -25
  161. package/tui/src/entrypoints/sdk/toolTypes.ts +3 -25
  162. package/tui/src/hooks/toolPermission/handlers/interactiveHandler.ts +10 -0
  163. package/tui/src/hooks/useApiKeyVerification.ts +1 -1
  164. package/tui/src/hooks/useVirtualScroll.ts +1 -1
  165. package/tui/src/ink/ink.tsx +33 -14
  166. package/tui/src/ink/reconciler.ts +2 -3
  167. package/tui/src/ink/render-to-screen.ts +30 -10
  168. package/tui/src/ipc/bridge.ts +62 -15
  169. package/tui/src/ipc/bridgeSingleton.ts +5 -1
  170. package/tui/src/ipc/codec.ts +3 -3
  171. package/tui/src/ipc/frames.generated.ts +12 -12
  172. package/tui/src/ipc/llmClient.ts +151 -27
  173. package/tui/src/ipc/schema/frame.schema.json +1 -1
  174. package/tui/src/keybindings/defaultBindings.ts +4 -0
  175. package/tui/src/main.tsx +29 -11
  176. package/tui/src/native-ts/file-index/index.ts +33 -3
  177. package/tui/src/observability/surface.ts +2 -2
  178. package/tui/src/probes/toolRegistryProbe.tsx +3 -1
  179. package/tui/src/projectOnboardingState.ts +7 -6
  180. package/tui/src/query/chatMessageTypes.ts +18 -0
  181. package/tui/src/query/chatMessagesBuilder.ts +1 -1
  182. package/tui/src/query/deps.ts +1 -1
  183. package/tui/src/query/messageGuards.ts +106 -0
  184. package/tui/src/query/publicDataTerminalRepair.ts +384 -0
  185. package/tui/src/query/run.ts +1075 -0
  186. package/tui/src/query/supportBoundary.ts +168 -0
  187. package/tui/src/query/toolResultErrors.ts +103 -0
  188. package/tui/src/query/toolRunner.ts +687 -0
  189. package/tui/src/query/unavailableToolRepair.ts +118 -0
  190. package/tui/src/query.ts +9 -2186
  191. package/tui/src/screens/REPL.tsx +40 -29
  192. package/tui/src/services/api/adapterManifest.ts +4 -0
  193. package/tui/src/services/api/backendChat/events.ts +117 -0
  194. package/tui/src/services/api/backendChat/finalMessage.ts +40 -0
  195. package/tui/src/services/api/backendChat/frame.ts +9 -0
  196. package/tui/src/services/api/backendChat/streaming.ts +430 -0
  197. package/tui/src/services/api/backendChat/types.ts +62 -0
  198. package/tui/src/services/api/backendChat.ts +1 -0
  199. package/tui/src/services/api/client.ts +65 -2
  200. package/tui/src/services/api/errorUtils.ts +5 -5
  201. package/tui/src/services/api/errors.ts +1 -1
  202. package/tui/src/services/api/logging.ts +1 -1
  203. package/tui/src/services/api/ummaya/evidence.ts +194 -0
  204. package/tui/src/services/api/ummaya/messages.ts +255 -0
  205. package/tui/src/services/api/ummaya/nonStreaming.ts +66 -0
  206. package/tui/src/services/api/ummaya/provider.ts +200 -0
  207. package/tui/src/services/api/ummaya/reasoning.ts +24 -0
  208. package/tui/src/services/api/ummaya/request.ts +200 -0
  209. package/tui/src/services/api/ummaya/selectionContext.ts +240 -0
  210. package/tui/src/services/api/ummaya/streaming.ts +365 -0
  211. package/tui/src/services/api/ummaya/streamingPayload.ts +129 -0
  212. package/tui/src/services/api/ummaya/streamingReader.ts +40 -0
  213. package/tui/src/services/api/ummaya/toolSelection.ts +217 -0
  214. package/tui/src/services/api/ummaya/types.ts +110 -0
  215. package/tui/src/services/api/ummaya/usage.ts +30 -0
  216. package/tui/src/services/api/ummaya.ts +26 -418
  217. package/tui/src/services/api/withRetry.ts +1 -1
  218. package/tui/src/services/awaySummary.ts +2 -2
  219. package/tui/src/services/claudeAiLimits.ts +1 -1
  220. package/tui/src/services/compact/autoCompact.ts +1 -1
  221. package/tui/src/services/compact/compact.ts +1 -1
  222. package/tui/src/services/lsp/types.ts +8 -30
  223. package/tui/src/services/tips/types.ts +6 -28
  224. package/tui/src/services/tokenEstimation.ts +1 -1
  225. package/tui/src/services/toolRegistry/bootGuard.ts +5 -5
  226. package/tui/src/services/toolUseSummary/toolUseSummaryGenerator.ts +1 -1
  227. package/tui/src/services/tools/toolExecution.ts +94 -1
  228. package/tui/src/store/pendingPermissionSlot.ts +1 -1
  229. package/tui/src/store/session-store.ts +10 -36
  230. package/tui/src/stubs/any-stub.ts +15 -10
  231. package/tui/src/stubs/color-diff-napi.ts +37 -23
  232. package/tui/src/stubs/globals.d.ts +3 -3
  233. package/tui/src/stubs/macro-preload.ts +23 -12
  234. package/tui/src/tools/AdapterTool/AdapterTool.ts +1207 -714
  235. package/tui/src/tools/AdapterTool/routeDiagnostics.ts +75 -0
  236. package/tui/src/tools/AgentTool/AgentTool.tsx +84 -1371
  237. package/tui/src/tools/AgentTool/agentToolHandoff.ts +114 -0
  238. package/tui/src/tools/AgentTool/agentToolPartialResult.ts +16 -0
  239. package/tui/src/tools/AgentTool/agentToolProgress.ts +32 -0
  240. package/tui/src/tools/AgentTool/agentToolResolver.ts +161 -0
  241. package/tui/src/tools/AgentTool/agentToolResult.ts +163 -0
  242. package/tui/src/tools/AgentTool/agentToolUtils.ts +14 -686
  243. package/tui/src/tools/AgentTool/asyncAgentLifecycle.ts +208 -0
  244. package/tui/src/tools/AgentTool/asyncLifecycle.ts +153 -0
  245. package/tui/src/tools/AgentTool/backgroundedCompletion.ts +126 -0
  246. package/tui/src/tools/AgentTool/backgroundedLifecycle.ts +174 -0
  247. package/tui/src/tools/AgentTool/foregroundBackground.ts +83 -0
  248. package/tui/src/tools/AgentTool/foregroundDrain.tsx +133 -0
  249. package/tui/src/tools/AgentTool/foregroundFinalize.ts +98 -0
  250. package/tui/src/tools/AgentTool/foregroundLifecycle.tsx +237 -0
  251. package/tui/src/tools/AgentTool/foregroundProgress.tsx +169 -0
  252. package/tui/src/tools/AgentTool/foregroundTask.ts +89 -0
  253. package/tui/src/tools/AgentTool/forkSubagent.ts +1 -12
  254. package/tui/src/tools/AgentTool/forkSubagentGate.ts +34 -0
  255. package/tui/src/tools/AgentTool/launchRouting.ts +203 -0
  256. package/tui/src/tools/AgentTool/lifecycle.ts +244 -0
  257. package/tui/src/tools/AgentTool/mcpRouting.ts +73 -0
  258. package/tui/src/tools/AgentTool/orchestrationSupport.ts +70 -0
  259. package/tui/src/tools/AgentTool/permissions.ts +39 -0
  260. package/tui/src/tools/AgentTool/promptSetup.ts +181 -0
  261. package/tui/src/tools/AgentTool/remoteRouting.ts +62 -0
  262. package/tui/src/tools/AgentTool/resultMapping.ts +116 -0
  263. package/tui/src/tools/AgentTool/resumeAgent.ts +39 -107
  264. package/tui/src/tools/AgentTool/resumeAgentHelpers.ts +140 -0
  265. package/tui/src/tools/AgentTool/runAgent.ts +1 -1
  266. package/tui/src/tools/AgentTool/runtimeConfig.ts +57 -0
  267. package/tui/src/tools/AgentTool/schemas.ts +196 -0
  268. package/tui/src/tools/AgentTool/sourceVerificationPropagation.ts +263 -0
  269. package/tui/src/tools/AgentTool/worktreeLifecycle.ts +105 -0
  270. package/tui/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx +174 -202
  271. package/tui/src/tools/BashTool/BashTool.tsx +71 -1072
  272. package/tui/src/tools/BashTool/bashCommandHelpers.ts +12 -12
  273. package/tui/src/tools/BashTool/bashPermissions/astPreflight.ts +173 -0
  274. package/tui/src/tools/BashTool/bashPermissions/classifierChecks.ts +199 -0
  275. package/tui/src/tools/BashTool/bashPermissions/compoundGuards.ts +53 -0
  276. package/tui/src/tools/BashTool/bashPermissions/constants.ts +99 -0
  277. package/tui/src/tools/BashTool/bashPermissions/index.ts +38 -0
  278. package/tui/src/tools/BashTool/bashPermissions/legacyMisparsing.ts +62 -0
  279. package/tui/src/tools/BashTool/bashPermissions/main.ts +135 -0
  280. package/tui/src/tools/BashTool/bashPermissions/normalizedCommands.ts +33 -0
  281. package/tui/src/tools/BashTool/bashPermissions/operatorFlow.ts +98 -0
  282. package/tui/src/tools/BashTool/bashPermissions/permissionChecks.ts +200 -0
  283. package/tui/src/tools/BashTool/bashPermissions/prefixSuggestions.ts +88 -0
  284. package/tui/src/tools/BashTool/bashPermissions/promptClassifierRules.ts +125 -0
  285. package/tui/src/tools/BashTool/bashPermissions/ruleDelegates.ts +19 -0
  286. package/tui/src/tools/BashTool/bashPermissions/ruleMatching.ts +145 -0
  287. package/tui/src/tools/BashTool/bashPermissions/sandboxAutoAllow.ts +75 -0
  288. package/tui/src/tools/BashTool/bashPermissions/subcommandFlow.ts +205 -0
  289. package/tui/src/tools/BashTool/bashPermissions/subcommandGuards.ts +73 -0
  290. package/tui/src/tools/BashTool/bashPermissions/subcommandResultHelpers.ts +116 -0
  291. package/tui/src/tools/BashTool/bashPermissions/types.ts +26 -0
  292. package/tui/src/tools/BashTool/bashPermissions/wrapperStripping.ts +139 -0
  293. package/tui/src/tools/BashTool/bashPermissions.ts +26 -2621
  294. package/tui/src/tools/BashTool/call.ts +202 -0
  295. package/tui/src/tools/BashTool/callLoader.ts +35 -0
  296. package/tui/src/tools/BashTool/commandClassification.ts +151 -0
  297. package/tui/src/tools/BashTool/commandClassificationLoader.ts +40 -0
  298. package/tui/src/tools/BashTool/cwdReset.ts +33 -0
  299. package/tui/src/tools/BashTool/lineTruncation.ts +11 -0
  300. package/tui/src/tools/BashTool/modeValidation.ts +13 -1
  301. package/tui/src/tools/BashTool/outputPersistence.ts +42 -0
  302. package/tui/src/tools/BashTool/permissionClassification.ts +66 -0
  303. package/tui/src/tools/BashTool/permissionLoader.ts +44 -0
  304. package/tui/src/tools/BashTool/resultLoader.ts +29 -0
  305. package/tui/src/tools/BashTool/resultMapping.ts +83 -0
  306. package/tui/src/tools/BashTool/sandboxPolicy.ts +79 -0
  307. package/tui/src/tools/BashTool/schemas.ts +65 -0
  308. package/tui/src/tools/BashTool/sedEditExecution.ts +59 -0
  309. package/tui/src/tools/BashTool/shellExecution.tsx +245 -0
  310. package/tui/src/tools/BashTool/shellOutputUtils.ts +85 -0
  311. package/tui/src/tools/BashTool/shellPermissionGauntlet.ts +97 -0
  312. package/tui/src/tools/BashTool/uiLoader.ts +37 -0
  313. package/tui/src/tools/BriefTool/upload.ts +1 -1
  314. package/tui/src/tools/CalculatorTool/parser.ts +2 -2
  315. package/tui/src/tools/DocumentPrimitive/DocumentPrimitive.ts +262 -0
  316. package/tui/src/tools/DocumentPrimitive/dispatchNormalization.ts +270 -0
  317. package/tui/src/tools/DocumentPrimitive/documentDestinationPath.ts +18 -0
  318. package/tui/src/tools/DocumentPrimitive/documentMutationGuard.ts +22 -0
  319. package/tui/src/tools/DocumentPrimitive/documentPatchNormalization.ts +248 -0
  320. package/tui/src/tools/DocumentPrimitive/documentSourceVerification.ts +245 -0
  321. package/tui/src/tools/DocumentPrimitive/documentSourceVerificationFields.ts +103 -0
  322. package/tui/src/tools/DocumentPrimitive/modelVisibleOutput.ts +40 -0
  323. package/tui/src/tools/DocumentPrimitive/prompt.ts +35 -0
  324. package/tui/src/tools/FileEditTool/FileEditTool.ts +9 -507
  325. package/tui/src/tools/FileEditTool/call.ts +228 -0
  326. package/tui/src/tools/FileEditTool/validateInput.ts +196 -0
  327. package/tui/src/tools/FileReadTool/imageProcessor.ts +13 -0
  328. package/tui/src/tools/FileWriteTool/FileWriteTool.ts +7 -300
  329. package/tui/src/tools/FileWriteTool/call.ts +223 -0
  330. package/tui/src/tools/FileWriteTool/validateInput.ts +80 -0
  331. package/tui/src/tools/ListMcpResourcesTool/ListMcpResourcesTool.ts +19 -3
  332. package/tui/src/tools/LookupPrimitive/LookupPrimitive.ts +25 -32
  333. package/tui/src/tools/LookupPrimitive/prompt.ts +0 -2
  334. package/tui/src/tools/MCPTool/trustPolicy.ts +118 -0
  335. package/tui/src/tools/McpAuthTool/McpAuthTool.ts +21 -3
  336. package/tui/src/tools/NotebookEditTool/NotebookEditTool.ts +7 -326
  337. package/tui/src/tools/NotebookEditTool/call.ts +254 -0
  338. package/tui/src/tools/NotebookEditTool/notebookModel.ts +51 -0
  339. package/tui/src/tools/NotebookEditTool/validateInput.ts +142 -0
  340. package/tui/src/tools/PowerShellTool/PowerShellTool.tsx +46 -937
  341. package/tui/src/tools/PowerShellTool/acceptEditsCommandValidation.ts +162 -0
  342. package/tui/src/tools/PowerShellTool/call.ts +179 -0
  343. package/tui/src/tools/PowerShellTool/callLoader.ts +37 -0
  344. package/tui/src/tools/PowerShellTool/commandClassification.ts +86 -0
  345. package/tui/src/tools/PowerShellTool/modeValidation.ts +25 -332
  346. package/tui/src/tools/PowerShellTool/outputPersistence.ts +42 -0
  347. package/tui/src/tools/PowerShellTool/permissionClassification.ts +28 -0
  348. package/tui/src/tools/PowerShellTool/resultLoader.ts +31 -0
  349. package/tui/src/tools/PowerShellTool/resultMapping.ts +75 -0
  350. package/tui/src/tools/PowerShellTool/schemas.ts +40 -0
  351. package/tui/src/tools/PowerShellTool/shellExecution.tsx +258 -0
  352. package/tui/src/tools/PowerShellTool/symlinkModeValidation.ts +44 -0
  353. package/tui/src/tools/PowerShellTool/uiLoader.ts +37 -0
  354. package/tui/src/tools/PowerShellTool/validation.ts +39 -0
  355. package/tui/src/tools/ReadMcpResourceTool/ReadMcpResourceTool.ts +19 -3
  356. package/tui/src/tools/ResolveLocationPrimitive/ResolveLocationPrimitive.ts +1 -11
  357. package/tui/src/tools/ResolveLocationPrimitive/prompt.ts +2 -6
  358. package/tui/src/tools/SkillTool/SkillTool.ts +2 -2
  359. package/tui/src/tools/SubmitPrimitive/SubmitPrimitive.ts +27 -10
  360. package/tui/src/tools/TaskCreateTool/TaskCreateTool.ts +16 -2
  361. package/tui/src/tools/TaskGetTool/TaskGetTool.ts +23 -3
  362. package/tui/src/tools/TaskListTool/TaskListTool.ts +22 -4
  363. package/tui/src/tools/TaskOutputTool/TaskOutputTool.tsx +46 -547
  364. package/tui/src/tools/TaskOutputTool/lookup.ts +216 -0
  365. package/tui/src/tools/TaskOutputTool/render.tsx +257 -0
  366. package/tui/src/tools/TaskOutputTool/schemas.ts +55 -0
  367. package/tui/src/tools/TaskOutputTool/serialization.ts +36 -0
  368. package/tui/src/tools/TaskStopTool/TaskStopTool.ts +10 -0
  369. package/tui/src/tools/TaskUpdateTool/TaskUpdateTool.ts +14 -364
  370. package/tui/src/tools/TaskUpdateTool/completion.ts +62 -0
  371. package/tui/src/tools/TaskUpdateTool/schemas.ts +62 -0
  372. package/tui/src/tools/TaskUpdateTool/serialization.ts +46 -0
  373. package/tui/src/tools/TaskUpdateTool/statusUpdate.ts +247 -0
  374. package/tui/src/tools/TodoWriteTool/TodoWriteTool.ts +21 -2
  375. package/tui/src/tools/ToolSearchTool/ToolSearchTool.ts +21 -302
  376. package/tui/src/tools/ToolSearchTool/ccSupportTools.ts +223 -0
  377. package/tui/src/tools/ToolSearchTool/descriptionCache.ts +50 -0
  378. package/tui/src/tools/ToolSearchTool/keywordSearch.ts +216 -0
  379. package/tui/src/tools/ToolSearchTool/prompt.ts +10 -4
  380. package/tui/src/tools/ToolSearchTool/resultMapping.ts +30 -0
  381. package/tui/src/tools/ToolSearchTool/schemas.ts +30 -0
  382. package/tui/src/tools/ToolSearchTool/searchPool.ts +47 -0
  383. package/tui/src/tools/ToolSearchTool/supportIntentHints.ts +140 -0
  384. package/tui/src/tools/TranslateTool/TranslateTool.ts +1 -1
  385. package/tui/src/tools/VerifyPrimitive/VerifyPrimitive.ts +2 -1
  386. package/tui/src/tools/WebFetchTool/WebFetchTool.ts +43 -138
  387. package/tui/src/tools/WebFetchTool/call.ts +227 -0
  388. package/tui/src/tools/WebFetchTool/resolvedAddressSafety.ts +78 -0
  389. package/tui/src/tools/WebFetchTool/sourceVerification.ts +204 -0
  390. package/tui/src/tools/WebFetchTool/types.ts +23 -0
  391. package/tui/src/tools/WebFetchTool/urlSafety.ts +181 -0
  392. package/tui/src/tools/WebFetchTool/utils.ts +1 -1
  393. package/tui/src/tools/WebSearchTool/UI.tsx +0 -1
  394. package/tui/src/tools/WebSearchTool/WebSearchTool.ts +9 -313
  395. package/tui/src/tools/WebSearchTool/call.ts +33 -0
  396. package/tui/src/tools/WebSearchTool/responseMapping.ts +190 -0
  397. package/tui/src/tools/WebSearchTool/resultBlock.ts +47 -0
  398. package/tui/src/tools/WebSearchTool/schemas.ts +47 -0
  399. package/tui/src/tools/WebSearchTool/toolSchema.ts +12 -0
  400. package/tui/src/tools/WorkspaceToolAdapter/WorkspaceToolAdapter.ts +79 -0
  401. package/tui/src/tools/WorkspaceToolAdapter/allowedRootPolicy.ts +85 -0
  402. package/tui/src/tools/WorkspaceToolAdapter/documentFormatGuards.ts +73 -0
  403. package/tui/src/tools/WorkspaceToolAdapter/inputNormalization.ts +105 -0
  404. package/tui/src/tools/WorkspaceToolAdapter/mcpExposurePolicy.ts +64 -0
  405. package/tui/src/tools/WorkspaceToolAdapter/toolDefFactory.ts +215 -0
  406. package/tui/src/tools/WorkspaceToolAdapter/toolNames.ts +6 -0
  407. package/tui/src/tools/WorkspaceToolAdapter/workspacePolicy.ts +15 -0
  408. package/tui/src/tools/_shared/dispatchPrimitive.ts +6 -6
  409. package/tui/src/tools/_shared/documentChangeToPatch.ts +125 -0
  410. package/tui/src/tools/_shared/documentDispatchArguments.ts +87 -0
  411. package/tui/src/tools/_shared/documentPrimitiveTimeout.ts +13 -0
  412. package/tui/src/tools/_shared/documentToolResultRender.ts +98 -0
  413. package/tui/src/tools/_shared/pendingCallRegistry.ts +1 -6
  414. package/tui/src/tools/_shared/rootPrimitiveInput.ts +1 -0
  415. package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPatterns.ts +58 -0
  416. package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPrompt.ts +271 -0
  417. package/tui/src/tools/_shared/toolChoiceRepair/documentRepair.ts +452 -0
  418. package/tui/src/tools/_shared/toolChoiceRepair/messageAccess.ts +80 -0
  419. package/tui/src/tools/_shared/toolChoiceRepair/publicDataRepair.ts +92 -0
  420. package/tui/src/tools/_shared/toolChoiceRepair/supportRepair.ts +135 -0
  421. package/tui/src/tools/_shared/toolChoiceRepair.ts +55 -860
  422. package/tui/src/tools/shared/mockDisclaimer.ts +1 -1
  423. package/tui/src/tools.ts +39 -190
  424. package/tui/src/types/fileSuggestion.ts +4 -26
  425. package/tui/src/types/generated/events_mono/claude_code/v1/claude_code_internal_event.ts +186 -148
  426. package/tui/src/types/generated/events_mono/common/v1/auth.ts +25 -11
  427. package/tui/src/types/generated/events_mono/growthbook/v1/growthbook_experiment_event.ts +47 -30
  428. package/tui/src/types/generated/google/protobuf/timestamp.ts +21 -7
  429. package/tui/src/types/message.ts +80 -102
  430. package/tui/src/types/messageQueueTypes.ts +6 -28
  431. package/tui/src/types/notebook.ts +16 -38
  432. package/tui/src/types/statusLine.ts +4 -26
  433. package/tui/src/types/tools.ts +24 -46
  434. package/tui/src/types/utils.ts +6 -28
  435. package/tui/src/upstreamproxy/relay.ts +7 -3
  436. package/tui/src/upstreamproxy/upstreamproxy.ts +1 -1
  437. package/tui/src/utils/assistantMessageFactories.ts +9 -3
  438. package/tui/src/utils/auth.ts +129 -139
  439. package/tui/src/utils/bash/ast.ts +23 -23
  440. package/tui/src/utils/bash/bashParser.ts +5 -5
  441. package/tui/src/utils/billing.ts +1 -1
  442. package/tui/src/utils/collapseReadSearch.ts +3 -3
  443. package/tui/src/utils/cronTasks.ts +1 -1
  444. package/tui/src/utils/execFileNoThrow.ts +1 -1
  445. package/tui/src/utils/filePersistence/types.ts +16 -38
  446. package/tui/src/utils/forkedAgent.ts +1 -1
  447. package/tui/src/utils/gracefulShutdown.ts +4 -4
  448. package/tui/src/utils/heapDumpService.ts +12 -8
  449. package/tui/src/utils/hooks/apiQueryHookHelper.ts +1 -1
  450. package/tui/src/utils/hooks/execPromptHook.ts +1 -1
  451. package/tui/src/utils/hooks/skillImprovement.ts +1 -1
  452. package/tui/src/utils/mcp/dateTimeParser.ts +1 -1
  453. package/tui/src/utils/messages.ts +18 -0
  454. package/tui/src/utils/migrateSessions.ts +3 -3
  455. package/tui/src/utils/model/model.ts +6 -6
  456. package/tui/src/utils/permissions/yoloClassifier.ts +1 -1
  457. package/tui/src/utils/plugins/headlessPluginInstall.ts +1 -1
  458. package/tui/src/utils/plugins/mcpPluginIntegration.ts +1 -1
  459. package/tui/src/utils/plugins/mcpbHandler.ts +1 -1
  460. package/tui/src/utils/plugins/pluginLoader.ts +8 -8
  461. package/tui/src/utils/protectedNamespace.ts +5 -3
  462. package/tui/src/utils/rawJsonToolCall.ts +242 -0
  463. package/tui/src/utils/ripgrep.ts +16 -7
  464. package/tui/src/utils/sessionTitle.ts +1 -1
  465. package/tui/src/utils/settings/permissionValidation.ts +14 -2
  466. package/tui/src/utils/shell/prefix.ts +1 -1
  467. package/tui/src/utils/sideQuery.ts +1 -1
  468. package/tui/src/utils/systemThemeWatcher.ts +13 -3
  469. package/tui/src/utils/teleport.tsx +1 -1
  470. package/uv.lock +400 -14
  471. package/tui/src/services/api/claude.ts +0 -3540
  472. package/tui/src/tools/_shared/directPublicDataGuard.ts +0 -362
  473. package/tui/src/tools/_shared/kmaAnalysisGuard.ts +0 -197
  474. package/tui/src/tools/_shared/kmaAviationGuard.ts +0 -70
  475. package/tui/src/tools/_shared/nmcAedGuard.ts +0 -234
  476. package/tui/src/tools/_shared/protectedCheckGuard.ts +0 -207
  477. package/tui/src/tools/_shared/textToolCallGuard.ts +0 -91
@@ -0,0 +1,528 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Promoted archive/container document engines."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import gzip
7
+ import html
8
+ import io
9
+ import re
10
+ import shutil
11
+ import subprocess
12
+ import tarfile
13
+ import tempfile
14
+ import zipfile
15
+ from decimal import Decimal
16
+ from pathlib import Path, PurePosixPath
17
+ from typing import NoReturn
18
+
19
+ from ummaya.tools.documents.engines import DocumentMutationBlockedError
20
+ from ummaya.tools.documents.models import (
21
+ BlockedReason,
22
+ DocumentExtraction,
23
+ DocumentFormat,
24
+ DocumentPatch,
25
+ DocumentPatchOperation,
26
+ FormField,
27
+ MetadataValue,
28
+ OperationType,
29
+ ParagraphBlock,
30
+ )
31
+
32
+ _ARCHIVE_FORMATS = (
33
+ DocumentFormat.epub,
34
+ DocumentFormat.zip,
35
+ DocumentFormat.seven_z,
36
+ DocumentFormat.tar,
37
+ DocumentFormat.gz,
38
+ )
39
+ _ARCHIVE_BODY_RE = re.compile(r"^/archive/(?P<member>.+)/body$")
40
+ _GZIP_BODY_TARGET = "/gzip/payload"
41
+ _MAX_INLINE_TEXT_BYTES = 128 * 1024
42
+ _BSDTAR_TIMEOUT_SECONDS = 15
43
+
44
+
45
+ class ArchiveContainerDocumentEngine:
46
+ """Bounded writer for archive containers with child-payload replacement."""
47
+
48
+ render_engine_id = "archive-container-structural-svg"
49
+ render_artifact_extension = "svg"
50
+ render_mime_type = "image/svg+xml"
51
+
52
+ def __init__(self, document_format: DocumentFormat) -> None:
53
+ if document_format not in _ARCHIVE_FORMATS:
54
+ raise ValueError(f"unsupported archive document format: {document_format.value}")
55
+ self.document_format = document_format
56
+ self.engine_id = f"archive-container-{document_format.value}"
57
+
58
+ def inspect(self, path: Path, *, artifact_id: str) -> DocumentExtraction:
59
+ """Inspect archive members without extracting to the workspace."""
60
+ entries = _read_entries(path, document_format=self.document_format)
61
+ paragraphs = [
62
+ ParagraphBlock(
63
+ block_id=f"archive-member-{index}",
64
+ text=entry.preview,
65
+ source_path=entry.target_path,
66
+ )
67
+ for index, entry in enumerate(entries, start=1)
68
+ ]
69
+ fields = [
70
+ FormField(
71
+ field_id=f"archive-member-{index}",
72
+ label=entry.name,
73
+ path=entry.target_path,
74
+ field_type="text",
75
+ required=False,
76
+ current_value=entry.text,
77
+ source_confidence=Decimal("0.85"),
78
+ )
79
+ for index, entry in enumerate(entries, start=1)
80
+ if entry.editable
81
+ ]
82
+ return DocumentExtraction(
83
+ artifact_id=artifact_id,
84
+ paragraphs=paragraphs,
85
+ fields=fields,
86
+ metadata=_metadata(
87
+ path,
88
+ document_format=self.document_format,
89
+ entry_count=len(entries),
90
+ ),
91
+ )
92
+
93
+ def apply_patch(self, path: Path, patch: DocumentPatch) -> bytes:
94
+ """Return a repacked archive derivative after replacing child payloads."""
95
+ replacements: dict[str, bytes] = {}
96
+ gzip_replacement: bytes | None = None
97
+ for operation in patch.operations:
98
+ target_name = _operation_target_name(operation)
99
+ replacement = _operation_replacement(operation)
100
+ if self.document_format is DocumentFormat.gz:
101
+ if target_name != _GZIP_BODY_TARGET:
102
+ _raise_unsupported_operation(operation)
103
+ gzip_replacement = replacement
104
+ continue
105
+ member_name = _member_name_from_target(target_name)
106
+ replacements[member_name] = replacement
107
+
108
+ if self.document_format is DocumentFormat.gz:
109
+ if gzip_replacement is None:
110
+ raise DocumentMutationBlockedError(
111
+ BlockedReason.validation_failed,
112
+ "Gzip archive patch did not include /gzip/payload.",
113
+ )
114
+ return _write_gzip_payload(gzip_replacement)
115
+ if not replacements:
116
+ raise DocumentMutationBlockedError(
117
+ BlockedReason.validation_failed,
118
+ "Archive patch did not include a child member replacement.",
119
+ )
120
+ if self.document_format in {DocumentFormat.zip, DocumentFormat.epub}:
121
+ return _replace_zip_members(
122
+ path,
123
+ replacements,
124
+ preserve_epub_mimetype=self.document_format is DocumentFormat.epub,
125
+ )
126
+ if self.document_format is DocumentFormat.seven_z:
127
+ return _replace_7z_members(path, replacements)
128
+ if self.document_format is DocumentFormat.tar:
129
+ return _replace_tar_members(path, replacements)
130
+ raise DocumentMutationBlockedError(
131
+ BlockedReason.unsupported_format,
132
+ f"Unsupported archive format: {self.document_format.value}",
133
+ )
134
+
135
+ def render(self, path: Path, *, artifact_id: str, output_dir: Path) -> tuple[bytes, ...]:
136
+ """Render archive member previews as structural SVG evidence."""
137
+ _ = output_dir
138
+ extraction = self.inspect(path, artifact_id=artifact_id)
139
+ lines = [paragraph.text for paragraph in extraction.paragraphs]
140
+ return (_render_lines_svg(lines, title=f"{self.document_format.value.upper()} archive"),)
141
+
142
+
143
+ class _ArchiveEntry:
144
+ def __init__(self, *, name: str, text: str, editable: bool) -> None:
145
+ self.name = name
146
+ self.text = text
147
+ self.editable = editable
148
+
149
+ @property
150
+ def target_path(self) -> str:
151
+ if self.name == "gzip-payload":
152
+ return _GZIP_BODY_TARGET
153
+ return f"/archive/{self.name}/body"
154
+
155
+ @property
156
+ def preview(self) -> str:
157
+ if self.text:
158
+ return f"{self.name}: {self.text}"
159
+ return self.name
160
+
161
+
162
+ def _read_entries(path: Path, *, document_format: DocumentFormat) -> list[_ArchiveEntry]:
163
+ if document_format in {DocumentFormat.zip, DocumentFormat.epub}:
164
+ return _read_zip_entries(path)
165
+ if document_format is DocumentFormat.seven_z:
166
+ return _read_7z_entries(path)
167
+ if document_format is DocumentFormat.tar:
168
+ return _read_tar_entries(path)
169
+ if document_format is DocumentFormat.gz:
170
+ return [_ArchiveEntry(name="gzip-payload", text=_read_gzip_text(path), editable=True)]
171
+ raise DocumentMutationBlockedError(
172
+ BlockedReason.unsupported_format,
173
+ f"Unsupported archive format: {document_format.value}",
174
+ )
175
+
176
+
177
+ def _read_zip_entries(path: Path) -> list[_ArchiveEntry]:
178
+ entries: list[_ArchiveEntry] = []
179
+ try:
180
+ with zipfile.ZipFile(path) as archive:
181
+ for info in archive.infolist():
182
+ if info.is_dir() or not _safe_member_name(info.filename):
183
+ continue
184
+ text = _read_zip_member_text(archive, info)
185
+ entries.append(_ArchiveEntry(name=info.filename, text=text, editable=bool(text)))
186
+ except zipfile.BadZipFile as exc:
187
+ raise DocumentMutationBlockedError(BlockedReason.corrupt, "Invalid ZIP container.") from exc
188
+ return entries
189
+
190
+
191
+ def _read_tar_entries(path: Path) -> list[_ArchiveEntry]:
192
+ entries: list[_ArchiveEntry] = []
193
+ try:
194
+ with tarfile.open(path, mode="r:*") as archive:
195
+ for member in archive.getmembers():
196
+ if not member.isfile() or not _safe_member_name(member.name):
197
+ continue
198
+ payload = archive.extractfile(member)
199
+ if payload is None:
200
+ continue
201
+ text = _decode_inline_text(payload.read(_MAX_INLINE_TEXT_BYTES))
202
+ entries.append(_ArchiveEntry(name=member.name, text=text, editable=bool(text)))
203
+ except tarfile.TarError as exc:
204
+ raise DocumentMutationBlockedError(BlockedReason.corrupt, "Invalid TAR container.") from exc
205
+ return entries
206
+
207
+
208
+ def _read_7z_entries(path: Path) -> list[_ArchiveEntry]:
209
+ with tempfile.TemporaryDirectory(prefix="ummaya-7z-read-") as temp_root:
210
+ root = Path(temp_root)
211
+ member_names = _extract_7z_archive(path, root)
212
+ entries: list[_ArchiveEntry] = []
213
+ for member_name in member_names:
214
+ member_path = _safe_extracted_member_path(root, member_name)
215
+ if member_path is None or not member_path.is_file() or member_path.is_symlink():
216
+ continue
217
+ payload = member_path.read_bytes()[:_MAX_INLINE_TEXT_BYTES]
218
+ text = _decode_inline_text(payload)
219
+ entries.append(_ArchiveEntry(name=member_name, text=text, editable=bool(text)))
220
+ return entries
221
+
222
+
223
+ def _read_zip_member_text(archive: zipfile.ZipFile, info: zipfile.ZipInfo) -> str:
224
+ if info.file_size > _MAX_INLINE_TEXT_BYTES:
225
+ return ""
226
+ try:
227
+ return _decode_inline_text(archive.read(info.filename))
228
+ except (KeyError, RuntimeError, NotImplementedError, zipfile.BadZipFile):
229
+ return ""
230
+
231
+
232
+ def _read_gzip_text(path: Path) -> str:
233
+ try:
234
+ with gzip.open(path, "rb") as archive:
235
+ return _decode_inline_text(archive.read(_MAX_INLINE_TEXT_BYTES))
236
+ except OSError as exc:
237
+ raise DocumentMutationBlockedError(BlockedReason.corrupt, "Invalid GZIP payload.") from exc
238
+
239
+
240
+ def _decode_inline_text(payload: bytes) -> str:
241
+ if b"\x00" in payload:
242
+ return ""
243
+ try:
244
+ return payload.decode("utf-8").strip()
245
+ except UnicodeDecodeError:
246
+ return ""
247
+
248
+
249
+ def _operation_target_name(operation: DocumentPatchOperation) -> str:
250
+ if operation.operation_type not in {OperationType.replace_text, OperationType.set_field_value}:
251
+ _raise_unsupported_operation(operation)
252
+ return operation.target_path
253
+
254
+
255
+ def _operation_replacement(operation: DocumentPatchOperation) -> bytes:
256
+ if operation.value is None:
257
+ _raise_unsupported_operation(operation)
258
+ return str(operation.value).encode("utf-8")
259
+
260
+
261
+ def _member_name_from_target(target_path: str) -> str:
262
+ match = _ARCHIVE_BODY_RE.fullmatch(target_path)
263
+ if match is None:
264
+ raise DocumentMutationBlockedError(
265
+ BlockedReason.unsupported_operation,
266
+ f"Archive target must match /archive/<member>/body: {target_path}",
267
+ )
268
+ member_name = match.group("member")
269
+ if not _safe_member_name(member_name):
270
+ raise DocumentMutationBlockedError(
271
+ BlockedReason.path_traversal_detected,
272
+ f"Archive member target is unsafe: {member_name}",
273
+ )
274
+ return member_name
275
+
276
+
277
+ def _replace_zip_members(
278
+ path: Path,
279
+ replacements: dict[str, bytes],
280
+ *,
281
+ preserve_epub_mimetype: bool,
282
+ ) -> bytes:
283
+ buffer = io.BytesIO()
284
+ found: set[str] = set()
285
+ with (
286
+ zipfile.ZipFile(path) as source,
287
+ zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as target,
288
+ ):
289
+ if preserve_epub_mimetype and "mimetype" in source.namelist():
290
+ target.writestr(
291
+ "mimetype",
292
+ source.read("mimetype"),
293
+ compress_type=zipfile.ZIP_STORED,
294
+ )
295
+ for info in source.infolist():
296
+ if info.is_dir():
297
+ continue
298
+ if preserve_epub_mimetype and info.filename == "mimetype":
299
+ continue
300
+ if not _safe_member_name(info.filename):
301
+ raise DocumentMutationBlockedError(
302
+ BlockedReason.path_traversal_detected,
303
+ f"Archive member path is unsafe: {info.filename}",
304
+ )
305
+ payload = replacements.get(info.filename)
306
+ if payload is None:
307
+ payload = source.read(info.filename)
308
+ else:
309
+ found.add(info.filename)
310
+ target.writestr(info.filename, payload)
311
+ _ensure_all_replacements_found(replacements, found)
312
+ return buffer.getvalue()
313
+
314
+
315
+ def _replace_tar_members(path: Path, replacements: dict[str, bytes]) -> bytes:
316
+ buffer = io.BytesIO()
317
+ found: set[str] = set()
318
+ with tarfile.open(path, mode="r:*") as source, tarfile.open(fileobj=buffer, mode="w") as target:
319
+ for member in source.getmembers():
320
+ if not _safe_member_name(member.name):
321
+ raise DocumentMutationBlockedError(
322
+ BlockedReason.path_traversal_detected,
323
+ f"Archive member path is unsafe: {member.name}",
324
+ )
325
+ if not member.isfile():
326
+ target.addfile(member)
327
+ continue
328
+ payload = replacements.get(member.name)
329
+ if payload is None:
330
+ source_payload = source.extractfile(member)
331
+ payload = b"" if source_payload is None else source_payload.read()
332
+ else:
333
+ found.add(member.name)
334
+ info = tarfile.TarInfo(member.name)
335
+ info.size = len(payload)
336
+ info.mode = member.mode
337
+ info.mtime = member.mtime
338
+ target.addfile(info, io.BytesIO(payload))
339
+ _ensure_all_replacements_found(replacements, found)
340
+ return buffer.getvalue()
341
+
342
+
343
+ def _replace_7z_members(path: Path, replacements: dict[str, bytes]) -> bytes:
344
+ with tempfile.TemporaryDirectory(prefix="ummaya-7z-write-") as temp_root:
345
+ root = Path(temp_root) / "src"
346
+ root.mkdir()
347
+ member_names = _extract_7z_archive(path, root)
348
+ found: set[str] = set()
349
+ for member_name, payload in replacements.items():
350
+ member_path = _safe_extracted_member_path(root, member_name)
351
+ if member_path is None or not member_path.is_file() or member_path.is_symlink():
352
+ continue
353
+ member_path.write_bytes(payload)
354
+ found.add(member_name)
355
+ _ensure_all_replacements_found(replacements, found)
356
+
357
+ output_path = Path(temp_root) / "updated.7z"
358
+ _run_bsdtar(
359
+ [
360
+ "-cf",
361
+ str(output_path),
362
+ "--format=7zip",
363
+ "-C",
364
+ str(root),
365
+ *member_names,
366
+ ]
367
+ )
368
+ return output_path.read_bytes()
369
+
370
+
371
+ def _write_gzip_payload(payload: bytes) -> bytes:
372
+ buffer = io.BytesIO()
373
+ with gzip.GzipFile(fileobj=buffer, mode="wb") as archive:
374
+ archive.write(payload)
375
+ return buffer.getvalue()
376
+
377
+
378
+ def _ensure_all_replacements_found(replacements: dict[str, bytes], found: set[str]) -> None:
379
+ missing = sorted(set(replacements) - found)
380
+ if missing:
381
+ raise DocumentMutationBlockedError(
382
+ BlockedReason.validation_failed,
383
+ f"Archive member does not exist: {missing[0]}",
384
+ )
385
+
386
+
387
+ def _safe_member_name(name: str) -> bool:
388
+ if not name or "\x00" in name or "\\" in name:
389
+ return False
390
+ path = PurePosixPath(name)
391
+ return not path.is_absolute() and not any(part in {"", ".", ".."} for part in path.parts)
392
+
393
+
394
+ def _extract_7z_archive(path: Path, destination: Path) -> list[str]:
395
+ member_names = _list_7z_member_names(path)
396
+ _run_bsdtar(
397
+ [
398
+ "-xf",
399
+ str(path),
400
+ "-C",
401
+ str(destination),
402
+ "--no-same-owner",
403
+ "--no-same-permissions",
404
+ ]
405
+ )
406
+ _validate_extracted_7z_tree(destination, member_names)
407
+ return member_names
408
+
409
+
410
+ def _list_7z_member_names(path: Path) -> list[str]:
411
+ result = _run_bsdtar(["-tf", str(path)], capture_output=True)
412
+ names = [line.strip() for line in result.stdout.decode("utf-8").splitlines() if line.strip()]
413
+ unsafe = [name for name in names if not _safe_member_name(name)]
414
+ if unsafe:
415
+ raise DocumentMutationBlockedError(
416
+ BlockedReason.path_traversal_detected,
417
+ f"7z archive member path is unsafe: {unsafe[0]}",
418
+ )
419
+ return names
420
+
421
+
422
+ def _validate_extracted_7z_tree(root: Path, member_names: list[str]) -> None:
423
+ expected = {_normalize_member_name(name) for name in member_names}
424
+ actual: set[str] = set()
425
+ for item in root.rglob("*"):
426
+ relative = item.relative_to(root).as_posix()
427
+ if item.is_symlink():
428
+ raise DocumentMutationBlockedError(
429
+ BlockedReason.unsupported_operation,
430
+ f"7z archive symlink entries are not promoted: {relative}",
431
+ )
432
+ if item.is_file():
433
+ actual.add(relative)
434
+ unexpected = sorted(actual - expected)
435
+ if unexpected:
436
+ raise DocumentMutationBlockedError(
437
+ BlockedReason.path_traversal_detected,
438
+ f"7z archive extracted an unexpected member: {unexpected[0]}",
439
+ )
440
+
441
+
442
+ def _safe_extracted_member_path(root: Path, member_name: str) -> Path | None:
443
+ normalized = _normalize_member_name(member_name)
444
+ if not _safe_member_name(normalized):
445
+ return None
446
+ candidate = root.joinpath(*PurePosixPath(normalized).parts)
447
+ try:
448
+ candidate.resolve().relative_to(root.resolve())
449
+ except ValueError:
450
+ return None
451
+ return candidate
452
+
453
+
454
+ def _normalize_member_name(member_name: str) -> str:
455
+ return member_name.rstrip("/")
456
+
457
+
458
+ def _run_bsdtar(
459
+ args: list[str],
460
+ *,
461
+ capture_output: bool = False,
462
+ ) -> subprocess.CompletedProcess[bytes]:
463
+ executable = shutil.which("bsdtar")
464
+ if executable is None:
465
+ raise DocumentMutationBlockedError(
466
+ BlockedReason.unsupported_operation,
467
+ "7z archive handling requires a local bsdtar/libarchive runtime.",
468
+ )
469
+ try:
470
+ return subprocess.run( # noqa: S603 - static executable lookup plus static argv.
471
+ [executable, *args],
472
+ check=True,
473
+ capture_output=capture_output,
474
+ timeout=_BSDTAR_TIMEOUT_SECONDS,
475
+ )
476
+ except subprocess.CalledProcessError as exc:
477
+ raise DocumentMutationBlockedError(
478
+ BlockedReason.corrupt,
479
+ f"7z archive operation failed through bsdtar: {exc.stderr.decode('utf-8', 'replace')}",
480
+ ) from exc
481
+ except subprocess.TimeoutExpired as exc:
482
+ raise DocumentMutationBlockedError(
483
+ BlockedReason.oversized_expanded_bytes,
484
+ "7z archive operation exceeded the local timeout budget.",
485
+ ) from exc
486
+
487
+
488
+ def _metadata(
489
+ path: Path,
490
+ *,
491
+ document_format: DocumentFormat,
492
+ entry_count: int,
493
+ ) -> dict[str, MetadataValue]:
494
+ return {
495
+ "adapter_id": f"archive-container-{document_format.value}-adapter",
496
+ "engine_id": f"archive-container-{document_format.value}",
497
+ "format": document_format.value,
498
+ "source_name": path.name,
499
+ "entry_count": entry_count,
500
+ "mutation_policy": "archive_child_derivative_write_render_save",
501
+ "render_oracle": "archive-container-structural-svg",
502
+ }
503
+
504
+
505
+ def _render_lines_svg(lines: list[str], *, title: str) -> bytes:
506
+ escaped_title = html.escape(title)
507
+ safe_lines = [html.escape(line) for line in lines if line]
508
+ height = max(160, 72 + len(safe_lines) * 28)
509
+ text_nodes = [
510
+ f'<text x="32" y="{84 + index * 28}" font-size="15" font-family="monospace">{line}</text>'
511
+ for index, line in enumerate(safe_lines[:80])
512
+ ]
513
+ svg = (
514
+ f'<svg xmlns="http://www.w3.org/2000/svg" width="1040" height="{height}" '
515
+ f'viewBox="0 0 1040 {height}">'
516
+ '<rect width="100%" height="100%" fill="#ffffff"/>'
517
+ f'<text x="32" y="40" font-size="22" font-family="sans-serif" '
518
+ f'font-weight="700">{escaped_title}</text>' + "".join(text_nodes) + "</svg>"
519
+ )
520
+ return svg.encode("utf-8")
521
+
522
+
523
+ def _raise_unsupported_operation(operation: DocumentPatchOperation) -> NoReturn:
524
+ raise DocumentMutationBlockedError(
525
+ BlockedReason.unsupported_operation,
526
+ f"Archive operation is not supported: "
527
+ f"{operation.operation_type.value} {operation.target_path}",
528
+ )
@@ -0,0 +1,41 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Shared engine-backed adapter protocol for document formats."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Protocol
8
+
9
+ from ummaya.tools.documents.models import DocumentExtraction, DocumentFormat, KnownDocumentFormat
10
+
11
+ if TYPE_CHECKING:
12
+ from ummaya.tools.documents.tool_defs import DocumentFieldPatch
13
+
14
+
15
+ class DocumentFormatAdapter(Protocol):
16
+ """Format-scoped adapter boundary below the single document primitive."""
17
+
18
+ adapter_id: str
19
+ known_formats: tuple[KnownDocumentFormat, ...]
20
+ promoted_formats: tuple[DocumentFormat, ...]
21
+
22
+ def inspect(self, path: Path, *, artifact_id: str) -> DocumentExtraction:
23
+ """Return LLM-readable document IR for a local artifact."""
24
+
25
+ def normalize_fill_patches(
26
+ self,
27
+ patches: tuple[DocumentFieldPatch, ...],
28
+ *,
29
+ extraction: DocumentExtraction | None,
30
+ ) -> tuple[DocumentFieldPatch, ...]:
31
+ """Normalize adapter-specific model-facing fill targets before mutation."""
32
+
33
+
34
+ class EngineBackedFormatAdapter(Protocol):
35
+ """Thin wrapper around a promoted external or optional document engine."""
36
+
37
+ document_format: DocumentFormat
38
+ engine_id: str
39
+
40
+ def inspect(self, path: Path, *, artifact_id: str) -> DocumentExtraction:
41
+ """Delegate inspection to the selected engine."""