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.
- metaproc/__init__.py +1 -0
- metaproc/__main__.py +6 -0
- metaproc/adapters/__init__.py +0 -0
- metaproc/adapters/base.py +472 -0
- metaproc/adapters/claude_code.py +1487 -0
- metaproc/adapters/cli_version.py +133 -0
- metaproc/adapters/codex.py +850 -0
- metaproc/adapters/gemini.py +281 -0
- metaproc/adapters/pi_cli.py +415 -0
- metaproc/adapters/registry.py +102 -0
- metaproc/agent_errors.py +126 -0
- metaproc/cli.py +142 -0
- metaproc/cloud/__init__.py +0 -0
- metaproc/cloud/gcp/__init__.py +0 -0
- metaproc/cloud/gcp/batch_backend.py +722 -0
- metaproc/cloud/gcp/billing.py +92 -0
- metaproc/cloud/gcp/container_bootstrap.py +546 -0
- metaproc/cloud/gcp/dispatch_artifacts.py +222 -0
- metaproc/cloud/gcp/gcp_credentials.py +184 -0
- metaproc/cloud/gcp/gcp_run_dispatch.py +159 -0
- metaproc/cloud/gcp/gcp_run_entrypoint.py +97 -0
- metaproc/cloud/gcp/gcp_run_logs.py +223 -0
- metaproc/cloud/gcp/orchestrator_dispatch.py +389 -0
- metaproc/cloud/gcp/orchestrator_entrypoint.py +224 -0
- metaproc/cloud/gcp/resolve_token.py +38 -0
- metaproc/cloud/gcp/worker_dispatch.py +901 -0
- metaproc/cloud/gcp/worker_entrypoint.py +255 -0
- metaproc/commands/__init__.py +0 -0
- metaproc/commands/auth.py +2205 -0
- metaproc/commands/auth_check.py +896 -0
- metaproc/commands/check_handlers.py +112 -0
- metaproc/commands/check_headers.py +127 -0
- metaproc/commands/claude_auth.py +357 -0
- metaproc/commands/codex_auth.py +440 -0
- metaproc/commands/compact_logs.py +118 -0
- metaproc/commands/compare.py +124 -0
- metaproc/commands/compare_matrix.py +124 -0
- metaproc/commands/compare_trace.py +211 -0
- metaproc/commands/deps.py +171 -0
- metaproc/commands/env.py +175 -0
- metaproc/commands/gcp.py +2637 -0
- metaproc/commands/gcp_run.py +351 -0
- metaproc/commands/gzip_text.py +640 -0
- metaproc/commands/help.py +67 -0
- metaproc/commands/helpers.py +260 -0
- metaproc/commands/kill.py +246 -0
- metaproc/commands/liveness_watch.py +293 -0
- metaproc/commands/override.py +118 -0
- metaproc/commands/plan.py +129 -0
- metaproc/commands/pool.py +1723 -0
- metaproc/commands/probe_tool_use.py +293 -0
- metaproc/commands/pulse.py +286 -0
- metaproc/commands/resource_report.py +212 -0
- metaproc/commands/resources.py +40 -0
- metaproc/commands/resume_daemon.py +264 -0
- metaproc/commands/run_manifest.py +44 -0
- metaproc/commands/run_parallel.py +2161 -0
- metaproc/commands/run_process.py +3176 -0
- metaproc/commands/run_step.py +598 -0
- metaproc/commands/searches.py +122 -0
- metaproc/commands/skill.py +103 -0
- metaproc/commands/softschema.py +206 -0
- metaproc/commands/ssh_utils.py +115 -0
- metaproc/commands/stats.py +83 -0
- metaproc/commands/status.py +619 -0
- metaproc/commands/tail.py +121 -0
- metaproc/commands/tools.py +153 -0
- metaproc/commands/trace.py +375 -0
- metaproc/commands/validate.py +224 -0
- metaproc/commands/variants.py +144 -0
- metaproc/commands/wait.py +65 -0
- metaproc/commands/write_usage.py +102 -0
- metaproc/config/__init__.py +5 -0
- metaproc/config/env_enum.py +243 -0
- metaproc/config/env_vars.py +415 -0
- metaproc/config/providers.py +198 -0
- metaproc/console.py +48 -0
- metaproc/data/execution-profiles.default.yaml +259 -0
- metaproc/data/pi-models.default.json +63 -0
- metaproc/data/pricing.md +690 -0
- metaproc/dispatch/__init__.py +8 -0
- metaproc/dispatch/auth_pool_flags.py +212 -0
- metaproc/dispatch/auth_usage.py +745 -0
- metaproc/dispatch/auth_usage_render.py +271 -0
- metaproc/dispatch/credential_pool.py +1759 -0
- metaproc/dispatch/known_bugs.py +226 -0
- metaproc/dispatch/orchestrator_payload.py +151 -0
- metaproc/dispatch/pool_dispatch.py +742 -0
- metaproc/dispatch/pool_status.py +349 -0
- metaproc/dispatch/preflight.py +534 -0
- metaproc/dispatch/repo_sync_payload.py +123 -0
- metaproc/dispatch/retry_later.py +206 -0
- metaproc/dispatch/secret_refs.py +145 -0
- metaproc/dispatch/slot_coordinator.py +720 -0
- metaproc/dispatch/worker_payload.py +131 -0
- metaproc/docs/__init__.py +40 -0
- metaproc/docs/metaproc-concepts-and-principles.md +988 -0
- metaproc/docs/metaproc-developer-guide.md +150 -0
- metaproc/docs/metaproc-operator-reference.md +670 -0
- metaproc/engine/__init__.py +0 -0
- metaproc/engine/build_plan.py +944 -0
- metaproc/engine/code_handler.py +154 -0
- metaproc/engine/condition.py +54 -0
- metaproc/engine/dep_state.py +360 -0
- metaproc/engine/discovery.py +181 -0
- metaproc/engine/graph.py +163 -0
- metaproc/engine/input_validation.py +197 -0
- metaproc/engine/lane_expand.py +191 -0
- metaproc/engine/parse_dispatch.py +125 -0
- metaproc/engine/pathing.py +193 -0
- metaproc/engine/placeholders.py +290 -0
- metaproc/engine/preflight.py +404 -0
- metaproc/engine/process_scope.py +100 -0
- metaproc/engine/resource_hierarchy.py +226 -0
- metaproc/engine/resource_rollup.py +815 -0
- metaproc/engine/retry.py +323 -0
- metaproc/engine/run_id.py +51 -0
- metaproc/engine/run_status.py +761 -0
- metaproc/engine/runtime.py +317 -0
- metaproc/engine/validation.py +328 -0
- metaproc/engine/write_boundary.py +423 -0
- metaproc/engine/yaml_repair.py +195 -0
- metaproc/errors.py +29 -0
- metaproc/io/__init__.py +71 -0
- metaproc/io/claimed_items.py +141 -0
- metaproc/io/digests.py +36 -0
- metaproc/io/dispatch_manifest.py +100 -0
- metaproc/io/frontmatter.py +186 -0
- metaproc/io/gz_io.py +228 -0
- metaproc/io/mkdir_lock.py +224 -0
- metaproc/io/orchestrator_lease.py +408 -0
- metaproc/io/overrides.py +188 -0
- metaproc/io/safe_archive.py +97 -0
- metaproc/io/schema_token.py +175 -0
- metaproc/io/state_io.py +294 -0
- metaproc/io/templating.py +122 -0
- metaproc/logutil/__init__.py +1 -0
- metaproc/logutil/compaction.py +630 -0
- metaproc/logutil/log_path_owner.py +174 -0
- metaproc/logutil/parsing.py +1819 -0
- metaproc/logutil/resource_event_extract.py +397 -0
- metaproc/logutil/resource_events.py +89 -0
- metaproc/logutil/throttling.py +175 -0
- metaproc/logutil/tool_failures.py +90 -0
- metaproc/logutil/tool_spans.py +270 -0
- metaproc/logutil/usage.py +644 -0
- metaproc/metabrowser_plugin/README.md +125 -0
- metaproc/metabrowser_plugin/__init__.py +31 -0
- metaproc/metabrowser_plugin/charts.py +150 -0
- metaproc/metabrowser_plugin/log_adapters.py +106 -0
- metaproc/metabrowser_plugin/plugin/domain_views.css +413 -0
- metaproc/metabrowser_plugin/plugin/domain_views.js +1680 -0
- metaproc/metabrowser_plugin/plugin/elk.bundled.js +6540 -0
- metaproc/metabrowser_plugin/plugin/elkjs-license.txt +264 -0
- metaproc/metabrowser_plugin/plugin/index.js +363 -0
- metaproc/metabrowser_plugin/plugin/manifest.toml +199 -0
- metaproc/metabrowser_plugin/plugin/styles.css +37 -0
- metaproc/metabrowser_plugin/plugin/viz.css +1032 -0
- metaproc/metabrowser_plugin/plugin/viz.js +2218 -0
- metaproc/metabrowser_plugin/sidekick.py +376 -0
- metaproc/models/__init__.py +0 -0
- metaproc/models/authored.py +541 -0
- metaproc/models/execution_profile.py +337 -0
- metaproc/models/invocation.py +48 -0
- metaproc/models/lane.py +217 -0
- metaproc/models/node_ids.py +52 -0
- metaproc/models/plan.py +123 -0
- metaproc/models/plan_bundle.py +36 -0
- metaproc/models/pricing.py +58 -0
- metaproc/models/qa.py +120 -0
- metaproc/models/resources.py +322 -0
- metaproc/models/run_manifest.py +137 -0
- metaproc/models/runtime.py +136 -0
- metaproc/models/usage.py +265 -0
- metaproc/models/viz.py +342 -0
- metaproc/osutils/__init__.py +1 -0
- metaproc/osutils/file_extensions.py +172 -0
- metaproc/osutils/ignore_filter.py +145 -0
- metaproc/osutils/memory_pressure.py +382 -0
- metaproc/osutils/psutil_sampler.py +183 -0
- metaproc/osutils/resource_context.py +392 -0
- metaproc/output.py +109 -0
- metaproc/paths.py +446 -0
- metaproc/plugins/__init__.py +0 -0
- metaproc/plugins/discovery.py +68 -0
- metaproc/plugins/protocol.py +115 -0
- metaproc/plugins/registry.py +118 -0
- metaproc/py.typed +0 -0
- metaproc/resource_docs.py +46 -0
- metaproc/runpool/README.md +32 -0
- metaproc/runpool/__init__.py +58 -0
- metaproc/runpool/backend.py +399 -0
- metaproc/runpool/concurrency.py +165 -0
- metaproc/runpool/event_models.py +215 -0
- metaproc/runpool/event_reader.py +54 -0
- metaproc/runpool/events.py +406 -0
- metaproc/runpool/host_admission.py +449 -0
- metaproc/runpool/kill.py +323 -0
- metaproc/runpool/mock_backend.py +108 -0
- metaproc/runpool/monitor.py +30 -0
- metaproc/runpool/pool.py +1975 -0
- metaproc/runpool/process_event_models.py +163 -0
- metaproc/runpool/process_events.py +395 -0
- metaproc/runpool/registry.py +88 -0
- metaproc/runpool/semaphore.py +87 -0
- metaproc/runpool/status.py +271 -0
- metaproc/runtime/__init__.py +8 -0
- metaproc/runtime/code_handler.py +33 -0
- metaproc/runtime_paths.py +50 -0
- metaproc/settings.py +284 -0
- metaproc/skill/__init__.py +7 -0
- metaproc/skill/baselines/__init__.py +1 -0
- metaproc/skill/baselines/metaproc.md +70 -0
- metaproc/skill/builtin.py +62 -0
- metaproc/skill/compose.py +59 -0
- metaproc/skill/registry.py +52 -0
- metaproc/skill/spec.py +40 -0
- metaproc/stats/__init__.py +1 -0
- metaproc/stats/analysis.py +210 -0
- metaproc/stats/engine.py +765 -0
- metaproc/stats/formatters.py +319 -0
- metaproc/stats/models.py +168 -0
- metaproc/stats/path_tally.py +89 -0
- metaproc/structure_report.py +324 -0
- metaproc/trace/__init__.py +36 -0
- metaproc/trace/aggregation.py +163 -0
- metaproc/trace/extractors/__init__.py +89 -0
- metaproc/trace/extractors/claude_agent.py +520 -0
- metaproc/trace/extractors/codex_agent.py +560 -0
- metaproc/trace/extractors/common.py +378 -0
- metaproc/trace/extractors/gemini_agent.py +520 -0
- metaproc/trace/extractors/metaproc_engine.py +347 -0
- metaproc/trace/extractors/pi_agent.py +475 -0
- metaproc/trace/ids.py +37 -0
- metaproc/trace/linker.py +179 -0
- metaproc/trace/named_views.py +243 -0
- metaproc/trace/quality.py +170 -0
- metaproc/trace/runner.py +30 -0
- metaproc/trace/schema.py +205 -0
- metaproc/trace/store.py +41 -0
- metaproc/trace/views.py +237 -0
- metaproc/utils/__init__.py +0 -0
- metaproc/utils/mtime_cache.py +142 -0
- metaproc/viz/__init__.py +21 -0
- metaproc/viz/decorators.py +76 -0
- metaproc/viz/layout.py +157 -0
- metaproc/viz/project.py +529 -0
- metaproc/viz/render_html.py +70 -0
- metaproc/viz/render_svg.py +212 -0
- metaproc/viz_loader.py +132 -0
- metaproc-0.2.0.dist-info/METADATA +288 -0
- metaproc-0.2.0.dist-info/RECORD +256 -0
- metaproc-0.2.0.dist-info/WHEEL +4 -0
- metaproc-0.2.0.dist-info/entry_points.txt +8 -0
- metaproc-0.2.0.dist-info/licenses/LICENSE +661 -0
- 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
|
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
|