okstra 0.199.4 → 0.200.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 +4 -1
- package/dist/lib/runtime-payload.mjs +1 -0
- package/dist/lib/runtime-payload.mjs.map +1 -1
- package/docs/cli.md +10 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-provider-exec.py +1 -1
- package/runtime/bin/okstra-zai-exec.sh +5 -0
- package/runtime/prompts/launch.template.md +12 -0
- package/runtime/prompts/lead/okstra-lead-contract.md +3 -1
- package/runtime/prompts/lead/plan-body-verification.md +16 -4
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +1 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +18 -4
- package/runtime/python/okstra_ctl/adapters/providers/zai/adapter.py +96 -0
- package/runtime/python/okstra_ctl/adapters/providers/zai/manifest.json +5 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +6 -3
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +23 -1
- package/runtime/python/okstra_ctl/consumers.py +0 -55
- package/runtime/python/okstra_ctl/domain/worker_exec.py +5 -2
- package/runtime/python/okstra_ctl/domain/write_policy.py +7 -1
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -17
- package/runtime/python/okstra_ctl/final_report_schema.py +16 -16
- package/runtime/python/okstra_ctl/plan_items_cli.py +6 -0
- package/runtime/python/okstra_ctl/report_assembly.py +4 -1
- package/runtime/python/okstra_ctl/report_corrections.py +24 -15
- package/runtime/python/okstra_ctl/report_finalize.py +1 -1
- package/runtime/python/okstra_ctl/report_narrative.py +11 -6
- package/runtime/python/okstra_ctl/report_projections.py +1 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -1
- package/runtime/python/okstra_ctl/wizard/confirmation.py +4 -0
- package/runtime/python/okstra_ctl/wizard/engine.py +4 -16
- package/runtime/python/okstra_ctl/wizard/picker_navigation.py +7 -37
- package/runtime/python/okstra_ctl/worker_runner.py +7 -2
- package/runtime/python/okstra_ctl/worktree_status_cli.py +2 -3
- package/runtime/python/okstra_ctl/write_policy.py +51 -4
- package/runtime/python/okstra_token_usage/claude.py +29 -0
- package/runtime/python/okstra_token_usage/collect.py +11 -1
- package/runtime/schemas/execution-manifest-v2.schema.json +1 -0
- package/runtime/schemas/final-report-v2.0.schema.json +3 -2
- package/runtime/schemas/final-report-v3.0.schema.json +3 -2
- package/runtime/skills/okstra-run/SKILL.md +9 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ Role assignments are persisted separately from the host runtime, but the lead pr
|
|
|
37
37
|
| Antigravity | Antigravity CLI | `okstra-antigravity-exec.sh` | leader, analyser, critic, implementer, verifier |
|
|
38
38
|
| Grok | Grok CLI | `okstra-grok-exec.sh` | leader, read-only analyser, and critic |
|
|
39
39
|
| Kimi | Kimi CLI | `okstra-kimi-exec.sh` | leader, read-only analyser, and critic |
|
|
40
|
+
| Z.ai GLM | — | `okstra-zai-exec.sh` (Claude Code) | analyser, critic, designer, planner, implementer, verifier, report-writer, translator |
|
|
40
41
|
|
|
41
42
|
Canonical role names are `leader`, `analyser`, `critic`, `designer`, `planner`, `implementer`, `verifier`, `report-writer`, and `translator`. `lead` is a compatibility alias for `leader`. `executor` is a compatibility alias for `implementer`. New records write only the canonical names.
|
|
42
43
|
|
|
@@ -46,7 +47,9 @@ Grok and Kimi can now lead through their registered host adapters. Their worker
|
|
|
46
47
|
|
|
47
48
|
`okstra run <host-id-or-alias>` resolves the requested adapter from the host registry and checks its `spawn-process` readiness before starting it. The installed `okstra-run` skill instead uses `current-session`: it declares only the semantic functions the live harness can actually perform (`plain_text_input`, plus any available native single-select, multi-select, or grouped-question function) and reuses the current lead session. User-installed adapters are discovered only from `~/.okstra/adapters/hosts/<id>/` and `~/.okstra/adapters/providers/<id>/`; project-local executable adapter code is ignored.
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
Z.ai GLM workers reuse the installed Claude Code executable with process-local Z.ai connection settings. Set `ZAI_API_KEY` in the environment that launches Okstra, then select `zai/glm-5.3` or `zai/glm-5.3-flash` for a worker role. Claude configuration files remain unchanged. See [GLM worker setup](docs/cli.md#zai-glm-workers) for isolation and accounting details.
|
|
51
|
+
|
|
52
|
+
The next direct-CLI candidates are Mistral Vibe and Qwen Code. DeepSeek V4 and MiniMax M2.7 remain API-adapter candidates because their current official coding-agent execution surfaces do not fit Okstra's wrapper contract as directly. Model catalog entries and account availability are distinct; GLM authorization is checked when its CLI process calls Z.ai.
|
|
50
53
|
|
|
51
54
|
okstra is **not** a one-shot code review tool. It is for work that **spans multiple phases, needs input from multiple agents, and feeds each phase's output into the next phase**.
|
|
52
55
|
|
|
@@ -15,6 +15,7 @@ export const RUNTIME_BIN_FILES = Object.freeze([
|
|
|
15
15
|
{ name: "okstra-antigravity-exec.sh" },
|
|
16
16
|
{ name: "okstra-grok-exec.sh" },
|
|
17
17
|
{ name: "okstra-kimi-exec.sh" },
|
|
18
|
+
{ name: "okstra-zai-exec.sh" },
|
|
18
19
|
{ name: "okstra-provider-exec.py" },
|
|
19
20
|
// 진입 스크립트들이 import 하는 경로 부트스트랩. 이들과 같은
|
|
20
21
|
// 디렉터리에 있어야 `sys.path` 를 고치기 전에 import 된다.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-payload.mjs","sourceRoot":"","sources":["../../src/lib/runtime-payload.mts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,oCAAoC;AACpC,0DAA0D;AAC1D,iEAAiE;AACjE,gEAAgE;AAChE,kDAAkD;AAClD,EAAE;AACF,kCAAkC;AAalC,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAA8B,MAAM,CAAC,MAAM,CAAC;IACxE,EAAE,IAAI,EAAE,WAAW,EAAE;IACrB,EAAE,IAAI,EAAE,sBAAsB,EAAE;IAChC,EAAE,IAAI,EAAE,uBAAuB,EAAE;IACjC,EAAE,IAAI,EAAE,4BAA4B,EAAE;IACtC,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAC/B,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAC/B,EAAE,IAAI,EAAE,yBAAyB,EAAE;IACnC,uCAAuC;IACvC,2CAA2C;IAC3C,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAC/B,EAAE,IAAI,EAAE,wBAAwB,EAAE;IAClC,EAAE,IAAI,EAAE,4BAA4B,EAAE;IACtC,EAAE,IAAI,EAAE,0BAA0B,EAAE;IACpC,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC7C,EAAE,IAAI,EAAE,+BAA+B,EAAE,MAAM,EAAE,IAAI,EAAE;IACvD,EAAE,IAAI,EAAE,+BAA+B,EAAE,MAAM,EAAE,IAAI,EAAE;IACvD,EAAE,IAAI,EAAE,+BAA+B,EAAE,MAAM,EAAE,IAAI,EAAE;IACvD,EAAE,IAAI,EAAE,4BAA4B,EAAE,MAAM,EAAE,IAAI,EAAE;IACpD,EAAE,IAAI,EAAE,2BAA2B,EAAE,MAAM,EAAE,IAAI,EAAE;IACnD,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,IAAI,EAAE;CAChD,CAAC,CAAC;AAEH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,uBAAuB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACtE,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,eAAe;CAChB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA2C,MAAM,CAAC,MAAM,CAAC;IACrF,CAAC,oBAAoB,EAAE,gBAAgB,CAAC;IACxC,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,WAAW,EAAE,WAAW,CAAC;IAC1B,CAAC,YAAY,EAAE,YAAY,CAAC;IAC5B,CAAC,QAAQ,EAAE,QAAQ,CAAC;CACZ,CAAC,CAAC;AAEZ,uDAAuD;AACvD,MAAM,CAAC,MAAM,oBAAoB,GAAsB,MAAM,CAAC,MAAM,CAClE,iBAAiB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAC7C,CAAC;AAEF,uDAAuD;AACvD,MAAM,CAAC,MAAM,mBAAmB,GAAsB,MAAM,CAAC,MAAM,CACjE,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAC7E,CAAC"}
|
|
1
|
+
{"version":3,"file":"runtime-payload.mjs","sourceRoot":"","sources":["../../src/lib/runtime-payload.mts"],"names":[],"mappings":"AAAA,uBAAuB;AACvB,EAAE;AACF,oCAAoC;AACpC,0DAA0D;AAC1D,iEAAiE;AACjE,gEAAgE;AAChE,kDAAkD;AAClD,EAAE;AACF,kCAAkC;AAalC,yDAAyD;AACzD,MAAM,CAAC,MAAM,iBAAiB,GAA8B,MAAM,CAAC,MAAM,CAAC;IACxE,EAAE,IAAI,EAAE,WAAW,EAAE;IACrB,EAAE,IAAI,EAAE,sBAAsB,EAAE;IAChC,EAAE,IAAI,EAAE,uBAAuB,EAAE;IACjC,EAAE,IAAI,EAAE,4BAA4B,EAAE;IACtC,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAC/B,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAC/B,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAC9B,EAAE,IAAI,EAAE,yBAAyB,EAAE;IACnC,uCAAuC;IACvC,2CAA2C;IAC3C,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAC/B,EAAE,IAAI,EAAE,wBAAwB,EAAE;IAClC,EAAE,IAAI,EAAE,4BAA4B,EAAE;IACtC,EAAE,IAAI,EAAE,0BAA0B,EAAE;IACpC,EAAE,IAAI,EAAE,qBAAqB,EAAE,MAAM,EAAE,IAAI,EAAE;IAC7C,EAAE,IAAI,EAAE,+BAA+B,EAAE,MAAM,EAAE,IAAI,EAAE;IACvD,EAAE,IAAI,EAAE,+BAA+B,EAAE,MAAM,EAAE,IAAI,EAAE;IACvD,EAAE,IAAI,EAAE,+BAA+B,EAAE,MAAM,EAAE,IAAI,EAAE;IACvD,EAAE,IAAI,EAAE,4BAA4B,EAAE,MAAM,EAAE,IAAI,EAAE;IACpD,EAAE,IAAI,EAAE,2BAA2B,EAAE,MAAM,EAAE,IAAI,EAAE;IACnD,EAAE,IAAI,EAAE,uBAAuB,EAAE,MAAM,EAAE,IAAI,EAAE;CAChD,CAAC,CAAC;AAEH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,uBAAuB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACtE,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,eAAe;CAChB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA2C,MAAM,CAAC,MAAM,CAAC;IACrF,CAAC,oBAAoB,EAAE,gBAAgB,CAAC;IACxC,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACpB,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,SAAS,EAAE,SAAS,CAAC;IACtB,CAAC,WAAW,EAAE,WAAW,CAAC;IAC1B,CAAC,YAAY,EAAE,YAAY,CAAC;IAC5B,CAAC,QAAQ,EAAE,QAAQ,CAAC;CACZ,CAAC,CAAC;AAEZ,uDAAuD;AACvD,MAAM,CAAC,MAAM,oBAAoB,GAAsB,MAAM,CAAC,MAAM,CAClE,iBAAiB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAC7C,CAAC;AAEF,uDAAuD;AACvD,MAAM,CAAC,MAAM,mBAAmB,GAAsB,MAAM,CAAC,MAAM,CACjE,iBAAiB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAC7E,CAAC"}
|
package/docs/cli.md
CHANGED
|
@@ -781,6 +781,16 @@ It then displays `Continue? [y/yes]:`. Any response other than `y` or `yes` exit
|
|
|
781
781
|
|
|
782
782
|
---
|
|
783
783
|
|
|
784
|
+
## Z.ai GLM workers
|
|
785
|
+
|
|
786
|
+
Z.ai is a worker provider (`zai`) that uses the existing `claude` executable. Install Claude Code, provide `ZAI_API_KEY` in the environment inherited by Okstra, and select `zai/glm-5.3` or `zai/glm-5.3-flash` in a role-model step. For explicit launch arguments, use `--role-model analyser=zai/glm-5.3`. These models support worker roles; this integration does not provide a Z.ai lead host.
|
|
787
|
+
|
|
788
|
+
Each GLM child process uses `https://api.z.ai/api/anthropic` and its own model/authentication environment. Okstra does not edit `~/.claude/settings.json`. GLM runs with `--setting-sources ""` so user, project, and local settings cannot override those connection values; settings-based hooks and integrations are consequently not inherited for GLM workers. The regular Claude provider keeps its existing settings behavior.
|
|
789
|
+
|
|
790
|
+
The runtime rejects a missing `ZAI_API_KEY` before starting the worker. Catalog visibility does not verify the account's model entitlement or remaining quota. The installed Claude Code version and Z.ai account must support the selected model. See [Z.ai's Claude Code setup](https://docs.z.ai/devpack/tool/claude) and [Coding Plan usage policy](https://docs.z.ai/devpack/usage-policy) for provider requirements.
|
|
791
|
+
|
|
792
|
+
GLM response model identities are recorded as `zai/<model>`; a startup model label alone is not treated as proof of the served model. The final Claude Code usage event supplies input, output, cache-creation, and cache-read counters. Repeated dispatches are summed by their status files. Missing usage stays unavailable, and billing-cost estimates are omitted rather than using Anthropic prices or treating subscription usage as metered API spend.
|
|
793
|
+
|
|
784
794
|
## `okstra` Node CLI — introspection subcommands
|
|
785
795
|
|
|
786
796
|
The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and introspection commands used by skills and agents. It goes through the Node wrapper instead of invoking the Python runtime directly, so `src/lib/python-helper.mts` wires `PYTHONPATH`.
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -405,7 +405,7 @@ def _check_command(strategy: ExecutionStrategy, request: WorkerExecRequest) -> N
|
|
|
405
405
|
"""Refuse a missing CLI before the run leaves any artifact behind.
|
|
406
406
|
|
|
407
407
|
Building the command is the only truthful way to learn which binary this
|
|
408
|
-
provider runs
|
|
408
|
+
provider runs and validates its configuration. The check stays out of the runner so a
|
|
409
409
|
refused dispatch leaves no `started` status sidecar for the liveness probe to
|
|
410
410
|
read as a worker that launched.
|
|
411
411
|
"""
|
|
@@ -41,6 +41,18 @@ Relaying a wizard step does not change the option set — relay every `options[]
|
|
|
41
41
|
|
|
42
42
|
## User closeout (BLOCKING)
|
|
43
43
|
|
|
44
|
+
Progress, remaining work, and recommendation
|
|
45
|
+
|
|
46
|
+
At each phase or implementation-stage boundary, at task completion, and before a controlled session pause or handoff, give a concise update in the resolved Report Language. Identify the task and the phase or stage, then include these three items (translate the labels):
|
|
47
|
+
|
|
48
|
+
- Progress: what this boundary actually completed, the result of checks already run, and links to the relevant persisted evidence. Distinguish a completed phase from a completed task; a dispatched worker is still in progress.
|
|
49
|
+
- Remaining work: unfinished work in the current task, open approval or input blockers with their IDs, and any registered follow-up tasks. Say explicitly when none remains in the verified scope; when state is unavailable, say unknown rather than inventing completion or a backlog.
|
|
50
|
+
- Recommendation: the next concrete action and why it comes next. After finalization, use `nextCommand` and `nextRecommendedPhase.rationale` from the result under the routing rules below, including `nextInGroup` when present. Before finalization, use the active run's recorded stage queue and recovery or resume information; do not reuse an earlier run's pointer or invent a resume command.
|
|
51
|
+
|
|
52
|
+
During an authorized continuous run, put the update beside the existing progress checkpoint and continue to the next queued step without asking for confirmation merely to deliver this update. At a controlled session pause or handoff, distinguish persisted results from in-progress work and include the available resume reference. At task completion, distinguish remaining work in this task from follow-ups or the next task in its group. If there is no recorded next action, say none is recommended. These updates do not replace checkpoints or authorize another task.
|
|
53
|
+
|
|
54
|
+
Prompt delivery is checked by `tests/contract/test_next_phase_authoring_delivery.py::test_lead_closeout_names_a_command_the_user_can_run`; this is guidance for the lead's prose, not runtime validation of the emitted message.
|
|
55
|
+
|
|
44
56
|
After Phase 7 persistence, the last user-facing message of this run is the next command. A status dump is not a close. A prohibition (`do not start implementation`) is not a next action. This applies to every task type.
|
|
45
57
|
|
|
46
58
|
**Read the pointer from the `report-finalize` result — do not re-derive it.** Its top-level `nextRecommendedPhase` object (`phase`, `status`, `rationale`) is the value every row below branches on, and the same three values are repeated on stderr as `next phase status:` / `next phase:` / `next phase rationale:`. You did not write that object and you cannot recompute it: you author your task type's routing field, and Phase 7 projects the pointer from it plus the approval state — for `implementation-planning` there is no routing field at all. If the result carries `nextRecommendedPhaseError`, say in one line that the pointer could not be read, then close on the `validate-run` row below. The same result also carries `nextCommand` — `{command, note}`, the table below already applied to this run. When `command` is non-empty it is the close; when it is empty the `note` says what to do with the `rationale` instead.
|
|
@@ -104,6 +104,8 @@ User-utterance interpretation rule:
|
|
|
104
104
|
|
|
105
105
|
## Progress reporting (BLOCKING)
|
|
106
106
|
|
|
107
|
+
At each phase or implementation-stage boundary, at task completion, and before a controlled session pause or handoff, follow the launch prompt's "Progress, remaining work, and recommendation" guidance. Include the result and evidence, unfinished work and blockers, and the next action with its reason. During an authorized continuous run, deliver this update beside the checkpoint and continue; do not turn the update into an approval gate. The same guidance applies to the final reply after persistence below.
|
|
108
|
+
|
|
107
109
|
A single okstra run frequently spans 30–120 minutes with multi-minute silent windows while workers run; without progress signals the user cannot distinguish "still working" from "hung". Lead MUST emit a single short progress line at each checkpoint below — plain user-facing text in a separate brief message (not buried inside a tool call), one line per checkpoint, format: `PROGRESS: <phase-id> <verb-phrase>`. Emit the line raw — the literal `PROGRESS:` token must begin the line. Do NOT wrap it in inline-code backticks (`` `PROGRESS: ...` ``) or a ```` ``` ```` code fence; markdown wrapping is what the post-hoc conformance validator scrapes around, and raw emit keeps the signal unambiguous.
|
|
108
110
|
|
|
109
111
|
Record each checkpoint with `okstra lead-progress append --project-root <dir> --run-manifest <path> --phase <phase-id>`, then emit the `progressLine` it prints as the user-facing line. The command resolves `leadEventsPath` from the run manifest and writes the checkpoint there — on a host whose adapter declares `sessionAccounting: artifact-only` that ledger is the only place the post-hoc validator can read it, so a conversation line alone leaves no trace and the run is reported as missing the checkpoint. Pass `--worker <role>` on the per-worker checkpoints (it rewrites a phase-specific functional label into the roster role team-state records), `--field NAME=VALUE` for the remaining `key=value` tokens in the order the line carries them, and `--detail <text>` where the line ends in a verb phrase; the wording listed below is the default for the fixed-prose checkpoints.
|
|
@@ -355,7 +357,7 @@ After each worker terminates, BEFORE classifying its terminal status, verify the
|
|
|
355
357
|
|
|
356
358
|
`--agent`, `--agent-role`, and `--error-type` are **closed enums**, not free-form labels — the role names used elsewhere in these contracts (`Codex worker`, `Claude worker`) are rejected. Use exactly:
|
|
357
359
|
|
|
358
|
-
- `--agent` — `claude-worker` | `codex-worker` | `antigravity-worker` | `grok-worker` | `kimi-worker` | `report-writer`
|
|
360
|
+
- `--agent` — `claude-worker` | `codex-worker` | `antigravity-worker` | `grok-worker` | `kimi-worker` | `zai-worker` | `report-writer`
|
|
359
361
|
- `--agent-role` — `lead` | `worker` | `report-writer`
|
|
360
362
|
- `--error-type` — `cli-failure` | `contract-violation` | `tool-failure`
|
|
361
363
|
|
|
@@ -335,10 +335,20 @@ Every plan-body verification and self-fix instruction passes [okstra-lead-contra
|
|
|
335
335
|
|
|
336
336
|
Before each verifier call, write one task-instructions file under the current
|
|
337
337
|
run's `state/` directory and run `okstra agent-prompt materialize` with
|
|
338
|
-
`--audience reverification-worker`,
|
|
339
|
-
`--assignment-ref reverify/<workerId>`, the exact `--worker-id`, and
|
|
338
|
+
`--audience reverification-worker`, the exact `--worker-id`, and
|
|
340
339
|
`--dispatch-kind plan-verify-r<N>`, matching the `<N>` in the result filename of
|
|
341
|
-
step 3.
|
|
340
|
+
step 3. For analyser verifiers, use `--assignment-ref reverify/<workerId>`.
|
|
341
|
+
For `critic-worker`, use `--assignment-ref critic/scope` from this run's
|
|
342
|
+
`invocationAssignments`, including a tie round or a later critic correction.
|
|
343
|
+
The verdict worker name `critic-worker` does not create a `reverify/critic-worker`
|
|
344
|
+
assignment. Keep the selected critic's existing model assignment.
|
|
345
|
+
**Enforced:** `okstra_ctl.agent.prompt_cli.run_identity._validate_run_identity`
|
|
346
|
+
accepts the planning critic's verification audience; materialization resolves
|
|
347
|
+
the declared assignment and reports the correct critic reference when the
|
|
348
|
+
invented `reverify/critic-worker` reference is used.
|
|
349
|
+
`tests/run/test_agent_prompt_cli.py` verifies the declared critic's reservation
|
|
350
|
+
and dispatch identity plus this recovery diagnostic.
|
|
351
|
+
That kind, not `reverify-r<N>`, is what this round takes: the verification
|
|
342
352
|
prompt contract requires the renderer signature of the queue's canonical renderer,
|
|
343
353
|
and for plan items that renderer is `okstra plan-items prompt`, not `okstra
|
|
344
354
|
convergence reverify-prompt`. Pass that command's output verbatim as
|
|
@@ -355,7 +365,9 @@ source `RoleExecution` row from the run manifest's static role state. Use its
|
|
|
355
365
|
`RoleExecution` row's `roleExecutionRef`, not that row's
|
|
356
366
|
`sourceRoleExecutionRef` field. Pass `--source-role-execution-ref
|
|
357
367
|
<sourceRoleExecutionRef>`. Do not derive that reference from `workerId`,
|
|
358
|
-
provider, model, or execution-label text.
|
|
368
|
+
provider, model, or execution-label text. For `critic-worker`, select the static
|
|
369
|
+
`role: critic` row; the runtime reserves its verifier execution under the
|
|
370
|
+
existing critic participant and model. A legacy v1 run omits this flag. Run
|
|
359
371
|
`okstra agent-prompt verify` against the
|
|
360
372
|
returned `metadataPath` before dispatch and use the returned `promptPath`
|
|
361
373
|
without modification. Native-session calls use only `hostModelValue`; before
|
|
@@ -175,7 +175,7 @@ roles:
|
|
|
175
175
|
- `### Carry-In` — for `depends-on (none)`: task-brief only. Otherwise: each depended-on stage's static exit contract + runtime sidecar path `runs/<impl-key>/carry/stage-<i>.json` placeholder.
|
|
176
176
|
- `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | outcome | expected`. `outcome` is one word — `PASS` or `FAIL` — and `expected` is the sentence saying what that looks like here; a verdict written inside the sentence is not read as one. The `files` cell lists each touched path in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...`), which does not resolve and is rejected by plan-body verification as a kind-b path mismatch. **The narrative row additionally carries `plannedPaths`: the same paths as an array, one repository-relative path per entry, with no globs, exclusions, counts or commentary.** `files` is the sentence a reader sees; `plannedPaths` is the ledger report assembly preserves and the implementer write policy enforces. When a step legitimately covers a set too large to enumerate, split it or name the directory the set lives under. **Effective row count ≤ 8** (excluding header / divider / blank). Each step is one cohesive, self-contained change. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test(s) that capture this stage's `Acceptance` **and the three declared `Test case (success|boundary|failure)` lines** (`outcome` = `FAIL`); at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`outcome` = `PASS`); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section. A stage that is truthfully none of those three declares `TDD exemption: user-bypass — <the user's words>`, which holds only while the user has granted it for that stage with `okstra prepare --tdd-bypass "<stage>:<reason>"` — never file the nearest of the three instead. Validator S10c enforces RED-first + GREEN; the `outcome` cell agreeing with its prefix is a schema conditional. S10e rejects an unsupported exemption reason and a `user-bypass` with no user grant (`validators/validate-implementation-plan-stages.py`).
|
|
177
177
|
- **The `command` cell runs inside an okstra task worktree, not a bare checkout (BLOCKING).** okstra provisions `.okstra`, the configured sync entries (`.project-docs`, `.claude`, …), and — for `implementation` — a nested `stage-<N>/` worktree into the tree the step executes in. Two consequences bind every command you write:
|
|
178
|
-
- **Clean-tree assertions use `okstra worktree-status --check-clean`.** A bare `git status --porcelain` is never empty there, so an assertion built on one fails on okstra's scaffolding rather than on the stage's work. The okstra command asks the same question over source paths only and exits 1 when dirty, so it stands alone as a step's assertion: `okstra worktree-status --check-clean`. Validator S13 rejects the bare form. Do not add a `git tag stage-<N>-exit` to the step
|
|
178
|
+
- **Clean-tree assertions use `okstra worktree-status --check-clean`.** A bare `git status --porcelain` is never empty there, so an assertion built on one fails on okstra's scaffolding rather than on the stage's work. The okstra command asks the same question over source paths only and exits 1 when dirty, so it stands alone as a step's assertion: `okstra worktree-status --check-clean`. Validator S13 rejects the bare form. Do not add a `git tag stage-<N>-exit` to the step. Stage completion records the commit in the consumer ledger without creating or moving git tags.
|
|
179
179
|
- **Never read an `.okstra/` artifact back out of a git object.** `.okstra/**` is gitignored and never committed — the executor aborts a commit that stages an ignored path and the verifier reports a committed `.okstra` path as a branch defect — so `git cat-file -e <tag>:.okstra/…`, `git show <tag>:.okstra/…`, and every variant of that read can never resolve, at any tag, in any stage. A later stage that needs a QA artifact reads it from the working tree or receives it through the carry sidecar / verifier result; do not design a stage contract around one being reachable from a tag. Validator S12 rejects the read.
|
|
180
180
|
- **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
|
|
181
181
|
- `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — declare that a Tier3 verification script will prove this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. This phase emits the line and the `requires` set only. Do NOT write `<task_root>/qa/scripts/stage-<N>.*` and do NOT add a `runCommand` or `conformance-manifest.json` entry here — the matching `implementation` stage run creates the script file and the manifest `runCommand`. A plan that declares tests with no script file on disk is valid at this gate. The data.json `conformanceTests` value carries only the remainder after the `Conformance tests: stage-<N> — ` prefix — never the `stage-<N> — ` label itself (report assembly strips a leftover label at publication, and the implementation entry gate rejects one).
|
|
@@ -646,6 +646,7 @@
|
|
|
646
646
|
},
|
|
647
647
|
"confirmation": {
|
|
648
648
|
"header": "선택 확인:",
|
|
649
|
+
"provider_data_scope": "\n전달 대상: 위 역할·모델 목록의 제공자(현재 세션 및 선택한 외부 모델 제공자).\n전달 자료: `{project_root}`의 이 작업에 대한 작업 개요, 작업 수행에 필요한 저장소 소스·문서, 선택한 근거 자료 및 실행 중 생성되는 관련 분석·검증 결과.\n진행을 선택하면 위 대상에 해당 자료를 전달하여 선택한 작업을 실행하는 것을 승인합니다. 선택에 없는 제공자나 작업과 무관한 자료는 승인 범위에 포함되지 않습니다. 실행 환경의 권한 검토는 별도로 적용됩니다.",
|
|
649
650
|
"static_role": " static-role : {role}#{ordinal} / {model}",
|
|
650
651
|
"dynamic_role": " dynamic-role : {role} / reuse selected participant model",
|
|
651
652
|
"workers_implementation_default": " workers : (프로필 기본 — executor + verifier 2 + report-writer)",
|
|
@@ -136,11 +136,11 @@ The `confirm` prompt's `label` is the selection summary (one line per resolved i
|
|
|
136
136
|
|
|
137
137
|
### Runtime-generated selectable screens
|
|
138
138
|
|
|
139
|
-
`wizard/engine.py` adapts choice screens before returning `next` when the session declares `native_single_select`.
|
|
139
|
+
`wizard/engine.py` adapts choice screens before returning `next` when the session declares `native_single_select`. Lists use same-screen question tabs only when the host supports the complete grouped selection. Otherwise, choices beyond `nativeLimits` and unsupported multi-selections use a complete numbered list. Codex has no native multi-select, so a single choice with more than three options uses `numbered-single`; multi-selection uses `numbered-multi`. Oversized or unsupported groups are presented one member at a time, with every option for that member visible. These paths are exercised by `tests/domain/wizard/test_picker_navigation.py` and `test_role_model_selection.py`.
|
|
140
140
|
|
|
141
|
-
Render
|
|
141
|
+
Render the returned screen using its `interaction.kind`. For numbered interactions, show every option label and description in original order in one message, then accept a number, label, or value; multiple choices accept comma-separated input. Submit that reply unchanged through `okstra wizard step`. Do not add a next-page choice, truncate the list, or require the user to remember a model identifier.
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
The complete numbered list is the intended fallback when a choice cannot fit the native control or same-screen tabs, including when the user generally prefers a selector. Text steps remain text steps; selecting the direct-input option opens the wizard's custom-value step.
|
|
144
144
|
|
|
145
145
|
### Plan decisions and execution permissions
|
|
146
146
|
|
|
@@ -168,7 +168,7 @@ Display the question only through the selected tool. Do not print it or its opti
|
|
|
168
168
|
|---|---|
|
|
169
169
|
| `read_artifacts` | Read the manifest-provided paths through the current host's file interface. |
|
|
170
170
|
| `write_artifact` | Write only core-authorized `.okstra/` artifacts and preserve their schemas. |
|
|
171
|
-
| `prompt_user` | Use the client-appropriate, mode-available question tool selected above for clarifications that fit `nativeLimits`. Show the question once through that tool and wait for the actual answer. Emit the question as the last thing in that turn: assistant text emitted after the call renders below the question and separates it from the user's answer. Follow the host's separate restrictions for permission requests.
|
|
171
|
+
| `prompt_user` | Use the client-appropriate, mode-available question tool selected above for clarifications that fit `nativeLimits`. Show the question once through that tool and wait for the actual answer. Emit the question as the last thing in that turn: assistant text emitted after the call renders below the question and separates it from the user's answer. Follow the host's separate restrictions for permission requests. For lists that cannot fit the native control or same-screen tabs, show the complete numbered list and accept the next message as the answer. |
|
|
172
172
|
| `dispatch_worker` | Verify each materialized invocation first. Dispatch `runner=native-session` with the current Codex host's primitive, the returned `promptPath`, and `hostModelValue`. Pass `runner=cli-wrapper` assignments to `okstra worker-dispatch --project-root <root> --run-manifest <path> --workers <ids>`; use `--dry-run` first when required. **Not in a cmux run:** when `terminalBackend` is `cmux-pane`, the cmux adapter overrides this row. |
|
|
173
173
|
| `await_workers` | Await native host workers through the host primitive and CLI workers through synchronous dispatch, then verify team-state terminal records and Result Paths for both. |
|
|
174
174
|
| `redispatch_worker` | Materialize and verify a fresh invocation, then start a fresh native worker or `okstra worker-dispatch` attempt according to the persisted runner. |
|
|
@@ -176,12 +176,26 @@ Display the question only through the selected tool. Do not print it or its opti
|
|
|
176
176
|
| `record_lead_event` | Append progress and activity records to the manifest-provided `leadEventsPath`. Use `okstra lead-progress append --phase <phase-id>` for a checkpoint and `okstra agent-activity append --kind <kind>` for an activity record; both resolve the ledger path from the run manifest. Emit the matching `PROGRESS:` line — the command prints it as `progressLine` — and, when an activity record is required, the immediately following `ACTIVITY:` line from the same structured fields. |
|
|
177
177
|
| `collect_usage` | Collect artifact/rollout-backed usage through the existing Okstra token-usage path; never read Claude session JSONL as a substitute. |
|
|
178
178
|
|
|
179
|
+
## Codex execution permissions
|
|
180
|
+
|
|
181
|
+
### Permission before bundle preparation
|
|
182
|
+
|
|
183
|
+
Before the first `okstra render-bundle` invocation, check the current host's declared filesystem restrictions and approval policy. Bundle preparation writes run artifacts and can create a task or stage worktree with `git worktree add -b`, which writes to the source repository's Git metadata. `--render-only` does not make this command read-only, and writable access to the project directory or `~/.okstra` does not imply writable access to protected `.git` metadata.
|
|
184
|
+
|
|
185
|
+
When those writes are restricted, use the host's supported execution permission on the original `render-bundle` command (`sandbox_permissions: "require_escalated"` for `exec_command` when available and permitted), preserving every `outcome.renderArgv` token. Explain that the command prepares the selected run and may create its branch and worktree. Do not first run it under restrictions already known to block these writes. Apply the same check to subsequent phase or stage bundle preparations. A wizard confirmation does not grant host privileges; follow the host's approval decision. If escalation is prohibited, report the constraint and retain the invocation instead of changing filesystem permissions or choosing an alternate launcher.
|
|
186
|
+
|
|
187
|
+
If preparation has already failed with a Git lock creation error or `Operation not permitted`, preserve the exact error and retry the same command only after the host permits the required writes. This is host-call guidance, not a permission grant or a runtime-enforced check.
|
|
188
|
+
|
|
179
189
|
## Codex dispatch details
|
|
180
190
|
|
|
181
191
|
### Permission at the dispatch boundary
|
|
182
192
|
|
|
183
193
|
Before a live CLI worker dispatch, check the current host's declared sandbox and approval policy. In a restricted Codex session, request the host's supported execution permission on the dispatch command itself (`sandbox_permissions: "require_escalated"` for `exec_command` when that mechanism is available and permitted). Scope the request to the prepared run and explain that it starts the selected worker processes. Follow the host's approval decision; a wizard confirmation or a successful preflight does not grant this permission.
|
|
184
194
|
|
|
195
|
+
Carry the user's existing authorization into that request: identify the task, the selected providers/models from the prepared assignments, and the task brief, relevant repository source/documents, selected evidence, and related run results those workers will process. Cite the actual `Proceed` response and the data-transfer scope displayed in that confirmation; `outcome.confirmationText` is the runtime's summary reference, not proof by itself that the user saw or accepted it. Explain this scope in the execution tool's justification rather than describing only process startup. Data-transfer authorization and host execution privileges are separate: preserve the former while requesting the latter.
|
|
196
|
+
|
|
197
|
+
Do not infer that an older confirmation included the new disclosure, widen the approved recipients or material, or treat model selection alone as blanket data-transfer consent. When the conversation already authorizes the same recipients and scope, use that evidence without asking again. If approval review rejects a dispatch, retain and report its reason; supply omitted existing evidence only through the host's permitted review mechanism. If the stated gap is not covered by the user's actual authorization, ask one focused question naming that gap and wait for the answer. Never bypass the rejection or repeat an unchanged request.
|
|
198
|
+
|
|
185
199
|
Apply this guidance to both `okstra worker-dispatch` and the cmux override's `okstra team dispatch`, including initial workers, reverify, critic, report-writer, and retries. Read-only previews do not start workers. A session already authorized to execute outside the sandbox does not need another request. When the host prohibits escalation, preserve the pending invocation and report the execution constraint instead of trying an alternate launcher or weakening host controls.
|
|
186
200
|
|
|
187
201
|
The worker's `--sandbox danger-full-access` flag only selects the child Codex policy; it does not remove restrictions inherited from the parent process. If startup reports `Operation not permitted`, retain the exact error and distinguish initialization failure from a worker verdict. Retry only through the permitted host mechanism after the execution conditions change, within the existing retry limit; do not repeat the same restricted invocation. This is host-call guidance, not a permission grant or a runtime-enforced check.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Claude Code 실행기를 사용하는 Z.ai GLM 작업자."""
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import replace
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
from okstra_ctl.adapters.providers.claude.adapter import ClaudeExecution
|
|
7
|
+
from okstra_ctl.domain.provider import ModelSpec, ProviderSpec, ServedModelAttestation
|
|
8
|
+
from okstra_ctl.domain.role import ROLE_DEFINITIONS
|
|
9
|
+
from okstra_ctl.domain.worker_exec import ExecCommand, PolicySupport, WorkerExecRequest
|
|
10
|
+
from okstra_ctl.domain.worker_presentation import JsonEvents
|
|
11
|
+
from okstra_ctl.domain.worker_stream import content_block_events
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
ZAI_MODELS = {
|
|
15
|
+
name: ModelSpec(name, label, name)
|
|
16
|
+
for name, label in (
|
|
17
|
+
("glm-5.3", "GLM-5.3"),
|
|
18
|
+
("glm-5.3-flash", "GLM-5.3 Flash"),
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def normalise_served_model(raw_model: str | None) -> ServedModelAttestation:
|
|
24
|
+
if not raw_model or not raw_model.strip():
|
|
25
|
+
return ServedModelAttestation.unknown()
|
|
26
|
+
return ServedModelAttestation(
|
|
27
|
+
raw_model, f"zai/{raw_model.strip().lower()}", "exact", "provider-output"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def observe_served_model(event: Mapping[str, Any]) -> str | None:
|
|
32
|
+
# init.model 은 실행기가 요청한 값이다. 응답 message.model 만 관측으로 인정한다.
|
|
33
|
+
message = event.get("message")
|
|
34
|
+
model = message.get("model") if isinstance(message, Mapping) else None
|
|
35
|
+
return model if isinstance(model, str) and model.strip() else None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def observe_usage(event: Mapping[str, Any]) -> Mapping[str, Any] | None:
|
|
39
|
+
# assistant.usage 는 턴별 값이므로 최종 누적 result.usage 와 섞어 더하지 않는다.
|
|
40
|
+
usage = event.get("usage") if event.get("type") == "result" else None
|
|
41
|
+
return usage if isinstance(usage, Mapping) and usage else None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ZaiExecution:
|
|
45
|
+
def build_command(self, request: WorkerExecRequest) -> ExecCommand:
|
|
46
|
+
api_key = os.environ.get("ZAI_API_KEY", "").strip()
|
|
47
|
+
if not api_key:
|
|
48
|
+
raise OSError("ZAI_API_KEY is required for Z.ai GLM workers")
|
|
49
|
+
command = ClaudeExecution().build_command(request)
|
|
50
|
+
return replace(
|
|
51
|
+
command,
|
|
52
|
+
# 설정 파일의 env 가 자식 환경을 다시 덮어쓰지 않게 한다.
|
|
53
|
+
argv=(*command.argv, "--setting-sources", ""),
|
|
54
|
+
environment={
|
|
55
|
+
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
|
56
|
+
"ANTHROPIC_AUTH_TOKEN": api_key,
|
|
57
|
+
"ANTHROPIC_API_KEY": None,
|
|
58
|
+
"CLAUDE_CODE_OAUTH_TOKEN": None,
|
|
59
|
+
"CLAUDE_CODE_USE_BEDROCK": None,
|
|
60
|
+
"CLAUDE_CODE_USE_VERTEX": None,
|
|
61
|
+
"CLAUDE_CODE_USE_FOUNDRY": None,
|
|
62
|
+
"CLAUDECODE": None,
|
|
63
|
+
"ANTHROPIC_MODEL": request.model,
|
|
64
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": request.model,
|
|
65
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": request.model,
|
|
66
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": request.model,
|
|
67
|
+
"CLAUDE_CODE_SUBAGENT_MODEL": request.model,
|
|
68
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
|
69
|
+
},
|
|
70
|
+
presentation=JsonEvents(
|
|
71
|
+
normalise=content_block_events,
|
|
72
|
+
observe=observe_served_model,
|
|
73
|
+
observe_usage=observe_usage,
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def policy_support(self) -> PolicySupport:
|
|
78
|
+
return ClaudeExecution().policy_support()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def create_provider() -> ProviderSpec:
|
|
82
|
+
roles = frozenset(role.id for role in ROLE_DEFINITIONS if role.id != "leader")
|
|
83
|
+
return ProviderSpec(
|
|
84
|
+
provider="zai",
|
|
85
|
+
display_label="Z.ai GLM",
|
|
86
|
+
models=ZAI_MODELS,
|
|
87
|
+
default_models={role: "glm-5.3" for role in roles},
|
|
88
|
+
wrapper="okstra-zai-exec.sh",
|
|
89
|
+
supported_roles=roles,
|
|
90
|
+
execution_capabilities=frozenset({
|
|
91
|
+
"worker-artifact-io", "source-readonly",
|
|
92
|
+
"extended-artifact-authoring", "project-mutation",
|
|
93
|
+
}),
|
|
94
|
+
exec_strategy=ZaiExecution(),
|
|
95
|
+
served_model_normalizer=normalise_served_model,
|
|
96
|
+
)
|
|
@@ -101,9 +101,12 @@ def run_corrections_check(
|
|
|
101
101
|
|
|
102
102
|
def semantic_validator(data: dict[str, Any]) -> list[str]:
|
|
103
103
|
block = data.get(block_key)
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
return [
|
|
105
|
+
f"{block_key}: {error}"
|
|
106
|
+
for error in validate_implementation_option_selection(
|
|
107
|
+
block if isinstance(block, Mapping) else {}, original_ids, analysers,
|
|
108
|
+
)
|
|
109
|
+
]
|
|
107
110
|
elif task_type == "implementation-planning":
|
|
108
111
|
|
|
109
112
|
def semantic_validator(data: dict[str, Any]) -> list[str]:
|
|
@@ -34,7 +34,11 @@ from ...assignment_resolver import AssignmentContext, resolve_dispatch_assignmen
|
|
|
34
34
|
from ...path_hints import hydrate_active_run_context
|
|
35
35
|
from ...worker_prompt_headers import worker_prompt_headers
|
|
36
36
|
from ...worker_prompt_contract import complete_reverify_instruction, validate_reverify_prompt
|
|
37
|
-
from ...worker_prompt_policy import
|
|
37
|
+
from ...worker_prompt_policy import (
|
|
38
|
+
critic_assignment_ref,
|
|
39
|
+
is_plan_critic_verification,
|
|
40
|
+
is_verification_dispatch_kind,
|
|
41
|
+
)
|
|
38
42
|
from ...paths import okstra_home
|
|
39
43
|
from ...final_report_paths import final_report_data_path
|
|
40
44
|
from ...final_report_schema import load_schema_version
|
|
@@ -251,10 +255,28 @@ def _materialize_run(
|
|
|
251
255
|
if args.assignment_ref not in assignments:
|
|
252
256
|
# 유효값은 이 매니페스트 안에만 있고 리드는 그것을 추측할 수 없다.
|
|
253
257
|
# 이름만 거절하면 읽는 쪽이 매니페스트를 직접 열어 키를 세게 된다.
|
|
258
|
+
critic_ref = critic_assignment_ref(str(manifest.get("taskType", "")))
|
|
259
|
+
recovery = ""
|
|
260
|
+
if (
|
|
261
|
+
args.assignment_ref == "reverify/critic-worker"
|
|
262
|
+
and critic_ref in assignments
|
|
263
|
+
and is_plan_critic_verification(
|
|
264
|
+
task_type=str(manifest.get("taskType", "")),
|
|
265
|
+
assignment_ref=critic_ref, dispatch_kind=args.dispatch_kind,
|
|
266
|
+
)
|
|
267
|
+
):
|
|
268
|
+
recovery = (
|
|
269
|
+
f"; for the rostered plan critic, use --assignment-ref {critic_ref} "
|
|
270
|
+
"with --worker-id critic-worker and --audience reverification-worker. "
|
|
271
|
+
"Keep the plan-verify-r<N> dispatch kind and, on v2 runs, the selected "
|
|
272
|
+
"critic's --source-role-execution-ref. This is a reference correction; "
|
|
273
|
+
"the existing critic assignment does not need to be added or changed"
|
|
274
|
+
)
|
|
254
275
|
raise AgentPromptCliError(
|
|
255
276
|
f"assignment reference is missing from run manifest: "
|
|
256
277
|
f"{args.assignment_ref}; this run declares "
|
|
257
278
|
+ (", ".join(sorted(assignments)) or "no invocation assignment")
|
|
279
|
+
+ recovery
|
|
258
280
|
)
|
|
259
281
|
assignment_payload = assignments[args.assignment_ref]
|
|
260
282
|
assignment = agent_model_assignment_from_payload(assignment_payload)
|
|
@@ -10,8 +10,6 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import json
|
|
12
12
|
import re
|
|
13
|
-
import subprocess
|
|
14
|
-
import sys
|
|
15
13
|
from dataclasses import dataclass
|
|
16
14
|
from pathlib import Path
|
|
17
15
|
from typing import Any, Dict, List, Optional
|
|
@@ -149,8 +147,6 @@ def append_consumer(plan_run_root: Path, *, impl_task_key: str, stage: int,
|
|
|
149
147
|
**fields,
|
|
150
148
|
}
|
|
151
149
|
_append_row(plan_run_root, record)
|
|
152
|
-
if status == "done":
|
|
153
|
-
_tag_stage_exit(plan_run_root, stage, fields.get("head_commit"))
|
|
154
150
|
# 종결 status 는 점유 해제 이벤트이기도 하다 — 중복 append(no-op)에서도 풀어야
|
|
155
151
|
# release 없이 done 만 기록된 과거 run 의 잔존 점유가 다음 호출에서 치유된다.
|
|
156
152
|
if status == "done":
|
|
@@ -159,57 +155,6 @@ def append_consumer(plan_run_root: Path, *, impl_task_key: str, stage: int,
|
|
|
159
155
|
_release_stage_occupancy_keeping_branch(impl_task_key, stage)
|
|
160
156
|
|
|
161
157
|
|
|
162
|
-
STAGE_EXIT_TAG = "stage-{stage}-exit"
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _repo_root(start: Path) -> Optional[Path]:
|
|
166
|
-
for candidate in [start, *start.parents]:
|
|
167
|
-
if (candidate / ".git").exists():
|
|
168
|
-
return candidate
|
|
169
|
-
return None
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def _tag_stage_exit(plan_run_root: Path, stage: int, head_commit: Any) -> None:
|
|
173
|
-
"""Move `stage-<N>-exit` to the commit this ledger row records.
|
|
174
|
-
|
|
175
|
-
The tag used to be a plan step, so it was written while the stage was still
|
|
176
|
-
running — before the carry evidence the ledger reads. A stage then had three
|
|
177
|
-
exit points that could disagree: the tag, the recorded `head_commit`, and
|
|
178
|
-
the branch tip. okstra writes the tag at the moment it settles the stage, so
|
|
179
|
-
the tag and the ledger cannot drift apart.
|
|
180
|
-
|
|
181
|
-
Best effort by design: the ledger is the record and a tag is a convenience
|
|
182
|
-
for the reader. A tree that is not a git repository, or a commit git cannot
|
|
183
|
-
resolve, leaves the row written and says so on stderr.
|
|
184
|
-
"""
|
|
185
|
-
if not isinstance(head_commit, str) or not head_commit.strip():
|
|
186
|
-
return
|
|
187
|
-
root = _repo_root(Path(plan_run_root).resolve())
|
|
188
|
-
if root is None:
|
|
189
|
-
return
|
|
190
|
-
commit = head_commit.strip()
|
|
191
|
-
tag = STAGE_EXIT_TAG.format(stage=stage)
|
|
192
|
-
try:
|
|
193
|
-
exists = subprocess.run(
|
|
194
|
-
["git", "-C", str(root), "cat-file", "-e", f"{commit}^{{commit}}"],
|
|
195
|
-
capture_output=True,
|
|
196
|
-
)
|
|
197
|
-
if exists.returncode != 0:
|
|
198
|
-
print(
|
|
199
|
-
f"okstra: stage {stage} done recorded, but {commit[:12]} is not "
|
|
200
|
-
f"a commit in {root} — `{tag}` not moved",
|
|
201
|
-
file=sys.stderr,
|
|
202
|
-
)
|
|
203
|
-
return
|
|
204
|
-
subprocess.run(
|
|
205
|
-
["git", "-C", str(root), "tag", "-f", tag, commit],
|
|
206
|
-
capture_output=True,
|
|
207
|
-
check=True,
|
|
208
|
-
)
|
|
209
|
-
except (OSError, subprocess.CalledProcessError) as exc:
|
|
210
|
-
print(f"okstra: could not move `{tag}` to {commit[:12]}: {exc}", file=sys.stderr)
|
|
211
|
-
|
|
212
|
-
|
|
213
158
|
def _equivalent_row_exists(plan_run_root: Path, impl_task_key: str, stage: int,
|
|
214
159
|
status: str, force_reappend: bool,
|
|
215
160
|
head_commit: Any) -> bool:
|
|
@@ -7,9 +7,9 @@ regardless of provider — see ``ExecutionPolicy``.
|
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
from dataclasses import dataclass
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import Literal, Protocol, runtime_checkable
|
|
12
|
+
from typing import Literal, Mapping, Protocol, runtime_checkable
|
|
13
13
|
|
|
14
14
|
from .worker_presentation import Presentation
|
|
15
15
|
from .write_policy import WriteEnforcement, WritePolicy
|
|
@@ -84,6 +84,9 @@ class ExecCommand:
|
|
|
84
84
|
stdin_text: str | None
|
|
85
85
|
cwd: Path
|
|
86
86
|
presentation: Presentation
|
|
87
|
+
# 인증 값은 프로세스 경계에서만 전달하고 명령 표현·실행 기록에서 제외한다.
|
|
88
|
+
# None 은 부모에게서 상속된 설정을 지운다.
|
|
89
|
+
environment: Mapping[str, str | None] = field(default_factory=dict, repr=False)
|
|
87
90
|
|
|
88
91
|
|
|
89
92
|
@dataclass(frozen=True)
|
|
@@ -131,8 +131,14 @@ def validate_write_policy_payload(payload: Mapping[str, Any]) -> None:
|
|
|
131
131
|
_relative_paths(artifact.get("allowedPaths", ()))
|
|
132
132
|
if source.get("mode") not in {"source-readonly", "project-mutation"}:
|
|
133
133
|
raise WritePolicyError("sourcePolicy mode is invalid")
|
|
134
|
-
if set(source)
|
|
134
|
+
if set(source) - {"plannedPathsDeclared"} != {
|
|
135
|
+
"mode", "allowedRoot", "plannedPaths", "protectedPaths"
|
|
136
|
+
}:
|
|
135
137
|
raise WritePolicyError("sourcePolicy is incomplete")
|
|
138
|
+
if "plannedPathsDeclared" in source and not isinstance(
|
|
139
|
+
source["plannedPathsDeclared"], bool
|
|
140
|
+
):
|
|
141
|
+
raise WritePolicyError("sourcePolicy.plannedPathsDeclared must be a boolean")
|
|
136
142
|
_absolute_text(source.get("allowedRoot"), "sourcePolicy.allowedRoot")
|
|
137
143
|
_relative_paths(source.get("plannedPaths", ()))
|
|
138
144
|
_relative_paths(source.get("protectedPaths", ()))
|
|
@@ -793,28 +793,17 @@ def _stable_git_projection(snapshot: MutationSnapshot) -> dict[str, Any]:
|
|
|
793
793
|
|
|
794
794
|
|
|
795
795
|
def _path_ledger_is_unenforceable(policy: WritePolicy) -> bool:
|
|
796
|
-
"""
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
없어 장부가 빈 채로 온다 — 종전에는 산문에서 유도한 문장 조각(`28 rows)`,
|
|
802
|
-
`captured in Stage 1)`)을 실었고, 그래서 계획이 지시한 파일 전부가 미허가
|
|
803
|
-
변경으로 읽혔다.
|
|
804
|
-
|
|
805
|
-
`project-mutation` 정책에서만 빈 장부가 "물을 수 없음" 을 뜻한다.
|
|
806
|
-
`source-readonly` 워커는 장부가 원래 비어 있고 그것이 "아무것도 바꾸지
|
|
807
|
-
말라" 는 뜻이므로, 그쪽 집행은 건드리지 않는다.
|
|
808
|
-
|
|
809
|
-
이 판정은 감사의 두 절반이 같은 함수를 읽는다. 종전에는 git 쪽만
|
|
810
|
-
`plannedPathsDeclared` 를 봤는데 그 키는 `build_write_policy` 가 만드는
|
|
811
|
-
sourcePolicy 에 아예 실리지 않아(4개 키 고정, `validate_write_policy_payload`
|
|
812
|
-
가 그 집합을 강제) 어느 쪽에서도 참이 된 적이 없다.
|
|
796
|
+
"""옛 계획의 미선언 목록만 면제하고, 산출물 전용 계획의 빈 소스 목록은 집행한다.
|
|
797
|
+
|
|
798
|
+
선언 여부가 저장되기 전 정책은 기존의 빈 목록 해석을 유지한다.
|
|
799
|
+
새 정책은 경로 분류 후에도 선언 여부를 보존하므로 산출물만 남은 계획이
|
|
800
|
+
임의의 소스 변경이나 커밋을 허용하지 않는다.
|
|
813
801
|
"""
|
|
814
802
|
source = policy.source_policy
|
|
815
803
|
return (
|
|
816
804
|
source.get("mode") == "project-mutation"
|
|
817
805
|
and not source.get("plannedPaths")
|
|
806
|
+
and not source.get("plannedPathsDeclared", False)
|
|
818
807
|
)
|
|
819
808
|
|
|
820
809
|
|