memstack-skill-loader 4.18.0__tar.gz → 4.19.0__tar.gz
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.
- {memstack_skill_loader-4.18.0/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.19.0}/PKG-INFO +1 -1
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/pyproject.toml +1 -1
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/agent_runner.py +101 -20
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/dashboard.html +561 -110
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/dashboard.py +58 -9
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/memory_db.py +97 -52
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/memory_migrations.py +125 -47
- memstack_skill_loader-4.19.0/src/memstack_skill_loader/models.py +785 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/stats_tracker.py +53 -3
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/stats.py +250 -48
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader.egg-info/SOURCES.txt +6 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_advisor_strategy.py +4 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_builder_scope.py +4 -0
- memstack_skill_loader-4.19.0/tests/test_burn_reconciliation.py +323 -0
- memstack_skill_loader-4.19.0/tests/test_credential_status.py +540 -0
- memstack_skill_loader-4.19.0/tests/test_dashboard_model_select.py +144 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_memory_migrations.py +192 -36
- memstack_skill_loader-4.19.0/tests/test_model_registry.py +411 -0
- memstack_skill_loader-4.19.0/tests/test_session_store.py +141 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/README.md +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/setup.cfg +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/diary_ingest.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/goal.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/goal_engine.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/hooks/__init__.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/hooks/deny_git.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/hooks/deny_manager_write.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/hooks/deny_reviewer_git.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/hooks/git_policy.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/index_meta.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/license.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/memory_engine.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/__init__.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/_diag.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/body_parser.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/compressor.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/forwarder.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/pro_compressor.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/proxy/server.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/server.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/session_context.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/skill_descriptions.json +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_body_parser_metric.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_code_read_savings.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_dashscan.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_diary_ingest.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_failed_approaches.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_git_policy.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_index_meta_guard.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_license_environment.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_license_grace.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_memory_engine.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_memory_inspector.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_memory_scoring.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_memory_tools.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_pro_compressor.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_pro_skills_update.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_pro_source_resolution.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_project_dir_canonical.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_search_memory.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_session_context.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_skill_drift.py +0 -0
- {memstack_skill_loader-4.18.0 → memstack_skill_loader-4.19.0}/tests/test_wheel_contents.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memstack-skill-loader
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.19.0
|
|
4
4
|
Summary: MCP server that vector-indexes MemStack Pro skills for on-demand loading
|
|
5
5
|
Project-URL: Homepage, https://memstack.pro
|
|
6
6
|
Project-URL: Documentation, https://memstack.pro/docs
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "memstack-skill-loader"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.19.0"
|
|
8
8
|
description = "MCP server that vector-indexes MemStack Pro skills for on-demand loading"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -20,19 +20,14 @@ from .stats import log_agent_invocation, log_api_cost
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
# ---------------------------------------------------------------------------
|
|
23
|
-
# Anthropic model pricing
|
|
23
|
+
# Anthropic model pricing
|
|
24
24
|
# ---------------------------------------------------------------------------
|
|
25
|
+
# The rate card lives in models.py, which is also what the dashboard reads, so
|
|
26
|
+
# there is one table rather than two that can drift. models.MODEL_PRICING is
|
|
27
|
+
# prefix-matched, so a dated id such as claude-haiku-4-5-20251001 resolves to
|
|
28
|
+
# its family rate instead of falling through to the unknown-model default.
|
|
29
|
+
from .models import MODEL_PRICING, price_for as _price_for # noqa: F401
|
|
25
30
|
|
|
26
|
-
MODEL_PRICING = {
|
|
27
|
-
"claude-fable-5": {"input": 10.0, "output": 50.0},
|
|
28
|
-
"claude-opus-5": {"input": 5.0, "output": 25.0},
|
|
29
|
-
"claude-sonnet-5": {"input": 3.0, "output": 15.0},
|
|
30
|
-
"claude-sonnet-4-6": {"input": 3.0, "output": 15.0},
|
|
31
|
-
"claude-opus-4-6": {"input": 5.0, "output": 25.0},
|
|
32
|
-
"claude-opus-4-7": {"input": 5.0, "output": 25.0},
|
|
33
|
-
"claude-opus-4-8": {"input": 5.0, "output": 25.0},
|
|
34
|
-
"claude-haiku-4-5": {"input": 1.0, "output": 5.0},
|
|
35
|
-
}
|
|
36
31
|
# Cache read: 90% discount on input rate
|
|
37
32
|
# Cache creation: 25% premium on input rate
|
|
38
33
|
|
|
@@ -44,8 +39,15 @@ def _calculate_api_cost(
|
|
|
44
39
|
cache_read_tokens: int = 0,
|
|
45
40
|
cache_creation_tokens: int = 0,
|
|
46
41
|
) -> float:
|
|
47
|
-
"""Calculate USD cost from token counts and model pricing.
|
|
48
|
-
|
|
42
|
+
"""Calculate USD cost from token counts and model pricing.
|
|
43
|
+
|
|
44
|
+
Returns 0.0 for an id with no list rate (a non-model placeholder such as
|
|
45
|
+
"<synthetic>"): there is no rate to multiply by, and inventing one at the
|
|
46
|
+
Opus rate is what this avoids.
|
|
47
|
+
"""
|
|
48
|
+
pricing = _price_for(model)
|
|
49
|
+
if not pricing["has_rate"]:
|
|
50
|
+
return 0.0
|
|
49
51
|
input_rate = pricing["input"]
|
|
50
52
|
output_rate = pricing["output"]
|
|
51
53
|
|
|
@@ -71,6 +73,9 @@ MAX_ITERATIONS = 9999
|
|
|
71
73
|
|
|
72
74
|
ANTHROPIC_BASE_URL = os.environ.get("ANTHROPIC_BASE_URL", "")
|
|
73
75
|
API_KEY_FILE = Path.home() / ".memstack" / "api_key"
|
|
76
|
+
# Holds the Claude Code SUBSCRIPTION token (sk-ant-oat01...), NOT an API key.
|
|
77
|
+
# The file name says "oauth" for install-compatibility only; do not rename it.
|
|
78
|
+
# An api03 key filed here is sent as a Bearer token and silently never works.
|
|
74
79
|
OAUTH_TOKEN_FILE = Path.home() / ".memstack" / "oauth_token"
|
|
75
80
|
API_DEFAULT_MODEL = "claude-sonnet-4-6"
|
|
76
81
|
API_MAX_TOKENS = 16000
|
|
@@ -94,7 +99,12 @@ def _load_api_key() -> str | None:
|
|
|
94
99
|
return None
|
|
95
100
|
|
|
96
101
|
def _load_oauth_token() -> str | None:
|
|
97
|
-
"""Get the
|
|
102
|
+
"""Get the Claude Code SUBSCRIPTION token (sk-ant-oat01...).
|
|
103
|
+
|
|
104
|
+
Reads CLAUDE_CODE_OAUTH_TOKEN, then ~/.memstack/oauth_token. That file holds
|
|
105
|
+
the subscription token from `claude setup-token`, not an API key, despite
|
|
106
|
+
the historic file name.
|
|
107
|
+
"""
|
|
98
108
|
token = os.environ.get("CLAUDE_CODE_OAUTH_TOKEN", "").strip()
|
|
99
109
|
if token:
|
|
100
110
|
return token
|
|
@@ -108,6 +118,54 @@ def _load_oauth_token() -> str | None:
|
|
|
108
118
|
return None
|
|
109
119
|
|
|
110
120
|
|
|
121
|
+
def _live_api_key():
|
|
122
|
+
"""(has_live_api_key, dead_rows) for the api-execution-mode fallback gate.
|
|
123
|
+
|
|
124
|
+
Validity, not presence. A key counts only when a probe of GET /v1/models no
|
|
125
|
+
older than 24h says the credential is live; the probe runs here when the
|
|
126
|
+
status file is absent or stale. ANTHROPIC_BASE_URL is a proxy address, not a
|
|
127
|
+
credential, and never counts. The old gate let it stand in for one, so a
|
|
128
|
+
machine with the TokenStack proxy exported ran "api" mode with no key at all.
|
|
129
|
+
|
|
130
|
+
Fails open on any error: an unreachable probe returns no live key, which
|
|
131
|
+
downgrades to subscription rather than running api mode blind.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
from . import models as model_registry
|
|
135
|
+
return model_registry.live_api_key_status()
|
|
136
|
+
except Exception as exc:
|
|
137
|
+
return False, [{"label": "probe", "kind_label": "",
|
|
138
|
+
"status": "unchecked", "error": str(exc)}]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _dead_key_reason(dead_rows) -> str:
|
|
142
|
+
"""One sentence naming which key was dead. Never any part of a value."""
|
|
143
|
+
if not dead_rows:
|
|
144
|
+
return "no API key credential found (env ANTHROPIC_API_KEY or ~/.memstack/api_key)"
|
|
145
|
+
parts = []
|
|
146
|
+
for r in dead_rows:
|
|
147
|
+
label = r.get("label", "?")
|
|
148
|
+
kind = r.get("kind_label", "")
|
|
149
|
+
code = r.get("http_status")
|
|
150
|
+
if r.get("status") == "dead":
|
|
151
|
+
detail = f"dead (HTTP {code})" if code else "dead"
|
|
152
|
+
else:
|
|
153
|
+
detail = "unchecked" + (f": {r.get('error', '')}" if r.get("error") else "")
|
|
154
|
+
parts.append(f"{label}" + (f" [{kind}]" if kind else "") + f": {detail}")
|
|
155
|
+
return "; ".join(parts)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _append_agent_log(path: Path, line: str) -> None:
|
|
159
|
+
"""Append one timestamped runner line to an agent log. Never raises."""
|
|
160
|
+
try:
|
|
161
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
ts = time.strftime("%Y-%m-%dT%H:%M:%S")
|
|
163
|
+
with open(path, "a", encoding="utf-8") as f:
|
|
164
|
+
f.write(f"[{ts}] [agent-runner] {line}\n")
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
|
|
111
169
|
SYSTEM_PROMPTS = {
|
|
112
170
|
"manager": (
|
|
113
171
|
"You are a Manager agent. You receive a task and project context (directory listing "
|
|
@@ -1071,6 +1129,8 @@ def _build_env(strip_api_key: bool = False, inject_api_key: str | None = None) -
|
|
|
1071
1129
|
# Set strip_api_key=False to revert.
|
|
1072
1130
|
if strip_api_key:
|
|
1073
1131
|
env.pop("ANTHROPIC_API_KEY", None)
|
|
1132
|
+
# Subscription token (sk-ant-oat01) from ~/.memstack/oauth_token: that
|
|
1133
|
+
# file holds the Claude Code subscription token, not an API key.
|
|
1074
1134
|
oauth_token = _load_oauth_token()
|
|
1075
1135
|
if oauth_token:
|
|
1076
1136
|
env["ANTHROPIC_AUTH_TOKEN"] = oauth_token
|
|
@@ -1094,11 +1154,17 @@ def _build_env(strip_api_key: bool = False, inject_api_key: str | None = None) -
|
|
|
1094
1154
|
return env
|
|
1095
1155
|
|
|
1096
1156
|
|
|
1097
|
-
def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int, str]:
|
|
1157
|
+
def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int, str, int, int]:
|
|
1098
1158
|
"""Extract text and token usage from claude --output-format stream-json.
|
|
1099
1159
|
|
|
1100
|
-
Returns (text, input_tokens, output_tokens, cost_usd, context_tokens, model
|
|
1160
|
+
Returns (text, input_tokens, output_tokens, cost_usd, context_tokens, model,
|
|
1161
|
+
cache_read_tokens, cache_creation_tokens).
|
|
1101
1162
|
context_tokens is the last iteration's total tokens (accurate context window fill).
|
|
1163
|
+
|
|
1164
|
+
The two cache populations are returned separately because the CLI bills for
|
|
1165
|
+
them inside total_cost_usd while leaving them out of usage.input_tokens.
|
|
1166
|
+
Dropping them here is what made the Burn Report's cost columns unprovable
|
|
1167
|
+
from its token columns.
|
|
1102
1168
|
"""
|
|
1103
1169
|
text_parts: list[str] = []
|
|
1104
1170
|
tool_parts: list[str] = []
|
|
@@ -1108,6 +1174,8 @@ def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int, str]:
|
|
|
1108
1174
|
cost_usd = 0.0
|
|
1109
1175
|
context_tokens = 0
|
|
1110
1176
|
stream_model = ""
|
|
1177
|
+
cache_read_tokens = 0
|
|
1178
|
+
cache_creation_tokens = 0
|
|
1111
1179
|
last_assistant_usage: dict = {}
|
|
1112
1180
|
for line in raw.splitlines():
|
|
1113
1181
|
line = line.strip()
|
|
@@ -1139,6 +1207,8 @@ def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int, str]:
|
|
|
1139
1207
|
usage = obj.get("usage", {})
|
|
1140
1208
|
input_tokens = usage.get("input_tokens", 0)
|
|
1141
1209
|
output_tokens = usage.get("output_tokens", 0)
|
|
1210
|
+
cache_read_tokens = usage.get("cache_read_input_tokens", 0) or 0
|
|
1211
|
+
cache_creation_tokens = usage.get("cache_creation_input_tokens", 0) or 0
|
|
1142
1212
|
cost_usd = obj.get("total_cost_usd", 0.0) or 0.0
|
|
1143
1213
|
if last_assistant_usage:
|
|
1144
1214
|
context_tokens = (
|
|
@@ -1156,7 +1226,8 @@ def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int, str]:
|
|
|
1156
1226
|
text = "[Agent completed with tool calls but no text summary]\n\n" + "\n\n".join(tool_parts)
|
|
1157
1227
|
else:
|
|
1158
1228
|
text = ""
|
|
1159
|
-
return text, input_tokens, output_tokens, cost_usd, context_tokens,
|
|
1229
|
+
return (text, input_tokens, output_tokens, cost_usd, context_tokens,
|
|
1230
|
+
stream_model, cache_read_tokens, cache_creation_tokens)
|
|
1160
1231
|
|
|
1161
1232
|
|
|
1162
1233
|
def _extract_text_from_stream_line(line: str) -> Optional[str]:
|
|
@@ -1574,7 +1645,8 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
1574
1645
|
|
|
1575
1646
|
raw_stdout = stdout_data or ""
|
|
1576
1647
|
stderr = stderr_data or ""
|
|
1577
|
-
output, input_tokens, output_tokens, cost_usd, context_tokens, stream_model
|
|
1648
|
+
(output, input_tokens, output_tokens, cost_usd, context_tokens, stream_model,
|
|
1649
|
+
cache_read_tokens, cache_creation_tokens) = _parse_stream_json(raw_stdout)
|
|
1578
1650
|
output = output.strip()
|
|
1579
1651
|
ts = time.strftime("%H:%M:%S")
|
|
1580
1652
|
|
|
@@ -1615,6 +1687,8 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
1615
1687
|
input_tokens=input_tokens, output_tokens=output_tokens,
|
|
1616
1688
|
actual_cost_usd=cost_usd, execution_mode=exec_mode,
|
|
1617
1689
|
project=_project,
|
|
1690
|
+
cache_read_tokens=cache_read_tokens,
|
|
1691
|
+
cache_creation_tokens=cache_creation_tokens,
|
|
1618
1692
|
)
|
|
1619
1693
|
except Exception:
|
|
1620
1694
|
pass
|
|
@@ -2091,11 +2165,18 @@ def _orchestrate(session: Session) -> None:
|
|
|
2091
2165
|
# Load execution modes and custom display names for this run
|
|
2092
2166
|
agent_modes = load_agent_modes()
|
|
2093
2167
|
display_names = _load_agent_display_names()
|
|
2094
|
-
has_api_key =
|
|
2168
|
+
has_api_key, dead_keys = _live_api_key()
|
|
2095
2169
|
for _ag in ("manager", "builder", "reviewer"):
|
|
2096
2170
|
if agent_modes[_ag] == "api" and not has_api_key:
|
|
2097
2171
|
agent_modes[_ag] = "subscription"
|
|
2098
|
-
|
|
2172
|
+
reason = _dead_key_reason(dead_keys)
|
|
2173
|
+
print(f"[agent-runner] {_ag}: mode=api but no live API key, "
|
|
2174
|
+
f"falling back to subscription ({reason})", file=sys.stderr)
|
|
2175
|
+
_append_agent_log(
|
|
2176
|
+
session_log_dir / f"{_ag}.log",
|
|
2177
|
+
f"mode=api requested but no live API key; "
|
|
2178
|
+
f"falling back to subscription. {reason}",
|
|
2179
|
+
)
|
|
2099
2180
|
|
|
2100
2181
|
# Step 1: Manager analyzes the task
|
|
2101
2182
|
session.agents["manager"]["status"] = "busy"
|