okstra 0.199.5 → 0.200.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/report-writer.md +3 -3
- 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/domain/worker_exec.py +5 -2
- package/runtime/python/okstra_ctl/report_projections.py +1 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +6 -24
- package/runtime/python/okstra_ctl/worker_runner.py +7 -2
- package/runtime/python/okstra_token_usage/claude.py +29 -0
- package/runtime/python/okstra_token_usage/collect.py +11 -1
- package/runtime/schemas/final-report-v2.0.schema.json +2 -1
- package/runtime/schemas/final-report-v3.0.schema.json +2 -1
- package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +1 -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
|
"""
|
|
@@ -357,7 +357,7 @@ After each worker terminates, BEFORE classifying its terminal status, verify the
|
|
|
357
357
|
|
|
358
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:
|
|
359
359
|
|
|
360
|
-
- `--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`
|
|
361
361
|
- `--agent-role` — `lead` | `worker` | `report-writer`
|
|
362
362
|
- `--error-type` — `cli-failure` | `contract-violation` | `tool-failure`
|
|
363
363
|
|
|
@@ -44,15 +44,15 @@ packet. The packet contains the task brief, analysis packet, report template,
|
|
|
44
44
|
report schema, convergence state, every successful settled worker result recorded
|
|
45
45
|
by the run manifest, accumulated `user-responses/` sidecars, and the current
|
|
46
46
|
session/token/cost accounting snapshot. Each file-backed source carries its owner,
|
|
47
|
-
project-relative path
|
|
47
|
+
project-relative path and value. The report writer receives the
|
|
48
48
|
packet's Markdown reading projection as the only task input instead of an
|
|
49
49
|
independently assembled list of raw paths.
|
|
50
50
|
|
|
51
51
|
The packet's authoring contract names the result path, narrative format, writing
|
|
52
52
|
instructions, runtime-owned content, and validation rules. Missing configured
|
|
53
53
|
paths and missing files are collected across the full source set before dispatch
|
|
54
|
-
and reported together with their owners. Report assembly
|
|
55
|
-
|
|
54
|
+
and reported together with their owners. Report assembly checks source availability
|
|
55
|
+
and readability without rejecting changed content or comparing hashes.
|
|
56
56
|
This is enforced by
|
|
57
57
|
`initial_prompt_materialization._materialize_report_writer_packet()`,
|
|
58
58
|
`report_synthesis_packet.build_report_synthesis_packet()`, and
|
|
@@ -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
|
+
)
|
|
@@ -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)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import copy
|
|
5
|
-
import hashlib
|
|
6
5
|
import json
|
|
7
6
|
import os
|
|
8
7
|
import re
|
|
@@ -54,7 +53,6 @@ class ReportSynthesisSource:
|
|
|
54
53
|
label: str
|
|
55
54
|
owner: str
|
|
56
55
|
path: str
|
|
57
|
-
digest: str
|
|
58
56
|
content: str
|
|
59
57
|
|
|
60
58
|
def to_dict(self) -> dict[str, str]:
|
|
@@ -62,7 +60,6 @@ class ReportSynthesisSource:
|
|
|
62
60
|
"label": self.label,
|
|
63
61
|
"owner": self.owner,
|
|
64
62
|
"path": self.path,
|
|
65
|
-
"digest": self.digest,
|
|
66
63
|
"content": self.content,
|
|
67
64
|
}
|
|
68
65
|
|
|
@@ -282,7 +279,6 @@ class ReportSynthesisPacket:
|
|
|
282
279
|
"user response carry-in",
|
|
283
280
|
],
|
|
284
281
|
"validationRules": [
|
|
285
|
-
"source-digest-match",
|
|
286
282
|
"writer-owned-fields-only",
|
|
287
283
|
"all-defects-collected",
|
|
288
284
|
*self._carry_validation_rules(),
|
|
@@ -307,7 +303,7 @@ class ReportSynthesisPacket:
|
|
|
307
303
|
"- Runtime-owned values: session identifiers, token usage, estimated cost, "
|
|
308
304
|
"user response carry-in",
|
|
309
305
|
"- Accounting details stay in the JSON snapshot; assembly supplies them.",
|
|
310
|
-
"- Validation:
|
|
306
|
+
"- Validation: writer-owned fields, all defects collected",
|
|
311
307
|
]
|
|
312
308
|
# 작성자가 실제로 읽는 것은 마크다운이다. JSON 정본에만 실으면 지시가
|
|
313
309
|
# 도달하지 않는다.
|
|
@@ -351,8 +347,7 @@ def _source_markdown(
|
|
|
351
347
|
if content != source.content.rstrip("\n"):
|
|
352
348
|
view = [
|
|
353
349
|
"- View: repeated extracts refer to frozen sources below; "
|
|
354
|
-
"the original line index is omitted.
|
|
355
|
-
"the frozen original.",
|
|
350
|
+
"the original line index is omitted.",
|
|
356
351
|
]
|
|
357
352
|
if source.label == "Final report schema":
|
|
358
353
|
try:
|
|
@@ -361,7 +356,7 @@ def _source_markdown(
|
|
|
361
356
|
schema = {}
|
|
362
357
|
view = ["- View: schema is not JSON; the original text is retained."]
|
|
363
358
|
if isinstance(schema, dict) and schema.get("properties"):
|
|
364
|
-
#
|
|
359
|
+
# 원본은 JSON 보관본에 남긴다. 저작용 읽기에는 검증기와
|
|
365
360
|
# 같은 소유권 투영을 쓰고, 도달하지 않는 정의만 기존 발췌기로 뺀다.
|
|
366
361
|
owned = writer_owned_schema(schema)
|
|
367
362
|
# 부분 서사 검증에서 빠지는 루트 조건도 작성 지침에는 남긴다.
|
|
@@ -374,8 +369,8 @@ def _source_markdown(
|
|
|
374
369
|
excerpt = build_schema_excerpt(owned, task_type)
|
|
375
370
|
content = json.dumps(excerpt, ensure_ascii=False, indent=2)
|
|
376
371
|
view = [
|
|
377
|
-
"- View: writer-owned schema excerpt
|
|
378
|
-
"
|
|
372
|
+
"- View: writer-owned schema excerpt. "
|
|
373
|
+
"Task-specific requirements are listed above.",
|
|
379
374
|
]
|
|
380
375
|
longest = max((len(run) for run in re.findall(r"`+", content)), default=0)
|
|
381
376
|
fence = "`" * max(3, longest + 1)
|
|
@@ -385,7 +380,6 @@ def _source_markdown(
|
|
|
385
380
|
"",
|
|
386
381
|
f"- Owner: `{source.owner}`",
|
|
387
382
|
f"- Path: `{source.path}`",
|
|
388
|
-
f"- Digest: `{source.digest}`",
|
|
389
383
|
*view,
|
|
390
384
|
"",
|
|
391
385
|
f"{fence}text",
|
|
@@ -700,13 +694,11 @@ def _read_sources(
|
|
|
700
694
|
except (OSError, UnicodeError) as exc:
|
|
701
695
|
issues.append(ReportSynthesisPacketIssue(owner, label, path, str(exc)))
|
|
702
696
|
continue
|
|
703
|
-
digest = "sha256:" + hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
704
697
|
sources.append(
|
|
705
698
|
ReportSynthesisSource(
|
|
706
699
|
label=label,
|
|
707
700
|
owner=owner,
|
|
708
701
|
path=_relative(project_root, path),
|
|
709
|
-
digest=digest,
|
|
710
702
|
content=content,
|
|
711
703
|
)
|
|
712
704
|
)
|
|
@@ -929,18 +921,8 @@ def verify_report_synthesis_packet_sources(
|
|
|
929
921
|
)
|
|
930
922
|
continue
|
|
931
923
|
try:
|
|
932
|
-
|
|
924
|
+
path.read_text(encoding="utf-8")
|
|
933
925
|
except (OSError, UnicodeError) as exc:
|
|
934
926
|
issues.append(ReportSynthesisPacketIssue(owner, label, path, str(exc)))
|
|
935
927
|
continue
|
|
936
|
-
digest = "sha256:" + hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
937
|
-
if digest != source.get("digest"):
|
|
938
|
-
issues.append(
|
|
939
|
-
ReportSynthesisPacketIssue(
|
|
940
|
-
owner,
|
|
941
|
-
label,
|
|
942
|
-
path,
|
|
943
|
-
"source value changed after packet materialization",
|
|
944
|
-
)
|
|
945
|
-
)
|
|
946
928
|
return tuple(issues)
|
|
@@ -202,7 +202,7 @@ def _launch(
|
|
|
202
202
|
stdout=subprocess.PIPE,
|
|
203
203
|
stderr=_stderr_target(strategy),
|
|
204
204
|
start_new_session=True,
|
|
205
|
-
env=_child_env(),
|
|
205
|
+
env=_child_env(command.environment),
|
|
206
206
|
)
|
|
207
207
|
on_spawn(process)
|
|
208
208
|
exit_code, timed_out, idle_seconds = _pump(
|
|
@@ -604,7 +604,7 @@ def _terminate(process: subprocess.Popen[bytes]) -> None:
|
|
|
604
604
|
process.wait()
|
|
605
605
|
|
|
606
606
|
|
|
607
|
-
def _child_env() -> dict[str, str]:
|
|
607
|
+
def _child_env(overrides: Mapping[str, str | None]) -> dict[str, str]:
|
|
608
608
|
"""The worker's environment with git's fsmonitor disabled.
|
|
609
609
|
|
|
610
610
|
The main worktree's fsmonitor daemon leaks its IPC socket into task
|
|
@@ -614,6 +614,11 @@ def _child_env() -> dict[str, str]:
|
|
|
614
614
|
GIT_CONFIG_* the caller already set.
|
|
615
615
|
"""
|
|
616
616
|
env = dict(os.environ)
|
|
617
|
+
for key, value in overrides.items():
|
|
618
|
+
if value is None:
|
|
619
|
+
env.pop(key, None)
|
|
620
|
+
else:
|
|
621
|
+
env[key] = value
|
|
617
622
|
index = int(env.get("GIT_CONFIG_COUNT", "0") or "0")
|
|
618
623
|
env[f"GIT_CONFIG_KEY_{index}"] = "core.fsmonitor"
|
|
619
624
|
env[f"GIT_CONFIG_VALUE_{index}"] = "false"
|
|
@@ -9,6 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
from .cursor import MAX_NEEDLES, fresh_cache, load_cache, save_cache
|
|
11
11
|
from .paths import claude_project_dir, ts_in_window
|
|
12
|
+
from okstra_ctl.wrapper_status import read_wrapper_status
|
|
12
13
|
|
|
13
14
|
# lead 의 단계 체크포인트 라인(prompts/lead/okstra-lead-contract.md "Progress reporting") — phase id 가
|
|
14
15
|
# `phase-<digit>` 로 시작하는 라인만 마커로 인정해 일반 대화의 오탐을 줄인다.
|
|
@@ -16,6 +17,34 @@ _PROGRESS_RE = re.compile(r"^PROGRESS:\s+(phase-\d\S*(?:[ \t].*)?)$", re.MULTILI
|
|
|
16
17
|
_PROGRESS_MARKER_MAX_CHARS = 160
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
def claude_code_status_total(status_path: Path) -> dict:
|
|
21
|
+
"""Claude Code의 최종 누적 사용량을 제공자 가격과 분리해서 읽는다."""
|
|
22
|
+
status = read_wrapper_status(status_path)
|
|
23
|
+
usage = status.raw.get("usage") if status is not None else None
|
|
24
|
+
if not isinstance(usage, dict) or not usage:
|
|
25
|
+
return {"available": False, "totalTokens": 0}
|
|
26
|
+
attestation = status.raw.get("servedModelAttestation")
|
|
27
|
+
model = attestation.get("observedModel") if isinstance(attestation, dict) else None
|
|
28
|
+
input_tokens = usage.get("input_tokens")
|
|
29
|
+
output_tokens = usage.get("output_tokens")
|
|
30
|
+
cache_creation = usage.get("cache_creation_input_tokens", 0)
|
|
31
|
+
cache_read = usage.get("cache_read_input_tokens", 0)
|
|
32
|
+
if any(
|
|
33
|
+
not isinstance(value, int) or isinstance(value, bool) or value < 0
|
|
34
|
+
for value in (input_tokens, output_tokens, cache_creation, cache_read)
|
|
35
|
+
):
|
|
36
|
+
return {"available": False, "totalTokens": 0}
|
|
37
|
+
return {
|
|
38
|
+
"available": True,
|
|
39
|
+
"totalTokens": input_tokens + output_tokens + cache_creation,
|
|
40
|
+
"inputTokens": input_tokens,
|
|
41
|
+
"outputTokens": output_tokens,
|
|
42
|
+
"cacheCreationTokens": cache_creation,
|
|
43
|
+
"cacheReadTokens": cache_read,
|
|
44
|
+
"model": model,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
19
48
|
def _event_from_record(rec: dict) -> dict | None:
|
|
20
49
|
"""jsonl 레코드 1개 → 압축 이벤트. 집계에 기여하지 않으면 None.
|
|
21
50
|
|
|
@@ -11,6 +11,7 @@ from okstra_project.dirs import OKSTRA_RELATIVE
|
|
|
11
11
|
|
|
12
12
|
from .blocks import na_block, usage_block
|
|
13
13
|
from .claude import (
|
|
14
|
+
claude_code_status_total,
|
|
14
15
|
claude_session_totals,
|
|
15
16
|
find_claude_agent_sessions,
|
|
16
17
|
find_claude_team_sessions,
|
|
@@ -662,6 +663,8 @@ def _cli_session_totals(
|
|
|
662
663
|
)
|
|
663
664
|
elif provider == "grok":
|
|
664
665
|
total = grok_session_total(session_path)
|
|
666
|
+
elif provider == "zai":
|
|
667
|
+
total = claude_code_status_total(session_path)
|
|
665
668
|
else:
|
|
666
669
|
continue
|
|
667
670
|
if total.get("available"):
|
|
@@ -705,12 +708,15 @@ def _cli_usage_block(provider: str, totals: dict, session_paths: list[Path]) ->
|
|
|
705
708
|
billable = provider_billable_equivalent(provider, *rates)
|
|
706
709
|
if billable is not None:
|
|
707
710
|
block["billableEquivalentTokens"] = billable
|
|
708
|
-
|
|
711
|
+
elif provider == "antigravity":
|
|
709
712
|
cost = antigravity_cost_usd(
|
|
710
713
|
totals.get("model"),
|
|
711
714
|
totals.get("inputTokens", 0) or 0,
|
|
712
715
|
totals.get("outputTokens", 0) or 0,
|
|
713
716
|
)
|
|
717
|
+
else:
|
|
718
|
+
cost = None
|
|
719
|
+
block["cliNote"] = "Token usage recorded; provider billing cost is unavailable."
|
|
714
720
|
if cost is not None:
|
|
715
721
|
block["cliEstimatedCostUsd"] = cost
|
|
716
722
|
return block
|
|
@@ -919,6 +925,10 @@ def _worker_cli_usage_block(
|
|
|
919
925
|
if path not in session_paths:
|
|
920
926
|
session_paths.append(path)
|
|
921
927
|
status_path = status_paths[-1] if status_paths else None
|
|
928
|
+
if provider == "zai":
|
|
929
|
+
session_paths = list(dict.fromkeys(
|
|
930
|
+
path for path in status_paths if path is not None and path.is_file()
|
|
931
|
+
))
|
|
922
932
|
if provider == "antigravity":
|
|
923
933
|
for raw, source_status in zip(prompt_paths, status_paths):
|
|
924
934
|
for source in _antigravity_usage_sources(
|