code-context-control 2.46.0__tar.gz → 2.49.0__tar.gz

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 (310) hide show
  1. {code_context_control-2.46.0/code_context_control.egg-info → code_context_control-2.49.0}/PKG-INFO +1 -1
  2. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/c3.py +11 -3
  3. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/commands/parser.py +10 -1
  4. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/compress.py +1 -2
  5. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/federate.py +0 -1
  6. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/filter.py +1 -2
  7. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/read.py +1 -2
  8. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/search.py +1 -2
  9. {code_context_control-2.46.0 → code_context_control-2.49.0/code_context_control.egg-info}/PKG-INFO +1 -1
  10. {code_context_control-2.46.0 → code_context_control-2.49.0}/code_context_control.egg-info/SOURCES.txt +29 -0
  11. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/config.py +11 -0
  12. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/oracle.html +46 -1
  13. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/oracle_server.py +132 -18
  14. code_context_control-2.49.0/oracle/oracle_ui.html +1215 -0
  15. code_context_control-2.49.0/oracle/services/c3_bridge.py +372 -0
  16. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/chat_engine.py +453 -141
  17. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/federated_graph.py +52 -2
  18. code_context_control-2.49.0/oracle/services/local_session.py +54 -0
  19. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/ollama_bridge.py +87 -7
  20. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/project_scanner.py +60 -6
  21. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/review_agent.py +84 -1
  22. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/tool_registry.py +82 -3
  23. code_context_control-2.49.0/oracle/ui/activity.js +101 -0
  24. code_context_control-2.49.0/oracle/ui/agents.js +108 -0
  25. code_context_control-2.49.0/oracle/ui/app.js +9 -0
  26. code_context_control-2.49.0/oracle/ui/busy.js +102 -0
  27. code_context_control-2.49.0/oracle/ui/chat/conversations.js +153 -0
  28. code_context_control-2.49.0/oracle/ui/chat/input.js +284 -0
  29. code_context_control-2.49.0/oracle/ui/chat/markdown.js +33 -0
  30. code_context_control-2.49.0/oracle/ui/chat/send.js +281 -0
  31. code_context_control-2.49.0/oracle/ui/chat/stream_renderer.js +767 -0
  32. code_context_control-2.49.0/oracle/ui/chat/toolbar.js +270 -0
  33. code_context_control-2.49.0/oracle/ui/core.js +60 -0
  34. code_context_control-2.49.0/oracle/ui/crossgraph.js +184 -0
  35. code_context_control-2.49.0/oracle/ui/header.js +69 -0
  36. code_context_control-2.49.0/oracle/ui/insights.js +52 -0
  37. code_context_control-2.49.0/oracle/ui/projects.js +245 -0
  38. code_context_control-2.49.0/oracle/ui/settings.js +186 -0
  39. code_context_control-2.49.0/oracle/ui/suggestions.js +47 -0
  40. code_context_control-2.49.0/oracle/ui/theme_tabs.js +52 -0
  41. {code_context_control-2.46.0 → code_context_control-2.49.0}/pyproject.toml +5 -1
  42. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/project_runtime.py +16 -3
  43. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/subprojects.py +7 -4
  44. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/task_store.py +1 -1
  45. code_context_control-2.49.0/tests/test_c3_bridge_project_artifacts.py +153 -0
  46. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_federated_graph.py +83 -0
  47. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_ghost_generation.py +2 -1
  48. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_oracle_apikey_api.py +8 -2
  49. code_context_control-2.49.0/tests/test_oracle_c3_bridge.py +174 -0
  50. code_context_control-2.49.0/tests/test_oracle_chat_engine.py +551 -0
  51. code_context_control-2.49.0/tests/test_oracle_local_auth.py +228 -0
  52. code_context_control-2.49.0/tests/test_oracle_ollama_bridge.py +203 -0
  53. code_context_control-2.49.0/tests/test_oracle_scanner_cache.py +94 -0
  54. code_context_control-2.49.0/tests/test_oracle_subproject_awareness.py +131 -0
  55. code_context_control-2.49.0/tests/test_oracle_ui_bundle.py +89 -0
  56. code_context_control-2.49.0/tests/test_review_digest.py +184 -0
  57. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_tool_registry.py +32 -0
  58. code_context_control-2.46.0/oracle/services/c3_bridge.py +0 -237
  59. {code_context_control-2.46.0 → code_context_control-2.49.0}/LICENSE +0 -0
  60. {code_context_control-2.46.0 → code_context_control-2.49.0}/README.md +0 -0
  61. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/__init__.py +0 -0
  62. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/_hook_utils.py +0 -0
  63. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/commands/__init__.py +0 -0
  64. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/commands/common.py +0 -0
  65. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/docs.html +0 -0
  66. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/edits.html +0 -0
  67. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/guide/bitbucket.html +0 -0
  68. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/guide/getting-started.html +0 -0
  69. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/guide/index.html +0 -0
  70. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/guide/oracle.html +0 -0
  71. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/guide/shared.css +0 -0
  72. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/guide/tools.html +0 -0
  73. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/guide/workflow.html +0 -0
  74. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_artifact.py +0 -0
  75. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_auto_snapshot.py +0 -0
  76. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_c3_signal.py +0 -0
  77. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_c3read.py +0 -0
  78. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_dispatch.py +0 -0
  79. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_edit_ledger.py +0 -0
  80. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_edit_unlock.py +0 -0
  81. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_filter.py +0 -0
  82. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_ghost_files.py +0 -0
  83. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_pretool_enforce.py +0 -0
  84. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_read.py +0 -0
  85. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_session_stats.py +0 -0
  86. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hook_terse_advisor.py +0 -0
  87. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub.html +0 -0
  88. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_server.py +0 -0
  89. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/app.js +0 -0
  90. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/add_project.js +0 -0
  91. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/config_editor.js +0 -0
  92. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/drill_artifacts.js +0 -0
  93. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/drill_health.js +0 -0
  94. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/drill_panel.js +0 -0
  95. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/drill_tasks.js +0 -0
  96. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/drill_views.js +0 -0
  97. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/global_search.js +0 -0
  98. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/mcp_manager.js +0 -0
  99. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/modals.js +0 -0
  100. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/project_card.js +0 -0
  101. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/project_tree.js +0 -0
  102. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/session_drawer.js +0 -0
  103. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/settings_modal.js +0 -0
  104. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/sidebar.js +0 -0
  105. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/summary_bar.js +0 -0
  106. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/task_board.js +0 -0
  107. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/toasts.js +0 -0
  108. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/components/topbar.js +0 -0
  109. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui/state.js +0 -0
  110. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/hub_ui.html +0 -0
  111. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/mcp_proxy.py +0 -0
  112. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/mcp_server.py +0 -0
  113. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/server.py +0 -0
  114. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/__init__.py +0 -0
  115. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/_helpers.py +0 -0
  116. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/agent.py +0 -0
  117. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/artifacts.py +0 -0
  118. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/bitbucket.py +0 -0
  119. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/delegate.py +0 -0
  120. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/edit.py +0 -0
  121. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/edits.py +0 -0
  122. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/impact.py +0 -0
  123. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/memory.py +0 -0
  124. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/project.py +0 -0
  125. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/session.py +0 -0
  126. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/shell.py +0 -0
  127. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/status.py +0 -0
  128. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/tasks.py +0 -0
  129. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/tools/validate.py +0 -0
  130. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/api.js +0 -0
  131. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/app.js +0 -0
  132. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/bitbucket.js +0 -0
  133. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/chat.js +0 -0
  134. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/dashboard.js +0 -0
  135. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/edits.js +0 -0
  136. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/instructions.js +0 -0
  137. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/memory.js +0 -0
  138. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/sessions.js +0 -0
  139. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/settings.js +0 -0
  140. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/sidebar.js +0 -0
  141. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/components/tasks.js +0 -0
  142. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/icons.js +0 -0
  143. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/pm_shared.js +0 -0
  144. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/shared.js +0 -0
  145. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui/theme.js +0 -0
  146. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui.html +0 -0
  147. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui_legacy.html +0 -0
  148. {code_context_control-2.46.0 → code_context_control-2.49.0}/cli/ui_nano.html +0 -0
  149. {code_context_control-2.46.0 → code_context_control-2.49.0}/code_context_control.egg-info/dependency_links.txt +0 -0
  150. {code_context_control-2.46.0 → code_context_control-2.49.0}/code_context_control.egg-info/entry_points.txt +0 -0
  151. {code_context_control-2.46.0 → code_context_control-2.49.0}/code_context_control.egg-info/requires.txt +0 -0
  152. {code_context_control-2.46.0 → code_context_control-2.49.0}/code_context_control.egg-info/top_level.txt +0 -0
  153. {code_context_control-2.46.0 → code_context_control-2.49.0}/core/__init__.py +0 -0
  154. {code_context_control-2.46.0 → code_context_control-2.49.0}/core/config.py +0 -0
  155. {code_context_control-2.46.0 → code_context_control-2.49.0}/core/ide.py +0 -0
  156. {code_context_control-2.46.0 → code_context_control-2.49.0}/core/mcp_toml.py +0 -0
  157. {code_context_control-2.46.0 → code_context_control-2.49.0}/core/web_security.py +0 -0
  158. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/__init__.py +0 -0
  159. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/mcp_oracle.py +0 -0
  160. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/__init__.py +0 -0
  161. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/activity_reporter.py +0 -0
  162. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/api_auth.py +0 -0
  163. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/chat_store.py +0 -0
  164. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/cross_memory.py +0 -0
  165. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/health_checker.py +0 -0
  166. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/insight_engine.py +0 -0
  167. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/memory_reader.py +0 -0
  168. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/memory_writer.py +0 -0
  169. {code_context_control-2.46.0 → code_context_control-2.49.0}/oracle/services/tool_executor.py +0 -0
  170. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/__init__.py +0 -0
  171. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/activity_log.py +0 -0
  172. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/agent_base.py +0 -0
  173. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/agents.py +0 -0
  174. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/artifact_defs.py +0 -0
  175. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/artifact_store.py +1 -1
  176. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/auto_memory.py +0 -0
  177. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/bench/__init__.py +0 -0
  178. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/bench/external/__init__.py +0 -0
  179. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/bench/external/aider_polyglot.py +0 -0
  180. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/bench/external/swe_bench.py +0 -0
  181. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/benchmark_dashboard.py +0 -0
  182. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/bitbucket_client.py +0 -0
  183. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/bitbucket_credentials.py +0 -0
  184. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/circuit_breaker.py +0 -0
  185. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/claude_md.py +0 -0
  186. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/compressor.py +0 -0
  187. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/context_snapshot.py +0 -0
  188. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/conversation_store.py +0 -0
  189. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/doc_index.py +0 -0
  190. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/e2e_benchmark.py +0 -0
  191. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/e2e_evaluator.py +0 -0
  192. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/e2e_tasks.py +0 -0
  193. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/edit_ledger.py +0 -0
  194. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/embedding_index.py +0 -0
  195. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/error_reporting.py +0 -0
  196. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/file_memory.py +0 -0
  197. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/git_context.py +0 -0
  198. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/hub_service.py +0 -0
  199. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/indexer.py +0 -0
  200. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/memory.py +0 -0
  201. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/memory_consolidator.py +0 -0
  202. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/memory_graph.py +0 -0
  203. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/memory_grounder.py +0 -0
  204. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/memory_scorer.py +0 -0
  205. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/metrics.py +0 -0
  206. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/notifications.py +0 -0
  207. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/ollama_client.py +0 -0
  208. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/output_filter.py +0 -0
  209. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/parser.py +0 -0
  210. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/project_manager.py +0 -0
  211. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/protocol.py +0 -0
  212. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/proxy_state.py +0 -0
  213. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/retention.py +0 -0
  214. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/retrieval_broker.py +0 -0
  215. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/router.py +0 -0
  216. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/runtime.py +1 -1
  217. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/session_benchmark.py +0 -0
  218. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/session_manager.py +0 -0
  219. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/session_preloader.py +0 -0
  220. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/telemetry.py +0 -0
  221. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/text_index.py +0 -0
  222. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/tool_classifier.py +0 -0
  223. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/transcript_index.py +0 -0
  224. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/validation_cache.py +0 -0
  225. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/vector_store.py +0 -0
  226. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/watcher.py +0 -0
  227. {code_context_control-2.46.0 → code_context_control-2.49.0}/services/win_subprocess.py +0 -0
  228. {code_context_control-2.46.0 → code_context_control-2.49.0}/setup.cfg +0 -0
  229. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_activity_reporter.py +0 -0
  230. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_aider_polyglot.py +0 -0
  231. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_artifact_store.py +0 -0
  232. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_artifact_tool.py +0 -0
  233. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_bitbucket_cli_smoke.py +0 -0
  234. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_bitbucket_client.py +0 -0
  235. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_bitbucket_config_fallback.py +0 -0
  236. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_bitbucket_credentials.py +0 -0
  237. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_bitbucket_tool.py +0 -0
  238. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_c3_shell.py +0 -0
  239. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_circuit_breaker.py +0 -0
  240. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_claude_md_merge.py +0 -0
  241. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_cli_smoke.py +0 -0
  242. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_compressor_large_file.py +0 -0
  243. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_delegate_cascade.py +0 -0
  244. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_e2e_benchmark.py +0 -0
  245. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_edit_ledger_hook.py +0 -0
  246. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_edit_normalization.py +0 -0
  247. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_enforcement_flip.py +0 -0
  248. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_filter_backoff.py +0 -0
  249. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_ghost_files.py +0 -0
  250. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_git_branch_awareness.py +0 -0
  251. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_hook_dispatch.py +0 -0
  252. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_hook_pretool_enforce.py +0 -0
  253. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_hook_smoke.py +1 -1
  254. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_hook_state.py +0 -0
  255. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_hub_inspect_api.py +0 -0
  256. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_hub_server_smoke.py +0 -0
  257. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_install_mcp_entrypoint.py +0 -0
  258. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_lazy_store_init.py +0 -0
  259. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_mcp_host_guard.py +0 -0
  260. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_mcp_server_smoke.py +0 -0
  261. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_mcp_toml.py +0 -0
  262. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_memory_graph_api.py +0 -0
  263. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_memory_system.py +0 -0
  264. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_notification_dedup.py +0 -0
  265. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_notification_discipline.py +0 -0
  266. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_oracle_api_auth.py +0 -0
  267. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_oracle_discovery_api.py +0 -0
  268. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_oracle_security_fixes.py +0 -0
  269. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_output_filter.py +0 -0
  270. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_permissions.py +0 -0
  271. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_pm_api.py +0 -0
  272. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_project_manager.py +0 -0
  273. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_project_manager_merge.py +0 -0
  274. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_project_tool.py +0 -0
  275. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_read_coercion.py +0 -0
  276. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_response_boilerplate.py +0 -0
  277. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_retention.py +0 -0
  278. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_service_durability.py +0 -0
  279. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_session_benchmark.py +0 -0
  280. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_session_budget.py +0 -0
  281. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_shell_robustness.py +0 -0
  282. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_subproject_exclusion.py +1 -1
  283. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_subproject_federation.py +0 -0
  284. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_subprojects.py +0 -0
  285. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_swe_bench.py +0 -0
  286. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_task_store.py +0 -0
  287. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_task_tool.py +0 -0
  288. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_token_telemetry.py +0 -0
  289. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_upgrade_and_version.py +0 -0
  290. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_validate.py +0 -0
  291. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_web_security.py +0 -0
  292. {code_context_control-2.46.0 → code_context_control-2.49.0}/tests/test_windows_reliability.py +0 -0
  293. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/__init__.py +0 -0
  294. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/backend.py +0 -0
  295. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/main.py +0 -0
  296. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/__init__.py +0 -0
  297. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/benchmark_view.py +0 -0
  298. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/claudemd_view.py +0 -0
  299. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/compress_view.py +0 -0
  300. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/index_view.py +0 -0
  301. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/init_view.py +0 -0
  302. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/mcp_view.py +0 -0
  303. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/optimize_view.py +0 -0
  304. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/pipe_view.py +0 -0
  305. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/projects_view.py +0 -0
  306. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/search_view.py +0 -0
  307. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/session_view.py +0 -0
  308. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/stats.py +0 -0
  309. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/screens/ui_view.py +0 -0
  310. {code_context_control-2.46.0 → code_context_control-2.49.0}/tui/theme.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.46.0
3
+ Version: 2.49.0
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer — and version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP).
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -85,7 +85,7 @@ console = Console() if HAS_RICH else None
85
85
  # Config
86
86
  CONFIG_DIR = ".c3"
87
87
  CONFIG_FILE = ".c3/config.json"
88
- __version__ = "2.46.0"
88
+ __version__ = "2.49.0"
89
89
 
90
90
 
91
91
  def _command_deps() -> CommandDeps:
@@ -5632,10 +5632,18 @@ def _bb_cmd_set_default(args, project_path: str) -> None:
5632
5632
 
5633
5633
 
5634
5634
  def cmd_oracle(args):
5635
- """Oracle Discovery API key + connection management."""
5635
+ """Oracle dashboard server + Discovery API key management."""
5636
5636
  sub = getattr(args, "oracle_cmd", None)
5637
+ if sub in ("serve", "start"):
5638
+ # Lazy import: run_oracle builds all Oracle services (matches
5639
+ # cmd_hub's deferred-import style so bare `c3` stays fast).
5640
+ from oracle.oracle_server import run_oracle
5641
+ run_oracle(port=getattr(args, "port", None),
5642
+ open_browser=not getattr(args, "no_browser", False))
5643
+ return
5637
5644
  if sub != "api":
5638
- print("Usage: c3 oracle api {info,key,rotate,clear}")
5645
+ print("Usage: c3 oracle {serve,api} — serve: launch the dashboard; "
5646
+ "api {info,key,rotate,clear}: manage the Discovery key")
5639
5647
  return
5640
5648
 
5641
5649
  from oracle.config import load_config
@@ -357,9 +357,18 @@ def build_parser(version: str, parse_cli_ide_arg):
357
357
  # ── Oracle Discovery API (v2.32.0) ──────────────────────────────────
358
358
  p_oracle = subparsers.add_parser(
359
359
  "oracle",
360
- help="Oracle Discovery API key + connection management",
360
+ help="Oracle dashboard server + Discovery API key management",
361
361
  )
362
362
  or_subs = p_oracle.add_subparsers(dest="oracle_cmd")
363
+ or_serve = or_subs.add_parser(
364
+ "serve",
365
+ aliases=["start"],
366
+ help="Launch the Oracle dashboard server (REST + MCP discovery endpoints)",
367
+ )
368
+ or_serve.add_argument("--port", type=int, default=None,
369
+ help="Server port (default: config 'port', 3331)")
370
+ or_serve.add_argument("--no-browser", action="store_true",
371
+ help="Don't open the browser")
363
372
  or_api = or_subs.add_parser(
364
373
  "api",
365
374
  help="Show connection info / manage the Discovery API key",
@@ -8,9 +8,8 @@ import sys
8
8
  from concurrent.futures import ThreadPoolExecutor, as_completed
9
9
  from pathlib import Path
10
10
 
11
- from core import count_tokens
12
-
13
11
  from cli.tools._helpers import finalize_with_tokens, show_token_ratios
12
+ from core import count_tokens
14
13
 
15
14
 
16
15
  def _run_memory_mcp_cli(args: list, cwd: str, timeout: int = 30) -> tuple:
@@ -7,7 +7,6 @@ unions child facts. Results stay in per-scope sections — TF-IDF scores are
7
7
  not comparable across corpora, so no interleaved ranking.
8
8
  """
9
9
 
10
- from pathlib import Path
11
10
 
12
11
  # Budget split for scope='all': the parent keeps the lion's share.
13
12
  _PARENT_BUDGET_SHARE = 0.6
@@ -10,9 +10,8 @@ import json
10
10
  import re
11
11
  from pathlib import Path
12
12
 
13
- from core import count_tokens
14
-
15
13
  from cli.tools._helpers import finalize_with_tokens, show_token_ratios
14
+ from core import count_tokens
16
15
 
17
16
 
18
17
  def handle_filter(file_path: str, text: str, pattern: str, max_lines: int,
@@ -6,9 +6,8 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
6
6
  from pathlib import Path
7
7
  from typing import Any
8
8
 
9
- from core import count_tokens
10
-
11
9
  from cli.tools._helpers import finalize_with_tokens
10
+ from core import count_tokens
12
11
 
13
12
 
14
13
  def _coerce_list(val: Any) -> list[str] | None:
@@ -5,9 +5,8 @@ import time
5
5
  from concurrent.futures import ThreadPoolExecutor, as_completed
6
6
  from pathlib import Path
7
7
 
8
- from core import count_tokens
9
-
10
8
  from cli.tools._helpers import finalize_with_tokens, show_token_ratios
9
+ from core import count_tokens
11
10
 
12
11
  # Hard cap: responses above this are truncated to avoid filling context.
13
12
  _RESPONSE_TOKEN_CAP = 2400
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-context-control
3
- Version: 2.46.0
3
+ Version: 2.49.0
4
4
  Summary: Local code-intelligence layer for AI coding tools (Claude Code, Codex, Gemini, Copilot). Retrieve less, read less, edit safer — and version the configs that shape your agent (CLAUDE.md, skills, hooks, MCP).
5
5
  Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -112,6 +112,7 @@ oracle/config.py
112
112
  oracle/mcp_oracle.py
113
113
  oracle/oracle.html
114
114
  oracle/oracle_server.py
115
+ oracle/oracle_ui.html
115
116
  oracle/services/__init__.py
116
117
  oracle/services/activity_reporter.py
117
118
  oracle/services/api_auth.py
@@ -122,6 +123,7 @@ oracle/services/cross_memory.py
122
123
  oracle/services/federated_graph.py
123
124
  oracle/services/health_checker.py
124
125
  oracle/services/insight_engine.py
126
+ oracle/services/local_session.py
125
127
  oracle/services/memory_reader.py
126
128
  oracle/services/memory_writer.py
127
129
  oracle/services/ollama_bridge.py
@@ -129,6 +131,24 @@ oracle/services/project_scanner.py
129
131
  oracle/services/review_agent.py
130
132
  oracle/services/tool_executor.py
131
133
  oracle/services/tool_registry.py
134
+ oracle/ui/activity.js
135
+ oracle/ui/agents.js
136
+ oracle/ui/app.js
137
+ oracle/ui/busy.js
138
+ oracle/ui/core.js
139
+ oracle/ui/crossgraph.js
140
+ oracle/ui/header.js
141
+ oracle/ui/insights.js
142
+ oracle/ui/projects.js
143
+ oracle/ui/settings.js
144
+ oracle/ui/suggestions.js
145
+ oracle/ui/theme_tabs.js
146
+ oracle/ui/chat/conversations.js
147
+ oracle/ui/chat/input.js
148
+ oracle/ui/chat/markdown.js
149
+ oracle/ui/chat/send.js
150
+ oracle/ui/chat/stream_renderer.js
151
+ oracle/ui/chat/toolbar.js
132
152
  services/__init__.py
133
153
  services/activity_log.py
134
154
  services/agent_base.py
@@ -199,6 +219,7 @@ tests/test_bitbucket_client.py
199
219
  tests/test_bitbucket_config_fallback.py
200
220
  tests/test_bitbucket_credentials.py
201
221
  tests/test_bitbucket_tool.py
222
+ tests/test_c3_bridge_project_artifacts.py
202
223
  tests/test_c3_shell.py
203
224
  tests/test_circuit_breaker.py
204
225
  tests/test_claude_md_merge.py
@@ -231,8 +252,15 @@ tests/test_notification_dedup.py
231
252
  tests/test_notification_discipline.py
232
253
  tests/test_oracle_api_auth.py
233
254
  tests/test_oracle_apikey_api.py
255
+ tests/test_oracle_c3_bridge.py
256
+ tests/test_oracle_chat_engine.py
234
257
  tests/test_oracle_discovery_api.py
258
+ tests/test_oracle_local_auth.py
259
+ tests/test_oracle_ollama_bridge.py
260
+ tests/test_oracle_scanner_cache.py
235
261
  tests/test_oracle_security_fixes.py
262
+ tests/test_oracle_subproject_awareness.py
263
+ tests/test_oracle_ui_bundle.py
236
264
  tests/test_output_filter.py
237
265
  tests/test_permissions.py
238
266
  tests/test_pm_api.py
@@ -242,6 +270,7 @@ tests/test_project_tool.py
242
270
  tests/test_read_coercion.py
243
271
  tests/test_response_boilerplate.py
244
272
  tests/test_retention.py
273
+ tests/test_review_digest.py
245
274
  tests/test_service_durability.py
246
275
  tests/test_session_benchmark.py
247
276
  tests/test_session_budget.py
@@ -18,10 +18,18 @@ DEFAULTS = {
18
18
  "mcp_port": 3332, # discovery MCP transport port (loopback)
19
19
  "ollama_base_url": "https://ollama.com",
20
20
  "ollama_api_key": "",
21
+ "llm_cache_ttl_sec": 86400, # disk cache TTL for generate() responses
21
22
  "model": "gemma4:31b-cloud",
22
23
  "hub_url": "http://localhost:3330",
24
+ "scanner_ttl_seconds": 20,
23
25
  "review_interval_seconds": 1800,
24
26
  "review_enabled": True,
27
+ # ── Scheduled activity digest (runs inside the review loop) ──
28
+ "digest_enabled": False, # off = current behavior (on-demand only)
29
+ "digest_interval_seconds": 86400, # daily cadence once enabled
30
+ "digest_narrate": False, # LLM prose costs a cloud call; opt-in
31
+ "digest_notify_file": "", # "" = disabled; else JSONL sink path
32
+ "digest_retention_days": 14, # prune stored digests
25
33
  "auto_open_browser": True,
26
34
  "theme": "dark",
27
35
  "max_facts_per_analysis": 100,
@@ -39,6 +47,7 @@ DEFAULTS = {
39
47
  "description": "Expert in system architecture, design patterns, and cross-project structure. Best for high-level analysis.",
40
48
  "system_prompt": "You are the Architect. Focus on structural integrity, design patterns, and the big picture. Provide high-level recommendations before diving into code.",
41
49
  "model": "gemma4:31b-cloud",
50
+ "backend": "ollama",
42
51
  "active": True
43
52
  },
44
53
  {
@@ -47,6 +56,7 @@ DEFAULTS = {
47
56
  "description": "Specializes in deep code analysis, bug hunting, and tracing execution paths.",
48
57
  "system_prompt": "You are the Code Explorer. Be incredibly precise, cite specific lines of code, and focus on the technical implementation details. Trace logic thoroughly.",
49
58
  "model": "gemma4:31b-cloud",
59
+ "backend": "ollama",
50
60
  "active": True
51
61
  },
52
62
  {
@@ -55,6 +65,7 @@ DEFAULTS = {
55
65
  "description": "Focuses on analyzing project memory, facts, and insights.",
56
66
  "system_prompt": "You are the Memory Analyst. Rely heavily on memory tools and facts to spot trends. Connect current issues to past context.",
57
67
  "model": "gemma4:31b-cloud",
68
+ "backend": "ollama",
58
69
  "active": True
59
70
  }
60
71
  ],
@@ -991,6 +991,7 @@
991
991
  <button class="btn btn-primary btn-sm" id="btnLoadActivity" onclick="loadActivity()">Refresh</button>
992
992
  </div>
993
993
  </div>
994
+ <div id="actScheduled" style="display:none;font-size:11px;color:var(--text2);margin-bottom:10px"></div>
994
995
  <div id="actSummary" style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:14px"></div>
995
996
  <div id="actNarrative" class="card" style="display:none;margin-bottom:14px"></div>
996
997
  <div id="actProjects"></div>
@@ -1104,6 +1105,16 @@
1104
1105
  <label>Review Interval (minutes)</label>
1105
1106
  <input id="cfgInterval" type="number" min="1" value="30">
1106
1107
  </div>
1108
+ <div class="field">
1109
+ <label style="display:flex;align-items:center;gap:8px">
1110
+ <input type="checkbox" id="cfgDigestEnabled" style="width:auto"> Scheduled activity digest
1111
+ </label>
1112
+ <p style="font-size:11px;color:var(--text2);margin-top:4px">Emit a cross-project digest from the review loop (Activity tab shows the latest).</p>
1113
+ </div>
1114
+ <div class="field">
1115
+ <label>Digest Interval (hours)</label>
1116
+ <input id="cfgDigestInterval" type="number" min="1" value="24">
1117
+ </div>
1107
1118
  <div style="display:flex;gap:8px;margin-top:16px">
1108
1119
  <button class="btn btn-primary" onclick="saveSettings()">Save</button>
1109
1120
  <button class="btn btn-ghost" onclick="testConnection()">Test Connection</button>
@@ -1172,6 +1183,17 @@
1172
1183
  <label>Model</label>
1173
1184
  <input id="agentModel" type="text" placeholder="e.g. gemma4:31b-cloud">
1174
1185
  </div>
1186
+ <div class="field">
1187
+ <label>Backend</label>
1188
+ <select id="agentBackend" style="width:100%;padding:8px;font-size:12px;background:var(--bg3);border:1px solid var(--border);border-radius:6px;color:var(--text)">
1189
+ <option value="ollama">Ollama (chat tool loop)</option>
1190
+ <option value="codex">Codex CLI (read-only, needs project)</option>
1191
+ <option value="gemini">Gemini CLI (read-only, needs project)</option>
1192
+ <option value="claude">Claude CLI (read-only, needs project)</option>
1193
+ <option value="auto">Auto cascade (read-only, needs project)</option>
1194
+ </select>
1195
+ <p style="font-size:11px;color:var(--text2);margin-top:4px">CLI backends run c3_delegate inside a registered project; the model field applies to Ollama only.</p>
1196
+ </div>
1175
1197
  <div class="field" style="display:flex;align-items:center;gap:8px">
1176
1198
  <input id="agentActive" type="checkbox" style="width:auto">
1177
1199
  <label style="margin:0;cursor:pointer" for="agentActive">Active</label>
@@ -1927,7 +1949,23 @@ async function dismissInsight(id) {
1927
1949
  // ═══════════════════════════════════════════════════════════
1928
1950
  // ── Activity Digest ──
1929
1951
  // ═══════════════════════════════════════════════════════════
1952
+ async function loadScheduledDigestBanner() {
1953
+ const el = document.getElementById('actScheduled');
1954
+ if (!el) return;
1955
+ try {
1956
+ const latest = await api('/api/activity/digest/latest');
1957
+ if (latest && latest.generated_at) {
1958
+ el.style.display = '';
1959
+ el.textContent = 'Last scheduled digest: ' +
1960
+ latest.generated_at.replace('T', ' ').slice(0, 19) + ' UTC';
1961
+ } else {
1962
+ el.style.display = 'none';
1963
+ }
1964
+ } catch { el.style.display = 'none'; }
1965
+ }
1966
+
1930
1967
  async function loadActivity() {
1968
+ loadScheduledDigestBanner();
1931
1969
  const dateEl = document.getElementById('actDate');
1932
1970
  if (dateEl && !dateEl.value) dateEl.value = new Date().toISOString().slice(0, 10);
1933
1971
  const date = dateEl ? dateEl.value : '';
@@ -2076,6 +2114,8 @@ async function loadSettings() {
2076
2114
  keyCheck.parentElement.style.display = hasKey ? '' : 'none';
2077
2115
  document.getElementById('cfgHubUrl').value = cfg.hub_url || '';
2078
2116
  document.getElementById('cfgInterval').value = Math.round((cfg.review_interval_seconds || 1800) / 60);
2117
+ document.getElementById('cfgDigestEnabled').checked = !!cfg.digest_enabled;
2118
+ document.getElementById('cfgDigestInterval').value = Math.round((cfg.digest_interval_seconds || 86400) / 3600);
2079
2119
  applyTheme(cfg.theme || 'dark');
2080
2120
  // Hub link
2081
2121
  const hubUrl = cfg.hub_url || 'http://localhost:3330';
@@ -2122,6 +2162,8 @@ async function saveSettings() {
2122
2162
  ollama_base_url: document.getElementById('cfgOllamaUrl').value,
2123
2163
  hub_url: document.getElementById('cfgHubUrl').value,
2124
2164
  review_interval_seconds: parseInt(document.getElementById('cfgInterval').value) * 60,
2165
+ digest_enabled: document.getElementById('cfgDigestEnabled').checked,
2166
+ digest_interval_seconds: parseInt(document.getElementById('cfgDigestInterval').value) * 3600,
2125
2167
  agents: window.oracleAgents
2126
2168
  };
2127
2169
  if (apiKeyVal) cfg.ollama_api_key = apiKeyVal;
@@ -4040,7 +4082,7 @@ function renderAgents() {
4040
4082
  <div style="flex:1;min-width:0;margin-right:16px">
4041
4083
  <div style="font-weight:600;font-size:14px;margin-bottom:4px;display:flex;align-items:center;gap:8px">
4042
4084
  ${esc(a.name)}
4043
- <span style="font-size:10px;font-family:monospace;background:var(--bg3);padding:2px 6px;border-radius:4px;color:var(--text2)">${esc(a.model)}</span>
4085
+ <span style="font-size:10px;font-family:monospace;background:var(--bg3);padding:2px 6px;border-radius:4px;color:var(--text2)">${esc(a.backend && a.backend !== 'ollama' ? a.backend : a.model)}</span>
4044
4086
  </div>
4045
4087
  <div style="font-size:12px;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="${esc(a.description)}">${esc(a.description)}</div>
4046
4088
  </div>
@@ -4071,6 +4113,7 @@ function openAgentModal(id = null) {
4071
4113
  descInput.value = a.description || '';
4072
4114
  promptInput.value = a.system_prompt || '';
4073
4115
  modelInput.value = a.model || '';
4116
+ document.getElementById('agentBackend').value = a.backend || 'ollama';
4074
4117
  activeInput.checked = a.active !== false;
4075
4118
  } else {
4076
4119
  title.textContent = 'Add Custom Agent';
@@ -4079,6 +4122,7 @@ function openAgentModal(id = null) {
4079
4122
  descInput.value = '';
4080
4123
  promptInput.value = '';
4081
4124
  modelInput.value = window.oracleConfig.model || 'gemma4:31b-cloud';
4125
+ document.getElementById('agentBackend').value = 'ollama';
4082
4126
  activeInput.checked = true;
4083
4127
  }
4084
4128
 
@@ -4100,6 +4144,7 @@ function saveAgent() {
4100
4144
  description: document.getElementById('agentDesc').value.trim(),
4101
4145
  system_prompt: document.getElementById('agentPrompt').value.trim(),
4102
4146
  model: document.getElementById('agentModel').value.trim(),
4147
+ backend: document.getElementById('agentBackend').value,
4103
4148
  active: document.getElementById('agentActive').checked
4104
4149
  };
4105
4150
 
@@ -20,7 +20,7 @@ from flask import Flask, Response, jsonify, request, send_from_directory # noqa
20
20
 
21
21
  from oracle.config import ORACLE_DIR, load_config, save_config # noqa: E402
22
22
  from oracle.mcp_oracle import mcp_url, start_mcp_thread # noqa: E402
23
- from oracle.services import api_auth # noqa: E402
23
+ from oracle.services import api_auth, local_session # noqa: E402
24
24
  from oracle.services.activity_reporter import ActivityReporter # noqa: E402
25
25
  from oracle.services.api_auth import extract_bearer # noqa: E402
26
26
  from oracle.services.api_auth import verify as verify_api_key # noqa: E402
@@ -68,6 +68,7 @@ def _init_services():
68
68
  base_url=_cfg.get("ollama_base_url", "https://ollama.com"),
69
69
  model=_cfg.get("model", "gemma4:31b-cloud"),
70
70
  api_key=_cfg.get("ollama_api_key", ""),
71
+ cache_ttl_sec=int(_cfg.get("llm_cache_ttl_sec", 86400)),
71
72
  )
72
73
  # Verify model works on startup (background thread to avoid blocking)
73
74
  def _verify():
@@ -80,7 +81,10 @@ def _init_services():
80
81
  _model_verified = False
81
82
  logging.getLogger("oracle").warning("Ollama unreachable — model not verified")
82
83
  threading.Thread(target=_verify, daemon=True, name="oracle-model-verify").start()
83
- _scanner = ProjectScanner(hub_url=_cfg.get("hub_url", "http://localhost:3330"))
84
+ _scanner = ProjectScanner(
85
+ hub_url=_cfg.get("hub_url", "http://localhost:3330"),
86
+ ttl=float(_cfg.get("scanner_ttl_seconds", 20)),
87
+ )
84
88
  _reader = MemoryReader()
85
89
  _checker = HealthChecker(_reader)
86
90
  _writer = MemoryWriter()
@@ -89,6 +93,8 @@ def _init_services():
89
93
  _chat_store = ChatStore()
90
94
  _c3_bridge = C3Bridge(scanner=_scanner)
91
95
  _federated = FederatedGraph(reader=_reader, cross_memory=_cross_memory, ollama_bridge=_bridge)
96
+ # Reporter before ReviewAgent: the review loop emits the scheduled digest.
97
+ _activity_reporter = ActivityReporter(scanner=_scanner, ollama_bridge=_bridge)
92
98
  _agent = ReviewAgent(
93
99
  scanner=_scanner,
94
100
  reader=_reader,
@@ -98,8 +104,8 @@ def _init_services():
98
104
  writer=_writer,
99
105
  interval=int(_cfg.get("review_interval_seconds", 1800)),
100
106
  federated_graph=_federated,
107
+ activity_reporter=_activity_reporter,
101
108
  )
102
- _activity_reporter = ActivityReporter(scanner=_scanner, ollama_bridge=_bridge)
103
109
  _chat_engine = ChatEngine(
104
110
  bridge=_bridge,
105
111
  reader=_reader,
@@ -122,9 +128,10 @@ def _init_services():
122
128
  # ── CORS ──────────────────────────────────────────────────
123
129
  # Localhost security guard + scoped CORS (replaces the previous wildcard CORS).
124
130
  # Host-header allowlist + Origin/Referer CSRF guard. Bearer auth on
125
- # /api/discovery/* (see _discovery_auth_guard below) still applies on top; this
126
- # guard also protects the ungated endpoints (config, chat, /api/apikey) from
127
- # cross-origin reads notably the raw Discovery token returned by api_apikey_get.
131
+ # /api/discovery/* (see _discovery_auth_guard below) and the local write gate
132
+ # (_local_write_guard: session cookie or Bearer on every other mutating
133
+ # /api/* call) still apply on top; this guard blocks cross-origin browsers,
134
+ # the write gate blocks unauthenticated local processes.
128
135
  from core.web_security import (
129
136
  allowed_hostnames as _allowed_hostnames,
130
137
  )
@@ -155,10 +162,99 @@ def _discovery_auth_guard():
155
162
  return None
156
163
 
157
164
 
165
+ # ── Local write gate ──────────────────────────────────────
166
+ @app.before_request
167
+ def _local_write_guard():
168
+ """Auth gate for mutating local API calls (everything except Discovery).
169
+
170
+ Requires either the per-boot dashboard session cookie (issued on ``GET /``
171
+ to loopback browsers) or the Discovery Bearer token. Default-deny: any
172
+ future mutating ``/api/*`` endpoint is covered automatically. Closes the
173
+ rotate-then-read kill chain — previously any local process could POST
174
+ /api/apikey/rotate unauthenticated and read the fresh token, defeating
175
+ the Bearer gates on /api/config and /api/discovery/*.
176
+ """
177
+ path = request.path or ""
178
+ if not path.startswith("/api/") or path.startswith("/api/discovery"):
179
+ return None
180
+ if request.method in ("GET", "HEAD", "OPTIONS"):
181
+ return None
182
+ if verify_api_key(extract_bearer(request.headers.get("Authorization"))):
183
+ return None
184
+ if local_session.verify(request.cookies.get(local_session.COOKIE_NAME)):
185
+ return None
186
+ return jsonify({"error": "unauthorized"}), 401
187
+
188
+
158
189
  # ── Static ────────────────────────────────────────────────
190
+
191
+ # JS load order for the concatenated Oracle UI build (mirrors cli/hub_server.py).
192
+ # One shared script scope: function declarations hoist across files, and
193
+ # app.js (the init IIFE) must stay LAST.
194
+ _ORACLE_JS_FILES = [
195
+ "ui/core.js",
196
+ "ui/busy.js",
197
+ "ui/theme_tabs.js",
198
+ "ui/crossgraph.js",
199
+ "ui/header.js",
200
+ "ui/projects.js",
201
+ "ui/insights.js",
202
+ "ui/activity.js",
203
+ "ui/suggestions.js",
204
+ "ui/settings.js",
205
+ "ui/agents.js",
206
+ "ui/chat/markdown.js",
207
+ "ui/chat/conversations.js",
208
+ "ui/chat/stream_renderer.js",
209
+ "ui/chat/toolbar.js",
210
+ "ui/chat/input.js",
211
+ "ui/chat/send.js",
212
+ "ui/app.js",
213
+ ]
214
+
215
+
216
+ def _build_oracle_html() -> str:
217
+ """Concatenate oracle_ui.html shell + all JS module files into one response."""
218
+ oracle_dir = Path(__file__).parent
219
+ shell_path = oracle_dir / "oracle_ui.html"
220
+ if not shell_path.exists():
221
+ return "<h1>Oracle UI not found.</h1>"
222
+
223
+ shell = shell_path.read_text(encoding="utf-8")
224
+
225
+ js_parts = []
226
+ for rel in _ORACLE_JS_FILES:
227
+ js_path = oracle_dir / rel
228
+ if js_path.exists():
229
+ js_parts.append(f"// ═══ {rel} ═══\n" + js_path.read_text(encoding="utf-8"))
230
+
231
+ return shell.replace("/* __C3_ORACLE_SCRIPTS__ */", "\n\n".join(js_parts))
232
+
233
+
234
+ # Cache the built HTML (built on first request; cleared on server restart).
235
+ _oracle_html_cache: str | None = None
236
+
237
+
159
238
  @app.route("/")
160
239
  def index():
161
- return send_from_directory(os.path.dirname(__file__), "oracle.html")
240
+ global _oracle_html_cache
241
+ if _oracle_html_cache is None:
242
+ _oracle_html_cache = _build_oracle_html()
243
+ resp = Response(_oracle_html_cache, mimetype="text/html")
244
+ # Issue the dashboard session cookie only to loopback browsers; remote
245
+ # viewers (LAN bind) can read GET dashboards but cannot mutate.
246
+ if local_session.is_loopback(request.remote_addr):
247
+ local_session.attach_cookie(resp)
248
+ return resp
249
+
250
+
251
+ @app.route("/legacy")
252
+ def index_legacy():
253
+ """Frozen pre-bundle Oracle UI — escape hatch for one release, then removed."""
254
+ resp = send_from_directory(os.path.dirname(__file__), "oracle.html")
255
+ if local_session.is_loopback(request.remote_addr):
256
+ local_session.attach_cookie(resp)
257
+ return resp
162
258
 
163
259
 
164
260
  # ── Health ────────────────────────────────────────────────
@@ -206,11 +302,9 @@ _CONFIG_SETTABLE_KEYS = frozenset(_CONFIG_DEFAULTS.keys())
206
302
  @app.route("/api/config", methods=["POST"])
207
303
  def api_config_set():
208
304
  global _cfg
209
- # Require the Bearer token: this endpoint can disable Discovery auth or
210
- # repoint ollama_base_url, so it must never be reachable unauthenticated.
211
- token = extract_bearer(request.headers.get("Authorization"))
212
- if not verify_api_key(token):
213
- return jsonify({"error": "unauthorized"}), 401
305
+ # Auth is enforced by _local_write_guard (session cookie or Bearer): this
306
+ # endpoint can disable Discovery auth or repoint ollama_base_url, so it
307
+ # must never be reachable unauthenticated.
214
308
  body = request.get_json(silent=True) or {}
215
309
  if not isinstance(body, dict):
216
310
  return jsonify({"error": "body must be a JSON object"}), 400
@@ -258,7 +352,8 @@ def api_projects():
258
352
 
259
353
  @app.route("/api/projects/scan", methods=["POST"])
260
354
  def api_projects_scan():
261
- projects = _scanner.discover() if _scanner else []
355
+ # Explicit Scan action bypasses the scanner's TTL cache.
356
+ projects = _scanner.discover(force=True) if _scanner else []
262
357
  return jsonify({"scanned": len(projects), "projects": projects})
263
358
 
264
359
 
@@ -635,6 +730,23 @@ def api_chat_conversation_state(conv_id):
635
730
 
636
731
 
637
732
  # ── Activity digest (Oracle UI) ───────────────────────────
733
+ @app.route("/api/activity/digest/latest", methods=["GET"])
734
+ def api_activity_digest_latest():
735
+ """Most recent SCHEDULED digest (written by the review loop), or null.
736
+
737
+ On-demand digests via /api/activity/digest are not persisted here; this
738
+ serves ~/.c3/oracle/activity_digests/latest.json.
739
+ """
740
+ latest = ORACLE_DIR / "activity_digests" / "latest.json"
741
+ try:
742
+ if latest.is_file():
743
+ return Response(latest.read_text(encoding="utf-8"),
744
+ mimetype="application/json")
745
+ except Exception as e:
746
+ return jsonify({"error": str(e)}), 500
747
+ return jsonify({"digest": None, "generated_at": None})
748
+
749
+
638
750
  @app.route("/api/activity/digest", methods=["GET"])
639
751
  def api_activity_digest():
640
752
  """Cross-project activity digest for the Oracle UI.
@@ -740,7 +852,7 @@ def api_discovery_mcp_info():
740
852
  })
741
853
 
742
854
 
743
- # ── Discovery API key management (local dashboard unauthenticated, loopback) ──
855
+ # ── Discovery API key management (mutations gated by _local_write_guard) ──
744
856
  def _apikey_status(reveal: bool = False) -> dict:
745
857
  """Status payload for the Discovery API token + connection info.
746
858
 
@@ -786,12 +898,14 @@ def _apikey_status(reveal: bool = False) -> dict:
786
898
  def api_apikey_get():
787
899
  """Return Discovery API token status + connection info.
788
900
 
789
- The unmasked token is only included when the caller presents a valid Bearer
790
- token; otherwise only the masked form is returned (never the raw key over
791
- HTTP unauthenticated).
901
+ The unmasked token is only included when the caller presents a valid
902
+ Bearer token or the dashboard session cookie; otherwise only the masked
903
+ form is returned (never the raw key over HTTP unauthenticated).
792
904
  """
793
905
  try:
794
- reveal = verify_api_key(extract_bearer(request.headers.get("Authorization")))
906
+ reveal = verify_api_key(
907
+ extract_bearer(request.headers.get("Authorization"))
908
+ ) or local_session.verify(request.cookies.get(local_session.COOKIE_NAME))
795
909
  return jsonify(_apikey_status(reveal=reveal))
796
910
  except Exception as e:
797
911
  return jsonify({"error": str(e)}), 500