105 Commits
Author SHA1 Message Date
zhayujie 4bc020d51a Merge pull request #3709 from rudycelekli/fix/cow50-cli-daemon-detach-stdin-20261012
fix(cli): detach standard input when starting the background service
2026-10-18 17:24:33 +08:00
zhayujieandcowagent 8e3e1177eb fix: keep the channel list annotation compatible with Python 3.7
Co-authored-by: cowagent <cow@cowagent.ai>
2026-10-18 16:41:35 +08:00
zhayujie 2c0c6c4e6f Merge remote-tracking branch 'origin/master' into pr-3709 2026-10-18 16:40:25 +08:00
Rudy Celekli 0b0b4cd810 fix: preserve executable skill permissions on restore 2026-10-17 10:54:13 -04:00
Rudy Celekli 3526388799 fix(cli): preserve stdin for mixed terminal channels
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-16 09:32:05 -04:00
zhayujie 2046e9a2da Merge pull request #3687 from rudycelekli/fix/cow50-cli-pid-permission-alive-20261012
fix(cli): retain process IDs when signal permission is denied
2026-10-16 16:36:58 +08:00
zhayujie f1ee381b9d Merge pull request #3753 from rudycelekli/fix/cow-net-new-skill-replacement-rollback-20261013
fix(skills): preserve installed skills during replacement failures
2026-10-16 16:35:09 +08:00
zhayujieandcowagent 80418d5da6 fix: report permission errors from cow stop instead of claiming success
Co-authored-by: cowagent <cow@cowagent.ai>
2026-10-16 16:01:40 +08:00
zhayujie 7a9a57541c Merge remote-tracking branch 'origin/master' into pr-3687 2026-10-16 15:56:55 +08:00
zhayujie 079b9a3592 Merge pull request #3685 from rudycelekli/fix/cow50-skill-bom-20261012
fix(skills): support UTF-8 BOM in skills and configuration
2026-10-16 15:55:03 +08:00
zhayujie dc6596aa89 Merge remote-tracking branch 'origin/master' into pr-3753 2026-10-16 12:26:33 +08:00
zhayujieandcowagent 395892b7ff feat: fall back to the default hub for unknown skills and keep display descriptions
- 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>
2026-10-14 18:27:20 +08:00
zhayujieandcowagent f72fae3371 feat: let a configured skill registry serve installs by name
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>
2026-10-14 16:07:25 +08:00
Rudy Celekli 254868efd4 fix(skills): preserve installed skills during replacement failures
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-13 11:19:48 -04:00
chenhan1 3d8d51765c fix: do not require sudo to run cow install-browser
The Linux branch hard-coded ["sudo", "apt-get", ...] for the CJK font, so on a
system without a `sudo` binary (e.g. the official Docker image, where we are
already root) the call raised an unhandled FileNotFoundError that aborted the
whole installer before Chromium was downloaded.

Add an _apt_install helper that only escalates when not root and sudo is
actually available, and returns non-zero otherwise so the caller falls back to
its existing "install it manually" hint. Also drop an unused `as e` binding so
the file passes the repo's changed-files F-class ruff gate. Covered by
tests/test_cli_install_browser_sudo.py.
2026-10-13 14:15:31 +08:00
Rudy Celekli f3ea386b32 fix(cli): detach standard input when starting the background service 2026-10-12 08:58:46 -04:00
Rudy Celekli 60270d48d1 fix(cli): preserve BOM-prefixed skill registrations during install 2026-10-12 08:54:38 -04:00
Rudy Celekli f0c2923a37 fix(cli): retain process IDs when signal permission is denied 2026-10-12 08:48:43 -04:00
zkjqdandcowagent 9596275ad8 feat: merge origin/master into feat/knowledge-document-edit
Co-authored-by: cowagent <cow@cowagent.ai>
2026-10-12 10:45:27 +08:00
zhayujie 7ae2142f0a Merge remote-tracking branch 'origin/master' into pr-3612
# Conflicts:
#	cli/commands/knowledge.py
2026-10-09 18:43:05 +08:00
zhayujie 9983ae9696 Merge pull request #3607 from rudycelekli/fix/cow-foreground-directory-20261008
fix(cli): launch foreground from the project directory
2026-10-09 18:41:28 +08:00
zhayujie b6b18a62d7 Merge pull request #3599 from rudycelekli/fix/cow-cli-config-duplicate-keys-20261008
fix(cli): share canonical duplicate configuration parsing
2026-10-09 18:40:27 +08:00
zhayujie de81747504 Merge pull request #3598 from rudycelekli/fix/cow-cli-yaml-frontmatter-20261007
fix(skills): parse CLI frontmatter with the existing YAML loader
2026-10-09 18:40:15 +08:00
zhayujie d088fcdb64 Merge pull request #3597 from rudycelekli/fix/cow-github-fallback-layout-20261007
fix(skills): preserve Contents API fallback directories
2026-10-09 18:40:04 +08:00
zhayujie 2b880d0fc2 Merge pull request #3595 from rudycelekli/fix/cow-knowledge-tree-pages-20261007
fix(cli): show root and nested knowledge documents
2026-10-09 18:39:40 +08:00
zhayujie 8ff0d10414 Merge pull request #3594 from rudycelekli/fix/cow-local-copy-overlap-20261007
fix(skills): refuse overlapping local install copies
2026-10-09 18:39:29 +08:00
Rudy Celekli 3387274381 fix: keep admitted Markdown extension cases visible
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-08 06:53:13 -04:00
Rudy Celekli 5ac8b2b957 fix(cli): launch foreground from the project directory
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-08 03:06:33 -04:00
Rudy Celekli 835df0f987 fix(cli): preserve canonical config lists in backup and restore
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-07 23:09:34 -04:00
Rudy Celekli 9ff24279bc fix(cli): use canonical YAML skill frontmatter parser
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-07 20:13:05 -04:00
Rudy Celekli 6e00f8a6d2 fix(skills): preserve Contents API fallback directories
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-07 19:55:06 -04:00
Rudy Celekli 18785821d0 fix(cli): bound canonical parent aliases in knowledge trees
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-07 18:06:55 -04:00
Rudy Celekli 13de0be1a4 fix(cli): show root and nested knowledge documents
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-07 17:57:45 -04:00
Rudy Celekli 9541189ab3 fix(skills): refuse overlapping local install copies
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-07 17:18:58 -04:00
Rudy Celekli 75dd350b8b fix(backup): use the effective registry default agent
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-07 16:13:28 -04:00
zhayujie 44e2c0f898 Merge pull request #3349 from c020627/fix-web-logs-tail-bounded-read
refactor(logs): share one bounded tail reader across the log views
2026-10-01 18:44:47 +08:00
zhayujieandcowagent 43f869a5f3 fix: trim the tail reader docstring and comments
Co-authored-by: cowagent <cow@cowagent.ai>
2026-10-01 15:56:14 +08:00
c020627 2fd55b34f3 fix: read user-edited config files as utf-8-sig (BOM tolerance)
Windows Notepad / PowerShell write UTF-8 with a BOM by default, and the JSON /
txt files CowAgent asks users to edit by hand are exactly the ones that get
re-saved that way. Plain `utf-8` turns the leading BOM into a `\ufeff`
character, and what happens next depends on the reader:

- `plugins/*/config.json` (godcmd's is created on first run for the operator
  to fill in): `json.load` raises "Unexpected UTF-8 BOM", the plugin's
  `__init__` fails, and `PluginManager.activate_plugins` answers a failed init
  by disabling the plugin and **persisting** `enabled=false` -- so it stayed
  off across restarts even after the file was put right.
- `banwords.txt`: the first line is read as `\ufeffword`, `str.strip()` does
  not remove U+FEFF (it is not whitespace), so the word is registered with the
  BOM glued to it and can never match. No error -- the first word of the list
  is silently unfiltered.
- `skills_config.json`: the read helper swallows the error and answers `{}`,
  and `cow skill list` rebuilds the file assuming `enabled: True` -- a skill
  the user had turned off comes back on, silently.
- `config.json` in `cow backup`: same swallow, so every caller treats the
  install as unconfigured and `cow backup` archives `~/cow` instead of the
  configured `agent_workspace`. `restore` hits the same read on the copy inside
  the archive, where it is not caught and aborts the restore.
- `mcp.json`: `_read_mcp_file` wraps it as `McpConfigError("mcp.json is not
  valid JSON: ...")`, which names the JSON rather than the BOM, so the file
  looks fine in an editor.

Apply the convention `agent/admin.py` already documents -- "utf-8-sig tolerates
a UTF-8 BOM (e.g. config.json edited with Windows Notepad / PowerShell)" -- at
every one of those read sites, replacing what were five separate PRs (#3419,
#3421, #3422, #3423) with one change.

`Plugin.load_config` also restores the `if os.path.exists(plugin_config_path)`
check inside the `if not plugin_conf` block that assigns `plugin_config_path`:
pulled out of it, the check ran on a name that was never assigned whenever
`pconf()` already returned a config, i.e. for every plugin with an entry in
`plugins/config.json`, and `load_config` raised `UnboundLocalError` -- the same
"plugin gets disabled" outcome the change was meant to avoid.
`tests/test_plugin_config_defaults.py` covers that case; it fails on the
previous revision with `UnboundLocalError: cannot access local variable
'plugin_config_path' where it is not associated with a value`.
2026-10-01 15:26:12 +08:00
c020627 e88c5a734a refactor(logs): share one bounded tail reader across the log views
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.
2026-10-01 15:17:30 +08:00
zkjqdandcowagent 34908b020c feat: merge origin/master into feat/knowledge-document-edit
Resolve the import conflicts in channel/web/api/{knowledge,skills}.py:
master moved _multipart_lists into channel.web.core._common, so the local
copy in skills.py is dropped. SkillCreateHandler now reads the form through
_multipart_lists as well, so every attachment survives on newer web.py.

Adjust tests to the merged code: the skill create endpoint's fake input,
the knowledge service fake in test_web_multi_file_upload, and the traversal
message expected from the shared archive extractor.

Co-authored-by: cowagent <cow@cowagent.ai>
2026-09-30 11:34:30 +08:00
Lengshuang 8974a5bc59 fix(cli): remove the temp dir when a repo archive cannot be extracted
_download_repo_zip creates its scratch directory before opening the
archive, and callers only learn the path from the return value, so any
failure after mkdtemp left the downloaded repo.zip behind in the system
temp directory. Clean up on the failure path, the way _clone_repo does.
2026-09-29 22:21:51 +08:00
zhayujie 724ecb045c Merge pull request #3319 from Lesereingrape/fix/restore-live-agent-roster
fix(cli): overlay the live roster when restoring a backup
2026-09-29 21:55:30 +08:00
zhayujieandcowagent f184c4b5e4 fix: support owner-qualified ClawHub installs for ambiguous skill names
ClawHub slugs are only unique per publisher, so a bare slug shared by
several publishers returns 409. Accept owner/slug, @owner/slug and skill
page URLs, pass the owner as ownerHandle, skip the slug-only mirror when
an owner is given, and explain the conflict instead of surfacing a raw 409.

Co-authored-by: cowagent <cow@cowagent.ai>
2026-09-29 21:21:34 +08:00
Lengshuang 52547ac015 fix(cli): overlay the live roster when restoring a backup
`team.migrate()` keeps the roster beside the workspaces, so `restore` read an
empty `agents` list from config.json and every Agent landed on the layout the
archive implies: the real workspace was left untouched and `team.compact()`
dropped its custom `workspace`, silently relocating the Agent.
2026-09-29 21:19:11 +08:00
zhayujieandcowagent 9340abe57c refactor: share one atomic file write helper across stores
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>
2026-09-29 14:51:02 +08:00
zkjqdandcowagent 81ddaa1060 feat: merge origin/master into feat/knowledge-document-edit
Take master as the base for the skills view: the Capabilities page with
MCP tools, the add-skill dialog with market install, and staged uploads
that are previewed before they are installed.

Keep from this branch the skill file tree and viewer card, the nested
frontmatter parser (now the shared pages/skills/frontmatter.ts), the
form-based create dialog beside "Add skill", background knowledge
reindexing with the protected-path check, and the trigram probe.

Drop this branch's direct upload install (SkillService.install_upload,
its handler, the upload tab and its strings) in favour of master's
staged flow, so every upload goes through a preview first.

Co-authored-by: cowagent <cow@cowagent.ai>
2026-09-29 14:32:14 +08:00
c020627 d1836b7654 fix(skills): name the replacement the way manager.py names its own
The sibling is now `.skills_config.json.<pid>.<tid>.tmp` instead of
`skills_config.json.tmp`. agent/evolution/executor.py lists the dot-prefixed
shape in _WATCH_IGNORE_PREFIXES, so the workspace scanner does not read the
in-flight replacement as a real change, and a per-writer name keeps two
overlapping saves from renaming each other's partial file.

The test assertion that named the old temp path would have passed vacuously
after this, so it now checks the directory contents instead.
2026-09-28 19:39:27 +08:00
c020627 280fc23f67 fix(skills): write skills_config.json through a sibling file
Problem
-------
skills_config.json is the installed-skill registry: both `cow skill`
(``cli.utils.load_skills_config``) and the runtime SkillManager read it.
All five writers in cli/commands/skill.py rewrote it in place with
``open(..., "w")``, which truncates before the new bytes are there, so a
save that fails halfway -- a full disk, the process being killed, a power
cut -- leaves a half-written document behind.

Reachability
------------
cli.utils.load_skills_config() (cli/utils.py:84) cannot parse that
document and answers ``{}``, so the very next
``_register_installed_skill`` / ``_set_enabled`` / ``uninstall`` /
``_merge_builtin_into_config`` starts from an empty baseline and, when it
saves, drops every other skill's entry -- source, enabled,
display_name -- from the registry. The runtime side reads the same path:
SkillManager._load_skills_config answers None for an unreadable file and
_sync_skills_config then falls back to its own view, so the user's
``enabled: false`` choices are reset as well.

Precedent
---------
agent/skills/manager.py:131 already writes this same file through a
sibling file and documents why: "Written to a temporary file and renamed
into place, so a concurrent reader sees either the old file or the new
one, never a partial write." Its reader docstring goes further -- the
None return exists "so the caller never mistakes it for an empty config
and writes the defaults over the user's choices". The CLI writers are the
copies that picked up neither half of that convention.

Fix
---
Add one module-level ``_write_skills_config`` that serialises into
``skills_config.json.tmp``, fsyncs and os.replace()s it into place, and
route all five call sites through it.

Scope
-----
Only the write path changes: the readers, the merge semantics and the
best-effort error handling at each call site are untouched. The same file
is also written by plugins/cow_cli/cow_cli.py (the inline console); that
entry point is left for a separate change so this diff stays focused on
cli/commands/skill.py.

Testing
-------
tests/test_skills_config_atomic_write.py: 2 failed, 1 passed against the
unfixed source (JSONDecodeError on the truncated ``{\n    "half``), 3
passed with the fix. Existing skill suites unchanged (see the PR body).
2026-09-28 19:28:38 +08:00
zhayujieandcowagent e7fe1f86fb feat: rework the Capabilities page with previewed skill installs and MCP JSON config
- Rename the Skills menu to Capabilities, covering built-in tools, MCP tools
  and skills; built-in tools collapse to two rows by default.
- MCP editor: form / JSON tabs (accepts pasted mcpServers configs, several at
  once), unified transport dropdown, structured tool list in test results,
  and a connection check on save with a "save anyway" fallback. Server
  status refreshes on its own while servers are still loading.
- Add skill dialog: install from Cow Skill Hub, GitHub or ClawHub, or upload
  an archive, SKILL.md or folder. Skills are staged and previewed first and
  only installed on confirm; stale previews are cleaned up.
- Parse multipart uploads without collapsing repeated fields on newer web.py.
- Same UI on the desktop app; docs and tests updated.

Co-authored-by: cowagent <cow@cowagent.ai>
2026-09-26 19:03:49 +08:00
zhayujie 7931d0ae4d Merge branch 'master' into pr-3155 2026-09-26 16:03:38 +08:00