4 Commits
Author SHA1 Message Date
flowjzhandflowjzh b46563261c fix(hooks): give the detached hook child the bundled gits on PATH (#801)
The session-start pull is spawned through the WMI service to escape the
host's job object, and a WMI-created process inherits the provider's
environment, not the caller's. On a machine with no system git - every
WorkBuddy client - the PATH that ran `teamai` therefore never reaches the
pull: bare-name `git` lookups (simple-git, providers, mr-hint) fail with
`spawn git ENOENT`, the failure is reported only on the console spinner
(a detached pull has no console), and the clone silently stops advancing
while the postPull script - spawned by absolute path - keeps deploying a
stale tree.

Resolve the bundled git at startup instead, one resolver per host in the
git counterpart of BUNDLED_SHELLS: a qualifying root's `<root>/cmd` goes
on PATH first, with the msys dirs appended for the tools git itself shells
out to. A machine that already resolves git is left untouched, and both
outcomes are logged. The pull's outcome and the divergence notice are
persisted too - the spinner is console-only and log.warn never reaches
debug.log, which is why a stale clone had no explanation anywhere.

The suite builds its "no git on PATH" fixtures as empty dirs under the
per-test home, never a host directory like /usr/bin, which really holds a
git on macOS and Linux - the win32 simulation drops the execute-bit
requirement, so such a path silently satisfies the gate and the case
proves nothing.

Co-authored-by: flowjzh <flowjzh@users.noreply.github.com>
2026-09-24 22:23:35 +08:00
PerryLinkandPerryLink 1e2c029466 feat(agents): support Qoder CN via its .qoder-cn user directory (#695)
* feat(agents): support Qoder CN via its .qoder-cn user directory

Qoder CN keeps its user directory at ~/.qoder-cn instead of ~/.qoder, so a CN install saw none of TeamAI's synced resources and users worked around it with symlinks. Register qoder-cn as its own built-in target mirroring qoder: skills discovery, toolPaths defaults, the Claude-compatible agent/MCP formats, and the subagent render/reverse switches. Docs list the new paths.

* fix(agents): resolve Qoder CN paths by scope, not by one flat table

The first cut registered `qoder-cn` with `.qoder-cn/*` as every path, which
wrote project-scope resources into a directory Qoder CN does not read. Only the
*user* directory differs (`~/.qoder-cn`); project scope still uses `.qoder/`.
`scopedToolPaths` documents the contract -- top-level fields are the project
paths, `userScope` carries the user overrides -- so the entry now follows it:
top-level stays `.qoder/*`, `userScope` holds `.qoder-cn/*`, `mcp` is the
user-level MCP file and `mcpProject` the project-level one.

`userScope` did not admit `settings` at all, so a user-scope `qoder-cn` would
have had its hooks written to `~/.qoder/settings.json` -- the international
install's config. `settings` is now part of the userScope schema and its splice.

Hooks do not necessarily live at the config's scope: `resolveHookScope` maps a
non-self project scope to HOME (#264). Every caller that resolved paths from
`localConfig` while using that HOME base directory therefore looked under the
project prefix inside HOME. `resolveHookScope` and
`resolveLegacyProjectHookScope` now report the scope they resolved, and the
inject, remove and check paths use it -- `hooks.ts`, `pull.ts`, `hooks-cmd.ts`
and `doctor.ts`, the last of which is why `teamai doctor` reported "teamai hooks
in qoder-cn settings" as missing.

Verified with a real CLI end-to-end run, not only unit tests. Against
`teamai-hub/teamai-cli-dev` with a scratch HOME, `--scope project --agent
qoder-cn`:

- `~/.qoder-cn/settings.json` is written; `~/.qoder/settings.json` is not
- project scope delivers to `<project>/.qoder/skills`; `.qoder-cn/` is not created
- `teamai doctor` reports `qoder-cn is installed` and `teamai hooks in qoder-cn
  settings` both green (the latter failed before this change)

`npx tsc --noEmit` clean; full suite unchanged at 30 failed / 240 passed files
and 75 failed / 3655 passed tests, the pre-existing Windows failures.

* fix(agents): keep Qoder CN off the paths Qoder already owns

Registering `qoder-cn` with the project paths `.qoder/*` made two targets
resolve to the same project-scope hook file. `reconcileHooksToAllTools` iterates
per tool and `reconcileClaudeFormat` re-renders every built-in entry with the
current tool's dispatch identity, so the later `qoder-cn` pass rewrote a plain
international-Qoder install's file as `--tool qoder-cn` and dropped team hooks
scoped to `tools: [qoder]`. Reproduced with the default configuration (no agent
whitelist) in self mode with `<root>/.qoder` present: 6 built-ins all
`--tool qoder-cn`, 0 `--tool qoder`, and the `tools: [qoder]` hook gone.

Each settings file is now reconciled once, for the first target that reaches it.
The shipped table lists `qoder` before `qoder-cn`, so the existing target keeps
ownership of `<root>/.qoder/settings.json` and CN remains a user-scope addition
there; a CN-only pass still targets the file as `qoder-cn` because nothing else
claims it. `hooksList` mirrors the same rule, so the shared file is listed once
instead of reporting the CN row as missing.

Two user-scope consumers resolved their paths at the config's scope while using
the HOME base directory `resolveHookScope` reports, so they looked in
`~/.qoder/settings.json` for hooks that live in `~/.qoder-cn/settings.json`:
`hooksList` reported a healthy CN install as missing, and `uninstall` discovery
never found the CN hooks and left them behind. Both now use the hook scope.
Skills, rules, agents and claudemd stay at the config scope in uninstall — they
are real project resources, and resolving those to user paths would strand the
project copies.

Corrected the MCP table in both `docs/usage-guide.md` and
`docs/usage-guide.zh-CN.md`, which still said `<project>/.qoder-cn/settings.json`
while `mcpProject` and the surrounding prose use `<project>/.qoder/`.

Known limitation, documented rather than fixed: in project scope a team hook
scoped `tools: [qoder-cn]` no longer lands in the shared file. One physical file
can carry only one dispatch identity, so it is not representable there; teams can
scope to `qoder` or omit `tools:`. Union semantics would need engine support for
alias-aware definitions and is out of scope here.

`tsc --noEmit` clean; full suite 30 failed / 240 passed files unchanged, the
71-test failure set byte-identical, +4 tests from this change.

* fix(cli): resolve the shared hook file by the enabled set on the read path

`hooksList` iterated the whole tool table without applying the enabled set, so
for a self-scope config that enabled Qoder CN alone, `qoder` — disabled, but
earlier in the shipped table — claimed `<root>/.qoder/settings.json`, was probed
for Qoder's dispatch identity, and was reported `missing` while the enabled
`qoder-cn` target never appeared in the table at all.

The write path already got this right: `reconcileHooksToAllTools` skips any tool
outside `filterAgents`, which `reconcileTeamHooksForConfig` derives from
`enabledAgents` and `disabledAgents`. Only the read path was missing the same
rule, so the fix reuses the filter `doctor` already applies to this path table.

Ownership of the shared file therefore follows the enabled set rather than the
table order, and the two paths now agree.

---------

Co-authored-by: PerryLink <255665900+PerryLink@users.noreply.github.com>
2026-09-22 22:23:18 +08:00
zdGrandeand林志达 04b6de2219 fix(hooks): run CodeBuddy's Windows hooks through cmd.exe (#637)
* fix(hooks): run CodeBuddy's Windows hooks through cmd.exe

bundled-runtime: CodeBuddy provides cmd.exe on Windows, so the /bin/sh shell gate is a false negative there. POSIX keeps the old check.

builtin-hooks: render cmd-syntax commands for codebuddy on win32 and write a teamai.cmd shim, so the PATHEXT lookup can resolve the CLI.

hooks: render the project gate in the host shell's syntax and recognise both renderings, so a platform switch cannot leave dead duplicates.

tests: win32 cases with a spied platform, so ubuntu CI covers them.

* fix(hooks): exit 0 on a project-gate miss and align the cmd wrapper path

hooks: the cmd gate `${gate} && (payload)` let a gate miss inherit findstr's
exit status — non-zero is surfaced as a hook error, and exit 2 blocks
UserPromptSubmit. Use `& if not errorlevel 1 (payload) else exit /b 0`: a miss
exits 0, a match still passes the payload's own status through.

builtin-hooks: the cmd wrapper searched `%USERPROFILE%\.teamai\bin` while the
writer uses getUserHome(), so HOME/USERPROFILE could diverge and `teamai` was
never found. Embed the resolved bin dir; drop TEAMAI_BIN_DIR_WIN.

tests: update the cmd-gate and codebuddy PATH assertions.

* fix: escape cmd project gate for CodeBuddy hooks on Windows

Cmd shell metacharacters in project roots break the team-hook
project gate; escape the root literal and match with findstr.
Sync tests and docs.

---------

Co-authored-by: 林志达 <linzhida@bosssoft.com.cn>
2026-09-21 23:09:45 +08:00
BeastAyyG 73012087e2 docs(windows): add Windows hook wiring guide (#660) 2026-09-20 23:57:05 +08:00