- A skill the configured registry does not know is looked up on the default
hub, without the registry's key
- Installers may record a display_description shown in place of SKILL.md's;
the agent still reads SKILL.md
- New skills are stamped with installed_at
Co-authored-by: cowagent <cow@cowagent.ai>
Add skill_hub_url and skill_hub_api_key. When set, bare-name installs,
skill search and the remote listing go to that registry, with the key
sent as a bearer token to it only; provider installs and the default
setup are unchanged. Display fields returned by the registry, or passed
as install meta from the console, are recorded in skills_config.json.
Sensitive values are masked when remote config is logged.
Co-authored-by: cowagent <cow@cowagent.ai>
Saving agent_max_context_tokens / agent_max_steps used to evict every
cached agent, so the next turn rebuilt from the short restore window and
lost the working context and prompt cache. Update the limits in place
and let the regular trim enforce a smaller budget. /config set now
applies them immediately too.
Co-authored-by: cowagent <cow@cowagent.ai>
A line longer than the chunk budget is hard-split into several pieces that
all share (start_line, end_line). The chunk id is derived from the line range
alone, so every piece of that line gets the same id and
save_chunks_batch's "ON CONFLICT(id) DO UPDATE" keeps only the last one —
the rest silently leave the index. Measured with a 950-char line and a
200-char budget: 5 pieces stored as 1 row, 800 of 950 chars dropped.
Carry a `part` ordinal out of the chunker so the pieces get distinct ids.
part == 0 is used for every ordinary chunk, so its id is unchanged and no
needless re-sync is triggered.
CHUNKER_VERSION 2 -> 3: the boundaries are unchanged, but an index that
already dropped the colliding pieces must still be flagged stale. Chunked
files are only re-split when their content hash changes, so a chunker-only
change would otherwise never revisit them and the loss would stay forever.
The /memory status hint is reworded to stay accurate for a bump that does
not alter how text is split.
- conftest points HOME at a temp dir before any test module is imported,
so ~/.cow/.env and backup restore targets never touch real data
- merge common/docx_text.py and common/spreadsheet_text.py into common/office_text.py
- drop the repeated utf-8-sig comments
Co-authored-by: cowagent <cow@cowagent.ai>
Three places replayed a log tail with `readlines()` and then sliced the result:
the web logs view (last 200 lines), the chat `cow logs` command (up to 50) and
`cow logs` on the CLI (50). `run.log` is appended to for as long as CowAgent
runs and is never rotated, so all three read every line ever written to display
a handful of them -- and the chat command does it inside the bot process, where
a long-lived instance gets OOM-killed by its own help command.
Add `common.utils.tail_lines`, which walks the file backwards in 8 KiB blocks
and stops once enough lines have been collected, and use it from all three:
- `channel/web/api/logs.py` for the initial replay,
- `plugins/cow_cli/cow_cli.py` for `cow logs` in chat,
- `cli/commands/process.py::_print_last_lines`, imported on use like the CLI's
other project imports (the CLI ships as its own package and only puts the
project root on `sys.path` when it needs something from it).
Measured on a 20000-line / 1.0 MB run.log: the three views went from reading
100% of the file to 1.6%, 0.8% and 0.8%, with no `readlines()` call left.
Also drops the two pre-existing pyflakes findings in `cli/commands/process.py`,
since the CI lint gate requires the files a PR touches to be F-clean: the dead
`flag` assignment in `_kill_pid` (the `/F` flag is appended directly) and the
`f` prefix on a string with no placeholders.
Replace the per-module sibling-file-and-rename copies with
common/atomic_write.py, which uses a unique dot-prefixed temp name,
fsyncs, and keeps the target's permissions.
Co-authored-by: cowagent <cow@cowagent.ai>
The four in-place writers in this plugin truncated a live config file before
writing it, so a crash, a kill, or a full disk partway through left it
half-written. For config.json that is fatal on the next start: load_config
re-raises on a parse error unless the packaged desktop build is running, and
the desktop-only recovery path exists precisely because of "a bad write
during a previous update". For skills_config.json, uninstall swallowed the
exception and still reported success while every record was gone.
Serialise into a dot-prefixed sibling and os.replace it into place, matching
what agent/skills/manager.py and channel/web/core/_common.py already do for
these same two files.
Read config.json with utf-8-sig so a BOM (e.g. saved by Windows Notepad)
no longer breaks config/model updates from the web console; write-back
stays plain utf-8 to strip the BOM on next save.
Gate the /poll and /api/scheduler/runs background pollers behind auth so
a password-protected console no longer spams "401 Unauthorized" before
the user logs in.
Co-authored-by: cowagent <cow@cowagent.ai>
Route scheduled delivery through the live ChannelManager instance (by instance_id, then the trusted recipient directory) instead of a bare create_channel() singleton, which fixed WeChat tasks stuck as "not ready" after restart in multi-agent mode (#3120). A shared common/channel_registry avoids the __main__-vs-app duplicate module hiding the manager from the scheduler.
Consolidate per-Agent tasks.json into a single global store; each task carries agent_id, and IM-task ownership is derived live from its delivery instance's binding (effective_task_agent_id) so re-binding a channel moves the task with no rewrite. Legacy per-Agent stores migrate once at startup; recipients.json moves under scheduler/.
Co-authored-by: cowagent <cow@cowagent.ai>
On top of the merged #3087 + #3079, adjust the integration so the model-derived
context budget is the default while staying backward compatible.
- config.py / agent_initializer.py: agent_max_context_tokens is restored as an
optional manual override (default 0 = derive from the model). Resolution
order for the input budget is catalog context_window -> explicit
agent_max_context_tokens (if > 0) -> name heuristics. This avoids the hard
removal being a breaking change for existing configs and integrations.
- web_channel.py / cloud_client.py / cow_cli.py: re-expose the key in the
config read/write/report paths.
- Desktop + web console: restore the "Max Context Tokens" field, relabelled as
an optional override (empty/0 = auto), with zh / zh-Hant / en strings.
- agent.py get_context_usage(): note that the tools slice is an UPPER BOUND —
with on-demand tool retrieval the live injected set can be smaller.
- .gitignore: ignore .obsidian/ to prevent re-committing the editor workspace.
Co-authored-by: cowagent <cow@cowagent.ai>
Merge chimyves:feat/model-catalog-v2 into the context-budget integration
branch. Conflicts (config.py, web_channel.py, and 3 desktop TS files) were
the "same region edited upstream" kind; resolved by keeping master's newer
logic (agent_bindings, optional custom-provider key, chat_fallback action,
key-dirty handling) and layering the catalog changes on top.
The optional-override treatment of agent_max_context_tokens is applied in a
follow-up commit.
The heading-aware chunker (previous commit) only re-splits a file when its
content hash changes, so upgrading the chunker alone never refreshes old
chunk boundaries. There was also no on-disk record of which chunking strategy
built an index, so nothing could detect the staleness.
Add lightweight, opt-in version tracking (no schema migration):
- TextChunker.CHUNKER_VERSION: bumped when the chunking strategy changes.
- _meta KV helpers on MemoryStorage (get_meta/set_meta); _meta already exists
(used for the trigram backfill flag), so no new table.
- MemoryManager.sync() stamps chunker_version when it (re)builds from an EMPTY
index (fresh install, or right after rebuild-index clears it). A non-empty
index may still hold chunks from an older algorithm, so it is left unstamped.
- detect_chunker_version(storage) in embedding/state.py mirrors
detect_index_dim; returns None when an index predates version tracking
(i.e. "old / unknown chunker").
- /memory status now shows a hint when the recorded version is missing or
stale, suggesting `/memory rebuild-index`. This is only a hint — rebuilding
re-embeds everything and is costly, so it stays the user's call.
Unstamped (legacy) and current indexes are indistinguishable from chunk data
alone, so the flag is the single source of truth: "no flag" == "built by an
older or unknown strategy".
Tests: tests/test_chunker_version.py (meta round-trip, detect on
stamped/unstamped, sync stamps only from an empty index).
Wires his router into the ambient identity: _identity_for now reads the
agent produce() already routed, instead of leaving agent_id unset. Without
that the bridge served the bound Agent while every workspace path still
resolved to the default one.
Routing an unavailable agent now raises instead of quietly substituting
the default, and IM replies that the assistant is disabled. Serving the
substitution would answer the user with another Agent's persona, memory
and workspace with nothing in the conversation revealing it, so his test
pinning the fallback is inverted rather than kept.
Steer keys were still unscoped while cancel keys were namespaced, so
/steer would have crossed agents sharing a session id. Both now go
through AgentBridge.scoped_session_key, including the session-delete and
cow_cli paths his branch predates.
Restores the agent_bindings config key and rebuilds the router when it
changes, matching the registry caching fix.
Co-authored-by: Cursor <cursoragent@cursor.com>
Resolved against the ambient-identity foundation: initialize_agent now
takes agent_id as an override on top of the routed identity rather than
as the only way to select a workspace, and the .env refresh loop keeps
his per-agent walk but goes through state_dir for the path.
Cancel tokens are namespaced per agent; steer tokens are not yet, which
is the same bug and lands with the state isolation work.
Co-authored-by: Cursor <cursoragent@cursor.com>
Every consumer read agent_workspace itself and joined its own subpaths,
so 42 call sites each hardcoded both where the workspace is and how it
is laid out. Adding a second agent, or a per-user layer under it, would
mean editing all of them.
1. Embedding model: support custom provider
- Add "custom" entry to EMBEDDING_VENDORS with supports_dim_param=False
- Parse custom:<id> credentials and model fallback in agent_initializer
- Expand custom_providers as custom:<id> entries in Web UI dropdown
2. Vision model: support custom provider
- Add custom:<id> routing in _route_by_provider_id
- Add _build_custom_provider reading credentials from custom_providers
- Expand custom_providers in Web UI dropdown, add validation in _set_vision
3. Fix memory_get Windows path validation bug!
- str.startswith(path+'/') always False on Windows due to backslashes, So All Users can not use "memory_get" tool in Windows.
- Use os.path.realpath + os.sep, consistent with MemoryService
4. Fix historical needsModel:false bug preventing embedding provider switch
- Change embedding needsModel to true in console.js
- Support custom:<id> resolution in cow_cli /memory status, also for adding custom provider support
Closes#2908, Closes#2880
Add embedding_provider config knob with native support for
openai / dashscope / doubao / zhipu / linkai, plus an in-chat
/memory status and /memory rebuild-index workflow for switching
vendors safely.