Skip to content

feat(installer): add QwenPaw and Qoder agent support alongside Claude Code and Codex - #60

Open
Leo (CallMeHFK) wants to merge 7 commits into
microsoft:mainfrom
CallMeHFK:feat/qwenpaw-agent-support
Open

Leo (CallMeHFK) wants to merge 7 commits into
microsoft:mainfrom
CallMeHFK:feat/qwenpaw-agent-support

Conversation

@CallMeHFK

@CallMeHFK Leo (CallMeHFK) commented Sep 18, 2026 •

Copy link
Copy Markdown

Summary

ResearchStudio's plugins (Idea, Reel) are standard Agent Skills (SKILL.md folders), and the installers already supported provisioning them into Claude Code and Codex. This PR adds QwenPaw (agentscope-ai/QwenPaw) and Qoder CLI (docs) as further agent runtimes, and restructures the agent handling in bin/install.mjs so adding another host is a one-line registry entry. It also fixes four installer defects that the verification work exposed.

What the two new runtimes need

QwenPaw organizes skills differently from Claude Code/Codex: a shared skill pool at $QWENPAW_WORKING_DIR/skill_pool/ (default ~/.qwenpaw/skill_pool/), from which skills are broadcast into per-agent workspaces (workspaces/<agent_id>/skills/). Two behaviors were verified against QwenPaw's source and docs before implementing:

  • Pool entries must be real copies, not symlinks. QwenPaw's safe_skill_dir resolves skill directories and rejects anything outside the pool root, so symlinked skills (the approach install.sh uses for Claude/Codex) would be silently dropped by its scanner.
  • Manually placed pool skills register on the next manifest reconcile and the QwenPaw runtime loads <working-dir>/.env into the env its skills run in — which the skills' own .env walker also reaches from the pool (≤ 4 levels up).

Qoder reads SKILL.md folders from .qoder/skills/ (project scope) and ~/.qoder/skills/ (user scope, which wins on a name conflict), and — unlike QwenPaw — it resolves symlinked skill dirs, so it joins the existing link path rather than needing a copy path. Established by probe before writing code: symlinking one skill into .qoder/skills/ made it appear in the discovered-skills list on the next turn.

Changes

bin/install.mjs

  • Replaces the AGENTS map with a data-driven registry (label + skillsDir(base) + pool flag); adding a new agent host is one entry.
  • Interactive flow now presents a multi-select agent list (Claude Code / Codex CLI / Qoder / QwenPaw) instead of the yes/no Codex follow-up; RS_AGENTS=claude,codex,qwenpaw,qoder for non-interactive runs, with graceful skipping of unknown names.
  • QwenPaw target: copies skills into the resolved skill pool (mirrors QwenPaw's constant.py resolution: QWENPAW_WORKING_DIR → legacy ~/.copaw → ~/.qwenpaw), writes the merged .env into the QwenPaw working dir (other agents keep the skills-dir .env), and best-effort runs qwenpaw skills list --pool to trigger the reconcile immediately. Pool agents have no project-scoped dir, so project scope still targets the pool with an explanatory note.
  • Qoder target is one registry entry with no pool flag, so global and project scope both work unchanged.

install.sh

  • New --qwenpaw / --no-qwenpaw and --qoder / --no-qoder flags plus QWENPAW_WORKING_DIR / QWENPAW_POOL_DIR / QODER_SKILLS_DIR env overrides. Pool entries are copied (cp -R), never linked; Qoder is symlinked into <repo-root>/.qoder/skills/ alongside the Claude and Codex slots. Seeds ~/.qwenpaw/.env from .env.template when absent (re-runs never overwrite), nudges a pool reconcile when the CLI is on PATH, and prints workspace-loading and /skills guidance.

Slot naming, both installers

  • The two installers disagreed: install.sh passed hand-written slot names (idea-spark) while bin/install.mjs copied source directory names (idea_spark), so one skill installed under two identities depending on which installer ran. Both now resolve the slot from the skill's own frontmatter name:, falling back to the directory name — no rename table to go stale.
  • That also lets stale-slot cleanup be derived rather than listed. install.sh only ever cleared a leftover idea_spark, so paper_search / scoop_check slots from a pre-rename install survived indefinitely; now whichever directory-named slot the frontmatter name replaces gets cleared, for the link runtimes and the QwenPaw pool alike.

Portability fixes

  • $PY could be a relative path: the documented PYTHON=./.venv/bin/python passed the early checks, then died at the connector check, which runs in a subshell that has already cd'd elsewhere. Resolve it to sys.executable once. (Pre-existing on main.)
  • Skill linking assumed ln -s always succeeds. On a filesystem without symlink support the output still printed the link arrow while nothing was installed. link_into now falls back to a real copy and the line is labelled (copied).
  • --help printed a hardcoded line range that had already silently swallowed the env-override list once; it now derives from the leading comment block.
  • writeEnv() re-serialised the merged env even when nothing new was entered, so a re-run replaced a hand-edited .env with a bare KEY=value dump — every comment lost. It now leaves an existing file alone when there is nothing to add, matching what install.sh has always done.

READMEs — main + Idea + Reel list both new runtimes. .gitignore adds .qoder/ alongside .claude/ and .codex/.

Verification

  • bash -n / node --check clean; --help renders the complete flag and env-override lists.
  • Non-interactive dry runs against sandboxed HOME / QWENPAW_WORKING_DIR for all four agents: skills land in ~/.claude/skills, ~/.codex/skills, <qoder>/skills, and <qwenpaw>/skill_pool respectively; .env written to <qwenpaw>/.env and confirmed reachable by the skills' env walker; qwenpaw skills list --pool reconcile registered the installed skills in skill_pool/skill.json (real copies, no symlinks in the pool).
  • Qoder path run for real against a live Qoder session: install.sh --idea --qoder symlinked three skills into .qoder/skills/ and Qoder's discovered-skills list picked them up in-session (observed, not inferred from files on disk). bin/install.mjs with RS_AGENTS=qoder verified for both RS_SCOPE=project and RS_SCOPE=global.
  • Pre-seeded stale underscore slots: after a run only the frontmatter names remained; a QwenPaw pool that held both paper_search and paper-search was reduced to the frontmatter name automatically.
  • link_into exercised against its own source with ln forced to fail: copies, returns 1, SKILL.md present. skill_slot_name checked against all eight shipped skills plus no-frontmatter and quoted-name: fallbacks.
  • .env guard checked both ways: no new keys → file byte-identical while the install still completes; with a key → merge and backup still happen.
  • Unknown RS_AGENTS entries are skipped with a hint listing known agents.

Note for reviewers

  • After a QwenPaw pool install, skills still need loading into a QwenPaw workspace (Console → Workspace → Skills → "Load from Skill Pool", or copy into workspaces/<agent_id>/skills/ + qwenpaw skills enable ...). That's QwenPaw's own pool→workspace model; the installers print this guidance rather than guessing an agent ID.
  • No .qoder/settings.json is seeded. The Claude and Codex seeds grant broad auto-approval, and silently writing that policy for a new runtime seemed the wrong default.
  • ppt-master is fetched from GitHub by npx skills add; that clone failed once on the machine used for testing (registry and git ls-remote were reachable, so it looks specific to that CLI's fetch method). The installer's warning path fired correctly and named the manual command.
  • install.sh --reel has no way to skip its sudo apt-get block, so an unprivileged Reel install currently has to go through bin/install.mjs. A --no-native flag would close that gap; not attempted here.

@CallMeHFK

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

…Codex

Both installers can now provision the Idea/Reel plugins into QwenPaw's
shared skill pool:

- bin/install.mjs: data-driven agent registry (one entry per agent host),
  multi-select agent prompt, and a QwenPaw target that copies skills into
  <QWENPAW_WORKING_DIR>/skill_pool and writes the merged .env into the
  QwenPaw working dir (its runtime loads it; the skills' own .env walker
  reaches it from the pool). Best-effort `qwenpaw skills list --pool`
  nudges a manifest reconcile so manually placed skills register at once.
- install.sh: --qwenpaw / --no-qwenpaw with QWENPAW_WORKING_DIR /
  QWENPAW_POOL_DIR overrides. Pool entries are real copies, not symlinks —
  QwenPaw's safe_skill_dir rejects resolved paths outside the pool root.
- READMEs: document QwenPaw as a supported agent runtime.
… CLI

`npx skills add` now requires --agent on non-interactive runs and dropped
the legacy .agents/skills scaffold it used to leave behind. Fetch into a
throwaway dir with -y --agent claude-code (the agent choice only decides
where content lands there), accept both the current .claude/skills/<name>
layout and the legacy one, then copy into each selected runtime.
Qoder reads SKILL.md folders from .qoder/skills/ (project scope) and
~/.qoder/skills/ (user scope), and — unlike QwenPaw — it resolves symlinked
skill dirs, so Qoder joins the link-based runtimes rather than the copy path.

- bin/install.mjs: one AGENTS registry entry; no pool flag, so global and
  project scope both work unchanged.
- install.sh: --qoder / --no-qoder with a QODER_SKILLS_DIR override, symlinked
  into <repo-root>/.qoder/skills/ alongside the Claude and Codex slots. Also
  fixes the --help range, which had been silently dropping the env-override
  list since the QwenPaw flags were added.
- READMEs: document Qoder as a supported agent runtime.
The Qoder commit was verified against one host, which hid three things that
are not true everywhere:

- $PY could be a relative path. The documented `PYTHON=./.venv/bin/python`
  passes the early checks, then dies at the connector check, which runs in a
  subshell that has already cd'd elsewhere. Resolve it to sys.executable's
  absolute path once. Pre-existing on main, not introduced by the Qoder work.
- Skill install assumed `ln -s` always succeeds. On a repo sitting on a
  filesystem without symlink support the link lines print as if the skill were
  installed while nothing is there. link_into now falls back to a real copy and
  the caller labels it "(copied)".
- --help printed a hardcoded line range, which had already silently swallowed
  the env-override list once. Derive it from the leading comment block instead.

Also stops asserting a specific `/skills reload` subcommand in the closing
Qoder hint, pointing at the docs page instead.
…allers

The two installers disagreed: install.sh passed hand-written slot names
(idea-spark), bin/install.mjs copied the source directory names (idea_spark),
so the same install surfaced under two identities depending on which installer
ran. Hosts list skills by that name, so a mixed history shows up duplicated.

Both now resolve the slot from the skill's own frontmatter `name:` — the
identity every host actually displays — falling back to the directory name. No
rename table left to go stale when a skill is renamed or added.

Writing the resolved name also lets the stale-slot cleanup be derived instead
of listed: install.sh only ever cleared a leftover `idea_spark`, so
`paper_search` / `scoop_check` slots from a pre-rename install survived forever.
It now clears whichever directory-named slot the frontmatter name replaces, for
the symlink runtimes and the QwenPaw pool alike.
…ered

writeEnv() re-serialised the merged env even when `values` was empty, so a
re-run with nothing new to record replaced a hand-edited .env with a bare
KEY=value dump — every comment and section header lost, and a surprise
.env.bak written next to it. Observed for real: a QwenPaw working-dir .env
went 1352 -> 427 bytes with no keys entered.

Skip the file entirely when there is nothing to add and one already exists.
install.sh has always left an existing .env untouched, so this also removes a
disagreement between the two installers. When keys *are* supplied the merge
still writes and still backs up.
@CallMeHFK
Leo (CallMeHFK) force-pushed the feat/qwenpaw-agent-support branch from 5efa9dd to c65b6ec Compare September 22, 2026 05:53
@CallMeHFK Leo (CallMeHFK) changed the title feat(installer): add QwenPaw agent support alongside Claude Code and Codex feat(installer): add QwenPaw and Qoder agent support alongside Claude Code and Codex Sep 22, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant