okstra 0.199.5 → 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/lead/okstra-lead-contract.md +1 -1
- 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/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/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
|
|
|
@@ -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)
|
|
@@ -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(
|