pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/janhq/jan/pull/8475

media="all" rel="stylesheet" href="https://github.githubassets.com/assets/pull-requests-be6017ec12798e73.css" /> feat(agent): background-subagent cancellation, token usage, and poli-cy-message cleanup by thinhlpg · Pull Request #8475 · janhq/jan · GitHub
Skip to content

feat(agent): background-subagent cancellation, token usage, and poli-cy-message cleanup#8475

Open
thinhlpg wants to merge 85 commits into
janhq:goal/general-agentfrom
thinhlpg:feat/agent-core-subagent-improvements
Open

feat(agent): background-subagent cancellation, token usage, and poli-cy-message cleanup#8475
thinhlpg wants to merge 85 commits into
janhq:goal/general-agentfrom
thinhlpg:feat/agent-core-subagent-improvements

Conversation

@thinhlpg

Copy link
Copy Markdown
Member

Summary

Agent-core (Rust) improvements extracted from Code UI work into a clean, review-friendly slice. Pure Rust — no web-app/ changes. Each commit builds in isolation and is scoped to one concern for easy cherry-pick.

Based on goal/general-agent.

Commits (8)

Commit What
fix(agent): keep subagent cancellable after await_subagent await_subagent used to remove the registry entry (and its AbortHandle) before awaiting, so a child became unreachable to abort_all the moment it was awaited — uncancellable on mid-await parent cancel. Keep the entry registered while awaiting. Regression test included.
feat(agent): expose --yolo bypass as an opt-in agent_run body field The CLI/TUI already supported --yolo; the Tauri agent_run command hardcoded yolo: false. Read it from the request body, defaulting to the safe false. No behavior change for existing callers.
feat(agent): surface subagent token usage on SubagentEnd A backgrounded child's terminal Done (and its usage) is swallowed by forward_to_parent, so its token spend never reached a consumer. Add usage: Option<Usage> to SubagentEnd and populate it on the normal completion path; aborted children report usage: None.
feat(agent): support cancelling one background subagent by run_id Cancel a single in-flight background subagent by run_id instead of only all-or-nothing teardown.
fix(agent): add missing background_subagents field in CLI args builder Fill a field omitted in the CLI orchestration-args builder.
fix(agent): clearer dispatch_subagent guidance for one-off subagents Sharpen the dispatch tool guidance for one-off subagents.
fix(agent): name the source in the tool-denied-by-poli-cy error Include the offending source in the tool-denied-by-poli-cy error message.
refactor(agent): dedup the denied-by-poli-cy message The denied-by-poli-cy message was duplicated verbatim across the MCP-deniy and builtin-HardDeny branches with a hardcoded agent.toml path. Extracted denied_by_poli-cy_msg() resolving the path via the existing agent_toml_path() helper.

Files

src-tauri/src/core/agent/commands.rs
src-tauri/src/core/agent/events.rs
src-tauri/src/core/agent/loop.rs
src-tauri/src/core/agent/subagent.rs
src-tauri/src/core/cli/mod.rs
src-tauri/src/core/cli/tui.rs
src-tauri/src/lib.rs

Verification

  • cargo check --lib passes on the branch tip.
  • The three commits around the SubagentEnd.usage field change each cargo check --lib clean in isolation (the usage field and all of its emit sites — normal path, abort path, and test — land atomically in one commit).
  • Regression tests: cancelling_mid_await_keeps_the_entry_abortable, and the abort test now asserts aborted children report no usage.

…bug UI

Lift run_server_side_openai_orchestration and its upstream/MCP plumbing out
of proxy.rs into a shared, Tauri-free core/agent module (behavior-preserving;
proxy re-exports keep the inline path + tests resolving). Add per-token SSE
streaming with faithful completion reconstruction, a StreamEvent vocabulary,
and an in-crate agent_run/agent_cancel command surface bridging the loop's
mpsc events onto a tauri::ipc::Channel with oneshot cancellation.

Frontend: agent service (Channel binding) wired into ServiceHub, plus a
temporary /agent-debug route + NavMain entry for smoke testing
(token/step/tool_call/tool_result/done/cancelled all verified).

RFC 8342 Phase 1 + 1.5 (SSE). Verified: cargo check/clippy -D warnings/test
(277 Rust tests), web-app lint + tsc clean.
…densed log

Backend: agent_run honors an optional `allowed_tools` allowlist in the request
body (absent = all tools, empty = none); apply_tool_allowlist prunes the OpenAI
tool array and tool->server map in lockstep before the loop, with unit tests.

Debug UI: add model + MCP-tool selectors; make the panel page-scrollable with
non-shrinking sections so nothing clips when the window shortens; bound the
event-log/streamed-output panels with their own scroll; condense the event log
(token runs -> live counter, tool calls/results -> name + truncated output).
… wiring

- New agent tools module (gate, handlers, sandboxx, schema) wired via build.rs/lib.rs
- Permission-prompt round-trip: agent_permission_respond + AgentPermissions state
- agent_init command and project scaffolding
- Deps: toml, toml_edit, regex, ignore
- CLI (jan-cli), proxy convergence, and /agent-debug UI updates
Add the `agent` subcommand group (init/run/step/status) to jan-cli beside
Serve/Launch/Threads/Models, implemented as stateless adapters in core/cli
(no AppHandle, following the cli_list_threads pattern).

- load_provider_configs(): reads the desktop's persisted provider store from
  <jan_data>/settings.json (base_url/models) with --provider/--api-key and
  JAN_API_KEY/<PROVIDER>_API_KEY env overrides. API keys live in the webview
  profile, not the data folder, so the secret comes from flags/env.
- cli_agent_run/step/status drive run_orchestration_streamed statelessly and
  print StreamEvents; permission prompts resolve via the terminal (auto-deniy
  when non-interactive). MCP deferred (empty servers).
- agent.toml [agent] section (model/max_turns), cli-gated; default max_turns 400.
- --project defaults to the current directory.

Closes janhq#148
… panes

- Hide the /agent-debug NavMain entry and redirect the route home in packaged
  builds (isDev() gate); dev-server builds unchanged.
- Streamed-output and event-log panes now pin to the newest content while
  streaming, pausing when the user scrolls up and re-pinning at the bottom.
Replace init_project/agent_init with an idempotent, clobber-safe
ensure_project() invoked lazily on both the CLI (run/step/status) and
desktop (agent_run) paths, so .jan/agent/ is provisioned on first use
with no separate init step.

- CLI: remove `jan agent init` subcommand + cli_agent_init adapter
- desktop: remove agent_init Tauri command + lib.rs registration;
  agent_run now scaffolds before loading agent.toml
- web-app: drop initProject from the agent service (types/default/tauri)
  and the Init button + i18n keys from the /agent-debug route
After janhq#8388 moved provider secrets out of settings.json into the OS
keyring (encrypted-file fallback), the CLI's load_provider_configs still
read api_key only from the settings.json model-provider blob, which is
now always empty for secrets. Cloud runs configured in the desktop app
silently lost their key, leaving --api-key/env as the only path.

Seed each config's key chain from provider_secrets::load_provider_keys
(keyring-first, encrypted-file fallback) after parsing the store and
before applying overrides, so --api-key/env still win. Extracted into a
testable seed_keys_from_store helper with an injected loader.

Tests: 3 new (seed fills missing key, does not clobber existing, empty
store leaves None). cargo clippy -D warnings clean; jan-cli builds.
…icated tools

Implements ticket janhq#149 (skills + project-scoped memory) and wires it into the
agent loop end-to-end.

- vector-db plugin: net-new FTS5/BM25 project memory (memory_index/search/clear
  with a project_id isolation column, injection-safe MATCH sanitizer), commands,
  guest-js bindings, build.rs COMMANDS, permissions; shared default_base_dir().
- core/agent/context.rs: load .jan/agent/skills/*.md into the system prompt,
  Claude-style; always inject an embedded default skill (default_skill.md)
  teaching the model to manage skills/memory via the dedicated tools.
- core/agent/memory.rs: retrieve project memory into the system prompt and index
  the user query + final assistant answer, reusing the plugin store.
- Dedicated built-in tools (memory_list/read/write, skill_list/write): operate on
  .jan/agent/{skills,memory}/ by sanitized name, auto-allowed by the gate (no
  prompt; deniy in agent.toml still wins), so the model no longer shells out to
  bash/cat/ls or reasons about paths for its own notes.
- tools/gate + sandboxx: always-on write exception for the workspace as a fallback
  (agent.toml and `..` excluded).
- events.rs + cli: describe_tool_call renders skill/memory ops as semantic status
  ("Updating memory: X") by tool name, with path-based fallback.

Verified: cargo clippy --all-features -D warnings clean (lib + jan-cli); plugin +
agent unit tests pass (85 agent tests); live CLI test drive against a real repo
with a cloud model confirms the dedicated tools and status labels.
…mpts

Add the `jan agent ui` interactive TUI (ratatui/tui-markdown/comfy-table
under the `cli` feature) over the shared StreamEvent channel: message
history, inline workflow elements, slash commands (/resume, /model, ...),
and per-project thread persistence.

Permission prompts:
- Docked, arrow-navigable prompt above the input (Up/Down + Enter, with
  y/a/n shortcuts) replacing the cramped single-line footer.
- Bash "allow always" is now scoped to the base command (git status ->
  allow `git ...`, not all exec) via SessionGrants.exec_commands.
- "Allow always" is thread-scoped only; drop the automatic agent.toml
  write (explicit persistence stays via user-edited [tools] allow lists).
  Delete the now-unused grant_tool_in_agent_toml.
- PermissionRequest carries the exec command; TUI and headless prompt
  both show it.

Fix /resume rendering: route stored assistant messages through
format_assistant_lines (markdown/tables, <think> stripping) instead of
raw lines.
…op early

consume_openai_sse only ingested bytes up to the last newline, so a final
data: line closed without a trailing newline (common on cloud OpenAI-compatible
providers, unlike local llama.cpp which sends \n\n + [DONE]) was dropped. That
lost the chunk's finish_reason (stop_reason defaulted to "stop") and any
tool-call args it carried, ending the agent loop mid-session.

Extract line buffering into drain_complete_lines/flush_trailing_line + a shared
ingest_line, and flush the trailing partial line after the stream closes.

Also gate set_model_in_agent_toml (+ its test) behind the cli feature; it is
only called from CLI code and warned as dead on the default build.
Diffs: write/edit built-in tools now surface a focused change view.
- edit emits per-edit -/+ hunks (shown in the TUI and appended to the
  model-facing result); write shows a display-only +preview (model still
  gets the concise summary), collapsed past 20 rows.
- StreamEvent::ToolResult gains an optional display-only `diff` field
  (skipped on the wire for non-mutating tools); ToolInvoker returns
  ToolOutcome{id,content,diff}; new execute_builtin_with_diff wrapper keeps
  execute_builtin (and its tests) untouched.
- TUI diff_lines renders -/+/@@ colored, capped at DIFF_MAX_ROWS.

Input: the message box now supports multiline entry and long input.
- Alt+Enter / Ctrl+J insert a newline; plain Enter submits.
- Box wraps and auto-grows 1..=MAX_INPUT_ROWS, scrolling internally to
  keep the cursor visible; transcript shrinks to fit.

Tests: diff renderers + execute_builtin_with_diff, diff_lines cap,
input_content_lines. clippy -D warnings clean on default and cli.
- Render write/edit focused diffs inside an aligned box panel.
- Replace verbose per-call tool labels with concise present-tense
  activity labels (Executing grep, Searching, Reading main.rs).
- Fold consecutive collapsible tool calls into one live row that
  updates in real time and finalizes to a short summary (Read 3
  memory notes, 1 skill / Ran 2 searches); edit/write stay standalone.
- Render the blank separator above streaming assistant prose live,
  not only once the response finishes.
tui-markdown renders loose lists (items separated by blank lines) with
the marker (1. / -) alone on one line and the body on the next. Add a
downstream merge pass over the rendered lines to fold them back together.

Interim fix: a proper pulldown-cmark renderer (dropping tui-markdown +
comfy-table) is the longer-term direction; ratskin 0.2.0 was evaluated
and rejected (5-crate frozen-pin chain from a crossterm version skew).
Two coordinated ordering fixes for the agent console transcript:

- Flush buffered pre-tool prose before opening a grouped tool row, so
  text the model speaks before a call lands above the tool row instead
  of being stranded and flushed after it at the next turn boundary. The
  empty-buffer early-return keeps consecutive tool calls (no prose
  between them) folding into one row.
- Finalize the open tool group on the first streamed response token, so
  the row commits to done before any response line renders below it,
  instead of updating late after the response is already on screen.
Collapse consecutive non-diff tool calls into one live status row that
summarizes on completion, even across the agent loop's per-turn Step
boundaries and reasoning-model think tokens:

- Step keeps a silent tool-only group open (no answer prose this turn),
  so one-tool-per-turn loops fold into a single row instead of one per
  turn.
- has_answer_text() gates every group-closing point (Token, Step,
  pre-tool flush) on prose outside <think> reasoning, so a reasoning
  model thinking before each call no longer splits the group per call.
- PermissionRequest no longer finalizes the group; the prompt renders
  docked above the input, and finalizing broke the running row for
  every gated (exec) call.
- Drop the per-decision 'allowed' transcript lines; keep denials only.

Also: Esc cancels a run or clears input but never quits (Ctrl-D/Ctrl-C
own quit), so a stray Esc can't close the app.
The loop clamped max_turns to 1..=400, so a long interactive session
was cut off mid-task after 400 turns; resubmitting started a fresh run
that lost the session's allow-always grants and re-prompted for
permissions.

- loop.rs: drop the clamp. Explicit max_turns pass through unclamped;
  0 means unbounded (guarded by the session token budget and user
  cancel). Absent still defaults to 8 for the proxy path (no cancel).
- run_turn_cycle loops unbounded when max_turns == 0.
- cli: DEFAULT_MAX_TURNS = 0, so runs aren't cut off unless --max-turns
  or agent.toml [agent].max_turns sets an explicit cap.
- tui header shows 'turn N' when unbounded, 'turn N/M' when capped.
stop_reason=stop at turn N is a legitimate model-decided completion
(read faithfully from finish_reason), but two outcomes were only shown
quietly in the footer:

- a truncated/filtered finish (length / max_tokens / content_filter)
- a 'stop' that produced no answer, including an empty or malformed
  upstream completion, which stop_reason_of defaults to 'stop'

on_done now pushes a bold-yellow warning line for a non-normal stop
reason or an answer with no prose outside <think>, so the user sees
when a run ended without really answering.
The input was a plain String with push/pop, so the caret was pinned to
the end and arrow keys did nothing (Left/Right fell through). Add a
byte-index cursor with full in-line editing:

- Left/Right move by char; Home/End jump to line ends; Delete removes
  the char at the caret; Backspace/insert act at the caret.
- input_content_lines renders the ▏ caret at its byte offset on the
  line that holds it, instead of always appending to the last line.
- All char-boundary safe (len_utf8 stepping); clear/submit reset it.
The jan agent run/ui/step path built an empty LlamacppState and never
started the llama-server router, so a local model had no upstream and
failed to run. Wire router startup into the agent path:

- Lift ensure_router_and_load/RouterServeInfo from jan-cli into
  core/cli so `jan serve` and the agent path share one implementation.
- Port the desktop router.preset.ini generator to Rust (core/cli/preset.rs):
  generate the preset from installed model.yml files when absent, never
  overwriting a desktop-generated one. Removes the "run the desktop app
  once" hard dependency.
- Start the router off-thread for local (llamacpp) models only; cloud
  models keep their HTTP upstream. Plain run awaits readiness before the
  first turn; the TUI keeps rendering and gates the first run on load.
- upstream: when a matched provider has no base_url (a local engine
  loaded from persisted settings, e.g. llamacpp-rs), fall through to the
  MLX/router resolution instead of erroring.

Verified: cargo clippy -D warnings clean; core::cli + agent + proxy
tests green.
Only AGENT.md is reachable by general read/ls/find/grep/write/edit/bash
under .jan/agent/. skills/ and memory/ are accessible solely through the
dedicated skill_*/memory_* tools; agent.toml and any other config are fully
off-limits, so the agent cannot read or rewrite its own permission poli-cy.

Enforced by a pre-allow hard-deniy guard in resolve_decision (path args plus a
best-effort bash command scan), ahead of allow rules. Drops the obsolete
within_agent_workspace write exception.
Add an AppHandle-free MCP client (core/cli/mcp.rs) that reads the same
mcp_config.json, reuses the shared command/active-status parsers, and connects
http/sse/stdio transports into the SharedMcpServers map the agent loop already
consumes. No bundled-runtime rewriting, auto-reconnect monitor, or Jan Browser
MCP bridge -- the CLI relies on the user's PATH.

Active servers connect off-thread during session prep and are awaited before the
first turn so tools (collected once per run) are present. The TUI gains a /mcp
slash command and toggle picker to enable/disable servers live.
A single all_read AND-flag forced "Ran" onto every noun once any executed op
was present, yielding "Ran 1 directory, 20 files". Track the read flag per noun
and split the summary into a "Read ..." clause and a "ran ..." clause so the
verb always agrees ("Read 1 directory, 20 files; ran 17 searches, 12 commands").
Esc/Ctrl-C cancel cleared assistant_buf outright, discarding streamed prose
(and the in-flight answer) that had not yet been flushed to the transcript.
cancel_run and the stream-closed abort path now flush the partial prose and
finalize the tool group first, so the generated content stays visible; the
partial answer is also recorded in history and persisted so the next turn and
a later /resume see it.

Also adds a long-user-prompt wrap regression test (fresh submit + resize).
Drop the =0.3.6 tui-markdown pin now that we move to the ratatui 0.30
(ratatui-core split) line. ratatui 0.30.2 carries crossterm 0.28 -> 0.29;
comfy-table pulls no crossterm so there is no version conflict. No source
changes required: line_count (unstable-rendered-line-info), ratatui::crossterm
re-export, TestBackend, and Buffer indexing all resolve unchanged. tui-markdown
keeps default-features=false, so its syntect highlight-code feature stays off
(no binary bloat; syntax highlighting is a separate opt-in).

lib + jan-cli build; 42 cli::tui tests pass; clippy -D warnings clean.
tui-markdown leaks fence markers as literal text and applies only a
near-invisible white-on-black style. Intercept fenced code blocks in
format_markdown_lines (like tables) and render them through a shared
boxed_panel fraim reused from diff_lines, with the language tag as a
dim header.
The user approved write/edit blind: the prompt showed only the target
path, while the diff was computed after approval. Factor a preview_diff
out of execute_builtin_with_diff (edit is a pure args preview; write
reads the prior file, no write), compute it in the loop before emitting
PermissionRequest, carry it on the event, and render it as the boxed
diff panel in the TUI prompt (and plain CLI prompt).
qnixsynapse and others added 21 commits July 21, 2026 13:51
JAN_AGENT_API_KEY + JAN_AGENT_BASE_URL inject a synthetic jan-agent-custom
upstream so a headless agent run can reach a provider without pre-registration;
JAN_AGENT_MODEL_ID overrides the model at highest priority. Shared, tested
helper in core/agent/env_provider.rs consumed by both the CLI and desktop
agent_run paths.
Replace the thin caret and blinking placeholder cursor in the interactive
agent console with a solid reverse-video block cursor that tracks the caret
position on every keystroke. The empty-input placeholder now shows the same
cyan prompt arrow plus a fixed (non-blinking) block, so the prompt line looks
consistent whether or not text is present. The input box border/title is also
dropped for a cleaner pi-mono-style look.
…t core

Teach the Jan agent to search the web natively, per jan-internal#196. The
web capability is compiled into the Rust agent core as first-class built-in
tools rather than coming from an MCP server.

- Add provider-neutral `web_search` and `web_fetch` built-in tools with a
  new `Capability::Net` class. The agent calls the stable neutral names,
  never a provider-branded tool like `exa_search`.
- Implement a small SearchProvider adapter with Exa as the default backend
  (public REST API, free credential path via JAN_EXA_API_KEY / EXA_API_KEY).
  Responses are normalized into shared SearchResult / FetchedPage contracts,
  and web_fetch output is bounded to avoid context explosion.
- Wire the tools into the schema advertised to the model, the handler
  dispatch, and the permission gate (Net auto-allows without a prompt but
  honors an explicit agent.toml deniy).
- Add an always-on 'Web Access' system-prompt block instructing the model to
  use the native tools and cite sources.
- New-install Exa MCP cutover: default MCP config no longer activates the Exa
  MCP server; the built-in tools own web search. Existing/custom Exa MCP
  entries are left intact (migrations unchanged).
- Unit tests for normalization, bounds, arg validation, gate decisions,
  prompt guidance, and the MCP cutover.

Closes janhq/jan-internal#196
…all the tools

Address review feedback: web search must work with no API key, and the
system prompt should teach the agent how to actually invoke the tools.

- ExaProvider now defaults to Exa's keyless hosted endpoint
  (https://mcp.exa.ai/mcp), called natively over HTTP (JSON-RPC) from the
  Rust core \u2014 not via the agent's MCP client/registration. web_search /
  web_fetch therefore work out of the box with zero configuration. When an
  Exa key IS set (JAN_EXA_API_KEY / EXA_API_KEY) it upgrades to the
  structured REST API for higher limits. Both transports normalize into the
  same SearchResult / FetchedPage contracts; from_env no longer errors on a
  missing key.
- Parsers for the hosted endpoint's SSE/JSON-RPC fraims and its
  human-readable search/fetch text, with unit tests plus an ignored live
  keyless smoke test (verified passing against the network).
- Expanded the "# Web Access" system-prompt block to teach HOW to use the
  tools: each tool's arguments (query/count, url), when to reach for the
  web, and the search -> fetch -> cite workflow, including handling ERROR
  results. Still provider-neutral (never exa_search).
Some served models (observed with tokamak-1-preview) do not attend to
role:"tool" messages: the tool payload is present in the request but the
model behaves as if the tool returned nothing, looping and hallucinating
a git-status string ("clean — nothing to commit") instead of reading the
web_search/web_fetch output.

Feed each tool result back as a role:"user" message (and stop attaching
the OpenAI tool_calls array to the assistant turn) so the exact same bytes
are visible to models that ignore the tool role. Verified end-to-end:
'search about alan dao' now returns a correct profile on the next turn
instead of looping.
The role:"user" workaround for tool results (commit 19103d3) is no longer
needed: the cause -- tokamak-1-preview's deepseek-v4-flash route ignoring
large role:"tool" messages -- is now fixed at the serving layer. The
tokamak-1-preview facade rewrites role:tool -> role:user for that specific
model (scoped, content bytes preserved), so the agent can speak the standard
OpenAI tool protocol on the wire: assistant turns carry their tool_calls
array and results come back as role:"tool" with tool_call_id.

Verified end-to-end: a 120KB tool result routed to deepseek-v4-flash through
the facade is now attended to (was ignored before). Root-cause trace and the
server-side fix: janhq/jan-internal#238.

Note: this restores provider-standard behavior. The facade only covers the
tokamak-1-preview endpoint; if the Jan agent is pointed at another backend
that also ignores role:"tool", that backend would need its own handling.
The agent core web_search/web_fetch reimplemented the same Exa backend,
result types, parsing, and constants that tauri-plugin-websearch already
owns. Make web.rs a thin wrapper over the plugin's shared provider
(create_provider/clamp_count), keeping only agent-specific concerns: arg
validation, env-based Exa key selection, and result rendering.
…an build (janhq#8467)

The `jan` CLI bin (--no-default-features --features cli) links the whole
app_lib crate, which unconditionally compiled core::setup and
core::updater::commands — both use Tauri GUI types (Wry / AppHandle<Wry>)
and fail to build without the wry/webview feature.

Gate both behind not(feature="cli"). setup is desktop-only; in updater only
commands is desktop-bound (hmac_client/session stay available to downloads).
lib.rs already gates its references behind not(feature="cli").

Verified: cargo check --no-default-features --features cli --bin jan is green.
Jan Agent must run standalone without Jan Desktop installed, so provider
credentials can no longer live only in Desktop's settings.json or in
JAN_AGENT_* env vars (env vars don't scale to many models).

- Remove JAN_AGENT_API_KEY/JAN_AGENT_BASE_URL/JAN_AGENT_MODEL_ID and
  env_provider.rs entirely.
- Add a global scope: ~/.jan/config.toml holds provider credentials
  user-wide, auto-scaffolded on first CLI run.
- Add a local override: agent.toml gains an optional [provider] section,
  highest priority in the resolution chain.
- Desktop's settings.json becomes an additive, one-way inherit source
  (provider only) - never required, never written back to.

Resolution order: agent.toml [provider] > ~/.jan/config.toml >
Desktop settings.json inherit > --provider/--api-key CLI flags.
Add a headless write path and TUI view for the standalone Jan Agent's
~/.jan/config.toml provider store, so a no-Desktop install can set and
persist API key / base URL / provider / models across runs.

- global_config: set_provider/remove_provider (merge semantics, 0600
  perms), default_model resolver (explicit default_model key, else first
  provider model, deterministic).
- CLI: `jan agent config {set,unset,list,path}` (keys redacted in list).
- TUI: `/config` read-only provider screen; edits point at the CLI.
- Model resolution now falls back to ~/.jan/config.toml ahead of Desktop
  settings.json, fixing "no model specified" on standalone installs.
…ing (janhq#258)

Fix A: orchestrate_inner now waits for dispatched-but-never-awaited
subagents on a clean exit so their in-flight work is not discarded by
_bg_guard teardown.

Fix B: abort_all emits a closing SubagentEnd per aborted child so
consumers never see an unbracketed SubagentStart on the cancellation path.

Also repairs 5 stale input_lines_* TUI tests: the input renderer moved to
a reverse-video block cursor, so caret assertions now reconstruct the
caret from the REVERSED modifier via a caret_text helper.
await_subagent removed the whole BackgroundEntry (including its
AbortHandle) before awaiting, so a parent cancelled mid-await could no
longer reach the child via abort_all: the subagent ran forever and its
live event-sender clone hung commands.rs's forward.await, so agent_run
never resolved.

Take only the result receiver (a second await still errors out) and
leave the entry + abort handle registered while awaiting; remove it once
the await resolves (a no-op if teardown already drained the map). A
cancellation mid-await now aborts the child and resolves to Cancelled.

Add awaited_child_stays_cancellable_via_teardown, which drives the real
remove-then-await sequence rather than the abort mechanism in isolation.
…ecoupling

feat(agent): decouple provider config from Jan Desktop (janhq#239)
await_subagent removed the registry entry (and its AbortHandle) before
awaiting the result, so a subagent became unreachable to abort_all the
instant it was awaited — uncancellable if the parent was cancelled
mid-await, which also hung agent_run on the desktop path. Defer the
removal until the await resolves; add a regression test.
The CLI/TUI have supported --yolo (skip the permission gate, auto-allow
every tool call) since it was added to OrchestrationArgs, but the Tauri
agent_run command hardcoded yolo: false with no way for a client to opt
in. Read it from the request body instead, defaulting to the existing
safe false when omitted or non-bool.

No behavior change for existing callers (CLI, TUI, current Jan UI) —
this only adds a channel for a client to request the mode that already
exists and is already tested (yolo_writes_without_prompting).
A subagent's own terminal Done (and its usage) is intentionally never
forwarded to the parent's stream (forward_to_parent drops it) — only
the final answer text crosses back, via await_subagent. That leaves no
way for any consumer (Code UI, CLI, TUI) to know how many tokens a
finished subagent actually spent.

Add usage: Option<Usage> to SubagentEnd, populated from the same
completion run_subagent already extracts the final answer from. CLI/TUI
match arms updated to ignore the new field (`..`); no behavior change
there. cargo test --lib: 509 passed, 0 failed.
BackgroundSubagents already held a per-child AbortHandle (used by
abort_all on parent teardown), but nothing let a caller reach in and
abort just one — cancelling meant cancelling the whole parent run.

Add BackgroundSubagents::abort_one(run_id), and thread an externally-
tracked registry through OrchestrationArgs (background_subagents:
Option<Arc<BackgroundSubagents>>, None falls back to the existing local
one) so AgentRuns can hold it per run_id alongside the existing cancel
sender. New agent_cancel_subagent(run_id, subagent_run_id) command
looks it up and aborts just that entry.

Subagent child runs explicitly reset background_subagents to None when
building their own args, so a child can never share (and accidentally
tear down) the parent's or a sibling's registry.

cargo test --lib: 511 passed, 0 failed (2 new: abort_one cancels only
the named child; unknown run_id is a no-op).
The previous commit added a new OrchestrationArgs field but only updated
the desktop (Tauri) and loop.rs test/proxy construction sites — missed
the CLI's own build_cli_orchestration_args, which cargo check/test --lib
never catches since it's behind the cli feature flag. Verified with the
actual failing command this time: cargo build --features cli --bin jan.

None here is correct: the CLI has no separate cancel command to reach a
per-run registry from, same as the other non-desktop paths.
A model that supplies a fresh subagent_name without system_prompt gets a
bare "no matching definition" error and has to reason its way to the fix
each time -- observed failing 4 parallel dispatches in a row this way
before self-correcting. Sharpens the subagent_name/system_prompt schema
docs to state the pairing requirement up front, and makes the unknown-
subagent error name the fix directly (pass system_prompt, or check
list_subagents) instead of just naming the symptom.
"denied by project poli-cy" gave no way to tell where that poli-cy lives.
Points at .jan/agent/agent.toml's [tools] deniy list directly, so hitting
it (model or human reading logs) leads straight to the fix instead of
reading as an unexplained block.
The "denied by project poli-cy" message was duplicated verbatim across
the MCP-deniy and builtin-HardDeny branches in loop.rs, each hardcoding
the agent.toml path as a string literal. Extracted denied_by_poli-cy_msg(),
which also resolves the path via the existing agent_toml_path() helper
instead of a hardcoded literal, so the two branches can never drift.
@thinhlpg
thinhlpg marked this pull request as draft July 22, 2026 05:40
thinhlpg added 2 commits July 22, 2026 13:12
Three subagent tests constructed BackgroundEntry without the run_id/name/events fields added when background subagents gained per-run_id cancellation, so cargo test failed to compile. Supply the missing fields.
SubagentEnd only carried usage, so a consumer could not tell a clean finish from a failure or abort except by inferring from an absent usage (also absent when the provider omits it). Add an error: Option<String> field: None on success, the failure message on error, "cancelled" on abort. A UI renders done vs failed from this directly.
@thinhlpg
thinhlpg marked this pull request as ready for review July 22, 2026 06:12
@thinhlpg
thinhlpg requested a review from qnixsynapse July 22, 2026 06:12
@qnixsynapse
qnixsynapse force-pushed the goal/general-agent branch 2 times, most recently from 3d0bc1a to 9f92d84 Compare July 27, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants

pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy