Tencent / Tencent/teamai-cli

[feat] dsh (DeepSeek Harness): no hook adapter, so init promises auto-sync that never happens

Open
#623 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
4.8k
Forks
342
Avg merge
13h 48m
Merged PRs (30d)
211

Description

Problem

dsh (DeepSeek Harness) is a supported agent id — it is in KNOWN_AGENTS
(src/known-agents.ts:121), accepted by --agent dsh, and documented in the
usage guide. Skill sync to .dsh/skills works well: the namespace layout
skills/<ns>/<name>/ is flattened correctly and dsh discovers the result
natively with no extra configuration.

What is missing is hook delivery. src/types.ts:339 declares

dsh: { skills: '.dsh/skills' },

with no settings path. In injectHooksToAllTools() (src/hooks.ts:1185) an
agent without settings needs a dedicated branch — openclaw, hermes and
opencode each have one (copilot is handled separately, and kiro embeds the
dispatch in its agent configs). dsh has neither a settings path nor a
branch, so it falls through the whole chain and is silently skipped.

The only other agent with no hook path at all is joycode — but that case is
deliberate and documented: the usage guide states JoyCode exposes no lifecycle
hook mechanism and tells users to run teamai pull manually. dsh has a usable
hook mechanism, and no such note.

The user-visible consequence is that teamai init --agent dsh finishes by
printing (src/init.ts:1628):

Skills, rules, env and docs will auto-sync on each session start (via hooks).

For dsh that statement is not true. Nothing is ever installed, so nothing runs
at session start and no sync ever happens. The README correctly marks dsh's
hooks column as , so this is a gap rather than a regression — but the CLI
still makes the promise, and a dsh user has no indication that they must run
teamai pull by hand forever.

Everything else needed for the feature already works. seedProjectAgentRoot()
(src/project-agent-root.ts) is fully generic — it reads skillsPath from
KNOWN_AGENTS, which dsh already registers. Verified by deleting the entire
.dsh/ directory and running a single

teamai hook-dispatch session-start --tool dsh

which recreated the agent root and synced all skills. So the only missing piece
is installing the hook; the dispatch path itself is already dsh-ready.

Proposed Solution

I have a working bridge and would be happy to send a PR, but dsh's hook
configuration does not fit the existing writers, so I would rather agree on the
shape first than guess.

Two dsh-specific constraints drive this:

  1. Config format. dsh has no Claude-shaped settings.json. It composes
    plugins through a YAML patch layer. The hook bridge it ships
    (@deepseek-ai/dsh-hooks-claude-code) does read a Claude Code hooks.json,
    so TeamAI's existing hook shape is reusable verbatim — but it must be mounted
    by a plugin entry, which lives in a separate YAML file.

  2. Activation. That YAML is applied with a launch flag
    (dsh --profile <name> --patch <file>), not by editing a file dsh reads on
    its own. TeamAI can generate both files idempotently, but it cannot make dsh
    load them the way writing settings.json activates a Claude hook. A profile
    also has a persistent cordis.patch.yml that needs no flag; writing into it
    removes the flag but means editing a file the user owns.

So the honest options are:

  • A — generate + instruct. Write hooks.json and the patch YAML under
    ~/.teamai/, then print the one-line command the user adds to their dsh
    invocation. Smallest change, no ownership questions, but activation stays
    manual and one-time.
  • B — generate + reconcile into the profile. Additionally merge the plugin
    entry into the profile's cordis.patch.yml (idempotent, marker-scoped, like
    the other reconcilers). Fully automatic afterwards, at the cost of writing
    into a user-owned dsh profile and needing a profile-discovery story.

I lean toward A as a first step: it is the smaller change, it matches the
"generate, then tell the user" precedent, and B stays open later if demand
justifies it. Happy to implement B instead if you prefer hooks to be automatic.

There is also one thing users must be told either way, which is not a bug in
either project: teamai writes outside the session workspace (~/.teamai
state, the team-repo clone, the dashboard log), and dsh's default
workspace-write sandbox correctly denies that. The hook runs and exits 0 while
the sync fails with

EROFS: read-only file system, open '~/.teamai/dashboard/events.jsonl'

Running dsh with DSH_PERMISSION_MODE=danger-full-access resolves it. That is a
deliberate trust decision about running team hook commands unconfined, so
whichever option is chosen should surface it in the output or docs rather than
hide it.

Alternatives Considered

  • Add only a settings path for dsh. Rejected: the settings writers emit
    Claude-shaped JSON into a file dsh never reads, so hooks would appear
    installed while doing nothing — strictly worse than today's honest no-op.
  • Leave it and document the manual setup. Reasonable and cheap, but the
    init success message would still need fixing, since it currently promises
    auto-sync that dsh cannot deliver.
  • Keep it out of teamai and ship the bridge as a dsh-side plugin. Viable,
    but it duplicates builtinHookDefs() in another repo and would drift from
    TeamAI's hook set on every change.

Additional Context

Verified end to end on teamai-cli 0.24.0 (main @ 6ed2515), dsh 0.1.5-rc.2,
Node 24, GitHub provider, private team repo:

  1. teamai init --agent dsh — reuses an existing gh login, no interactive
    OAuth; skills land in .dsh/skills and dsh discovers all of them.
  2. With the bridge mounted, pushing a new skill to the team repo and then only
    starting dsh (no manual teamai pull) syncs it automatically; on the next
    session the model loads it through the Skill tool.
  3. Without the bridge, the same flow never syncs — which is today's behavior.

One unrelated observation worth recording for anyone testing this: skills
carrying disable-model-invocation: true (e.g. triage, wayfinder) sync
correctly but do not appear in dsh's model-facing catalog by design. Pick a
plain skill when verifying the sync path, or the result reads like a sync
failure.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/hooks.ts at injectHooksToAllTools(), then inspect the dsh entry in src/types.ts and the init message around src/init.ts:1628. Run teamai hook-dispatch session-start --tool dsh to confirm the existing dispatch path, and compare the openclaw, hermes, and opencode branches. Done means an agreed dsh hook setup is installed or clearly instructed, and init no longer promises automatic sync when it is unavailable.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
cli, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.