okstra 0.137.0 → 0.138.0
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.
- package/docs/architecture.md +3 -2
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/project-structure-overview.md +24 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/prompts/lead/adapters/claude-code.md +1 -0
- package/runtime/prompts/lead/adapters/codex.md +1 -0
- package/runtime/prompts/lead/adapters/external.md +1 -0
- package/runtime/prompts/lead/report-writer.md +11 -10
- package/runtime/prompts/lead/team-contract.md +4 -2
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
- package/runtime/python/okstra_ctl/codex_dispatch.py +158 -365
- package/runtime/python/okstra_ctl/dispatch_core.py +118 -138
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +1053 -0
- package/runtime/python/okstra_ctl/render.py +9 -4
- package/runtime/python/okstra_ctl/run.py +40 -89
- package/runtime/python/okstra_ctl/stage_targets.py +232 -46
- package/runtime/python/okstra_ctl/wizard.py +2 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +73 -0
- package/runtime/python/okstra_ctl/worktree.py +37 -29
- package/runtime/validators/lib/fixtures.sh +2 -0
|
@@ -29,7 +29,6 @@ from .dispatch_state import (
|
|
|
29
29
|
string_value as _string_value,
|
|
30
30
|
utc_now as _utc_now,
|
|
31
31
|
validate_initial_prompts as _validate_initial_prompts,
|
|
32
|
-
worker_prompt_path as _worker_prompt_path,
|
|
33
32
|
worker_state as _worker_state,
|
|
34
33
|
worktree_path as _worktree_path,
|
|
35
34
|
write_json as _write_json,
|
|
@@ -39,13 +38,15 @@ from .final_report_paths import (
|
|
|
39
38
|
final_report_markdown_path as _final_report_markdown_path,
|
|
40
39
|
)
|
|
41
40
|
from .lead_events import LeadEvent, append_lead_event
|
|
41
|
+
from .initial_prompt_materialization import (
|
|
42
|
+
InitialPromptMaterializationError,
|
|
43
|
+
InitialPromptMaterializationRequest,
|
|
44
|
+
InitialPromptWorkerRequest,
|
|
45
|
+
PromptDeliveryMode,
|
|
46
|
+
materialize_initial_prompts,
|
|
47
|
+
)
|
|
42
48
|
from .path_hints import hydrate_active_run_context
|
|
43
|
-
from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
|
|
44
|
-
from .report_language import resolve_report_language
|
|
45
|
-
from .worker_prompt_body import analysis_prompt_body, report_writer_prompt_body
|
|
46
49
|
from .worker_prompt_body import REPORT_WRITER_WORKER_ID
|
|
47
|
-
from .worker_prompt_contract import PromptRecord, validate_initial_prompt_records
|
|
48
|
-
from .worker_prompt_policy import resolve_prompt_plan_for_manifest
|
|
49
50
|
from .worker_artifact_paths import audit_sidecar_rel
|
|
50
51
|
from .wrapper_status import read_wrapper_status, status_path_for_prompt
|
|
51
52
|
|
|
@@ -116,6 +117,15 @@ class _BuildOptions:
|
|
|
116
117
|
dispatch_kind: str
|
|
117
118
|
|
|
118
119
|
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class _RosterWorkerFacts:
|
|
122
|
+
worker_id: str
|
|
123
|
+
model: str
|
|
124
|
+
provider: str
|
|
125
|
+
result_path: str
|
|
126
|
+
role: str
|
|
127
|
+
|
|
128
|
+
|
|
119
129
|
def build_dispatch_plan(
|
|
120
130
|
*,
|
|
121
131
|
project_root: Path,
|
|
@@ -148,18 +158,22 @@ def build_dispatch_plan(
|
|
|
148
158
|
unsupported_worker_label=unsupported_worker_label,
|
|
149
159
|
dispatch_kind=dispatch_kind,
|
|
150
160
|
)
|
|
151
|
-
|
|
152
|
-
project_root,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
if jobs_file:
|
|
162
|
+
jobs = _jobs_from_file(project_root, workspace_root, jobs_file, options)
|
|
163
|
+
_validate_initial_prompts(manifest, jobs)
|
|
164
|
+
else:
|
|
165
|
+
jobs = _jobs_from_roster(
|
|
166
|
+
project_root,
|
|
167
|
+
workspace_root,
|
|
168
|
+
manifest_path,
|
|
169
|
+
manifest,
|
|
170
|
+
team_state,
|
|
171
|
+
active_context,
|
|
172
|
+
requested_workers,
|
|
173
|
+
enable_report_writer,
|
|
174
|
+
report_writer_model,
|
|
175
|
+
options,
|
|
176
|
+
)
|
|
163
177
|
return DispatchPlan(
|
|
164
178
|
project_root=project_root,
|
|
165
179
|
workspace_root=workspace_root.resolve(),
|
|
@@ -220,6 +234,7 @@ def await_dispatches(
|
|
|
220
234
|
def _jobs_from_roster(
|
|
221
235
|
project_root: Path,
|
|
222
236
|
workspace_root: Path,
|
|
237
|
+
manifest_path: Path,
|
|
223
238
|
manifest: Mapping[str, Any],
|
|
224
239
|
team_state: Mapping[str, Any],
|
|
225
240
|
active_context: Mapping[str, Any],
|
|
@@ -230,12 +245,27 @@ def _jobs_from_roster(
|
|
|
230
245
|
) -> list[WorkerJob]:
|
|
231
246
|
selected = _select_workers(manifest, requested_workers, enable_report_writer, options)
|
|
232
247
|
_mark_roster_skips(project_root, manifest, team_state, selected, requested_workers, options)
|
|
248
|
+
worker_facts, prompt_paths = _materialize_roster_prompts(
|
|
249
|
+
project_root,
|
|
250
|
+
workspace_root,
|
|
251
|
+
manifest_path,
|
|
252
|
+
manifest,
|
|
253
|
+
team_state,
|
|
254
|
+
selected,
|
|
255
|
+
report_writer_model,
|
|
256
|
+
options,
|
|
257
|
+
)
|
|
233
258
|
return [
|
|
234
259
|
_job_from_roster_worker(
|
|
235
|
-
project_root,
|
|
236
|
-
|
|
260
|
+
project_root,
|
|
261
|
+
workspace_root,
|
|
262
|
+
manifest,
|
|
263
|
+
active_context,
|
|
264
|
+
fact,
|
|
265
|
+
prompt_path,
|
|
266
|
+
options,
|
|
237
267
|
)
|
|
238
|
-
for
|
|
268
|
+
for fact, prompt_path in zip(worker_facts, prompt_paths, strict=True)
|
|
239
269
|
]
|
|
240
270
|
|
|
241
271
|
|
|
@@ -270,50 +300,87 @@ def _job_from_roster_worker(
|
|
|
270
300
|
project_root: Path,
|
|
271
301
|
workspace_root: Path,
|
|
272
302
|
manifest: Mapping[str, Any],
|
|
273
|
-
team_state: Mapping[str, Any],
|
|
274
303
|
active_context: Mapping[str, Any],
|
|
275
|
-
|
|
276
|
-
|
|
304
|
+
fact: _RosterWorkerFacts,
|
|
305
|
+
prompt_path: Path,
|
|
277
306
|
options: _BuildOptions,
|
|
278
307
|
) -> WorkerJob:
|
|
279
|
-
|
|
280
|
-
provider = _provider_for_worker(worker_id)
|
|
281
|
-
model = _model_for_worker(worker_id, state, report_writer_model)
|
|
282
|
-
prompt_rel = _worker_prompt_path(manifest, worker_id)
|
|
283
|
-
result_rel = _require_string(state, "resultPath")
|
|
284
|
-
prompt_path = _resolve_project_path(project_root, prompt_rel)
|
|
285
|
-
result_path = _resolve_project_path(project_root, result_rel)
|
|
286
|
-
_materialize_prompt_if_missing(
|
|
287
|
-
prompt_path=prompt_path,
|
|
288
|
-
prompt_rel=prompt_rel,
|
|
289
|
-
worker_id=worker_id,
|
|
290
|
-
result_rel=result_rel,
|
|
291
|
-
project_root=project_root,
|
|
292
|
-
manifest=manifest,
|
|
293
|
-
team_state=team_state,
|
|
294
|
-
active_context=active_context,
|
|
295
|
-
dispatch_kind=options.dispatch_kind,
|
|
296
|
-
model=model,
|
|
297
|
-
role=_string_value(state.get("role")) or "worker",
|
|
298
|
-
)
|
|
308
|
+
result_path = _resolve_project_path(project_root, fact.result_path)
|
|
299
309
|
return WorkerJob(
|
|
300
|
-
worker_id=worker_id,
|
|
301
|
-
provider=provider,
|
|
310
|
+
worker_id=fact.worker_id,
|
|
311
|
+
provider=fact.provider,
|
|
302
312
|
backend=options.default_backend,
|
|
303
313
|
project_root=project_root,
|
|
304
|
-
model_execution_value=model,
|
|
305
|
-
wrapper_path=_resolve_wrapper(provider, workspace_root, options),
|
|
314
|
+
model_execution_value=fact.model,
|
|
315
|
+
wrapper_path=_resolve_wrapper(fact.provider, workspace_root, options),
|
|
306
316
|
prompt_path=prompt_path,
|
|
307
|
-
result_path=_result_path_for_worker(
|
|
317
|
+
result_path=_result_path_for_worker(
|
|
318
|
+
fact.worker_id, result_path, manifest, project_root
|
|
319
|
+
),
|
|
308
320
|
worker_result_path=result_path,
|
|
309
|
-
completion_paths=_completion_paths(
|
|
321
|
+
completion_paths=_completion_paths(
|
|
322
|
+
fact.worker_id, result_path, manifest, project_root
|
|
323
|
+
),
|
|
310
324
|
worktree_path=_worktree_path(manifest, active_context),
|
|
311
|
-
role=
|
|
325
|
+
role=fact.role,
|
|
312
326
|
idle_timeout_seconds=options.idle_timeout_seconds,
|
|
313
327
|
dispatch_kind=options.dispatch_kind,
|
|
314
328
|
)
|
|
315
329
|
|
|
316
330
|
|
|
331
|
+
def _materialize_roster_prompts(
|
|
332
|
+
project_root: Path,
|
|
333
|
+
workspace_root: Path,
|
|
334
|
+
manifest_path: Path,
|
|
335
|
+
manifest: Mapping[str, Any],
|
|
336
|
+
team_state: Mapping[str, Any],
|
|
337
|
+
selected: Sequence[str],
|
|
338
|
+
report_writer_model: str,
|
|
339
|
+
options: _BuildOptions,
|
|
340
|
+
) -> tuple[tuple[_RosterWorkerFacts, ...], tuple[Path, ...]]:
|
|
341
|
+
worker_facts = tuple(
|
|
342
|
+
_roster_worker_facts(team_state, worker_id, report_writer_model)
|
|
343
|
+
for worker_id in selected
|
|
344
|
+
)
|
|
345
|
+
try:
|
|
346
|
+
prompt_paths = materialize_initial_prompts(
|
|
347
|
+
InitialPromptMaterializationRequest(
|
|
348
|
+
project_root=project_root,
|
|
349
|
+
run_manifest_path=manifest_path,
|
|
350
|
+
runtime_root=workspace_root,
|
|
351
|
+
delivery_mode=_delivery_mode(options.default_backend),
|
|
352
|
+
workers=tuple(
|
|
353
|
+
InitialPromptWorkerRequest(fact.worker_id, fact.model)
|
|
354
|
+
for fact in worker_facts
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
except InitialPromptMaterializationError as exc:
|
|
359
|
+
raise DispatchError(f"{exc.reason}: {exc}") from exc
|
|
360
|
+
return worker_facts, prompt_paths
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _roster_worker_facts(
|
|
364
|
+
team_state: Mapping[str, Any],
|
|
365
|
+
worker_id: str,
|
|
366
|
+
report_writer_model: str,
|
|
367
|
+
) -> _RosterWorkerFacts:
|
|
368
|
+
state = _worker_state(team_state, worker_id)
|
|
369
|
+
return _RosterWorkerFacts(
|
|
370
|
+
worker_id=worker_id,
|
|
371
|
+
model=_model_for_worker(worker_id, state, report_writer_model),
|
|
372
|
+
provider=_provider_for_worker(worker_id),
|
|
373
|
+
result_path=_require_string(state, "resultPath"),
|
|
374
|
+
role=_string_value(state.get("role")) or "worker",
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _delivery_mode(default_backend: str) -> PromptDeliveryMode:
|
|
379
|
+
if default_backend == BACKEND_TMUX_PANE:
|
|
380
|
+
return PromptDeliveryMode.LAZY_PATH_REFERENCE
|
|
381
|
+
return PromptDeliveryMode.EAGER_INCLUDE
|
|
382
|
+
|
|
383
|
+
|
|
317
384
|
def _jobs_from_file(
|
|
318
385
|
project_root: Path,
|
|
319
386
|
workspace_root: Path,
|
|
@@ -728,91 +795,6 @@ def _resolve_wrapper(provider: str, workspace_root: Path, options: _BuildOptions
|
|
|
728
795
|
raise DispatchError(f"{script} not found (searched: {', '.join(str(c) for c in candidates)})")
|
|
729
796
|
|
|
730
797
|
|
|
731
|
-
def _materialize_prompt_if_missing(
|
|
732
|
-
*,
|
|
733
|
-
prompt_path: Path,
|
|
734
|
-
prompt_rel: str,
|
|
735
|
-
worker_id: str,
|
|
736
|
-
result_rel: str,
|
|
737
|
-
project_root: Path,
|
|
738
|
-
manifest: Mapping[str, Any],
|
|
739
|
-
team_state: Mapping[str, Any],
|
|
740
|
-
active_context: Mapping[str, Any],
|
|
741
|
-
dispatch_kind: str,
|
|
742
|
-
model: str,
|
|
743
|
-
role: str,
|
|
744
|
-
) -> None:
|
|
745
|
-
if prompt_path.is_file():
|
|
746
|
-
return
|
|
747
|
-
prompt_path.parent.mkdir(parents=True, exist_ok=True)
|
|
748
|
-
audit_source_rel = ""
|
|
749
|
-
if worker_id == REPORT_WRITER_WORKER_ID:
|
|
750
|
-
# The report-writer's Result Path is the report data.json; its own worker
|
|
751
|
-
# result stays the markdown the audit sidecar is derived from.
|
|
752
|
-
audit_source_rel = result_rel
|
|
753
|
-
result_rel = str(_final_report_data_path(
|
|
754
|
-
Path(_require_string(manifest, "expectedReportPath"))
|
|
755
|
-
))
|
|
756
|
-
try:
|
|
757
|
-
headers = worker_prompt_headers(
|
|
758
|
-
project_root=project_root,
|
|
759
|
-
prompt_rel=prompt_rel,
|
|
760
|
-
result_rel=result_rel,
|
|
761
|
-
audit_source_rel=audit_source_rel or None,
|
|
762
|
-
worker_id=worker_id,
|
|
763
|
-
dispatch_kind=dispatch_kind,
|
|
764
|
-
manifest=manifest,
|
|
765
|
-
active_context=active_context,
|
|
766
|
-
)
|
|
767
|
-
except WorkerPromptHeaderError as exc:
|
|
768
|
-
raise DispatchError(str(exc)) from exc
|
|
769
|
-
try:
|
|
770
|
-
plan = resolve_prompt_plan_for_manifest(
|
|
771
|
-
manifest=manifest,
|
|
772
|
-
worker_id=worker_id,
|
|
773
|
-
dispatch_kind=dispatch_kind,
|
|
774
|
-
)
|
|
775
|
-
except ValueError as exc:
|
|
776
|
-
raise DispatchError(str(exc)) from exc
|
|
777
|
-
lines = list(headers)
|
|
778
|
-
if plan.audience == "report-writer":
|
|
779
|
-
lines.extend(
|
|
780
|
-
report_writer_prompt_body(
|
|
781
|
-
manifest,
|
|
782
|
-
active_context,
|
|
783
|
-
team_state,
|
|
784
|
-
model,
|
|
785
|
-
resolve_report_language(project_root, manifest, active_context),
|
|
786
|
-
)
|
|
787
|
-
)
|
|
788
|
-
elif plan.audience in {
|
|
789
|
-
"analysis",
|
|
790
|
-
"implementation-executor",
|
|
791
|
-
"implementation-verifier",
|
|
792
|
-
}:
|
|
793
|
-
if _string_value(manifest.get("taskType")) == "implementation":
|
|
794
|
-
worktree = _worktree_path(manifest, active_context)
|
|
795
|
-
if not worktree:
|
|
796
|
-
raise DispatchError(
|
|
797
|
-
"implementation prompt generation requires a worktree path"
|
|
798
|
-
)
|
|
799
|
-
lines.append(f"**Worktree:** {worktree}")
|
|
800
|
-
try:
|
|
801
|
-
lines.extend(
|
|
802
|
-
analysis_prompt_body(
|
|
803
|
-
manifest,
|
|
804
|
-
active_context,
|
|
805
|
-
worker_id,
|
|
806
|
-
model,
|
|
807
|
-
role,
|
|
808
|
-
plan,
|
|
809
|
-
)
|
|
810
|
-
)
|
|
811
|
-
except ValueError as exc:
|
|
812
|
-
raise DispatchError(str(exc)) from exc
|
|
813
|
-
prompt_path.write_text("\n".join([*lines, ""]), encoding="utf-8")
|
|
814
|
-
|
|
815
|
-
|
|
816
798
|
def _provider_for_worker(worker_id: str) -> str:
|
|
817
799
|
if worker_id == REPORT_WRITER_WORKER_ID:
|
|
818
800
|
return "claude"
|
|
@@ -870,5 +852,3 @@ def _run_seq(manifest: Mapping[str, Any]) -> str:
|
|
|
870
852
|
if isinstance(seqs, Mapping):
|
|
871
853
|
return _string_value(seqs.get("manifests")) or "001"
|
|
872
854
|
return "001"
|
|
873
|
-
|
|
874
|
-
|