okstra 0.147.0 → 0.148.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 +21 -7
- package/docs/architecture/storage-model.md +34 -61
- package/docs/architecture.md +51 -49
- package/docs/cli.md +38 -21
- package/docs/for-ai/skills/okstra-run.md +13 -34
- package/docs/performance-improvement-plan-v2.md +2 -2
- package/docs/pr-template-usage.md +1 -1
- package/docs/project-structure-overview.md +10 -8
- package/docs/task-process/README.md +4 -4
- package/docs/task-process/common-flow.md +12 -12
- package/docs/task-process/final-verification.md +2 -2
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +2 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +2 -2
- package/runtime/agents/workers/grok-worker.md +256 -0
- package/runtime/agents/workers/kimi-worker.md +256 -0
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/lib/okstra/cli.sh +13 -1
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/usage.sh +17 -12
- package/runtime/bin/okstra-grok-exec.sh +5 -0
- package/runtime/bin/okstra-kimi-exec.sh +5 -0
- package/runtime/bin/okstra-provider-exec.py +235 -0
- package/runtime/bin/okstra.sh +3 -0
- package/runtime/prompts/lead/adapters/antigravity.md +48 -0
- package/runtime/prompts/lead/adapters/claude-code.md +13 -11
- package/runtime/prompts/lead/adapters/codex.md +7 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +4 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +12 -12
- package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
- package/runtime/prompts/profiles/error-analysis.md +2 -0
- package/runtime/prompts/profiles/feature-analysis.md +2 -0
- package/runtime/prompts/profiles/final-verification.md +3 -1
- package/runtime/prompts/profiles/forbidden-actions.json +4 -4
- package/runtime/prompts/profiles/implementation-planning.md +3 -1
- package/runtime/prompts/profiles/implementation.md +2 -2
- package/runtime/prompts/profiles/improvement-discovery.md +3 -1
- package/runtime/prompts/profiles/project-analysis.md +2 -0
- package/runtime/prompts/profiles/release-handoff.md +7 -7
- package/runtime/prompts/profiles/requirements-discovery.md +2 -0
- package/runtime/prompts/wizard/prompts.ko.json +9 -1
- package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
- package/runtime/python/okstra_ctl/lead_events.py +1 -1
- package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
- package/runtime/python/okstra_ctl/models.py +156 -8
- package/runtime/python/okstra_ctl/path_hints.py +9 -25
- package/runtime/python/okstra_ctl/paths.py +1 -1
- package/runtime/python/okstra_ctl/render.py +172 -74
- package/runtime/python/okstra_ctl/report_html/common.py +38 -2
- package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
- package/runtime/python/okstra_ctl/report_html/render.py +7 -0
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/visualizations.py +32 -6
- package/runtime/python/okstra_ctl/run.py +264 -45
- package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
- package/runtime/python/okstra_ctl/team.py +2 -7
- package/runtime/python/okstra_ctl/wizard.py +194 -21
- package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
- package/runtime/python/okstra_ctl/workers.py +3 -1
- package/runtime/python/okstra_ctl/workflow.py +4 -2
- package/runtime/python/okstra_token_usage/__init__.py +1 -0
- package/runtime/python/okstra_token_usage/collect.py +32 -23
- package/runtime/python/okstra_token_usage/pricing.py +35 -3
- package/runtime/schemas/final-report-v2.0.schema.json +2 -2
- package/runtime/skills/okstra-run/SKILL.md +31 -42
- package/runtime/templates/prd/pr-body.template.md +1 -1
- package/runtime/templates/reports/html/assets/base.css +6 -3
- package/runtime/templates/reports/html/base.template.html +22 -8
- package/runtime/templates/reports/html/macros/forms.html +2 -2
- package/runtime/templates/reports/html/macros/layout.html +5 -5
- package/runtime/templates/reports/html/macros/visualizations.html +11 -1
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +12 -12
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +9 -9
- package/runtime/templates/reports/html/tasks/final-verification.template.html +7 -7
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +12 -12
- package/runtime/templates/reports/html/tasks/implementation.template.html +7 -7
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +6 -6
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +5 -5
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +8 -8
- package/runtime/templates/reports/report.js +21 -4
- package/runtime/templates/reports/settings.template.json +4 -0
- package/runtime/templates/reports/task-brief.template.md +7 -7
- package/runtime/validators/validate-run.py +11 -6
- package/runtime/validators/validate_session_conformance.py +2 -1
- package/src/cli-registry.mjs +4 -4
- package/src/commands/execute/codex-dispatch.mjs +7 -10
- package/src/commands/execute/render-bundle.mjs +3 -3
- package/src/commands/execute/run.mjs +17 -52
- package/src/commands/execute/wizard.mjs +4 -1
- package/src/commands/lifecycle/doctor.mjs +6 -3
- package/src/commands/lifecycle/install.mjs +31 -8
- package/src/lib/runtime-manifest.mjs +1 -1
- package/src/lib/runtime-resolver.mjs +2 -2
- package/src/lib/worker-agent-render.mjs +50 -0
|
@@ -45,8 +45,12 @@ from okstra_ctl.models import (
|
|
|
45
45
|
PROVIDER_MAPPINGS,
|
|
46
46
|
UnknownModelError,
|
|
47
47
|
picker_options,
|
|
48
|
+
provider_ids,
|
|
49
|
+
provider_supports_role,
|
|
48
50
|
resolve_model_metadata,
|
|
49
51
|
)
|
|
52
|
+
from okstra_ctl.lead_runtime import lead_runtime_info
|
|
53
|
+
from okstra_ctl.runner_resolution import native_provider_for_host
|
|
50
54
|
from okstra_ctl.clarification_items import (
|
|
51
55
|
scan_approval_gate,
|
|
52
56
|
user_response_sidecars,
|
|
@@ -135,10 +139,12 @@ EXECUTORS = ["claude", "codex", "antigravity"]
|
|
|
135
139
|
|
|
136
140
|
# 역할 한 줄 설명 — defaults_or_custom 안내에서 각 역할이 무슨 일을 하는지 보여준다.
|
|
137
141
|
ROLE_BLURBS = {
|
|
138
|
-
"lead": "
|
|
142
|
+
"lead": "Okstra lead · 워커 dispatch·교차검증 수렴·최종 합성",
|
|
139
143
|
"claude": "독립 분석 워커 (Claude)",
|
|
140
144
|
"codex": "독립 분석 워커 (Codex)",
|
|
141
145
|
"antigravity": "독립 분석 워커 (Antigravity)",
|
|
146
|
+
"grok": "독립 분석 워커 (Grok)",
|
|
147
|
+
"kimi": "독립 분석 워커 (Kimi)",
|
|
142
148
|
"report-writer": "최종 리포트 작성 (분석은 하지 않음)",
|
|
143
149
|
}
|
|
144
150
|
|
|
@@ -165,6 +171,8 @@ HOST_PICK_OPTION_CAP = 4
|
|
|
165
171
|
CLAUDE_MODEL_OPTIONS = ["default", *picker_options("claude")]
|
|
166
172
|
CODEX_MODEL_OPTIONS = ["default", *picker_options("codex")]
|
|
167
173
|
ANTIGRAVITY_MODEL_OPTIONS = ["default", *picker_options("antigravity")]
|
|
174
|
+
GROK_MODEL_OPTIONS = ["default", *picker_options("grok")]
|
|
175
|
+
KIMI_MODEL_OPTIONS = ["default", *picker_options("kimi")]
|
|
168
176
|
|
|
169
177
|
# special pick value: start a brand-new task
|
|
170
178
|
TASK_PICK_NEW_TOKEN = "__new__"
|
|
@@ -315,6 +323,8 @@ S_EXECUTOR_MODEL = "executor_model"
|
|
|
315
323
|
S_CLAUDE_MODEL = "claude_model"
|
|
316
324
|
S_CODEX_MODEL = "codex_model"
|
|
317
325
|
S_ANTIGRAVITY_MODEL = "antigravity_model"
|
|
326
|
+
S_GROK_MODEL = "grok_model"
|
|
327
|
+
S_KIMI_MODEL = "kimi_model"
|
|
318
328
|
S_REPORT_WRITER_MODEL = "report_writer_model"
|
|
319
329
|
S_DIRECTIVE_PICK = "directive_pick"
|
|
320
330
|
S_DIRECTIVE = "directive"
|
|
@@ -341,7 +351,8 @@ GROUP_MAX_TABS = 4 # AskUserQuestion 의 질문(탭) 수 한도
|
|
|
341
351
|
# *_TEXT 후속 / workers_override / pr_template_scope 는 의존성 때문에 개별 유지.
|
|
342
352
|
PROMPT_GROUPS: dict[str, tuple[str, ...]] = {
|
|
343
353
|
GROUP_MODELS: (S_LEAD_MODEL, S_EXECUTOR_MODEL, S_CLAUDE_MODEL,
|
|
344
|
-
S_CODEX_MODEL, S_ANTIGRAVITY_MODEL,
|
|
354
|
+
S_CODEX_MODEL, S_ANTIGRAVITY_MODEL, S_GROK_MODEL,
|
|
355
|
+
S_KIMI_MODEL, S_REPORT_WRITER_MODEL),
|
|
345
356
|
GROUP_OPTIONS: (S_DIRECTIVE_PICK, S_RELATED_TASKS_PICK,
|
|
346
357
|
S_CLARIFICATION_PICK, S_PR_TEMPLATE_PICK),
|
|
347
358
|
}
|
|
@@ -362,6 +373,7 @@ class WizardState:
|
|
|
362
373
|
workspace_root: str = ""
|
|
363
374
|
project_root: str = ""
|
|
364
375
|
project_id: str = ""
|
|
376
|
+
host_runtime: str = "claude-code"
|
|
365
377
|
|
|
366
378
|
# task identity
|
|
367
379
|
is_new_task: Optional[bool] = None
|
|
@@ -432,10 +444,14 @@ class WizardState:
|
|
|
432
444
|
# customize
|
|
433
445
|
use_defaults: Optional[bool] = None
|
|
434
446
|
workers_override: str = ""
|
|
447
|
+
lead_provider: str = ""
|
|
435
448
|
lead_model: str = ""
|
|
436
449
|
claude_model: str = ""
|
|
437
450
|
codex_model: str = ""
|
|
438
451
|
antigravity_model: str = ""
|
|
452
|
+
grok_model: str = ""
|
|
453
|
+
kimi_model: str = ""
|
|
454
|
+
report_writer_provider: str = ""
|
|
439
455
|
report_writer_model: str = ""
|
|
440
456
|
directive: str = ""
|
|
441
457
|
directive_pending_text: bool = False
|
|
@@ -3141,7 +3157,8 @@ _REUSE_FILLED_STEPS = (
|
|
|
3141
3157
|
S_PROJECT_EVIDENCE_PICK, S_PROJECT_EVIDENCE,
|
|
3142
3158
|
S_ANALYSIS_TARGET_PICK, S_ANALYSIS_TARGET,
|
|
3143
3159
|
S_DEFAULTS_OR_CUSTOM, S_WORKERS_OVERRIDE, S_LEAD_MODEL, S_EXECUTOR_MODEL,
|
|
3144
|
-
S_CLAUDE_MODEL, S_CODEX_MODEL, S_ANTIGRAVITY_MODEL,
|
|
3160
|
+
S_CLAUDE_MODEL, S_CODEX_MODEL, S_ANTIGRAVITY_MODEL, S_GROK_MODEL,
|
|
3161
|
+
S_KIMI_MODEL, S_REPORT_WRITER_MODEL,
|
|
3145
3162
|
S_DIRECTIVE_PICK, S_DIRECTIVE, S_RELATED_TASKS_PICK, S_RELATED_TASKS,
|
|
3146
3163
|
S_CLARIFICATION_PICK, S_CLARIFICATION)
|
|
3147
3164
|
|
|
@@ -3205,6 +3222,26 @@ def _safe_model(provider: str, raw: object) -> str:
|
|
|
3205
3222
|
return ""
|
|
3206
3223
|
|
|
3207
3224
|
|
|
3225
|
+
def _safe_provider(raw: object, role: str) -> str:
|
|
3226
|
+
provider = raw.strip().lower() if isinstance(raw, str) else ""
|
|
3227
|
+
if not provider:
|
|
3228
|
+
return ""
|
|
3229
|
+
return provider if provider_supports_role(provider, role) else ""
|
|
3230
|
+
|
|
3231
|
+
|
|
3232
|
+
def _assignment_model(inputs: dict, worker_id: str, provider: str) -> str:
|
|
3233
|
+
assignments = inputs.get("workerAssignments")
|
|
3234
|
+
if not isinstance(assignments, list):
|
|
3235
|
+
return ""
|
|
3236
|
+
for assignment in assignments:
|
|
3237
|
+
if not isinstance(assignment, dict):
|
|
3238
|
+
continue
|
|
3239
|
+
if assignment.get("workerId") != worker_id:
|
|
3240
|
+
continue
|
|
3241
|
+
return _safe_model(provider, assignment.get("model"))
|
|
3242
|
+
return ""
|
|
3243
|
+
|
|
3244
|
+
|
|
3208
3245
|
def _build_reuse_previous(state: WizardState) -> Prompt:
|
|
3209
3246
|
t = _p(state.workspace_root, "reuse_previous")
|
|
3210
3247
|
return Prompt(
|
|
@@ -3229,12 +3266,27 @@ def _submit_reuse_previous(state: WizardState, value: str) -> Optional[str]:
|
|
|
3229
3266
|
state.workers_override = (
|
|
3230
3267
|
",".join(w for w in workers if isinstance(w, str))
|
|
3231
3268
|
if isinstance(workers, list) else "")
|
|
3232
|
-
state.
|
|
3269
|
+
state.lead_provider = _safe_provider(inputs.get("leadProvider"), "lead")
|
|
3270
|
+
lead_provider = state.lead_provider or "claude"
|
|
3271
|
+
native_provider = native_provider_for_host(state.host_runtime)
|
|
3272
|
+
if native_provider and lead_provider != native_provider:
|
|
3273
|
+
raise WizardError(
|
|
3274
|
+
f"previous run used provider {lead_provider!r}, but "
|
|
3275
|
+
f"{state.host_runtime} requires provider {native_provider!r}; "
|
|
3276
|
+
"choose step-by-step re-entry"
|
|
3277
|
+
)
|
|
3278
|
+
state.lead_model = _safe_model(lead_provider, inputs.get("leadModel"))
|
|
3233
3279
|
state.claude_model = _safe_model("claude", inputs.get("claudeModel"))
|
|
3234
3280
|
state.codex_model = _safe_model("codex", inputs.get("codexModel"))
|
|
3235
3281
|
state.antigravity_model = _safe_model("antigravity", inputs.get("antigravityModel"))
|
|
3282
|
+
state.grok_model = _assignment_model(inputs, "grok", "grok")
|
|
3283
|
+
state.kimi_model = _assignment_model(inputs, "kimi", "kimi")
|
|
3284
|
+
state.report_writer_provider = _safe_provider(
|
|
3285
|
+
inputs.get("reportWriterProvider"), "report-writer",
|
|
3286
|
+
)
|
|
3287
|
+
report_writer_provider = state.report_writer_provider or "claude"
|
|
3236
3288
|
state.report_writer_model = _safe_model(
|
|
3237
|
-
|
|
3289
|
+
report_writer_provider, inputs.get("reportWriterModel"))
|
|
3238
3290
|
directive = inputs.get("directive")
|
|
3239
3291
|
state.directive = directive if isinstance(directive, str) else ""
|
|
3240
3292
|
related = inputs.get("relatedTasks")
|
|
@@ -3285,7 +3337,7 @@ def _role_model_lines(state: WizardState) -> str:
|
|
|
3285
3337
|
ex = state.executor or "claude"
|
|
3286
3338
|
lines.append(line(f"executor={ex}", "코드 구현 실행자", rec.get(ex, "auto")))
|
|
3287
3339
|
else:
|
|
3288
|
-
for w in ("
|
|
3340
|
+
for w in provider_ids("analyser"):
|
|
3289
3341
|
if w in roster:
|
|
3290
3342
|
lines.append(line(w, ROLE_BLURBS[w], rec[w]))
|
|
3291
3343
|
if impl or "report-writer" in roster:
|
|
@@ -3371,15 +3423,65 @@ def _model_pick(step: str, label: str, options: list[str], echo: str) -> Prompt:
|
|
|
3371
3423
|
options=opts, echo_template=echo)
|
|
3372
3424
|
|
|
3373
3425
|
|
|
3426
|
+
def _qualified_role_model_options(role: str) -> list[str]:
|
|
3427
|
+
options = list(CLAUDE_MODEL_OPTIONS)
|
|
3428
|
+
for provider in provider_ids(role):
|
|
3429
|
+
if provider == "claude":
|
|
3430
|
+
continue
|
|
3431
|
+
options.extend(
|
|
3432
|
+
f"{provider}:{model}" for model in picker_options(provider)
|
|
3433
|
+
)
|
|
3434
|
+
return options
|
|
3435
|
+
|
|
3436
|
+
|
|
3437
|
+
def _submit_qualified_role_model(
|
|
3438
|
+
state: WizardState, value: str, role: str, model_field: str, provider_field: str,
|
|
3439
|
+
) -> str:
|
|
3440
|
+
raw = (value or "").strip()
|
|
3441
|
+
if ":" not in raw:
|
|
3442
|
+
setattr(state, provider_field, "")
|
|
3443
|
+
setattr(state, model_field, _validate_model("claude", raw))
|
|
3444
|
+
return getattr(state, model_field) or "default"
|
|
3445
|
+
provider, model = (part.strip().lower() for part in raw.split(":", 1))
|
|
3446
|
+
if not provider_supports_role(provider, role):
|
|
3447
|
+
raise WizardError(f"provider {provider!r} does not support the {role} role")
|
|
3448
|
+
setattr(state, provider_field, provider)
|
|
3449
|
+
setattr(state, model_field, _validate_model(provider, model))
|
|
3450
|
+
return raw
|
|
3451
|
+
|
|
3452
|
+
|
|
3374
3453
|
def _build_lead_model(state: WizardState) -> Prompt:
|
|
3375
3454
|
t = _p(state.workspace_root, "lead_model")
|
|
3376
|
-
|
|
3377
|
-
|
|
3455
|
+
provider = native_provider_for_host(state.host_runtime)
|
|
3456
|
+
options = (
|
|
3457
|
+
["default", *picker_options(provider)]
|
|
3458
|
+
if provider else _qualified_role_model_options("lead")
|
|
3459
|
+
)
|
|
3460
|
+
return _model_pick(S_LEAD_MODEL, t["label"], options, t["echo_template"])
|
|
3378
3461
|
|
|
3379
3462
|
|
|
3380
3463
|
def _submit_lead_model(state: WizardState, value: str) -> Optional[str]:
|
|
3381
|
-
|
|
3382
|
-
|
|
3464
|
+
native_provider = native_provider_for_host(state.host_runtime)
|
|
3465
|
+
if native_provider:
|
|
3466
|
+
raw = (value or "").strip()
|
|
3467
|
+
if ":" in raw:
|
|
3468
|
+
selected_provider, raw = (
|
|
3469
|
+
part.strip().lower() for part in raw.split(":", 1)
|
|
3470
|
+
)
|
|
3471
|
+
if selected_provider != native_provider:
|
|
3472
|
+
host_label = lead_runtime_info(state.host_runtime).agent_label
|
|
3473
|
+
raise WizardError(
|
|
3474
|
+
f"{host_label.removesuffix(' CLI')} host requires provider "
|
|
3475
|
+
f"{native_provider!r} "
|
|
3476
|
+
"for the in-session lead"
|
|
3477
|
+
)
|
|
3478
|
+
state.lead_provider = native_provider
|
|
3479
|
+
state.lead_model = _validate_model(native_provider, raw)
|
|
3480
|
+
return f"lead-model: {native_provider}:{state.lead_model or 'default'}"
|
|
3481
|
+
selected = _submit_qualified_role_model(
|
|
3482
|
+
state, value, "lead", "lead_model", "lead_provider",
|
|
3483
|
+
)
|
|
3484
|
+
return f"lead-model: {selected}"
|
|
3383
3485
|
|
|
3384
3486
|
|
|
3385
3487
|
def _build_executor_model(state: WizardState) -> Prompt:
|
|
@@ -3431,17 +3533,44 @@ def _submit_antigravity_model(state: WizardState, value: str) -> Optional[str]:
|
|
|
3431
3533
|
return f"antigravity-model: {state.antigravity_model or 'default'}"
|
|
3432
3534
|
|
|
3433
3535
|
|
|
3536
|
+
def _build_grok_model(state: WizardState) -> Prompt:
|
|
3537
|
+
t = _p(state.workspace_root, "grok_model")
|
|
3538
|
+
return _model_pick(
|
|
3539
|
+
S_GROK_MODEL, t["label"], GROK_MODEL_OPTIONS, t["echo_template"],
|
|
3540
|
+
)
|
|
3541
|
+
|
|
3542
|
+
|
|
3543
|
+
def _submit_grok_model(state: WizardState, value: str) -> Optional[str]:
|
|
3544
|
+
state.grok_model = _validate_model("grok", value)
|
|
3545
|
+
return f"grok-model: {state.grok_model or 'default'}"
|
|
3546
|
+
|
|
3547
|
+
|
|
3548
|
+
def _build_kimi_model(state: WizardState) -> Prompt:
|
|
3549
|
+
t = _p(state.workspace_root, "kimi_model")
|
|
3550
|
+
return _model_pick(
|
|
3551
|
+
S_KIMI_MODEL, t["label"], KIMI_MODEL_OPTIONS, t["echo_template"],
|
|
3552
|
+
)
|
|
3553
|
+
|
|
3554
|
+
|
|
3555
|
+
def _submit_kimi_model(state: WizardState, value: str) -> Optional[str]:
|
|
3556
|
+
state.kimi_model = _validate_model("kimi", value)
|
|
3557
|
+
return f"kimi-model: {state.kimi_model or 'default'}"
|
|
3558
|
+
|
|
3559
|
+
|
|
3434
3560
|
def _build_report_writer_model(state: WizardState) -> Prompt:
|
|
3435
3561
|
t = _p(state.workspace_root, "report_writer_model")
|
|
3436
3562
|
return _model_pick(S_REPORT_WRITER_MODEL,
|
|
3437
3563
|
t["label"],
|
|
3438
|
-
|
|
3564
|
+
_qualified_role_model_options("report-writer"),
|
|
3439
3565
|
t["echo_template"])
|
|
3440
3566
|
|
|
3441
3567
|
|
|
3442
3568
|
def _submit_report_writer_model(state: WizardState, value: str) -> Optional[str]:
|
|
3443
|
-
|
|
3444
|
-
|
|
3569
|
+
selected = _submit_qualified_role_model(
|
|
3570
|
+
state, value, "report-writer", "report_writer_model",
|
|
3571
|
+
"report_writer_provider",
|
|
3572
|
+
)
|
|
3573
|
+
return f"report-writer-model: {selected}"
|
|
3445
3574
|
|
|
3446
3575
|
|
|
3447
3576
|
def _build_directive(state: WizardState) -> Prompt:
|
|
@@ -3941,7 +4070,7 @@ STEPS: list[Step] = [
|
|
|
3941
4070
|
applies=lambda s: (s.use_defaults is False
|
|
3942
4071
|
and S_LEAD_MODEL not in s.answered),
|
|
3943
4072
|
build=_build_lead_model, submit=_submit_lead_model,
|
|
3944
|
-
owns=("lead_model"
|
|
4073
|
+
owns=("lead_provider", "lead_model")),
|
|
3945
4074
|
Step(S_EXECUTOR_MODEL,
|
|
3946
4075
|
applies=lambda s: (s.use_defaults is False
|
|
3947
4076
|
and s.task_type == "implementation"
|
|
@@ -3969,13 +4098,27 @@ STEPS: list[Step] = [
|
|
|
3969
4098
|
and S_ANTIGRAVITY_MODEL not in s.answered),
|
|
3970
4099
|
build=_build_antigravity_model, submit=_submit_antigravity_model,
|
|
3971
4100
|
owns=("antigravity_model",)),
|
|
4101
|
+
Step(S_GROK_MODEL,
|
|
4102
|
+
applies=lambda s: (s.use_defaults is False
|
|
4103
|
+
and s.task_type != "implementation"
|
|
4104
|
+
and "grok" in _resolved_roster(s)
|
|
4105
|
+
and S_GROK_MODEL not in s.answered),
|
|
4106
|
+
build=_build_grok_model, submit=_submit_grok_model,
|
|
4107
|
+
owns=("grok_model",)),
|
|
4108
|
+
Step(S_KIMI_MODEL,
|
|
4109
|
+
applies=lambda s: (s.use_defaults is False
|
|
4110
|
+
and s.task_type != "implementation"
|
|
4111
|
+
and "kimi" in _resolved_roster(s)
|
|
4112
|
+
and S_KIMI_MODEL not in s.answered),
|
|
4113
|
+
build=_build_kimi_model, submit=_submit_kimi_model,
|
|
4114
|
+
owns=("kimi_model",)),
|
|
3972
4115
|
Step(S_REPORT_WRITER_MODEL,
|
|
3973
4116
|
applies=lambda s: (s.use_defaults is False
|
|
3974
4117
|
and (s.task_type == "implementation"
|
|
3975
4118
|
or "report-writer" in _resolved_roster(s))
|
|
3976
4119
|
and S_REPORT_WRITER_MODEL not in s.answered),
|
|
3977
4120
|
build=_build_report_writer_model, submit=_submit_report_writer_model,
|
|
3978
|
-
owns=("report_writer_model"
|
|
4121
|
+
owns=("report_writer_provider", "report_writer_model")),
|
|
3979
4122
|
# directive(이번 run 의 추가 지시)는 기본값/커스터마이즈와 무관하게 항상
|
|
3980
4123
|
# 묻는다 — 매 run 마다 줄 수 있는 입력이므로 'Use defaults' 분기 뒤에 숨기지
|
|
3981
4124
|
# 않는다. (use_defaults is not None: defaults_or_custom 답 이후에만 등장)
|
|
@@ -4151,8 +4294,9 @@ _FIELD_DEFAULTS: dict[str, Any] = {
|
|
|
4151
4294
|
"executor": "", "critic": "", "critic_pending_text": False,
|
|
4152
4295
|
"reuse_previous": None,
|
|
4153
4296
|
"use_defaults": None, "workers_override": "",
|
|
4154
|
-
"lead_model": "", "claude_model": "", "codex_model": "",
|
|
4155
|
-
"antigravity_model": "", "
|
|
4297
|
+
"lead_provider": "", "lead_model": "", "claude_model": "", "codex_model": "",
|
|
4298
|
+
"antigravity_model": "", "grok_model": "", "kimi_model": "",
|
|
4299
|
+
"report_writer_provider": "", "report_writer_model": "", "directive": "",
|
|
4156
4300
|
"directive_pending_text": False,
|
|
4157
4301
|
"related_tasks_raw": "", "related_tasks_pending_text": False,
|
|
4158
4302
|
"clarification_response_path": "", "clarification_pending_text": False,
|
|
@@ -4176,13 +4320,15 @@ def _reset_field(state: WizardState, fname: str) -> None:
|
|
|
4176
4320
|
# ---- Public API ---------------------------------------------------------
|
|
4177
4321
|
|
|
4178
4322
|
def init_state(
|
|
4179
|
-
*, workspace_root: str, project_root: str, project_id: str
|
|
4323
|
+
*, workspace_root: str, project_root: str, project_id: str,
|
|
4324
|
+
host_runtime: str = "claude-code",
|
|
4180
4325
|
) -> WizardState:
|
|
4181
4326
|
"""Bootstrap a new wizard state."""
|
|
4182
4327
|
return WizardState(
|
|
4183
4328
|
workspace_root=workspace_root,
|
|
4184
4329
|
project_root=project_root,
|
|
4185
4330
|
project_id=project_id,
|
|
4331
|
+
host_runtime=host_runtime,
|
|
4186
4332
|
)
|
|
4187
4333
|
|
|
4188
4334
|
|
|
@@ -4402,6 +4548,14 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
4402
4548
|
for path in (state.feature_evidence_path, state.project_evidence_path)
|
|
4403
4549
|
if path
|
|
4404
4550
|
]
|
|
4551
|
+
worker_models = ",".join(
|
|
4552
|
+
f"{provider}={model}"
|
|
4553
|
+
for provider, model in (
|
|
4554
|
+
("grok", state.grok_model),
|
|
4555
|
+
("kimi", state.kimi_model),
|
|
4556
|
+
)
|
|
4557
|
+
if model
|
|
4558
|
+
)
|
|
4405
4559
|
return {
|
|
4406
4560
|
"project-root": state.project_root,
|
|
4407
4561
|
"project-id": state.project_id,
|
|
@@ -4420,10 +4574,13 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
4420
4574
|
"base-ref": base_ref,
|
|
4421
4575
|
"workers": workers,
|
|
4422
4576
|
"directive": state.directive,
|
|
4577
|
+
"lead-provider": state.lead_provider,
|
|
4423
4578
|
"lead-model": state.lead_model,
|
|
4424
4579
|
"claude-model": state.claude_model,
|
|
4425
4580
|
"codex-model": state.codex_model,
|
|
4426
4581
|
"antigravity-model": state.antigravity_model,
|
|
4582
|
+
"worker-model": worker_models,
|
|
4583
|
+
"report-writer-provider": state.report_writer_provider,
|
|
4427
4584
|
"report-writer-model": state.report_writer_model,
|
|
4428
4585
|
"related-tasks": state.related_tasks_raw,
|
|
4429
4586
|
"clarification-response": state.clarification_response_path,
|
|
@@ -4480,15 +4637,25 @@ def confirmation_block(state: WizardState) -> str:
|
|
|
4480
4637
|
else:
|
|
4481
4638
|
roster = state.workers_override or ",".join(state.profile_workers) or "(profile default)"
|
|
4482
4639
|
lines.append(f" workers : {roster}")
|
|
4483
|
-
lines.append(
|
|
4640
|
+
lines.append(
|
|
4641
|
+
f" lead : {state.lead_provider or 'claude'} / "
|
|
4642
|
+
f"{state.lead_model or 'default'}"
|
|
4643
|
+
)
|
|
4484
4644
|
if state.claude_model:
|
|
4485
4645
|
lines.append(f" claude-model : {state.claude_model}")
|
|
4486
4646
|
if state.codex_model:
|
|
4487
4647
|
lines.append(f" codex-model : {state.codex_model}")
|
|
4488
4648
|
if state.antigravity_model:
|
|
4489
4649
|
lines.append(f" antigravity-model : {state.antigravity_model}")
|
|
4490
|
-
if state.
|
|
4491
|
-
lines.append(f"
|
|
4650
|
+
if state.grok_model:
|
|
4651
|
+
lines.append(f" grok-model : {state.grok_model}")
|
|
4652
|
+
if state.kimi_model:
|
|
4653
|
+
lines.append(f" kimi-model : {state.kimi_model}")
|
|
4654
|
+
if state.report_writer_model or state.report_writer_provider:
|
|
4655
|
+
lines.append(
|
|
4656
|
+
f" report-writer : {state.report_writer_provider or 'claude'} / "
|
|
4657
|
+
f"{state.report_writer_model or 'default'}"
|
|
4658
|
+
)
|
|
4492
4659
|
lines.append(f" directive : {state.directive or '(none)'}")
|
|
4493
4660
|
if state.related_tasks_raw:
|
|
4494
4661
|
lines.append(f" related-tasks : {state.related_tasks_raw}")
|
|
@@ -4587,6 +4754,11 @@ def _cli(argv: list[str]) -> int:
|
|
|
4587
4754
|
p_init.add_argument("--workspace-root", required=True)
|
|
4588
4755
|
p_init.add_argument("--project-root", required=True)
|
|
4589
4756
|
p_init.add_argument("--project-id", required=True)
|
|
4757
|
+
p_init.add_argument(
|
|
4758
|
+
"--host-runtime",
|
|
4759
|
+
default="claude-code",
|
|
4760
|
+
choices=("claude-code", "codex", "antigravity", "external"),
|
|
4761
|
+
)
|
|
4590
4762
|
p_init.add_argument("--critic", default="")
|
|
4591
4763
|
|
|
4592
4764
|
p_step = sub.add_parser("step")
|
|
@@ -4615,6 +4787,7 @@ def _cli(argv: list[str]) -> int:
|
|
|
4615
4787
|
workspace_root=args.workspace_root,
|
|
4616
4788
|
project_root=args.project_root,
|
|
4617
4789
|
project_id=args.project_id,
|
|
4790
|
+
host_runtime=args.host_runtime,
|
|
4618
4791
|
)
|
|
4619
4792
|
if args.critic:
|
|
4620
4793
|
state.critic = args.critic
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Provider-registry-derived worker artifact paths."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
from .models import provider_ids
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def artifact_worker_ids() -> list[str]:
|
|
11
|
+
"""All analysis worker ids plus the non-analysis report writer slot."""
|
|
12
|
+
return [*provider_ids("analyser"), "report-writer"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def artifacts_from_context(
|
|
16
|
+
ctx: Mapping[str, Any], *, relative: bool
|
|
17
|
+
) -> dict[str, dict[str, str]]:
|
|
18
|
+
"""Derive prompt/result/error paths without provider-specific branches."""
|
|
19
|
+
prompt_root = _context_root(ctx, "RUN_PROMPTS", relative)
|
|
20
|
+
result_root = _context_root(ctx, "WORKER_RESULTS", relative)
|
|
21
|
+
task_type = str(ctx.get("TASK_TYPE_SEGMENT", ""))
|
|
22
|
+
prompt_suffix = f"-{task_type}-{ctx.get('RUN_PROMPTS_SEQ', '')}"
|
|
23
|
+
result_suffix = f"-{task_type}-{ctx.get('WORKER_RESULTS_SEQ', '')}"
|
|
24
|
+
return {
|
|
25
|
+
worker_id: {
|
|
26
|
+
"promptPath": str(prompt_root / f"{worker_id}-worker-prompt{prompt_suffix}.md"),
|
|
27
|
+
"resultPath": str(result_root / f"{worker_id}-worker{result_suffix}.md"),
|
|
28
|
+
"errorsSidecarPath": str(
|
|
29
|
+
result_root / f"{worker_id}-worker-errors{result_suffix}.json"
|
|
30
|
+
),
|
|
31
|
+
}
|
|
32
|
+
for worker_id in artifact_worker_ids()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _context_root(ctx: Mapping[str, Any], name: str, relative: bool) -> Path:
|
|
37
|
+
if name == "WORKER_RESULTS":
|
|
38
|
+
key = "WORKER_RESULTS_RELATIVE_PATH" if relative else "WORKER_RESULTS_PATH"
|
|
39
|
+
else:
|
|
40
|
+
suffix = "RELATIVE_PATH" if relative else "DIR"
|
|
41
|
+
key = f"{name}_{suffix}"
|
|
42
|
+
value = str(ctx.get(key, ""))
|
|
43
|
+
if value or relative:
|
|
44
|
+
return Path(value)
|
|
45
|
+
relative_value = str(ctx.get(f"{name}_RELATIVE_PATH", ""))
|
|
46
|
+
return Path(str(ctx.get("PROJECT_ROOT", ""))) / relative_value
|
|
@@ -8,7 +8,9 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
from .models import provider_ids
|
|
12
|
+
|
|
13
|
+
ALLOWED_WORKERS = provider_ids("analyser") + ["report-writer"]
|
|
12
14
|
DEFAULT_WORKERS = ["claude", "codex", "report-writer"]
|
|
13
15
|
PROFILE_BULLET_HEADERS = {
|
|
14
16
|
"- Workers:",
|
|
@@ -108,7 +108,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
108
108
|
" - `Out-of-plan edits` block listing every file touched outside the approved plan with rationale (empty block preferred)\n"
|
|
109
109
|
' - validation evidence: actual stdout/stderr and exit code for every pre / mid / post command from the plan (no paraphrased "tests pass")\n'
|
|
110
110
|
" - TDD evidence for TDD-applicable steps: failing-test output before implementation commit and passing-test output after, with framing SHAs\n"
|
|
111
|
-
" - per-verifier sections for every verifier in the resolved roster
|
|
111
|
+
" - per-verifier sections for every verifier in the resolved roster, with an independent verdict (PASS / CONCERNS / FAIL) and cited diff snippets; dissent is preserved by the Okstra lead\n"
|
|
112
112
|
" - rollback verification (advisory, never blocks — record the revert path for a human; `result` is ok / not-applicable / advisory — human-run)\n"
|
|
113
113
|
" - routing recommendation for `final-verification` (ready / needs new error-analysis or planning loop)"
|
|
114
114
|
),
|
|
@@ -125,7 +125,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
125
125
|
" - entering this phase only when the cited final-verification report's `Verdict Token` is exactly `accepted`\n"
|
|
126
126
|
" - asking the user (via `AskUserQuestion` / interactive prompt) which delivery action to take: `local checkout`, `push + PR`, or `skip` (end the run)\n"
|
|
127
127
|
" - asking the user to pick a PR base branch from `staging` | `preprod` | `prod` | `main` | `dev` | a user-supplied branch name\n"
|
|
128
|
-
" - drafting PR title and PR body **inline as the
|
|
128
|
+
" - drafting PR title and PR body **inline as the Okstra lead** (no drafter worker and no delegated dispatch); the lead reviews its own draft with the user before any mutating git / gh command runs\n"
|
|
129
129
|
" - read-only git inspection: `git status`, `git diff`, `git log`, `git rev-parse`\n"
|
|
130
130
|
" - pushing the current feature branch to its origin remote via `git push -u origin <current-branch>` (the feature branch only — NEVER the base branch)\n"
|
|
131
131
|
" - creating a pull request via `gh pr create --base <chosen-base> --head <current-branch>`; if a PR with the same head already exists, surface its URL and skip creation\n"
|
|
@@ -190,6 +190,8 @@ def compute_workflow_state(
|
|
|
190
190
|
else:
|
|
191
191
|
if current_task_status in (
|
|
192
192
|
"instruction-set-generated",
|
|
193
|
+
"ready-for-lead",
|
|
194
|
+
# Compatibility with task manifests prepared before provider-neutral statuses.
|
|
193
195
|
"ready-for-claude",
|
|
194
196
|
"claude-session-started",
|
|
195
197
|
):
|
|
@@ -20,7 +20,9 @@ from .antigravity import (
|
|
|
20
20
|
find_antigravity_sessions,
|
|
21
21
|
)
|
|
22
22
|
from .paths import claude_project_dir, utc_now
|
|
23
|
-
from .pricing import
|
|
23
|
+
from .pricing import antigravity_cost_usd, provider_cost_usd
|
|
24
|
+
from okstra_ctl.models import provider_wrappers
|
|
25
|
+
from okstra_ctl.lead_runtime import ARTIFACT_ONLY_LEAD_RUNTIMES
|
|
24
26
|
from okstra_ctl.wrapper_status import read_wrapper_status, status_path_for_prompt
|
|
25
27
|
|
|
26
28
|
|
|
@@ -446,14 +448,16 @@ def _codex_worker_windows(project_root: Path, state: dict) -> dict[str, list[tup
|
|
|
446
448
|
return windows
|
|
447
449
|
|
|
448
450
|
|
|
449
|
-
def
|
|
451
|
+
def _cli_assignment_provider(worker: dict) -> str:
|
|
450
452
|
worker_id = str(worker.get("workerId") or "").strip()
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if
|
|
455
|
-
return
|
|
456
|
-
|
|
453
|
+
explicit_provider = str(worker.get("provider") or "").strip()
|
|
454
|
+
provider = explicit_provider or str(worker.get("agent") or "").strip()
|
|
455
|
+
runner = str(worker.get("runner") or "").strip()
|
|
456
|
+
if runner and runner != "cli-wrapper":
|
|
457
|
+
return ""
|
|
458
|
+
if not explicit_provider and worker_id == "report-writer":
|
|
459
|
+
provider = "codex"
|
|
460
|
+
return provider if provider in provider_wrappers() else ""
|
|
457
461
|
|
|
458
462
|
|
|
459
463
|
def _cli_sessions_for_windows(
|
|
@@ -482,8 +486,10 @@ def _cli_session_totals(provider: str, session_paths: list[Path]) -> list[dict]:
|
|
|
482
486
|
for session_path in session_paths:
|
|
483
487
|
if provider == "codex":
|
|
484
488
|
total = codex_session_total(session_path)
|
|
485
|
-
|
|
489
|
+
elif provider == "antigravity":
|
|
486
490
|
total = antigravity_session_total(session_path)
|
|
491
|
+
else:
|
|
492
|
+
continue
|
|
487
493
|
if total.get("available"):
|
|
488
494
|
totals.append(total)
|
|
489
495
|
return totals
|
|
@@ -496,8 +502,9 @@ def _cli_usage_block(provider: str, totals: dict, session_paths: list[Path]) ->
|
|
|
496
502
|
if totals.get("model"):
|
|
497
503
|
block["model"] = totals["model"]
|
|
498
504
|
block["cliModel"] = totals["model"]
|
|
499
|
-
if provider
|
|
500
|
-
cost =
|
|
505
|
+
if provider in {"codex", "grok", "kimi"}:
|
|
506
|
+
cost = provider_cost_usd(
|
|
507
|
+
provider,
|
|
501
508
|
totals.get("model"),
|
|
502
509
|
totals.get("inputTokens", 0) or 0,
|
|
503
510
|
totals.get("cachedInputTokens", 0) or 0,
|
|
@@ -627,7 +634,7 @@ def _attach_cli_usage(
|
|
|
627
634
|
) -> None:
|
|
628
635
|
"""Layer aggregated CLI token/cost onto a Claude-side worker ``block``.
|
|
629
636
|
|
|
630
|
-
A
|
|
637
|
+
A CLI-wrapper worker re-dispatched within one aggregated wrapper
|
|
631
638
|
window produces several rollout jsonls; pricing only the latest (the old
|
|
632
639
|
``find_codex_session`` -> ``sessions[-1]`` behavior) dropped the earlier
|
|
633
640
|
attempts' tokens. We sum every in-window session here so the redispatched
|
|
@@ -653,18 +660,19 @@ def _attach_cli_usage(
|
|
|
653
660
|
block[key] = cli[key]
|
|
654
661
|
|
|
655
662
|
|
|
656
|
-
def
|
|
663
|
+
def _collect_cli_runtime_usage(state: dict, project_root: Path) -> dict:
|
|
657
664
|
windows_by_worker = _codex_worker_windows(project_root, state)
|
|
658
665
|
state["leadUsage"] = na_block(
|
|
659
|
-
"
|
|
666
|
+
"Host lead token accounting is unavailable; CLI worker usage is collected only from attributable provider logs."
|
|
660
667
|
)
|
|
661
668
|
for worker in state.get("workers", []):
|
|
662
669
|
if not isinstance(worker, dict):
|
|
663
670
|
continue
|
|
664
|
-
provider =
|
|
671
|
+
provider = _cli_assignment_provider(worker)
|
|
665
672
|
if not provider:
|
|
666
673
|
worker["usage"] = na_block(
|
|
667
|
-
|
|
674
|
+
"worker is host-native or has no registered CLI provider: "
|
|
675
|
+
f"{worker.get('provider') or worker.get('agent') or worker.get('workerId')}"
|
|
668
676
|
)
|
|
669
677
|
continue
|
|
670
678
|
worker_id = str(worker.get("workerId") or "").strip()
|
|
@@ -745,7 +753,7 @@ def _populate_usage_summary(
|
|
|
745
753
|
"definitions": {
|
|
746
754
|
"totalTokens": "Sum of input + output + cache_creation + cache_read tokens (raw processed volume; matches Anthropic API breakdown). Cache reads are 95%+ in long sessions.",
|
|
747
755
|
"billableEquivalentTokens": "Tokens normalized to base-input-price units (cache_creation_5m x1.25, cache_creation_1h x2.0, cache_read x0.1, output x5). 5m vs 1h is split from usage.cache_creation when the API breakdown is present; otherwise all cache_creation falls into 5m.",
|
|
748
|
-
"estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers
|
|
756
|
+
"estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers attributable registered-provider CLI calls.",
|
|
749
757
|
},
|
|
750
758
|
}
|
|
751
759
|
|
|
@@ -761,8 +769,8 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
761
769
|
task_key = state.get("taskKey", "")
|
|
762
770
|
team_name = resolve_team_name(state)
|
|
763
771
|
lead_sid = (state.get("lead") or {}).get("sessionId")
|
|
764
|
-
if state.get("leadRuntime")
|
|
765
|
-
return
|
|
772
|
+
if state.get("leadRuntime") in ARTIFACT_ONLY_LEAD_RUNTIMES:
|
|
773
|
+
return _collect_cli_runtime_usage(state, cwd)
|
|
766
774
|
|
|
767
775
|
# 1) Claude sessions (lead + claude-side workers). Cache totals at scan
|
|
768
776
|
# time so we don't re-read the jsonl when a worker matches multiple
|
|
@@ -878,9 +886,10 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
878
886
|
block["additionalSessionIds"] = [sid for sid, _, _ in matched[1:]]
|
|
879
887
|
block["matchedAgentNames"] = sorted({t.get("agentName") for _, _, t in matched if t.get("agentName")})
|
|
880
888
|
|
|
881
|
-
# For
|
|
882
|
-
#
|
|
883
|
-
|
|
889
|
+
# For CLI-wrapper workers, retain execution evidence and attach any
|
|
890
|
+
# attributable provider transcript usage without treating absence as zero.
|
|
891
|
+
provider = _cli_assignment_provider(worker)
|
|
892
|
+
if provider:
|
|
884
893
|
windows, status_path, used_fallback = _worker_cli_windows(
|
|
885
894
|
cwd,
|
|
886
895
|
worker,
|
|
@@ -888,7 +897,7 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
|
|
|
888
897
|
)
|
|
889
898
|
_attach_cli_usage(
|
|
890
899
|
block,
|
|
891
|
-
|
|
900
|
+
provider,
|
|
892
901
|
cwd,
|
|
893
902
|
windows,
|
|
894
903
|
status_path,
|