Reject fast built-in file tools (fs_* / ast_grep) in job__start per the
backgroundable-tools whitelist; clean up env-snapshot temp files on every
exit of run_process_job via a drop guard; bound the output-pump awaits and
abort them on the failure path; treat signal death (no exit code) as a
failure with a teaching message; bound job__collect's post-drain join with
a SIGKILL escalation so a TERM-ignoring process cannot hang collect after
a Ctrl-C teardown; document the unguarded SIGTERM pid-reuse window; give
the injected Background Jobs prompt section a fresh line on both sides;
extract the MCP server name with strip_prefix instead of replace.
Capacity-0 audit for jobs-disabled contexts: REPL displays have no
supervisor consumers (only Ctrl-C/exit cancel_recursive at
repl/mod.rs:460,473, kind-agnostic); session save/load does not persist
supervisor state (src/config/session.rs has no supervisor references) --
nothing to test for either.
Agents whose function pool includes job__* declarations get a Background
Jobs section teaching start/check/collect/cancel discipline and the
snapshot/no-persistence semantics. Presence of the declarations doubles
as the jobs_enabled predicate, so a context with function calling off or
max_concurrent_jobs 0 sees no job prompt text.
Detached tokio::process runner with a frozen JobEnvSnapshot (env-derived
bin dirs, vault-interpolated agent envs, COYOTE_TOOL_TIMEOUT resolved at
start), process_group(0) with pgid-guarded SIGTERM/SIGKILL escalation,
capture-only ring-buffer telemetry, and LLM_OUTPUT read after wait().
MCP jobs snapshot a single-entry McpRuntime holding only the validated
server and render through the same free fn as the foreground path.
job__start enforces its gates synchronously before any spawn:
jobs_enabled, the backgroundable whitelist with directionality teaching
errors, the per-request declared-names stash captured in
before_chat_completion, then capacity (lazy supervisor get-or-init in
plain sessions). job__check/list read the shared JobState cell without
consuming; job__collect blocks with the escalation early-out and applies
a tail-biased char-boundary cap plus optional tail_lines; job__cancel
kills the group with a 5s grace.
Job declarations are injected iff jobs are enabled at agent init, the
plain-session function-init sites, and the exit_agent rebuild; job__ is
carved out of enabled_tools filtering and excluded from
concrete_tool_names so REPL toggles cannot grant or revoke it.
When max_chars landed inside a multi-byte UTF-8 character of the
serialized output, s.get(..max_chars) returned None and the code fell
back to the FULL untruncated string while still prepending the
truncation marker — the "truncated" output actually grew. The cut is
now floored to the previous char boundary so the prefix is always a
valid, genuinely truncated slice.
agent__check on a finished agent delegated to agent__collect, which
returned the full (unbounded) result and consumed the handle. That
contradicted the tool's own docs and broke the check-then-collect
pattern: a second collect on the same id failed.
check now reports { status: finished } with a pointer to
agent__collect and leaves the handle registered; collect is the single
retrieval verb. The tool description and prompt table are updated to
stop promising that check returns the result.
The turn-end guardrail only counted still-running agents, so an agent
that finished before the turn ended was invisible: its uncollected
result was silently dropped. Jobs were never counted at all.
The guardrail now enumerates every registered task (running and
finished, agents and jobs) via Supervisor::list_tasks and renders a
kind-aware prompt with two sections: still-running tasks to reclaim,
and completed-but-uncollected tasks with the exact collect command.
At the force-terminate cap, finished-but-uncollected handles are
explicitly discarded with a warning naming the lost ids, so the
guardrail cannot loop forever on handles nobody will collect.
Implements T1 of plans/background-jobs-design.md (§6, R7/R8/R9):
- Supervisor.handles is now HashMap<String, TaskHandle> where
TaskHandle = Agent(AgentHandle) | Job(JobHandle); agent-facing
accessors (active_count, effective_active_count, is_finished, take,
inbox, abort_signal_for, list_agents) match only Agent variants,
preserving all existing external behavior byte-for-byte.
- New JobHandle/JobState/JobStatus/JobResult types with pgid-guarded
process-group kill discipline: Drop and cancel_all/cancel_recursive
kill the group only while state.pgid is still set (pid-reuse guard),
via libc::killpg on unix and JoinHandle::abort elsewhere.
- Per-kind job capacity: Supervisor carries max_concurrent_jobs
(builder-set, default 0); job registration rejects at capacity.
- Cross-kind teaching errors at the four agent-lookup miss sites
(agent__check/collect/cancel/send_message) when the id is a
registered job or job_-prefixed; genuinely-unknown ids keep their
existing messages.
- Supervisor init condition is now can_spawn_agents || jobs_enabled in
use_agent and both child-agent spawn paths, with agent capacity 0 in
jobs-only contexts; use_agent cancels the old supervisor recursively
before replacing it.
- max_concurrent_jobs config plumbing: global Config field, AgentConfig
override + accessor, all four AppConfig touch points including the
COYOTE_MAX_CONCURRENT_JOBS env override; shared
effective_max_concurrent_jobs/jobs_enabled predicates
(agent override -> global -> default 5; 0 disables).
- Stage dependency-free RingBuf (64 KiB default) in src/function/jobs.rs
for the upcoming job output pump.
- New sanctioned dependency: libc 0.2 under cfg(unix).
Completes the .prompt/.temp-role split: --prompt set an ad-hoc system
role, which is what .temp-role now means everywhere. The --prompt name
is left unbound so a future one-shot MCP prompt flag can take it with
properly designed non-interactive semantics. use_prompt follows the
rename as use_temp_role.
BREAKING CHANGE: invocations using --prompt <text> must switch to
--temp-role <text>; clap rejects the old flag loudly.
Windows review findings on the bundle provenance code:
- clones now pin core.autocrlf=false and core.eol=lf so recorded sha256
values reflect repository bytes, not the machine's git config (autocrlf
on Windows previously made every text file a false conflict on update),
plus core.longpaths=true for deep bundle trees
- is_safe_relative_path additionally rejects NTFS alternate data stream
colons, reserved device names (con, nul, COM1..), and trailing dots or
spaces; such names never come from a valid checkout and previously
desynced or failed on Windows
- file ownership dedupe compares paths case-insensitively on Windows and
macOS where case variants denote one physical file (uninstalling one
bundle could previously delete another bundle's file)
- a failed git clone no longer leaks its partial tree in the temp dir,
and temp cleanup failures are logged instead of swallowed
- recording a bundle file outside the config dir (asset dir override)
now warns instead of silently producing an undeletable record
Windows review findings: reserved device names (con, nul, COM1..) and
trailing dots in server names break or desync directory creation, so
sanitize_server now escapes reserved stems, strips trailing dots, and
caps length at 64 chars. Spill writes go through a temp file + rename
so a visible file is always complete (closes a cross-process partial
read race), and eviction protection compares content-hashed file names
instead of full paths. Also drops a duplicated cfg attribute.
The non-interactive bail under test only triggers without a TTY; from a
terminal the code correctly opens the interactive selector instead, so
the test hung or failed depending on input. Same guard as the three
sibling non-interactive tests.
MCP prompts are live, server-owned catalog entries, not managed assets;
list_prompt_assets/prompt_asset_rows implied otherwise and are now
list_mcp_prompts/mcp_prompt_rows. The .list completer was also missing
the prompts kind that the usage string and unknown-kind error advertise.
The unified install parser accepts flags in any position, so the
first-argument completion list now offers all four flags instead of
only --git-host and --help.
Update the README's MCP feature entry to cover the full capability trio
(tools, resources, prompts): the capability-gated mcp_read/mcp_prompt
meta-tools, bounded results and blob spilling, and the .prompt REPL
command with staged tab-completion and .list prompts.
Per plans/mcp-resources-prompts-design.md section 10.T9.
Route CallToolResult content through the render.rs content policy per
plans/mcp-resources-prompts-design.md §6 (T8): oversized text sliced at
TEXT_MAX_BYTES_CLAMP with a self-explaining truncation note, image/audio/
embedded blob content spilled (or inlined when UTF-8-clean) instead of
shipping base64 into model context, and structuredContent subject to the
same ceiling. Clamp server-controlled uri/mime metadata strings to the new
METADATA_MAX_BYTES bound in both the read and tool-result paths, sanitize
the terminal rendering of MCP dispatch errors while keeping raw text in
the tool_call_error payload, and surface resource audience annotations in
both mcp_search results and mcp_read metadata via the catalog.
Emit an mcp_prompt_<server> declaration for servers advertising the
prompts capability, execute prompts via McpRuntime::prompt on both tool
dispatch chains, and return the flattened prompt text as the tool
result. Sanitize server-controlled prompt names, descriptions, and
argument names before terminal rendering, and attribute the .prompt
argument inquire label to its server and prompt.
Per plans/mcp-resources-prompts-design.md §5.2 (T7).
Implements plans/mcp-resources-prompts-design.md §5.1/§5.4 (T6):
- .prompt <server> <name> [key=value ...] fetches an MCP prompt and
submits the result as chat input via Input::from_str + ask(), never
through REPL line parsing; GetPromptResult messages are flattened
into one user-role block with unconditional [user]/[assistant] labels
- missing required prompt arguments are collected interactively
- .list prompts renders server/name/description/args via the unified
catalog (CatalogItem gains an arguments field), degrading per server
- staged live tab-completion: enabled+running+prompts-capable servers
(no RPC), then live prompt names, then key= argument suggestions with
(required) markers; 2s timeout per RPC, all errors degrade to silent
empty suggestions, ctx read guard dropped before blocking
- the enabled-server alias expansion is factored into a shared helper
used by both tool-scope rebuild and completion
- BREAKING: the former .prompt <text> temp-role builtin is renamed to
.temp-role <text> (behavior preserved); .prompt now belongs to MCP
prompts, and a user macro named prompt or temp-role is shadowed
Implements plans/mcp-resources-prompts-design.md section 4.3 (T4):
mcp_read_<server> declaration and handler wired to render.rs, RFC 6570
Level-1-only URI template expansion, defensive ResourceContents parsing,
per-item text paging with pattern filtering, blob spill metadata, an
overall 204800-byte multi-content ceiling, dispatch wiring on both
eval chains, and a render_text paging-stall guard.
Per-server McpServerFeatures (tools fail-open, resources/prompts
fail-closed) now drive which meta-functions are declared, with
gated_meta_function_prefixes as the single gating seam; read/prompt
declarations land together with their handlers. The server-enablement
sentinel keys on the always-emitted search name so resources-only
servers survive role filtering.
Implements plans/mcp-resources-prompts-design.md §4.4/D7 (T5).
Single content-policy module for MCP resource and tool content, per
plans/mcp-resources-prompts-design.md §4.5 (T3):
- render_text: UTF-8-boundary-safe paging with clamped max_bytes and
grep-style fancy-regex line filtering (2 lines of context, 1-based
line-number prefixes, merged hunks); offsets walk the filtered stream.
- render_blob/render_blob_at: streaming base64 decode with a 50 MiB
ceiling, UTF-8 sniff, sha256-named 0600 spill files under a sanitized
server dir with a fixed mime->ext allowlist, and best-effort
oldest-first eviction bounding the spill tree at 512 MiB.
Not yet wired to call sites; module carries #![allow(dead_code)] until
the read/prompt surfaces land.
Implements T1 of plans/mcp-resources-prompts-design.md (§4.1, §4.6):
- Replace list_tools(None) with cursor-following list_all_tools() at the
three call sites (start_server catalog build, catalog_items, describe)
so paginating servers no longer silently lose tools past page one.
- Add MCP_READ/MCP_PROMPT prefix constants (declared nowhere yet; wired
in T4/T7) plus centralized helpers MCP_META_FUNCTION_PREFIXES,
is_mcp_meta_function, and mcp_meta_function_names.
- Mechanically replace every hand-rolled 3-prefix starts_with triple
(partition in eval_tool_calls, 3 exclusion triples in
select_enabled_functions, 3 inclusion triples + per-server name
construction in select_enabled_mcp_servers) with the helpers,
preserving the existing lax starts_with matching semantics and the
mcp_invoke_* enablement sentinel (sentinel moves to search in T5).
- Behavior-neutral: dispatch chains keep their 3 arms, emission stays
at exactly 3 meta-functions per server, existing tests unmodified.
- Add unit tests: helper classification, prefix-soundness property,
lax-matching pin, ordered candidate-name construction.
The unified install parser accepts flags in any position, so the
first-argument completion list now offers all four flags instead of
only --git-host and --help.