okstra 0.186.7 → 0.187.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli-registry.d.mts +88 -1
- package/dist/cli-registry.mjs +68 -111
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +0 -1
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +8 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.mjs +1 -14
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/config.mjs +38 -40
- package/dist/commands/lifecycle/config.mjs.map +1 -1
- package/dist/commands/lifecycle/doctor.d.mts +22 -7
- package/dist/commands/lifecycle/doctor.mjs +77 -49
- package/dist/commands/lifecycle/doctor.mjs.map +1 -1
- package/dist/commands/lifecycle/install.d.mts +12 -10
- package/dist/commands/lifecycle/install.mjs +104 -39
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/lifecycle/paths.mjs +8 -3
- package/dist/commands/lifecycle/paths.mjs.map +1 -1
- package/dist/commands/lifecycle/preflight.mjs +2 -1
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/lifecycle/setup.mjs +22 -36
- package/dist/commands/lifecycle/setup.mjs.map +1 -1
- package/dist/commands/lifecycle/uninstall.d.mts +4 -2
- package/dist/commands/lifecycle/uninstall.mjs +59 -15
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +7 -1
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/helper-scripts.d.mts +1 -1
- package/dist/lib/helper-scripts.mjs +10 -18
- package/dist/lib/helper-scripts.mjs.map +1 -1
- package/dist/lib/host-config.d.mts +72 -0
- package/dist/lib/host-config.mjs +404 -0
- package/dist/lib/host-config.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +2 -2
- package/dist/lib/host-registry-client.mjs +0 -3
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/dist/lib/install-assets.d.mts +1 -0
- package/dist/lib/install-assets.mjs +4 -0
- package/dist/lib/install-assets.mjs.map +1 -1
- package/dist/lib/proc.d.mts +2 -0
- package/dist/lib/proc.mjs +12 -0
- package/dist/lib/proc.mjs.map +1 -1
- package/dist/lib/python-command.d.mts +2 -0
- package/dist/lib/python-command.mjs +52 -0
- package/dist/lib/python-command.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +2 -5
- package/dist/lib/python-helper.mjs +3 -52
- package/dist/lib/python-helper.mjs.map +1 -1
- package/dist/lib/runtime-payload.d.mts +23 -0
- package/dist/lib/runtime-payload.mjs +57 -0
- package/dist/lib/runtime-payload.mjs.map +1 -0
- package/dist/lib/types.d.mts +24 -13
- package/docs/architecture/storage-model.md +21 -5
- package/docs/architecture.md +37 -36
- package/docs/cli.md +55 -76
- package/docs/coding-rules.md +295 -0
- package/docs/container.md +10 -36
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/skills/okstra-code-review.md +0 -1
- package/docs/for-ai/skills/okstra-container-build.md +8 -41
- package/docs/for-ai/skills/okstra-inspect.md +4 -4
- package/docs/for-ai/skills/okstra-manager.md +2 -2
- package/docs/for-ai/skills/okstra-rollup.md +0 -1
- package/docs/for-ai/skills/okstra-run.md +3 -3
- package/docs/for-ai/skills/okstra-user-response.md +0 -1
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +62 -62
- package/docs/task-process/README.md +3 -3
- package/docs/task-process/common-flow.md +1 -1
- package/docs/task-process/error-analysis.md +3 -3
- package/docs/task-process/implementation-planning.md +3 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +3 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +6 -11
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/lib/okstra/cli.sh +4 -0
- package/runtime/bin/lib/okstra/globals.sh +2 -0
- package/runtime/bin/lib/okstra/interactive.sh +26 -173
- package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/bin/okstra-compact-reminder.sh +1 -5
- package/runtime/bin/okstra-error-log.py +19 -2
- package/runtime/bin/okstra-import-check.py +26 -0
- package/runtime/bin/okstra-inject-report-index.py +5 -4
- package/runtime/bin/okstra-provider-exec.py +22 -17
- package/runtime/bin/okstra-render-final-report.py +16 -6
- package/runtime/bin/okstra-render-report-views.py +25 -18
- package/runtime/bin/okstra-report-translate.py +30 -18
- package/runtime/bin/okstra-spawn-followups.py +4 -0
- package/runtime/bin/okstra-token-usage.py +3 -0
- package/runtime/bin/okstra.sh +4 -2
- package/runtime/bin/okstra_bootstrap.py +58 -0
- package/runtime/prompts/coding-preflight/overview.md +1 -1
- package/runtime/prompts/duties/planning-worker.md +1 -1
- package/runtime/prompts/launch.template.md +35 -9
- package/runtime/prompts/lead/convergence.md +96 -87
- package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
- package/runtime/prompts/lead/plan-body-verification.md +65 -46
- package/runtime/prompts/lead/report-writer.md +34 -4
- package/runtime/prompts/lead/team-contract.md +4 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +3 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
- package/runtime/prompts/profiles/error-analysis.md +4 -3
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +22 -17
- package/runtime/prompts/profiles/implementation.md +3 -2
- package/runtime/prompts/profiles/requirements-discovery.md +2 -1
- package/runtime/prompts/wizard/prompts.ko.json +29 -8
- package/runtime/python/okstra_ctl/__init__.py +6 -27
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +40 -22
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -21
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
- package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
- package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +12 -1
- package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
- package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +215 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +408 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
- package/runtime/python/okstra_ctl/approval_decisions.py +260 -8
- package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
- package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
- package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
- package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
- package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
- package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
- package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
- package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
- package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
- package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
- package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
- package/runtime/python/okstra_ctl/cmux.py +38 -31
- package/runtime/python/okstra_ctl/code_review_target.py +163 -1
- package/runtime/python/okstra_ctl/conformance.py +23 -0
- package/runtime/python/okstra_ctl/container.py +38 -421
- package/runtime/python/okstra_ctl/context_cost.py +16 -2
- package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
- package/runtime/python/okstra_ctl/convergence.py +101 -15
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
- package/runtime/python/okstra_ctl/convergence_store.py +5 -29
- package/runtime/python/okstra_ctl/design_prep.py +108 -12
- package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
- package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
- package/runtime/python/okstra_ctl/dispatch_core.py +102 -25
- package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
- package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -1
- package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
- package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
- package/runtime/python/okstra_ctl/error_log_core.py +1 -1
- package/runtime/python/okstra_ctl/error_report.py +14 -0
- package/runtime/python/okstra_ctl/error_zip.py +18 -2
- package/runtime/python/okstra_ctl/execution_identity.py +100 -7
- package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
- package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +90 -16
- package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
- package/runtime/python/okstra_ctl/handoff.py +24 -1
- package/runtime/python/okstra_ctl/ids.py +6 -16
- package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
- package/runtime/python/okstra_ctl/implementation_options.py +25 -11
- package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
- package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
- package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
- package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
- package/runtime/python/okstra_ctl/index.py +2 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
- package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
- package/runtime/python/okstra_ctl/json_boundary.py +10 -0
- package/runtime/python/okstra_ctl/listing.py +0 -91
- package/runtime/python/okstra_ctl/locks.py +5 -19
- package/runtime/python/okstra_ctl/log_report.py +14 -0
- package/runtime/python/okstra_ctl/manager_cli.py +22 -3
- package/runtime/python/okstra_ctl/manager_launch.py +4 -8
- package/runtime/python/okstra_ctl/material.py +2 -2
- package/runtime/python/okstra_ctl/migrate.py +23 -1
- package/runtime/python/okstra_ctl/model_cli.py +19 -5
- package/runtime/python/okstra_ctl/model_discovery.py +46 -47
- package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
- package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
- package/runtime/python/okstra_ctl/model_io/references.py +370 -0
- package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
- package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
- package/runtime/python/okstra_ctl/model_pool.py +10 -6
- package/runtime/python/okstra_ctl/models.py +8 -8
- package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
- package/runtime/python/okstra_ctl/next_phase.py +121 -69
- package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
- package/runtime/python/okstra_ctl/pane_title.py +11 -8
- package/runtime/python/okstra_ctl/path_hints.py +36 -11
- package/runtime/python/okstra_ctl/paths.py +76 -8
- package/runtime/python/okstra_ctl/plan_items.py +132 -16
- package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
- package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
- package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
- package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
- package/runtime/python/okstra_ctl/prepare_error.py +20 -0
- package/runtime/python/okstra_ctl/prior_planning.py +157 -0
- package/runtime/python/okstra_ctl/profile_show.py +18 -1
- package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
- package/runtime/python/okstra_ctl/recap.py +22 -1
- package/runtime/python/okstra_ctl/reconcile.py +84 -0
- package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
- package/runtime/python/okstra_ctl/render.py +98 -32
- package/runtime/python/okstra_ctl/render_final_report.py +3 -7
- package/runtime/python/okstra_ctl/report_assembly.py +297 -7
- package/runtime/python/okstra_ctl/report_contract.py +30 -7
- package/runtime/python/okstra_ctl/report_finalize.py +206 -15
- package/runtime/python/okstra_ctl/report_html/common.py +2 -2
- package/runtime/python/okstra_ctl/report_html/render.py +8 -12
- package/runtime/python/okstra_ctl/report_language.py +8 -4
- package/runtime/python/okstra_ctl/report_narrative.py +90 -3
- package/runtime/python/okstra_ctl/report_projections.py +8 -2
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
- package/runtime/python/okstra_ctl/report_translation.py +2 -35
- package/runtime/python/okstra_ctl/report_views.py +4 -22
- package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
- package/runtime/python/okstra_ctl/rollup.py +13 -0
- package/runtime/python/okstra_ctl/run.py +639 -138
- package/runtime/python/okstra_ctl/run_audit.py +13 -0
- package/runtime/python/okstra_ctl/run_index_row.py +0 -12
- package/runtime/python/okstra_ctl/seeding.py +21 -11
- package/runtime/python/okstra_ctl/sequence.py +1 -1
- package/runtime/python/okstra_ctl/session.py +64 -14
- package/runtime/python/okstra_ctl/set_work_status.py +18 -0
- package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
- package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
- package/runtime/python/okstra_ctl/stage_map.py +57 -11
- package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
- package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
- package/runtime/python/okstra_ctl/stage_targets.py +3 -12
- package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
- package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
- package/runtime/python/okstra_ctl/task_target.py +4 -16
- package/runtime/python/okstra_ctl/team.py +49 -2
- package/runtime/python/okstra_ctl/time_report.py +17 -2
- package/runtime/python/okstra_ctl/usage_report.py +11 -0
- package/runtime/python/okstra_ctl/user_response.py +55 -218
- package/runtime/python/okstra_ctl/user_response_values.py +242 -0
- package/runtime/python/okstra_ctl/validation_contract.py +3 -0
- package/runtime/python/okstra_ctl/verification_target.py +68 -0
- package/runtime/python/okstra_ctl/wizard.py +457 -92
- package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
- package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
- package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
- package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
- package/runtime/python/okstra_ctl/worker_request.py +2 -1
- package/runtime/python/okstra_ctl/worker_runner.py +16 -9
- package/runtime/python/okstra_ctl/worker_state.py +16 -1
- package/runtime/python/okstra_ctl/workflow.py +18 -1
- package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
- package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
- package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
- package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
- package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
- package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
- package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
- package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
- package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
- package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
- package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
- package/runtime/python/okstra_ctl/write_policy.py +90 -213
- package/runtime/python/okstra_project/__init__.py +0 -4
- package/runtime/python/okstra_project/dirs.py +2 -2
- package/runtime/python/okstra_project/phase_pointer.py +84 -0
- package/runtime/python/okstra_project/slug.py +24 -0
- package/runtime/python/okstra_project/state.py +22 -230
- package/runtime/python/okstra_token_usage/claude.py +9 -1
- package/runtime/python/okstra_token_usage/cli.py +3 -1
- package/runtime/python/okstra_token_usage/collect.py +71 -10
- package/runtime/python/okstra_token_usage/cursor.py +2 -0
- package/runtime/python/okstra_token_usage/grok.py +1 -5
- package/runtime/python/okstra_token_usage/paths.py +26 -0
- package/runtime/python/okstra_token_usage/pricing.py +19 -7
- package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
- package/runtime/schemas/final-report-v2.0.schema.json +9 -26
- package/runtime/schemas/final-report-v3.0.schema.json +173 -41
- package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
- package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
- package/runtime/skills/okstra-container-build/SKILL.md +16 -47
- package/runtime/skills/okstra-inspect/facets/history.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +7 -6
- package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +7 -5
- package/runtime/templates/report-writer-prompt-preamble.md +1 -1
- package/runtime/templates/reports/brief.template.md +6 -2
- package/runtime/templates/reports/error-analysis-input.template.md +2 -0
- package/runtime/templates/reports/final-verification-input.template.md +2 -0
- package/runtime/templates/reports/implementation-input.template.md +7 -1
- package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
- package/runtime/templates/reports/quick-input.template.md +2 -0
- package/runtime/templates/reports/release-handoff-input.template.md +6 -1
- package/runtime/templates/reports/report.js +20 -3
- package/runtime/templates/reports/schedule.template.md +2 -0
- package/runtime/templates/reports/settings.template.json +0 -10
- package/runtime/templates/reports/task-brief.template.md +3 -0
- package/runtime/templates/reports/user-response.template.md +7 -3
- package/runtime/validators/checks/fixtures-01.py +57 -0
- package/runtime/validators/checks/fixtures-02.py +565 -0
- package/runtime/validators/checks/runners-01.py +108 -0
- package/runtime/validators/checks/validate-assets-01.py +60 -0
- package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
- package/runtime/validators/checks/validate-tasks-01.py +46 -0
- package/runtime/validators/checks/validate-tasks-02.py +85 -0
- package/runtime/validators/checks/validate-tasks-03.py +61 -0
- package/runtime/validators/checks/validate-tasks-04.py +118 -0
- package/runtime/validators/forbidden_actions.py +73 -3
- package/runtime/validators/lib/common.sh +5 -0
- package/runtime/validators/lib/fixtures.sh +7 -591
- package/runtime/validators/lib/paths.sh +13 -0
- package/runtime/validators/lib/runners.sh +6 -104
- package/runtime/validators/lib/summary.sh +1 -1
- package/runtime/validators/lib/validate-assets.sh +6 -56
- package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
- package/runtime/validators/lib/validate-tasks.sh +9 -294
- package/runtime/validators/validate-implementation-plan-stages.py +4 -4
- package/runtime/validators/validate-run.py +1369 -2654
- package/runtime/validators/validate-workflow.sh +56 -16
- package/runtime/validators/validate_improvement_report.py +2 -1
- package/runtime/validators/validate_session_conformance.py +295 -49
- package/dist/commands/execute/agent-prompt.d.mts +0 -1
- package/dist/commands/execute/agent-prompt.mjs +0 -24
- package/dist/commands/execute/agent-prompt.mjs.map +0 -1
- package/dist/commands/execute/codex-dispatch.d.mts +0 -3
- package/dist/commands/execute/codex-dispatch.mjs +0 -6
- package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
- package/dist/commands/execute/codex-run.d.mts +0 -3
- package/dist/commands/execute/codex-run.mjs +0 -62
- package/dist/commands/execute/codex-run.mjs.map +0 -1
- package/dist/commands/execute/convergence.d.mts +0 -1
- package/dist/commands/execute/convergence.mjs +0 -38
- package/dist/commands/execute/convergence.mjs.map +0 -1
- package/dist/commands/execute/error-log.d.mts +0 -1
- package/dist/commands/execute/error-log.mjs +0 -18
- package/dist/commands/execute/error-log.mjs.map +0 -1
- package/dist/commands/execute/git-reconcile.d.mts +0 -1
- package/dist/commands/execute/git-reconcile.mjs +0 -30
- package/dist/commands/execute/git-reconcile.mjs.map +0 -1
- package/dist/commands/execute/handoff.d.mts +0 -1
- package/dist/commands/execute/handoff.mjs +0 -31
- package/dist/commands/execute/handoff.mjs.map +0 -1
- package/dist/commands/execute/incremental-carry.d.mts +0 -1
- package/dist/commands/execute/incremental-carry.mjs +0 -20
- package/dist/commands/execute/incremental-carry.mjs.map +0 -1
- package/dist/commands/execute/incremental-scope.d.mts +0 -1
- package/dist/commands/execute/incremental-scope.mjs +0 -29
- package/dist/commands/execute/incremental-scope.mjs.map +0 -1
- package/dist/commands/execute/integrate-stages.d.mts +0 -1
- package/dist/commands/execute/integrate-stages.mjs +0 -24
- package/dist/commands/execute/integrate-stages.mjs.map +0 -1
- package/dist/commands/execute/pane-title.d.mts +0 -1
- package/dist/commands/execute/pane-title.mjs +0 -20
- package/dist/commands/execute/pane-title.mjs.map +0 -1
- package/dist/commands/execute/plan-items.d.mts +0 -1
- package/dist/commands/execute/plan-items.mjs +0 -9
- package/dist/commands/execute/plan-items.mjs.map +0 -1
- package/dist/commands/execute/plan-validate.d.mts +0 -1
- package/dist/commands/execute/plan-validate.mjs +0 -68
- package/dist/commands/execute/plan-validate.mjs.map +0 -1
- package/dist/commands/execute/plan-verify.d.mts +0 -1
- package/dist/commands/execute/plan-verify.mjs +0 -43
- package/dist/commands/execute/plan-verify.mjs.map +0 -1
- package/dist/commands/execute/spawn-followups.d.mts +0 -1
- package/dist/commands/execute/spawn-followups.mjs +0 -22
- package/dist/commands/execute/spawn-followups.mjs.map +0 -1
- package/dist/commands/execute/team.d.mts +0 -3
- package/dist/commands/execute/team.mjs +0 -66
- package/dist/commands/execute/team.mjs.map +0 -1
- package/dist/commands/execute/token-usage.d.mts +0 -1
- package/dist/commands/execute/token-usage.mjs +0 -19
- package/dist/commands/execute/token-usage.mjs.map +0 -1
- package/dist/commands/execute/worker-audit-check.d.mts +0 -1
- package/dist/commands/execute/worker-audit-check.mjs +0 -34
- package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
- package/dist/commands/execute/worker-dispatch.d.mts +0 -7
- package/dist/commands/execute/worker-dispatch.mjs +0 -64
- package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
- package/dist/commands/execute/worker-state.d.mts +0 -1
- package/dist/commands/execute/worker-state.mjs +0 -28
- package/dist/commands/execute/worker-state.mjs.map +0 -1
- package/dist/commands/execute/worktree-lookup.d.mts +0 -1
- package/dist/commands/execute/worktree-lookup.mjs +0 -92
- package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
- package/dist/commands/execute/worktree-status.d.mts +0 -1
- package/dist/commands/execute/worktree-status.mjs +0 -121
- package/dist/commands/execute/worktree-status.mjs.map +0 -1
- package/dist/commands/inspect/code-review.d.mts +0 -1
- package/dist/commands/inspect/code-review.mjs +0 -32
- package/dist/commands/inspect/code-review.mjs.map +0 -1
- package/dist/commands/inspect/container.d.mts +0 -1
- package/dist/commands/inspect/container.mjs +0 -25
- package/dist/commands/inspect/container.mjs.map +0 -1
- package/dist/commands/inspect/context-cost.d.mts +0 -1
- package/dist/commands/inspect/context-cost.mjs +0 -25
- package/dist/commands/inspect/context-cost.mjs.map +0 -1
- package/dist/commands/inspect/design-prep.d.mts +0 -1
- package/dist/commands/inspect/design-prep.mjs +0 -22
- package/dist/commands/inspect/design-prep.mjs.map +0 -1
- package/dist/commands/inspect/error-report.d.mts +0 -1
- package/dist/commands/inspect/error-report.mjs +0 -25
- package/dist/commands/inspect/error-report.mjs.map +0 -1
- package/dist/commands/inspect/error-zip.d.mts +0 -1
- package/dist/commands/inspect/error-zip.mjs +0 -24
- package/dist/commands/inspect/error-zip.mjs.map +0 -1
- package/dist/commands/inspect/log-report.d.mts +0 -1
- package/dist/commands/inspect/log-report.mjs +0 -26
- package/dist/commands/inspect/log-report.mjs.map +0 -1
- package/dist/commands/inspect/model-io.d.mts +0 -1
- package/dist/commands/inspect/model-io.mjs +0 -25
- package/dist/commands/inspect/model-io.mjs.map +0 -1
- package/dist/commands/inspect/profile-show.d.mts +0 -1
- package/dist/commands/inspect/profile-show.mjs +0 -28
- package/dist/commands/inspect/profile-show.mjs.map +0 -1
- package/dist/commands/inspect/recap.d.mts +0 -1
- package/dist/commands/inspect/recap.mjs +0 -30
- package/dist/commands/inspect/recap.mjs.map +0 -1
- package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
- package/dist/commands/inspect/resolve-task-key.mjs +0 -24
- package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
- package/dist/commands/inspect/rollup.d.mts +0 -1
- package/dist/commands/inspect/rollup.mjs +0 -25
- package/dist/commands/inspect/rollup.mjs.map +0 -1
- package/dist/commands/inspect/run-audit.d.mts +0 -1
- package/dist/commands/inspect/run-audit.mjs +0 -25
- package/dist/commands/inspect/run-audit.mjs.map +0 -1
- package/dist/commands/inspect/set-work-status.d.mts +0 -1
- package/dist/commands/inspect/set-work-status.mjs +0 -29
- package/dist/commands/inspect/set-work-status.mjs.map +0 -1
- package/dist/commands/inspect/stage-map.d.mts +0 -1
- package/dist/commands/inspect/stage-map.mjs +0 -131
- package/dist/commands/inspect/stage-map.mjs.map +0 -1
- package/dist/commands/inspect/task-list.d.mts +0 -1
- package/dist/commands/inspect/task-list.mjs +0 -149
- package/dist/commands/inspect/task-list.mjs.map +0 -1
- package/dist/commands/inspect/task-show.d.mts +0 -1
- package/dist/commands/inspect/task-show.mjs +0 -108
- package/dist/commands/inspect/task-show.mjs.map +0 -1
- package/dist/commands/inspect/time-report.d.mts +0 -1
- package/dist/commands/inspect/time-report.mjs +0 -24
- package/dist/commands/inspect/time-report.mjs.map +0 -1
- package/dist/commands/inspect/usage-report.d.mts +0 -1
- package/dist/commands/inspect/usage-report.mjs +0 -23
- package/dist/commands/inspect/usage-report.mjs.map +0 -1
- package/dist/commands/inspect/user-response.d.mts +0 -1
- package/dist/commands/inspect/user-response.mjs +0 -35
- package/dist/commands/inspect/user-response.mjs.map +0 -1
- package/dist/commands/inspect/worker-liveness.d.mts +0 -1
- package/dist/commands/inspect/worker-liveness.mjs +0 -45
- package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
- package/dist/commands/lifecycle/contract-check.d.mts +0 -1
- package/dist/commands/lifecycle/contract-check.mjs +0 -18
- package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
- package/dist/commands/lifecycle/migrate.d.mts +0 -1
- package/dist/commands/lifecycle/migrate.mjs +0 -30
- package/dist/commands/lifecycle/migrate.mjs.map +0 -1
- package/dist/commands/lifecycle/model.d.mts +0 -1
- package/dist/commands/lifecycle/model.mjs +0 -22
- package/dist/commands/lifecycle/model.mjs.map +0 -1
- package/dist/commands/manager.d.mts +0 -3
- package/dist/commands/manager.mjs +0 -50
- package/dist/commands/manager.mjs.map +0 -1
- package/dist/commands/report/agent-activity.d.mts +0 -1
- package/dist/commands/report/agent-activity.mjs +0 -20
- package/dist/commands/report/agent-activity.mjs.map +0 -1
- package/dist/commands/report/approval-decision.d.mts +0 -1
- package/dist/commands/report/approval-decision.mjs +0 -21
- package/dist/commands/report/approval-decision.mjs.map +0 -1
- package/dist/commands/report/design-snapshot.d.mts +0 -1
- package/dist/commands/report/design-snapshot.mjs +0 -19
- package/dist/commands/report/design-snapshot.mjs.map +0 -1
- package/dist/commands/report/finalize.d.mts +0 -4
- package/dist/commands/report/finalize.mjs +0 -64
- package/dist/commands/report/finalize.mjs.map +0 -1
- package/dist/commands/report/inject-report-index.d.mts +0 -1
- package/dist/commands/report/inject-report-index.mjs +0 -21
- package/dist/commands/report/inject-report-index.mjs.map +0 -1
- package/dist/commands/report/render-final-report.d.mts +0 -1
- package/dist/commands/report/render-final-report.mjs +0 -23
- package/dist/commands/report/render-final-report.mjs.map +0 -1
- package/dist/commands/report/render-views.d.mts +0 -1
- package/dist/commands/report/render-views.mjs +0 -27
- package/dist/commands/report/render-views.mjs.map +0 -1
- package/dist/commands/report/translate.d.mts +0 -1
- package/dist/commands/report/translate.mjs +0 -33
- package/dist/commands/report/translate.mjs.map +0 -1
- package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
- package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
- package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
- package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
- package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
- package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
- package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
- package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
- package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
- package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
- package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
- package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
- package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
- package/runtime/bin/okstra-central.sh +0 -152
- package/runtime/bin/okstra-incremental-carry.py +0 -10
- package/runtime/bin/okstra-incremental-scope.py +0 -10
- package/runtime/bin/okstra-team-reconcile.sh +0 -36
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
- package/runtime/python/okstra_ctl/batch.py +0 -60
- package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
- package/runtime/python/okstra_ctl/container_registry.py +0 -72
- package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
- package/runtime/python/okstra_ctl/resolver.py +0 -54
- package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
- package/runtime/python/okstra_ctl/tmux.py +0 -134
- package/runtime/python/okstra_ctl/worktree.py +0 -1099
|
@@ -29,7 +29,7 @@ from pathlib import Path
|
|
|
29
29
|
from typing import Any, Callable, Mapping, Sequence
|
|
30
30
|
|
|
31
31
|
from . import cmux
|
|
32
|
-
from .
|
|
32
|
+
from .agent.invocation import (
|
|
33
33
|
AgentInvocationError,
|
|
34
34
|
AgentModelAssignment,
|
|
35
35
|
InvocationMetadataIdentity,
|
|
@@ -39,7 +39,11 @@ from .agent_invocation import (
|
|
|
39
39
|
verify_agent_invocation,
|
|
40
40
|
)
|
|
41
41
|
from .domain.role import RoleCatalogError, role_for_duty
|
|
42
|
-
from .execution_identity import
|
|
42
|
+
from .execution_identity import ( # noqa: F401 — build_dispatch_id 재노출
|
|
43
|
+
Attempt,
|
|
44
|
+
Invocation,
|
|
45
|
+
build_dispatch_id,
|
|
46
|
+
)
|
|
43
47
|
from .execution_manifest import (
|
|
44
48
|
finish_attempt_mutation,
|
|
45
49
|
read_execution_manifest,
|
|
@@ -52,6 +56,7 @@ from .worker_prompt_body import REPORT_WRITER_WORKER_ID
|
|
|
52
56
|
from .worker_prompt_contract import (
|
|
53
57
|
PromptRecord,
|
|
54
58
|
validate_initial_prompt_records,
|
|
59
|
+
validate_prompt_model_header,
|
|
55
60
|
validate_reverify_prompt,
|
|
56
61
|
)
|
|
57
62
|
from .worker_runner import LIVE, QUIET
|
|
@@ -65,6 +70,7 @@ from .wrapper_status import (
|
|
|
65
70
|
from .write_policy import (
|
|
66
71
|
build_invocation_write_contract,
|
|
67
72
|
planned_paths_from_run_manifest,
|
|
73
|
+
task_root_from_run_manifest,
|
|
68
74
|
write_policy_from_payload,
|
|
69
75
|
)
|
|
70
76
|
from .convergence_store import write_json_atomic
|
|
@@ -188,6 +194,7 @@ class WorkerJob:
|
|
|
188
194
|
execution_label: str = ""
|
|
189
195
|
duty_id: str = ""
|
|
190
196
|
invocation_ref: str = ""
|
|
197
|
+
result_aliases: tuple[Path, ...] = ()
|
|
191
198
|
attempt: int = 0
|
|
192
199
|
|
|
193
200
|
@property
|
|
@@ -352,13 +359,6 @@ def resolve_required_path(
|
|
|
352
359
|
return resolve_project_path(project_root, require_string(manifest, key))
|
|
353
360
|
|
|
354
361
|
|
|
355
|
-
def worker_prompt_path(manifest: Mapping[str, Any], worker_id: str) -> str:
|
|
356
|
-
paths = manifest.get("workerPromptPathByWorkerId")
|
|
357
|
-
if not isinstance(paths, Mapping):
|
|
358
|
-
raise DispatchError("run manifest has no workerPromptPathByWorkerId object")
|
|
359
|
-
return require_string(paths, worker_id)
|
|
360
|
-
|
|
361
|
-
|
|
362
362
|
def worktree_path(
|
|
363
363
|
manifest: Mapping[str, Any], active_context: Mapping[str, Any]
|
|
364
364
|
) -> str:
|
|
@@ -505,6 +505,35 @@ _AGENT_ENFORCEMENT_MODES = frozenset({
|
|
|
505
505
|
})
|
|
506
506
|
|
|
507
507
|
|
|
508
|
+
def _require_prompt_model_matches_assignment(
|
|
509
|
+
metadata_path: Path, assignment: Any
|
|
510
|
+
) -> None:
|
|
511
|
+
"""디스패치 직전, 프롬프트가 이 배정의 모델을 가리키는지 확인한다.
|
|
512
|
+
|
|
513
|
+
메타데이터는 프롬프트 옆에 `<prompt>.meta.json` 으로 놓인다. 읽을 수 없으면
|
|
514
|
+
조용히 통과한다 — 이 검사는 어긋남을 앞당겨 잡는 것이지, 프롬프트를 못 읽는
|
|
515
|
+
사정을 새 차단 사유로 만드는 것이 아니다. 그 사정은 이미 다른 검사가 본다.
|
|
516
|
+
"""
|
|
517
|
+
prompt_path = metadata_path.with_name(
|
|
518
|
+
metadata_path.name.removesuffix(".meta.json")
|
|
519
|
+
)
|
|
520
|
+
if prompt_path == metadata_path or not prompt_path.is_file():
|
|
521
|
+
return
|
|
522
|
+
expected = str(getattr(assignment, "model_execution_value", "") or "").strip()
|
|
523
|
+
if not expected:
|
|
524
|
+
return
|
|
525
|
+
try:
|
|
526
|
+
text = prompt_path.read_text(encoding="utf-8")
|
|
527
|
+
except OSError:
|
|
528
|
+
return
|
|
529
|
+
errors = validate_prompt_model_header(text, expected)
|
|
530
|
+
if errors:
|
|
531
|
+
raise DispatchError(
|
|
532
|
+
f"agent dispatch prompt does not name this assignment's model: "
|
|
533
|
+
+ "; ".join(errors)
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
|
|
508
537
|
def record_verified_agent_dispatch(
|
|
509
538
|
*,
|
|
510
539
|
project_root: Path,
|
|
@@ -577,6 +606,7 @@ def record_verified_agent_dispatch(
|
|
|
577
606
|
)
|
|
578
607
|
if errors:
|
|
579
608
|
raise DispatchError("agent invocation verification failed: " + "; ".join(errors))
|
|
609
|
+
_require_prompt_model_matches_assignment(metadata_path, assignment)
|
|
580
610
|
if (
|
|
581
611
|
enforcement_mode == "host-native-spec-link-gate"
|
|
582
612
|
and assignment.runner != "native-session"
|
|
@@ -691,9 +721,6 @@ def record_verified_agent_dispatch(
|
|
|
691
721
|
dispatch_kind=dispatch_kind,
|
|
692
722
|
round=_execution_dispatch_round(dispatch_kind),
|
|
693
723
|
input_digest=prompt_digest,
|
|
694
|
-
write_policy=write_policy.to_payload(),
|
|
695
|
-
write_policy_digest=write_policy.digest,
|
|
696
|
-
write_enforcement=write_enforcement.to_payload(),
|
|
697
724
|
),
|
|
698
725
|
Attempt(
|
|
699
726
|
invocation_ref=execution_identity.invocation_ref,
|
|
@@ -705,6 +732,9 @@ def record_verified_agent_dispatch(
|
|
|
705
732
|
error_path=None,
|
|
706
733
|
change_summary={},
|
|
707
734
|
evidence_seal_ref=None,
|
|
735
|
+
write_policy=write_policy.to_payload(),
|
|
736
|
+
write_policy_digest=write_policy.digest,
|
|
737
|
+
write_enforcement=write_enforcement.to_payload(),
|
|
708
738
|
),
|
|
709
739
|
task_key=require_string(manifest, "taskKey"),
|
|
710
740
|
)
|
|
@@ -833,6 +863,7 @@ def _agent_write_contract(
|
|
|
833
863
|
project_root=project_root,
|
|
834
864
|
worktree=worktree,
|
|
835
865
|
artifact_paths=artifacts,
|
|
866
|
+
task_root=task_root_from_run_manifest(project_root, authority),
|
|
836
867
|
maximum_precision=maximum_precision,
|
|
837
868
|
planned_paths=planned[0],
|
|
838
869
|
planned_paths_declared=planned[1],
|
|
@@ -1017,6 +1048,9 @@ def link_agent_dispatch_result(
|
|
|
1017
1048
|
f"agent result link requires exactly one dispatch record: {dispatch_id}"
|
|
1018
1049
|
)
|
|
1019
1050
|
dispatch = dispatches[0]
|
|
1051
|
+
_require_recorded_result_path(
|
|
1052
|
+
project_root, dispatch, resolved_result, result_relative
|
|
1053
|
+
)
|
|
1020
1054
|
link: dict[str, Any] = {
|
|
1021
1055
|
"dispatchId": dispatch_id,
|
|
1022
1056
|
"resultPath": result_relative,
|
|
@@ -1081,20 +1115,21 @@ def _finish_native_agent_attempt(
|
|
|
1081
1115
|
if identity is None:
|
|
1082
1116
|
return
|
|
1083
1117
|
execution = read_execution_manifest(manifest_path)
|
|
1084
|
-
|
|
1118
|
+
attempt = next(
|
|
1085
1119
|
(
|
|
1086
|
-
row for row in execution.
|
|
1120
|
+
row for row in execution.attempts
|
|
1087
1121
|
if row.invocation_ref == identity["invocation_ref"]
|
|
1122
|
+
and row.attempt == identity["attempt"]
|
|
1088
1123
|
),
|
|
1089
1124
|
None,
|
|
1090
1125
|
)
|
|
1091
|
-
if
|
|
1092
|
-
raise DispatchError("agent result has no canonical
|
|
1093
|
-
if dispatch.get("writePolicyDigest") !=
|
|
1126
|
+
if attempt is None:
|
|
1127
|
+
raise DispatchError("agent result has no canonical attempt policy")
|
|
1128
|
+
if dispatch.get("writePolicyDigest") != attempt.write_policy_digest:
|
|
1094
1129
|
raise DispatchError("agent dispatch write policy digest changed")
|
|
1095
|
-
if dispatch.get("writeEnforcement") !=
|
|
1130
|
+
if dispatch.get("writeEnforcement") != attempt.write_enforcement:
|
|
1096
1131
|
raise DispatchError("agent dispatch write enforcement changed")
|
|
1097
|
-
policy = write_policy_from_payload(
|
|
1132
|
+
policy = write_policy_from_payload(attempt.write_policy)
|
|
1098
1133
|
status, summary = _native_mutation_result(
|
|
1099
1134
|
project_root=project_root,
|
|
1100
1135
|
dispatch=dispatch,
|
|
@@ -1290,6 +1325,127 @@ def reject_agent_dispatch_result(
|
|
|
1290
1325
|
return dict(row)
|
|
1291
1326
|
|
|
1292
1327
|
|
|
1328
|
+
def _require_recorded_result_path(
|
|
1329
|
+
project_root: Path,
|
|
1330
|
+
dispatch: Mapping[str, Any],
|
|
1331
|
+
resolved_result: Path,
|
|
1332
|
+
result_relative: str,
|
|
1333
|
+
) -> None:
|
|
1334
|
+
"""링크되는 결과가 이 디스패치가 실제로 기다린 산출물인가.
|
|
1335
|
+
|
|
1336
|
+
비교 대상을 여기서 다시 계산하지 않는다. `record-dispatch` 가 이미
|
|
1337
|
+
`completionPaths` 로 그 목록을 적어 두었고(리포트 작성자는 narrative 와
|
|
1338
|
+
포인터 기록 둘), 그것이 이 디스패치가 완료로 인정하는 경로의 정본이다.
|
|
1339
|
+
|
|
1340
|
+
이 대조가 없으면 잘못된 경로를 링크해도 그 자리에서는 통과하고, 조립이
|
|
1341
|
+
`required input is missing` 으로 죽을 때에야 드러난다 — 그때는 워커가 이미
|
|
1342
|
+
돌고 난 뒤다. 실측(2026-08-26): 리드가 지어낸 seq(`-002.md`)를 넘겼고
|
|
1343
|
+
매니페스트의 선언은 `-004.md` 였다. 여기서 막으면 워커를 다시 돌릴 필요 없이
|
|
1344
|
+
같은 명령을 옳은 인자로 다시 부르면 된다.
|
|
1345
|
+
|
|
1346
|
+
`completionPaths` 를 기록하지 않은 행은 판정하지 않는다 — 목록이 없으면
|
|
1347
|
+
무엇이 정당한지 알 수 없고, 모르면서 막는 것은 추측이다.
|
|
1348
|
+
"""
|
|
1349
|
+
recorded = dispatch.get("completionPaths")
|
|
1350
|
+
if not isinstance(recorded, list) or not recorded:
|
|
1351
|
+
return
|
|
1352
|
+
allowed = {
|
|
1353
|
+
resolve_project_path(project_root, str(value)).resolve()
|
|
1354
|
+
for value in recorded
|
|
1355
|
+
if isinstance(value, str) and value.strip()
|
|
1356
|
+
}
|
|
1357
|
+
if not allowed or resolved_result in allowed:
|
|
1358
|
+
return
|
|
1359
|
+
expected = ", ".join(sorted(_relative_project_path(project_root, path) for path in allowed))
|
|
1360
|
+
raise DispatchError(
|
|
1361
|
+
f"agent result link is not one of this dispatch's completion paths: "
|
|
1362
|
+
f"linked {result_relative}, expected one of {expected}"
|
|
1363
|
+
)
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
def abandon_agent_dispatch_attempt(
|
|
1367
|
+
*,
|
|
1368
|
+
project_root: Path,
|
|
1369
|
+
run_manifest_path: Path,
|
|
1370
|
+
invocation_ref: str,
|
|
1371
|
+
reason: str,
|
|
1372
|
+
) -> dict[str, Any]:
|
|
1373
|
+
"""Close a started attempt whose worker died without producing a result.
|
|
1374
|
+
|
|
1375
|
+
Every other path that closes an attempt needs something the dead worker
|
|
1376
|
+
never made: `link-result` needs its result file, and
|
|
1377
|
+
`_abandon_unstarted_attempt` only runs when the dispatch itself failed
|
|
1378
|
+
before the process existed. A worker that started and then died — its host
|
|
1379
|
+
killed, its pane closed, an import error before any log — leaves the attempt
|
|
1380
|
+
`started` forever, and `_validate_next_attempt` then refuses every retry with
|
|
1381
|
+
`previous attempt is not terminal`. The run has to be re-rendered from
|
|
1382
|
+
scratch to make progress.
|
|
1383
|
+
|
|
1384
|
+
**Only a `source-readonly` attempt may be abandoned.** The manifest accepts a
|
|
1385
|
+
following attempt only after `failed-no-mutation`, so closing an attempt is
|
|
1386
|
+
also an assertion that nothing was written. For a read-only worker that
|
|
1387
|
+
assertion is true by policy — it never held permission to write — so no
|
|
1388
|
+
guess is involved. For a `project-mutation` attempt it would be a guess, and
|
|
1389
|
+
a wrong one lets the retry run on top of a half-mutated tree, which is the
|
|
1390
|
+
invariant this manifest exists to protect. That case is refused here and
|
|
1391
|
+
belongs to the mutation audit.
|
|
1392
|
+
"""
|
|
1393
|
+
if not reason.strip():
|
|
1394
|
+
raise DispatchError(
|
|
1395
|
+
"an abandonment reason is required — the ledger has to say why an "
|
|
1396
|
+
"attempt was closed without a result"
|
|
1397
|
+
)
|
|
1398
|
+
project_root = project_root.resolve()
|
|
1399
|
+
manifest_path = resolve_project_path(
|
|
1400
|
+
project_root, str(run_manifest_path)
|
|
1401
|
+
).resolve(strict=True)
|
|
1402
|
+
manifest = load_json_object(manifest_path, "run manifest")
|
|
1403
|
+
execution = read_execution_manifest(manifest_path)
|
|
1404
|
+
attempts = [
|
|
1405
|
+
row for row in execution.attempts if row.invocation_ref == invocation_ref
|
|
1406
|
+
]
|
|
1407
|
+
if not attempts:
|
|
1408
|
+
raise DispatchError(f"no attempt for invocation {invocation_ref}")
|
|
1409
|
+
row = max(attempts, key=lambda item: item.attempt)
|
|
1410
|
+
if row.finished_at is not None:
|
|
1411
|
+
raise DispatchError(
|
|
1412
|
+
f"attempt {invocation_ref} #{row.attempt} is already terminal "
|
|
1413
|
+
f"({row.status})"
|
|
1414
|
+
)
|
|
1415
|
+
mode = _attempt_source_mode(row)
|
|
1416
|
+
if mode != "source-readonly":
|
|
1417
|
+
raise DispatchError(
|
|
1418
|
+
f"attempt {invocation_ref} #{row.attempt} runs under sourcePolicy "
|
|
1419
|
+
f"`{mode}` — only a source-readonly attempt may be abandoned, "
|
|
1420
|
+
"because closing one asserts that nothing was written. Resolve a "
|
|
1421
|
+
"mutating attempt through its mutation audit."
|
|
1422
|
+
)
|
|
1423
|
+
finish_attempt_mutation(
|
|
1424
|
+
manifest_path,
|
|
1425
|
+
invocation_ref=invocation_ref,
|
|
1426
|
+
attempt=row.attempt,
|
|
1427
|
+
finished_at=utc_now(),
|
|
1428
|
+
status="failed-no-mutation",
|
|
1429
|
+
result_path=None,
|
|
1430
|
+
error_path=None,
|
|
1431
|
+
change_summary={"abandonedReason": reason.strip()},
|
|
1432
|
+
task_key=require_string(manifest, "taskKey"),
|
|
1433
|
+
)
|
|
1434
|
+
return {
|
|
1435
|
+
"invocationRef": invocation_ref,
|
|
1436
|
+
"attempt": row.attempt,
|
|
1437
|
+
"status": "failed-no-mutation",
|
|
1438
|
+
"reason": reason.strip(),
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
|
|
1442
|
+
def _attempt_source_mode(row: Any) -> str:
|
|
1443
|
+
policy = getattr(row, "write_policy", None)
|
|
1444
|
+
source = policy.get("sourcePolicy") if isinstance(policy, Mapping) else None
|
|
1445
|
+
mode = source.get("mode") if isinstance(source, Mapping) else None
|
|
1446
|
+
return str(mode or "unknown")
|
|
1447
|
+
|
|
1448
|
+
|
|
1293
1449
|
def _relative_project_path(project_root: Path, path: Path) -> str:
|
|
1294
1450
|
try:
|
|
1295
1451
|
return path.resolve(strict=False).relative_to(project_root).as_posix()
|
|
@@ -1307,6 +1463,55 @@ def worker_state(team_state: Mapping[str, Any], worker_id: str) -> Mapping[str,
|
|
|
1307
1463
|
raise DispatchError(f"team-state has no workerId={worker_id}")
|
|
1308
1464
|
|
|
1309
1465
|
|
|
1466
|
+
def _task_project_root(team_state_path: Path) -> Path | None:
|
|
1467
|
+
"""team-state 경로에서 프로젝트 루트를 되짚는다 (`.okstra` 를 기준점으로).
|
|
1468
|
+
|
|
1469
|
+
stage run 은 `runs/<phase>/stage-N/state/` 로 한 층이 더 깊으므로 부모를
|
|
1470
|
+
세는 방식은 빗나간다. 기준점을 못 찾으면 None — 예상 밖 배치에서 판정을
|
|
1471
|
+
포기하는 쪽이, 실행을 통째로 막는 쪽보다 낫다. 최종 검증이 같은 모순을
|
|
1472
|
+
다시 잡는다.
|
|
1473
|
+
"""
|
|
1474
|
+
for parent in team_state_path.resolve().parents:
|
|
1475
|
+
if parent.name == ".okstra":
|
|
1476
|
+
return parent.parent
|
|
1477
|
+
return None
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
def _reject_completed_without_result(
|
|
1481
|
+
team_state_path: Path, worker_id: str, worker: Mapping[str, Any]
|
|
1482
|
+
) -> None:
|
|
1483
|
+
"""산출물이 없는 역할을 완료로 적지 못하게 한다.
|
|
1484
|
+
|
|
1485
|
+
okstra 는 디스패치 결과를 스스로 기록한다. 그 원장이 두 시도 모두 `error`
|
|
1486
|
+
라고 적은 역할이 명부에는 `completed` 로 남을 수 있었다 — 명부 상태가
|
|
1487
|
+
원장에서 파생되지 않기 때문이다. 모순은 phase 끝 `validate-run` 이
|
|
1488
|
+
"completed but worker result file is missing" 으로 잡지만, 그때는 워커를
|
|
1489
|
+
다시 돌릴 수 없어 실행 전체가 막힌다.
|
|
1490
|
+
|
|
1491
|
+
조인 대신 명부 자신의 `resultPath` 를 본다. 그 값이 없는 역할(critic scope
|
|
1492
|
+
등)은 판정 대상이 아니다.
|
|
1493
|
+
|
|
1494
|
+
실측(2026-08-27, `fontsninja-v3-site` `dev-10341`): 명부 5건 중 `resultPath`
|
|
1495
|
+
를 가진 4건에서 3건은 파일이 있었고, grok-planner 만 없었다.
|
|
1496
|
+
"""
|
|
1497
|
+
relative = str(worker.get("resultPath") or "").strip()
|
|
1498
|
+
if not relative:
|
|
1499
|
+
return
|
|
1500
|
+
candidate = Path(relative)
|
|
1501
|
+
if not candidate.is_absolute():
|
|
1502
|
+
project_root = _task_project_root(team_state_path)
|
|
1503
|
+
if project_root is None:
|
|
1504
|
+
return
|
|
1505
|
+
candidate = project_root / candidate
|
|
1506
|
+
if candidate.is_file():
|
|
1507
|
+
return
|
|
1508
|
+
raise DispatchError(
|
|
1509
|
+
f"worker {worker_id} cannot be recorded completed: its result "
|
|
1510
|
+
f"{relative} does not exist. Record the terminal status the dispatch "
|
|
1511
|
+
"actually reached (`error` / `timeout` / `not-run`) with a reason."
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
|
|
1310
1515
|
def transition_worker_status(
|
|
1311
1516
|
team_state_path: Path,
|
|
1312
1517
|
worker_id: str,
|
|
@@ -1332,6 +1537,10 @@ def transition_worker_status(
|
|
|
1332
1537
|
raise DispatchError(f"team-state workers must be an array: {team_state_path}")
|
|
1333
1538
|
for worker in workers:
|
|
1334
1539
|
if isinstance(worker, dict) and worker.get("workerId") == worker_id:
|
|
1540
|
+
if status == "completed":
|
|
1541
|
+
_reject_completed_without_result(
|
|
1542
|
+
team_state_path, worker_id, worker
|
|
1543
|
+
)
|
|
1335
1544
|
worker["status"] = status
|
|
1336
1545
|
worker["reason"] = reason
|
|
1337
1546
|
if status == "in-progress":
|
|
@@ -1400,25 +1609,6 @@ def _has_recorded_team_create(team_state: Mapping[str, Any]) -> bool:
|
|
|
1400
1609
|
|
|
1401
1610
|
# --- job facts ----------------------------------------------------------------
|
|
1402
1611
|
|
|
1403
|
-
def build_dispatch_id(invocation_id: str, attempt: int) -> str:
|
|
1404
|
-
"""The only place Python assembles a dispatch id.
|
|
1405
|
-
|
|
1406
|
-
Two generators drifted: this module pinned the attempt to 1 while
|
|
1407
|
-
`dispatch_core` used the real one, so a re-dispatch took the first attempt's
|
|
1408
|
-
id, team-state kept the same row twice, and `validate-run` failed that run
|
|
1409
|
-
with `agent dispatch ID is duplicated`.
|
|
1410
|
-
|
|
1411
|
-
Prose mirrors of this shape live outside Python, where no guard reaches
|
|
1412
|
-
them: `prompts/lead/convergence.md`, `prompts/lead/report-writer.md`,
|
|
1413
|
-
`prompts/lead/plan-body-verification.md` and the five
|
|
1414
|
-
`adapters/hosts/*/relay.md` each spell an `--dispatch-id` argument out for
|
|
1415
|
-
the lead. They agree with this function only because
|
|
1416
|
-
`record_verified_agent_dispatch` records the first attempt and no other;
|
|
1417
|
-
changing that convention has to change them too.
|
|
1418
|
-
"""
|
|
1419
|
-
return f"{invocation_id}:attempt-{attempt}"
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
1612
|
def dispatch_mode(jobs: Sequence[WorkerJob]) -> str:
|
|
1423
1613
|
backends = {job.backend for job in jobs}
|
|
1424
1614
|
if len(backends) == 1:
|
|
@@ -1427,7 +1617,18 @@ def dispatch_mode(jobs: Sequence[WorkerJob]) -> str:
|
|
|
1427
1617
|
|
|
1428
1618
|
|
|
1429
1619
|
def missing_completion_paths(job: WorkerJob) -> tuple[Path, ...]:
|
|
1430
|
-
|
|
1620
|
+
missing: list[Path] = []
|
|
1621
|
+
for path in job.completion_paths:
|
|
1622
|
+
if path.is_file():
|
|
1623
|
+
continue
|
|
1624
|
+
# reports seq 와 workerResults seq 가 갈라지면 워커는 다른 쪽
|
|
1625
|
+
# 파일명으로 쓴다. 둘 중 하나가 있으면 산출물은 있는 것이다.
|
|
1626
|
+
if path in {job.result_path, job.worker_result_path} and any(
|
|
1627
|
+
alias.is_file() for alias in job.result_aliases
|
|
1628
|
+
):
|
|
1629
|
+
continue
|
|
1630
|
+
missing.append(path)
|
|
1631
|
+
return tuple(missing)
|
|
1431
1632
|
|
|
1432
1633
|
|
|
1433
1634
|
def dispatch_session_id(provider: str) -> str:
|
|
@@ -1534,6 +1735,32 @@ def dispatch_completion_paths(
|
|
|
1534
1735
|
return (data_json, worker_result_path)
|
|
1535
1736
|
|
|
1536
1737
|
|
|
1738
|
+
def _plan_verify_result_aliases(
|
|
1739
|
+
worker_result_path: Path, manifest: Mapping[str, Any],
|
|
1740
|
+
) -> tuple[Path, ...]:
|
|
1741
|
+
"""reports seq 파일의 workerResults seq 짝.
|
|
1742
|
+
|
|
1743
|
+
plan-verify 결과 파일명만 해당한다. 다른 산출물의 seq 를 바꾸면 안 된다.
|
|
1744
|
+
"""
|
|
1745
|
+
if "-plan-verify-r" not in worker_result_path.name:
|
|
1746
|
+
return ()
|
|
1747
|
+
sequences = manifest.get("runSequencesByCategory")
|
|
1748
|
+
if not isinstance(sequences, Mapping):
|
|
1749
|
+
return ()
|
|
1750
|
+
reports = str(sequences.get("reports") or "").strip()
|
|
1751
|
+
workers = str(sequences.get("workerResults") or "").strip()
|
|
1752
|
+
if not reports or not workers or reports == workers:
|
|
1753
|
+
return ()
|
|
1754
|
+
name = worker_result_path.name
|
|
1755
|
+
if name.endswith(f"-{reports}.md"):
|
|
1756
|
+
other = name[: -len(f"-{reports}.md")] + f"-{workers}.md"
|
|
1757
|
+
elif name.endswith(f"-{workers}.md"):
|
|
1758
|
+
other = name[: -len(f"-{workers}.md")] + f"-{reports}.md"
|
|
1759
|
+
else:
|
|
1760
|
+
return ()
|
|
1761
|
+
return (worker_result_path.with_name(other),)
|
|
1762
|
+
|
|
1763
|
+
|
|
1537
1764
|
def validate_initial_prompts(
|
|
1538
1765
|
manifest: Mapping[str, Any], jobs: Sequence[WorkerJob]
|
|
1539
1766
|
) -> None:
|
|
@@ -1937,6 +2164,7 @@ def _worker_job_from_file(
|
|
|
1937
2164
|
completion_paths = dispatch_completion_paths(
|
|
1938
2165
|
worker_id, worker_result_path, manifest, project_root
|
|
1939
2166
|
)
|
|
2167
|
+
result_aliases = _plan_verify_result_aliases(worker_result_path, manifest)
|
|
1940
2168
|
digests = item.get("digests")
|
|
1941
2169
|
digest_values = digests if isinstance(digests, Mapping) else {}
|
|
1942
2170
|
host_model_value = item.get("hostModelValue")
|
|
@@ -1985,6 +2213,7 @@ def _worker_job_from_file(
|
|
|
1985
2213
|
prompt_digest=string_value(digest_values.get("promptDigest")),
|
|
1986
2214
|
host_model_value=host_model_value,
|
|
1987
2215
|
enforcement_mode=string_value(item.get("enforcementMode")),
|
|
2216
|
+
result_aliases=result_aliases,
|
|
1988
2217
|
**(identity or {}),
|
|
1989
2218
|
)
|
|
1990
2219
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""`okstra doctor` 가 부르는 진단 프로젝션.
|
|
2
|
+
|
|
3
|
+
진단 로직 자체는 `okstra_ctl.doctor` 에 있다. 이 모듈은 그것을 CLI 경계로
|
|
4
|
+
노출하기만 한다 — 원래 `src/commands/lifecycle/doctor.mts` 의 문자열 리터럴
|
|
5
|
+
안에 있던 두 스니펫을 옮긴 것이다(docs/coding-rules.md R1 위반).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
from .doctor import model_pool_diagnostics, phase_diagnostics
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(argv: list[str] | None = None) -> int:
|
|
16
|
+
parser = argparse.ArgumentParser(prog="okstra doctor")
|
|
17
|
+
sub = parser.add_subparsers(dest="op", required=True)
|
|
18
|
+
|
|
19
|
+
pool = sub.add_parser("model-pool")
|
|
20
|
+
pool.add_argument("--host-runtime", default="claude-code")
|
|
21
|
+
pool.add_argument("--cwd", required=True)
|
|
22
|
+
|
|
23
|
+
phase = sub.add_parser("phase")
|
|
24
|
+
phase.add_argument("--phase", required=True)
|
|
25
|
+
phase.add_argument("--cwd", required=True)
|
|
26
|
+
phase.add_argument("--workspace-root", required=True)
|
|
27
|
+
# `--home` 은 사용자 홈이다. 에이전트 설치 루트가 `<home>/.claude/agents` 라서
|
|
28
|
+
# okstra 홈(~/.okstra)을 넘기면 모든 worker-agent 점검이 없는 경로를 짚는다.
|
|
29
|
+
phase.add_argument("--home", required=True)
|
|
30
|
+
phase.add_argument("--host-runtime", default="")
|
|
31
|
+
|
|
32
|
+
args = parser.parse_args(argv)
|
|
33
|
+
if args.op == "model-pool":
|
|
34
|
+
payload = model_pool_diagnostics(host_runtime=args.host_runtime, cwd=args.cwd)
|
|
35
|
+
else:
|
|
36
|
+
payload = phase_diagnostics(
|
|
37
|
+
args.phase,
|
|
38
|
+
cwd=args.cwd,
|
|
39
|
+
workspace_root=args.workspace_root,
|
|
40
|
+
home=args.home,
|
|
41
|
+
host_runtime=args.host_runtime,
|
|
42
|
+
)
|
|
43
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
raise SystemExit(main())
|
|
@@ -12,9 +12,12 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Literal, Protocol, runtime_checkable
|
|
13
13
|
|
|
14
14
|
from .worker_presentation import Presentation
|
|
15
|
-
from
|
|
15
|
+
from .write_policy import WriteEnforcement, WritePolicy
|
|
16
16
|
|
|
17
17
|
SERVED_MODEL_MISMATCH_EXIT_CODE = 78
|
|
18
|
+
# JSON 이벤트 스트림이 result 이벤트 없이 깨끗하게(exit 0) 끝난 run.
|
|
19
|
+
# CLI 가 성공을 주장했지만 답을 내지 않았다 — sysexits 의 EX_PROTOCOL.
|
|
20
|
+
NO_RESULT_EXIT_CODE = 76
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
@dataclass(frozen=True)
|
|
@@ -22,10 +25,12 @@ class ExecutionPolicy:
|
|
|
22
25
|
"""How a non-interactive worker is allowed to act.
|
|
23
26
|
|
|
24
27
|
Identical for every worker: nobody is at the keyboard to answer an approval
|
|
25
|
-
prompt, so the gate has to be open
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
prompt, so the gate has to be open. No provider CLI enforces a sandbox
|
|
29
|
+
boundary any more, so `write_scope` is what the worker is told to write, not
|
|
30
|
+
what it is prevented from leaving — `write_policy` and `write_enforcement`
|
|
31
|
+
are what the run checks after the fact. Declared once here rather than per
|
|
32
|
+
provider, because a policy that lives in one adapter file per provider
|
|
33
|
+
drifts into one policy per provider.
|
|
29
34
|
"""
|
|
30
35
|
|
|
31
36
|
auto_approve: bool
|
|
@@ -117,11 +117,24 @@ class JsonEvents:
|
|
|
117
117
|
# 않으면 pane 이 한 글자 한 줄이 된다. 기본은 끄고, 그런 CLI 의 어댑터가 켠다 —
|
|
118
118
|
# 어느 CLI 가 그런지는 이 계층이 알 필요도, 알아서도 안 되는 사실이다.
|
|
119
119
|
join_adjacent_text: bool = False
|
|
120
|
+
# 이 어휘에서 "정상 종결" 을 뜻하는 원시 이벤트. 기본은 Result 이벤트지만,
|
|
121
|
+
# 어떤 CLI 의 어휘에는 Result 가 없고 별도의 종결 이벤트가 있다 — 어댑터가
|
|
122
|
+
# 자기 어휘의 표지를 넘긴다. 어느 CLI 가 그런지는 이 계층이 알 필요도,
|
|
123
|
+
# 알아서도 안 되는 사실이다. 판정 없이는 그런 CLI 의 건강한 run 전부가
|
|
124
|
+
# 결과 부재로 강등된다.
|
|
125
|
+
is_conclusion: Callable[[Mapping[str, Any]], bool] | None = None
|
|
120
126
|
_pending_text: list[str] = field(default_factory=list, repr=False, compare=False)
|
|
127
|
+
_saw_conclusion: list[bool] = field(
|
|
128
|
+
default_factory=list, repr=False, compare=False
|
|
129
|
+
)
|
|
121
130
|
|
|
122
131
|
def merges_stderr(self) -> bool:
|
|
123
132
|
return True
|
|
124
133
|
|
|
134
|
+
def concluded(self) -> bool:
|
|
135
|
+
"""스트림이 이 어휘의 종결 표지로 끝났는가."""
|
|
136
|
+
return bool(self._saw_conclusion)
|
|
137
|
+
|
|
125
138
|
def sinks(self, writer: TranscriptWriter) -> tuple[SinkSpec, ...]:
|
|
126
139
|
def emit(line: str) -> str | None:
|
|
127
140
|
stripped = line.strip()
|
|
@@ -138,6 +151,8 @@ class JsonEvents:
|
|
|
138
151
|
if not isinstance(event, dict):
|
|
139
152
|
return None
|
|
140
153
|
self.observe(event)
|
|
154
|
+
if self.is_conclusion is not None and self.is_conclusion(event):
|
|
155
|
+
self._saw_conclusion.append(True)
|
|
141
156
|
closing: str | None = None
|
|
142
157
|
for entry in self.normalise(event):
|
|
143
158
|
closing = self._write_entry(writer, entry) or closing
|
|
@@ -164,7 +179,12 @@ class JsonEvents:
|
|
|
164
179
|
screen=format_live(entry),
|
|
165
180
|
archive=format_log(entry),
|
|
166
181
|
)
|
|
167
|
-
|
|
182
|
+
closing = final_text(entry)
|
|
183
|
+
if closing is not None:
|
|
184
|
+
# Result 이벤트는 어휘와 무관한 종결이다. 빈 결과 텍스트도 종결이므로
|
|
185
|
+
# closing_text 의 진릿값과 따로 기록한다.
|
|
186
|
+
self._saw_conclusion.append(True)
|
|
187
|
+
return closing
|
|
168
188
|
|
|
169
189
|
def _publish_complete_lines(self, writer: TranscriptWriter) -> None:
|
|
170
190
|
combined = "".join(self._pending_text)
|