Skip to content

feat: git-free HTTP team repo + hooks-driven agent status reporting#68

Merged
jeff-r2026 merged 17 commits into
mainfrom
feature/http-team-repo-reporter
Jun 30, 2026
Merged

feat: git-free HTTP team repo + hooks-driven agent status reporting#68
jeff-r2026 merged 17 commits into
mainfrom
feature/http-team-repo-reporter

Conversation

@jeff-r2026

@jeff-r2026 jeff-r2026 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

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 on main by #66).

方案一 — git-free HTTP team repo (read-only skill pull)

  • source-http.ts: materialize GET /repo (inline files[] + commands[] via the shared executor), with path-traversal protection and version as 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 /reporeporting-only mode.
  • read-only.ts: push/contribute/remove are explicitly rejected for http repos.

方案二 — hooks-driven agent status reporting

  • machine-id.ts: cross-platform machine id + local_agent_id derivation (install_path is only hashed locally, never uploaded).
  • status-report.ts: report / sync / ack with an offline queue and clawpro-vs-local source tagging; endpoint paths go through an overridable internal map (TEAMAI_REPORT_PATHS).
  • Wired onto the existing dispatch via the feat: unified hooks management — team-declared hooks + built-in hooks as data (#19) #65 hook registry: 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.md and flat zips (SKILL.md at 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

Test plan

  • npx tsc --noEmit
  • npx vitest run — 124 files / 1632 passed, 4 skipped
  • npm run build
  • CLI smoke: init --help shows --http/--token; top-level login removed

@jeff-r2026 jeff-r2026 force-pushed the feature/http-team-repo-reporter branch from 44ac153 to 34312af Compare June 30, 2026 05:29
jeff-r2026 and others added 17 commits June 30, 2026 14:48
实现 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>
@jeff-r2026 jeff-r2026 force-pushed the feature/http-team-repo-reporter branch from 34312af to e1060b0 Compare June 30, 2026 06:49
@jeff-r2026 jeff-r2026 merged commit 8825589 into main Jun 30, 2026
7 checks passed
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>
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.

Git-free HTTP team repo + hooks-driven agent status reporting

1 participant