deepseek-harness
deepseek-ai
DeepSeek Harness: Everything is a Plugin.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:ringclaw/dsh-ringcentral
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
RingCentral Team Messaging IM channel plugin for deepseek-harness (dsh). It turns a RingCentral Bot Add-in into a first-class frontend for a dsh agent: inbound posts from RingCentral chats drive the agent loop, and assistant replies flow back as threaded RingCentral posts.
English | 中文说明
RingCentral user ──▶ WebSocket (PostAdded) ──▶ im-ringcentral ──▶ ctx.agents ──▶ dsh agent loop ──▶ LLM
│ │
└── admission / session / event ◀────┘
(assistant reply ──▶ RingCentral post, threaded)
The plugin is a pure Cordis plugin following the dsh "Plugins, not loop changes" principle. It speaks the RingCentral Team Messaging v1 REST API + WebSocket subscription stream directly (no external SDK) and reuses the host dsh services for agents, sessions, models, compaction, and tool presentation.
# install into a profile
npx @deepseek-ai/dsh plugin --profile ringcentral add dsh-ringcentral
# start
export RC_BOT_TOKEN="your-bot-jwt"
export DEEPSEEK_API_KEY="your-deepseek-key"
npx @deepseek-ai/dsh --profile ringcentral
Or run the bundled installer: sh install.sh.
cd /path/to/dsh-ringcentral
pnpm install && pnpm build
npx @deepseek-ai/dsh plugin --profile ringcentral add /path/to/dsh-ringcentral
export RC_BOT_TOKEN="your-bot-jwt"
npx @deepseek-ai/dsh --profile ringcentral
The --patch overlay loads the plugin from a local absolute path without
installing it into a profile. Generate the machine-local patch first, then
boot:
cd /path/to/dsh-ringcentral
pnpm install && pnpm build # dist entry (npx dsh cannot resolve .js -> .ts)
node scripts/gen-dev-patch.mjs # writes cordis.local.yml with the real path
export RC_BOT_TOKEN="your-bot-jwt"
npx @deepseek-ai/dsh web --patch ./cordis.local.yml
Use pnpm dev (tsc --watch) while iterating: the loader hot-reloads the
plugin whenever dist/ changes. Pointing the patch at src/index.ts only
works inside a deepseek-harness source tree (pnpm dsh), not with the
npx-installed package.
TeamMessaging, ReadAccounts, WebSocketsSubscription.RC_BOT_TOKEN.Optional owner credentials (JWT REST API app for your own account, with
TeamMessaging + WebSocketsSubscription + ReadMessages) give
ringcentral_get_recent_messages owner read fallback and outbound owner
fallback. The history tool always reads through the bot client
(RC_BOT_TOKEN) first; without owner credentials it uses only the bot
client (chats the bot is a member of).
Config follows dsh practice: the cordis config tree is the single source
(profile cordis.patch.yml / cordis.yml), with Schema defaults applied
automatically. Secrets (RC_BOT_TOKEN, RC_USER_CLIENT_ID,
RC_USER_CLIENT_SECRET, RC_USER_JWT_TOKEN) resolve through the host
credentials domain — an explicit config value wins, then environment →
managed $DSH_HOME/.credentials.yaml → project/user .env, with the plain
process environment as the final fallback. RC_SERVER_URL works the other
way for operational overrides (e.g. sandbox): the environment value wins over
the configured/default server. Secrets are never persisted into profile YAML.
To drive any other setting from an environment variable, use the cordis
loader's !!js tag (double bang — a single !js is not evaluated), e.g.
access.groupMode: !!js process.env.RC_GROUP_MODE ?? 'open'.
The access-control block mirrors @tencent-connect/dsh-qqbot exactly
(QQ's c2c surface is dm here). RingCentral's three non-DM chat types
(Team / Everyone / Group) are all governed by the group surface.
| Config | Type | Default | Description |
|---|---|---|---|
botToken |
string | required | Bot static JWT (env: RC_BOT_TOKEN) |
ownerCredentials.clientId / clientSecret / jwt |
string | - | Owner JWT (env: RC_USER_*) |
server |
string | https://platform.ringcentral.com |
API server (env: RC_SERVER_URL) |
access.dmMode |
enum | open |
DM handling: disabled, allowlist, open |
access.dmAllow |
string[] | [] |
Person ids allowed in DMs; empty or ["*"] = allow all |
access.groupMode |
enum | open |
Group handling: disabled, allowlist, open |
access.groupAllow |
string[] | [] |
Chat ids allowed in groups; empty or ["*"] = allow all |
requireMention |
boolean | true |
Require @-mention in group chats |
groupPrompt |
string | - | Extra system prompt for group chats |
directPrompt |
string | - | Extra system prompt for DMs |
processingPlaceholder.enabled |
boolean | false |
Post 👀 → ⏳ while the agent works (texts/delay are fixed) |
historyMessageLimit |
number | 250 |
Default record count for the history tool |
homeChannel |
string | - | Fallback target for the history tool |
textChunkLimit |
number | 4000 |
Max chars per outgoing post |
provider / model |
string | host default | LLM route (fallback chain: per-peer prefs → config → host agentDefaultModel) |
preset |
string | - | Agent preset id |
cwd |
string | process.cwd() |
Agent working directory |
sessionIdleTimeout |
number | 1800000 |
Idle session eviction (ms) |
showToolResults |
boolean | false |
Show successful tool results (errors always show) |
debug |
boolean | false |
Debug logging (includes inbound message logs) |
Every row except the secret entries is editable from the Web GUI card described below.
Installed into a web profile (dsh plugin --profile web add dsh-ringcentral),
the plugin registers a settings namespace and a configuration card under
Settings → Plugins → Plugin configuration. The card:
RC_BOT_TOKEN, RC_USER_*) as write-only
controls: it reports only whether each is configured and writes through the
settings domain (fallback to the host credentials domain where reachable);
secret values are never sent back to the page. Credential changes apply
live: within ~1s the plugin rebuilds its REST clients and reconnects the
websocket with the new token — no restart, and the per-peer session context
survives rotation (the account key derives from the stable bot identity).Without a settings service (a bare custom cordis.yml), the card simply does
not mount and the plugin behaves exactly as before.
| Command | Description |
|---|---|
/new (/reset, /clear) |
Start a new session (clear context) |
/compact |
Compress session history (summary replaces old records) |
/model |
Show or switch the model |
/stop |
Abort the current generation |
/rc-ping |
Connectivity test |
/rc-version |
Plugin version |
/rc-status |
Current session status |
/rc-help |
List all commands |
sessionKey: ringcentral:<accountScopeKey>:<scope>:<peerId> where scope is
direct (peer = person id), group (peer = Group DM chat id), or
channel (peer = Team/Everyone chat id), and accountScopeKey is a
SHA-256 fingerprint of server + bot token. The SessionId is derived
deterministically (SHA-256), so the same user/chat always routes to the same
session and survives restarts. Resolution order: in-process reuse → persisted
resume → fresh create.
When the agent calls ask_user_question, the plugin renders the question
into the chat (thread-anchored) and waits for the user to reply in the same
session:
multi_select
accepts "1, 3"), or type a free-text answer.Note: the provider registers on the userQuestions service seam. In a web
profile the GUI provider takes precedence — questions then appear in the web
UI, not in RingCentral. Use a dedicated profile for IM-only operation.
inject = ['agents']; tools/compaction/presets are optional seams.pnpm install
pnpm build # or: pnpm dev (watch)
pnpm test
pnpm typecheck
# run against the npx-installed dsh
export RC_BOT_TOKEN="xxx"
node scripts/gen-dev-patch.mjs
npx @deepseek-ai/dsh web --patch ./cordis.local.yml
scripts/gen-dev-patch.mjs emits the gitignored cordis.local.yml directly,
with the entry resolved to an absolute path on the machine (default
dist/index.js; pass src/index.ts to point at the TypeScript entry).
pnpm test runs 143 cases across 17 files. No network is touched — clients,
websockets, and harness services are stubbed.
| File | Covers |
|---|---|
tests/accounts.test.ts |
Secret resolution precedence, schema-default fallbacks, legacy __FROM_ENV__ compat, numeric clamping |
tests/account-scope-key.test.ts |
Stable-identity account key: rotation keeps session keys; token-fingerprint fallback |
tests/credentials.test.ts |
resolveSecret chain (service → env → managed file), inject handle, file parser, file watcher |
tests/settings-merge.test.ts |
Live config merge into runtime/account copies, secret-field exemption, server/placeholder branches |
tests/settings-redact.test.ts |
role('secret') wire redaction and its sidecar |
tests/settings-section.test.ts |
Cross-version settings mounting: installSection (harness ≥ 0.1.2) vs legacy installSettingsSection fallback (≤ 0.1.1-rc.2), failure containment |
tests/monitor.test.ts |
WS frame extraction, post filtering (own echoes, answer markers), websocket URL building |
tests/rotation-scheduler.test.ts |
Debounce coalescing, single-flight, dispose (extracted module, fake timers) |
tests/gateway-rotation.test.ts |
Fake-harness integration: rotate() rebuilds clients, re-registers the history tool, reconnects the websocket; no-op on same token; fail-safe on missing token |
tests/session-manager.test.ts |
Deterministic session ids, per-scope isolation, setAccountKey re-derivation |
tests/inbound.test.ts |
Admission (dm/group allowlists), mention gating, prompt injection, command routing |
tests/outbound.test.ts |
Reply chunking, showToolResults gating, placeholder lifecycle |
tests/history-tool.test.ts |
Reader precedence (bot → owner), record clamping, target resolution |
tests/threading.test.ts |
Thread-participation tracking and follow-up classification |
tests/targets.test.ts |
Reply-target resolution and thread anchoring |
tests/markdown.test.ts |
Mini-Markdown conversion |
tests/user-questions.test.ts |
IM answer surface for agent questions |
tests/debug-log.test.ts |
On-disk debug log format and gating |
| Symptom | Likely cause | Fix |
|---|---|---|
| Plugin not starting | RC_BOT_TOKEN missing |
Set RC_BOT_TOKEN (env or $DSH_HOME/.credentials.yaml) or botToken in config |
Debug log shows [cred] … service=absent |
In desktop-style deployments, bundle-row plugins cannot read host services via ctx.get (isolate scoping) |
Not an error: 0.3.15+ ships a file fallback and 0.3.17 an inject-based path — see docs/credentials-service-unreachable.md |
| Bot never replies in a group chat | access.groupMode: disabled, not allowlisted, or no mention |
Check access.groupMode / access.groupAllow and @-mention the bot |
| DM ignored | access.dmMode: disabled or sender not in access.dmAllow |
Check access.dmMode / access.dmAllow |
| History tool returns nothing | Chat not visible to bot or owner | Reads try the bot first, then the owner; pass a bare chat id or channel:<chatId> and make sure one client is a member |
| Agent question not answered from RingCentral | Web GUI provider registered (web profile) | Questions go to the web UI; use a dedicated profile or answer in the web UI |
| Plugin config card missing from the Web GUI, or save/reset fails, after a harness upgrade | Harness ≥ 0.1.2 moved settings registration to settings.installSection() and removed connection.api; ≤ 0.4.1 used the removed APIs |
Upgrade to dsh-ringcentral ≥ 0.4.2 (dual-path: installSection on ≥ 0.1.2, legacy fallback on ≤ 0.1.1-rc.2) |
dsh-ringcentral ≥ 0.4.2 supports both harness lines: ≥ 0.1.2-alpha (settings via settings.installSection, writes via settingsScope.mutate) and 0.1.1-rc.2 and earlier (legacy installSettingsSection + connection.api.settings.mutate fallback).dsh-ringcentral ≤ 0.4.1 requires the ≤ 0.1.1-rc.2 settings API; it does not start on harness ≥ 0.1.2.CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: 无有效分类标签。