92b71c702c19e500c6dff3da37e09fac03efd7b3
4481 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
948c9dbfc0 |
fix(files): scope backslash compensation to local Windows
The shared quoting helper doubled regex backslashes for remote shells because the controller ran Windows. Apply compensation only when the backend executes locally; serialized POSIX command text stays literal. Path translation and file-write payloads remain unchanged. Verified with the real LocalEnvironment argv path and JSON command text delivered to a real Bash stdin. Tests compare received bytes for quotes, metacharacters, newlines, empty values and backslash runs. The serialized case fails before the fix while the local case already passes. Integrated: 114 tests passed, 6 host skips. Ruff and whitespace checks pass. No Docker/SSH service or POSIX host run is claimed. The rejected grep multiline rewrite is not included. |
||
|
|
f2a19b0e06 |
refactor: remove unused extraction copies
Use the existing session-export, transcription and DingTalk owners. Remove unused setup/watchdog helpers and the no-op package migration hook. No package overrides it; user-state migrations keep their own existing owners. Keep version-change installation coverage and the scheduled external plugin compatibility blocks. Verified with the real adapter, transcription, session-snapshot and PM core suites. No live messaging service or user-state operation. |
||
|
|
fd605dcacf |
fix(wake): use one engine family and honor selected capture
Move the pyopen adapter to the shared engine module and remove shadowing definitions. Request audio-io only for local capture. Pass the caller's resolved capture mode into engine construction, so auto-selected client audio does not install microphone packages. Verified through the listener entry and existing engine/detector tests with disposable state. No live microphone or native SDK acceptance. |
||
|
|
e4cc7f09d9 |
merge: integrate upstream catalog with PM publication
Keep upstream's reviewed catalog as the only plugin name index. Catalog pins and custom update sources share staged PM validation. Publish code and dependencies with recovery after process death. Reject a concurrent enablement change before publishing disabled code. Use the manifest loader's supported version in the installer. Keep probe cooldowns for timeouts, not TLS failures that a CA change fixes. Preserve the backup, uninstall, browser and memory-provider repairs. Verified with the canonical runner on native Windows ARM64, real Git repositories, local TLS endpoints and UV dependency generations. Desktop catalog tests and both TypeScript checks pass. The full suite and native release builds were not run. No remote push. |
||
|
|
1c8fae6180 |
fix(pm): preserve runtime and user state across failure paths
Keep downloads bound to their remote representation and publish through atomic destination-local staging. Serialize shared partial ownership. Keep explicit CA trust scoped to provider probes. Preserve checkpoint history and edited files, validate all profile inputs before dependency publication, and separate data removal from installed runtime ownership. Exclude machine-specific PM state from portable transfers. Keep plugin files and nested skill tools intact. Preserve native test isolation. Focused native Windows receipts cover the individual repairs and their integration. This commit does not claim a full-suite or release build. |
||
|
|
85e423482a |
fix(tools): kill the browser_exec CLI tree on timeout on Windows too
The salvaged fix only ran the CLI in its own session on POSIX and kept plain subprocess.run on Windows — the one platform where the wedge in #106244 is actually reproducible: CPython's run() retries an unbounded communicate() after kill() there, so a grandchild holding the capture pipes blocks the worker forever. On POSIX run() wait()s the PID and returns promptly; the grandchild merely leaks (live-reproduced on Linux). One code path for both platforms: - _group_popen_kwargs: start_new_session=True on POSIX, CREATE_NEW_PROCESS_GROUP + hide flags on Windows (replaces the hide-only _windows_popen_kwargs). - _kill_cli_process_group: os.killpg SIGKILL on POSIX, taskkill /T /F on Windows (same kwarg set as the sibling taskkill sites). - The Popen decodes with encoding="utf-8", errors="replace" like every other subprocess call in this file (windows footgun rule). - Drain test patches the kill helper instead of os.killpg so it runs on every host. Windows behaviour is not live-verifiable on this Linux host. |
||
|
|
7416201baf |
fix(tools): exempt POSIX-only killpg from the Windows footgun scan
The group kill lives behind browser_exec's os.name != "nt" branch, so os.killpg/SIGKILL are never reached on Windows; mark the line per the scan's suppression convention. |
||
|
|
60debff28d |
fix(tools): kill the whole browser-use CLI process group on browser_exec timeout
subprocess.run only kills the direct CLI child on TimeoutExpired; a browser_harness daemon / Chrome helper grandchild inherits the stdout/ stderr pipes and keeps them open, so the internal communicate() blocks on pipe EOF forever. The wedged tool call never returns, its activity heartbeat keeps stamping last_activity_at every 30s, and the session is pinned at "now" in the desktop sidebar indefinitely (#106244). On POSIX, run the CLI in its own session (start_new_session=True) and SIGKILL the whole process group on timeout, then drain the pipes under a bounded deadline. Windows keeps subprocess.run. |
||
|
|
3dc3809d07 |
refactor(fal): render the managed billing message once in fal_common
Image and video callers were each formatting the same four-field dict into the same sentence. Return the rendered tail from _managed_fal_billing_error so the wording lives in one place; output is byte-identical. |
||
|
|
acf9177c70 |
test(fal): trim the billing-409 salvage to two invariant tests
Keep the two tests that fail on main without the fix: - test_fal_common: a keyed managed submit makes exactly one POST (plus the negative arm: an unkeyed submit still goes through the SDK retry ladder) - test_image_generation: the 409 BILLING_ERROR body surfaces `unsupported_pricing_meter` instead of the generic "not yet enabled" text Dropped from #106484: the duplicate video-plugin billing test (same helper, same assertion), the `_fal_client = fake` / `import_fal_client` stub churn and the `tools.lazy_deps` stub — fal-client is installed in CI (`--extra fal`) so those fixtures were not needed; the `_load_fal_client` no-op fixture on TestManagedGatewayErrorTranslation for the same reason. Also drop the redundant `retry_request is None` re-check in `_ManagedFalSyncClient.submit` — `__init__` already raises when the helper is missing. |
||
|
|
0c6b94e499 |
fix(image-gen): preserve managed FAL billing errors
Avoid retrying idempotent managed FAL submissions because the retry can mask the initial billing failure. Surface structured Nous billing diagnostics consistently for image and video paths, with hermetic regression coverage. (cherry picked from commit 289ce039e9a522dc8016ae4a512214c05d0a8bc0) |
||
|
|
cff103a8b7 |
fix(mcp): classify an SDK-first transport close after dispatch as an ambiguous stdio death
The child watcher polls every 250 ms, so in practice the MCP SDK sees the closed pipe first and `call_tool` raises ClosedResourceError / "Connection closed" before the watcher fires. That exception is not a _StdioChildExited, so it fell past the stdio recoverer into _handle_session_expired_and_retry, which reconnects and replays the call -- the exact duplicated-side-effect path the previous two commits close. Live repro against a real stdio child that applies an effect then exits without replying: 2 effects with the contributor's commits alone, 1 effect + outcome_uncertain after this. On a stdio server, a session-expired-class error raised by an RPC that was already dispatched is re-raised as _StdioChildExited(in_flight=True) so the stdio recoverer owns it. HTTP servers are untouched: their session-expired retry is still the right recovery. Tests trimmed to the salvage bar: the contributor's test_precall_respawn_retry_dying_midcall_is_uncertain_without_replay (a variant of the watcher-race case) is replaced by the SDK-first regression the review on #106440 asked for; the existing pre-call retry test still pins that a never-sent call is retried once. |
||
|
|
73c104ee35 |
fix(mcp): preserve uncertainty after retry exit
(cherry picked from commit 775dd2c4d7eb28cd82cb8d943c8ea11640474227) |
||
|
|
144b86ef48 |
fix(mcp): avoid replay after mid-call stdio exit
(cherry picked from commit b29e68d0b020c805d1fccfc55f3a7c6ee6589ba9) |
||
|
|
d5926b2494 | fix: persist API delegation units once without waking the model | ||
|
|
dffc0fa2d5 |
fix(gateway): require wake-capable session provenance for background delegate_task (#98619)
Rebuilt against the post-refactor owners: the chat-completions route now
lives in gateway/platforms/api_server_openai_routes.py, the wake gate in
tools/delegate_tool_dispatch.py, and session_context.py was reshaped —
the original patch aimed at code main no longer has.
Header-less OpenAI-compatible clients get a fingerprint-derived session
id bound as the api_server chat_id. delegate_task's background gate
treated ANY bound session id as wake-capable and dispatched detached
subagents, but for derived ids the wake self-post lands in a session
whose history the client never reloads — the result is undeliverable by
construction.
Bind a wake_capable provenance flag at session-bind time, DEFAULT-DENY
at the central boundary (set_session_vars and _bind_api_server_session
both treat an omitted declaration as denied; a binder that says nothing
grants no wake authority): "1" only from audited producers whose client
can address the id again (explicit X-Hermes-Session-Id — 403-gated on
API_SERVER_KEY — native /api/sessions/{id} routes, /v1/runs); "" for
fingerprint-derived ids. The delegate gate requires the flag (fail-closed,
captured pre-child-construction alongside origin_wake_sid) and keeps the
forced-sync fallback with its honest note.
Reported-and-investigated-by: shojikumaru (Sho + Alpha) via #98619
|
||
|
|
e2763baf1c |
refactor(kanban): route the reviewer guard through _check and tighten its tests
Use the module's `_check`/`_Reject` idiom instead of an inline `return tool_error(...)` so every kanban_request_review validation failure renders through the same path, and drop the unreachable `or "none"` (list_profile_names() always contains "default"). Tests: compare the task's (status, assignee, run) tuple and the event log before/after instead of the unordered 6-assert block, use the context-managed kanban_db_connect.connect (the kb.connect alias is a plugin-compat pointer — scripts/check_compat_pointers.py flagged it), and reference #106163 in the invariant's docstring. Salvage note vs #106214 (@gaoanze888): that PR guards the same condition inside hermes_cli/kanban_db.py::request_review, but the DB primitive is also the chokepoint for `hermes kanban request-review` and the dashboard's drag-to-review, both operator surfaces where a non-profile assignee (external/human review lane) is a documented board shape (website/docs/user-guide/features/kanban-worker-lanes.md) — and it forced five unrelated test fixtures to monkeypatch profile_exists to True. The model-facing tool wrapper is the layer where a typo'd string is a bug, so the guard lives there. |
||
|
|
1d89286b36 |
fix(kanban): reject phantom worker reviewers
kanban_request_review(reviewer=<name>) reassigned the task to whatever string the model supplied. A non-profile value (e.g. the literal "reviewer") parked the card in `review` on an assignee the dispatcher can never spawn, with no error to the worker — the chain stalled silently (#106163). Validate the explicit reviewer against installed profiles before touching the board and return a tool error listing the installed profiles so the model can self-correct. Salvage of #97429: the kanban_diagnostics `review_reopened` hunk and its tests were dropped (main already replaced that loop with `_latest_event_ts`; the tests targeted the PR's pre-refactor base). Re-authored from the placeholder identity `regen <regen@local>` to the PR author's GitHub noreply address (misconfigured local git, not malice). |
||
|
|
4ddbcbd35e |
fix(mcp): a profile only adopts a shared connection whose credentials match its own config
_same_server_route compared config_fingerprint alone, which by design excludes env/headers/auth (so the schema cache survives a token rotation). Profile B with the same URL but different headers/env therefore adopted profile A's live connection and called tools as A. _connection_identity = route fingerprint + env + headers + auth mode, used by both the adopt and the stale-removal checks. Also collapses the three writers of _server_tool_scopes to two: the adoption loop re-implemented in mcp_tool_discovery._select_new_servers is dropped — register_connected_into_current_scope (which runs first in register_mcp_servers) is the single adopter, and _register_candidates records scope for freshly registered tools. |
||
|
|
d02edc2cbc | fix(gateway): register shared MCP tools per profile | ||
|
|
7107185f58 | fix(gateway): preserve shared MCP visibility across profile reloads | ||
|
|
e333113871 |
fix(review): keep /refine under the background_review origin; attendedness is its own flag
The salvaged commit forked an explicit /refine under a new "refine_review" origin so the memory delete gate would not treat it as unattended. But is_background_review() is the key for every other review guard — skill_manager_guards (curator-owned-only, read-before-write), skill_manager_tool (archive instead of rmtree), skill_ledger actor, write_approval staging, the [auto] tag — so a /refine fork silently escaped all of them. Carry attendedness separately: the fork keeps origin "background_review" and sets _review_attended; turn_context binds it beside the origin ContextVar; the memory gate keys on the new is_unattended_review(). Also run the gate AFTER _validate_single_op / the operations list check, as memory_tool's own docstring requires, so an invalid replace is rejected now rather than staged and failed at approve time. |
||
|
|
c0714575c3 |
fix(review): distinguish explicit /refine from unattended reviews and surface staged consolidations
Review follow-up on #105944 (#105921): - explicit /refine forks now run under the refine_review write origin (explicit flows from the CLI/gateway handlers through _spawn_background_review_now and spawn_background_review_thread down to build_cache_parity_fork), so a user-requested review keeps the full memory operation set; only automatic reviews stay behind the unattended delete gate. - the unattended delete gate now stages the denied replace/remove (or whole batch) into the pending store instead of dropping it: the fork's own review summary is never published, so a plain denial lost the consolidation request with no surfacing path. The staged proposal carries a proposal_staged marker that summarize surfaces as an action line, and a staging failure still fails closed to a plain denial. - regression tests: explicit-path origin pass-through, refine_review keeping replace working, near-limit denial end to end (add rejected by budget -> replace staged -> proposal surfaces, store unchanged). |
||
|
|
1571f502a9 |
fix(agent): scope background review memory access to its trigger (#105921)
The review fork's tool whitelist granted the whole memory toolset whenever the profile had memory enabled, regardless of which nudge fired, so a skill-nudge fork held remove/replace on MEMORY.md it was never asked to use; combined with the memory tool's near-limit 'consolidate now' hint, an unattended fork deleted standing rules with no user in the loop. - Pass review_memory from spawn_background_review_thread through _run_review_in_thread/_run_review_fork into _review_tool_whitelist; a skill-only review no longer gets the memory tool at all. - Fail-closed operation gate in memory_tool: a background-review fork may add, never replace/remove (single or in a batch) — consolidation decisions reach a human via the review summary instead. - Keep the deny/prompt wording in sync with the whitelist so a memory-less review doesn't advertise memory. |
||
|
|
1562b87d5d | fix(agent): isolate periodic scheduler callbacks from blocking siblings (#102574) | ||
|
|
ac10770894 |
fix(cron): don't stamp the next occurrence on an off-tick manual run
claim_job_for_fire() derives the occurrence identity from next_run_at before the same function advances it. On a scheduler tick next_run_at is the occurrence being run, which is correct; on an off-tick manual run it is the NEXT occurrence, so the execution is stamped with the identity of a slot that has not happened yet. _job_is_due() then finds a completed execution carrying that identity and skips the real slot, returning before the last_dispatch write — no error, no log line, no dispatch record. The manual flag already guards this and both _job_is_due() and claim_job_for_fire() honour it; the agent-facing run-now path never declared itself. Add a keyword-only manual= parameter and pass it from _claim_for_manual_run(). Deliberately not force=True: force also calls _activate_job_record(), which would resume a paused or disabled job, and the run-now tool depends on continuing to refuse those. The local flag is renamed to manual_fire so the new parameter is not shadowed inside the apply closure, which would raise UnboundLocalError. Three existing tests in tests/tools/ pinned the old call signature via assert_called_once_with; they now pin manual=True, so dropping the flag again fails loudly rather than silently reintroducing the skip. Restores the intent stated in #104790 — the column records the scheduled instant an execution was claimed for, and an off-tick manual run was claimed for none. Fixes #105690 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0e9fc2cc15 |
test(process-registry): exercise the portable probe payload
Execute the selected no-op rather than freeze its spelling, while rejecting /bin/true to model the NixOS failure. Mark the regression Linux-only and retain the current user-bus environment handling. Consolidates the earlier NixOS scope-probe report and fix in #102587 with the PATH-independent payload from #105436. The fallback resolver is not needed when /bin/sh is used directly. Co-authored-by: Scott Garrand <sgarrand@gmail.com> |
||
|
|
7a7ead8179 | fix(process-registry): use portable /bin/sh probe for systemd-run scope availability (#105365) | ||
|
|
defdf64790 |
simplify(agent): surface switch — reuse flatten_message_text / agent_tool_names / one runtime-boundary split
- _transcript_row_texts re-implemented agent.message_content.flatten_message_text and the api_content sidecar rule; the note can only land on a user row, so the transcript scan now skips assistant/tool rows (the bulk of the bytes). - Three sites computed "names of agent.tools"; tools.mcp_tool_agent gains agent_tool_names() used by the switch note and conversation_loop, which also stops importing the private _def_name across modules. The name list is only captured when a switch was announced. - split_runtime_boundary() is the single owner of the runtime-block rpartition/END check for both identity_line_value and _stored_prompt_matches_runtime. - platform_surface_hint was a public alias of _platform_hint; the function is now platform_hint (its docstring pointed at the pre-move module). - consume_gateway_turn_context_notes and consume_surface_switch_note share _pop_turn_note so the two one-shot channels have identical semantics. - platform check hoisted above the transcript scan. |
||
|
|
8b7eae99ef |
fix(pm): own interpreter selection and dependency recovery
Pin uv and uvx to the PM interpreter instead of ambient Python discovery. A matching dependency stamp cannot prove that installed files still exist. Repair now rebuilds the recorded workspace and lock in a fresh generation, checks startup imports, and publishes the selection only after success. Run startup recovery before dependency activation. Keep manual PM repair reachable when the selected environment is damaged. Preserve plugin selection, retry ownership, and the previous generation on failure. Remove the separate pip, ensurepip, per-extra, and install-time quarantine ladders. Keep orphan launcher restoration. Verification: 717 targeted tests passed on native Windows ARM64, with 56 skipped. Ruff, diff checks, and the source-scoped compat check passed. A disposable real Hermes install recovered deleted YAML and dotenv files, then printed CLI help with exit 0. Its lock and stamp stayed unchanged. The full suite and a release build were not run for this change. |
||
|
|
9d865810b6 |
fix(mcp-oauth): keep refresh_token when a refresh response omits it (#62333)
HermesProviderMixin._handle_refresh_response overrides the SDK's handler (to accept any 2xx and keep token bodies out of logs) but dropped the SDK's RFC 6749 section 6 carry-forward. An authorization server that does not rotate refresh tokens (TinyFish, Google, Zoho, Asana, Futu) answers the refresh grant without a refresh_token; we then stored the response verbatim, erasing the only refresh token we had, so the next expiry had nothing to refresh with and forced a browser re-auth roughly one TTL after every login. Carry the prior refresh_token (and scope, per section 5.1) forward on the OAuthToken before _store_tokens, so both the live provider and the on-disk token file keep it. A rotating AS still wins: only None fields are filled. Tests: two invariants on the real HermesMCPOAuthProvider + HermesTokenStorage (omitted -> preserved in memory and on disk; provided -> rotated). The carry-forward test is red on main. |
||
|
|
facb9eb4f1 | fix: trim computer use tool schema guidance | ||
|
|
7d2b3b767d |
merge: integrate upstream/main into ethie/pm-clean
Merge upstream
|
||
|
|
b1f003e186 | feat: add FAL GPT Image 2.5 generation and editing selections | ||
|
|
b48cc47a4c |
fix(tools): use Python 3.14 daemon worker contexts
Python 3.14 moves initializer state into a worker context. The old worker arguments fail on the first submission to a fresh executor. Use the current stdlib worker signature while preserving daemon shutdown and per-submission profile isolation. Cover worker initialization and context isolation on reused threads. Verified on Python 3.14.7: 41 focused tests pass through scripts/run_tests.sh. Ruff and diff checks pass. Packaged release verification remains pending. |
||
|
|
aa83c6d614 | fix: hide inactive grouping options from delegation schema | ||
|
|
6f11a3296e |
refactor(gateway): wait on the target user's bus socket, not the generic control-socket predicate
_user_systemd_socket_ready() accepts systemd/private alone, which is enough for systemctl --user but not for the systemd-run --user that restart-safe workers need; systemd_user_bus_env() requires the bus socket. Replace the uid threading through five helpers with one _wait_for_target_user_bus(uid) that polls /run/user/<uid>/bus, and move the post-enable wait + restart hint out of _ensure_linger_enabled into _ensure_system_service_linger so the activity probe runs only when linger was actually just enabled. Kanban applies the bus env unconditionally like the cron sibling. Refs #104893. |
||
|
|
be9c2bd19c |
fix(cron): derive the user bus env per probe and scoped spawn
A system-level gateway unit has no ordering against user@<uid>.service and linger may be enabled after boot, so the bus can appear after the one-shot adoption in run_gateway() ran. Derive XDG_RUNTIME_DIR/DBUS_SESSION_BUS_ADDRESS fresh for the availability probe and every scoped spawn (cron worker, Kanban worker, PTY/pipe terminal spawns, scope cleanup) so the 60s failure TTL can actually recover. Refs #104893. |
||
|
|
c0fc5bb993 |
fix(wake): exclude the ARM-only engine on Intel macOS
The pyopen-wakeword universal2 wheel contains an ARM64-only TFLite library. Exclude that dependency on Intel macOS in both wake extras and the PM gate. Keep sherpa and Porcupine available without changing the selected provider. Wake status now refuses unsupported engines even when lazy installs are allowed. It recommends supported alternatives instead of a failing install. The documentation identifies the platform limit and the configuration command. Marker and status regressions failed before the change. Focused tests pass: 52 passed and 2 skipped on Python 3.11, 50 passed and 4 skipped on native Python 3.14. Frozen exports retain both Intel alternatives. No locked package versions changed. Native release acceptance remains pending. |
||
|
|
fb5715c19f | fix: preserve subagent controls across attached session peers | ||
|
|
7befa11bf2 | fix: retain subagent control after live session reattachment | ||
|
|
866332bfb5 |
fix(relay): authorize send_message targets and surface egress declines (P5) (#99220)
* fix(relay): authorize send_message targets and surface egress declines
P5 of the relay egress-authorization workstream. The relay path
authenticated the SENDER but never authorized the DESTINATION, and the
gateway compounded it from both ends.
(a) send_message could silently name an arbitrary relay target. Its
`target` parameter is free-form ('platform:chat_id'), so a model could
name ANY chat id and the gateway would emit an outbound frame for it.
gateway/relay/egress.py adds an attestation floor: a relay-routed
destination must have a provenance this gateway can show -- the
operator's home channel, the channel directory, or its own gateway
session origins. Anything else is refused HERE, with a visible tool
error naming the target, before a frame is written. Non-relay platforms
and platforms served by a live native adapter in this process are
untouched (same precedence resolve_delivery_transport applies).
(b) Connector declines were swallowed into apparent successes. The
connector's egress floor answers an unauthorized destination with a
DEFINITE failure whose text is deliberately uniform (F-005). Several
relay lanes degrade a *transport drop* by design and were degrading an
*authorization refusal* the same way:
- _send_media returned None, sending the caller into
BasePlatformAdapter's text fallback -- a DIFFERENT op re-addressed at
the very chat the connector had just refused.
- _send_prompt returned None, so exec-approval / slash-confirm /
clarify reported "relay prompt op unavailable" (a wrong reason) and
ran their numbered-text fallbacks into the refused chat.
- task_card_stop discarded the error entirely.
- typing / delete / react / thread ops degraded silently at debug.
is_egress_decline() classifies THAT a decline happened (never why --
the uniform text is not parsed for reasons) and requires a definite,
non-ambiguous failure, so a lost-ack retry is still a transport
outcome. Lanes with an error-carrying contract now report the decline
verbatim; cosmetic bool/None lanes still degrade but log it at WARNING.
Advisory progress drops that legitimately degrade are unchanged: the
task_card send lane, the draft ambiguous/except branches, and every
transport-exception path keep their existing fail-open behaviour.
Tests: 21 mutations of the production source, all KILLED.
* fix(relay): authorize the RESOLVED target; declines must not fall back
Review round 1 (independently confirmed by a second reviewer) found three
blockers. Two are fixed here; the third (B-2, Telegram @username) is a policy
decision left open deliberately.
B-1 — THE FIX CAUSED THE OUTAGE IT PREVENTED (tools/send_message_tool.py)
The P5(a) guard ran ABOVE Slack user->DM resolution, so it authorized the
internal pseudo-id `_parse_target_ref` emits (`user_name:ben`, `user:U...`).
Provenances only ever hold RESOLVED conversation ids, so a fully attested DM
was compared as a handle against a set of `D...` ids and refused:
base slack:@ben SENT head(before) slack:@ben REFUSED
Every Slack DM by handle was broken. Moved the guard below resolution; it now
authorizes the destination that is actually sent to, and the refusal names the
resolved id. Position is load-bearing, so it is commented as such and pinned:
reverting the move turns exactly the four new cases red.
B-3 — A DECLINE IS NOT A LANE FAILURE (gateway/run.py)
`_approval_send_outcome` had only sent/failed/ambiguous, so a connector
decline collapsed into `failed` — which is the cue to run the plain-text
fallback into the chat the connector had just refused. The adapter fix in the
previous commit improved the error STRING while user-visible behaviour stayed
identical to base; the commit message overstated it. Fixed properly:
- new `declined` verdict, recognised via the shared `is_egress_decline`
contract (not string sniffing at the call site)
- exec-approval returns without the text fallback
- slash-confirm suppresses the text reply AND clears the registration, so a
card that never rendered cannot capture the user's next message
`send_clarify` was already correct (returns early inside the adapter).
MUTATIONS (production source; both directions)
classifier never returns 'declined' -> KILLED (4 cases)
ALL failures classified as 'declined' -> KILLED (2 cases)
guard moved back above Slack resolution -> KILLED (4 cases)
decline CODE changed (review M05) -> KILLED
marker match made case-sensitive (M10) -> KILLED
M05 was a tautology: the test asserted the imported constant against itself,
so changing the constant could not fail it. The wire contract is now pinned as
a literal, because the connector stamps that exact string and a one-sided
change is a silent cross-repo break.
REGRESSION CHECK: the 12 failures + 1 collection error in this test selection
are PRE-EXISTING cross-test contamination — the identical set fails at
|
||
|
|
712734436e |
fix(pm): make bootstrap and bundle ownership explicit
Finish bootstrap uv before PM replaces its store entry. Keep failure receipts stdlib-only and align the cryptography requirement and override with the locked version. Let bundle builders declare launch paths and update ownership. Remove payload discovery, Store probing, and the unused develop command. Derive Nix Python from the PM lock and share its provenance stamp. Document setup, activation, optional dependencies, and distribution ownership. Targeted Windows tests, relocated runtime launches, Electron bundling, and bilingual docs builds pass. Native Nix and signed-package acceptance remain CI gates. |
||
|
|
5280fe9987 |
fix: cron and local DMs reach an open Desktop Bot Chat
Route local producers to durable owner ingress before attempting the unowned CLI lane. Preserve per-run/per-message IDs and receipt-first retry handling; never fall back after ambiguous admission. Report cron admission as queued, not completed or failed, in job status, the execution ledger and CLI/tool UX. Native isolated Electron validation reproduces SESSION_NOT_OWNED on main for both idle and busy owners. Fixed owner consumes idle cron, busy cron, local DM and mounted-chat cron exactly once, keeps its lease, yields to queued human input, and preserves the prior model-request prefix and tool schema. Inference alone used a deterministic loopback wire stub; no paid model call. |
||
|
|
db96c8ced7 |
fix: admit Bot Chat deliveries through the live session owner
Adapt FalconOrtiz's owner-mailbox proposal from #101564 onto the current notification poller and topical modules. The durable mailbox is cross-process ingress only: the existing owner admits its normal prompt turn after the current turn and human FIFO clear. Retain immutable receipts, stable admission identities, capability and lease fencing, compression lineage, and disable blind recovery replay of imported turns. The original stale server hunks and expiring receipt protocol were rebuilt rather than cherry-picked: the current facade decomposition and durable busy admission contract differ. Credit the earlier owner-mailbox work in #100544 and durable producer work in #100319. Co-authored-by: fangliquanflq <fangliquan@qq.com> Co-authored-by: 686f6c61 <github@00b.tech> |
||
|
|
6178e9f4ee | fix(approvals): honor GNU env split escapes and argv0 operands | ||
|
|
50617d1c75 | fix(approvals): preserve env argv and shell comment boundaries | ||
|
|
58faa10134 |
fix(approvals): match denied executable paths behind shell prefixes
Adapt the command-position, bounded-candidate and launcher-option work from embwl0x's #76063 to the current detection owner, then add executable basename projection from Rohith Pariki's #104338. Parse raw quote state before applying existing text normalization so quoted arguments do not become commands. Cover shell payloads and literal env split-string carriers, retain path-specific rules and whole-command globs, and document the supported normalization rather than claiming an OS capability sandbox. Related: #104308, #76037, #76063, #104338, #78521, #86711. No automatic closing directives: the older carriers also contain broader case syntax and git-option work not included here. Co-authored-by: embwl0x <embwl0x@users.noreply.github.com> Co-authored-by: Rohith Pariki <rohithpariki@gmail.com> |
||
|
|
4810074d73 | fix: retain completions until explicit adapter admission | ||
|
|
3b7ff435fd |
fix(kanban): preserve durable origins for worker-created tasks
Carry the owning task's notification subscriptions independently of dependency edges, within the creation transaction. Prefer its durable session over worker and request-local sessions while preserving explicit overrides. Cover worker CLI create and built-in decomposition, and retain conversation route anchors. Auto-subscribe no longer upgrades an inherited passive subscription. Slim adaptation of Christopher-Schulze's session-precedence fix in #85687, expanded to durable subscription provenance and sibling creation paths. Related: #85575, #85687 Validation: strict RED/GREEN (7 failing cases before; 7 passing after), then 58 Kanban test files: 383 passed, 2 skipped. Real dispatcher-spawn subprocess probe covers direct, linked, unlinked, explicit-session, worker CLI, built-in children and a plain CLI negative control, with recording transport only. Co-authored-by: Christopher <210261288+Christopher-Schulze@users.noreply.github.com> |