codex-flow 2.1.13__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.
- codex_flow/__init__.py +28 -0
- codex_flow/__main__.py +9 -0
- codex_flow/cli.py +242 -0
- codex_flow/data/LICENSE +21 -0
- codex_flow/data/README.en.md +303 -0
- codex_flow/data/README.md +305 -0
- codex_flow/data/VERSION +1 -0
- codex_flow/data/apps/chatgpt-mcp/README.md +86 -0
- codex_flow/data/apps/chatgpt-mcp/__init__.py +1 -0
- codex_flow/data/apps/chatgpt-mcp/adapter.py +458 -0
- codex_flow/data/apps/chatgpt-mcp/server.py +358 -0
- codex_flow/data/apps/chatgpt-mcp/widget.html +927 -0
- codex_flow/data/apps/macos-overlay/README.en.md +121 -0
- codex_flow/data/apps/macos-overlay/README.md +123 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayRuntimeState.swift +126 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayScreenGeometry.swift +82 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayWindowController.swift +1052 -0
- codex_flow/data/apps/macos-overlay/Sources/Localization.swift +197 -0
- codex_flow/data/apps/macos-overlay/Sources/Models/TelemetryData.swift +1557 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/AccountSnapshotService.swift +1101 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/FlowPilotInstanceLock.swift +153 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/IPCServer.swift +298 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryQueryEngine.swift +800 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryWatcher.swift +135 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/UpdateService.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AccountView.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AnalyticsView.swift +566 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AutostartView.swift +293 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/BubbleView.swift +317 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HistoryView.swift +1124 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HoverRevealText.swift +165 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/InspectorSkillsToolsView.swift +121 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/LogoView.swift +182 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SleekSwitch.swift +117 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/StrategyModeView.swift +561 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SummaryView.swift +1273 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/UpdateView.swift +352 -0
- codex_flow/data/apps/macos-overlay/Sources/main.swift +340 -0
- codex_flow/data/apps/macos-overlay/Tests/OverlayScreenGeometryTests.swift +163 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryPhase1ContractTests.swift +357 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQueryEngineConcurrencyTests.swift +221 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQuotaSelectionTests.swift +158 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryWorkerTokenTests.swift +122 -0
- codex_flow/data/apps/macos-overlay/build.sh +75 -0
- codex_flow/data/benchmark/corpus.json +103 -0
- codex_flow/data/benchmark/manifest.example.json +41 -0
- codex_flow/data/benchmark/manifest.schema.json +137 -0
- codex_flow/data/benchmark/prices/gpt-5.6-2026-08-30.json +5 -0
- codex_flow/data/benchmark/profiles.json +90 -0
- codex_flow/data/benchmark/schema.json +77 -0
- codex_flow/data/benchmark/tasks.json +50 -0
- codex_flow/data/completions/codex-flow.bash +34 -0
- codex_flow/data/completions/codex-flow.zsh +52 -0
- codex_flow/data/glama.json +6 -0
- codex_flow/data/install-release.ps1 +126 -0
- codex_flow/data/install-release.sh +155 -0
- codex_flow/data/install.ps1 +349 -0
- codex_flow/data/install.sh +362 -0
- codex_flow/data/policy/benchmark.toml +49 -0
- codex_flow/data/policy/defaults.toml +70 -0
- codex_flow/data/scripts/analyze-benchmark.py +510 -0
- codex_flow/data/scripts/benchmark-local.py +171 -0
- codex_flow/data/scripts/check-recommendation.py +277 -0
- codex_flow/data/scripts/doctor.py +449 -0
- codex_flow/data/scripts/generate-release-manifest.py +74 -0
- codex_flow/data/scripts/localization.py +192 -0
- codex_flow/data/scripts/manage-hooks.py +448 -0
- codex_flow/data/scripts/manage-instructions.py +389 -0
- codex_flow/data/scripts/manage-shell.py +151 -0
- codex_flow/data/scripts/materialize-corpus.py +193 -0
- codex_flow/data/scripts/menu.py +646 -0
- codex_flow/data/scripts/migrations/0001_update_settings.py +80 -0
- codex_flow/data/scripts/package-release.py +132 -0
- codex_flow/data/scripts/render-benchmark-report.py +292 -0
- codex_flow/data/scripts/run-benchmark.py +829 -0
- codex_flow/data/scripts/strategies/__init__.py +28 -0
- codex_flow/data/scripts/strategies/balanced.py +115 -0
- codex_flow/data/scripts/strategies/base.py +363 -0
- codex_flow/data/scripts/strategies/efficient.py +158 -0
- codex_flow/data/scripts/strategies/lifecycle_runtime.py +590 -0
- codex_flow/data/scripts/strategies/quality.py +209 -0
- codex_flow/data/scripts/strategies/speed.py +108 -0
- codex_flow/data/scripts/strategies/task_budget_runtime.py +644 -0
- codex_flow/data/scripts/strategies/task_phase_runtime.py +341 -0
- codex_flow/data/scripts/strategies/work_unit_runtime.py +421 -0
- codex_flow/data/scripts/strategy_runtime.py +1091 -0
- codex_flow/data/scripts/telemetry.py +400 -0
- codex_flow/data/scripts/telemetry_core/__init__.py +192 -0
- codex_flow/data/scripts/telemetry_core/app_server.py +1192 -0
- codex_flow/data/scripts/telemetry_core/collector.py +1247 -0
- codex_flow/data/scripts/telemetry_core/common.py +421 -0
- codex_flow/data/scripts/telemetry_core/latency.py +593 -0
- codex_flow/data/scripts/telemetry_core/query.py +427 -0
- codex_flow/data/scripts/telemetry_core/quota_ledger.py +598 -0
- codex_flow/data/scripts/telemetry_core/render.py +460 -0
- codex_flow/data/scripts/telemetry_core/repair.py +223 -0
- codex_flow/data/scripts/ui.py +266 -0
- codex_flow/data/scripts/update-homebrew-formula.py +146 -0
- codex_flow/data/scripts/update_runtime_config.py +134 -0
- codex_flow/data/scripts/updater.py +1718 -0
- codex_flow/data/smithery.yaml +18 -0
- codex_flow/data/templates/agents/worker-explorer.toml +24 -0
- codex_flow/data/templates/agents/worker-implementer.toml +49 -0
- codex_flow/data/templates/agents/worker-reviewer.toml +25 -0
- codex_flow/data/templates/flow-pilot-instructions.md +35 -0
- codex_flow/data/templates/skills/flow-pilot/SKILL.md +577 -0
- codex_flow/mcp.py +35 -0
- codex_flow-2.1.13.dist-info/METADATA +342 -0
- codex_flow-2.1.13.dist-info/RECORD +113 -0
- codex_flow-2.1.13.dist-info/WHEEL +5 -0
- codex_flow-2.1.13.dist-info/entry_points.txt +3 -0
- codex_flow-2.1.13.dist-info/licenses/LICENSE +21 -0
- codex_flow-2.1.13.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Phase-aware admission for the canonical ExecutionPlan task budget.
|
|
3
|
+
|
|
4
|
+
The compiler emits one self-consistent task budget. Its soft deadline is the
|
|
5
|
+
end of general writable work. If independent review is planned, its hard
|
|
6
|
+
deadline already includes the review-stage hard window plus an explicit Parent
|
|
7
|
+
finalization reserve. This helper validates that contract and performs
|
|
8
|
+
phase-aware reservations; it never derives a second budget.
|
|
9
|
+
|
|
10
|
+
Required completion has two admission windows:
|
|
11
|
+
- review may start from the general-work cutoff until ``review_deadline``;
|
|
12
|
+
- Parent finalization retains the remaining tail until the absolute hard stop.
|
|
13
|
+
|
|
14
|
+
A reviewer retry therefore cannot consume the Parent finalization reserve.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import math
|
|
22
|
+
import sys
|
|
23
|
+
from dataclasses import asdict
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from .base import TaskBudgetPolicy
|
|
28
|
+
from .task_budget_runtime import (
|
|
29
|
+
GENERAL_RESERVATION_KINDS,
|
|
30
|
+
LedgerError,
|
|
31
|
+
init_ledger,
|
|
32
|
+
ledger_status,
|
|
33
|
+
reserve,
|
|
34
|
+
)
|
|
35
|
+
except ImportError:
|
|
36
|
+
from base import TaskBudgetPolicy
|
|
37
|
+
from task_budget_runtime import (
|
|
38
|
+
GENERAL_RESERVATION_KINDS,
|
|
39
|
+
LedgerError,
|
|
40
|
+
init_ledger,
|
|
41
|
+
ledger_status,
|
|
42
|
+
reserve,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
PHASES = ("exploration", "implementation", "required_completion")
|
|
46
|
+
RESERVATION_PHASE = {
|
|
47
|
+
"work_unit": "implementation",
|
|
48
|
+
"implementation_attempt": "implementation",
|
|
49
|
+
"replan": "implementation",
|
|
50
|
+
"replacement": "implementation",
|
|
51
|
+
"review_attempt": "required_completion",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _strict_number(value: Any, label: str) -> float:
|
|
56
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
57
|
+
raise LedgerError(f"{label} must be a finite number")
|
|
58
|
+
number = float(value)
|
|
59
|
+
if not math.isfinite(number) or number < 0:
|
|
60
|
+
raise LedgerError(f"{label} must be a finite non-negative number")
|
|
61
|
+
return number
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _cli_now(value: Any) -> float:
|
|
65
|
+
if isinstance(value, bool):
|
|
66
|
+
raise LedgerError("now must be finite seconds")
|
|
67
|
+
try:
|
|
68
|
+
number = float(value)
|
|
69
|
+
except (TypeError, ValueError):
|
|
70
|
+
raise LedgerError("now must be finite seconds") from None
|
|
71
|
+
if not math.isfinite(number) or number < 0:
|
|
72
|
+
raise LedgerError("now must be finite non-negative seconds")
|
|
73
|
+
return number
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _strict_int(value: Any, label: str) -> int:
|
|
77
|
+
if type(value) is not int or value < 0:
|
|
78
|
+
raise LedgerError(f"{label} must be a non-negative integer")
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _task_policy(plan: dict[str, Any]) -> TaskBudgetPolicy:
|
|
83
|
+
try:
|
|
84
|
+
return TaskBudgetPolicy.from_dict(plan["task_budget"])
|
|
85
|
+
except (TypeError, ValueError) as exc:
|
|
86
|
+
raise LedgerError(str(exc)) from None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _policy_fingerprint(policy: TaskBudgetPolicy) -> str:
|
|
90
|
+
encoded = json.dumps(asdict(policy), ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
91
|
+
return hashlib.sha256(encoded).hexdigest()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _completion_reserve(plan: dict[str, Any], policy: TaskBudgetPolicy) -> tuple[int, int, int]:
|
|
95
|
+
reviewer_workers = _strict_int(plan.get("reviewer_workers"), "reviewer_workers")
|
|
96
|
+
review_stage = plan.get("review_stage")
|
|
97
|
+
if reviewer_workers == 0:
|
|
98
|
+
if review_stage is not None:
|
|
99
|
+
raise LedgerError("review_stage must be null when reviewer_workers is zero")
|
|
100
|
+
if policy.max_review_attempts != 0:
|
|
101
|
+
raise LedgerError("max_review_attempts must be zero when reviewer_workers is zero")
|
|
102
|
+
return 0, 0, 0
|
|
103
|
+
if type(review_stage) is not dict:
|
|
104
|
+
raise LedgerError("review_stage is required when reviewer_workers is positive")
|
|
105
|
+
if policy.max_review_attempts < reviewer_workers:
|
|
106
|
+
raise LedgerError("review topology exceeds task budget max_review_attempts")
|
|
107
|
+
review_hard = _strict_int(review_stage.get("hard_timeout_seconds"), "review_stage.hard_timeout_seconds")
|
|
108
|
+
if review_hard < 1:
|
|
109
|
+
raise LedgerError("review_stage.hard_timeout_seconds must be positive")
|
|
110
|
+
parent_finalization = policy.parent_finalization_seconds
|
|
111
|
+
return review_hard, parent_finalization, review_hard + parent_finalization
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _plan(value: Any) -> dict[str, Any]:
|
|
115
|
+
if type(value) is not dict:
|
|
116
|
+
raise LedgerError("ExecutionPlan must be an object")
|
|
117
|
+
if value.get("schema_version") != 11:
|
|
118
|
+
raise LedgerError("task phase runtime requires ExecutionPlan schema v11")
|
|
119
|
+
if type(value.get("task_budget")) is not dict:
|
|
120
|
+
raise LedgerError("ExecutionPlan task_budget must be an object")
|
|
121
|
+
policy = _task_policy(value)
|
|
122
|
+
implementation_workers = _strict_int(value.get("implementation_workers"), "implementation_workers")
|
|
123
|
+
implementation_stage = value.get("implementation_stage")
|
|
124
|
+
if implementation_workers > 0:
|
|
125
|
+
if type(implementation_stage) is not dict:
|
|
126
|
+
raise LedgerError("implementation_stage is required when implementation_workers is positive")
|
|
127
|
+
maximum_work_units = implementation_stage.get("maximum_work_units")
|
|
128
|
+
if type(maximum_work_units) is not int or maximum_work_units < 1:
|
|
129
|
+
raise LedgerError("implementation_stage.maximum_work_units must be a positive integer")
|
|
130
|
+
if maximum_work_units != policy.max_work_units:
|
|
131
|
+
raise LedgerError("task budget max_work_units must match implementation_stage maximum_work_units")
|
|
132
|
+
if implementation_workers > policy.max_work_units:
|
|
133
|
+
raise LedgerError("implementation topology exceeds task budget max_work_units")
|
|
134
|
+
if implementation_workers > policy.max_implementation_attempts:
|
|
135
|
+
raise LedgerError("implementation topology exceeds task budget max_implementation_attempts")
|
|
136
|
+
implementation_soft = implementation_stage.get("soft_timeout_seconds")
|
|
137
|
+
if type(implementation_soft) is not int or implementation_soft < 1:
|
|
138
|
+
raise LedgerError("implementation_stage.soft_timeout_seconds must be a positive integer")
|
|
139
|
+
if policy.soft_timeout_seconds < implementation_soft:
|
|
140
|
+
raise LedgerError("task soft timeout cannot precede implementation soft checkpoint budget")
|
|
141
|
+
elif implementation_stage is not None:
|
|
142
|
+
raise LedgerError("implementation_stage must be null when implementation_workers is zero")
|
|
143
|
+
|
|
144
|
+
_review_hard, _parent_finalization, reserve_seconds = _completion_reserve(value, policy)
|
|
145
|
+
if reserve_seconds > 0 and policy.hard_timeout_seconds - policy.soft_timeout_seconds < reserve_seconds:
|
|
146
|
+
raise LedgerError("task budget does not reserve the full required-completion tail")
|
|
147
|
+
return value
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _validate_ledger_identity(plan: dict[str, Any], ledger: dict[str, Any]) -> TaskBudgetPolicy:
|
|
151
|
+
policy = _task_policy(plan)
|
|
152
|
+
if ledger.get("policy_fingerprint") != _policy_fingerprint(policy):
|
|
153
|
+
raise LedgerError("task phase plan/policy mismatch; refusing to use a different budget plan")
|
|
154
|
+
return policy
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def phase_decision(plan_value: Any, ledger_value: Any, phase: str, now: Any) -> dict[str, Any]:
|
|
158
|
+
if phase not in PHASES:
|
|
159
|
+
raise LedgerError(f"invalid task phase: {phase}")
|
|
160
|
+
plan = _plan(plan_value)
|
|
161
|
+
if type(ledger_value) is not dict:
|
|
162
|
+
raise LedgerError("ledger status must be an object")
|
|
163
|
+
policy = _validate_ledger_identity(plan, ledger_value)
|
|
164
|
+
current_now = _strict_number(now, "now")
|
|
165
|
+
started_at = _strict_number(ledger_value.get("started_at"), "started_at")
|
|
166
|
+
soft_deadline = _strict_number(ledger_value.get("soft_deadline"), "soft_deadline")
|
|
167
|
+
hard_deadline = _strict_number(ledger_value.get("hard_deadline"), "hard_deadline")
|
|
168
|
+
if soft_deadline != started_at + policy.soft_timeout_seconds:
|
|
169
|
+
raise LedgerError("task phase soft deadline does not match ExecutionPlan task budget")
|
|
170
|
+
if hard_deadline != started_at + policy.hard_timeout_seconds:
|
|
171
|
+
raise LedgerError("task phase hard deadline does not match ExecutionPlan task budget")
|
|
172
|
+
closed = ledger_value.get("closed")
|
|
173
|
+
if type(closed) is not bool:
|
|
174
|
+
raise LedgerError("ledger closed must be boolean")
|
|
175
|
+
|
|
176
|
+
review_hard, parent_finalization, reserve_seconds = _completion_reserve(plan, policy)
|
|
177
|
+
hard_open = (not closed) and current_now < hard_deadline
|
|
178
|
+
permits_general_work = hard_open and current_now < soft_deadline
|
|
179
|
+
permits_required_completion = hard_open and reserve_seconds > 0
|
|
180
|
+
review_deadline = hard_deadline - parent_finalization if reserve_seconds > 0 else None
|
|
181
|
+
permits_review_start = permits_required_completion and review_deadline is not None and current_now < review_deadline
|
|
182
|
+
permits_parent_finalization = hard_open
|
|
183
|
+
|
|
184
|
+
if not hard_open:
|
|
185
|
+
action = "stop"
|
|
186
|
+
elif phase == "required_completion":
|
|
187
|
+
if not permits_required_completion:
|
|
188
|
+
action = "stop"
|
|
189
|
+
elif permits_review_start:
|
|
190
|
+
action = "complete_required"
|
|
191
|
+
else:
|
|
192
|
+
action = "finalize_parent"
|
|
193
|
+
elif permits_general_work:
|
|
194
|
+
action = "continue"
|
|
195
|
+
elif reserve_seconds > 0:
|
|
196
|
+
action = "converge_for_required_completion"
|
|
197
|
+
else:
|
|
198
|
+
action = "converge"
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"phase": phase,
|
|
202
|
+
"action": action,
|
|
203
|
+
"permits_phase_start": permits_required_completion if phase == "required_completion" else permits_general_work,
|
|
204
|
+
"permits_general_work": permits_general_work,
|
|
205
|
+
"permits_required_completion": permits_required_completion,
|
|
206
|
+
"permits_review_start": permits_review_start,
|
|
207
|
+
"permits_parent_finalization": permits_parent_finalization,
|
|
208
|
+
"reviewer_reserve_seconds": review_hard,
|
|
209
|
+
"parent_finalization_reserve_seconds": parent_finalization,
|
|
210
|
+
"required_completion_reserve_seconds": reserve_seconds,
|
|
211
|
+
"general_work_deadline": soft_deadline,
|
|
212
|
+
"review_deadline": review_deadline,
|
|
213
|
+
"soft_deadline": soft_deadline,
|
|
214
|
+
"hard_deadline": hard_deadline,
|
|
215
|
+
"remaining_general_work_seconds": max(0.0, soft_deadline - current_now),
|
|
216
|
+
"remaining_review_seconds": 0.0 if review_deadline is None else max(0.0, review_deadline - current_now),
|
|
217
|
+
"remaining_hard_seconds": max(0.0, hard_deadline - current_now),
|
|
218
|
+
"checkpoint_convergence_required": phase == "implementation" and hard_open and current_now >= soft_deadline,
|
|
219
|
+
"required_completion_handoff": reserve_seconds > 0 and hard_open and current_now >= soft_deadline,
|
|
220
|
+
"task_budget": asdict(policy),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def init(state_file: str, task_id: str, plan: dict[str, Any], now: Any) -> dict[str, Any]:
|
|
225
|
+
plan = _plan(plan)
|
|
226
|
+
policy = _task_policy(plan)
|
|
227
|
+
result = init_ledger(state_file, task_id, policy, now)
|
|
228
|
+
result.update(phase_decision(plan, result, "exploration", now))
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def status(state_file: str, task_id: str, plan: dict[str, Any], phase: str, now: Any) -> dict[str, Any]:
|
|
233
|
+
plan = _plan(plan)
|
|
234
|
+
ledger = ledger_status(state_file, task_id, now)
|
|
235
|
+
result = dict(ledger)
|
|
236
|
+
result.update(phase_decision(plan, ledger, phase, now))
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def reserve_phase(
|
|
241
|
+
state_file: str,
|
|
242
|
+
task_id: str,
|
|
243
|
+
plan: dict[str, Any],
|
|
244
|
+
phase: str,
|
|
245
|
+
kind: str,
|
|
246
|
+
reservation_id: str,
|
|
247
|
+
fingerprint: str,
|
|
248
|
+
now: Any,
|
|
249
|
+
) -> dict[str, Any]:
|
|
250
|
+
plan = _plan(plan)
|
|
251
|
+
if phase not in PHASES:
|
|
252
|
+
raise LedgerError(f"invalid task phase: {phase}")
|
|
253
|
+
expected_phase = RESERVATION_PHASE.get(kind)
|
|
254
|
+
if expected_phase is None:
|
|
255
|
+
raise LedgerError(f"unsupported task phase reservation kind: {kind}")
|
|
256
|
+
if phase != expected_phase:
|
|
257
|
+
raise LedgerError(f"{kind} reservations require phase={expected_phase}")
|
|
258
|
+
if phase == "implementation" and kind not in GENERAL_RESERVATION_KINDS:
|
|
259
|
+
raise LedgerError("implementation phase only accepts general-work reservations")
|
|
260
|
+
|
|
261
|
+
if kind == "review_attempt":
|
|
262
|
+
decision = status(state_file, task_id, plan, phase, now)
|
|
263
|
+
review_deadline = decision["review_deadline"]
|
|
264
|
+
if review_deadline is None:
|
|
265
|
+
raise LedgerError("review_attempt requires a planned review deadline")
|
|
266
|
+
result = reserve(
|
|
267
|
+
state_file,
|
|
268
|
+
task_id,
|
|
269
|
+
kind,
|
|
270
|
+
reservation_id,
|
|
271
|
+
fingerprint,
|
|
272
|
+
now,
|
|
273
|
+
new_reservation_deadline=review_deadline,
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
result = reserve(state_file, task_id, kind, reservation_id, fingerprint, now)
|
|
277
|
+
result.update(phase_decision(plan, result, phase, now))
|
|
278
|
+
return result
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _json(raw: str, label: str) -> Any:
|
|
282
|
+
try:
|
|
283
|
+
return json.loads(raw)
|
|
284
|
+
except json.JSONDecodeError as exc:
|
|
285
|
+
raise LedgerError(f"invalid {label} JSON: {exc.msg}") from None
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _common(parser: argparse.ArgumentParser) -> None:
|
|
289
|
+
parser.add_argument("--state-file", required=True)
|
|
290
|
+
parser.add_argument("--task-id", required=True)
|
|
291
|
+
parser.add_argument("--plan-json", required=True)
|
|
292
|
+
parser.add_argument("--now", required=True)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _parser() -> argparse.ArgumentParser:
|
|
296
|
+
parser = argparse.ArgumentParser(prog="task_phase_runtime.py", description="phase-aware admission for FlowPilot task budgets")
|
|
297
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
298
|
+
init_cmd = commands.add_parser("init")
|
|
299
|
+
_common(init_cmd)
|
|
300
|
+
status_cmd = commands.add_parser("status")
|
|
301
|
+
_common(status_cmd)
|
|
302
|
+
status_cmd.add_argument("--phase", choices=PHASES, required=True)
|
|
303
|
+
reserve_cmd = commands.add_parser("reserve")
|
|
304
|
+
_common(reserve_cmd)
|
|
305
|
+
reserve_cmd.add_argument("--phase", choices=PHASES, required=True)
|
|
306
|
+
reserve_cmd.add_argument("--kind", choices=tuple(RESERVATION_PHASE), required=True)
|
|
307
|
+
reserve_cmd.add_argument("--reservation-id", required=True)
|
|
308
|
+
reserve_cmd.add_argument("--fingerprint", required=True)
|
|
309
|
+
return parser
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def main(argv: list[str] | None = None) -> int:
|
|
313
|
+
parser = _parser()
|
|
314
|
+
ns = parser.parse_args(argv)
|
|
315
|
+
try:
|
|
316
|
+
plan = _plan(_json(ns.plan_json, "plan"))
|
|
317
|
+
current_now = _cli_now(ns.now)
|
|
318
|
+
if ns.command == "init":
|
|
319
|
+
result = init(ns.state_file, ns.task_id, plan, current_now)
|
|
320
|
+
elif ns.command == "status":
|
|
321
|
+
result = status(ns.state_file, ns.task_id, plan, ns.phase, current_now)
|
|
322
|
+
else:
|
|
323
|
+
result = reserve_phase(
|
|
324
|
+
ns.state_file,
|
|
325
|
+
ns.task_id,
|
|
326
|
+
plan,
|
|
327
|
+
ns.phase,
|
|
328
|
+
ns.kind,
|
|
329
|
+
ns.reservation_id,
|
|
330
|
+
ns.fingerprint,
|
|
331
|
+
current_now,
|
|
332
|
+
)
|
|
333
|
+
except LedgerError as exc:
|
|
334
|
+
print(str(exc), file=sys.stderr)
|
|
335
|
+
return 2
|
|
336
|
+
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
|
|
337
|
+
return 0
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
if __name__ == "__main__":
|
|
341
|
+
raise SystemExit(main())
|