okstra 0.189.3 โ 0.190.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/README.md +1 -1
- package/dist/cli-registry.mjs +9 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/chat/chat.mjs +54 -4
- package/dist/commands/chat/chat.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +12 -5
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/lifecycle/uninstall.mjs +2 -1
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
- package/dist/lib/host-config.d.mts +5 -3
- package/dist/lib/host-config.mjs +89 -31
- package/dist/lib/host-config.mjs.map +1 -1
- package/docs/architecture/storage-model.md +4 -1
- package/docs/architecture.md +3 -3
- package/docs/cli.md +11 -9
- package/docs/for-ai/skills/okstra-brief-gen.md +1 -1
- package/docs/for-ai/skills/okstra-chat.md +3 -3
- package/docs/for-ai/skills/okstra-inspect.md +11 -1
- package/docs/for-ai/skills/okstra-rollup.md +1 -0
- package/docs/for-ai/skills/okstra-run.md +5 -4
- package/docs/for-ai/skills/okstra-user-response.md +1 -1
- package/docs/project-structure-overview.md +17 -5
- package/docs/task-process/README.md +1 -1
- package/docs/task-process/error-analysis.md +2 -2
- package/docs/task-process/final-verification.md +1 -1
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +8 -6
- package/docs/task-process/requirements-discovery.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +4 -0
- package/runtime/bin/okstra-report-translate.py +10 -4
- package/runtime/prompts/launch.template.md +11 -5
- package/runtime/prompts/lead/adapters/cmux.md +4 -7
- package/runtime/prompts/lead/context-loader.md +2 -0
- package/runtime/prompts/lead/convergence.md +20 -83
- package/runtime/prompts/lead/okstra-lead-contract.md +24 -6
- package/runtime/prompts/lead/plan-body-verification.md +10 -3
- package/runtime/prompts/lead/report-writer.md +15 -5
- package/runtime/prompts/lead/team-contract.md +15 -9
- package/runtime/prompts/profiles/_common-contract.md +2 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -0
- package/runtime/prompts/profiles/_implementation-diff-review.md +4 -0
- package/runtime/prompts/profiles/_implementation-executor.md +9 -5
- package/runtime/prompts/profiles/_implementation-self-check.md +2 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -4
- package/runtime/prompts/profiles/_stage-discipline.md +12 -5
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/prompts/profiles/implementation.md +2 -0
- package/runtime/prompts/profiles/release-handoff.md +4 -3
- package/runtime/prompts/wizard/prompts.ko.json +2 -1
- package/runtime/python/okstra_ctl/adapters/dispatch/cmux.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +9 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +17 -3
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +3 -3
- package/runtime/python/okstra_ctl/agent/invocation.py +107 -5
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +16 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +3 -2
- package/runtime/python/okstra_ctl/agent/prompt_cli/jobs.py +87 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +72 -3
- package/runtime/python/okstra_ctl/analysis_packet.py +47 -6
- package/runtime/python/okstra_ctl/cmux.py +34 -13
- package/runtime/python/okstra_ctl/consumers.py +30 -7
- package/runtime/python/okstra_ctl/convergence.py +110 -21
- package/runtime/python/okstra_ctl/convergence_provenance.py +83 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +127 -5
- package/runtime/python/okstra_ctl/dispatch_state.py +17 -9
- package/runtime/python/okstra_ctl/domain/host.py +5 -0
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +1 -0
- package/runtime/python/okstra_ctl/error_log_write.py +44 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +84 -7
- package/runtime/python/okstra_ctl/fixed_text.py +25 -7
- package/runtime/python/okstra_ctl/group_context.py +475 -10
- package/runtime/python/okstra_ctl/handoff.py +109 -32
- package/runtime/python/okstra_ctl/handoff_verification.py +80 -0
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +52 -10
- package/runtime/python/okstra_ctl/lead_progress.py +291 -0
- package/runtime/python/okstra_ctl/model_io/renderers.py +60 -1
- package/runtime/python/okstra_ctl/model_io_cli.py +6 -1
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +4 -0
- package/runtime/python/okstra_ctl/recap.py +179 -17
- package/runtime/python/okstra_ctl/reconcile.py +55 -4
- package/runtime/python/okstra_ctl/render.py +34 -8
- package/runtime/python/okstra_ctl/report_assembly.py +17 -10
- package/runtime/python/okstra_ctl/report_finalize.py +204 -11
- package/runtime/python/okstra_ctl/report_html/common.py +241 -113
- package/runtime/python/okstra_ctl/report_html/context_links.py +121 -0
- package/runtime/python/okstra_ctl/report_html/models.py +11 -5
- package/runtime/python/okstra_ctl/report_html/render.py +69 -17
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +8 -1
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +8 -1
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +11 -1
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +13 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +9 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_option_selection.py +29 -2
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +14 -9
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +8 -1
- package/runtime/python/okstra_ctl/report_html/view_models/project_analysis.py +15 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +7 -1
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +9 -1
- package/runtime/python/okstra_ctl/report_translation.py +58 -1
- package/runtime/python/okstra_ctl/run.py +54 -14
- package/runtime/python/okstra_ctl/stage_targets.py +66 -0
- package/runtime/python/okstra_ctl/verdict_blocks.py +39 -12
- package/runtime/python/okstra_ctl/wizard/__init__.py +148 -0
- package/runtime/python/okstra_ctl/wizard/__main__.py +13 -0
- package/runtime/python/okstra_ctl/wizard/cli.py +176 -0
- package/runtime/python/okstra_ctl/wizard/confirmation.py +331 -0
- package/runtime/python/okstra_ctl/wizard/engine.py +363 -0
- package/runtime/python/okstra_ctl/wizard/ids.py +411 -0
- package/runtime/python/okstra_ctl/wizard/prompts.py +202 -0
- package/runtime/python/okstra_ctl/wizard/registry.py +836 -0
- package/runtime/python/okstra_ctl/wizard/render.py +189 -0
- package/runtime/python/okstra_ctl/wizard/roles.py +737 -0
- package/runtime/python/okstra_ctl/wizard/sources.py +703 -0
- package/runtime/python/okstra_ctl/wizard/state.py +689 -0
- package/runtime/python/okstra_ctl/wizard/statefile.py +145 -0
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +558 -0
- package/runtime/python/okstra_ctl/wizard/steps_identity.py +828 -0
- package/runtime/python/okstra_ctl/wizard/steps_options.py +340 -0
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +959 -0
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +672 -0
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +43 -3
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +14 -0
- package/runtime/python/okstra_ctl/workflow.py +2 -2
- package/runtime/python/okstra_ctl/worktree/__init__.py +2 -0
- package/runtime/python/okstra_ctl/worktree/cleanliness.py +7 -4
- package/runtime/python/okstra_ctl/worktree/git_ops.py +7 -0
- package/runtime/python/okstra_ctl/worktree/naming.py +15 -6
- package/runtime/python/okstra_ctl/worktree/provision.py +2 -1
- package/runtime/python/okstra_ctl/worktree_registry.py +27 -0
- package/runtime/python/okstra_ctl/wrapper_status.py +4 -0
- package/runtime/schemas/final-report-v2.0.schema.json +4 -0
- package/runtime/schemas/final-report-v3.0.schema.json +4 -0
- package/runtime/skills/okstra-brief-gen/SKILL.md +22 -3
- package/runtime/skills/okstra-chat/SKILL.md +8 -6
- package/runtime/skills/okstra-inspect/SKILL.md +2 -2
- package/runtime/skills/okstra-inspect/facets/recap.md +52 -4
- package/runtime/skills/okstra-rollup/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +17 -7
- package/runtime/skills/okstra-schedule-gen/SKILL.md +2 -0
- package/runtime/skills/okstra-user-response/SKILL.md +1 -1
- package/runtime/templates/reports/brief.template.md +15 -7
- package/runtime/templates/reports/final-verification-input.template.md +2 -0
- package/runtime/templates/reports/group-context.template.md +7 -0
- package/runtime/templates/reports/html/base.template.html +13 -3
- package/runtime/templates/reports/html/i18n/en.json +43 -5
- package/runtime/templates/reports/html/i18n/ko.json +43 -5
- package/runtime/templates/reports/html/macros/forms.html +4 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +11 -1
- package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +16 -15
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +5 -5
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +2 -2
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +1 -1
- package/runtime/templates/reports/implementation-planning-input.template.md +3 -0
- package/runtime/templates/reports/improvement-discovery-input.template.md +3 -0
- package/runtime/templates/reports/release-handoff-input.template.md +5 -3
- package/runtime/templates/reports/schedule.template.md +13 -4
- package/runtime/templates/reports/task-brief.template.md +1 -1
- package/runtime/templates/reverify-output-contract.md +5 -0
- package/runtime/templates/worker-error-contract.md +2 -0
- package/runtime/templates/worker-prompt-preamble.md +1 -1
- package/runtime/validators/validate-report-views.py +30 -1
- package/runtime/validators/validate-run.py +194 -25
- package/runtime/validators/validate_session_conformance.py +102 -2
- package/runtime/python/okstra_ctl/wizard.py +0 -7168
|
@@ -117,6 +117,14 @@ _NESTED_KEYS = {
|
|
|
117
117
|
"promptDigest",
|
|
118
118
|
},
|
|
119
119
|
}
|
|
120
|
+
EXECUTION_DELIVERY_CONTRACT = "execution-identity-v1"
|
|
121
|
+
EXECUTION_DELIVERY_PREFIXES = (
|
|
122
|
+
"**Execution delivery contract:**", "**Invocation metadata path:**",
|
|
123
|
+
*(f"**{field}:**" for field in (
|
|
124
|
+
"participantRef", "roleExecutionRef", "executionLabel", "invocationRef",
|
|
125
|
+
"dutyId", "attempt",
|
|
126
|
+
)),
|
|
127
|
+
)
|
|
120
128
|
|
|
121
129
|
|
|
122
130
|
class AgentInvocationError(RuntimeError):
|
|
@@ -586,6 +594,12 @@ def materialize_retry_invocation(
|
|
|
586
594
|
manifest_path = _project_path(
|
|
587
595
|
project_root, source.get("runManifestPath"), must_exist=True
|
|
588
596
|
)
|
|
597
|
+
errors = verify_agent_invocation(
|
|
598
|
+
metadata_path, project_root=project_root,
|
|
599
|
+
expected_run_manifest_path=manifest_path,
|
|
600
|
+
)
|
|
601
|
+
if errors:
|
|
602
|
+
raise AgentInvocationError("cannot retry invalid source invocation: " + "; ".join(errors))
|
|
589
603
|
prompt_path = _project_path(
|
|
590
604
|
project_root, prompt_spec.get("path"), must_exist=True
|
|
591
605
|
)
|
|
@@ -600,10 +614,14 @@ def materialize_retry_invocation(
|
|
|
600
614
|
retry_metadata["invocationId"] = retry_invocation_id
|
|
601
615
|
retry_metadata["attempt"] = attempt
|
|
602
616
|
retry_metadata["prompt"] = {
|
|
617
|
+
**prompt_spec,
|
|
603
618
|
"path": _project_relative(
|
|
604
619
|
retry_prompt_path, project_root, must_exist=False
|
|
605
620
|
)
|
|
606
621
|
}
|
|
622
|
+
retry_body = _retry_prompt_bytes(
|
|
623
|
+
prompt_path.read_bytes(), retry_metadata, retry_prompt_path, retry_metadata_path,
|
|
624
|
+
)
|
|
607
625
|
manifest = _load_json_object(manifest_path, "run manifest")
|
|
608
626
|
contract = manifest.get("agentContract")
|
|
609
627
|
if not isinstance(contract, dict):
|
|
@@ -636,7 +654,7 @@ def materialize_retry_invocation(
|
|
|
636
654
|
_pretty_json(reservation),
|
|
637
655
|
)
|
|
638
656
|
with _exclusive_lock(_prompt_lock_path(retry_prompt_path)):
|
|
639
|
-
_publish_or_reuse_exact(retry_prompt_path,
|
|
657
|
+
_publish_or_reuse_exact(retry_prompt_path, retry_body)
|
|
640
658
|
_publish_or_reuse_exact(
|
|
641
659
|
retry_metadata_path, _pretty_json(retry_metadata)
|
|
642
660
|
)
|
|
@@ -665,6 +683,34 @@ def materialize_retry_invocation(
|
|
|
665
683
|
)
|
|
666
684
|
|
|
667
685
|
|
|
686
|
+
def _retry_prompt_bytes(
|
|
687
|
+
body: bytes, metadata: dict[str, object], prompt_path: Path, metadata_path: Path,
|
|
688
|
+
) -> bytes:
|
|
689
|
+
if metadata["prompt"].get("deliveryContract") != EXECUTION_DELIVERY_CONTRACT:
|
|
690
|
+
return body
|
|
691
|
+
prefix, remainder = body.decode("utf-8").split("\n\n## Duty Contract\n\n", 1)
|
|
692
|
+
paths = {
|
|
693
|
+
"**Prompt History Path:**": metadata["prompt"]["path"],
|
|
694
|
+
"Assigned worker prompt history path:": str(prompt_path),
|
|
695
|
+
}
|
|
696
|
+
lines = [line for line in prefix.splitlines() if not line.startswith(EXECUTION_DELIVERY_PREFIXES)]
|
|
697
|
+
lines = [next((f"{label} {path}" for label, path in paths.items() if line.startswith(label)), line) for line in lines]
|
|
698
|
+
lines.extend(_execution_delivery_lines(metadata, metadata_path))
|
|
699
|
+
text = "\n".join(lines) + "\n\n## Duty Contract\n\n" + remainder
|
|
700
|
+
prefix, task = _split_prompt(text)
|
|
701
|
+
instruction = {
|
|
702
|
+
"anchorLines": _extract_anchor_lines(prefix),
|
|
703
|
+
"body": task.rstrip("\n"),
|
|
704
|
+
"sourcePaths": metadata["instruction"]["sourcePaths"],
|
|
705
|
+
}
|
|
706
|
+
metadata["digests"] = {
|
|
707
|
+
**metadata["digests"],
|
|
708
|
+
"instructionDigest": _sha256(_canonical_json(instruction)),
|
|
709
|
+
"promptDigest": _sha256(text.encode("utf-8")),
|
|
710
|
+
}
|
|
711
|
+
return text.encode("utf-8")
|
|
712
|
+
|
|
713
|
+
|
|
668
714
|
def compose_agent_prompt(request: AgentInvocationRequest) -> str:
|
|
669
715
|
"""Render and validate a prompt candidate without publishing artifacts."""
|
|
670
716
|
_validate_request(request)
|
|
@@ -995,9 +1041,11 @@ def _materialize(request: AgentInvocationRequest) -> _MaterializedInvocation:
|
|
|
995
1041
|
duty = catalog[request.audience]
|
|
996
1042
|
if _request_identity_version(request) == 2 and request.duty_id != duty.id:
|
|
997
1043
|
raise AgentInvocationError("v2 execution identity duty does not match audience")
|
|
998
|
-
|
|
1044
|
+
metadata = _metadata_payload(request, duty, {})
|
|
1045
|
+
delivery = _execution_delivery_lines(metadata, request.metadata_path)
|
|
1046
|
+
prompt_bytes = _render_prompt(request, common, duty, delivery).encode("utf-8")
|
|
999
1047
|
digests = _invocation_digests(request, duty, prompt_bytes)
|
|
1000
|
-
metadata =
|
|
1048
|
+
metadata["digests"] = digests
|
|
1001
1049
|
prepared = PreparedAgentInvocation(
|
|
1002
1050
|
invocation_id=request.invocation_id,
|
|
1003
1051
|
worker_id=request.worker_id,
|
|
@@ -1212,6 +1260,7 @@ def _render_prompt(
|
|
|
1212
1260
|
request: AgentInvocationRequest,
|
|
1213
1261
|
common: DutyContract,
|
|
1214
1262
|
duty: DutyContract,
|
|
1263
|
+
delivery: tuple[str, ...] = (),
|
|
1215
1264
|
) -> str:
|
|
1216
1265
|
assignment = request.assignment
|
|
1217
1266
|
task_body = request.instruction.body.rstrip("\n")
|
|
@@ -1255,6 +1304,7 @@ def _render_prompt(
|
|
|
1255
1304
|
header.append(f"{delivery_mode} eager-include")
|
|
1256
1305
|
if assignment.host_model_value is not None:
|
|
1257
1306
|
header.append(f"**Host model value:** {assignment.host_model_value}")
|
|
1307
|
+
header.extend(delivery)
|
|
1258
1308
|
duty_body = f"{common.body.rstrip()}\n\n{duty.body.rstrip()}"
|
|
1259
1309
|
return (
|
|
1260
1310
|
"\n".join(header)
|
|
@@ -1367,6 +1417,7 @@ def _metadata_payload(
|
|
|
1367
1417
|
"attempt": request.attempt,
|
|
1368
1418
|
})
|
|
1369
1419
|
payload.pop("workerId")
|
|
1420
|
+
payload["prompt"]["deliveryContract"] = EXECUTION_DELIVERY_CONTRACT
|
|
1370
1421
|
return payload
|
|
1371
1422
|
|
|
1372
1423
|
|
|
@@ -1591,7 +1642,7 @@ def _replace_undispatched(
|
|
|
1591
1642
|
"""
|
|
1592
1643
|
if not request.replace_undispatched:
|
|
1593
1644
|
raise _existing_conflict(f"existing prompt differs ({difference})")
|
|
1594
|
-
dispatched =
|
|
1645
|
+
dispatched = invocation_dispatch_ids(request)
|
|
1595
1646
|
if dispatched:
|
|
1596
1647
|
raise _existing_conflict(
|
|
1597
1648
|
"existing prompt differs and this invocation was already "
|
|
@@ -1607,7 +1658,7 @@ def _replace_undispatched(
|
|
|
1607
1658
|
)
|
|
1608
1659
|
|
|
1609
1660
|
|
|
1610
|
-
def
|
|
1661
|
+
def invocation_dispatch_ids(request: AgentInvocationRequest) -> set[str]:
|
|
1611
1662
|
"""Dispatch rows in team-state that already reference this invocation id.
|
|
1612
1663
|
|
|
1613
1664
|
Both ledgers are read: `agentDispatches` is written by the host-native
|
|
@@ -1746,9 +1797,56 @@ def _verify_prompt_file(
|
|
|
1746
1797
|
errors = []
|
|
1747
1798
|
errors.extend(_verify_instruction_digest(metadata, body))
|
|
1748
1799
|
errors.extend(_verify_model_header(metadata, body))
|
|
1800
|
+
errors.extend(_verify_execution_delivery(metadata, path, body))
|
|
1749
1801
|
return errors
|
|
1750
1802
|
|
|
1751
1803
|
|
|
1804
|
+
def _execution_delivery_lines(
|
|
1805
|
+
metadata: Mapping[str, object], metadata_path: Path,
|
|
1806
|
+
) -> tuple[str, ...]:
|
|
1807
|
+
if metadata.get("executionIdentityVersion") != 2:
|
|
1808
|
+
return ()
|
|
1809
|
+
values = (
|
|
1810
|
+
EXECUTION_DELIVERY_CONTRACT, str(metadata_path.resolve()),
|
|
1811
|
+
*(str(metadata[key]) for key in (
|
|
1812
|
+
"participantRef", "roleExecutionRef", "executionLabel", "invocationRef",
|
|
1813
|
+
"dutyId", "attempt",
|
|
1814
|
+
)),
|
|
1815
|
+
)
|
|
1816
|
+
return tuple(f"{prefix} {value}" for prefix, value in zip(EXECUTION_DELIVERY_PREFIXES, values))
|
|
1817
|
+
|
|
1818
|
+
|
|
1819
|
+
def _verify_execution_delivery(
|
|
1820
|
+
metadata: Mapping[str, object], prompt_path: Path, body: bytes,
|
|
1821
|
+
) -> list[str]:
|
|
1822
|
+
if metadata["prompt"].get("deliveryContract") != EXECUTION_DELIVERY_CONTRACT:
|
|
1823
|
+
return []
|
|
1824
|
+
metadata_path = prompt_path.with_name(prompt_path.name + ".meta.json")
|
|
1825
|
+
expected = _execution_delivery_lines(metadata, metadata_path)
|
|
1826
|
+
prefix = body.decode("utf-8").split("\n\n## Duty Contract\n\n", 1)[0]
|
|
1827
|
+
actual = tuple(line for line in prefix.splitlines() if line.startswith(EXECUTION_DELIVERY_PREFIXES))
|
|
1828
|
+
if actual != expected:
|
|
1829
|
+
return ["prompt execution identity or metadata path does not match invocation metadata"]
|
|
1830
|
+
return []
|
|
1831
|
+
|
|
1832
|
+
|
|
1833
|
+
def agent_prompt_task_bytes(prompt_bytes: bytes) -> bytes:
|
|
1834
|
+
"""์๋๋ณ ์ ๋ฌ๊ฐ์ ์ ์ธํ ๋
ผ๋ฆฌ ์์
์
๋ ฅ. ์์ฝ๊ณผ ๋ ๋ฐฐ์น ๊ฒฝ๊ณ๊ฐ ๊ณต์ ํ๋ค."""
|
|
1835
|
+
prefix, remainder = prompt_bytes.decode("utf-8").split("\n\n## Duty Contract\n\n", 1)
|
|
1836
|
+
history_prefixes = ("**Prompt History Path:**", "Assigned worker prompt history path:")
|
|
1837
|
+
lines = [line for line in prefix.splitlines() if not line.startswith(EXECUTION_DELIVERY_PREFIXES)]
|
|
1838
|
+
lines = [next((label for label in history_prefixes if line.startswith(label)), line) for line in lines]
|
|
1839
|
+
return ("\n".join(lines) + "\n\n## Duty Contract\n\n" + remainder).encode("utf-8")
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
def invocation_input_digest(metadata: Mapping[str, object], project_root: Path) -> str:
|
|
1843
|
+
"""๊ตฌํ ์์ฝ์ ์
๋ ฅ ํด์๋ฅผ ์ ์งํ๊ณ ์ ์ ๋ฌ ๊ณ์ฝ์ ๋
ผ๋ฆฌ ์์
์ ํด์ํ๋ค."""
|
|
1844
|
+
if metadata["prompt"].get("deliveryContract") != EXECUTION_DELIVERY_CONTRACT:
|
|
1845
|
+
return str(metadata["digests"]["promptDigest"])
|
|
1846
|
+
path = _project_path(project_root, metadata["prompt"]["path"], must_exist=True)
|
|
1847
|
+
return _sha256(agent_prompt_task_bytes(path.read_bytes()))
|
|
1848
|
+
|
|
1849
|
+
|
|
1752
1850
|
def _verify_instruction_digest(
|
|
1753
1851
|
metadata: Mapping[str, object],
|
|
1754
1852
|
prompt_bytes: bytes,
|
|
@@ -2137,6 +2235,10 @@ def _metadata_schema_is_exact(metadata: Mapping[str, object]) -> bool:
|
|
|
2137
2235
|
return False
|
|
2138
2236
|
for key, expected_keys in _NESTED_KEYS.items():
|
|
2139
2237
|
value = metadata.get(key)
|
|
2238
|
+
if key == "prompt" and isinstance(value, dict) and "deliveryContract" in value:
|
|
2239
|
+
if schema != "2.0" or value["deliveryContract"] != EXECUTION_DELIVERY_CONTRACT:
|
|
2240
|
+
return False
|
|
2241
|
+
expected_keys = expected_keys | {"deliveryContract"}
|
|
2140
2242
|
if not isinstance(value, dict) or set(value) != expected_keys:
|
|
2141
2243
|
return False
|
|
2142
2244
|
sources = metadata["instruction"]["sourcePaths"]
|
|
@@ -16,6 +16,7 @@ from ...dispatch_state import BACKEND_CLI_WRAPPER, BACKEND_CMUX_PANE, DispatchEr
|
|
|
16
16
|
from .emit import _emit, _prepared_payload
|
|
17
17
|
from .inputs import AgentPromptCliError
|
|
18
18
|
from .materialize import _apply_corrections, _check_corrections, _materialize
|
|
19
|
+
from .jobs import generate_jobs
|
|
19
20
|
from .results import (
|
|
20
21
|
_complete,
|
|
21
22
|
_link_result,
|
|
@@ -30,6 +31,7 @@ from .results import (
|
|
|
30
31
|
|
|
31
32
|
_CLI_EPILOG = r"""Usage:
|
|
32
33
|
okstra agent-prompt materialize [options]
|
|
34
|
+
okstra agent-prompt jobs --project-root <dir> --run-manifest <path> --dispatch-kind <kind> --metadata <path> [--metadata <path>] --out <path>
|
|
33
35
|
okstra agent-prompt check-corrections --project-root <dir> --run-manifest <path> --corrections <file>
|
|
34
36
|
okstra agent-prompt apply-corrections --project-root <dir> --run-manifest <path> --corrections <file>
|
|
35
37
|
okstra agent-prompt verify [options]
|
|
@@ -54,6 +56,13 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
54
56
|
_add_materialize_parser(commands)
|
|
55
57
|
_add_completion_parsers(commands)
|
|
56
58
|
_add_dispatch_parsers(commands)
|
|
59
|
+
jobs = commands.add_parser("jobs", help="generate a verified jobs file without dispatching")
|
|
60
|
+
_common_paths(jobs)
|
|
61
|
+
jobs.add_argument("--run-manifest", required=True)
|
|
62
|
+
jobs.add_argument("--dispatch-kind", required=True)
|
|
63
|
+
jobs.add_argument("--metadata", action="append", required=True)
|
|
64
|
+
jobs.add_argument("--out", required=True)
|
|
65
|
+
jobs.add_argument("--json", action="store_true")
|
|
57
66
|
return parser
|
|
58
67
|
|
|
59
68
|
|
|
@@ -221,6 +230,13 @@ def _standalone_completion_args(parser: argparse.ArgumentParser) -> None:
|
|
|
221
230
|
def main(argv: list[str] | None = None) -> int:
|
|
222
231
|
try:
|
|
223
232
|
args = _parser().parse_args(argv)
|
|
233
|
+
if args.command == "jobs":
|
|
234
|
+
payload = generate_jobs(args)
|
|
235
|
+
if args.json:
|
|
236
|
+
_emit(payload, True)
|
|
237
|
+
else:
|
|
238
|
+
print(payload["jobsPath"])
|
|
239
|
+
return 0
|
|
224
240
|
if args.command == "materialize":
|
|
225
241
|
prepared = _materialize(args)
|
|
226
242
|
_emit(_prepared_payload(prepared, args.audience), args.json)
|
|
@@ -18,6 +18,7 @@ from ..invocation import (
|
|
|
18
18
|
AgentModelAssignment,
|
|
19
19
|
compose_agent_prompt,
|
|
20
20
|
compose_unbound_run_prompt,
|
|
21
|
+
agent_prompt_task_bytes,
|
|
21
22
|
v2_role_assignment_authority_errors,
|
|
22
23
|
)
|
|
23
24
|
from ...convergence_store import (
|
|
@@ -47,7 +48,7 @@ def _reserve_dynamic_verifier_request(
|
|
|
47
48
|
duty_id=args.audience,
|
|
48
49
|
round_number=_reverify_round(args.dispatch_kind),
|
|
49
50
|
task_key=_required_manifest_string(manifest, "taskKey"),
|
|
50
|
-
input_digest="sha256:" + hashlib.sha256(prompt_bytes).hexdigest(),
|
|
51
|
+
input_digest="sha256:" + hashlib.sha256(agent_prompt_task_bytes(prompt_bytes)).hexdigest(),
|
|
51
52
|
invocation_ref=args.invocation_id,
|
|
52
53
|
)
|
|
53
54
|
bound = replace(
|
|
@@ -58,7 +59,7 @@ def _reserve_dynamic_verifier_request(
|
|
|
58
59
|
duty_id=args.audience,
|
|
59
60
|
invocation_ref=invocation.invocation_ref,
|
|
60
61
|
)
|
|
61
|
-
if compose_agent_prompt(bound).encode("utf-8") != prompt_bytes:
|
|
62
|
+
if agent_prompt_task_bytes(compose_agent_prompt(bound).encode("utf-8")) != agent_prompt_task_bytes(prompt_bytes):
|
|
62
63
|
raise AgentPromptCliError(
|
|
63
64
|
"dynamic verifier identity changed immutable prompt bytes"
|
|
64
65
|
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""๊ฒ์ฆ๋ ํธ์ถ์์ ๋ฐฐ์น ์
๋ ฅ์ ์์ฑํ๊ณ ์ ์ฒด ๊ฒ์ฌ ๋ค ๋ถ๋ณ ๋ฐํํ๋ค."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Mapping
|
|
8
|
+
|
|
9
|
+
from ..invocation import (
|
|
10
|
+
_exclusive_lock, _publish_or_reuse_exact, verify_agent_invocation,
|
|
11
|
+
)
|
|
12
|
+
from ...dispatch_state import worker_jobs_from_payload, validate_dispatch_prompts, prompt_anchor_values
|
|
13
|
+
from ...models import provider_wrappers
|
|
14
|
+
from ...path_hints import hydrate_active_run_context
|
|
15
|
+
from ...paths import okstra_home
|
|
16
|
+
from .inputs import (
|
|
17
|
+
AgentPromptCliError, _authorized_path, _project_input, _project_root,
|
|
18
|
+
_read_json_object,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def generate_jobs(args: argparse.Namespace) -> dict[str, object]:
|
|
23
|
+
project = _project_root(args.project_root)
|
|
24
|
+
manifest_path = _project_input(project, args.run_manifest, "run manifest")
|
|
25
|
+
manifest = _read_json_object(manifest_path, "run manifest")
|
|
26
|
+
if manifest.get("executionIdentityVersion") != 2:
|
|
27
|
+
raise AgentPromptCliError("agent-prompt jobs requires a v2 run manifest")
|
|
28
|
+
output = _authorized_path(project, args.out, [manifest["runDirectoryPath"]], "jobs", must_exist=False)
|
|
29
|
+
paths = [_project_input(project, value, "metadata") for value in args.metadata]
|
|
30
|
+
rows = [_metadata_job(project, manifest_path, manifest, path, args.dispatch_kind) for path in paths]
|
|
31
|
+
refs = [(row["invocationRef"], row["attempt"]) for row in rows]
|
|
32
|
+
if len(set(refs)) != len(refs):
|
|
33
|
+
raise AgentPromptCliError("jobs metadata contains duplicate invocation attempts")
|
|
34
|
+
context_path = _project_input(project, manifest["activeRunContextPath"], "active run context")
|
|
35
|
+
context = hydrate_active_run_context(_read_json_object(context_path, "active run context"))
|
|
36
|
+
payload = {"dispatchKind": args.dispatch_kind, "workers": rows}
|
|
37
|
+
wrappers = provider_wrappers("analyser")
|
|
38
|
+
jobs = worker_jobs_from_payload(
|
|
39
|
+
project, payload, manifest=manifest, active_context=context,
|
|
40
|
+
backend="cli-wrapper", idle_timeout_seconds=None,
|
|
41
|
+
default_dispatch_kind=args.dispatch_kind,
|
|
42
|
+
resolve_wrapper=lambda provider: okstra_home() / "bin" / wrappers[provider],
|
|
43
|
+
default_provider=lambda worker_id: worker_id,
|
|
44
|
+
)
|
|
45
|
+
validate_dispatch_prompts(manifest, context, jobs)
|
|
46
|
+
body = (json.dumps(payload, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
|
|
47
|
+
with _exclusive_lock(output.with_name(f".{output.name}.lock")):
|
|
48
|
+
if output.exists() and output.read_bytes() != body:
|
|
49
|
+
raise AgentPromptCliError(f"jobs output differs; existing file preserved: {output}; choose a new --out path")
|
|
50
|
+
_publish_or_reuse_exact(output, body)
|
|
51
|
+
return {"ok": True, "jobsPath": str(output), "workers": len(rows)}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _metadata_job(
|
|
55
|
+
project: Path, manifest_path: Path, manifest: Mapping[str, Any],
|
|
56
|
+
metadata_path: Path, dispatch_kind: str,
|
|
57
|
+
) -> dict[str, object]:
|
|
58
|
+
errors = verify_agent_invocation(
|
|
59
|
+
metadata_path, project_root=project, expected_run_manifest_path=manifest_path,
|
|
60
|
+
)
|
|
61
|
+
if errors:
|
|
62
|
+
raise AgentPromptCliError(f"{metadata_path}: " + "; ".join(errors))
|
|
63
|
+
metadata = _read_json_object(metadata_path, "agent metadata")
|
|
64
|
+
if metadata.get("executionIdentityVersion") != 2 or metadata["dispatchKind"] != dispatch_kind:
|
|
65
|
+
raise AgentPromptCliError(f"{metadata_path}: expected v2 dispatchKind {dispatch_kind!r}")
|
|
66
|
+
execution = next(row for row in manifest["roleExecutions"] if row["roleExecutionRef"] == metadata["roleExecutionRef"])
|
|
67
|
+
if execution["role"] == "translator":
|
|
68
|
+
raise AgentPromptCliError("translator uses canonical reservation dispatch: worker-dispatch --workers translator")
|
|
69
|
+
prompt = _project_input(project, metadata["prompt"]["path"], "prompt")
|
|
70
|
+
anchors = prompt_anchor_values(prompt, ("Result Path", "Worker Result Path"))
|
|
71
|
+
result = anchors.get("Worker Result Path") or anchors.get("Result Path")
|
|
72
|
+
if not result:
|
|
73
|
+
raise AgentPromptCliError(f"prompt has no result anchor: {prompt}")
|
|
74
|
+
assignment = metadata["modelAssignment"]
|
|
75
|
+
if assignment["provider"] not in provider_wrappers("analyser"):
|
|
76
|
+
raise AgentPromptCliError(f"unsupported worker provider: {assignment['provider']}")
|
|
77
|
+
return {
|
|
78
|
+
**{key: metadata[key] for key in (
|
|
79
|
+
"schemaVersion", "executionIdentityVersion", "participantRef", "roleExecutionRef",
|
|
80
|
+
"executionLabel", "dutyId", "invocationRef", "attempt", "invocationId",
|
|
81
|
+
"audience", "assignmentRef", "digests",
|
|
82
|
+
)},
|
|
83
|
+
"provider": assignment["provider"], "modelExecutionValue": assignment["modelExecutionValue"],
|
|
84
|
+
"hostModelValue": assignment["hostModelValue"], "role": execution["role"],
|
|
85
|
+
"promptPath": str(prompt), "promptMetadataPath": str(metadata_path),
|
|
86
|
+
"workerResultPath": result, "enforcementMode": "core-pre-dispatch",
|
|
87
|
+
}
|
|
@@ -26,11 +26,15 @@ from ..invocation import (
|
|
|
26
26
|
digest_duty_catalog,
|
|
27
27
|
invocation_execution_identity_from_manifest,
|
|
28
28
|
prepare_agent_invocation,
|
|
29
|
+
compose_agent_prompt,
|
|
30
|
+
compose_unbound_run_prompt,
|
|
29
31
|
)
|
|
30
32
|
from ...assignment_environment import load_assignment_context
|
|
31
33
|
from ...assignment_resolver import AssignmentContext, resolve_dispatch_assignment
|
|
32
34
|
from ...path_hints import hydrate_active_run_context
|
|
33
35
|
from ...worker_prompt_headers import worker_prompt_headers
|
|
36
|
+
from ...worker_prompt_contract import complete_reverify_instruction, validate_reverify_prompt
|
|
37
|
+
from ...paths import okstra_home
|
|
34
38
|
from ...final_report_paths import final_report_data_path
|
|
35
39
|
from ...final_report_schema import load_schema_version
|
|
36
40
|
from ...report_inputs import report_narrative_path, uses_report_contract_v3
|
|
@@ -207,8 +211,31 @@ def _materialize_run(
|
|
|
207
211
|
result_path=result_path,
|
|
208
212
|
audit_source_path=audit_source_path,
|
|
209
213
|
)
|
|
210
|
-
elif
|
|
211
|
-
audit_source_path
|
|
214
|
+
elif (
|
|
215
|
+
audit_source_path is None
|
|
216
|
+
or _normalized(audit_source_path) == _normalized(result_path)
|
|
217
|
+
):
|
|
218
|
+
# translator ๋ค. report-writer ์ ๊ฐ์ ๋-๊ฒฝ๋ก audience ์ธ๋ฐ ์ข
์ ์ ๋น ์ง
|
|
219
|
+
# `--audit-source` ๋ฅผ `--result` ๋ก ์กฐ์ฉํ ์ ์๋ค. ๊ทธ๋ฌ๋ฉด ํ๋กฌํํธ์
|
|
220
|
+
# `**Worker Result Path:**` ์ต์ปค๊ฐ ์ ์๊ธฐ๋๋ฐ, `worker-dispatch` ๋ ๊ทธ
|
|
221
|
+
# ์ต์ปค๊ฐ `**Result Path:**` ์ ๋ค๋ฅผ ๊ฒ์ ์๊ตฌํ๋ฏ๋ก ๋ฐํ์ ๋์ง๋ง ์ด๋ค
|
|
222
|
+
# ๋์คํจ์น๋ ๋ฐ์ง ๋ชปํ๋ ์์ฝ์ด ๋จ๋๋ค. ์์ฝ์ ํ์ ๋ช
๋ น์ด ์์ด ๋ฆฌ๋๊ฐ
|
|
223
|
+
# ๋ ๋ฒ์งธ id ๋ก ๋ค์ ๋ง๋๋ ์๊ฐ "exactly one reservation" ์ด ์์
|
|
224
|
+
# ๊นจ์ก๋ค(2026-09-08 ์ค์ธก, fontsninja-v3-site dev-10627-2
|
|
225
|
+
# requirements-discovery). ๋ฐํ ์์ ์ ๊ฑฐ์ ํ๋ค.
|
|
226
|
+
raise AgentPromptCliError(
|
|
227
|
+
"translator materialization requires --audit-source distinct from "
|
|
228
|
+
"--result: --result is the translator's own report "
|
|
229
|
+
"(e.g. worker-results/translator-translations-<task-type>-<seq>.md) "
|
|
230
|
+
"and --audit-source the worker-result .md the audit sidecar is "
|
|
231
|
+
"derived from (worker-results/translator-worker-<task-type>-<seq>.md). "
|
|
232
|
+
"`worker-dispatch --workers translator` reads both from the prompt's "
|
|
233
|
+
"**Result Path:** and **Worker Result Path:** anchors and refuses a "
|
|
234
|
+
"prompt without the second. To fix a prompt already published "
|
|
235
|
+
"without it, re-run this command with the same --invocation-id and "
|
|
236
|
+
"--prompt plus --replace-undispatched; a new invocation id would "
|
|
237
|
+
"leave two reservations and block the translator for this run"
|
|
238
|
+
)
|
|
212
239
|
_validate_run_identity(
|
|
213
240
|
manifest,
|
|
214
241
|
worker_id=args.worker_id,
|
|
@@ -272,6 +299,9 @@ def _materialize_run(
|
|
|
272
299
|
active_context=active_context,
|
|
273
300
|
))
|
|
274
301
|
body = instruction_path.read_text(encoding="utf-8")
|
|
302
|
+
is_reverify = args.dispatch_kind.startswith("reverify-r")
|
|
303
|
+
if is_reverify:
|
|
304
|
+
body = _complete_run_reverify_body(args, manifest, active_context, assignment, body, instruction_path)
|
|
275
305
|
if args.audience == "report-writer":
|
|
276
306
|
body = _with_inputs_section(
|
|
277
307
|
body,
|
|
@@ -315,7 +345,9 @@ def _materialize_run(
|
|
|
315
345
|
source_paths=(AgentInstructionSource(
|
|
316
346
|
kind="project",
|
|
317
347
|
path=_relative(project_root, instruction_path),
|
|
318
|
-
),)
|
|
348
|
+
),) + ((AgentInstructionSource(
|
|
349
|
+
kind="runtime", path="templates/reverify-output-contract.md",
|
|
350
|
+
),) if is_reverify else ()),
|
|
319
351
|
),
|
|
320
352
|
project_root=project_root,
|
|
321
353
|
run_manifest_path=manifest_path,
|
|
@@ -332,6 +364,15 @@ def _materialize_run(
|
|
|
332
364
|
attempt=1,
|
|
333
365
|
replace_undispatched=args.replace_undispatched,
|
|
334
366
|
)
|
|
367
|
+
if is_reverify:
|
|
368
|
+
candidate = compose_unbound_run_prompt(request).decode("utf-8") if dynamic_source else compose_agent_prompt(request)
|
|
369
|
+
errors = validate_reverify_prompt(
|
|
370
|
+
candidate, task_type=str(manifest["taskType"]),
|
|
371
|
+
forbidden_actions=str(active_context["workflow"]["forbiddenActions"]),
|
|
372
|
+
expected_model=assignment.model_execution_value,
|
|
373
|
+
)
|
|
374
|
+
if errors:
|
|
375
|
+
raise AgentPromptCliError("; ".join(errors))
|
|
335
376
|
if dynamic_source is not None:
|
|
336
377
|
request = _reserve_dynamic_verifier_request(
|
|
337
378
|
request,
|
|
@@ -343,6 +384,34 @@ def _materialize_run(
|
|
|
343
384
|
return prepare_agent_invocation(request)
|
|
344
385
|
|
|
345
386
|
|
|
387
|
+
def _complete_run_reverify_body(
|
|
388
|
+
args: argparse.Namespace, manifest: Mapping[str, Any],
|
|
389
|
+
active_context: Mapping[str, Any], assignment: AgentModelAssignment,
|
|
390
|
+
body: str, instruction_path: Path,
|
|
391
|
+
) -> str:
|
|
392
|
+
workflow = _mapping(active_context.get("workflow"), "active workflow")
|
|
393
|
+
forbidden = workflow.get("forbiddenActions")
|
|
394
|
+
if not isinstance(forbidden, str) or not forbidden.strip():
|
|
395
|
+
raise AgentPromptCliError("active workflow.forbiddenActions is missing or invalid")
|
|
396
|
+
resources = active_context.get("runtimeResources") or {}
|
|
397
|
+
error_contract = resources.get("workerErrorContractPath")
|
|
398
|
+
template = Path(
|
|
399
|
+
error_contract or okstra_home() / "templates/worker-error-contract.md"
|
|
400
|
+
).with_name("reverify-output-contract.md")
|
|
401
|
+
try:
|
|
402
|
+
return complete_reverify_instruction(
|
|
403
|
+
body, task_type=str(manifest["taskType"]), forbidden_actions=forbidden,
|
|
404
|
+
model=assignment.model_execution_value,
|
|
405
|
+
output_contract=template.read_text(encoding="utf-8"),
|
|
406
|
+
)
|
|
407
|
+
except ValueError as exc:
|
|
408
|
+
raise AgentPromptCliError(
|
|
409
|
+
f"{exc}; correct {instruction_path} and rerun agent-prompt materialize "
|
|
410
|
+
f"with --invocation-id {args.invocation_id} --prompt {args.prompt} "
|
|
411
|
+
"(use a fresh ID and path if already dispatched or dynamically reserved)"
|
|
412
|
+
) from exc
|
|
413
|
+
|
|
414
|
+
|
|
346
415
|
def _normalized(path: Path) -> Path:
|
|
347
416
|
return Path(os.path.normpath(path))
|
|
348
417
|
|
|
@@ -4,6 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
import re
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from . import group_context
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
BRIEF_SECTIONS = (
|
|
9
11
|
"Identity",
|
|
@@ -136,10 +138,15 @@ def build_analysis_packet(
|
|
|
136
138
|
bool(group_context_text),
|
|
137
139
|
)
|
|
138
140
|
)
|
|
139
|
-
# ๊ทธ๋ฃน
|
|
140
|
-
# ์ฝ๊ธฐ(agy view_file)์์๋
|
|
141
|
-
|
|
141
|
+
# ๊ทธ๋ฃน ๋ฌธ์๋ ์ฌ๋ ์ ๊ณผ okstra ๋ฉ๋ชจ๋ฆฌ ์์ญ์ผ๋ก ๊ฐ๋ผ ์ฃ๋๋ค. ์ฌ๋ ์ ("์")์
|
|
142
|
+
# ๋ธ๋ฆฌํ ๋ฐ์ท๋ณด๋ค ์ โ ์งง๊ณ , 46KB ์์ ์๋ฆฌ๋ ์ฝ๊ธฐ(agy view_file)์์๋
|
|
143
|
+
# ๋์ฐฉํด์ผ ํ๋ค. ํ์ task ์ ๊ฒฐ๋ก ์ ๋ธ๋ฆฌํ ๋ค โ ๋ธ๋ฆฌํ๋ฅผ ๋ฐ์ด๋ด๋ฉด ์ ๋๋ค.
|
|
144
|
+
group_human, group_memory, _ = group_context.split_memory_region(
|
|
145
|
+
_strip_frontmatter(group_context_text)
|
|
146
|
+
)
|
|
147
|
+
parts.extend(_group_context_block(group_human))
|
|
142
148
|
parts.extend(_brief_block(brief_text))
|
|
149
|
+
parts.extend(_group_memory_block(group_memory, _own_task_segment(task_key)))
|
|
143
150
|
parts.extend(_profile_block(task_type, profile_text))
|
|
144
151
|
parts.extend(_reference_block(reference_text))
|
|
145
152
|
parts.extend(_fix_history_block(fix_history_text))
|
|
@@ -296,8 +303,20 @@ GROUP_CONTEXT_PREFACE = (
|
|
|
296
303
|
)
|
|
297
304
|
|
|
298
305
|
|
|
299
|
-
|
|
300
|
-
|
|
306
|
+
GROUP_MEMORY_PREFACE = (
|
|
307
|
+
"What the other tasks of this task-group concluded in their latest run, "
|
|
308
|
+
"written by okstra at each run's report-finalize into the group's "
|
|
309
|
+
"`group-context.md`. A headline is a claim; its evidence is the record at "
|
|
310
|
+
"`record`. This section is not this task's requirement ledger, and a sibling's "
|
|
311
|
+
"decision does not bind this run: where a sibling's conclusion conflicts with "
|
|
312
|
+
"the `## Task-Specific Brief Extract`, raise a `Clarification Items` row rather "
|
|
313
|
+
"than re-deriving what the sibling already settled or silently overriding it."
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _group_context_block(human_text: str) -> list[str]:
|
|
318
|
+
"""์ฌ๋ ์ ๋ง ์ฃ๋๋ค. ๋ฉ๋ชจ๋ฆฌ ์ ์ฉ ๋ฌธ์(์ฌ๋ `## ` ์ ์์)๋ ์ ์ ๋ง๋ค์ง ์๋๋ค."""
|
|
319
|
+
if not any(line.startswith("## ") for line in human_text.splitlines()):
|
|
301
320
|
return []
|
|
302
321
|
return [
|
|
303
322
|
"",
|
|
@@ -305,7 +324,29 @@ def _group_context_block(group_context_text: str) -> list[str]:
|
|
|
305
324
|
"",
|
|
306
325
|
GROUP_CONTEXT_PREFACE,
|
|
307
326
|
"",
|
|
308
|
-
|
|
327
|
+
human_text.strip(),
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _own_task_segment(task_key: str) -> str:
|
|
332
|
+
return group_context.slugify_task_segment(re.split(r"[:/]", task_key)[-1])
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _group_memory_block(region: str, own_task_segment: str) -> list[str]:
|
|
336
|
+
"""ํ์ task ์ ํญ๋ชฉ๋ง ์ฃ๋๋ค. ์๊ธฐ ํญ๋ชฉ์ timelineยทprior-planning ์ด ์ด๋ฏธ ๋๋ฅธ๋ค."""
|
|
337
|
+
entries = [
|
|
338
|
+
entry for entry in group_context.parse_memory_entries(region)
|
|
339
|
+
if entry.task_id != own_task_segment
|
|
340
|
+
]
|
|
341
|
+
if not entries:
|
|
342
|
+
return []
|
|
343
|
+
return [
|
|
344
|
+
"",
|
|
345
|
+
"## Task-Group Memory",
|
|
346
|
+
"",
|
|
347
|
+
GROUP_MEMORY_PREFACE,
|
|
348
|
+
"",
|
|
349
|
+
group_context.render_memory_entries(entries).strip(),
|
|
309
350
|
]
|
|
310
351
|
|
|
311
352
|
|
|
@@ -225,6 +225,13 @@ def plan_worker_placement(
|
|
|
225
225
|
return _extend_the_worker_grid(workers)
|
|
226
226
|
|
|
227
227
|
|
|
228
|
+
def _overlaps_vertically(first: PaneGeometry, second: PaneGeometry) -> bool:
|
|
229
|
+
"""๊ฒฝ๊ณ๋ง ๋ง๋ฟ์ ์์๋ ์ธ์
์ ๊ฐ๋ก ๊ณต๊ฐ์ ๊ณต์ ํ์ง ์๋๋ค."""
|
|
230
|
+
return max(first.y, second.y) < min(
|
|
231
|
+
first.y + first.height_points, second.y + second.height_points,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
228
235
|
def lead_target_width(
|
|
229
236
|
panes: Sequence[PaneGeometry],
|
|
230
237
|
*,
|
|
@@ -238,20 +245,29 @@ def lead_target_width(
|
|
|
238
245
|
fourth worker is on screen, all of it when they are gone. The share is
|
|
239
246
|
taken of what okstra may actually place into, not of the window: a
|
|
240
247
|
workspace can hold panes okstra never opened, and handing the lead the
|
|
241
|
-
whole container would shove those off their own width.
|
|
242
|
-
|
|
248
|
+
whole container would shove those off their own width.
|
|
249
|
+
|
|
250
|
+
๋ฆฌ๋์ ์ธ๋ก ์์ญ์ด ๊ฒน์น๋ ๋ค๋ฅธ ์ฐฝ์ ๊ฐ๋ก ๊ตฌ๊ฐ๋ง ์ฐจ๊ฐํ๋ค. ์์๋๋ก
|
|
251
|
+
์์ธ ์ฐฝ๋ค์ด ๊ฐ์ ๊ฐ๋ก ๊ตฌ๊ฐ์ ์ฐจ์งํ๋ฉด ํด๋น ๊ตฌ๊ฐ์ ํ ๋ฒ๋ง ์ฐจ๊ฐํ๋ค.
|
|
243
252
|
|
|
244
253
|
Deriving the target from the container rather than from a fixed column count
|
|
245
254
|
is what keeps the split honest at any window size โ a hardcoded 80 columns
|
|
246
255
|
is two fifths of one particular display and an arbitrary slice of every
|
|
247
256
|
other.
|
|
248
257
|
"""
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
lead = _pane_by_id(panes, lead_pane_id)
|
|
259
|
+
intervals = sorted(
|
|
260
|
+
(pane.x, pane.x + pane.width_points)
|
|
251
261
|
for pane in panes
|
|
252
262
|
if pane.pane_id != lead_pane_id
|
|
253
263
|
and not _holds_an_okstra_surface(pane, owned_surface_ids)
|
|
264
|
+
and _overlaps_vertically(lead, pane)
|
|
254
265
|
)
|
|
266
|
+
strangers = 0.0
|
|
267
|
+
right_edge = float("-inf")
|
|
268
|
+
for left, right in intervals:
|
|
269
|
+
strangers += max(0.0, right - max(left, right_edge))
|
|
270
|
+
right_edge = max(right_edge, right)
|
|
255
271
|
usable = container_width_points - strangers
|
|
256
272
|
if usable <= 0:
|
|
257
273
|
return 0.0
|
|
@@ -707,18 +723,23 @@ def _size_lead_pane(workspace: str, owned_surface_ids: Collection[str]) -> None:
|
|
|
707
723
|
return
|
|
708
724
|
panes = _panes_from(payload)
|
|
709
725
|
lead = _lead_pane(panes)
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
owned_surface_ids=owned_surface_ids,
|
|
716
|
-
container_width_points=container_width,
|
|
717
|
-
),
|
|
726
|
+
target = lead_target_width(
|
|
727
|
+
panes,
|
|
728
|
+
lead_pane_id=lead.pane_id,
|
|
729
|
+
owned_surface_ids=owned_surface_ids,
|
|
730
|
+
container_width_points=container_width,
|
|
718
731
|
)
|
|
732
|
+
if target <= 0:
|
|
733
|
+
# ์๋ชป๋๊ฑฐ๋ ๊ณต๊ฐ์ด ์๋ ๋ฐฐ์น๋ฅผ ์ต์ ํญ๊น์ง ์ถ์ํ๋ ์์ฒญ์ผ๋ก ๋ฐ๊พธ์ง ์๋๋ค.
|
|
734
|
+
return
|
|
735
|
+
offset = lead_resize_points(lead, target_width_points=target)
|
|
719
736
|
if offset == 0:
|
|
720
737
|
return
|
|
721
|
-
neighbours = [
|
|
738
|
+
neighbours = [
|
|
739
|
+
pane for pane in panes
|
|
740
|
+
if abs(pane.x - (lead.x + lead.width_points)) <= 1
|
|
741
|
+
and _overlaps_vertically(lead, pane)
|
|
742
|
+
]
|
|
722
743
|
if not neighbours:
|
|
723
744
|
return
|
|
724
745
|
narrowing = offset > 0
|