metaproc 0.2.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 (256) hide show
  1. metaproc/__init__.py +1 -0
  2. metaproc/__main__.py +6 -0
  3. metaproc/adapters/__init__.py +0 -0
  4. metaproc/adapters/base.py +472 -0
  5. metaproc/adapters/claude_code.py +1487 -0
  6. metaproc/adapters/cli_version.py +133 -0
  7. metaproc/adapters/codex.py +850 -0
  8. metaproc/adapters/gemini.py +281 -0
  9. metaproc/adapters/pi_cli.py +415 -0
  10. metaproc/adapters/registry.py +102 -0
  11. metaproc/agent_errors.py +126 -0
  12. metaproc/cli.py +142 -0
  13. metaproc/cloud/__init__.py +0 -0
  14. metaproc/cloud/gcp/__init__.py +0 -0
  15. metaproc/cloud/gcp/batch_backend.py +722 -0
  16. metaproc/cloud/gcp/billing.py +92 -0
  17. metaproc/cloud/gcp/container_bootstrap.py +546 -0
  18. metaproc/cloud/gcp/dispatch_artifacts.py +222 -0
  19. metaproc/cloud/gcp/gcp_credentials.py +184 -0
  20. metaproc/cloud/gcp/gcp_run_dispatch.py +159 -0
  21. metaproc/cloud/gcp/gcp_run_entrypoint.py +97 -0
  22. metaproc/cloud/gcp/gcp_run_logs.py +223 -0
  23. metaproc/cloud/gcp/orchestrator_dispatch.py +389 -0
  24. metaproc/cloud/gcp/orchestrator_entrypoint.py +224 -0
  25. metaproc/cloud/gcp/resolve_token.py +38 -0
  26. metaproc/cloud/gcp/worker_dispatch.py +901 -0
  27. metaproc/cloud/gcp/worker_entrypoint.py +255 -0
  28. metaproc/commands/__init__.py +0 -0
  29. metaproc/commands/auth.py +2205 -0
  30. metaproc/commands/auth_check.py +896 -0
  31. metaproc/commands/check_handlers.py +112 -0
  32. metaproc/commands/check_headers.py +127 -0
  33. metaproc/commands/claude_auth.py +357 -0
  34. metaproc/commands/codex_auth.py +440 -0
  35. metaproc/commands/compact_logs.py +118 -0
  36. metaproc/commands/compare.py +124 -0
  37. metaproc/commands/compare_matrix.py +124 -0
  38. metaproc/commands/compare_trace.py +211 -0
  39. metaproc/commands/deps.py +171 -0
  40. metaproc/commands/env.py +175 -0
  41. metaproc/commands/gcp.py +2637 -0
  42. metaproc/commands/gcp_run.py +351 -0
  43. metaproc/commands/gzip_text.py +640 -0
  44. metaproc/commands/help.py +67 -0
  45. metaproc/commands/helpers.py +260 -0
  46. metaproc/commands/kill.py +246 -0
  47. metaproc/commands/liveness_watch.py +293 -0
  48. metaproc/commands/override.py +118 -0
  49. metaproc/commands/plan.py +129 -0
  50. metaproc/commands/pool.py +1723 -0
  51. metaproc/commands/probe_tool_use.py +293 -0
  52. metaproc/commands/pulse.py +286 -0
  53. metaproc/commands/resource_report.py +212 -0
  54. metaproc/commands/resources.py +40 -0
  55. metaproc/commands/resume_daemon.py +264 -0
  56. metaproc/commands/run_manifest.py +44 -0
  57. metaproc/commands/run_parallel.py +2161 -0
  58. metaproc/commands/run_process.py +3176 -0
  59. metaproc/commands/run_step.py +598 -0
  60. metaproc/commands/searches.py +122 -0
  61. metaproc/commands/skill.py +103 -0
  62. metaproc/commands/softschema.py +206 -0
  63. metaproc/commands/ssh_utils.py +115 -0
  64. metaproc/commands/stats.py +83 -0
  65. metaproc/commands/status.py +619 -0
  66. metaproc/commands/tail.py +121 -0
  67. metaproc/commands/tools.py +153 -0
  68. metaproc/commands/trace.py +375 -0
  69. metaproc/commands/validate.py +224 -0
  70. metaproc/commands/variants.py +144 -0
  71. metaproc/commands/wait.py +65 -0
  72. metaproc/commands/write_usage.py +102 -0
  73. metaproc/config/__init__.py +5 -0
  74. metaproc/config/env_enum.py +243 -0
  75. metaproc/config/env_vars.py +415 -0
  76. metaproc/config/providers.py +198 -0
  77. metaproc/console.py +48 -0
  78. metaproc/data/execution-profiles.default.yaml +259 -0
  79. metaproc/data/pi-models.default.json +63 -0
  80. metaproc/data/pricing.md +690 -0
  81. metaproc/dispatch/__init__.py +8 -0
  82. metaproc/dispatch/auth_pool_flags.py +212 -0
  83. metaproc/dispatch/auth_usage.py +745 -0
  84. metaproc/dispatch/auth_usage_render.py +271 -0
  85. metaproc/dispatch/credential_pool.py +1759 -0
  86. metaproc/dispatch/known_bugs.py +226 -0
  87. metaproc/dispatch/orchestrator_payload.py +151 -0
  88. metaproc/dispatch/pool_dispatch.py +742 -0
  89. metaproc/dispatch/pool_status.py +349 -0
  90. metaproc/dispatch/preflight.py +534 -0
  91. metaproc/dispatch/repo_sync_payload.py +123 -0
  92. metaproc/dispatch/retry_later.py +206 -0
  93. metaproc/dispatch/secret_refs.py +145 -0
  94. metaproc/dispatch/slot_coordinator.py +720 -0
  95. metaproc/dispatch/worker_payload.py +131 -0
  96. metaproc/docs/__init__.py +40 -0
  97. metaproc/docs/metaproc-concepts-and-principles.md +988 -0
  98. metaproc/docs/metaproc-developer-guide.md +150 -0
  99. metaproc/docs/metaproc-operator-reference.md +670 -0
  100. metaproc/engine/__init__.py +0 -0
  101. metaproc/engine/build_plan.py +944 -0
  102. metaproc/engine/code_handler.py +154 -0
  103. metaproc/engine/condition.py +54 -0
  104. metaproc/engine/dep_state.py +360 -0
  105. metaproc/engine/discovery.py +181 -0
  106. metaproc/engine/graph.py +163 -0
  107. metaproc/engine/input_validation.py +197 -0
  108. metaproc/engine/lane_expand.py +191 -0
  109. metaproc/engine/parse_dispatch.py +125 -0
  110. metaproc/engine/pathing.py +193 -0
  111. metaproc/engine/placeholders.py +290 -0
  112. metaproc/engine/preflight.py +404 -0
  113. metaproc/engine/process_scope.py +100 -0
  114. metaproc/engine/resource_hierarchy.py +226 -0
  115. metaproc/engine/resource_rollup.py +815 -0
  116. metaproc/engine/retry.py +323 -0
  117. metaproc/engine/run_id.py +51 -0
  118. metaproc/engine/run_status.py +761 -0
  119. metaproc/engine/runtime.py +317 -0
  120. metaproc/engine/validation.py +328 -0
  121. metaproc/engine/write_boundary.py +423 -0
  122. metaproc/engine/yaml_repair.py +195 -0
  123. metaproc/errors.py +29 -0
  124. metaproc/io/__init__.py +71 -0
  125. metaproc/io/claimed_items.py +141 -0
  126. metaproc/io/digests.py +36 -0
  127. metaproc/io/dispatch_manifest.py +100 -0
  128. metaproc/io/frontmatter.py +186 -0
  129. metaproc/io/gz_io.py +228 -0
  130. metaproc/io/mkdir_lock.py +224 -0
  131. metaproc/io/orchestrator_lease.py +408 -0
  132. metaproc/io/overrides.py +188 -0
  133. metaproc/io/safe_archive.py +97 -0
  134. metaproc/io/schema_token.py +175 -0
  135. metaproc/io/state_io.py +294 -0
  136. metaproc/io/templating.py +122 -0
  137. metaproc/logutil/__init__.py +1 -0
  138. metaproc/logutil/compaction.py +630 -0
  139. metaproc/logutil/log_path_owner.py +174 -0
  140. metaproc/logutil/parsing.py +1819 -0
  141. metaproc/logutil/resource_event_extract.py +397 -0
  142. metaproc/logutil/resource_events.py +89 -0
  143. metaproc/logutil/throttling.py +175 -0
  144. metaproc/logutil/tool_failures.py +90 -0
  145. metaproc/logutil/tool_spans.py +270 -0
  146. metaproc/logutil/usage.py +644 -0
  147. metaproc/metabrowser_plugin/README.md +125 -0
  148. metaproc/metabrowser_plugin/__init__.py +31 -0
  149. metaproc/metabrowser_plugin/charts.py +150 -0
  150. metaproc/metabrowser_plugin/log_adapters.py +106 -0
  151. metaproc/metabrowser_plugin/plugin/domain_views.css +413 -0
  152. metaproc/metabrowser_plugin/plugin/domain_views.js +1680 -0
  153. metaproc/metabrowser_plugin/plugin/elk.bundled.js +6540 -0
  154. metaproc/metabrowser_plugin/plugin/elkjs-license.txt +264 -0
  155. metaproc/metabrowser_plugin/plugin/index.js +363 -0
  156. metaproc/metabrowser_plugin/plugin/manifest.toml +199 -0
  157. metaproc/metabrowser_plugin/plugin/styles.css +37 -0
  158. metaproc/metabrowser_plugin/plugin/viz.css +1032 -0
  159. metaproc/metabrowser_plugin/plugin/viz.js +2218 -0
  160. metaproc/metabrowser_plugin/sidekick.py +376 -0
  161. metaproc/models/__init__.py +0 -0
  162. metaproc/models/authored.py +541 -0
  163. metaproc/models/execution_profile.py +337 -0
  164. metaproc/models/invocation.py +48 -0
  165. metaproc/models/lane.py +217 -0
  166. metaproc/models/node_ids.py +52 -0
  167. metaproc/models/plan.py +123 -0
  168. metaproc/models/plan_bundle.py +36 -0
  169. metaproc/models/pricing.py +58 -0
  170. metaproc/models/qa.py +120 -0
  171. metaproc/models/resources.py +322 -0
  172. metaproc/models/run_manifest.py +137 -0
  173. metaproc/models/runtime.py +136 -0
  174. metaproc/models/usage.py +265 -0
  175. metaproc/models/viz.py +342 -0
  176. metaproc/osutils/__init__.py +1 -0
  177. metaproc/osutils/file_extensions.py +172 -0
  178. metaproc/osutils/ignore_filter.py +145 -0
  179. metaproc/osutils/memory_pressure.py +382 -0
  180. metaproc/osutils/psutil_sampler.py +183 -0
  181. metaproc/osutils/resource_context.py +392 -0
  182. metaproc/output.py +109 -0
  183. metaproc/paths.py +446 -0
  184. metaproc/plugins/__init__.py +0 -0
  185. metaproc/plugins/discovery.py +68 -0
  186. metaproc/plugins/protocol.py +115 -0
  187. metaproc/plugins/registry.py +118 -0
  188. metaproc/py.typed +0 -0
  189. metaproc/resource_docs.py +46 -0
  190. metaproc/runpool/README.md +32 -0
  191. metaproc/runpool/__init__.py +58 -0
  192. metaproc/runpool/backend.py +399 -0
  193. metaproc/runpool/concurrency.py +165 -0
  194. metaproc/runpool/event_models.py +215 -0
  195. metaproc/runpool/event_reader.py +54 -0
  196. metaproc/runpool/events.py +406 -0
  197. metaproc/runpool/host_admission.py +449 -0
  198. metaproc/runpool/kill.py +323 -0
  199. metaproc/runpool/mock_backend.py +108 -0
  200. metaproc/runpool/monitor.py +30 -0
  201. metaproc/runpool/pool.py +1975 -0
  202. metaproc/runpool/process_event_models.py +163 -0
  203. metaproc/runpool/process_events.py +395 -0
  204. metaproc/runpool/registry.py +88 -0
  205. metaproc/runpool/semaphore.py +87 -0
  206. metaproc/runpool/status.py +271 -0
  207. metaproc/runtime/__init__.py +8 -0
  208. metaproc/runtime/code_handler.py +33 -0
  209. metaproc/runtime_paths.py +50 -0
  210. metaproc/settings.py +284 -0
  211. metaproc/skill/__init__.py +7 -0
  212. metaproc/skill/baselines/__init__.py +1 -0
  213. metaproc/skill/baselines/metaproc.md +70 -0
  214. metaproc/skill/builtin.py +62 -0
  215. metaproc/skill/compose.py +59 -0
  216. metaproc/skill/registry.py +52 -0
  217. metaproc/skill/spec.py +40 -0
  218. metaproc/stats/__init__.py +1 -0
  219. metaproc/stats/analysis.py +210 -0
  220. metaproc/stats/engine.py +765 -0
  221. metaproc/stats/formatters.py +319 -0
  222. metaproc/stats/models.py +168 -0
  223. metaproc/stats/path_tally.py +89 -0
  224. metaproc/structure_report.py +324 -0
  225. metaproc/trace/__init__.py +36 -0
  226. metaproc/trace/aggregation.py +163 -0
  227. metaproc/trace/extractors/__init__.py +89 -0
  228. metaproc/trace/extractors/claude_agent.py +520 -0
  229. metaproc/trace/extractors/codex_agent.py +560 -0
  230. metaproc/trace/extractors/common.py +378 -0
  231. metaproc/trace/extractors/gemini_agent.py +520 -0
  232. metaproc/trace/extractors/metaproc_engine.py +347 -0
  233. metaproc/trace/extractors/pi_agent.py +475 -0
  234. metaproc/trace/ids.py +37 -0
  235. metaproc/trace/linker.py +179 -0
  236. metaproc/trace/named_views.py +243 -0
  237. metaproc/trace/quality.py +170 -0
  238. metaproc/trace/runner.py +30 -0
  239. metaproc/trace/schema.py +205 -0
  240. metaproc/trace/store.py +41 -0
  241. metaproc/trace/views.py +237 -0
  242. metaproc/utils/__init__.py +0 -0
  243. metaproc/utils/mtime_cache.py +142 -0
  244. metaproc/viz/__init__.py +21 -0
  245. metaproc/viz/decorators.py +76 -0
  246. metaproc/viz/layout.py +157 -0
  247. metaproc/viz/project.py +529 -0
  248. metaproc/viz/render_html.py +70 -0
  249. metaproc/viz/render_svg.py +212 -0
  250. metaproc/viz_loader.py +132 -0
  251. metaproc-0.2.0.dist-info/METADATA +288 -0
  252. metaproc-0.2.0.dist-info/RECORD +256 -0
  253. metaproc-0.2.0.dist-info/WHEEL +4 -0
  254. metaproc-0.2.0.dist-info/entry_points.txt +8 -0
  255. metaproc-0.2.0.dist-info/licenses/LICENSE +661 -0
  256. metaproc-0.2.0.dist-info/licenses/NOTICE.md +32 -0
metaproc/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """metaproc — Generic process framework for structured multi-step agent workflows."""
metaproc/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m metaproc``."""
2
+
3
+ from metaproc.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
File without changes
@@ -0,0 +1,472 @@
1
+ """Adapter protocol and shared types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any, ClassVar, Literal, Protocol, cast, runtime_checkable
10
+
11
+
12
+ @dataclass
13
+ class AuthStatus:
14
+ """Result of an adapter auth check."""
15
+
16
+ adapter_type: str
17
+ cli_found: bool
18
+ cli_path: str | None
19
+ credentials_found: bool
20
+ auth_mode: str
21
+ details: str
22
+ setup_hint: str
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class ConfigRejection:
27
+ """One adapter-config key rejected during planner validation."""
28
+
29
+ key: str
30
+ reason: str
31
+
32
+
33
+ def resolve_templates(text: str, variables: dict[str, str]) -> str:
34
+ """Resolve {{KEY}} placeholders in text."""
35
+
36
+ def _replace(match: re.Match[str]) -> str:
37
+ key = match.group(1).strip()
38
+ return variables.get(key, match.group(0))
39
+
40
+ return re.sub(r"\{\{(\w+)\}\}", _replace, text)
41
+
42
+
43
+ @runtime_checkable
44
+ class Adapter(Protocol):
45
+ """Translates merged adapter config into a launchable CLI command."""
46
+
47
+ adapter_type: str
48
+ short_name: str
49
+ default_model: str | None
50
+
51
+ def build_command(
52
+ self,
53
+ prompt_file: Path,
54
+ merged_config: dict[str, object],
55
+ variables: dict[str, str],
56
+ ) -> list[str]: ...
57
+
58
+ def prepare_env(
59
+ self,
60
+ env: dict[str, str],
61
+ merged_config: dict[str, object],
62
+ ) -> dict[str, str]: ...
63
+
64
+ def working_directory(self, merged_config: dict[str, object]) -> Path | None: ...
65
+
66
+ def parse_result_event(self, line: str) -> dict[str, object] | None: ...
67
+
68
+ def check_auth(self) -> AuthStatus: ...
69
+
70
+ def auth_info(self) -> str: ...
71
+
72
+ def validate_config(self, merged_config: dict[str, object]) -> list[ConfigRejection]: ...
73
+
74
+ def bootstrap(self, home: Path) -> None:
75
+ """Materialize per-task filesystem state under *home* before
76
+ ``build_command()``. Default implementation is a no-op; adapters
77
+ that need to stage credentials or other on-disk state (e.g.,
78
+ Claude Code's ``~/.claude/.credentials.json`` Vehicle B) override
79
+ this. Called once per worker task after env is populated.
80
+ """
81
+ ...
82
+
83
+
84
+ def parse_jsonl_event(line: str, event_type: str) -> dict[str, object] | None:
85
+ """Parse a JSONL line and return it if its ``type`` matches *event_type*."""
86
+ try:
87
+ event: Any = json.loads(line)
88
+ except (json.JSONDecodeError, ValueError):
89
+ return None
90
+ if isinstance(event, dict) and cast("dict[str, Any]", event).get("type") == event_type:
91
+ return cast("dict[str, object]", event)
92
+ return None
93
+
94
+
95
+ # ─── Auth-pool types (plan-2026-04-21-auth-credential-pool.md §Design) ──────
96
+
97
+
98
+ class FailureSeverity:
99
+ """Dispatch-verdict axis for auth failures (plan §Phase 5).
100
+
101
+ Orthogonal to :attr:`AuthFailureClassification.status`. ``status``
102
+ drives pool transitions (cooling / expired); ``severity`` drives
103
+ dispatch decisions (retry immediately / wait for reset / abort).
104
+
105
+ The two axes disagree exactly when the credential is healthy but
106
+ the dispatch has a different problem — the case known-bug
107
+ detection addresses.
108
+
109
+ Declared as a class with string constants (not a StrEnum) so tests
110
+ and classifiers that build ``AuthFailureClassification`` inline
111
+ don't need a second enum import and plain string comparison /
112
+ serialization works without coercion.
113
+ """
114
+
115
+ RETRY_NOW: ClassVar[str] = "retry-now"
116
+ """Transient; immediate retry is the right move. Network failures,
117
+ upstream 5xx, conn reset. Pool state unchanged; generic
118
+ retry-with-backoff handles it."""
119
+
120
+ RETRY_AFTER_WAIT: ClassVar[str] = "retry-after-wait"
121
+ """Recoverable but requires waiting. Quota / rate-limit family
122
+ — 429, overloaded_error, Anthropic ratelimit headers, Pacific
123
+ reset phrases. Pool marks the label cooling; dispatch either
124
+ fallbacks or waits."""
125
+
126
+ ABORT: ClassVar[str] = "abort"
127
+ """Permanent / operator action required. ``invalid_grant``,
128
+ ``authentication_error``, ``billing_error``, known-bug
129
+ signatures, schema mismatches. Pool marks the label expired when
130
+ it's a credential issue; dispatch fails fast otherwise so
131
+ operators see the bug instead of burning retries on it."""
132
+
133
+
134
+ def default_severity_for_status(status: str) -> str:
135
+ """Map a pool-state status to its default dispatch severity.
136
+
137
+ ``cooling`` → RETRY_AFTER_WAIT (quota will reset).
138
+ ``expired`` → ABORT (operator must re-auth).
139
+ ``ok`` / ``unknown`` → RETRY_NOW (either we're healthy or we
140
+ don't know; default to the less-destructive option and let the
141
+ generic retry classifier own the real decision from there).
142
+ """
143
+ if status == "cooling":
144
+ return FailureSeverity.RETRY_AFTER_WAIT
145
+ if status == "expired":
146
+ return FailureSeverity.ABORT
147
+ return FailureSeverity.RETRY_NOW
148
+
149
+
150
+ @dataclass(frozen=True)
151
+ class AuthFailureClassification:
152
+ """Outcome of an auth-capable adapter's failure classification.
153
+
154
+ Distinct from ``metaproc.engine.retry.FailureClass`` which classifies
155
+ generic run-parallel retry verdicts. The auth classification is
156
+ consumed by the slot coordinator to decide lease transitions and
157
+ fallback; the retry classifier is consumed by
158
+ ``_classify_and_maybe_retry`` to decide whether to reschedule. The
159
+ two are orthogonal: a cooling auth failure is a RETRY verdict that
160
+ also triggers a slot swap before the next attempt.
161
+
162
+ ``cooling_until_ts`` is UTC epoch seconds. ``None`` means cooling
163
+ indefinitely until operator probe; selection treats it as
164
+ ineligible. ``reason`` is free-form for logs and never contains
165
+ the payload.
166
+
167
+ ``severity`` (plan Phase 5) is the dispatch-verdict axis:
168
+ RETRY_NOW / RETRY_AFTER_WAIT / ABORT. ``None`` means "derive from
169
+ status" (see :meth:`effective_severity` and
170
+ :func:`default_severity_for_status`) — older classifiers not yet
171
+ updated for Phase 5 keep working via the status→severity default.
172
+
173
+ ``known_bug_signature`` (plan P5.3) is populated when the failure
174
+ matches a :class:`~metaproc.dispatch.known_bugs.KnownBugSignature`.
175
+ Operators grep logs for this field to aggregate re-occurrences.
176
+ Setting this field always forces ``effective_severity() == ABORT``
177
+ — we don't retry past a known bug.
178
+ """
179
+
180
+ status: Literal["ok", "cooling", "expired", "unknown"]
181
+ cooling_until_ts: int | None = None
182
+ reason: str = ""
183
+ severity: str | None = None
184
+ known_bug_signature: str | None = None
185
+
186
+ # HTTP-axis signals from the adapter's CLI debug output. Adapters
187
+ # that surface these populate them; absent / ``None`` means "signal
188
+ # not applicable or not observed". Carried through to AuthOutcome.
189
+ api_status: int | None = None
190
+ oauth_refresh_status: int | None = None
191
+ request_id: str = ""
192
+ error_body_excerpt: str = ""
193
+ retry_after_s: int | None = None
194
+
195
+ def effective_severity(self) -> str:
196
+ """Return the dispatch verdict — explicit severity, or derived.
197
+
198
+ Rules (in order):
199
+ 1. Known-bug signature → ABORT (bugs always abort).
200
+ 2. Explicit ``severity`` → return it verbatim.
201
+ 3. Fall back to :func:`default_severity_for_status`.
202
+ """
203
+ if self.known_bug_signature is not None:
204
+ return FailureSeverity.ABORT
205
+ if self.severity is not None:
206
+ return self.severity
207
+ return default_severity_for_status(self.status)
208
+
209
+
210
+ @dataclass(frozen=True)
211
+ class QuotaUsage:
212
+ """Current quota usage reported by an auth-capable adapter.
213
+
214
+ ``remaining_ratio`` is in [0.0, 1.0]. ``None`` fields mean the
215
+ adapter reports one half of the ratio but not both total and
216
+ remaining. ``resets_at`` is UTC epoch seconds; ``None`` means no
217
+ reset is known. ``unit_kind`` is free-form ("tokens", "messages",
218
+ "requests"). ``detail`` is an optional free-form summary string;
219
+ never contains the payload.
220
+ """
221
+
222
+ remaining_ratio: float | None
223
+ remaining_units: int | None
224
+ total_units: int | None
225
+ resets_at: int | None
226
+ unit_kind: str
227
+ detail: str = ""
228
+
229
+
230
+ @runtime_checkable
231
+ class AuthCapableCliAdapter(Adapter, Protocol):
232
+ """Adapter that can participate in the labeled credential pool.
233
+
234
+ Extends ``Adapter`` with the surface required by
235
+ ``metaproc.dispatch.credential_pool`` and the slot coordinator:
236
+ slot-local credential materialization, failure classification, and
237
+ optional quota querying. Only adapters that need credential
238
+ injection implement this (Claude Code, Codex, Gemini); adapters
239
+ that authenticate out-of-band (e.g. Pi via ``ANTHROPIC_API_KEY``)
240
+ do not. Use ``metaproc.adapters.registry.get_auth_capable`` to
241
+ probe, never ``hasattr``.
242
+ """
243
+
244
+ slot_credential_filename: str
245
+ """Per-adapter native credential filename written under the slot dir.
246
+
247
+ Claude Code: ``.credentials.json``. Codex: the adapter writes
248
+ ``.codex/auth.json`` relative to the slot; this field still names
249
+ the user-visible blob filename for logs and flush diffs.
250
+ """
251
+
252
+ compatible_fallback_adapters: list[str]
253
+ """Adapters this one is cross-provider-compatible with for fallback.
254
+
255
+ Walked by the slot coordinator in order after same-provider
256
+ candidates are exhausted, when ``FallbackPolicy`` is
257
+ ``cross-provider`` or ``both``. Empty list means no cross-provider
258
+ fallback from this adapter. Compatibility is a declaration about
259
+ this adapter's request/response surface being substitutable for
260
+ metaproc steps — not a statement about model quality.
261
+ """
262
+
263
+ def credential_scope_env(
264
+ self,
265
+ slot_dir: Path,
266
+ *,
267
+ vehicle: Any = None,
268
+ blob: str = "",
269
+ ) -> dict[str, str]:
270
+ """Env vars that scope this CLI's credential lookup to *slot_dir*.
271
+
272
+ Claude Code (Vehicle B): ``{"CLAUDE_CONFIG_DIR": str(slot_dir)}``.
273
+ Claude Code (Vehicle A — schema_version 2): scopes the slot AND
274
+ injects ``CLAUDE_CODE_OAUTH_TOKEN=<blob>`` plus the static-bearer
275
+ hardening flags (``CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1``,
276
+ ``DISABLE_UPDATES=1``). The subprocess sees the static token and
277
+ no ``.credentials.json`` is materialized.
278
+ Codex / Gemini: ignore *vehicle* (single-vehicle adapters).
279
+
280
+ ``vehicle`` is typed ``Any`` here to avoid an import cycle with
281
+ ``metaproc.dispatch.credential_pool.Vehicle``; adapters import it
282
+ at use site. ``vehicle=None`` means "current call site has no
283
+ vehicle awareness yet" and adapters MUST treat that as the
284
+ legacy Vehicle B path so existing callers (slot coordinator
285
+ before Phase 3, ``auth check``, etc.) keep working unchanged.
286
+ """
287
+ ...
288
+
289
+ def credential_scrub_env(
290
+ self,
291
+ *,
292
+ vehicle: Any = None,
293
+ ) -> dict[str, str]:
294
+ """Env vars to unset (value ``""``) before launching a pool slot.
295
+
296
+ Higher-precedence vars in the CLI's auth chain that would
297
+ silently out-vote the slot credential. Each ``""`` value is
298
+ interpreted by the launcher as "unset". Does NOT include vars
299
+ that indicate enterprise-managed auth (e.g. ``ANTHROPIC_API_KEY``)
300
+ — those cause the coordinator to refuse slot acquisition with a
301
+ warning rather than silently clobbering operator intent.
302
+
303
+ Vehicle-aware (schema_version 2): under Vehicle A the static
304
+ ``CLAUDE_CODE_OAUTH_TOKEN`` is the credential, so adapters omit
305
+ it from the scrub set even though it is otherwise scrubbed.
306
+ """
307
+ ...
308
+
309
+ def materialize_credential(
310
+ self,
311
+ slot_dir: Path,
312
+ blob: str,
313
+ *,
314
+ vehicle: Any = None,
315
+ ) -> None:
316
+ """Materialize the credential into the slot directory.
317
+
318
+ Vehicle B (default): writes ``slot_dir / self.slot_credential_filename``
319
+ mode 0600 plus any adapter-specific metadata (Codex writes
320
+ ``slot_dir/.codex/auth.json`` and a minimal ``config.toml``).
321
+ Creates ``slot_dir`` mode 0700 if missing. Never touches
322
+ ~/.claude/, ~/.codex/, or the OS Keychain.
323
+
324
+ Vehicle A (schema_version 2): no file is written — the
325
+ credential is injected as ``CLAUDE_CODE_OAUTH_TOKEN`` via
326
+ :meth:`credential_scope_env`. Adapters still create ``slot_dir``
327
+ mode 0700 and verify it is empty of ``.credentials.json`` /
328
+ ``settings.json`` (defense in depth: a leaked ``apiKeyHelper``
329
+ in a settings file would silently override Vehicle A in the
330
+ precedence chain).
331
+ """
332
+ ...
333
+
334
+ def capture_credential(self) -> str:
335
+ """Read the native credential from the host for a pool push.
336
+
337
+ Reads the operator's live credential state — typically Keychain
338
+ on macOS, ``~/.claude/.credentials.json`` on Linux. Raises
339
+ ``RuntimeError`` with an operator-actionable message on miss.
340
+ Used only by ``metaproc auth push``.
341
+ """
342
+ ...
343
+
344
+ def classify_failure(
345
+ self,
346
+ exc: BaseException | None,
347
+ stderr: str,
348
+ session_log_path: Path | None,
349
+ ) -> AuthFailureClassification:
350
+ """Map an adapter failure to a pool state transition + reset timestamp.
351
+
352
+ ``session_log_path`` is the per-step CLI session log (e.g.
353
+ ``.logs/tool-adhoc_<item>_<ts>.jsonl``). When the CLI emits
354
+ HTTP response metadata in the session log
355
+ (``anthropic-ratelimit-*-reset`` / ``retry-after``), prefer
356
+ those as the source of ``cooling_until_ts``. Otherwise fall
357
+ back to parsing the human-readable error body (Claude:
358
+ ``"resets Xam (America/Los_Angeles)"``).
359
+ """
360
+ ...
361
+
362
+ def flush_refreshed_credential(self, slot_dir: Path) -> str | None:
363
+ """Return the current on-disk blob if it differs from bootstrap.
364
+
365
+ Called post-run, before the slot is torn down. The worker
366
+ compares fingerprint against the bootstrapped blob and, if
367
+ rotated, writes back to the pool under the label's lease before
368
+ releasing. Returns ``None`` if no write-back is required.
369
+ """
370
+ ...
371
+
372
+ def debug_capture_args(self, slot_dir: Path) -> list[str]:
373
+ """Return CLI args that capture API-level debug output to *slot_dir*.
374
+
375
+ Workers append these to the launched command so OAuth
376
+ refresh-attempt boundaries, AxiosError details, and per-attempt
377
+ API status are written to a file in the slot dir. The slot
378
+ teardown reads the file before ``rm -rf`` to populate the
379
+ :class:`AuthOutcome` event with the real error string instead of
380
+ the static known-bug template name. Adapters without a
381
+ request-side debug capture flag return ``[]``.
382
+
383
+ The filenames the args write to must be reported by
384
+ :meth:`diagnostic_filenames` so the orchestrator can preserve
385
+ them under the run's standard ``.logs/`` directory after the
386
+ slot is torn down.
387
+ """
388
+ ...
389
+
390
+ def diagnostic_filenames(self) -> tuple[str, ...]:
391
+ """Return slot-relative filenames the adapter writes for diagnostics.
392
+
393
+ These files are written by the worker during the run (e.g.
394
+ through :meth:`debug_capture_args`) and are NOT credential
395
+ material — the orchestrator copies them out of the slot dir
396
+ into the run's ``.logs/`` directory just before slot teardown,
397
+ so post-mortem can correlate API-level debug output with the
398
+ captured stream-json session log. Returns ``()`` for adapters
399
+ that don't write any (current default for codex / gemini /
400
+ pi until they grow equivalents to ``claude -d api``).
401
+
402
+ Filenames are namespaced by adapter (e.g. ``claude-code-debug.log``
403
+ rather than ``debug.log``) so a future multi-adapter slot or
404
+ side-by-side comparison stays unambiguous in the operator-facing
405
+ logs tree.
406
+ """
407
+ return ()
408
+
409
+ def setup_token_command(self) -> list[str] | None:
410
+ """Return argv for an interactive Vehicle A setup-token mint, or None.
411
+
412
+ Adapters that support ``metaproc auth setup``'s interactive
413
+ token-mint path return the argv to spawn — e.g.
414
+ ``ClaudeCodeCliAdapter`` returns ``["claude", "setup-token"]``.
415
+ ``setup_cmd`` runs that subprocess with stdout captured (the
416
+ token) and stderr passed through to the operator's terminal so
417
+ the OAuth flow is visible.
418
+
419
+ Adapters with no setup-token equivalent today (codex, gemini)
420
+ return ``None``; ``auth setup`` refuses with a message listing
421
+ the adapters that do support the flow. When those adapters
422
+ grow an equivalent, override here — no ``auth setup`` change.
423
+ """
424
+ return None
425
+
426
+ def query_quota_usage(self, slot_dir: Path) -> QuotaUsage | None:
427
+ """Return current quota usage for the credential staged in *slot_dir*.
428
+
429
+ Adapters that expose a native usage endpoint (Claude Code CLI)
430
+ hit it in-process using the bootstrapped credential and return
431
+ a ``QuotaUsage``. Adapters without one return ``None`` — the
432
+ generic caller treats ``None`` as "unsupported for this
433
+ adapter" and renders ``—`` in list tables.
434
+
435
+ Called opportunistically: on ``metaproc auth list --usage``, on
436
+ ``metaproc auth usage``, and as a selection tiebreak when
437
+ multiple eligible labels are available and the adapter supports
438
+ it. Must not mutate the slot or write to the pool. Must be safe
439
+ to call for a ``cooling`` label (to see how much longer it's
440
+ cooling). Never prints or returns the blob. Errors are
441
+ swallowed and returned as ``None`` — quota queries are
442
+ best-effort, never blocking.
443
+ """
444
+ ...
445
+
446
+ def query_live_quota(
447
+ self,
448
+ slot_dir: Path,
449
+ *,
450
+ vehicle: Any = None,
451
+ blob: str = "",
452
+ ) -> QuotaUsage | None:
453
+ """Return live per-account quota by hitting the vendor's HUD endpoint
454
+ for the credential staged in *slot_dir*.
455
+
456
+ Distinct from :meth:`query_quota_usage` (which may derive from local
457
+ rate-limit-event records): this is a network call against the
458
+ vendor's own quota-rendering endpoint, working before the first
459
+ 429 of a run. Required for the ``metaproc auth quota`` pre-dispatch
460
+ gate.
461
+
462
+ Returns ``None`` when:
463
+
464
+ - the adapter does not expose a live endpoint;
465
+ - the network call fails;
466
+ - the credential cannot be read from the slot or pool blob.
467
+
468
+ Errors are swallowed — callers fall back to
469
+ :meth:`query_quota_usage` for the derived signal. Must not mutate
470
+ the slot. Never returns or prints the blob.
471
+ """
472
+ return None