feat: git-free HTTP team repo + hooks-driven agent status reporting#68
Merged
Conversation
44ac153 to
34312af
Compare
实现 issue #1 评审通过的两套方案,共享同一套 skill 分发原语。 方案一(git-free HTTP 团队仓库 + skill 只读拉取): - source-http.ts: GET /repo 物化(files[] 内联 + commands[] 走共享执行器), 路径穿越防护,version 作为增量缓存 key - init --http <url>: 只读消费者 onboarding(只需 API key,跳过 git/clone/member/reviewer) - pull.ts: 抽象 refreshTeamRepo() 收口 git/http 两种刷新,其余管线原样复用 - read-only.ts: http kind 下 push/contribute/remove 明确拒绝 方案二(hooks 驱动的 agent 状态上报): - machine-id.ts: 跨平台 machine_id(macOS ioreg / Windows reg / Linux machine-id) + local_agent_id 派生(install_path 仅本地哈希,不上报) - status-report.ts: report/sync/ack 三接口 + 离线队列 + clawpro/local 来源区分; 接口路径走可覆盖的内部映射(默认 iWiki 契约,TEAMAI_REPORT_PATHS 可覆盖) - 挂到既有 hook dispatch: session-start→report+sync,prompt-submit→sync - openclaw-hooks.ts: WorkBuddy(龙虾系)HOOK.md + handler.ts 注入适配器 共享地基: - skill-command.ts: executeSkillCommand(fflate 解压 zip,含 SKILL.md 校验、 路径穿越防护、SMH 直连下载),push/pull 两条路径共用 - api-key.ts: 统一 Bearer 凭证解析/保存(0600,不入 config/不上报) - teamai login <key> 命令 测试: 本地实现三接口 mock HTTP 服务(进程内 helper + scripts/mock-teamai-server.mjs 独立可运行版),单测 + 集成端到端共 49 个新用例全部通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> (cherry picked from commit e8e50532024bfccf83a1b968cfc7d5a0de4472e3)
后端契约调整(对齐 clawpro https://5hborhrw.cvmopenclaw.site): 1. 接口名去掉 v1:/api/v1/local-agent/* → /api/local-agent/* 2. ack 的 command id 从 path 移到 request body,类型 int: POST /api/local-agent/commands/ack,body { id: <int>, status, error } - status-report.ts: EndpointMap.ack 由路径构造函数改为固定路径字符串; 默认路径去 v1;ack body 带 int id。TEAMAI_REPORT_PATHS 覆盖保留。 - skill-command.ts: SkillCommand.id string → number。 - mock-server.ts / mock-teamai-server.mjs / 单测同步更新。 端到端验证(真实后端):report/sync → 200,ack 路由按 int body id 校验; 完整 install→ack 闭环(mock)通过。tsc 通过,vitest 1454 passed 无回归。 Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit fe5d649f4f29a0e70996a011ee670bb32ec7ca27)
Real-device verification (WorkBuddy 5.2.0) shows WorkBuddy embeds the CodeBuddy CLI engine and reads Claude-format hooks from ~/.workbuddy/settings.json: SessionStart / UserPromptSubmit / PostToolUse all fire with PascalCase event names and CLI-style tool names (tool_name="Bash"), identical to codebuddy. MR 191 originally (wrongly) assumed WorkBuddy used the OpenClaw HOOK.md engine, so teamai never wired it (0 workbuddy events on a real machine). Fix: - types.ts: toolPaths.workbuddy now carries settings: '.workbuddy/settings.json' → routes through the Claude-format injection path like codebuddy. - hooks.ts: drop workbuddy from OPENCLAW_TOOLS (kept for the still-unverified openclaw/qclaw/easyclaw/autoclaw variants). - openclaw-hooks.ts: clarify it's no longer for WorkBuddy; default tool=openclaw. - tests updated to assert workbuddy → settings.json hooks (--tool workbuddy), OpenClaw HOOK.md path only for openclaw. Verified end-to-end on the real machine: `teamai hooks inject` writes the workbuddy hook block (preserving claw/enabledPlugins/sandbox); real WorkBuddy fires the hooks; `hook-dispatch --tool workbuddy` records tool=workbuddy in the dashboard. tsc OK, vitest 110 files / 1466 passed. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit d1ec907433177e3ac76ed81867fec928be647293)
MR 191 added injectOpenClawHooks but uninstall only removed hooks from tools with a `settings` path, so OpenClaw-style HOOK.md/handler.ts dirs (~/.<tool>/hooks/teamai-status-report) leaked on uninstall. - uninstall.ts: discover + remove OpenClaw hook dirs for settings-less tools (mirrors the inject path); listed in the removal summary. - Added regression test asserting the OpenClaw HOOK.md dir is removed. Verified end-to-end: with workbuddy now settings-based, `uninstall` strips its teamai hooks while preserving claw/enabledPlugins/sandbox; OpenClaw HOOK.md dirs are removed (previously leaked); real-machine dry-run lists ~/.workbuddy/ settings.json. tsc OK, vitest 110 files / 1467 passed. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 4a9a2361c55292547dd033ade98825ac3083811a)
A clawpro-style backend may ship the status-reporter endpoints before the team-repo /repo endpoint exists. Previously `teamai init --http <url>` hard- failed on such endpoints (/repo → 404 or 200 HTML), so there was no first-class way to configure endpoint+key for reporting. - source-http.ts: fetchRepoSnapshot now distinguishes "/repo not live yet" (404 or non-JSON 200 body) by throwing RepoNotAvailableError; auth (401/403) and other errors stay hard failures. - init.ts: on RepoNotAvailableError, init --http enters reporting-only mode — writes a minimal local teamai.yaml (default toolPaths), saves the http config (endpoint+key), injects hooks, and prints a clear notice. When /repo later comes online, a normal pull materializes skills with no re-init. - pull.ts: refreshTeamRepo swallows RepoNotAvailableError (reporting-only) so every session doesn't error while /repo is absent. - tests: RepoNotAvailableError classification (404 / non-JSON / 500). Verified e2e against the real reporter-only backend: `teamai login <key>` + `teamai init --http <url>` now succeeds (reporting-only), reporter report/sync hit the backend (200, no offline queue), and pull no longer hard-fails. tsc OK, vitest 110 files / 1470 passed. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 75b03c633a731f302375c764df970a5b84dba00d)
…n command) Reduces command surface and puts endpoint + key in one place (addresses the "why are login and endpoint separate" awkwardness). The `login` command was only introduced in this unreleased HTTP/reporter feature set, so removing it is not a breaking change for released users. - index.ts: remove `login` command; add `--token <key>` to `init`. - init.ts: initHttp persists --token via saveApiKey (0600) before resolving; still falls back to TEAMAI_API_TOKEN / existing apikey file. - update "run `teamai login`" hints in source-http.ts / pull.ts / api-key.ts. One-command setup now: teamai init --http <url> --token <key> Verified e2e against the real reporter-only backend: single command saves the key, configures the http endpoint (reporting-only), injects hooks; reporter report/sync hit the backend (200). `login` no longer appears in --help. tsc OK, vitest 110 files / 1470 passed. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit e98b48f7fc63eddc755fa9ef04260ad9055c49d3)
…RROR) In HTTP team-repo mode the local team-repo path is not a git checkout, so pull's Step 5 usage auto-report (reportUsageToTeam, git-based) failed every session with `[ERROR] Auto-report skipped: fatal: not a git repository`. HTTP consumers are read-only and have no git remote to report to, so skip the step entirely when repo.kind === 'http'. Verified: `teamai pull` in HTTP reporting-only mode now produces no Auto-report error. tsc OK, vitest 110 files / 1470 passed. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 388078d0c587c03602b93a5635e193961e84e592)
Skill install/uninstall driven by `sync` commands previously ran silently — on failure the reporter only ack'd `failed` with no local log, making it impossible to see why a dispatched skill (e.g. fd-find) didn't install. Now: - log.debug the number of commands returned by sync - log.debug each command success - log.error each command FAILURE with the underlying error message No behavior change beyond logging. tsc OK, vitest 110 files / 1470 passed. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 9bbabfcc0ef467b1addf605a29a2754632c455f4)
Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 882698389411271ca359725b17e61907143c12ad)
A failed install previously logged only "download failed: HTTP 409", giving no clue what was being fetched. Now downloadZip logs the signed download_url before fetching and includes the server response body + URL in the thrown error (which lands in debug.log and the ack error field). The reporter also logs each sync command (incl. download_url) and renders empty skill_version as "?" instead of a bare "@". Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit 2a58ede7abf0e411ad61474adc0b65d21887f713)
Previously a successful report or sync produced no log line, so there was
no way to tell whether a SessionStart/UserPromptSubmit hook actually fired
or whether sync ran ("seems sync never triggered"). Now each invocation
logs run (agent/phase/id/endpoint) and the OK/FAILED outcome of report,
sync (with command count), and each ack.
Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 49ac63953c9d6e33e8f9998a2be86ea91da2e00e)
teamai-share-learnings and teamai-wiki both write to the team repo, so in reporting-only HTTP mode (no /repo) they are non-functional. refreshTeamRepo now reports reportingOnly, which pull threads into deployBuiltinSkills to skip injecting them. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit d5c40475ba99078174ba553719b707e6a0d88ccc)
The clawpro/skillhub backend packages skills as a flat zip (SKILL.md + _meta.json at the root, e.g. find-skills-skill), but installSkillZip required a top-level <slug>/ directory and failed with "skill package missing <slug>/SKILL.md". installSkillZip now resolves the SKILL.md location across three layouts (nested-by-slug, flat root, nested-other-name) and installs the contents into <skillsDir>/<slug>/. Co-authored-by: Cursor <cursoragent@cursor.com> (cherry picked from commit fbdf643c79c42613eb52fb7c383fddeb893b9b83)
Reconcile the issue-#1 HTTP init path onto main's unified-hooks (#65) architecture: the old `injectHooksToAllTools(toolPaths, baseDir)` entry no longer exists, so the HTTP consumer now injects hooks via the same authoritative `reconcileTeamHooksForConfig` path the git init uses. Fixes the tsc TS2304 (injectHooksToAllTools / resolveBaseDir not found) seen in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Add a "Read-only consumers (HTTP team repo, no git)" quick-start subsection to both README.md and README.zh-CN.md, covering `init --http --token`, reporting-only fallback, hooks-driven status reporting, and the local-only hashing of install path / machine id. Co-authored-by: Cursor <cursoragent@cursor.com>
Add a collapsible "HTTP contract" block to both READMEs spelling out what a --http backend must serve: the fixed GET /repo snapshot shape, the three (overridable) local-agent reporter endpoints, the signed download_url + accepted zip layouts, and the env vars that make paths/hosts/agents configurable. Clarifies what is fixed vs configurable. Co-authored-by: Cursor <cursoragent@cursor.com>
The report payload no longer tags each skill `source: clawpro|local`. With `source` gone, the entire clawpro-skills.json bookkeeping (getClawproSlugs / recordClawproSlug / clawproRecordPath) had no remaining consumer, so it is removed. `scanReportableSkills(skillsDir)` now just lists every skill found in the agent's own skills dir. Tests updated accordingly. Co-authored-by: Cursor <cursoragent@cursor.com>
34312af to
e1060b0
Compare
jeff-r2026
added a commit
that referenced
this pull request
Jun 30, 2026
Post-merge quality cleanups on the HTTP team-repo + status-reporter code (PRs #68/#70/#71), surfaced by /simplify. No behavior change. - path-safety: extract assertWithinRoot() and replace the hand-rolled containment check duplicated in source-http.ts and skill-command.ts. It compares resolved-but-not-symlink-followed paths, so it stays correct for not-yet-created targets and under symlinked roots (e.g. macOS /var -> /private/var) — unlike assertSafePath, which would false-reject legitimate writes there. - status-report: drop the no-op `await import('fs-extra')` in flushQueue (fs-extra is already loaded via utils/fs) and use the project remove/writeFile helpers; parallelize scanReportableSkills with Promise.all instead of awaiting per-skill reads in a loop. - team-push: reuse readJson/writeJson for the reported-interventions snapshot instead of hand-rolled JSON.parse/stringify. Verified: `tsc --noEmit` clean; full unit suite green (1662 passed). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #69.
Summary
Implements the two designs from #69, sharing a single skill-distribution primitive. Rebased cleanly onto
main(unified hooks #65 + CodeBuddy tool-name normalization #66), so the overlapping #63 work is intentionally dropped here (already fixed onmainby #66).方案一 — git-free HTTP team repo (read-only skill pull)
source-http.ts: materializeGET /repo(inlinefiles[]+commands[]via the shared executor), with path-traversal protection andversionas the incremental cache key.teamai init --http <url> --token <key>: read-only consumer onboarding (only needs an API key; skips git/clone/member/reviewer). Tolerates a missing/not-yet-live/repo→ reporting-only mode.read-only.ts:push/contribute/removeare explicitly rejected forhttprepos.方案二 — hooks-driven agent status reporting
machine-id.ts: cross-platform machine id +local_agent_idderivation (install_path is only hashed locally, never uploaded).status-report.ts:report/sync/ackwith an offline queue and clawpro-vs-local source tagging; endpoint paths go through an overridable internal map (TEAMAI_REPORT_PATHS).session-start→ report+sync,prompt-submit→ sync.openclaw-hooks.ts: HOOK.md + handler.ts adapter for the OpenClaw (lobster) family. WorkBuddy is intentionally routed through~/.workbuddy/settings.json(Claude-format hooks, verified on 5.2.0), not the HOOK.md path.Shared foundation
skill-command.ts:executeSkillCommand(fflate unzip, SKILL.md validation, path-traversal protection, SMH-signed direct download). Accepts both nested<slug>/SKILL.mdand flat zips (SKILL.mdat root). Used by both the push (reporter) and pull (HTTP repo) paths.api-key.ts: unified Bearer credential resolve/save (0600, never written to config, never reported).Plus observability: every reporter run + report/sync/ack outcome is logged, and a failed skill install surfaces the download URL + server response body.
Notes
teamai loginwas folded intoinit --http --token(login command removed).main: the CodeBuddy tool-name fix ([bug] CodeBuddy IDE (Craft Agent) 工具名为 IDE 风格,导致 auto-recall 与 skill 追踪在 CodeBuddy 内静默失效 #63) is dropped because fix(hooks): normalize IDE-style tool names from CodeBuddy Craft Agent #66 already covers it; HTTP init injects hooks via the feat: unified hooks management — team-declared hooks + built-in hooks as data (#19) #65reconcileTeamHooksForConfigpath.Test plan
npx tsc --noEmitnpx vitest run— 124 files / 1632 passed, 4 skippednpm run buildinit --helpshows--http/--token; top-levelloginremoved