okstra 0.179.2 → 0.183.2
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 +30 -2
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/incremental-carry.mjs +9 -8
- package/dist/commands/execute/incremental-carry.mjs.map +1 -1
- package/dist/commands/execute/plan-verify.mjs +3 -1
- package/dist/commands/execute/plan-verify.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.d.mts +4 -2
- package/dist/commands/execute/render-bundle.mjs +46 -5
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +11 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/inspect/model-io.d.mts +1 -0
- package/dist/commands/inspect/model-io.mjs +25 -0
- package/dist/commands/inspect/model-io.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.mjs +29 -8
- package/dist/commands/inspect/stage-map.mjs.map +1 -1
- package/dist/commands/inspect/task-list.mjs +52 -6
- package/dist/commands/inspect/task-list.mjs.map +1 -1
- package/dist/commands/inspect/user-response.mjs +14 -4
- package/dist/commands/inspect/user-response.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.d.mts +1 -0
- package/dist/commands/lifecycle/check-project.mjs +69 -50
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/contract-check.d.mts +1 -0
- package/dist/commands/lifecycle/contract-check.mjs +18 -0
- package/dist/commands/lifecycle/contract-check.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.mjs +154 -51
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/pr/pr.d.mts +1 -0
- package/dist/commands/pr/pr.mjs +19 -1
- package/dist/commands/pr/pr.mjs.map +1 -1
- package/dist/commands/report/agent-activity.mjs +2 -2
- package/dist/commands/report/approval-decision.d.mts +1 -0
- package/dist/commands/report/approval-decision.mjs +21 -0
- package/dist/commands/report/approval-decision.mjs.map +1 -0
- package/dist/commands/report/design-snapshot.d.mts +1 -0
- package/dist/commands/report/design-snapshot.mjs +19 -0
- package/dist/commands/report/design-snapshot.mjs.map +1 -0
- package/dist/commands/report/translate.mjs +3 -0
- package/dist/commands/report/translate.mjs.map +1 -1
- package/dist/lib/host-registry-client.mjs +13 -9
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -1
- package/docs/architecture.md +21 -10
- package/docs/cli.md +36 -20
- package/docs/container.md +6 -4
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-brief-gen.md +5 -3
- package/docs/for-ai/skills/okstra-code-review.md +4 -4
- package/docs/for-ai/skills/okstra-container-build.md +20 -17
- package/docs/for-ai/skills/okstra-inspect.md +20 -23
- package/docs/for-ai/skills/okstra-manager.md +19 -18
- package/docs/for-ai/skills/okstra-memory.md +2 -2
- package/docs/for-ai/skills/okstra-pr-gen.md +3 -3
- package/docs/for-ai/skills/okstra-rollup.md +14 -13
- package/docs/for-ai/skills/okstra-run.md +7 -3
- package/docs/for-ai/skills/okstra-schedule-gen.md +15 -18
- package/docs/for-ai/skills/okstra-setup.md +7 -7
- package/docs/for-ai/skills/okstra-usage.md +5 -4
- package/docs/for-ai/skills/okstra-user-response.md +50 -32
- package/docs/project-structure-overview.md +43 -31
- package/docs/task-process/implementation-planning.md +2 -2
- package/docs/task-process/implementation.md +7 -4
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +4 -4
- package/runtime/agents/workers/report-writer-worker.md +15 -164
- package/runtime/agents/workers/translator-worker.md +5 -13
- package/runtime/bin/okstra-error-log.py +51 -11
- package/runtime/bin/okstra-report-translate.py +210 -23
- package/runtime/prompts/host-orchestration/implementation.md +1 -1
- package/runtime/prompts/launch.template.md +9 -12
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/context-loader.md +41 -141
- package/runtime/prompts/lead/convergence.md +10 -8
- package/runtime/prompts/lead/okstra-lead-contract.md +40 -49
- package/runtime/prompts/lead/plan-body-verification.md +217 -23
- package/runtime/prompts/lead/report-writer.md +81 -421
- package/runtime/prompts/lead/team-contract.md +9 -54
- package/runtime/prompts/profiles/_clarification-recommendation.md +5 -4
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +3 -2
- package/runtime/prompts/profiles/_common-contract.md +4 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/feature-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +20 -12
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/project-analysis.md +1 -1
- package/runtime/prompts/profiles/release-handoff.md +0 -1
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +0 -1
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +69 -17
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +13 -4
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +8 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +50 -5
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +66 -5
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +70 -2
- package/runtime/python/okstra_ctl/agent_activity.py +141 -38
- package/runtime/python/okstra_ctl/agent_invocation.py +19 -6
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +70 -23
- package/runtime/python/okstra_ctl/analysis_inputs.py +5 -4
- package/runtime/python/okstra_ctl/analysis_packet.py +124 -3
- package/runtime/python/okstra_ctl/approval_decisions.py +328 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +2 -2
- package/runtime/python/okstra_ctl/backfill.py +13 -10
- package/runtime/python/okstra_ctl/batch.py +2 -4
- package/runtime/python/okstra_ctl/build_tools.py +6 -3
- package/runtime/python/okstra_ctl/claim_reproduction.py +101 -0
- package/runtime/python/okstra_ctl/clarification_items.py +27 -13
- package/runtime/python/okstra_ctl/cmux.py +130 -52
- package/runtime/python/okstra_ctl/code_review_target.py +34 -8
- package/runtime/python/okstra_ctl/consumers.py +5 -4
- package/runtime/python/okstra_ctl/container.py +103 -8
- package/runtime/python/okstra_ctl/context_cost.py +2 -1
- package/runtime/python/okstra_ctl/contract_graph.py +497 -0
- package/runtime/python/okstra_ctl/contract_graph_cli.py +62 -0
- package/runtime/python/okstra_ctl/convergence.py +338 -17
- package/runtime/python/okstra_ctl/convergence_engine.py +10 -18
- package/runtime/python/okstra_ctl/convergence_provenance.py +58 -8
- package/runtime/python/okstra_ctl/convergence_store.py +55 -34
- package/runtime/python/okstra_ctl/design_prep.py +7 -4
- package/runtime/python/okstra_ctl/design_snapshot.py +134 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +97 -69
- package/runtime/python/okstra_ctl/dispatch_state.py +163 -63
- package/runtime/python/okstra_ctl/doctor.py +6 -3
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +70 -9
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +16 -30
- package/runtime/python/okstra_ctl/error_log_write.py +35 -30
- package/runtime/python/okstra_ctl/error_report.py +26 -1
- package/runtime/python/okstra_ctl/error_zip.py +27 -5
- package/runtime/python/okstra_ctl/execution_identity.py +3 -2
- package/runtime/python/okstra_ctl/execution_manifest.py +7 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -2
- package/runtime/python/okstra_ctl/final_report_schema.py +26 -17
- package/runtime/python/okstra_ctl/fix_cycles.py +2 -2
- package/runtime/python/okstra_ctl/fixed_text.py +39 -0
- package/runtime/python/okstra_ctl/git_reconcile.py +41 -9
- package/runtime/python/okstra_ctl/handoff.py +5 -4
- package/runtime/python/okstra_ctl/i18n.py +4 -2
- package/runtime/python/okstra_ctl/implementation_direction.py +22 -14
- package/runtime/python/okstra_ctl/implementation_outcome.py +4 -7
- package/runtime/python/okstra_ctl/incremental_carry.py +130 -17
- package/runtime/python/okstra_ctl/incremental_scope.py +12 -4
- package/runtime/python/okstra_ctl/index.py +8 -11
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +79 -7
- package/runtime/python/okstra_ctl/invocation.py +3 -6
- package/runtime/python/okstra_ctl/json_boundary.py +366 -0
- package/runtime/python/okstra_ctl/json_registry.py +10 -12
- package/runtime/python/okstra_ctl/jsonl.py +19 -2
- package/runtime/python/okstra_ctl/lead_events.py +33 -1
- package/runtime/python/okstra_ctl/listing.py +3 -3
- package/runtime/python/okstra_ctl/log_report.py +24 -2
- package/runtime/python/okstra_ctl/manager_cli.py +92 -7
- package/runtime/python/okstra_ctl/manager_store.py +12 -10
- package/runtime/python/okstra_ctl/material.py +5 -1
- package/runtime/python/okstra_ctl/migrate.py +29 -25
- package/runtime/python/okstra_ctl/model_cli.py +3 -15
- package/runtime/python/okstra_ctl/model_io_cli.py +1051 -0
- package/runtime/python/okstra_ctl/mutation_probe.py +13 -4
- package/runtime/python/okstra_ctl/pane_reclaim.py +3 -2
- package/runtime/python/okstra_ctl/path_hints.py +12 -0
- package/runtime/python/okstra_ctl/paths.py +21 -0
- package/runtime/python/okstra_ctl/plan_items.py +115 -4
- package/runtime/python/okstra_ctl/plan_items_cli.py +626 -34
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +2 -1
- package/runtime/python/okstra_ctl/pr_template.py +3 -2
- package/runtime/python/okstra_ctl/project_meta.py +5 -7
- package/runtime/python/okstra_ctl/recap.py +5 -4
- package/runtime/python/okstra_ctl/reconcile.py +21 -27
- package/runtime/python/okstra_ctl/registry/host_discovery.py +3 -2
- package/runtime/python/okstra_ctl/registry/provider_registry.py +3 -2
- package/runtime/python/okstra_ctl/render.py +74 -16
- package/runtime/python/okstra_ctl/render_final_report.py +10 -8
- package/runtime/python/okstra_ctl/report_assembly.py +509 -0
- package/runtime/python/okstra_ctl/report_contract.py +2 -1
- package/runtime/python/okstra_ctl/report_finalize.py +67 -32
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_inputs.py +72 -0
- package/runtime/python/okstra_ctl/report_language.py +3 -2
- package/runtime/python/okstra_ctl/report_markdown.py +81 -8
- package/runtime/python/okstra_ctl/report_narrative.py +351 -0
- package/runtime/python/okstra_ctl/report_projections.py +265 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +518 -0
- package/runtime/python/okstra_ctl/report_views.py +3 -2
- package/runtime/python/okstra_ctl/rollup.py +65 -4
- package/runtime/python/okstra_ctl/run.py +184 -65
- package/runtime/python/okstra_ctl/run_audit.py +3 -2
- package/runtime/python/okstra_ctl/run_context.py +6 -9
- package/runtime/python/okstra_ctl/run_index_row.py +2 -8
- package/runtime/python/okstra_ctl/schedule_semantics.py +5 -2
- package/runtime/python/okstra_ctl/schema_excerpt.py +15 -8
- package/runtime/python/okstra_ctl/session_transcript.py +27 -1
- package/runtime/python/okstra_ctl/set_work_status.py +64 -38
- package/runtime/python/okstra_ctl/stage_fix_carry.py +8 -6
- package/runtime/python/okstra_ctl/stage_ledger.py +132 -18
- package/runtime/python/okstra_ctl/stage_map.py +96 -28
- package/runtime/python/okstra_ctl/stage_targets.py +3 -4
- package/runtime/python/okstra_ctl/team.py +3 -2
- package/runtime/python/okstra_ctl/team_reconcile.py +11 -2
- package/runtime/python/okstra_ctl/time_report.py +51 -4
- package/runtime/python/okstra_ctl/usage_identity.py +2 -1
- package/runtime/python/okstra_ctl/usage_report.py +58 -4
- package/runtime/python/okstra_ctl/user_response.py +1431 -66
- package/runtime/python/okstra_ctl/wizard.py +11 -44
- package/runtime/python/okstra_ctl/work_categories.py +3 -2
- package/runtime/python/okstra_ctl/worker_dispatch.py +5 -2
- package/runtime/python/okstra_ctl/worker_prompt_body.py +49 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +3 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +31 -3
- package/runtime/python/okstra_ctl/worker_runner.py +14 -12
- package/runtime/python/okstra_ctl/workflow.py +2 -1
- package/runtime/python/okstra_ctl/worktree.py +3 -2
- package/runtime/python/okstra_ctl/wrapper_status.py +4 -2
- package/runtime/python/okstra_ctl/write_policy.py +4 -2
- package/runtime/python/okstra_token_usage/antigravity.py +39 -12
- package/runtime/python/okstra_token_usage/collect.py +90 -38
- package/runtime/python/okstra_token_usage/grok.py +127 -0
- package/runtime/schemas/final-report-v2.0.schema.json +9 -0
- package/runtime/schemas/final-report-v3.0.schema.json +10219 -0
- package/runtime/schemas/report-narrative-v3.0.schema.json +30 -0
- package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +140 -0
- package/runtime/skills/okstra-brief-gen/SKILL.md +9 -7
- package/runtime/skills/okstra-code-review/SKILL.md +21 -11
- package/runtime/skills/okstra-container-build/SKILL.md +18 -18
- package/runtime/skills/okstra-inspect/SKILL.md +12 -11
- package/runtime/skills/okstra-inspect/facets/error-zip.md +8 -8
- package/runtime/skills/okstra-inspect/facets/errors.md +2 -2
- package/runtime/skills/okstra-inspect/facets/history.md +9 -14
- package/runtime/skills/okstra-inspect/facets/logs.md +2 -2
- package/runtime/skills/okstra-inspect/facets/recap.md +5 -5
- package/runtime/skills/okstra-inspect/facets/report.md +6 -10
- package/runtime/skills/okstra-inspect/facets/status.md +9 -8
- package/runtime/skills/okstra-inspect/facets/time.md +3 -3
- package/runtime/skills/okstra-manager/SKILL.md +16 -14
- package/runtime/skills/okstra-memory/SKILL.md +3 -3
- package/runtime/skills/okstra-pr-gen/SKILL.md +5 -4
- package/runtime/skills/okstra-rollup/SKILL.md +6 -16
- package/runtime/skills/okstra-run/SKILL.md +7 -7
- package/runtime/skills/okstra-schedule-gen/SKILL.md +21 -17
- package/runtime/skills/okstra-setup/SKILL.md +21 -13
- package/runtime/skills/okstra-setup/references/project-config.md +2 -2
- package/runtime/skills/okstra-usage/SKILL.md +10 -10
- package/runtime/skills/okstra-user-response/SKILL.md +78 -107
- package/runtime/templates/report-writer-prompt-preamble.md +29 -19
- package/runtime/templates/reports/html/macros/forms.html +6 -4
- package/runtime/templates/reports/schedule.template.md +4 -4
- package/runtime/templates/worker-error-contract.md +17 -29
- package/runtime/validators/validate-run.py +636 -72
- package/runtime/validators/validate_session_conformance.py +43 -7
|
@@ -623,7 +623,6 @@
|
|
|
623
623
|
"static_role": " static-role : {role}#{ordinal} / {model}",
|
|
624
624
|
"dynamic_role": " dynamic-role : {role} / reuse selected participant model",
|
|
625
625
|
"workers_implementation_default": " workers : (프로필 기본 — executor + verifier 2 + report-writer)",
|
|
626
|
-
"critic_shares_analyser_provider": " ⚠ critic provider 가 분석 워커 `{worker}` 와 같습니다 — gap 검증에서 그 워커는 투표할 수 없어 유효 투표자가 {remaining}명으로 줍니다",
|
|
627
626
|
"base_ref_stage_isolated": " base-ref : (stage 격리 — 의존 stage 기준으로 run 준비 시점에 자동 해소)",
|
|
628
627
|
"base_ref_reuse_task_dir": " base-ref : (기존 `{task_key}` 디렉터리 재사용 — 최초 base 유지)",
|
|
629
628
|
"worktree_new": " worktree : 새 브랜치 `{branch}` (base-ref `{base_ref}`) → `{path}`",
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Capability-based strategy shared by bundled host adapters."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import json
|
|
4
5
|
from collections.abc import Callable, Mapping
|
|
5
6
|
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
6
8
|
|
|
7
9
|
from okstra_ctl.adapters.dispatch import default_worker_dispatch_port
|
|
8
10
|
from okstra_ctl.domain.host import (
|
|
@@ -57,27 +59,52 @@ INTERACTION_FUNCTIONS = PLAIN_TEXT_FUNCTIONS | frozenset({
|
|
|
57
59
|
"native_multi_select",
|
|
58
60
|
"native_question_group",
|
|
59
61
|
})
|
|
60
|
-
|
|
61
|
-
_NATIVE_OPTION_RANGE = range(2, 5)
|
|
62
|
+
_RELAY_HEADING = "## Wizard interaction relay"
|
|
62
63
|
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class NativePickerLimits:
|
|
67
|
+
"""How many questions and options a host's native picker can show.
|
|
68
|
+
|
|
69
|
+
Counts that miss the range keep every option and fall back to numbered
|
|
70
|
+
text. The defaults are Claude Code's AskUserQuestion shape; a host that
|
|
71
|
+
can show more must declare that in its relay `nativeLimits`.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
min_options: int = 2
|
|
75
|
+
max_options: int = 4
|
|
76
|
+
max_questions: int = 4
|
|
77
|
+
|
|
78
|
+
def option_count_fits(self, count: int) -> bool:
|
|
79
|
+
return self.min_options <= count <= self.max_options
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def wizard_relay_contract(path: Path) -> dict[str, object]:
|
|
83
|
+
"""The JSON object under `## Wizard interaction relay` in a host relay."""
|
|
84
|
+
body = path.read_text(encoding="utf-8")
|
|
85
|
+
section = body.split(_RELAY_HEADING, 1)[1]
|
|
86
|
+
encoded = section.split("```json\n", 1)[1].split("\n```", 1)[0]
|
|
87
|
+
payload = json.loads(encoded)
|
|
88
|
+
if not isinstance(payload, dict):
|
|
89
|
+
raise ValueError(f"wizard relay in {path} is not a JSON object")
|
|
90
|
+
return payload
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def native_limits_from_relay(contract: Mapping[str, object]) -> NativePickerLimits:
|
|
94
|
+
raw = contract.get("nativeLimits")
|
|
95
|
+
if not isinstance(raw, Mapping):
|
|
96
|
+
return NativePickerLimits()
|
|
97
|
+
return NativePickerLimits(
|
|
98
|
+
min_options=int(raw.get("minOptions", 2)),
|
|
99
|
+
max_options=int(raw.get("maxOptions", 4)),
|
|
100
|
+
max_questions=int(raw.get("maxQuestions", 4)),
|
|
77
101
|
)
|
|
78
102
|
|
|
79
103
|
|
|
80
104
|
class CapabilityInteractionPort:
|
|
105
|
+
def __init__(self, *, limits: NativePickerLimits | None = None) -> None:
|
|
106
|
+
self._limits = limits or NativePickerLimits()
|
|
107
|
+
|
|
81
108
|
def plan(
|
|
82
109
|
self,
|
|
83
110
|
prompt: WizardPrompt,
|
|
@@ -87,7 +114,7 @@ class CapabilityInteractionPort:
|
|
|
87
114
|
if prompt.kind == "pick_group":
|
|
88
115
|
if (
|
|
89
116
|
"native_question_group" in functions
|
|
90
|
-
and _native_group_fits(prompt)
|
|
117
|
+
and self._native_group_fits(prompt, functions)
|
|
91
118
|
):
|
|
92
119
|
return InteractionPlan("native-group", AnswerProtocol("group-json"))
|
|
93
120
|
return InteractionPlan(
|
|
@@ -96,7 +123,7 @@ class CapabilityInteractionPort:
|
|
|
96
123
|
)
|
|
97
124
|
if prompt.kind != "pick":
|
|
98
125
|
return InteractionPlan("plain-text", AnswerProtocol("exact-value"))
|
|
99
|
-
native_options_fit = _native_options_fit(prompt)
|
|
126
|
+
native_options_fit = self._native_options_fit(prompt)
|
|
100
127
|
if (
|
|
101
128
|
prompt.multi
|
|
102
129
|
and "native_multi_select" in functions
|
|
@@ -115,11 +142,36 @@ class CapabilityInteractionPort:
|
|
|
115
142
|
kind = "numbered-multi" if prompt.multi else "numbered-single"
|
|
116
143
|
return InteractionPlan(kind, AnswerProtocol("numbered", multi=prompt.multi))
|
|
117
144
|
|
|
145
|
+
def _native_options_fit(self, prompt: WizardPrompt) -> bool:
|
|
146
|
+
labels = tuple(option.label for option in prompt.options)
|
|
147
|
+
return (
|
|
148
|
+
self._limits.option_count_fits(len(labels))
|
|
149
|
+
and len(set(labels)) == len(labels)
|
|
150
|
+
and (not prompt.multi or all(", " not in label for label in labels))
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def _native_group_fits(
|
|
154
|
+
self, prompt: WizardPrompt, functions: frozenset[str]
|
|
155
|
+
) -> bool:
|
|
156
|
+
if any(question.multi for question in prompt.questions) and (
|
|
157
|
+
"native_multi_select" not in functions
|
|
158
|
+
):
|
|
159
|
+
return False
|
|
160
|
+
return (
|
|
161
|
+
1 <= len(prompt.questions) <= self._limits.max_questions
|
|
162
|
+
and all(self._native_options_fit(question) for question in prompt.questions)
|
|
163
|
+
)
|
|
164
|
+
|
|
118
165
|
|
|
119
166
|
def numbered_interaction_port() -> CapabilityInteractionPort:
|
|
120
167
|
return CapabilityInteractionPort()
|
|
121
168
|
|
|
122
169
|
|
|
170
|
+
def relay_interaction_port(relay_path: str | Path) -> CapabilityInteractionPort:
|
|
171
|
+
contract = wizard_relay_contract(Path(relay_path))
|
|
172
|
+
return CapabilityInteractionPort(limits=native_limits_from_relay(contract))
|
|
173
|
+
|
|
174
|
+
|
|
123
175
|
class ProviderLeadSessionPort:
|
|
124
176
|
def __init__(
|
|
125
177
|
self,
|
|
@@ -12,7 +12,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
|
|
|
12
12
|
INTERACTION_FUNCTIONS,
|
|
13
13
|
PENDING_HOST_PORT,
|
|
14
14
|
CapabilityHostAdapter,
|
|
15
|
-
|
|
15
|
+
relay_interaction_port,
|
|
16
16
|
)
|
|
17
17
|
from okstra_ctl.domain.host import HostClaim, HostDescriptor, HostResolutionContext
|
|
18
18
|
from okstra_ctl.domain.provider import HostModelBinding
|
|
@@ -21,6 +21,11 @@ from okstra_ctl.ports.host_model import (
|
|
|
21
21
|
HostModelBindingRequest,
|
|
22
22
|
)
|
|
23
23
|
from okstra_ctl.registry.provider_registry import ProviderRegistry
|
|
24
|
+
from okstra_ctl.json_boundary import (
|
|
25
|
+
JsonBoundaryError,
|
|
26
|
+
external_claude_global_json_source,
|
|
27
|
+
load_external_json,
|
|
28
|
+
)
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
DESCRIPTOR = HostDescriptor(
|
|
@@ -108,7 +113,11 @@ def _workspace_trust_checks(context) -> tuple[dict[str, object], ...]:
|
|
|
108
113
|
if not (project_root / ".claude" / "settings.local.json").exists():
|
|
109
114
|
return ({"id": "workspace-trust", "status": "not-applicable"},)
|
|
110
115
|
try:
|
|
111
|
-
|
|
116
|
+
# 외부 입력: Claude Code가 소유하는 사용자 workspace trust 상태다.
|
|
117
|
+
state = load_external_json(
|
|
118
|
+
external_claude_global_json_source(home_dir / ".claude.json", home_dir),
|
|
119
|
+
artifact="Claude Code configuration",
|
|
120
|
+
)
|
|
112
121
|
projects = state.get("projects") if isinstance(state, dict) else None
|
|
113
122
|
if not isinstance(projects, dict):
|
|
114
123
|
return _workspace_trust_unavailable()
|
|
@@ -124,7 +133,7 @@ def _workspace_trust_checks(context) -> tuple[dict[str, object], ...]:
|
|
|
124
133
|
if accepted is False or accepted is None:
|
|
125
134
|
return _workspace_trust_required()
|
|
126
135
|
return _workspace_trust_unavailable()
|
|
127
|
-
except
|
|
136
|
+
except JsonBoundaryError:
|
|
128
137
|
return _workspace_trust_unavailable()
|
|
129
138
|
|
|
130
139
|
|
|
@@ -160,7 +169,7 @@ def create_adapter(
|
|
|
160
169
|
interaction_port=(
|
|
161
170
|
interaction_port
|
|
162
171
|
if interaction_port is not None
|
|
163
|
-
else
|
|
172
|
+
else relay_interaction_port(DESCRIPTOR.relay_contract)
|
|
164
173
|
),
|
|
165
174
|
lead_session_port=lead_session_port,
|
|
166
175
|
worker_dispatch_port=worker_dispatch_port,
|
|
@@ -32,6 +32,11 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
32
32
|
"native_multi_select",
|
|
33
33
|
"native_question_group"
|
|
34
34
|
],
|
|
35
|
+
"nativeLimits": {
|
|
36
|
+
"minOptions": 2,
|
|
37
|
+
"maxOptions": 4,
|
|
38
|
+
"maxQuestions": 4
|
|
39
|
+
},
|
|
35
40
|
"interactions": {
|
|
36
41
|
"native-single": {
|
|
37
42
|
"function": "AskUserQuestion",
|
|
@@ -124,7 +129,7 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
124
129
|
}
|
|
125
130
|
```
|
|
126
131
|
|
|
127
|
-
For `AskUserQuestion`, map each wizard option to the tool's `{label, description}` input without removing, shortening, or reordering entries. Look up each answer by the exact rendered `question` string sent to the tool, including the progress suffix; the `answers` keys are question text, not positions or headers. A single-select answer is the selected label. A multi-select answer is one string whose selected labels are joined with `, `. Match those labels back to the complete original option list, emit their `value` fields in original option order, and join the values with `,` for the wizard. Native plans are emitted only
|
|
132
|
+
For `AskUserQuestion`, map each wizard option to the tool's `{label, description}` input without removing, shortening, or reordering entries. Look up each answer by the exact rendered `question` string sent to the tool, including the progress suffix; the `answers` keys are question text, not positions or headers. A single-select answer is the selected label. A multi-select answer is one string whose selected labels are joined with `, `. Match those labels back to the complete original option list, emit their `value` fields in original option order, and join the values with `,` for the wizard. Native plans are emitted only when the prompt fits `nativeLimits` (unique option labels, two to four options, one to four questions); other prompts use the text mapping so no option is dropped. For a group, key the compact JSON object by the corresponding `questions[].step`; a multi-select question stores its ordered value CSV as one string, not a JSON array. Never use the display header as an answer key. The `Q1`…`Q4` headers only satisfy the host tool's short-header input and carry no domain meaning.
|
|
128
133
|
|
|
129
134
|
For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer. Do not translate numbers, CSV members, labels, or values before `okstra wizard step`. A sequential group wraps each raw reply in one compact JSON object keyed by `questions[].step`; the wizard owns normalization.
|
|
130
135
|
|
|
@@ -158,7 +163,8 @@ For a `host-text` mapping, render each numbered item as its option label followe
|
|
|
158
163
|
- CLI-wrapper assignments never enter the Agent layer. `okstra worker-dispatch` validates the metadata and passes `modelExecutionValue` to the registered provider script.
|
|
159
164
|
- Missing or unsupported family-token mapping is a pre-dispatch contract failure. Never inherit the lead model, choose a nearby alias, or switch provider silently.
|
|
160
165
|
- Every analysis dispatch sets `name: "<workerId>-worker"`; convergence retries append `-reverify-r<N>`, implementation uses the functional `-executor` / `-verifier` suffix, and report writing uses `report-writer`. These values are retained as `agentName` in session JSONL for usage attribution.
|
|
161
|
-
-
|
|
166
|
+
- A CLI worker's role reaches the entrypoint from the invocation's duty, NOT from the prompt text. `WorkerJob.wrapper_role` resolves `role_for_duty(dutyId)` and passes the canonical role in the role positional; the entrypoint checks it against the same derivation in the invocation metadata and refuses a mismatch. Do not add, edit, or rely on a `**Pane role:**` line to change what a dispatch runs as — the prompt body carries that header for the initial analysis audiences only, and it selects nothing.
|
|
167
|
+
- The role decides the dispatch's idle budget, and `worker-dispatch` leaves the budget positional EMPTY so it can: an empty slot makes the entrypoint read the role's own budget (`domain/worker_role.role_spec` — `implementer` and `verifier` run silent build+test suites and get 1500s, every other role 600s). Passing an explicit `--idle-timeout-seconds` overrides that for the whole dispatch, so pass one only for a run-specific reason; filling the slot with a blanket default is what made the role budgets unreachable and reaped healthy workers mid-suite.
|
|
162
168
|
- The host may supply transport metadata for native calls, but acceptance records only the verified invocation specification link. Record `enforcementMode=host-native-spec-link-gate`, `promptPath`, and `metadataPath`; do not claim the host-delivered bytes were observed.
|
|
163
169
|
- A retry keeps the same Agent `name`. When logging a twice-failed CLI-wrapper attempt, reference both attempts' `bash_ids` and prompt-history paths.
|
|
164
170
|
- An internally detected contract violation without a specific worker uses `--agent "claude-lead"` in the error-log event.
|
|
@@ -12,7 +12,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
|
|
|
12
12
|
PENDING_HOST_PORT,
|
|
13
13
|
CapabilityHostAdapter,
|
|
14
14
|
no_automatic_claim,
|
|
15
|
-
|
|
15
|
+
relay_interaction_port,
|
|
16
16
|
)
|
|
17
17
|
from okstra_ctl.domain.host import HostDescriptor
|
|
18
18
|
from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
|
|
@@ -73,7 +73,7 @@ def create_adapter(
|
|
|
73
73
|
interaction_port=(
|
|
74
74
|
interaction_port
|
|
75
75
|
if interaction_port is not None
|
|
76
|
-
else
|
|
76
|
+
else relay_interaction_port(DESCRIPTOR.relay_contract)
|
|
77
77
|
),
|
|
78
78
|
lead_session_port=lead_session_port,
|
|
79
79
|
worker_dispatch_port=worker_dispatch_port,
|
|
@@ -10,7 +10,7 @@ This adapter maps the neutral Okstra lead operations to the Codex artifact-first
|
|
|
10
10
|
|---|---|
|
|
11
11
|
| `runtime` | `codex` |
|
|
12
12
|
| `leadRoleLabel` | `Codex lead` |
|
|
13
|
-
| `userPromptMode` | `
|
|
13
|
+
| `userPromptMode` | `native-question` |
|
|
14
14
|
| `workerDispatchBackend` | `mixed` |
|
|
15
15
|
| `initialPromptDeliveryMode` | `eager-include` |
|
|
16
16
|
| `sessionAccounting` | `artifact-only` |
|
|
@@ -20,14 +20,57 @@ This adapter maps the neutral Okstra lead operations to the Codex artifact-first
|
|
|
20
20
|
|
|
21
21
|
## Wizard interaction relay
|
|
22
22
|
|
|
23
|
-
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host.
|
|
23
|
+
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host. `semanticFunctions` is an allowlist: the skill may declare a function only when both this list and the live harness expose it. Codex `request_user_input` is single-select only (2–3 mutually exclusive choices, 1–3 questions); it has no native multi-select.
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"schemaVersion": 1,
|
|
28
28
|
"runtime": "codex",
|
|
29
|
-
"semanticFunctions": [
|
|
29
|
+
"semanticFunctions": [
|
|
30
|
+
"plain_text_input",
|
|
31
|
+
"native_single_select",
|
|
32
|
+
"native_question_group"
|
|
33
|
+
],
|
|
34
|
+
"nativeLimits": {
|
|
35
|
+
"minOptions": 2,
|
|
36
|
+
"maxOptions": 3,
|
|
37
|
+
"maxQuestions": 3
|
|
38
|
+
},
|
|
30
39
|
"interactions": {
|
|
40
|
+
"native-single": {
|
|
41
|
+
"function": "request_user_input",
|
|
42
|
+
"input": {
|
|
43
|
+
"callCount": 1,
|
|
44
|
+
"questions": "one",
|
|
45
|
+
"id": "prompt.step",
|
|
46
|
+
"header": "Q1",
|
|
47
|
+
"question": "label-with-progress",
|
|
48
|
+
"options": "all-in-original-order-as-label-description"
|
|
49
|
+
},
|
|
50
|
+
"response": {
|
|
51
|
+
"envelope": "answers",
|
|
52
|
+
"key": "question-id",
|
|
53
|
+
"selection": "answers-array",
|
|
54
|
+
"submit": "matching-option-value"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"native-group": {
|
|
58
|
+
"function": "request_user_input",
|
|
59
|
+
"input": {
|
|
60
|
+
"callCount": 1,
|
|
61
|
+
"questions": "all-in-original-order",
|
|
62
|
+
"id": "questions[].step",
|
|
63
|
+
"header": "Q<one-based-question-position>",
|
|
64
|
+
"question": "label-with-progress",
|
|
65
|
+
"options": "all-in-original-order-as-label-description"
|
|
66
|
+
},
|
|
67
|
+
"response": {
|
|
68
|
+
"envelope": "answers",
|
|
69
|
+
"key": "question-id",
|
|
70
|
+
"selection": "answers-array",
|
|
71
|
+
"submit": "compact-step-json-values"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
31
74
|
"numbered-single": {
|
|
32
75
|
"function": "host-text",
|
|
33
76
|
"input": {
|
|
@@ -67,7 +110,9 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
67
110
|
}
|
|
68
111
|
```
|
|
69
112
|
|
|
70
|
-
|
|
113
|
+
For `request_user_input`, send one to three questions. Each question carries `id` (`prompt.step` or `questions[].step`), a header of at most 12 characters (`Q1`…`Q3`), the rendered question text including the progress suffix, and every wizard option as `{label, description}` in original order — the tool has no option `value` field. Do not include an `Other` option; the client adds a free-form row. Native plans are emitted only when the prompt fits `nativeLimits` (unique labels, two or three options, one to three questions) and no grouped question is multi-select. Other prompts use the text mapping so no option is dropped. Look up each answer by question `id`. The selected strings are in `answers[id].answers`. Match those strings to option labels, emit their `value` fields in original option order, and join with `,` when more than one is present. If the typed Other text matches no label, submit it unchanged.
|
|
114
|
+
|
|
115
|
+
For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer: do not translate a number such as `1`, a CSV reply such as `1, 3`, an option label, or an option value before `okstra wizard step`. For `sequential-group`, collect one raw reply per question in order and build one compact JSON object keyed by the corresponding `questions[].step`; the wizard owns all normalization.
|
|
71
116
|
|
|
72
117
|
|
|
73
118
|
## Semantic operation mapping
|
|
@@ -76,7 +121,7 @@ Render every numbered item as its option label followed by its description verba
|
|
|
76
121
|
|---|---|
|
|
77
122
|
| `read_artifacts` | Read the manifest-provided paths through the current host's file interface. |
|
|
78
123
|
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
|
|
79
|
-
| `prompt_user` |
|
|
124
|
+
| `prompt_user` | Use `request_user_input` for approvals and clarifications that fit two or three options; otherwise ask through host text and stop until an explicit answer arrives. |
|
|
80
125
|
| `dispatch_worker` | Verify each materialized invocation first. Dispatch `runner=native-session` with the current Codex host's primitive, the returned `promptPath`, and `hostModelValue`. Pass `runner=cli-wrapper` assignments to `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; use `--dry-run` first when required. **Not in a cmux run:** when `terminalBackend` is `cmux-pane`, the cmux adapter overrides this row. |
|
|
81
126
|
| `await_workers` | Await native host workers through the host primitive and CLI workers through synchronous dispatch, then verify team-state terminal records and Result Paths for both. |
|
|
82
127
|
| `redispatch_worker` | Materialize and verify a fresh invocation, then start a fresh native worker or `okstra worker-dispatch` attempt according to the persisted runner. |
|
|
@@ -91,7 +91,7 @@ Render every numbered item as its option label followed by its description verba
|
|
|
91
91
|
- Worker completion is valid only from `workerDispatches[]`, terminal status sidecars, and required Result Paths. Pane creation alone is not completion.
|
|
92
92
|
- Reverify uses a fresh jobs file at `runs/<task-type>/state/reverify-jobs-r<N>-<task-type>-<seq>.json`, sets `dispatchKind: "reverify-r<N>"`, and dispatches with `okstra team dispatch --project-root <root> --run-manifest <path> --dispatch-kind reverify-r<N> --jobs-file <jobs-file>`.
|
|
93
93
|
- Report-writer uses a fresh one-job jobs file with `dispatchKind: "report-writer"` and the same schema, then dispatches through `okstra team dispatch --project-root <root> --run-manifest <path> --jobs-file <jobs-file>`.
|
|
94
|
-
- Every reverify or report-writer jobs file carries `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `promptMetadataPath`, `invocationId`, `assignmentRef`, `audience`, the five prompt digests, `resultPath`, `workerResultPath`, and `completionPaths`. `worker-dispatch` verifies these fields before launching the provider process. For reverify, set `role` to `worker-reverify-r<N>`. The report-writer completion paths include
|
|
94
|
+
- Every reverify or report-writer jobs file carries `workerId`, `provider`, `role`, `modelExecutionValue`, `promptPath`, `promptMetadataPath`, `invocationId`, `assignmentRef`, `audience`, the five prompt digests, `resultPath`, `workerResultPath`, and `completionPaths`. `worker-dispatch` verifies these fields before launching the provider process. For reverify, set `role` to `worker-reverify-r<N>`. The report-writer completion paths include its narrative Markdown, worker-result pointer, and audit sidecar; Phase 7 later assembles `data.json`.
|
|
95
95
|
- After either dispatch, run `okstra team await --project-root <root> --run-manifest <path>` before evaluating terminal status or completion paths.
|
|
96
96
|
|
|
97
97
|
## Completion, cleanup, and resume
|
|
@@ -11,7 +11,7 @@ from okstra_ctl.adapters.hosts.capability_adapter import (
|
|
|
11
11
|
PENDING_HOST_PORT,
|
|
12
12
|
CapabilityHostAdapter,
|
|
13
13
|
no_automatic_claim,
|
|
14
|
-
|
|
14
|
+
relay_interaction_port,
|
|
15
15
|
)
|
|
16
16
|
from okstra_ctl.domain.host import HostDescriptor
|
|
17
17
|
from okstra_ctl.ports.host_model import NativeExecutionValueHostModelBindingPort
|
|
@@ -54,7 +54,7 @@ def create_adapter(
|
|
|
54
54
|
interaction_port=(
|
|
55
55
|
interaction_port
|
|
56
56
|
if interaction_port is not None
|
|
57
|
-
else
|
|
57
|
+
else relay_interaction_port(DESCRIPTOR.relay_contract)
|
|
58
58
|
),
|
|
59
59
|
lead_session_port=lead_session_port,
|
|
60
60
|
worker_dispatch_port=worker_dispatch_port,
|
|
@@ -10,7 +10,7 @@ This adapter maps neutral Okstra lead operations to the Grok CLI. Read it only w
|
|
|
10
10
|
|---|---|
|
|
11
11
|
| `runtime` | `grok` |
|
|
12
12
|
| `leadRoleLabel` | `Grok lead` |
|
|
13
|
-
| `userPromptMode` | `
|
|
13
|
+
| `userPromptMode` | `native-question` |
|
|
14
14
|
| `workerDispatchBackend` | `mixed` |
|
|
15
15
|
| `initialPromptDeliveryMode` | `eager-include` |
|
|
16
16
|
| `sessionAccounting` | `artifact-only` |
|
|
@@ -20,14 +20,73 @@ This adapter maps neutral Okstra lead operations to the Grok CLI. Read it only w
|
|
|
20
20
|
|
|
21
21
|
## Wizard interaction relay
|
|
22
22
|
|
|
23
|
-
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host.
|
|
23
|
+
Read this contract when `okstra preflight` returns this file as `runtimeReadiness.relayContract`. The JSON is the complete interaction mapping for this host. `semanticFunctions` is an allowlist: the skill may declare a function only when both this list and the live harness expose it.
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"schemaVersion": 1,
|
|
28
28
|
"runtime": "grok",
|
|
29
|
-
"semanticFunctions": [
|
|
29
|
+
"semanticFunctions": [
|
|
30
|
+
"plain_text_input",
|
|
31
|
+
"native_single_select",
|
|
32
|
+
"native_multi_select",
|
|
33
|
+
"native_question_group"
|
|
34
|
+
],
|
|
35
|
+
"nativeLimits": {
|
|
36
|
+
"minOptions": 2,
|
|
37
|
+
"maxOptions": 15,
|
|
38
|
+
"maxQuestions": 8
|
|
39
|
+
},
|
|
30
40
|
"interactions": {
|
|
41
|
+
"native-single": {
|
|
42
|
+
"function": "ask_user_question",
|
|
43
|
+
"input": {
|
|
44
|
+
"callCount": 1,
|
|
45
|
+
"questions": "one",
|
|
46
|
+
"question": "label-with-progress",
|
|
47
|
+
"options": "all-in-original-order-as-label-description",
|
|
48
|
+
"multiSelect": false
|
|
49
|
+
},
|
|
50
|
+
"response": {
|
|
51
|
+
"envelope": "answers",
|
|
52
|
+
"key": "rendered-question-text",
|
|
53
|
+
"selection": "selected-label",
|
|
54
|
+
"submit": "matching-option-value"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"native-multi": {
|
|
58
|
+
"function": "ask_user_question",
|
|
59
|
+
"input": {
|
|
60
|
+
"callCount": 1,
|
|
61
|
+
"questions": "one",
|
|
62
|
+
"question": "label-with-progress",
|
|
63
|
+
"options": "all-in-original-order-as-label-description",
|
|
64
|
+
"multiSelect": true
|
|
65
|
+
},
|
|
66
|
+
"response": {
|
|
67
|
+
"envelope": "answers",
|
|
68
|
+
"key": "rendered-question-text",
|
|
69
|
+
"selection": "selected-labels-joined-comma-space",
|
|
70
|
+
"submit": "csv-matching-option-values-in-option-order"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"native-group": {
|
|
74
|
+
"function": "ask_user_question",
|
|
75
|
+
"input": {
|
|
76
|
+
"callCount": 1,
|
|
77
|
+
"questions": "all-in-original-order",
|
|
78
|
+
"question": "label-with-progress",
|
|
79
|
+
"options": "all-in-original-order-as-label-description",
|
|
80
|
+
"multiSelect": "questions[].multi"
|
|
81
|
+
},
|
|
82
|
+
"response": {
|
|
83
|
+
"envelope": "answers",
|
|
84
|
+
"key": "rendered-question-text",
|
|
85
|
+
"selection": "selected-label-or-labels-joined-comma-space",
|
|
86
|
+
"multiValue": "csv-matching-option-values-in-option-order",
|
|
87
|
+
"submit": "compact-step-json-values"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
31
90
|
"numbered-single": {
|
|
32
91
|
"function": "host-text",
|
|
33
92
|
"input": {
|
|
@@ -67,7 +126,9 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
67
126
|
}
|
|
68
127
|
```
|
|
69
128
|
|
|
70
|
-
|
|
129
|
+
For `ask_user_question`, map each wizard option to the tool's `{label, description}` input without removing, shortening, or reordering entries. Do not send `preview`. Look up each answer by the exact rendered `question` string sent to the tool, including the progress suffix. A single-select answer is the selected label. A multi-select answer is one string whose selected labels are joined with `, `. Match those labels back to the complete original option list, emit their `value` fields in original option order, and join the values with `,` for the wizard. Native plans are emitted only when the prompt fits `nativeLimits` (unique option labels, two to fifteen options, one to eight questions); other prompts use the text mapping so no option is dropped. The host adds a free-text row on every question. Do not drop a wizard option to make room for it. If the user types in that row and the text matches no option label, submit the typed text unchanged. For a group, key the compact JSON object by the corresponding `questions[].step`; a multi-select question stores its ordered value CSV as one string, not a JSON array.
|
|
130
|
+
|
|
131
|
+
For a `host-text` mapping, render each numbered item as its option label followed by its description verbatim; preserve every item and its order. The next user message is the raw answer. Do not translate numbers, CSV members, labels, or values before `okstra wizard step`. A sequential group wraps each raw reply in one compact JSON object keyed by `questions[].step`; the wizard owns normalization.
|
|
71
132
|
|
|
72
133
|
## Semantic operation mapping
|
|
73
134
|
|
|
@@ -75,7 +136,7 @@ Render every numbered item as its option label followed by its description verba
|
|
|
75
136
|
|---|---|
|
|
76
137
|
| `read_artifacts` | Read the manifest-provided paths through the current Grok host file interface. |
|
|
77
138
|
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
|
|
78
|
-
| `prompt_user` |
|
|
139
|
+
| `prompt_user` | Use `ask_user_question` for approvals and clarifications; do not infer an answer from silence. |
|
|
79
140
|
| `dispatch_worker` | Verify the materialized invocation. Use the host primitive with `promptPath` and `hostModelValue` for `native-session`; use deterministic `okstra worker-dispatch` with `modelExecutionValue` for `cli-wrapper`. **Not in a cmux run:** the cmux adapter overrides this row. |
|
|
80
141
|
| `await_workers` | Await through the selected common dispatch backend, then verify terminal state and Result Paths. |
|
|
81
142
|
| `redispatch_worker` | Start a fresh attempt from the persisted assignment and record the supplied dispatch kind. |
|
|
@@ -16,7 +16,8 @@ from okstra_ctl.domain.worker_exec import (
|
|
|
16
16
|
PolicySupport,
|
|
17
17
|
WorkerExecRequest,
|
|
18
18
|
)
|
|
19
|
-
from okstra_ctl.domain.worker_presentation import
|
|
19
|
+
from okstra_ctl.domain.worker_presentation import JsonEvents
|
|
20
|
+
from okstra_ctl.domain.worker_stream import StreamEvent, Text, ToolCall, ToolResult
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
GROK = {
|
|
@@ -108,6 +109,65 @@ def observe_served_model(event: Mapping[str, Any]) -> str | None:
|
|
|
108
109
|
return names[0]
|
|
109
110
|
return None
|
|
110
111
|
|
|
112
|
+
|
|
113
|
+
def stream_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
114
|
+
"""grok `--output-format streaming-json` 한 줄을 공통 이벤트로 옮긴다.
|
|
115
|
+
|
|
116
|
+
이 CLI 의 `-p` 기본 `plain` 은 종료 시에만 stdout 에 쓴다. 파이프에 붙은
|
|
117
|
+
워커는 그 동안 pane 과 로그가 0바이트다(실측 2026-08-22). 같은 호출에
|
|
118
|
+
`streaming-json` 을 주면 ACP 세션 업데이트가 NDJSON 으로 흐른다. 키는
|
|
119
|
+
`type=text|tool_call|tool_call_update|end` 이고 Claude 의
|
|
120
|
+
`message.content` 가 아니다 — 그 스키마로 읽으면 이벤트는 전부 버려진다.
|
|
121
|
+
"""
|
|
122
|
+
kind = event.get("type")
|
|
123
|
+
if kind == "text":
|
|
124
|
+
data = event.get("data")
|
|
125
|
+
# 공백만 있는 토큰도 살린다. 버리면 붙인 문장에서 단어가 붙는다.
|
|
126
|
+
return (Text(body=data),) if isinstance(data, str) and data else ()
|
|
127
|
+
if kind == "tool_call":
|
|
128
|
+
name = str(event.get("toolName") or event.get("title") or "tool")
|
|
129
|
+
return (ToolCall(name=name, detail=_call_detail(event)),)
|
|
130
|
+
if kind == "tool_call_update":
|
|
131
|
+
return _completed_tool(event)
|
|
132
|
+
if kind == "error":
|
|
133
|
+
message = event.get("message")
|
|
134
|
+
return (Text(body=message),) if isinstance(message, str) and message.strip() else ()
|
|
135
|
+
return ()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _call_detail(event: Mapping[str, Any]) -> str:
|
|
139
|
+
payload = event.get("rawInput")
|
|
140
|
+
if isinstance(payload, Mapping):
|
|
141
|
+
for key in ("command", "file_path", "path", "pattern", "query"):
|
|
142
|
+
value = payload.get(key)
|
|
143
|
+
if value:
|
|
144
|
+
return str(value)
|
|
145
|
+
return next(
|
|
146
|
+
(str(value) for value in payload.values() if isinstance(value, str) and value),
|
|
147
|
+
"",
|
|
148
|
+
)
|
|
149
|
+
title = event.get("title")
|
|
150
|
+
return str(title) if isinstance(title, str) else ""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _completed_tool(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
154
|
+
if event.get("status") != "completed":
|
|
155
|
+
return ()
|
|
156
|
+
body = _output_body(event.get("rawOutput"))
|
|
157
|
+
return (ToolResult(body=body, size_bytes=len(body.encode("utf-8"))),)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _output_body(output: Any) -> str:
|
|
161
|
+
if isinstance(output, str):
|
|
162
|
+
return output
|
|
163
|
+
if isinstance(output, Mapping):
|
|
164
|
+
text = next(
|
|
165
|
+
(str(value) for value in output.values() if isinstance(value, str) and value),
|
|
166
|
+
"",
|
|
167
|
+
)
|
|
168
|
+
return text or json.dumps(output, ensure_ascii=False)
|
|
169
|
+
return "" if output is None else str(output)
|
|
170
|
+
|
|
111
171
|
GROK_LEAD_LAUNCH = LeadLaunchSpec(
|
|
112
172
|
executable="grok",
|
|
113
173
|
model_flag="--model",
|
|
@@ -125,6 +185,8 @@ class GrokExecution:
|
|
|
125
185
|
|
|
126
186
|
def build_command(self, request: WorkerExecRequest) -> ExecCommand:
|
|
127
187
|
cwd = request.worktree_path or request.project_root
|
|
188
|
+
# `-p` 기본 `plain` 은 종료 시에만 stdout 에 쓴다. 러너는 파이프라
|
|
189
|
+
# TUI 도 열리지 않고, `--no-alt-screen` 도 그 침묵을 바꾸지 않는다.
|
|
128
190
|
return ExecCommand(
|
|
129
191
|
argv=(
|
|
130
192
|
"grok",
|
|
@@ -134,10 +196,16 @@ class GrokExecution:
|
|
|
134
196
|
request.model,
|
|
135
197
|
"--cwd",
|
|
136
198
|
str(cwd),
|
|
199
|
+
"--output-format",
|
|
200
|
+
"streaming-json",
|
|
137
201
|
),
|
|
138
202
|
stdin_text=None,
|
|
139
203
|
cwd=cwd,
|
|
140
|
-
presentation=
|
|
204
|
+
presentation=JsonEvents(
|
|
205
|
+
normalise=stream_events,
|
|
206
|
+
observe=observe_served_model,
|
|
207
|
+
join_adjacent_text=True,
|
|
208
|
+
),
|
|
141
209
|
)
|
|
142
210
|
|
|
143
211
|
def policy_support(self) -> PolicySupport:
|