vidbyte-cli 0.1.0__py3-none-any.whl

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 (269) hide show
  1. vidbyte_cli/__init__.py +11 -0
  2. vidbyte_cli/__main__.py +6 -0
  3. vidbyte_cli/cli.py +18 -0
  4. vidbyte_cli/commands/__init__.py +86 -0
  5. vidbyte_cli/commands/agent_options.py +53 -0
  6. vidbyte_cli/commands/agents/__init__.py +31 -0
  7. vidbyte_cli/commands/agents/suggestion/__init__.py +38 -0
  8. vidbyte_cli/commands/agents/suggestion/prompts/__init__.py +3 -0
  9. vidbyte_cli/commands/agents/suggestion/prompts/all_categories.md +5 -0
  10. vidbyte_cli/commands/agents/suggestion/prompts/approaches.md +100 -0
  11. vidbyte_cli/commands/agents/suggestion/prompts/avoid.md +100 -0
  12. vidbyte_cli/commands/agents/suggestion/prompts/blockers.md +100 -0
  13. vidbyte_cli/commands/agents/suggestion/prompts/capability.md +100 -0
  14. vidbyte_cli/commands/agents/suggestion/prompts/categories.md +5 -0
  15. vidbyte_cli/commands/agents/suggestion/prompts/category.md +5 -0
  16. vidbyte_cli/commands/agents/suggestion/prompts/completed.md +100 -0
  17. vidbyte_cli/commands/agents/suggestion/prompts/constraint.md +100 -0
  18. vidbyte_cli/commands/agents/suggestion/prompts/context.md +100 -0
  19. vidbyte_cli/commands/agents/suggestion/prompts/count.md +5 -0
  20. vidbyte_cli/commands/agents/suggestion/prompts/critic_model.md +100 -0
  21. vidbyte_cli/commands/agents/suggestion/prompts/decision.md +100 -0
  22. vidbyte_cli/commands/agents/suggestion/prompts/dry_run.md +5 -0
  23. vidbyte_cli/commands/agents/suggestion/prompts/extra_compute.md +5 -0
  24. vidbyte_cli/commands/agents/suggestion/prompts/feedback_accept.md +7 -0
  25. vidbyte_cli/commands/agents/suggestion/prompts/feedback_group.md +7 -0
  26. vidbyte_cli/commands/agents/suggestion/prompts/feedback_project.md +100 -0
  27. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reason.md +98 -0
  28. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reject.md +7 -0
  29. vidbyte_cli/commands/agents/suggestion/prompts/feedback_suggestion.md +101 -0
  30. vidbyte_cli/commands/agents/suggestion/prompts/files.md +100 -0
  31. vidbyte_cli/commands/agents/suggestion/prompts/forbidden.md +100 -0
  32. vidbyte_cli/commands/agents/suggestion/prompts/goal.md +100 -0
  33. vidbyte_cli/commands/agents/suggestion/prompts/handoff.md +7 -0
  34. vidbyte_cli/commands/agents/suggestion/prompts/horizon.md +5 -0
  35. vidbyte_cli/commands/agents/suggestion/prompts/hypotheses.md +100 -0
  36. vidbyte_cli/commands/agents/suggestion/prompts/idea.md +100 -0
  37. vidbyte_cli/commands/agents/suggestion/prompts/in_progress.md +100 -0
  38. vidbyte_cli/commands/agents/suggestion/prompts/input.md +100 -0
  39. vidbyte_cli/commands/agents/suggestion/prompts/library.py +29 -0
  40. vidbyte_cli/commands/agents/suggestion/prompts/max_messages.md +5 -0
  41. vidbyte_cli/commands/agents/suggestion/prompts/max_output_tokens.md +5 -0
  42. vidbyte_cli/commands/agents/suggestion/prompts/max_total_tokens.md +5 -0
  43. vidbyte_cli/commands/agents/suggestion/prompts/mistakes.md +100 -0
  44. vidbyte_cli/commands/agents/suggestion/prompts/outcomes.md +100 -0
  45. vidbyte_cli/commands/agents/suggestion/prompts/project.md +100 -0
  46. vidbyte_cli/commands/agents/suggestion/prompts/project_create.md +7 -0
  47. vidbyte_cli/commands/agents/suggestion/prompts/project_description.md +98 -0
  48. vidbyte_cli/commands/agents/suggestion/prompts/project_group.md +7 -0
  49. vidbyte_cli/commands/agents/suggestion/prompts/project_key.md +96 -0
  50. vidbyte_cli/commands/agents/suggestion/prompts/project_list.md +7 -0
  51. vidbyte_cli/commands/agents/suggestion/prompts/project_title.md +99 -0
  52. vidbyte_cli/commands/agents/suggestion/prompts/provider.md +5 -0
  53. vidbyte_cli/commands/agents/suggestion/prompts/question.md +100 -0
  54. vidbyte_cli/commands/agents/suggestion/prompts/risks.md +100 -0
  55. vidbyte_cli/commands/agents/suggestion/prompts/rounds.md +5 -0
  56. vidbyte_cli/commands/agents/suggestion/prompts/run.md +5 -0
  57. vidbyte_cli/commands/agents/suggestion/prompts/success.md +100 -0
  58. vidbyte_cli/commands/agents/suggestion/prompts/timeout.md +5 -0
  59. vidbyte_cli/commands/agents/suggestion/prompts/trajectory.md +100 -0
  60. vidbyte_cli/commands/agents/suggestion/prompts/view.md +5 -0
  61. vidbyte_cli/commands/agents/suggestion/prompts/view_all.md +5 -0
  62. vidbyte_cli/commands/agents/suggestion/render.py +77 -0
  63. vidbyte_cli/commands/agents/suggestion/request_builder.py +240 -0
  64. vidbyte_cli/commands/agents/suggestion/suggest.py +154 -0
  65. vidbyte_cli/commands/agents/suggestion/suggestion_categories.py +77 -0
  66. vidbyte_cli/commands/agents/suggestion/suggestion_feedback.py +85 -0
  67. vidbyte_cli/commands/agents/suggestion/suggestion_handoff.py +67 -0
  68. vidbyte_cli/commands/agents/suggestion/suggestion_projects.py +95 -0
  69. vidbyte_cli/commands/auth/__init__.py +0 -0
  70. vidbyte_cli/commands/auth/login.py +100 -0
  71. vidbyte_cli/commands/auth/logout.py +41 -0
  72. vidbyte_cli/commands/auth/whoami.py +51 -0
  73. vidbyte_cli/commands/billing/__init__.py +20 -0
  74. vidbyte_cli/commands/billing/prompts/__init__.py +1 -0
  75. vidbyte_cli/commands/billing/prompts/confirm.md +16 -0
  76. vidbyte_cli/commands/billing/prompts/group.md +13 -0
  77. vidbyte_cli/commands/billing/prompts/idempotency_key.md +16 -0
  78. vidbyte_cli/commands/billing/prompts/library.py +18 -0
  79. vidbyte_cli/commands/billing/prompts/top_up.md +16 -0
  80. vidbyte_cli/commands/billing/top_up.py +65 -0
  81. vidbyte_cli/commands/config/__init__.py +0 -0
  82. vidbyte_cli/commands/config/get.py +45 -0
  83. vidbyte_cli/commands/config/set.py +39 -0
  84. vidbyte_cli/commands/provider/__init__.py +9 -0
  85. vidbyte_cli/commands/provider/login.py +102 -0
  86. vidbyte_cli/commands/provider/logout.py +45 -0
  87. vidbyte_cli/commands/provider/whoami.py +49 -0
  88. vidbyte_cli/commands/research/__init__.py +5 -0
  89. vidbyte_cli/commands/research/add.py +41 -0
  90. vidbyte_cli/commands/research/options.py +134 -0
  91. vidbyte_cli/commands/research/render.py +157 -0
  92. vidbyte_cli/commands/research/resume.py +37 -0
  93. vidbyte_cli/commands/research/start.py +37 -0
  94. vidbyte_cli/commands/research/status.py +32 -0
  95. vidbyte_cli/commands/research/thread.py +30 -0
  96. vidbyte_cli/commands/research/threads.py +35 -0
  97. vidbyte_cli/commands/research/watch.py +93 -0
  98. vidbyte_cli/commands/runtime/__init__.py +23 -0
  99. vidbyte_cli/commands/runtime/adversarial_team.py +41 -0
  100. vidbyte_cli/commands/runtime/doctor.py +44 -0
  101. vidbyte_cli/commands/runtime/list.py +47 -0
  102. vidbyte_cli/commands/runtime/persistence.py +141 -0
  103. vidbyte_cli/commands/runtime/same_host_ensemble.py +221 -0
  104. vidbyte_cli/commands/runtime/stages.py +553 -0
  105. vidbyte_cli/commands/runtime/task_board.py +1342 -0
  106. vidbyte_cli/commands/setup/__init__.py +0 -0
  107. vidbyte_cli/commands/setup/doctor.py +64 -0
  108. vidbyte_cli/commands/setup/prompts/__init__.py +1 -0
  109. vidbyte_cli/commands/setup/prompts/browser.md +7 -0
  110. vidbyte_cli/commands/setup/prompts/library.py +18 -0
  111. vidbyte_cli/commands/setup/prompts/star.md +13 -0
  112. vidbyte_cli/commands/setup/star.py +132 -0
  113. vidbyte_cli/lib/__init__.py +0 -0
  114. vidbyte_cli/lib/api/__init__.py +0 -0
  115. vidbyte_cli/lib/api/client.py +236 -0
  116. vidbyte_cli/lib/api/endpoints/__init__.py +0 -0
  117. vidbyte_cli/lib/api/endpoints/auth.py +24 -0
  118. vidbyte_cli/lib/api/endpoints/billing.py +36 -0
  119. vidbyte_cli/lib/api/endpoints/research.py +103 -0
  120. vidbyte_cli/lib/api/endpoints/runtime.py +113 -0
  121. vidbyte_cli/lib/api/problem.py +86 -0
  122. vidbyte_cli/lib/api/response.py +101 -0
  123. vidbyte_cli/lib/api/retry.py +116 -0
  124. vidbyte_cli/lib/api/runtime_payment.py +89 -0
  125. vidbyte_cli/lib/auth/__init__.py +16 -0
  126. vidbyte_cli/lib/auth/credentials.py +223 -0
  127. vidbyte_cli/lib/auth/input.py +59 -0
  128. vidbyte_cli/lib/auth/keyring_store.py +126 -0
  129. vidbyte_cli/lib/auth/provider_credentials.py +157 -0
  130. vidbyte_cli/lib/auth/provider_input.py +54 -0
  131. vidbyte_cli/lib/auth/provider_resolver.py +89 -0
  132. vidbyte_cli/lib/auth/provider_store.py +149 -0
  133. vidbyte_cli/lib/auth/provider_verifier.py +219 -0
  134. vidbyte_cli/lib/auth/resolver.py +63 -0
  135. vidbyte_cli/lib/auth/verifier.py +38 -0
  136. vidbyte_cli/lib/config/__init__.py +25 -0
  137. vidbyte_cli/lib/config/atomic.py +47 -0
  138. vidbyte_cli/lib/config/config.py +141 -0
  139. vidbyte_cli/lib/config/migration.py +81 -0
  140. vidbyte_cli/lib/config/models.py +145 -0
  141. vidbyte_cli/lib/config/paths.py +66 -0
  142. vidbyte_cli/lib/config/resolver.py +137 -0
  143. vidbyte_cli/lib/constants/__init__.py +1 -0
  144. vidbyte_cli/lib/constants/runtime.py +186 -0
  145. vidbyte_cli/lib/errors/__init__.py +12 -0
  146. vidbyte_cli/lib/errors/cli_error.py +86 -0
  147. vidbyte_cli/lib/errors/codes.py +43 -0
  148. vidbyte_cli/lib/errors/failures.py +2910 -0
  149. vidbyte_cli/lib/errors/handler.py +72 -0
  150. vidbyte_cli/lib/files/__init__.py +6 -0
  151. vidbyte_cli/lib/files/documents.py +94 -0
  152. vidbyte_cli/lib/files/store.py +156 -0
  153. vidbyte_cli/lib/io/__init__.py +16 -0
  154. vidbyte_cli/lib/io/attachments.py +129 -0
  155. vidbyte_cli/lib/io/codex_attachments.py +47 -0
  156. vidbyte_cli/lib/io/prompt.py +71 -0
  157. vidbyte_cli/lib/io/streams.py +37 -0
  158. vidbyte_cli/lib/io/terminal.py +66 -0
  159. vidbyte_cli/lib/output/__init__.py +7 -0
  160. vidbyte_cli/lib/output/formats.py +29 -0
  161. vidbyte_cli/lib/output/manager.py +95 -0
  162. vidbyte_cli/lib/output/models.py +49 -0
  163. vidbyte_cli/lib/runtime/__init__.py +9 -0
  164. vidbyte_cli/lib/runtime/application.py +134 -0
  165. vidbyte_cli/lib/runtime/context.py +250 -0
  166. vidbyte_cli/lib/runtime/options.py +159 -0
  167. vidbyte_cli/lib/runtime/version.py +30 -0
  168. vidbyte_cli/lib/runtime_primitives/__init__.py +28 -0
  169. vidbyte_cli/lib/runtime_primitives/executor.py +54 -0
  170. vidbyte_cli/lib/runtime_primitives/gate.py +105 -0
  171. vidbyte_cli/lib/runtime_primitives/hosts.py +45 -0
  172. vidbyte_cli/lib/runtime_primitives/planner.py +79 -0
  173. vidbyte_cli/lib/runtime_primitives/stages.py +338 -0
  174. vidbyte_cli/lib/runtime_primitives/task_board.py +982 -0
  175. vidbyte_cli/lib/runtime_primitives/task_board_checkpoints.py +473 -0
  176. vidbyte_cli/lib/runtime_primitives/task_board_system.md +33 -0
  177. vidbyte_cli/lib/runtime_primitives/task_board_tasks.py +111 -0
  178. vidbyte_cli/lib/runtime_primitives/verification.py +68 -0
  179. vidbyte_cli/services/__init__.py +5 -0
  180. vidbyte_cli/services/ensemble/__init__.py +7 -0
  181. vidbyte_cli/services/ensemble/prompts/__init__.py +6 -0
  182. vidbyte_cli/services/ensemble/prompts/implementer_system.md +546 -0
  183. vidbyte_cli/services/ensemble/prompts/implementer_turn.md +49 -0
  184. vidbyte_cli/services/ensemble/prompts/library.py +188 -0
  185. vidbyte_cli/services/ensemble/prompts/planner_system.md +728 -0
  186. vidbyte_cli/services/ensemble/prompts/planner_turn.md +6 -0
  187. vidbyte_cli/services/ensemble/prompts/role_system.md +104 -0
  188. vidbyte_cli/services/ensemble/prompts/role_turn.md +19 -0
  189. vidbyte_cli/services/ensemble/prompts/selector_final_turn.md +19 -0
  190. vidbyte_cli/services/ensemble/prompts/selector_round_turn.md +17 -0
  191. vidbyte_cli/services/ensemble/prompts/selector_system.md +89 -0
  192. vidbyte_cli/services/ensemble/runner.py +30 -0
  193. vidbyte_cli/services/ensemble/sdk.py +134 -0
  194. vidbyte_cli/services/ensemble/service.py +233 -0
  195. vidbyte_cli/services/ensemble/settings.py +60 -0
  196. vidbyte_cli/services/persistence/__init__.py +6 -0
  197. vidbyte_cli/services/persistence/prompts/__init__.py +6 -0
  198. vidbyte_cli/services/persistence/prompts/library.py +46 -0
  199. vidbyte_cli/services/persistence/prompts/persistence_system.md +1 -0
  200. vidbyte_cli/services/persistence/prompts/persistence_turn.md +3 -0
  201. vidbyte_cli/services/persistence/runner.py +35 -0
  202. vidbyte_cli/services/persistence/session.py +116 -0
  203. vidbyte_cli/services/suggestions/__init__.py +7 -0
  204. vidbyte_cli/services/suggestions/categories.py +270 -0
  205. vidbyte_cli/services/suggestions/context.py +222 -0
  206. vidbyte_cli/services/suggestions/extra_compute.py +53 -0
  207. vidbyte_cli/services/suggestions/handoff.py +173 -0
  208. vidbyte_cli/services/suggestions/message_tools.py +47 -0
  209. vidbyte_cli/services/suggestions/project.py +253 -0
  210. vidbyte_cli/services/suggestions/prompts/__init__.py +1 -0
  211. vidbyte_cli/services/suggestions/prompts/categories/__init__.py +3 -0
  212. vidbyte_cli/services/suggestions/prompts/categories/adjacent_opportunity.md +37 -0
  213. vidbyte_cli/services/suggestions/prompts/categories/alternative.md +38 -0
  214. vidbyte_cli/services/suggestions/prompts/categories/big_bets.md +37 -0
  215. vidbyte_cli/services/suggestions/prompts/categories/bottleneck.md +38 -0
  216. vidbyte_cli/services/suggestions/prompts/categories/business_growth.md +38 -0
  217. vidbyte_cli/services/suggestions/prompts/categories/completion.md +37 -0
  218. vidbyte_cli/services/suggestions/prompts/categories/continuation.md +37 -0
  219. vidbyte_cli/services/suggestions/prompts/categories/coordination.md +37 -0
  220. vidbyte_cli/services/suggestions/prompts/categories/creative_exploration.md +37 -0
  221. vidbyte_cli/services/suggestions/prompts/categories/cross_domain.md +37 -0
  222. vidbyte_cli/services/suggestions/prompts/categories/delegation.md +37 -0
  223. vidbyte_cli/services/suggestions/prompts/categories/experiment.md +37 -0
  224. vidbyte_cli/services/suggestions/prompts/categories/feedback.md +37 -0
  225. vidbyte_cli/services/suggestions/prompts/categories/goal_clarification.md +37 -0
  226. vidbyte_cli/services/suggestions/prompts/categories/immediate_next_steps.md +37 -0
  227. vidbyte_cli/services/suggestions/prompts/categories/investigation.md +37 -0
  228. vidbyte_cli/services/suggestions/prompts/categories/learning.md +37 -0
  229. vidbyte_cli/services/suggestions/prompts/categories/leverage.md +37 -0
  230. vidbyte_cli/services/suggestions/prompts/categories/long_term_directions.md +37 -0
  231. vidbyte_cli/services/suggestions/prompts/categories/preparation.md +37 -0
  232. vidbyte_cli/services/suggestions/prompts/categories/prerequisite.md +37 -0
  233. vidbyte_cli/services/suggestions/prompts/categories/prioritization.md +37 -0
  234. vidbyte_cli/services/suggestions/prompts/categories/product_experience.md +37 -0
  235. vidbyte_cli/services/suggestions/prompts/categories/quick_wins.md +37 -0
  236. vidbyte_cli/services/suggestions/prompts/categories/reframing.md +37 -0
  237. vidbyte_cli/services/suggestions/prompts/categories/risk_prevention.md +37 -0
  238. vidbyte_cli/services/suggestions/prompts/categories/simplification.md +37 -0
  239. vidbyte_cli/services/suggestions/prompts/categories/stop_or_defer.md +37 -0
  240. vidbyte_cli/services/suggestions/prompts/categories/strategy.md +37 -0
  241. vidbyte_cli/services/suggestions/prompts/categories/technical_possibilities.md +37 -0
  242. vidbyte_cli/services/suggestions/prompts/categories/verification.md +37 -0
  243. vidbyte_cli/services/suggestions/prompts/critic.md +209 -0
  244. vidbyte_cli/services/suggestions/prompts/critic_message.md +8 -0
  245. vidbyte_cli/services/suggestions/prompts/generator.md +64 -0
  246. vidbyte_cli/services/suggestions/prompts/library.py +72 -0
  247. vidbyte_cli/services/suggestions/prompts/message_generator_argument.md +1 -0
  248. vidbyte_cli/services/suggestions/prompts/message_generator_tool.md +1 -0
  249. vidbyte_cli/services/suggestions/prompts/message_parent_argument.md +1 -0
  250. vidbyte_cli/services/suggestions/prompts/message_parent_tool.md +1 -0
  251. vidbyte_cli/services/suggestions/prompts/message_receipt.md +1 -0
  252. vidbyte_cli/services/suggestions/prompts/revision.md +8 -0
  253. vidbyte_cli/services/suggestions/sdk.py +228 -0
  254. vidbyte_cli/services/suggestions/selection.py +78 -0
  255. vidbyte_cli/services/suggestions/service.py +540 -0
  256. vidbyte_cli/types/__init__.py +0 -0
  257. vidbyte_cli/types/api.py +101 -0
  258. vidbyte_cli/types/attachments.py +79 -0
  259. vidbyte_cli/types/ensemble.py +189 -0
  260. vidbyte_cli/types/provider.py +62 -0
  261. vidbyte_cli/types/research.py +260 -0
  262. vidbyte_cli/types/runtime.py +1098 -0
  263. vidbyte_cli/types/suggestions.py +518 -0
  264. vidbyte_cli-0.1.0.dist-info/METADATA +335 -0
  265. vidbyte_cli-0.1.0.dist-info/RECORD +269 -0
  266. vidbyte_cli-0.1.0.dist-info/WHEEL +5 -0
  267. vidbyte_cli-0.1.0.dist-info/entry_points.txt +2 -0
  268. vidbyte_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  269. vidbyte_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,222 @@
1
+ """Builds the bounded context snapshot for one suggestion run.
2
+
3
+ Only explicitly supplied files are read; nothing scans the repository or the
4
+ Codex history. File contents are treated as task data, never as instructions,
5
+ and omissions or truncations are reported in the manifest rather than hidden.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import json
12
+ from dataclasses import dataclass, field
13
+ from pathlib import Path
14
+ from typing import Literal
15
+
16
+ from ...types.suggestions import (
17
+ MAX_CONTEXT_CHARS,
18
+ ContextManifestEntry,
19
+ SuggestionContextItem,
20
+ )
21
+
22
+ ManifestStatus = Literal["included", "truncated", "omitted", "not_supplied"]
23
+
24
+ _MAX_FILE_CHARS = MAX_CONTEXT_CHARS
25
+ _MAX_TOTAL_CHARS = MAX_CONTEXT_CHARS
26
+ _SUPPORTED_SUFFIXES = {".txt", ".md", ".json"}
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class ContextSnapshot:
31
+ """Resolved items plus the manifest and warnings the result must carry."""
32
+
33
+ items: tuple[SuggestionContextItem, ...]
34
+ manifest: tuple[ContextManifestEntry, ...]
35
+ warnings: tuple[str, ...]
36
+ by_ref: dict[str, str]
37
+
38
+
39
+ @dataclass
40
+ class _ContextAccumulator:
41
+ """Mutable construction state kept private to one context build."""
42
+
43
+ items: list[SuggestionContextItem] = field(default_factory=list)
44
+ manifest: list[ContextManifestEntry] = field(default_factory=list)
45
+ warnings: list[str] = field(default_factory=list)
46
+ total: int = 0
47
+ counter: int = 0
48
+
49
+ def next_ref(self) -> str:
50
+ self.counter += 1
51
+ return f"ctx-{self.counter:03d}"
52
+
53
+
54
+ class SuggestionContextBuilder:
55
+ """Reads explicit inputs into stable refs with hashing and limit enforcement."""
56
+
57
+ def __init__(self) -> None:
58
+ # No state across runs; the counter below is per-build and starts at zero.
59
+ pass
60
+
61
+ def build(self, fields: dict[str, tuple[str, ...]], files: tuple[Path, ...]) -> ContextSnapshot:
62
+ # Resolves text fields first, then files, so refs stay deterministic per run.
63
+ return self._assemble(fields, files)
64
+
65
+ def _assemble(
66
+ self, fields: dict[str, tuple[str, ...]], files: tuple[Path, ...]
67
+ ) -> ContextSnapshot:
68
+ # Keeps the orchestration small while each helper owns one input shape.
69
+ state = _ContextAccumulator()
70
+ self._append_fields(state, fields)
71
+ self._append_files(state, files)
72
+ if self._contradicts(fields):
73
+ state.warnings.append(
74
+ "Supplied context marks one statement both completed "
75
+ "and in-progress; both are preserved."
76
+ )
77
+ return ContextSnapshot(
78
+ items=tuple(state.items),
79
+ manifest=tuple(state.manifest),
80
+ warnings=tuple(state.warnings),
81
+ by_ref={item.ref: item.content for item in state.items},
82
+ )
83
+
84
+ def _append_fields(
85
+ self, state: _ContextAccumulator, fields: dict[str, tuple[str, ...]]
86
+ ) -> None:
87
+ # Preserves the caller's field order, which makes refs and manifests stable.
88
+ for kind, values in fields.items():
89
+ for value in values:
90
+ self._append_field(state, kind, value)
91
+
92
+ def _append_field(self, state: _ContextAccumulator, kind: str, value: str) -> None:
93
+ # Bounds repeated flag values before Pydantic sees them.
94
+ original = value.strip()
95
+ if not original:
96
+ return
97
+ ref = state.next_ref()
98
+ body, status, note = self._bound_text(original, f"{kind} context")
99
+ if not self._fits(state, len(body)):
100
+ state.manifest.append(self._manifest_entry(ref, kind, f"flag:{kind}", body, "omitted"))
101
+ state.warnings.append(f"Omitted {kind} context: total context budget exceeded.")
102
+ return
103
+ state.items.append(
104
+ SuggestionContextItem(
105
+ ref=ref,
106
+ kind=kind,
107
+ label=kind,
108
+ description="Caller-supplied context value.",
109
+ content=body,
110
+ source=f"flag:{kind}",
111
+ caller_supplied=True,
112
+ )
113
+ )
114
+ state.manifest.append(self._manifest_entry(ref, kind, f"flag:{kind}", body, status))
115
+ if status == "truncated":
116
+ state.warnings.append(note)
117
+ state.total += len(body)
118
+
119
+ def _append_files(self, state: _ContextAccumulator, files: tuple[Path, ...]) -> None:
120
+ # Reads files only after all repeated flag values have been accounted for.
121
+ for path in files:
122
+ self._append_file(state, path)
123
+
124
+ def _append_file(self, state: _ContextAccumulator, path: Path) -> None:
125
+ # Rejects invalid files before adding an item or charging the total budget.
126
+ ref = state.next_ref()
127
+ body, status, note = self._read_one(path)
128
+ if not body:
129
+ state.manifest.append(self._manifest_entry(ref, "file", str(path), body, "omitted"))
130
+ state.warnings.append(f"Omitted empty context file: {path}.")
131
+ return
132
+ if not self._fits(state, len(body)):
133
+ state.manifest.append(self._manifest_entry(ref, "file", str(path), body, "omitted"))
134
+ state.warnings.append(f"Omitted {path}: total context budget exceeded.")
135
+ return
136
+ state.items.append(
137
+ SuggestionContextItem(
138
+ ref=ref,
139
+ kind="file",
140
+ label=path.name,
141
+ description="Caller-supplied file content.",
142
+ content=body,
143
+ source=str(path),
144
+ caller_supplied=False,
145
+ )
146
+ )
147
+ entry_status: ManifestStatus = "truncated" if status == "truncated" else "included"
148
+ state.manifest.append(self._manifest_entry(ref, "file", str(path), body, entry_status))
149
+ if status == "truncated":
150
+ state.warnings.append(note)
151
+ state.total += len(body)
152
+
153
+ def _fits(self, state: _ContextAccumulator, chars: int) -> bool:
154
+ # Keeps the aggregate body budget independent from prompt and metadata text.
155
+ return state.total + chars <= _MAX_TOTAL_CHARS
156
+
157
+ def _manifest_entry(
158
+ self,
159
+ ref: str,
160
+ kind: str,
161
+ source: str,
162
+ body: str,
163
+ status: ManifestStatus,
164
+ ) -> ContextManifestEntry:
165
+ # Hashes the exact bounded body that the agent will receive.
166
+ return ContextManifestEntry(
167
+ ref=ref,
168
+ kind=kind,
169
+ source=source,
170
+ chars=len(body),
171
+ sha256=hashlib.sha256(body.encode("utf-8")).hexdigest()[:16],
172
+ status=status,
173
+ )
174
+
175
+ def _bound_text(self, text: str, label: str) -> tuple[str, ManifestStatus, str]:
176
+ # Applies the same generous per-item cap to repeated flag values as to files.
177
+ if len(text) <= _MAX_FILE_CHARS:
178
+ return text, "included", ""
179
+ kept = text[:_MAX_FILE_CHARS]
180
+ return kept, "truncated", f"Truncated {label} to {_MAX_FILE_CHARS} chars."
181
+
182
+ def _read_one(self, path: Path) -> tuple[str, ManifestStatus, str]:
183
+ # Rejects directories, missing paths, and non-text shapes before reading.
184
+ if path.is_dir():
185
+ raise ValueError(f"context path is a directory: {path}")
186
+ if not path.exists():
187
+ raise ValueError(f"context file not found: {path}")
188
+ if path.suffix.lower() not in _SUPPORTED_SUFFIXES:
189
+ raise ValueError(f"unsupported context file type: {path}")
190
+ try:
191
+ text = path.read_text(encoding="utf-8-sig")
192
+ except OSError as error:
193
+ raise ValueError(f"unreadable context file: {path}") from error
194
+ if path.suffix.lower() == ".json":
195
+ try:
196
+ parsed = json.loads(text)
197
+ except json.JSONDecodeError as error:
198
+ raise ValueError(f"malformed JSON context file: {path}") from error
199
+ if (
200
+ isinstance(parsed, dict)
201
+ and "schema_version" in parsed
202
+ and parsed["schema_version"] != 1
203
+ ):
204
+ raise ValueError(f"unsupported schema version in: {path}")
205
+ text = json.dumps(parsed, indent=2, sort_keys=True)
206
+ if len(text) > _MAX_FILE_CHARS:
207
+ kept = text[:_MAX_FILE_CHARS]
208
+ return kept, "truncated", f"Truncated {path} to {_MAX_FILE_CHARS} chars."
209
+ return text, "included", ""
210
+
211
+ def _contradicts(self, fields: dict[str, tuple[str, ...]]) -> bool:
212
+ # Surfaces exact-statement overlap between completed and in-progress lists.
213
+ completed = {
214
+ value.strip().lower() for value in fields.get("completed", ()) if value.strip()
215
+ }
216
+ in_progress = {
217
+ value.strip().lower() for value in fields.get("in_progress", ()) if value.strip()
218
+ }
219
+ return bool(completed & in_progress)
220
+
221
+
222
+ __all__ = ["ContextSnapshot", "SuggestionContextBuilder"]
@@ -0,0 +1,53 @@
1
+ """Runs one isolated generator context per selected category in parallel."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import math
7
+ from collections.abc import Awaitable, Callable
8
+ from typing import Literal
9
+
10
+ from ...types.suggestions import (
11
+ SuggestionAgentContext,
12
+ SuggestionCandidateBatch,
13
+ SuggestionDraft,
14
+ SuggestionRequest,
15
+ )
16
+ from .categories import SuggestionCategories
17
+ from .prompts.library import SuggestionPrompts
18
+
19
+ AgentCall = Callable[
20
+ [Literal["generator", "critic"], str, SuggestionAgentContext, str | None],
21
+ Awaitable[SuggestionCandidateBatch],
22
+ ]
23
+
24
+
25
+ class ExtraComputeService:
26
+ """Fans out focused category windows and combines their typed drafts stably."""
27
+
28
+ def __init__(self, categories: SuggestionCategories, prompts: SuggestionPrompts) -> None:
29
+ self._categories = categories
30
+ self._prompts = prompts
31
+
32
+ async def generate(
33
+ self,
34
+ request: SuggestionRequest,
35
+ category_ids: tuple[str, ...],
36
+ pool_size: int,
37
+ call: AgentCall,
38
+ ) -> tuple[SuggestionDraft, ...]:
39
+ per_category = max(1, math.ceil(pool_size / len(category_ids)))
40
+
41
+ async def run_one(category_id: str) -> SuggestionCandidateBatch:
42
+ context = SuggestionAgentContext.for_stage(
43
+ request.context, self._categories.prompt_section((category_id,))
44
+ )
45
+ prompt = self._prompts.generator_turn(request.goal, per_category)
46
+ return await call("generator", prompt, context, None)
47
+
48
+ batches = await asyncio.gather(*(run_one(category) for category in category_ids))
49
+ drafts = tuple(draft for batch in batches for draft in batch.ideas)
50
+ return drafts[:pool_size]
51
+
52
+
53
+ __all__ = ["AgentCall", "ExtraComputeService"]
@@ -0,0 +1,173 @@
1
+ """Builds deterministic execution handoffs from validated suggestion fields."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ...types.suggestions import (
6
+ SuggestionDraft,
7
+ SuggestionHandoff,
8
+ SuggestionHandoffEvidence,
9
+ SuggestionIdea,
10
+ SuggestionRequest,
11
+ )
12
+
13
+
14
+ class SuggestionHandoffBuilder:
15
+ """Builds one self-contained action packet without another model call."""
16
+
17
+ def build(self, idea: SuggestionIdea, request: SuggestionRequest) -> SuggestionHandoff:
18
+ """Rebuild a handoff from the idea fields so the prompt cannot drift."""
19
+ return self._build(
20
+ idea_id=idea.id,
21
+ revision=idea.revision,
22
+ title=idea.title,
23
+ summary=idea.summary,
24
+ actions=idea.suggested_actions,
25
+ decisions=idea.decision_points,
26
+ considerations=idea.considerations,
27
+ evidence_refs=idea.evidence_refs,
28
+ completion=idea.completion_criteria,
29
+ dependencies=idea.dependencies,
30
+ why_now=idea.why_now,
31
+ request=request,
32
+ )
33
+
34
+ def build_draft(
35
+ self, draft: SuggestionDraft, request: SuggestionRequest, idea_id: str, revision: int
36
+ ) -> SuggestionHandoff:
37
+ """Build the provisional handoff used when the critic sees generated candidates."""
38
+ return self._build(
39
+ idea_id=idea_id,
40
+ revision=revision,
41
+ title=draft.title,
42
+ summary=draft.summary,
43
+ actions=draft.suggested_actions,
44
+ decisions=draft.decision_points,
45
+ considerations=draft.considerations,
46
+ evidence_refs=draft.evidence_refs,
47
+ completion=draft.completion_criteria,
48
+ dependencies=draft.dependencies,
49
+ why_now=draft.why_now,
50
+ request=request,
51
+ )
52
+
53
+ def _build(
54
+ self,
55
+ *,
56
+ idea_id: str,
57
+ revision: int,
58
+ title: str,
59
+ summary: str,
60
+ actions: tuple[str, ...],
61
+ decisions: tuple[str, ...],
62
+ considerations: tuple[str, ...],
63
+ evidence_refs: tuple[str, ...],
64
+ completion: str,
65
+ dependencies: tuple[str, ...],
66
+ why_now: str,
67
+ request: SuggestionRequest,
68
+ ) -> SuggestionHandoff:
69
+ handoff = SuggestionHandoff(
70
+ idea_id=idea_id,
71
+ idea_revision=revision,
72
+ original_goal=request.goal,
73
+ title=title,
74
+ summary=summary,
75
+ suggested_actions=actions,
76
+ decisions_along_way=decisions,
77
+ considerations=considerations,
78
+ evidence=self._evidence(evidence_refs, request, why_now),
79
+ completion_checks=(completion,),
80
+ dependencies=dependencies,
81
+ warnings=self._warnings(request),
82
+ forbidden_actions=self._context_values(request, "forbidden"),
83
+ stop_conditions=(
84
+ "The completion checks pass.",
85
+ "Evidence contradicts the proposed action.",
86
+ "Required authority is missing.",
87
+ "Required prerequisites are missing.",
88
+ ),
89
+ return_report=(
90
+ "Report actions taken, decisions made, evidence observed, completion checks, "
91
+ "and unresolved follow-ups to the calling agent."
92
+ ),
93
+ execution_prompt="pending",
94
+ )
95
+ return handoff.model_copy(update={"execution_prompt": self.render_prompt(handoff)})
96
+
97
+ def render_prompt(self, handoff: SuggestionHandoff) -> str:
98
+ """Render only validated fields in a stable order."""
99
+ lines = [
100
+ f"Goal: {handoff.original_goal}",
101
+ f"Suggested action: {handoff.title}",
102
+ f"Summary: {handoff.summary}",
103
+ "Actions:",
104
+ *self._lines(handoff.suggested_actions),
105
+ "Decisions along the way:",
106
+ *self._lines(handoff.decisions_along_way),
107
+ "Things to consider:",
108
+ *self._lines(handoff.considerations),
109
+ "Evidence:",
110
+ *self._evidence_lines(handoff.evidence),
111
+ "Completion checks:",
112
+ *self._lines(handoff.completion_checks),
113
+ "Dependencies:",
114
+ *self._lines(handoff.dependencies),
115
+ "Warnings:",
116
+ *self._lines(handoff.warnings),
117
+ "Forbidden actions:",
118
+ *self._lines(handoff.forbidden_actions),
119
+ "Stop when:",
120
+ *self._lines(handoff.stop_conditions),
121
+ f"Authority: {handoff.authority}",
122
+ f"Report: {handoff.return_report}",
123
+ ]
124
+ return "\n".join(lines)
125
+
126
+ def _evidence(
127
+ self, refs: tuple[str, ...], request: SuggestionRequest, why_now: str
128
+ ) -> tuple[SuggestionHandoffEvidence, ...]:
129
+ items_by_ref = {item.ref: item for item in request.context_items}
130
+ return tuple(
131
+ SuggestionHandoffEvidence(
132
+ ref=ref,
133
+ source=items_by_ref[ref].source,
134
+ content=items_by_ref[ref].content[:4096],
135
+ relevance=f"Supports why this action matters now: {why_now}",
136
+ )
137
+ for ref in refs
138
+ if ref in items_by_ref
139
+ )
140
+
141
+ def _warnings(self, request: SuggestionRequest) -> tuple[str, ...]:
142
+ warnings = list(request.context_warnings)
143
+ for item in request.context_items:
144
+ if item.kind == "mistake":
145
+ parts = tuple(part.strip() for part in item.content.split("|"))
146
+ if len(parts) == 3 and all(parts):
147
+ warnings.append(
148
+ f"Past mistake lesson: {parts[2]} (failure: {parts[0]}; cause: {parts[1]})"
149
+ )
150
+ else:
151
+ warnings.append(item.content)
152
+ elif item.kind == "risk" or (
153
+ item.kind == "hypothesis" and item.content.lower().startswith("low:")
154
+ ):
155
+ warnings.append(item.content)
156
+ return tuple(dict.fromkeys(warnings))
157
+
158
+ def _context_values(self, request: SuggestionRequest, kind: str) -> tuple[str, ...]:
159
+ return tuple(item.content for item in request.context_items if item.kind == kind)
160
+
161
+ def _lines(self, values: tuple[str, ...]) -> list[str]:
162
+ return [f"- {value}" for value in values] or ["- none supplied"]
163
+
164
+ def _evidence_lines(self, evidence: tuple[SuggestionHandoffEvidence, ...]) -> list[str]:
165
+ if not evidence:
166
+ return ["- none cited"]
167
+ return [
168
+ f"- [{item.ref}] {item.content} (source: {item.source}; {item.relevance})"
169
+ for item in evidence
170
+ ]
171
+
172
+
173
+ __all__ = ["SuggestionHandoffBuilder"]
@@ -0,0 +1,47 @@
1
+ """Stop-and-message tools that let one suggestion agent end its turn with a message.
2
+
3
+ This module owns the CLI side of each tool: its model-facing text, the message
4
+ validation, and the stop signal the service waits on. It never imports the SDK;
5
+ `sdk.py` wraps each tool as an SDK `BaseTool` so Codex can call it.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from typing import Literal
12
+
13
+ from ...types.suggestions import MAX_AGENT_MESSAGE_CHARS
14
+ from .prompts.library import SuggestionPrompts
15
+
16
+ MessageToolName = Literal["message_parent", "message_generator"]
17
+
18
+
19
+ class SuggestionMessageTool:
20
+ """One agent's route to another agent: the first valid message stops its turn."""
21
+
22
+ def __init__(self, name: MessageToolName, prompts: SuggestionPrompts) -> None:
23
+ # One instance serves exactly one agent, so its message and signal are never shared.
24
+ self.name = name
25
+ self.description = prompts.message_tool(name)
26
+ self.message_description = prompts.message_argument(name)
27
+ self._receipt = prompts.message_receipt()
28
+ self.message: str | None = None
29
+ self.stopped = asyncio.Event()
30
+
31
+ def deliver(self, message: object) -> str:
32
+ """Record the model's message, signal the stop, and return the tool receipt."""
33
+ if not isinstance(message, str) or not message.strip():
34
+ raise ValueError(f"{self.name} requires a non-empty message string.")
35
+ text = message.strip()
36
+ if len(text) > MAX_AGENT_MESSAGE_CHARS:
37
+ raise ValueError(
38
+ f"{self.name} messages must be at most {MAX_AGENT_MESSAGE_CHARS} characters."
39
+ )
40
+ # A second call in the same turn cannot replace the message already being delivered.
41
+ if self.message is None:
42
+ self.message = text
43
+ self.stopped.set()
44
+ return self._receipt
45
+
46
+
47
+ __all__ = ["MessageToolName", "SuggestionMessageTool"]