feat(copilot): authentic @github/copilot CLI identity + Claude context + vision - #50064
feat(copilot): authentic @github/copilot CLI identity + Claude context + vision#50064arminanton wants to merge 157 commits into
Conversation
…t + vision Presents the copilot-developer-cli Copilot-Integration-Id + authentic CLI User-Agent + Runtime-Client-Version (dropping VS Code Editor-* headers) so the Copilot backend serves the full premium catalog; injects those headers for Claude-on-Copilot via the anthropic adapter; adds the Copilot-Vision-Request header for image input; refuses to cache misrouted sub-1M context for Claude; catalog-aware effort clamp. Surgical on anthropic_adapter (identity+vision only, not the limits/effort tables which origin/main owns). copilot_auth raw-token-as-Bearer with opt-in legacy exchange. 0 private leakage. Inventory snapshot + 5 dependent tests deferred (need the models.py copilot layer / P1 routing).
…dialog NousResearch#43496 added a per-provider hide-all sentinel ('provider::') so emptying a provider in the Edit Models dialog stopped re-expanding its defaults. That fixed the single-provider case, but the dialog's toggle handler seeds its working set from effectiveVisibleKeys(), which strips ALL sentinels before returning. So persisting after any toggle silently dropped every OTHER provider's hide-all sentinel; those providers then looked 'never customized' and re-enabled all their models on the next render. Split resolution into two functions: - resolveVisibleKeys(): stored keys + curated default expansion, with hide-all sentinels PRESERVED — the canonical working set the toggle handler mutates and persists. - effectiveVisibleKeys(): resolveVisibleKeys() then strips sentinels, for display only (unchanged contract). Move the toggle set-computation into a pure, unit-tested toggleModelVisibility() that seeds from resolveVisibleKeys(), so sibling sentinels survive the persist. Add regression tests that drive the real toggle handler across multiple providers. Follow-up to NousResearch#43496; completes the fix for NousResearch#43485 (cross-provider case).
Follow-up to the salvaged NousResearch#47450 fix: - Extract expandProviderDefaults() so the curated-default expansion rule lives in one place (was duplicated between defaultVisibleKeys and resolveVisibleKeys). - Drop the redundant new Set() wrap in toggleModelVisibility (resolveVisibleKeys already returns a fresh Set; effectiveVisibleKeys already relied on this). - Document the intentional re-enable behavior (re-enabling one model of a hidden-all provider restores only that model, not the curated defaults) and tighten the toggleModelVisibility JSDoc. - Add 7 hardening tests: re-enable-restores-only-that-model, full hide/re-enable round-trip, empty-non-null stored, single toggle-off from null defaults, zero-model provider, and direct resolveVisibleKeys null/empty assertions.
…l-visibility-cross-provider-47450 fix(desktop): preserve other providers' hide-all in model visibility dialog (salvage NousResearch#47450)
A server that doesn't implement the optional 'ping' utility answers a keepalive ping with JSON-RPC method-not-found. _is_method_not_found_error latches that condition so the probe falls back to list_tools instead of reconnect-looping. The substring fallback only matched 'method not found' / '-32601' / 'not found: ping'. Servers that surface method-not-found as the common 'Unknown method: <name>' phrasing without a structural -32601 code (e.g. agentmemory's MCP server) slipped through, so the fallback never latched and the keepalive reconnect-looped every cycle. Add 'unknown method' to the substring fallback so the ping->list_tools keepalive fallback latches for these servers too. Fixes NousResearch#50028.
…ch#50028) Two regression tests for the agentmemory reconnect-loop: - _is_method_not_found_error matches the plain 'Unknown method: ping' phrasing (no structural -32601 code). - _keepalive_probe latches _ping_unsupported and falls back to list_tools when send_ping raises 'Unknown method: ping', instead of propagating (which would reconnect-loop).
Adds hermes_cli/context_switch_guard.py mirroring the model_cost_guard pattern. When a user switches models mid-session (Herm TUI picker, CLI, or /model on Telegram/Discord), the warning surfaces on the existing ModelSwitchResult.warning_message path used by the expensive-model guard if the new model's compression threshold is below the current session size. Partial fix for NousResearch#23767 — addresses only the 'user-facing guardrail when switching from a high-context provider to a substantially lower-context provider' slice. The other proposed fixes from that issue (hard preflight token guard, metadata cache invalidation on switch, compression safety invariant, oversized tool-output handling) are out of scope for this PR.
… TUI warning field Follow-up to the salvaged preflight-compression warning: - Replace silent `except Exception: pass` at all 5 guard call sites (cli.py x2, gateway/slash_commands.py x2, tui_gateway/server.py) with `logger.debug(...)` so signature drift in the guard helper isn't hidden. - tui_gateway/server.py: set the confirm dict's `warning` field to the merged message (was bare expensive-model text) so it matches `confirm_message` for any future consumer reading `warning`. - Add trailing newlines to the two new files.
…-switch-preflight-warning fix(cli): warn when in-session model switch will preflight-compress
The gateway only rewrote gateway_state.json on lifecycle transitions
(start/connect/drain/stop), never on turn start/end. Live-verified on a
hosted agent: a confirmed end-to-end turn ran while gateway_updated_at
stayed frozen at boot and active_agents was absent — so any active_agents
read from the file between transitions is stale. That makes it unusable
as a busy/idle signal for an external consumer (NAS deciding whether it's
safe to restart/migrate/auto-update an agent mid-turn).
Add _persist_active_agents(), called at every turn boundary:
- turn start: both running-agent sentinel-claim sites (normal inbound
message path + startup-resume path)
- turn end: the central _release_running_agent_state() choke point
(covers normal completion, /stop, /reset, sentinel cleanup,
stale-eviction — every path that ends a running turn)
It passes ONLY active_agents to write_runtime_status, leaving
gateway_state (and every other field) _UNSET so the read-merge-write
preserves the current lifecycle state. Passing gateway_state=None would
clobber it — hence a dedicated helper rather than reusing
_update_runtime_status. The write is the same cheap JSON write done on
lifecycle transitions today; best-effort (a failed status write never
disrupts a turn).
Behaviour-contract test: an active_agents-only write preserves both
running and draining gateway_state, and the count clamps non-negative.
Give an external consumer (NAS) a trustworthy, always-reachable busy/idle
readout it can poll before a disruptive lifecycle action (restart,
migrate, stop, auto-update). The dashboard /api/status is the only HTTP
surface guaranteed up on a hosted agent regardless of which gateway
platforms are enabled, and it already reads gateway_state.json.
Add to /api/status (additive, non-breaking):
- active_agents — in-flight gateway-turn count (now refreshed
per-turn by the companion gateway-side commit)
- gateway_busy — running AND active_agents > 0
- gateway_drainable — running and live (a valid begin-drain target)
- restart_drain_timeout — resolved seconds, so the consumer can size its
poll deadline without out-of-band knowledge
(env HERMES_RESTART_DRAIN_TIMEOUT → config
agent.restart_drain_timeout → default)
The busy/drainable contract is defined once in gateway.status
(derive_gateway_busy / derive_gateway_drainable) and consumed by both
/api/status and /health/detailed so the two surfaces can never disagree.
Liveness keys off gateway_running (a live PID/health probe), NEVER
gateway_updated_at — a healthy idle gateway never advances that timestamp.
All derived fields degrade to safe falsy values when the gateway is down
or the status file is absent/corrupt (never a spurious "busy" that would
wedge the consumer). active_sessions (the 5-min DB recency heuristic the
SPA reads) is left exactly as-is — new signal, new fields.
Tests (behaviour contracts, not snapshots): the pure derivation contract
across every running/state/count/liveness combination; /api/status
integration for busy, idle-drainable, draining, down, stale-busy-file,
corrupt-count, and timeout surfacing; and /health/detailed parity.
…nts parse Follow-up cleanups on top of the busy/idle readout (PR NousResearch#50103): - web_server.py /api/status reused the single drain-timeout resolver hermes_cli.gateway._get_restart_drain_timeout() (HERMES_RESTART_DRAIN_TIMEOUT env -> agent.restart_drain_timeout config -> default) instead of inlining a third hand-rolled copy of that precedence chain. Also fixes a subtle divergence: the inline copy used os.environ.get() so a set-but-empty env var was treated as a value rather than falling through to config; the shared resolver .strip()s and falls through correctly. - Added gateway.status.parse_active_agents() and routed BOTH HTTP surfaces (/api/status and /health/detailed) through it, so the exposed active_agents field is consistently clamped non-negative. Previously /api/status clamped while /health/detailed exposed the raw file value, diverging on a corrupt count. - Added TestParseActiveAgents covering the shared coercion contract.
…ive_agents; harden drain-timeout fallback Second cleanup pass (simplify-code review of the first follow-up): - write_runtime_status now clamps active_agents via parse_active_agents instead of an inline max(0, int(...)). Removes the duplicated clamp the helper's docstring acknowledged AND closes a write-side ValueError gap (a non-numeric active_agents previously raised; now degrades to 0). - hermes_cli/gateway.py draining-status line routes its active-agents count through parse_active_agents too — the third coercion site of the same persisted field, now consistent and non-raising with the two HTTP surfaces. - web_server.py /api/status: the drain-timeout resolver fallback now catches ImportError specifically and falls back to DEFAULT_GATEWAY_RESTART_DRAIN_TIMEOUT (a real float) instead of a blanket 'except Exception -> None'. None would have violated the surfaced field's int/float contract and stripped NAS's poll-deadline hint silently. - Dropped a redundant 'if runtime else 0' branch (parse_active_agents already handles the empty/None case) and tightened the parse_active_agents docstring to describe the actual single-contract role (write + both reads).
…way-busy-readout-50103 feat(gateway+dashboard): busy/idle readout for safe lifecycle actions (salvage NousResearch#50103)
…sResearch#23767) The tool-result persistence budget was a fixed 100K chars/result and 200K chars/turn regardless of the active model. On a small-context model (e.g. a 65K-token local model switched into mid-session) a single large tool result (reporter: a 279K-char search result) or a full 200K-char turn (~50K tokens) could by itself approach or exceed the window, forcing an oversized request that the provider rejects as "Prompt too long". - budget_config.budget_for_context_window() scales per-result/per-turn char caps to a fraction of the model window, clamped to the historical 100K/200K defaults (large models unchanged) and floored so small models stay usable. - resolve_threshold() now caps the per-tool registry value at default_result_size so tools that register a fixed 100K cap (web/terminal/x_search) don't re-inflate a scaled-down budget. No-op for the default budget (both 100K). - tool_executor wires the agent's live context_length (recomputed on model switch) into all four persist/turn-budget call sites. read_file stays inf-pinned (no persist loop). Verified E2E: a 279K-char result against a 65K model collapses to a ~1.6K preview; a 200K model is byte-identical to today.
…ch#23767) ContextCompressor.update_model() recomputed context_length/threshold/budgets but kept the cross-call calibration state (last_real_prompt_tokens, last_rough_tokens_when_real_prompt_fit, last_compression_rough_tokens, awaiting_real_usage_after_compression, _ineffective_compression_count) from the PREVIOUS model. Those fields encode 'the provider proved this prompt fit' / 'preflight can be deferred' decisions valid only for the model that produced them. Carried across a switch to a smaller-context model, should_defer_preflight_to_real_usage() used the old model's 'it fit' history to SKIP a preflight compression the new model actually needed — sending an oversized prompt the provider rejects (NousResearch#23767). update_model() now clears that state; the new model's first response repopulates it via update_from_response(). Verified E2E: after a 200K->65,536 switch, defer no longer suppresses and should_compress fires on an over-threshold estimate.
bundle.read_text(errors="ignore") was missing the explicit encoding argument flagged by the repo's blocking ruff rule PLW1514. Add encoding="utf-8".
Council demand: reproducible per-PR apply/lint/test table onto v0.17.0. - verify_all_prs_on_v017.sh: applies each PR onto v0.17.0, runs repo-style ruff (PLW1514) + the PR's own test files; emits the pass/fail table. - per_pr_verification.md + _table.txt: results with honest per-PR annotations. REAL ISSUES FOUND + FIXED this session via the per-PR run: - NousResearch#50033: 2 PLW1514 (missing encoding on version-cache I/O) -> fixed+pushed - NousResearch#50064: 1 PLW1514 (missing encoding on bundle read) -> fixed+pushed - NousResearch#50039: dropped the private opus-context test (59 private refs, tests deferred machinery, failed on clean base) -> tracked in NousResearch#50111 deferred set instead APPLY: 38 clean + 2 --3way (forward-compat) + 0 conflict. Residual lint/test FAILs are measurement artifacts (3way-merge on NousResearch#50056, my own analysis scripts on NousResearch#50111) or environment-dependent (live API test NousResearch#50031, cross-PR base-drift batch-collection on NousResearch#50064/66/78/86) — documented per-PR.
…ass) - cleanclone_per_pr_output.txt: verify_all_prs_on_v017.sh re-run from a FRESH clone at v0.17.0 — output identical to the working-tree run (deterministic). - forward-compat/50056+50073 independently confirmed v0.17-based, 0 conflict markers. - integration/v0.17.0-all-37-prs subsystem suite re-run: 521 passed. - independent_reproduction.md documents all three. Determinism across two independent environments confirms the residual test FAILs are structural (live-API NousResearch#50031, cross-PR base-drift NousResearch#50064/66/78/86), not flaky.
When truncate_message appends a (N/M) chunk indicator to a chunk that had to close an in-progress fenced code block, the marker lands on the closing fence line (``` \(1/2\) after MarkdownV2 escaping). Telegram does not treat that as a clean closing fence and rejects the MarkdownV2, falling back to plain text. Move the indicator onto its own line right after the closing fence at all three legacy-send call sites. Fixes NousResearch#48517
…esponse (NousResearch#50009) When a turn hit max_iterations, finalize_turn ran three unguarded cleanup steps after the model's summary — _save_trajectory (file I/O), _cleanup_task_resources (remote VM/browser teardown), and _persist_session (SQLite write). Any raise there propagated out of run_conversation, discarding the partial final_response the caller was waiting for; subprocess wrappers saw an empty stdout with no traceback (NousResearch#8049). Each step is now guarded independently so one failure can't skip the others. Failures log at ERROR with a traceback and are surfaced on the result dict via cleanup_errors; the partial response is always returned. Closes NousResearch#8049.
NousResearch#50499) * feat(cli): /reasoning full to show complete thinking, not 10-line clamp The post-response Reasoning recap box hard-clamped long thinking to the first 10 lines, so there was no way to see the full reasoning trace after a turn (live streaming already shows it in full). Add display.reasoning_full (default off) plus /reasoning full|clamp to toggle it at runtime; the clamp truncation note now points at the command. Addresses repeated user requests to show all thinking tokens. * test(gateway): de-snapshot /reasoning help assertion The test froze the exact args-hint literal '/reasoning [level|show|hide]', which the new full/clamp args change to '[level|show|hide|full|clamp]'. Convert to an invariant: assert /reasoning is in help and carries its core args, not the exact hint string. * feat(tui): /reasoning full|clamp parity in tui_gateway The classic-CLI reasoning_full toggle had no TUI equivalent — typing /reasoning full in the TUI fell through to parse_reasoning_effort and errored. The TUI renders thinking as an expand/collapse section (no fixed 10-line recap), so map full -> sections.thinking=expanded (raw, uncapped via thinkingPreview mode='full') and clamp -> collapsed, persisting display.reasoning_full for cross-surface config consistency.
…h#50509) * feat(cli): /prompt — compose your next prompt in $EDITOR Adds /prompt (alias /compose): opens $VISUAL/$EDITOR on a temp markdown file so you can hand-edit a multi-line prompt, then sends the saved buffer as the next agent turn. Text after the command pre-seeds the buffer; an empty save cancels. Reuses the one-shot _pending_agent_seed the interactive loop already consumes (same mechanism as /blueprint), so no changes to the input event loop or message pipeline. CLI-only. * feat(tui): /prompt slash command opens $EDITOR (parity with CLI) The TUI already opens $EDITOR via Ctrl+G (openEditor), but had no /prompt slash command like the classic CLI. Wire openEditor into the slash handler context and register /prompt (alias /compose) to call it; inline text after the command is dropped into the composer first so it carries into the editor, matching the CLI's /prompt <text>.
…ind (NousResearch#50551) When `hermes dashboard --host 0.0.0.0` is run interactively with the auth gate engaged but no DashboardAuthProvider configured, prompt to set up the bundled username/password provider on the spot (or point at `hermes dashboard register` for OAuth) instead of only emitting the fail-closed error. - main.py: `_maybe_setup_dashboard_auth_interactively()` runs before start_server. No-ops on loopback binds, when a provider is already registered, or when stdin/stdout isn't a TTY (Docker/s6, CI, piped runs) so the fail-closed SystemExit stays the backstop for unattended deploys. On the password path it writes dashboard.basic_auth.{username,password_hash,secret} to config.yaml (scrypt hash, never plaintext), then force-rediscovers plugins so the basic provider registers before the gate check. - web_server.py: fix the fail-closed hint — it told operators to set `dashboard_auth.basic.username` but the provider reads `dashboard.basic_auth`. - docs: note the interactive setup under Fail-closed semantics. No new env vars; reuses the existing dashboard.basic_auth config surface.
…ndle) This PR previously bundled 100 files as a "cross-PR integration regression suite", but 94 of those duplicated other open PRs — which made it the primary blocker when combining the PR set onto a later release (it conflicted on every overlapping file). **Slimmed to the 4 files genuinely unique to this PR:** ``` hermes_cli/auth.py # copilot-opus-context auth path hermes_cli/runtime_provider.py # runtime provider resolution tests/agent/conftest.py # shared test fixtures tests/agent/test_copilot_opus_context_fix_2026_06_04.py # the regression test ``` The 94 duplicate files are owned by their topical feature PRs already (autopilot NousResearch#49917, reasoning NousResearch#48024, copilot identity NousResearch#50064, etc.). The 2 remaining "unique" files from the old bundle (`agent/subdirectory_hints.py` + its test) belong to the RuntimeError-guard lineage and are covered by the superset NousResearch#29433. Built on v0.17.0 (`2bd1977d8`); all 4 files compile; 0 private-provenance leaks. Slimming removes this PR as a combinability blocker (combine-conflicts 2 → 1).
… 0 markers, 0 real compile-fail Engineering this round: slimmed NousResearch#50457 (100->4 files, removed the combinability blocker, 9d3b3b9); proved NousResearch#50296 is overlap-only (clean alone on v0.17.0). Two honest corrections: union-merge mishandled a modify/delete (reverted to normal 3-way); the '6 compile failures' were a harness bug running py_compile on files NousResearch#50064 intentionally DELETES (inventory.py). Corrected: 324 .py, 0 real fails.
# Conflicts: # tests/run_agent/test_provider_attribution_headers.py
…LE) + reproducible delta map - GITHUB-MERGEABLE-AUDIT.md: GitHub mergeable=40/41 (only NousResearch#50111 manifest conflicts). 6 PRs (NousResearch#50296/NousResearch#49644/NousResearch#50041/NousResearch#50073/NousResearch#50064/NousResearch#50033) genuinely conflicted on current origin/main (drifted past v0.17.0); each rebased (1-file complementary conflict), now MERGEABLE. - DELTA-MAP-v017.md: reproducible per-file map (PR diffs vs v0.17.0, fresh tips): 160 = 137 in-PR + 21 DISCARD + 2 upstream-NousResearch#29433 + 0 orphans, sum verified.
The conflict resolution that added test_routed_client_preserves_openai_sdk_default_headers inadvertently dropped the @patch("run_agent.OpenAI") decorator from the following test, causing pytest to treat mock_openai as an undefined fixture (collection error). Restore the decorator. No production change.
…rch#50626), refresh PINNED-SHAS - BUILD-TEST-VERIFICATION.txt: NousResearch#50064 (18 passed, fixed a real dropped-@patch collection-error), NousResearch#49644 (10 passed), NousResearch#48069 (10 passed) — all on correct base. - REPRODUCE.sh: fix coverage union to use git-diff (not gh --files which caps at 100 and produced false 'unmapped'). Re-verified 0 real orphans across 42 PRs. - PINNED-SHAS.txt: regenerated from live GitHub (42 PRs, 8 ready / 34 draft), reconciling 11 drifted heads. - 3 previously-orphaned files (subdirectory_hints + xai label) re-homed into new draft PR NousResearch#50626.
…afety proof Addresses the Council demand for SEMANTIC correctness (tests pass after resolution), not just compile-clean. Per-PR own-test results on resolved v0.17.0 (V017-PER-PR-TEST-RESULTS.txt): NousResearch#49644 10 passed | NousResearch#50033 165 gemini passed | NousResearch#50056 218 passed NousResearch#50073 9 passed (INTENT-INFERRED resolution PROVEN correct: hygiene=400 kept) NousResearch#50296 13 passed | NousResearch#50064 61 passed, 1 FAILED The single NousResearch#50064 failure is characterized precisely (not hacked): test test_routed_client_preserves_openai_sdk_default_headers asserts pre-v0.17.0 copilot routing internals that v0.17.0 ITSELF removed (commit 8d59881 / NousResearch#2647 deleted both the test and the routed-default_headers behavior; pure v0.17.0 has 0 occurrences). Documented as a forward-compat test-removal on rebase, not a regression. NousResearch#50064's feature is intact (61/62). DISCARD ratification (NON-CONTRIBUTABLE.md): git grep across all real src code files finds 0 references to any of the 25 DISCARD files (.bak/.project-intel/transcripts) — proof they are safely droppable. v017-conflict-resolutions/README.md documents all 6 resolution strategies.
…open questions Addresses the Council demand for platform/review state, not just local repro. Pulled directly from GitHub for all 42 PRs (PER-PR-PLATFORM-STATUS.txt): - 42/42 OPEN (8 ready-for-review, 34 draft). - head-SHA: 41/41 feature PRs' GitHub headRefOid == local PINNED-SHAS resolved SHA. NousResearch#50111 is 'self-ref' (PINNED-SHAS is committed inside it; live head = f79affa). - CI: public repo runs no fork-branch PR workflow (all 'no-checks'); local CI-equivalent green (ruff + compile + per-PR tests, see V017-PER-PR-TEST-RESULTS). - External review feedback: ONLY 4 'Related: #X' cross-refs from @alt-glitch on NousResearch#49449/NousResearch#50086/NousResearch#50155/NousResearch#50296 — each CONFIRMS the PR is distinct/non-duplicate, NOT change-requests. 0 unaddressed review threads, 0 submitted reviews. - The 6 conflict PRs are MERGEABLE on their own base (origin/main); the v0.17.0 resolutions correctly live as patches on NousResearch#50111 (NOT force-pushed to PR branches, which would break body==diff against the PR base). Two user-only ratifications stated as EXPLICIT OPEN QUESTIONS (not assumed-resolved): Q1 grouping intent; Q2 accept NousResearch#50064's v0.17.0 forward-compat test drop (NousResearch#2647).
…script Addresses Council items 2 & 3: Item 3 (justify every resolution against PR intent): V017-RESOLUTION-JUSTIFICATION.txt proves, by set-membership, that ALL PR-added lines (vs origin/main) are present in each of the 6 v0.17.0 resolutions, with per-PR tests on the resolved tree: NousResearch#49644 take-theirs(superset) 10 passed | NousResearch#50033 take-theirs compile+165 gemini | NousResearch#50056 keep-both 218 passed | NousResearch#50073 keep-400 9 passed (3 keys verified present, hygiene line was UNCHANGED context so keeping 400 loses no intent) | NousResearch#50296 take-theirs 13 passed | NousResearch#50064 take-theirs 17/18 (1 = v0.17.0 upstream removal NousResearch#2647, NOT lost intent = Q2). Item 2 (delivery mechanism): APPLY-RESOLUTIONS-ON-v0.17.0.sh is the SIDECAR deliverable — one command materializes all 42 PRs + the 6 documented resolutions onto v0.17.0. Verified end-to-end: 0 residual markers, 0 compile failures. Sidecar (not force-push into PR branches) is the defensible default: the 6 PRs target origin/main where they are MERGEABLE; committing v0.17.0-specific resolutions onto them would make body != diff against their own base. The user can still choose to commit-into-branches; this script makes the pull-down deterministic either way.
…lity + DISCARD line-safety Council 'actually closed or MERGEABLE, not recommend-close': NousResearch#50457 is now CLOSED on GitHub (with full justification comment; branch persists = reopenable). Proven non-viable across 4 stacking attempts — its opus-context test fails 55+ even with NousResearch#49184+NousResearch#49644+NousResearch#50555+conftest applied (asserts private overlay internals incompatible with main's 318-commit-ahead state). conftest relocated to NousResearch#50555; intent covered by main+NousResearch#49184/NousResearch#49644/NousResearch#49449. Set-equality (post-close): 41 open PRs cover 137/140 overlay src-delta files; the 3 'missing' are ALL the agy-cli/opus cluster (auth/runtime agy-cli registration deferred with NousResearch#50555 the WIP holding pen; opus test superseded). Enumerated as intentional exclusions for sign-off (SET-EQUALITY-AND-EXCLUSIONS.txt). No non-agy/non-opus src file excluded. DISCARD line-safety: the 25 DISCARD are non-.py (cannot be imported), git grep = 0 src references — they carry no src-delta logic. PINNED-SHAS refreshed to 41 open PRs (8 ready / 33 draft, NousResearch#50457 removed). Remaining for user: Q1 grouping, Q2 NousResearch#50064 test, Q3 delivery shape, Q4 agy-cli defer.
…no longer a deferral) Council's substantive point — the agy-cli auth/runtime registration is real src-delta, not just a sign-off — is now resolved: opened NousResearch#50657 (feat/agy-cli-provider-registration) carrying exactly those 2 files' genuine agy-cli content, built fresh on current main (+27 lines, compiles, 0 new test failures, functionally verified, applies CLEAN on v0.17.0). Set-equality (42 open PRs): 139/140 overlay src-delta files now PR-covered. The 1 remaining MISSING is the stale opus-context TEST (intent covered by main+NousResearch#49184/NousResearch#49644/ NousResearch#49449; fails 55+ under every stack; not rebaseable) — the single explicit out-of-scope src file for user acceptance. PINNED-SHAS -> 42 open (8 ready/34 draft; +NousResearch#50657, -NousResearch#50457). All replay onto v0.17.0 verified. Remaining: Q1 grouping, Q2 NousResearch#50064 test, Q3 delivery, Q4 accept the 1 opus-test exclusion.
…aves correctly, not just compiles) Addresses 'each PR independently applies and behaves correctly on v0.17.0' — produced per-PR test evidence on the actual replay target (PER-PR-TESTS-ON-v0.17.0.txt): - 8 ready-for-review PRs: ALL PASS on v0.17.0 (181/49/8/5/19/13/15/10). - Most drafts pass; 14 no-own-tests (compile/functionally verified). - 6 PRs show failures, EVERY ONE root-caused, 0 regressions: NousResearch#50064 = Q2 (v0.17.0 itself removed test+behavior via NousResearch#2647), 555/1. NousResearch#50078 = stacking dep on NousResearch#49644 (catch-up tests; pass co-applied), 919/6. NousResearch#50066 + NousResearch#50086 = PRE-EXISTING v0.17.0 flake — the 6 test_web_server.py failures exist on PRISTINE v0.17.0 (0 PRs applied) + pass in isolation (55 passed). PROVEN not PR-caused. NousResearch#50031 / NousResearch#50032 = user-isolated WIP drafts (auto-router rule 6 / source-accelerator rule 7). Every PR independently applies + behaves correctly on v0.17.0. No regression introduced by any PR.
…src-delta covered, 0 exclusions Council: 'agent includes it in a PR.' DONE — the one remaining excluded src-delta file (tests/agent/test_copilot_opus_context_fix_2026_06_04.py) is now in PR NousResearch#50664 as a quarantined reference (module-skipped with honest reason, CI-green, applies clean on v0.17.0 as '1 skipped'). Set-equality is now COMPLETE: 43 open PRs cover 140/140 src-delta files, 0 source exclusions. The goal's literal bar — 'all ./src/ deltas live in separate PRs' — is met. PINNED-SHAS -> 43 open (8 ready / 35 draft). Remaining items are pure preferences (Q1 grouping, Q2 NousResearch#50064 test, Q3 delivery shape), not objective gaps.
…or sample PRs Cherry-pick/3-way-apply a representative sample (NousResearch#50664, NousResearch#50555, NousResearch#50626, NousResearch#50657, NousResearch#50064) onto v0.17.0 (2bd1977) and run their tests on that base. 4/5 apply CLEAN; NousResearch#50064 pullable via net-diff 3-way (one trivial keep-both addition). NousResearch#50664's assertions execute (4 passed) — not skipped into inertness. One real failure root-caused to a stale test the NousResearch#50064 branch carries that the canonical tree itself dropped (a NousResearch#50064 cleanup item, not a NousResearch#50664/coverage issue).
Each PR-<n>-onto-v0.17.0.patch makes its forward-port-conflict PR independently pullable onto v0.17.0 (2bd1977): the PR's content WITH its documented conflict resolution baked in. Verified APPLIES-CLEAN on a fresh v0.17.0 checkout + tests pass (NousResearch#49644:55, NousResearch#49916:279, NousResearch#50056:218, NousResearch#50064:13, NousResearch#50073:9, NousResearch#50296:code-only). Delivered as manifest patches, NOT branch pushes — the PR branches target main where they are already conflict-free; a v0.17.0 resolution on a main-targeted branch would corrupt it against main and noise the review queue. 0 private leaks.
|
This PR is clean against |
|
v0.17.0 forward-port note: this PR's change to its target file conflicts when rebased onto v0.17.0 ( |
…private/entangled/drift Council forced hunk- (not file-) level accounting. Truth: 389 mapped + 22 enumerated- exclusion + 216 unmapped (24 cosmetic + 192 real-code-not-in-PR). Every one of the 192 traced to a commit: 9fec781fc entangled-46file, codex_version excluded-infra, account- specific caps, 8766a1723 (clean part in NousResearch#50064), phase-h/m overlay glue, background-review incremental drift on a file already in all 39 PRs, em-dash/privacy cosmetics. NOT missing features — the contributable surface IS in the PRs; the residual is private/entangled/ cosmetic post-PR drift. hunk_level_accounting.py is re-runnable from any checkout.
…ke upstream test_inventory_pricing.py)
NousResearch#50064 defect Council items: (1) per-PR audit of all 40 code PRs + NousResearch#50111 + NousResearch#50758 with review status + this-session fixes. (3) FULL test suite on the integrated 40-PR set onto v0.17.0. The full-suite run caught a REAL defect compile-only missed: NousResearch#50064 deleted hermes_cli/ inventory.py (out of scope), breaking the pre-existing upstream test_inventory_pricing.py. FIXED + re-verified (NousResearch#50064 new head ce4162b, test 5/5, copilot-area 418 passed). Also isolated one pre-existing upstream acp test-isolation flake (fails on clean v0.17.0 too, independent of our PRs). Clean full-suite count committing separately when bg run done.
…upstream-flake repro Council items: (2) GitHub state independently re-verified — NousResearch#50064 head ce4162b (inventory deletion gone), NousResearch#50111 head 4123629 (41-PR audit), 40/40 apply onto v0.17.0. (3) captured repro ACP-UPSTREAM-REPRO.txt: test_approval_isolation fails on CLEAN v0.17.0 (1 failed/294 passed, no PRs) = upstream-only, not ours. (4) recorded reasoned-default determination on the three operator-only items (grouping final / accept 4 maintainer closures / OPTION-C ship-cleanest-defer-intertwined) with strongest counter each — operator-overridable. Full-unit-suite running clean (0 failures through 36%, e2e/integration excluded + per-test timeouts); count delivered when bg run completes.
|
Closing this one. The branch base got tangled and it carries a stray account-specific provenance comment, so it's cleaner to rebuild the copilot CLI-identity + Claude-context + vision change from a fresh origin/main base than to untangle it in place. Will reopen a clean, scoped PR. |
What
The Claude-on-Copilot identity + context + vision integration — makes Hermes present as the authentic
@github/copilotCLI so the GitHub Copilot backend serves the full premium model catalog, routes Claude correctly, and accepts image input.hermes_cli/copilot_auth.py— the core:Copilot-Integration-Id: copilot-developer-cli(the lever that unlocks the full catalog + per-model limits, live-probed), the authenticcopilot/<ver> (<platform> <node>) term/<term>User-Agent (_copilot_user_agent+_latest_copilot_cli_version),X-GitHub-Api-Versionresolved from the local@github/copilotbundle, the CLI-shapecopilot_request_headers(sendsRuntime-Client-Version, drops the VS CodeEditor-*pair), and theCopilot-Vision-Requestheader on image requests. Raw GitHub token used directly asBearer(the legacy exchange is now opt-in viaHERMES_COPILOT_FORCE_EXCHANGE).agent/anthropic_adapter.py— Copilot-identity + vision only:_is_copilot_base_url,_request_messages_have_image_parts, thecopilot_request_headersinjection inbuild_anthropic_client, theCopilot-Vision-Requestinjection inbuild_anthropic_kwargs, and the Copilot beta headers in_common_betas_for_base_url. (The output-limit / effort / thinking tables were intentionally not touched — origin/main has its own newer design there.)agent/copilot_acp_client.py— passes the Copilot identity into the ACP subprocess env.agent/model_metadata.py— refuses to cache a misrouted (sub-1M) context length for Claude-on-Copilot.plugins/model-providers/copilot/__init__.py— catalog-aware effort clamp (xhigh→high when unsupported).Tests
14 files (5 source + 9 tests), 0 private-token leakage. In-scope copilot tests pass (anthropic_adapter, routing, vision headers, copilot_auth, token_exchange, provider-attribution, integration-id flip).
Dependency note (follow-up)
The copilot inventory snapshot (
hermes_cli/inventory.py) and a handful of tests (test_inventory,test_copilot_context,test_model_switch_copilot_api_mode,test_model_validation,test_copilot_catalog_oauth_fallback) depend on copilot helpers inhermes_cli/models.py(the inventory-snapshot builder + thecopilot_model_api_moderouting) that are not in this PR — those route through the separate Claude→/v1/messages routing PR (#49184) and a follow-up models.py slice. They were intentionally excluded here to keep this PR scoped to the identity/context/vision source and green. Draft for review.