okstra 0.198.0 → 0.198.1
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/dist/commands/execute/wizard.mjs +6 -0
- package/dist/commands/execute/wizard.mjs.map +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +5 -3
- package/runtime/python/okstra_ctl/cmux.py +20 -5
- package/runtime/python/okstra_ctl/render.py +4 -11
- package/runtime/python/okstra_ctl/wizard/cli.py +2 -0
- package/runtime/python/okstra_ctl/wizard/engine.py +2 -0
- package/runtime/python/okstra_ctl/wizard/state.py +1 -0
- package/runtime/python/okstra_ctl/wizard/steps_identity.py +14 -3
- package/runtime/skills/okstra-run/SKILL.md +7 -1
- package/runtime/templates/reports/html/base.template.html +3 -2
- package/runtime/templates/reports/html/i18n/en.json +1 -0
- package/runtime/templates/reports/html/i18n/ko.json +1 -0
- package/runtime/validators/validate-run.py +7 -10
|
@@ -27,6 +27,8 @@ Usage:
|
|
|
27
27
|
|
|
28
28
|
'init' auto-fills --workspace-root from 'okstra paths --field workspace',
|
|
29
29
|
so callers do not pass it.
|
|
30
|
+
Pass --preferred-task-key <project:group:task> to prioritize the task selected
|
|
31
|
+
in this conversation over project-wide recommendations without skipping the picker.
|
|
30
32
|
|
|
31
33
|
All subcommands emit a single JSON object on stdout. On validation failure
|
|
32
34
|
'step' returns {ok:false, error, current} so the skill can re-prompt.
|
|
@@ -102,6 +104,10 @@ export function buildPythonArgs(sub, flags, workspaceRoot = "") {
|
|
|
102
104
|
if (hostRuntime) {
|
|
103
105
|
pyArgs.push("--host-runtime", hostRuntime);
|
|
104
106
|
}
|
|
107
|
+
const preferredTaskKey = stringFlag(flags, "preferred-task-key");
|
|
108
|
+
if (preferredTaskKey) {
|
|
109
|
+
pyArgs.push("--preferred-task-key", preferredTaskKey);
|
|
110
|
+
}
|
|
105
111
|
for (const value of stringListFlag(flags, "available-function")) {
|
|
106
112
|
pyArgs.push("--available-function", value);
|
|
107
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard.mjs","sourceRoot":"","sources":["../../../src/commands/execute/wizard.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,MAAM,KAAK,GAAG
|
|
1
|
+
{"version":3,"file":"wizard.mjs","sourceRoot":"","sources":["../../../src/commands/execute/wizard.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -20,6 +20,8 @@ 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
|
+
Set `multi_select` explicitly on each object in `questions[]`: `true` for a multi-select question and `false` for a single-select question. The field belongs inside the question object, not at the call's top level. Grok uses `multi_select`; `multiSelect` is the Claude Code field. Before calling `ask_user_question`, check that every question carries the value specified by this relay. The relay field names and values are covered by `tests/contract/test_host_orchestration_rules.py`; the live host call still depends on the lead passing them.
|
|
24
|
+
|
|
23
25
|
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
26
|
|
|
25
27
|
```json
|
|
@@ -45,7 +47,7 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
45
47
|
"questions": "one",
|
|
46
48
|
"question": "label-with-progress",
|
|
47
49
|
"options": "all-in-original-order-as-label-description",
|
|
48
|
-
"
|
|
50
|
+
"multi_select": false
|
|
49
51
|
},
|
|
50
52
|
"response": {
|
|
51
53
|
"envelope": "answers",
|
|
@@ -61,7 +63,7 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
61
63
|
"questions": "one",
|
|
62
64
|
"question": "label-with-progress",
|
|
63
65
|
"options": "all-in-original-order-as-label-description",
|
|
64
|
-
"
|
|
66
|
+
"multi_select": true
|
|
65
67
|
},
|
|
66
68
|
"response": {
|
|
67
69
|
"envelope": "answers",
|
|
@@ -77,7 +79,7 @@ Read this contract when `okstra preflight` returns this file as `runtimeReadines
|
|
|
77
79
|
"questions": "all-in-original-order",
|
|
78
80
|
"question": "label-with-progress",
|
|
79
81
|
"options": "all-in-original-order-as-label-description",
|
|
80
|
-
"
|
|
82
|
+
"multi_select": "questions[].multi"
|
|
81
83
|
},
|
|
82
84
|
"response": {
|
|
83
85
|
"envelope": "answers",
|
|
@@ -456,11 +456,26 @@ def spawn_worker_surface(
|
|
|
456
456
|
)
|
|
457
457
|
target = _pane_by_id(panes, placement.pane_id)
|
|
458
458
|
surface_uuid = _open_worker_surface(workspace, placement, target)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
459
|
+
try:
|
|
460
|
+
renamed = rename_surface(surface_uuid, title)
|
|
461
|
+
if renamed.returncode != 0:
|
|
462
|
+
raise RuntimeError(renamed.stderr.strip() or "cmux could not name the worker")
|
|
463
|
+
_exec_worker(surface_uuid, cwd=cwd, command=command)
|
|
464
|
+
owned = (*owned_surface_ids, surface_uuid)
|
|
465
|
+
_size_lead_pane(workspace, owned)
|
|
466
|
+
_equalize_worker_heights(workspace, owned)
|
|
467
|
+
except (RuntimeError, OSError, subprocess.SubprocessError) as exc:
|
|
468
|
+
# 호출이 반환되기 전에는 명부에 창 식별자가 없다. 여기서 정리하지
|
|
469
|
+
# 않으면 대체 실행으로 전환된 뒤에도 회수할 수 없는 창이 남는다.
|
|
470
|
+
try:
|
|
471
|
+
closed = run_cmux(["close-surface", "--surface", surface_uuid])
|
|
472
|
+
if closed.returncode != 0:
|
|
473
|
+
raise RuntimeError(closed.stderr.strip() or "cmux close failed")
|
|
474
|
+
except (RuntimeError, OSError, subprocess.SubprocessError) as cleanup_error:
|
|
475
|
+
raise RuntimeError(
|
|
476
|
+
f"{exc}; cleanup failed for cmux surface {surface_uuid}: {cleanup_error}"
|
|
477
|
+
) from exc
|
|
478
|
+
raise
|
|
464
479
|
return surface_uuid
|
|
465
480
|
|
|
466
481
|
|
|
@@ -1188,17 +1188,10 @@ def _required_worker_roles(ctx: dict, reviewers: list[str]) -> list[dict]:
|
|
|
1188
1188
|
|
|
1189
1189
|
|
|
1190
1190
|
def _optional_worker_roles(ctx: dict) -> list[dict]:
|
|
1191
|
-
"""
|
|
1191
|
+
"""선택해 배정한 비평 역할. 선택 후에는 실행 또는 미실행 사유가 필요하다.
|
|
1192
1192
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
the liveness reader locate a worker by its team-state row: dispatching a
|
|
1196
|
-
critic failed with `team-state has no workerId=acceptance`, while adding
|
|
1197
|
-
the row by hand failed validation as an `unexpected worker role`. Declaring
|
|
1198
|
-
it here makes the roster say what the run may run, so both sides agree.
|
|
1199
|
-
|
|
1200
|
-
Keyed off `invocationAssignments`, which is where the run records the
|
|
1201
|
-
critic it actually resolved — an absent `critic/*` entry means no critic.
|
|
1193
|
+
`invocationAssignments`의 `critic/*` 배정만 선언하므로 역할을 선택하지
|
|
1194
|
+
않은 실행에는 행이 없다. 완료 검증은 선언된 행의 결과와 사유를 검사한다.
|
|
1202
1195
|
"""
|
|
1203
1196
|
assignments = _invocation_assignments(ctx)
|
|
1204
1197
|
roles: list[dict] = []
|
|
@@ -1220,7 +1213,7 @@ def _optional_worker_roles(ctx: dict) -> list[dict]:
|
|
|
1220
1213
|
# critic has no prompt or result until it is dispatched.
|
|
1221
1214
|
"resultPath": "",
|
|
1222
1215
|
"promptPath": "",
|
|
1223
|
-
"attemptRequired":
|
|
1216
|
+
"attemptRequired": True,
|
|
1224
1217
|
})
|
|
1225
1218
|
return roles
|
|
1226
1219
|
|
|
@@ -42,6 +42,7 @@ def main(argv: list[str]) -> int:
|
|
|
42
42
|
p_init.add_argument("--workspace-root", required=True)
|
|
43
43
|
p_init.add_argument("--project-root", required=True)
|
|
44
44
|
p_init.add_argument("--project-id", required=True)
|
|
45
|
+
p_init.add_argument("--preferred-task-key", default="")
|
|
45
46
|
p_init.add_argument(
|
|
46
47
|
"--host-runtime",
|
|
47
48
|
default="claude-code",
|
|
@@ -81,6 +82,7 @@ def main(argv: list[str]) -> int:
|
|
|
81
82
|
workspace_root=args.workspace_root,
|
|
82
83
|
project_root=args.project_root,
|
|
83
84
|
project_id=args.project_id,
|
|
85
|
+
preferred_task_key=args.preferred_task_key,
|
|
84
86
|
host_runtime=args.host_runtime,
|
|
85
87
|
host_entry_mode=args.entry_mode,
|
|
86
88
|
available_functions=args.available_function,
|
|
@@ -54,6 +54,7 @@ def init_state(
|
|
|
54
54
|
host_runtime: str = "claude-code",
|
|
55
55
|
host_entry_mode: str = "current-session",
|
|
56
56
|
available_functions: Optional[list[str]] = None,
|
|
57
|
+
preferred_task_key: str = "",
|
|
57
58
|
) -> WizardState:
|
|
58
59
|
"""Bootstrap a new wizard state."""
|
|
59
60
|
return WizardState(
|
|
@@ -61,6 +62,7 @@ def init_state(
|
|
|
61
62
|
workspace_root=workspace_root,
|
|
62
63
|
project_root=project_root,
|
|
63
64
|
project_id=project_id,
|
|
65
|
+
preferred_task_key=preferred_task_key,
|
|
64
66
|
host_runtime=host_runtime,
|
|
65
67
|
host_entry_mode=host_entry_mode,
|
|
66
68
|
available_functions=list(available_functions or []),
|
|
@@ -39,6 +39,7 @@ class WizardState:
|
|
|
39
39
|
workspace_root: str = ""
|
|
40
40
|
project_root: str = ""
|
|
41
41
|
project_id: str = ""
|
|
42
|
+
preferred_task_key: str = ""
|
|
42
43
|
host_runtime: str = "claude-code"
|
|
43
44
|
host_entry_mode: str = "current-session"
|
|
44
45
|
available_functions: list[str] = field(default_factory=list)
|
|
@@ -129,9 +129,12 @@ def _next_phase_cell(raw: Any) -> str:
|
|
|
129
129
|
return f"{cell} ({pointer['status']})"
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
def _recommended_task_entry(
|
|
132
|
+
def _recommended_task_entry(
|
|
133
|
+
remaining: list[dict], latest_key: str, preferred_key: str = "",
|
|
134
|
+
) -> dict | None:
|
|
133
135
|
"""남은 task 중 이 run 이 권하는 하나.
|
|
134
136
|
|
|
137
|
+
현재 대화에서 전달한 task 를 우선한다. 전달값이 없으면
|
|
135
138
|
포인터가 `ready` 인 task 는 지금 바로 다음 phase 를 시작할 수 있는 task 다
|
|
136
139
|
— 그중 최신(카탈로그는 updatedAt 내림차순)을 권한다. `ready` 가 없으면
|
|
137
140
|
마지막으로 만진 task(`latest-task.json`)를, 그것도 목록에 없으면 최신
|
|
@@ -139,6 +142,11 @@ def _recommended_task_entry(remaining: list[dict], latest_key: str) -> dict | No
|
|
|
139
142
|
산문에서 2번을 권했다 — 추천이 여럿이면 라벨은 아무것도 고르지 않은
|
|
140
143
|
것이다.
|
|
141
144
|
"""
|
|
145
|
+
if preferred_key:
|
|
146
|
+
for entry in remaining:
|
|
147
|
+
if entry.get("taskKey") == preferred_key:
|
|
148
|
+
return entry
|
|
149
|
+
raise WizardError(f"preferred task not found in project catalog: {preferred_key}")
|
|
142
150
|
if not remaining:
|
|
143
151
|
return None
|
|
144
152
|
for entry in remaining:
|
|
@@ -158,9 +166,10 @@ def _build_task_pick(state: WizardState) -> Prompt:
|
|
|
158
166
|
latest = read_latest_task(project_root) or {}
|
|
159
167
|
latest_key = latest.get("taskKey") or ""
|
|
160
168
|
latest_suffix = t["options"].get("_LATEST_SUFFIX", "")
|
|
161
|
-
remaining = [e for e in tasks if (e.get("workStatus") or "") != "done"
|
|
169
|
+
remaining = [e for e in tasks if (e.get("workStatus") or "") != "done"
|
|
170
|
+
or e.get("taskKey") == state.preferred_task_key]
|
|
162
171
|
# 추천은 하나이고 1번이다. 나머지는 최신순 그대로.
|
|
163
|
-
recommended = _recommended_task_entry(remaining, latest_key)
|
|
172
|
+
recommended = _recommended_task_entry(remaining, latest_key, state.preferred_task_key)
|
|
164
173
|
ordered = (
|
|
165
174
|
[recommended, *(e for e in remaining if e is not recommended)]
|
|
166
175
|
if recommended is not None else remaining
|
|
@@ -187,6 +196,7 @@ def _build_task_pick(state: WizardState) -> Prompt:
|
|
|
187
196
|
def _submit_task_pick(state: WizardState, value: str) -> Optional[str]:
|
|
188
197
|
value = value.strip()
|
|
189
198
|
if value == TASK_PICK_NEW_TOKEN:
|
|
199
|
+
state.preferred_task_key = ""
|
|
190
200
|
state.is_new_task = True
|
|
191
201
|
state.task_group = ""
|
|
192
202
|
state.task_id = ""
|
|
@@ -207,6 +217,7 @@ def _submit_task_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
207
217
|
state.is_new_task = False
|
|
208
218
|
state.task_group = tg
|
|
209
219
|
state.task_id = tid
|
|
220
|
+
state.preferred_task_key = value
|
|
210
221
|
state.existing_brief_path = _existing_task_brief(
|
|
211
222
|
Path(state.project_root), value
|
|
212
223
|
)
|
|
@@ -149,7 +149,13 @@ okstra wizard new-state-file
|
|
|
149
149
|
|
|
150
150
|
This prints one absolute path on stdout (e.g. `/var/folders/.../okstra-wizard.AbCd.json`). Read that path from the tool output and **paste it literally** into every subsequent `--state-file` argument.
|
|
151
151
|
|
|
152
|
-
Then initialize the wizard with the literal `projectRoot` / `projectId` you parsed from Step 1 and the literal state-file path from above
|
|
152
|
+
Then initialize the wizard with the literal `projectRoot` / `projectId` you parsed from Step 1 and the literal state-file path from above.
|
|
153
|
+
|
|
154
|
+
For task continuity, pass `--preferred-task-key <project-id:task-group:task-id>` on every initialization when this conversation has a known task. Use the task explicitly requested in the current message first; otherwise use the last task the user selected in this conversation's wizard. Resolve abbreviated task IDs against the project catalog before passing the full key. If the ID is ambiguous, ask which matching task the user means. Never infer this conversation's selection from project-wide `latest-task.json`, catalog recency, or another session. With neither an explicit task nor a prior selection in this conversation, omit the flag. The runtime puts the preferred task first, including a completed task when explicitly carried in, and rejects a key absent from the catalog instead of substituting another task; ordering is covered by `tests/domain/wizard/test_task_selection.py`. The task picker still lets the user change tasks.
|
|
155
|
+
|
|
156
|
+
After a successful task selection, retain the returned full task key in this conversation as its task context, including in continuation summaries, and carry it into the next initialization. A different selection replaces it. Selecting a brand-new task clears the previous selection; retain the new full key once its project, group, and task ID are resolved. This conversation carry-forward is a lead instruction, not automatic host-session discovery by the runtime.
|
|
157
|
+
|
|
158
|
+
Initialize with the optional preferred key resolved above:
|
|
153
159
|
|
|
154
160
|
```bash
|
|
155
161
|
okstra wizard init \
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
</table>
|
|
78
78
|
</section>
|
|
79
79
|
{% endif %}
|
|
80
|
-
{{ clarification_responses(clarificationItems) }}
|
|
81
80
|
{% if evidenceIndex %}
|
|
82
81
|
<section data-report-section="evidence-ledger" data-reader-kind="audit">
|
|
83
82
|
<h2>{{ t('base.evidence-ledger') }}</h2>
|
|
@@ -139,6 +138,7 @@
|
|
|
139
138
|
</table>
|
|
140
139
|
</section>
|
|
141
140
|
{% endif %}
|
|
141
|
+
{{ clarification_responses(clarificationItems) }}
|
|
142
142
|
</main>
|
|
143
143
|
{% if approval is not defined or not (approval and approval.reentry_command) %}<footer class="human-report-footer">
|
|
144
144
|
<button type="button" data-action="export-user-response">{{ t('base.export-my-answers') }}</button>
|
|
@@ -147,8 +147,9 @@
|
|
|
147
147
|
<p class="user-response-hint">{{ t('base.export-downloads') }} <code>user-response-{{ runMeta.task_type }}-{{ runMeta.seq }}.md</code>{{ t('base.drop-that-file-into') }} <code>{{ userResponseDir }}</code> {{ t('base.and-the-next-run-picks-your-answers-up-on-it') }}</p>
|
|
148
148
|
<pre id="user-response-output" aria-live="polite"></pre>
|
|
149
149
|
</footer>{% endif %}
|
|
150
|
+
<div id="bottom"></div>
|
|
150
151
|
<div class="back-to-top-wrap">
|
|
151
|
-
<nav class="back-to-top-index" id="back-to-top-index" aria-label="{{ t('base.contents') }}"><!--report-index-items
|
|
152
|
+
<nav class="back-to-top-index" id="back-to-top-index" aria-label="{{ t('base.contents') }}"><!--report-index-items--><a href="#bottom">{{ t('base.go-to-bottom') }}</a></nav>
|
|
152
153
|
<a class="back-to-top" href="#top">{{ t('base.back-to-top') }}</a>
|
|
153
154
|
</div>
|
|
154
155
|
<script id="run-meta" type="application/json">{{ {
|
|
@@ -1570,7 +1570,12 @@ def validate_team_state(
|
|
|
1570
1570
|
by_role[role] = worker
|
|
1571
1571
|
|
|
1572
1572
|
expected_workers: dict[str, dict] = {}
|
|
1573
|
-
|
|
1573
|
+
# 선택은 배정 전의 선택이다. 배정된 비평 역할도 결과 또는 생략 사유가
|
|
1574
|
+
# 있어야 하므로 필수 역할과 같은 상태 검사를 거친다.
|
|
1575
|
+
for worker in [
|
|
1576
|
+
*contract["required_worker_roles"],
|
|
1577
|
+
*contract.get("optional_worker_roles", []),
|
|
1578
|
+
]:
|
|
1574
1579
|
if not isinstance(worker, dict):
|
|
1575
1580
|
failures.append("requiredWorkerRoles contains a non-object entry")
|
|
1576
1581
|
continue
|
|
@@ -1646,15 +1651,7 @@ def validate_team_state(
|
|
|
1646
1651
|
if status != "completed" and not reason:
|
|
1647
1652
|
failures.append(f"{role} with status `{status}` must include a reason")
|
|
1648
1653
|
|
|
1649
|
-
|
|
1650
|
-
# absent: the run states which ones it can dispatch (today, the critics),
|
|
1651
|
-
# and running one is not a contract violation.
|
|
1652
|
-
optional_roles = {
|
|
1653
|
-
str(worker.get("role", "")).strip()
|
|
1654
|
-
for worker in contract.get("optional_worker_roles", [])
|
|
1655
|
-
if isinstance(worker, dict) and str(worker.get("role", "")).strip()
|
|
1656
|
-
}
|
|
1657
|
-
unexpected_roles = set(by_role) - set(expected_workers) - optional_roles
|
|
1654
|
+
unexpected_roles = set(by_role) - set(expected_workers)
|
|
1658
1655
|
for role in sorted(unexpected_roles):
|
|
1659
1656
|
failures.append(f"unexpected worker role detected: {role}")
|
|
1660
1657
|
|