okstra 0.92.0 → 0.92.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/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -358,7 +358,7 @@
|
|
|
358
358
|
}
|
|
359
359
|
},
|
|
360
360
|
"defaults_or_custom": {
|
|
361
|
-
"label": "역할별
|
|
361
|
+
"label": "역할별 모델 선택 단계입니다 (참여 워커 구성을 바꾸는 게 아닙니다).\n이번 run 에서 모델을 고를 역할:\n{role_models}\n· 기본값으로 진행 — 위 추천 모델을 그대로 쓰고, directive·관련 task 없이 바로 넘어갑니다.\n· 커스터마이즈 — 위 역할별 모델을 직접 고르고, 추가 directive·관련 task 도 지정합니다.\n(추천 값은 runtime 기본값으로 해소됩니다. 분석에 참여하지 않는 역할은 위 목록에 나오지 않습니다 — 예: antigravity 는 implementation 의 executor 로 선택했을 때만 표시됩니다.)",
|
|
362
362
|
"echo_template": "customize: {value}",
|
|
363
363
|
"options": {
|
|
364
364
|
"defaults": "기본값으로 진행 (역할별 추천 모델 그대로)",
|
|
@@ -1315,33 +1315,42 @@ class _ModelBindings:
|
|
|
1315
1315
|
executor_model_meta: ModelAssignment
|
|
1316
1316
|
|
|
1317
1317
|
|
|
1318
|
+
def recommended_role_models() -> dict[str, str]:
|
|
1319
|
+
"""역할 → 추천 모델 display 값 (env override 반영). prepare 의 모델 해소와
|
|
1320
|
+
wizard 의 안내 표기가 공유하는 단일 기준점."""
|
|
1321
|
+
lead_default = _default("OKSTRA_DEFAULT_LEAD_MODEL", "opus")
|
|
1322
|
+
return {
|
|
1323
|
+
"lead": lead_default,
|
|
1324
|
+
"claude": _default("OKSTRA_DEFAULT_CLAUDE_MODEL", "opus"),
|
|
1325
|
+
"codex": _default("OKSTRA_DEFAULT_CODEX_MODEL", "gpt-5.5"),
|
|
1326
|
+
"antigravity": _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", "auto"),
|
|
1327
|
+
"report-writer": _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", lead_default),
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
|
|
1318
1331
|
def _resolve_worker_models(inp: PrepareInputs) -> dict:
|
|
1319
1332
|
"""lead/claude/codex/antigravity/report-writer 모델을 alias → (display, execution) 로 해소."""
|
|
1320
|
-
|
|
1321
|
-
claude_default = _default("OKSTRA_DEFAULT_CLAUDE_MODEL", "opus")
|
|
1322
|
-
codex_default = _default("OKSTRA_DEFAULT_CODEX_MODEL", "gpt-5.5")
|
|
1323
|
-
antigravity_default = _default("OKSTRA_DEFAULT_ANTIGRAVITY_MODEL", "auto")
|
|
1324
|
-
report_writer_default = _default("OKSTRA_DEFAULT_REPORT_WRITER_MODEL", lead_default)
|
|
1333
|
+
rec = recommended_role_models()
|
|
1325
1334
|
return {
|
|
1326
1335
|
"lead": resolve_model_metadata(
|
|
1327
1336
|
provider="claude", raw_value=inp.lead_model,
|
|
1328
|
-
default_display=
|
|
1337
|
+
default_display=rec["lead"], default_execution=rec["lead"],
|
|
1329
1338
|
),
|
|
1330
1339
|
"cw": resolve_model_metadata(
|
|
1331
1340
|
provider="claude", raw_value=inp.claude_model,
|
|
1332
|
-
default_display=
|
|
1341
|
+
default_display=rec["claude"], default_execution=rec["claude"],
|
|
1333
1342
|
),
|
|
1334
1343
|
"co": resolve_model_metadata(
|
|
1335
1344
|
provider="codex", raw_value=inp.codex_model,
|
|
1336
|
-
default_display=
|
|
1345
|
+
default_display=rec["codex"], default_execution=rec["codex"],
|
|
1337
1346
|
),
|
|
1338
1347
|
"ge": resolve_model_metadata(
|
|
1339
1348
|
provider="antigravity", raw_value=inp.antigravity_model,
|
|
1340
|
-
default_display=
|
|
1349
|
+
default_display=rec["antigravity"], default_execution=rec["antigravity"],
|
|
1341
1350
|
),
|
|
1342
1351
|
"rw": resolve_model_metadata(
|
|
1343
1352
|
provider="claude", raw_value=inp.report_writer_model,
|
|
1344
|
-
default_display=
|
|
1353
|
+
default_display=rec["report-writer"], default_execution=rec["report-writer"],
|
|
1345
1354
|
),
|
|
1346
1355
|
}
|
|
1347
1356
|
|
|
@@ -44,6 +44,7 @@ from okstra_ctl.run import (
|
|
|
44
44
|
_reject_blocking_plan_body_gate,
|
|
45
45
|
_set_data_json_approved_true_if_present,
|
|
46
46
|
_validate_data_json_approval_consistency,
|
|
47
|
+
recommended_role_models,
|
|
47
48
|
)
|
|
48
49
|
from okstra_ctl.user_response import (
|
|
49
50
|
UserResponseApprovalRecord,
|
|
@@ -93,6 +94,15 @@ TASK_TYPE_VALUES = [tt for tt, _ in TASK_TYPES]
|
|
|
93
94
|
|
|
94
95
|
EXECUTORS = ["claude", "codex", "antigravity"]
|
|
95
96
|
|
|
97
|
+
# 역할 한 줄 설명 — defaults_or_custom 안내에서 각 역할이 무슨 일을 하는지 보여준다.
|
|
98
|
+
ROLE_BLURBS = {
|
|
99
|
+
"lead": "Claude lead · 워커 dispatch·교차검증 수렴·최종 합성",
|
|
100
|
+
"claude": "독립 분석 워커 (Claude)",
|
|
101
|
+
"codex": "독립 분석 워커 (Codex)",
|
|
102
|
+
"antigravity": "독립 분석 워커 (Antigravity)",
|
|
103
|
+
"report-writer": "최종 리포트 작성 (분석은 하지 않음)",
|
|
104
|
+
}
|
|
105
|
+
|
|
96
106
|
# Task types that consume an approved plan and need a stage-scope pick:
|
|
97
107
|
# implementation executes a stage, final-verification verifies a stage
|
|
98
108
|
# (or the whole task via `auto`). Both gate the approved-plan + stage steps.
|
|
@@ -2357,11 +2367,34 @@ def _submit_reuse_previous(state: WizardState, value: str) -> Optional[str]:
|
|
|
2357
2367
|
f"lead-model={state.lead_model or 'default'})")
|
|
2358
2368
|
|
|
2359
2369
|
|
|
2370
|
+
def _role_model_lines(state: WizardState) -> str:
|
|
2371
|
+
"""이번 run 에서 실제로 모델을 고르게 되는 역할만, 추천 모델과 함께 나열한다.
|
|
2372
|
+
뒤따르는 *_model 단계의 등장 조건과 1:1 로 맞춰 안내와 실제 화면이 어긋나지
|
|
2373
|
+
않게 한다 (그래서 분석에 참여하지 않는 antigravity 는 executor 일 때만 나온다)."""
|
|
2374
|
+
rec = recommended_role_models()
|
|
2375
|
+
roster = _resolved_roster(state)
|
|
2376
|
+
impl = state.task_type == "implementation"
|
|
2377
|
+
|
|
2378
|
+
def line(role_label: str, blurb: str, model: str) -> str:
|
|
2379
|
+
return f" · {role_label} ({blurb}) — 추천 {model}"
|
|
2380
|
+
|
|
2381
|
+
lines = [line("lead", ROLE_BLURBS["lead"], rec["lead"])]
|
|
2382
|
+
if impl:
|
|
2383
|
+
ex = state.executor or "claude"
|
|
2384
|
+
lines.append(line(f"executor={ex}", "코드 구현 실행자", rec.get(ex, "auto")))
|
|
2385
|
+
else:
|
|
2386
|
+
for w in ("claude", "codex", "antigravity"):
|
|
2387
|
+
if w in roster:
|
|
2388
|
+
lines.append(line(w, ROLE_BLURBS[w], rec[w]))
|
|
2389
|
+
if impl or "report-writer" in roster:
|
|
2390
|
+
lines.append(line("report-writer", ROLE_BLURBS["report-writer"],
|
|
2391
|
+
rec["report-writer"]))
|
|
2392
|
+
return "\n".join(lines)
|
|
2393
|
+
|
|
2394
|
+
|
|
2360
2395
|
def _build_defaults_or_custom(state: WizardState) -> Prompt:
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
or "(profile default)")
|
|
2364
|
-
t = _p(state.workspace_root, "defaults_or_custom", workers=roster)
|
|
2396
|
+
t = _p(state.workspace_root, "defaults_or_custom",
|
|
2397
|
+
role_models=_role_model_lines(state))
|
|
2365
2398
|
return Prompt(
|
|
2366
2399
|
step=S_DEFAULTS_OR_CUSTOM, kind="pick",
|
|
2367
2400
|
label=t["label"],
|
|
@@ -3306,11 +3339,16 @@ def _remaining_screens(state: WizardState) -> int:
|
|
|
3306
3339
|
return screens
|
|
3307
3340
|
|
|
3308
3341
|
|
|
3309
|
-
def _screen_progress(state: WizardState) -> dict[str,
|
|
3342
|
+
def _screen_progress(state: WizardState) -> dict[str, Any]:
|
|
3310
3343
|
passed = _passed_screens(state)
|
|
3311
3344
|
total = passed + _remaining_screens(state)
|
|
3312
3345
|
index = passed + 1
|
|
3313
|
-
|
|
3346
|
+
remaining = max(0, total - index)
|
|
3347
|
+
suffix = "마지막 단계" if remaining == 0 else f"앞으로 {remaining} 스텝 남음"
|
|
3348
|
+
return {
|
|
3349
|
+
"index": index, "total": total, "remaining": remaining,
|
|
3350
|
+
"label": f"Step {index}/{total} · {suffix}",
|
|
3351
|
+
}
|
|
3314
3352
|
|
|
3315
3353
|
|
|
3316
3354
|
def prompt_payload(state: WizardState, prompt: Prompt) -> dict[str, Any]:
|
|
@@ -32,7 +32,7 @@ Every wizard call returns JSON. The two shapes you'll see:
|
|
|
32
32
|
"progress": { "index": 5, "total": 11, "remaining": 6 } } }
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit it). `index`
|
|
35
|
+
Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit it). It holds `index` (1-based number of the **current screen**; pick_group counts as one screen), `total` (the wizard's forward estimate of the full screen count — may grow by a few when the user opens a branch, e.g. choosing *Customize* adds the model screen), `remaining` (`total − index`), and **`label`** — the ready-to-render Korean marker the wizard already composed (e.g. `Step 10/11 · 앞으로 1 스텝 남음`, or `Step 11/11 · 마지막 단계` on the final screen). **Always suffix the rendered prompt with `progress.label` verbatim** — see Step 3. Do not recompute the marker or decide "마지막 단계" yourself; only the wizard knows whether more screens follow.
|
|
36
36
|
|
|
37
37
|
```json
|
|
38
38
|
{ "ok": false, "error": "approved plan has no APPROVED marker: ...",
|
|
@@ -96,7 +96,7 @@ Output: the same `{ok, next}` JSON described above. The first `next` is always `
|
|
|
96
96
|
|
|
97
97
|
Repeat until `next.kind == "done"` (or `"aborted"` — terminal cancel, see "How the wizard talks to you"):
|
|
98
98
|
|
|
99
|
-
1. **Render** the prompt according to `kind` (and `multi` for pick). **Always append the progress marker to the rendered question label** (the `AskUserQuestion` question text, or the `text`-prompt message): suffix
|
|
99
|
+
1. **Render** the prompt according to `kind` (and `multi` for pick). **Always append the progress marker to the rendered question label** (the `AskUserQuestion` question text, or the `text`-prompt message): suffix it with ` (<next.progress.label>)` — render `progress.label` exactly as the wizard sent it, never recompute it. Example: label `Step 8/11 · 앞으로 3 스텝 남음` → `모델을 선택하세요 (Step 8/11 · 앞으로 3 스텝 남음)`. Re-prompts after `ok: false` reuse `current.progress.label` the same way. The progress marker is presentation-only — never send it back to the wizard as part of an answer.
|
|
100
100
|
- `pick` + `multi: false` → `AskUserQuestion` with `multiSelect: false`, `label`, and `options`. The user's chosen option's `value` is the answer string.
|
|
101
101
|
- `pick` + `multi: true` → `AskUserQuestion` with `multiSelect: true`, `label`, and `options`. Join the selected `value`s with `,` into a single literal CSV string (e.g. `"claude,codex,antigravity"`) and submit it as a single `--answer "claude,codex,antigravity"`. Empty selection submits `--answer ""` and the wizard re-prompts.
|
|
102
102
|
- `pick_group` → one `AskUserQuestion` with one question per `questions[]` entry (tab). Map each tab's selected `value` back by `questions[].step`, assemble a JSON object, and submit it as a single literal `--answer '<json>'`.
|