Commit Graph

21 Commits

Author SHA1 Message Date
teknium1
0fc1ebca93 fix(acp): honour the executor's is_error when closing a tool call
tool.completed carries is_error, but the ACP bridge dropped it and
re-derived status from the result text alone, so a tool cancelled by a
user interrupt (plain-text result) or one returning an error dict closed
as completed. Pass the flag through close_tool_call and OR it into
build_tool_complete's failed predicate; the text heuristic stays as the
fallback for the step-closer path.
2026-09-18 10:33:54 -07:00
teknium1
ac6fc98c0f fix(acp): step-closer fallback coerces wire arguments; trim to two invariant tests
Follow-up to the salvaged #114442 (@hteo1337), which closes each ACP tool call
from its own ``tool.completed`` (the mechanism PR #50741 by @liuhao1024 filed in
June, and PR #27854 by @godlin-gh filed first in May via ``tool_complete_callback``) and fails whatever is still open at turn end, draining
``tool_call_ids`` and ``tool_call_meta`` together.

Live repro of the fallback path exposed a third mechanism the issue did not
name: ``make_step_cb`` passed ``prev_tools[i]["arguments"]`` — the wire JSON
*string* — as ``function_args`` into ``build_tool_complete``, whose content
builders index it as a dict. The ``.get`` on a str raised inside the swallowed
step callback, so a ``write_file`` close never reached the client even when a
next step existed. Coerce with ``coerce_tool_args`` (meta args when absent).

Tests: the contributor's six tests folded into two invariants — a call is
closed exactly once from ``tool.completed`` with the step closer standing down;
the step fallback survives JSON-string arguments and the turn-end flush fails
the remaining call while emptying BOTH per-turn dicts.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Co-authored-by: godlin <ganlinbupt@gmail.com>
2026-09-18 10:33:54 -07:00
HTeo
12d7b4ab9c fix(acp): give every tool call a terminal status before the turn ends
The step callback only fires on the next step, so a turn's last tool calls stayed in_progress forever, and a blocked or permission-denied call projects no tool.completed at all. Close each call from its own tool.completed event, stand the step-callback fallback down once completions arrive, and fail whatever is still open when the turn ends.
2026-09-18 10:33:54 -07:00
teknium1
a00f832c9f fix: ACP assistant messageIds are UUIDs, not a counter
The ACP schema (agent-client-protocol 0.9.0, ContentChunk.messageId) says
"Both clients and agents MUST use UUID format for message IDs". The
ported allocator emitted hermes-assistant-N strings, which a strict
client may reject or fail to group. A fresh uuid4 per message keeps the
grouping semantics and can never collide with an earlier turn's id, so
the counter/prefix state is gone.

Tests trimmed to three invariants: chunks share one UUID until the None
flush sentinel (empty deltas ignored), thought + text share an id, and
the no-allocator shape stays unchanged.
2026-09-13 21:00:53 -07:00
Teknium
4a1e44dd2f Port from PrimeIntellect-ai/prime-agent#1781: stable assistant messageIds on ACP streamed chunks
ACP clients group streamed agent_message_chunk / agent_thought_chunk
updates into one assistant reply by messageId, and use a new id to
start the next reply (root-reply replacement semantics). Hermes' ACP
adapter sent every chunk without a messageId, so clients that replace
'the current assistant message' per chunk collapsed separate
autonomous turns into one bubble.

- AssistantMessageIdAllocator (per ACP session, monotonic across
  turns): a contiguous run of reasoning + text deltas shares one
  hermes-assistant-N id; the None flush sentinel Hermes core emits
  before tool execution / at end of stream closes it.
- make_message_cb / make_thinking_cb stamp update.message_id when an
  allocator is provided; legacy no-allocator shape unchanged.
- Unstreamed final responses open their own id; plugin-transformed
  responses reuse the streamed message's id (replacement).
- Tests: grouping until flush, thought+text sharing, monotonic ids,
  empty-string vs None sentinel, legacy shape.
2026-09-13 21:00:53 -07:00
Bartok
8b79d8731b fix(acp): preserve falsey tool results in step callback
make_step_cb() used 'or' to fall back from 'result' to 'output' key:
    result = tool_info.get('result') or tool_info.get('output')

This collapsed valid falsey results (empty string, 0, False) to None,
because Python's 'or' operator treats all falsey values as missing.

Use explicit key presence check instead:
    result = tool_info.get('result') if 'result' in tool_info else tool_info.get('output')

This preserves the actual value when the 'result' key exists (even if
falsey), and only falls back to 'output' when 'result' is truly absent.

Adds regression tests for empty string, zero, and output-key fallback.

Fixes #10845.
2026-09-12 08:29:48 -07:00
Teknium
2776813df3 compat(plugins): temporary import-path shims for external plugins — ONE commit, revert on schedule
The Sep 2026 decomposition (PR #102117) makes internal import paths a non-API: names now live in
the focused modules that define them. This commit is the ONLY thing keeping the old paths alive,
so external plugins have time to update. It is deliberately a single, unsquashed commit:

    git revert <this sha>

removes every shim, stub and manifest at once on the announced date. Nothing in-tree may depend on
these pointers: scripts/check_compat_pointers.py (wired into lint.yml) fails CI if it does.

What it adds (see COMPAT_MANIFEST.md, compat_manifest.json):
- 332 facade modules get one delimited `PLUGIN-COMPAT` block appended at the end of the file
- 1,172 moved names resolved lazily via a module `__getattr__` (PEP 562) — never a top-level import,
  so no import cycles; facades that already had `__getattr__` get a chained one
- 592 third-party/stdlib names the old modules used to expose, with their original import statements
- 266 public definitions that had been deleted as unused, restored byte-for-byte from the pre-decomposition
  tree (+40 private helpers and 16 imports pulled in only because a restored definition needs them)
- 3 deleted modules recreated as re-export stubs (gateway/startup_watchdog, hermes_cli/observability/
  relay_runtime, tools/environments/modal_utils)
- private names (`_x`) get no pointer: they were never API (3,792 skipped)

Verified: all 335 touched modules import under a fresh HERMES_HOME and every manifest name resolves;
the lint reports zero in-tree uses; ruff clean; targeted suites unchanged.
2026-09-03 17:13:22 -07:00
Teknium
3a7ce79912 refactor(acp): session/events/edit_approval/permissions/provenance/entry/auth — hoist lazy hermes_constants imports, fold branches, pack literals 2026-09-02 19:45:20 -07:00
Teknium
07ae6d4b16 refactor(acp): AST-neutral bracket packing pass 2 (118 cols) + docstring closer folds 2026-09-02 19:06:54 -07:00
Teknium
41e861bf71 refactor(acp): unify tool-arg coercion (coerce_tool_args) across events/server; inline history chunk factory; flatten cancel/reset/queue paths; entry flag dispatch 2026-09-02 18:29:26 -07:00
Teknium
492967f86c refactor(acp): AST-neutral bracket packing/hugging across acp_adapter 2026-09-02 18:18:21 -07:00
Teknium
df7c867027 refactor(acp): events _upgrade_queue single key; edit_approval uses Path.is_relative_to; session toolset expansion via dict.fromkeys; fork_session early return 2026-09-02 18:13:29 -07:00
Teknium
beacf4e942 refactor(tui): resume — verified partial work (acp_adapter + methods_* compaction, bind_module via globals()) 2026-09-02 14:09:01 -07:00
HenkDz
0292398604 fix(acp): use modes for edit auto-approval 2026-05-18 01:19:55 -07:00
HenkDz
bd3a5873e1 fix(acp): replay native todo plans 2026-05-15 14:07:53 -07:00
HenkDz
4444d5fe4f fix(acp): emit native plan updates for todo 2026-05-15 14:07:53 -07:00
Teknium
4e89c53082 fix(async): close unscheduled coroutines in all threadsafe bridges (#26584)
Wraps every sync->async coroutine-scheduling site in the codebase with a
new agent.async_utils.safe_schedule_threadsafe() helper that closes the
coroutine on scheduling failure (closed loop, shutdown race, etc.)
instead of leaking it as 'coroutine was never awaited' RuntimeWarnings
plus reference leaks.

22 production call sites migrated across the codebase:
- acp_adapter/events.py, acp_adapter/permissions.py
- agent/lsp/manager.py
- cron/scheduler.py (media + text delivery paths)
- gateway/platforms/feishu.py (5 sites, via existing _submit_on_loop helper
  which now delegates to safe_schedule_threadsafe)
- gateway/run.py (10 sites: telegram rename, agent:step hook, status
  callback, interim+bg-review, clarify send, exec-approval button+text,
  temp-bubble cleanup, channel-directory refresh)
- plugins/memory/hindsight, plugins/platforms/google_chat
- tools/browser_supervisor.py (3), browser_cdp_tool.py,
  computer_use/cua_backend.py, slash_confirm.py
- tools/environments/modal.py (_AsyncWorker)
- tools/mcp_tool.py (2 + 8 _run_on_mcp_loop callers converted to
  factory-style so the coroutine is never constructed on a dead loop)
- tui_gateway/ws.py

Tests: new tests/agent/test_async_utils.py covers helper behavior under
live loop, dead loop, None loop, and scheduling exceptions. Regression
tests added at three PR-original sites (acp events, acp permissions,
mcp loop runner) mirroring contributor's intent.

Live-tested end-to-end:
- Helper stress test: 1500 schedules across live/dead/race scenarios,
  zero leaked coroutines
- Race exercised: 5000 schedules with loop killed mid-flight, 100 ok /
  4900 None returns, zero leaks
- hermes chat -q with terminal tool call (exercises step_callback bridge)
- MCP probe against failing subprocess servers + factory path
- Real gateway daemon boot + SIGINT shutdown across multiple platform
  adapter inits
- WSTransport 100 live + 50 dead-loop writes
- Cron delivery path live + dead loop

Salvages PR #2657 — adopts contributor's intent over a much wider site
list and a single centralized helper instead of inline try/except at
each site. 3 of the original PR's 6 sites no longer exist on main
(environments/patches.py deleted, DingTalk refactored to native async);
the equivalent fix lives in tools/environments/modal.py instead.

Co-authored-by: JithendraNara <jithendranaidunara@gmail.com>
2026-05-15 14:00:01 -07:00
Henkey
cb883f9e97 fix(acp): improve zed integration 2026-04-17 13:29:26 -07:00
Mibayy
cc2b56b26a feat(api): structured run events via /v1/runs SSE endpoint
Add POST /v1/runs to start async agent runs and GET /v1/runs/{run_id}/events
for SSE streaming of typed lifecycle events (tool.started, tool.completed,
message.delta, reasoning.available, run.completed, run.failed).

Changes the internal tool_progress_callback signature from positional
(tool_name, preview, args) to event-type-first
(event_type, tool_name, preview, args, **kwargs). Existing consumers
filter on event_type and remain backward-compatible.

Adds concurrency limit (_MAX_CONCURRENT_RUNS=10) and orphaned run sweep.

Fixes logic inversion in cli.py _on_tool_progress where the original PR
would have displayed internal tools instead of non-internal ones.

Co-authored-by: Mibayy <mibayy@users.noreply.github.com>
2026-04-05 12:05:13 -07:00
Teknium
8bb1d15da4 chore: remove ~100 unused imports across 55 files (#3016)
Automated cleanup via pyflakes + autoflake with manual review.

Changes:
- Removed unused stdlib imports (os, sys, json, pathlib.Path, etc.)
- Removed unused typing imports (List, Dict, Any, Optional, Tuple, Set, etc.)
- Removed unused internal imports (hermes_cli.auth, hermes_cli.config, etc.)
- Fixed cli.py: removed 8 shadowed banner imports (imported from hermes_cli.banner
  then immediately redefined locally — only build_welcome_banner is actually used)
- Added noqa comments to imports that appear unused but serve a purpose:
  - Re-exports (gateway/session.py SessionResetPolicy, tools/terminal_tool.py
    is_interrupted/_interrupt_event)
  - SDK presence checks in try/except (daytona, fal_client, discord)
  - Test mock targets (auxiliary_client.py Path, mcp_config.py get_hermes_home)

Zero behavioral changes. Full test suite passes (6162/6162, 2 pre-existing
streaming test failures unrelated to this change).
2026-03-25 15:02:03 -07:00
Teknium
25481d4286 feat: restore ACP server implementation from PR #949 (#1254)
Restore the ACP editor-integration implementation that was present on the
original PR branch but did not actually land in main.

Includes:
- acp_adapter/ server, session manager, event bridge, auth, permissions,
  and tool helpers
- hermes acp subcommand and hermes-acp entry point
- hermes-acp curated toolset
- ACP registry manifest, setup guide, and ACP test suite
- jupyter-live-kernel data science skill from the original branch

Also updates the revived ACP code for current main by:
- resolving runtime providers through the modern shared provider router
- binding ACP sessions to per-session cwd task overrides
- tracking duplicate same-name tool calls with FIFO IDs
- restoring terminal approval callbacks after prompts
- normalizing supporting docs/skill metadata

Validated with tests/acp and the full pytest suite (-n0).
2026-03-14 00:09:05 -07:00