Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions argus_skill/adapters/agent_cli_backend/_exec_finalize.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def finalize_result(
token_usage: TokenUsage | None = None,
premium_requests: float | None = None,
error: str = "",
startup_receipt: dict | None = None,
) -> RunnerResult:
backend = ctx.backend
persisted_error = redact_secrets_text(
Expand Down Expand Up @@ -161,6 +162,7 @@ def finalize_result(
thread_id=result.thread_id,
model_usage=result.model_usage,
error=persisted_error,
startup_receipt=startup_receipt,
)
appended = UsageLedger(
ctx.usage_project_root,
Expand Down
1 change: 1 addition & 0 deletions argus_skill/adapters/agent_cli_backend/_exec_spawn.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,5 @@ def spawn_and_finish(ctx: "_ExecContext", cli_options: Any) -> RunnerResult:
else "completed"
),
error=safe_failure_text,
startup_receipt=complete_row,
)
48 changes: 48 additions & 0 deletions argus_skill/core/runner_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,51 @@ def result_has_pre_provider_refusal(result: Any) -> bool:
"result_has_pre_provider_refusal",
"result_has_unrecoverable_resume_state",
]


def is_copilot_context_parser_error(value: object) -> bool:
"""Exact runner-wrapped parser diagnostic; text alone is not authority."""
prefix = (
"Process exited with code 1 before turn completion.\n"
"error: unknown option '--context'\n"
)
suffix = "Try 'copilot --help' for more information."
return str(value or "").strip() in (
prefix + "(Did you mean --connect?)\n\n" + suffix,
prefix + "\n" + suffix,
)


def is_copilot_context_parser_refusal(
error: object, *, provider: str, call_id: str, run_label: str,
status: str, thread_id: object, source: str,
receipt: dict[str, Any] | None,
) -> bool:
"""Use only host-generated agent.io.complete, never model/tool JSON.

A parser diagnostic is positive startup evidence only when the matching
process receipt confirms an unsuccessful, silent pre-turn CLI invocation.
Usage accounting must independently reject every observed usage field.
"""
if not is_copilot_context_parser_error(error) or not receipt:
return False
command = receipt.get("command")
return bool(
provider == "copilot" and status == "error" and source == "run_exec"
and not thread_id and call_id
and receipt.get("type") == "agent.io.complete"
and receipt.get("backend") == provider
and receipt.get("call_id") == call_id
and receipt.get("run_label") == run_label
and receipt.get("exit_code") == 1
and receipt.get("turn_failed") is True
and receipt.get("turn_completed") is False
and receipt.get("thread_id") is None
and receipt.get("fatal_error") == "Process exited with code 1 before turn completion."
and receipt.get("tool_activity_observed") is False
and all(receipt.get(key) == 0 for key in (
"agent_message_count", "stdout_line_count", "json_event_count"))
and isinstance(command, list)
and any(command[i:i+2] == ["--context", "default"]
for i in range(1, len(command)-1))
)
68 changes: 63 additions & 5 deletions argus_skill/core/usage.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
)
from .event_catalog import CALL_SCOPED_EVENT_TYPES, EventType, canonical_event_type
from .pricing import PricingQuote, PricingStatus, quote_copilot_usage, quote_token_usage
from .runner_errors import is_pre_provider_refusal_error
from .runner_errors import (
is_copilot_context_parser_error,
is_copilot_context_parser_refusal,
is_pre_provider_refusal_error,
)
from .token_usage import TokenUsage, extract_token_usage

try: # pragma: no cover - Windows usage mutations use portalocker below
Expand Down Expand Up @@ -90,13 +94,26 @@ def to_jsonable(self) -> dict[str, Any]:
return row

@classmethod
def from_jsonable(cls, row: dict[str, Any]) -> "UsageRecord":
def from_jsonable(
cls, row: dict[str, Any], *, startup_receipt: dict[str, Any] | None = None,
) -> "UsageRecord":
cost = _optional_float(row.get("cost_usd"))
pricing_status = _pricing_status(row.get("pricing_status"))
pricing_tier = str(row.get("pricing_tier") or "unknown")
error = str(row.get("error") or "")
if (
is_pre_provider_refusal_error(error)
(is_pre_provider_refusal_error(error) or (
is_copilot_context_parser_refusal(
error, provider=str(row.get("provider") or ""),
call_id=str(row.get("call_id") or ""),
run_label=str(row.get("run_label") or ""),
status=str(row.get("status") or ""),
thread_id=row.get("thread_id"),
source=str(row.get("source") or ""), receipt=startup_receipt,
)
and row.get("premium_requests") is None
and row.get("premium_request_cost_usd") is None
))
and cost is None
and row.get("total_nano_aiu") is None
and not row.get("model_usage")
Expand Down Expand Up @@ -272,13 +289,20 @@ def build_usage_record(
model_usage: Iterable[dict[str, Any]] | None = None,
error: str = "",
source: UsageSource = "run_exec",
startup_receipt: dict[str, Any] | None = None,
) -> UsageRecord:
usage = token_usage or TokenUsage()
normalized_model_usage = _normalize_model_usage(model_usage)
normalized_provider = str(provider or "").strip().lower()
premium_quote = quote_copilot_usage(premium_requests)
missing_resume_target = (
is_pre_provider_refusal_error(error)
(is_pre_provider_refusal_error(error) or (
is_copilot_context_parser_refusal(
error, provider=normalized_provider, call_id=call_id,
run_label=run_label, status=status, thread_id=thread_id,
source=source, receipt=startup_receipt,
) and premium_requests is None
))
and total_nano_aiu is None
and provider_cost_usd is None
and not normalized_model_usage
Expand Down Expand Up @@ -495,6 +519,7 @@ def records(
handle = self.path.open("r", encoding="utf-8")
except OSError:
return out
startup_receipts = None
with handle:
for raw in handle:
try:
Expand All @@ -503,7 +528,12 @@ def records(
continue
if not isinstance(row, dict):
continue
record = UsageRecord.from_jsonable(row)
receipt = None
if is_copilot_context_parser_error(row.get("error")):
if startup_receipts is None:
startup_receipts = _startup_completion_receipts(self.project_root)
receipt = startup_receipts.get(str(row.get("call_id") or ""))
record = UsageRecord.from_jsonable(row, startup_receipt=receipt)
if not record.call_id or record.call_id in seen:
continue
seen.add(record.call_id)
Expand Down Expand Up @@ -1689,3 +1719,31 @@ def _call_status(value: Any) -> CallStatus:
"summarize_usage",
"usage_recorded_event",
]


def _startup_completion_receipts(project_root: Path) -> dict[str, dict[str, Any]]:
"""Read host lifecycle summaries, not raw provider/tool stream frames.

Ambiguous duplicate completion receipts fail closed. Only consulted when
an exact historical parser diagnostic needs the missing runner context.
"""
receipts: dict[str, dict[str, Any]] = {}
seen: set[str] = set()
try:
with (project_root / "events.jsonl").open(encoding="utf-8") as handle:
for raw in handle:
try:
event = json.loads(raw)
except (ValueError, TypeError):
continue
if not isinstance(event, dict) or event.get("type") != "agent.io.complete":
continue
call_id = str(event.get("call_id") or "")
if call_id in seen:
receipts.pop(call_id, None)
else:
receipts[call_id] = event
seen.add(call_id)
except OSError:
return {}
return receipts
4 changes: 2 additions & 2 deletions argus_skill/release_manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"package_version": "0.1.1",
"release_id": "0.1.1+b9d192ba6b186634",
"release_id": "0.1.1+f2f2ab05a41299fc",
"schema_version": 1,
"source_digest": "b9d192ba6b1866346496b561464a2052d261b03b2eab1424d2299f676eb9ba13"
"source_digest": "f2f2ab05a41299fc21fbb430c0bc5de704d2c01259d39ae5bfac6a98489bf776"
}
4 changes: 2 additions & 2 deletions frontend/core/src/release.generated.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Generated by argus_skill.release_tools.generate_manifest. Do not edit.
export const RELEASE_ID = "0.1.1+b9d192ba6b186634";
export const RELEASE_SOURCE_DIGEST = "b9d192ba6b1866346496b561464a2052d261b03b2eab1424d2299f676eb9ba13";
export const RELEASE_ID = "0.1.1+f2f2ab05a41299fc";
export const RELEASE_SOURCE_DIGEST = "f2f2ab05a41299fc21fbb430c0bc5de704d2c01259d39ae5bfac6a98489bf776";
2 changes: 1 addition & 1 deletion frontend/tui/bundle/argus.mjs

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading