跳到主要内容

dsh-plugin-teambition

已验证

dsh-plugin-teambition · v1.1.3 · MIT

Connect Teambition Open Platform: projects, task types, tasks (by type / parent task), task status transition, requirements, bugs, documents, test plans/cases, worktime, stats.

安装

dsh plugin add dsh-plugin-teambition

用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。

源码

发布到 npm 但没有公开仓库。安装前请检查包内容。

标签

作者

说明文档

dsh-plugin-teambition

A DeepSeek Harness Studio plugin that connects to the Teambition Open Platform and exposes model tools to query projects, task types, tasks (by type / parent task), workflow statuses, requirements, bugs, documents, test plans/cases, worktime and statistics — plus one write operation: updating a task's workflow status.

Implements the design in dsh-plugin-teambition-开发设计文档.md.

Tools

Tool Endpoint Notes
tb_set_config — configure credentials + connectivity check
tb_get_token POST /appToken verify appId/appSecret; never echoes the secret
tb_list_projects GET /v3/project/query by id / name / uniqueIdPrefix
tb_list_task_types GET /v3/project/{id}/scenariofieldconfig/search task type name ↔ sfcId
tb_list_tasks GET /v3/project/{id}/task/query by task type + project + parent task
tb_get_task_detail GET /v3/task/query by taskId / shortIds / parentTaskId
tb_list_task_statuses GET /v3/task/{taskId}/tfs statuses of the task's workflow
tb_update_task_status PUT /v3/task/{taskId}/taskflowstatus the only write tool — safe by default
tb_project_stats GET /v3/project/{id}/task/count 任务计数

Changed in v1.1.3: tb_list_tasks now defaults to executor = config.userId. Omitting executorId queries only the configured user's tasks; pass executorId: "all" (or "*") for the whole project, or another user id to filter by that member. The implicit filter is skipped when raw q is supplied, and is always echoed in warnings + the returned tql so a narrowed query is never silent.

Removed in v1.1.2: tb_list_documents, tb_list_testplans, tb_list_testcases, tb_worktime. Their endpoints exist but return code 403 — the app lacks the permission, and it is not being requested. Keeping dead-but-listed tools is worse than removing them. tb_list_testplans additionally used a wrong path (/v3/project/{id}/testplan; the official one is /v3/project/{id}/testplan/query?testplanIds=…, where testplanIds is required — there is no "list all test plans" endpoint).

Removed in v1.1.1: tb_list_stories and tb_list_bugs. The endpoints they called (GET /v3/project/{id}/story/query and /bug/query) do not exist — the API returns code 421 "url not found". In Teambition, 需求 / 缺陷 are task types (sfcId), not standalone resources. Query them through tb_list_tasks with sfcId (or taskType): 需求 = the type whose tasks have storygroupId set; 缺陷 = the type whose workflow contains 修复中 / 已解决 / 重复提交.

tb-core:project.sfc:list is still not granted, so tb_list_task_types currently returns code 403 — pass sfcId directly until it is. The endpoints they called (GET /v3/project/{id}/story/query and /bug/query) do not exist — the API returns code 421 "url not found". In Teambition, 需求 / 缺陷 are task types (sfcId), not standalone resources. Query them through tb_list_tasks with sfcId (or taskType): 需求 = the type whose tasks have storygroupId set; 缺陷 = the type whose workflow contains 修复中 / 已解决 / 重复提交.

Four other tools (tb_list_documents, tb_list_testplans, tb_list_testcases, tb_worktime) return code 403 in this tenant: their endpoints do exist, but the app lacks the permission. They will work once granted.

Auth

Enterprise app mode: appId + appSecret + orgId. The plugin signs a local HS256 JWT ({ _appId, iat, exp }) as the appAccessToken and sends Authorization: Bearer <jwt> plus X-Tenant-Type: organization and X-Tenant-Id: <orgId>.

iat is aligned to the hour boundary with a 660s margin (exp = iat + 3960), so the token is identical within a natural hour and is reused without a round-trip.

A raw token is also accepted as a fallback (used only when appId/appSecret are absent). appSecret is never echoed in tool output.

Task status update is safe by default

tb_update_task_status(taskId, statusName="已完成")                  → resolves + previews, NO write
tb_update_task_status(taskId, statusName="已完成", confirm=true)    → performs the PUT
  • Without confirm: true the tool only resolves the target status and returns a preview. No PUT is issued.
  • Status names are resolved locally against GET /v3/task/{taskId}/tfs. A name that is ambiguous or not found is refused — the plugin never guesses.
  • Write operations are never retried automatically, and 5xx never triggers a replay.
  • One call affects exactly one task; there is no batch mode.

Configuration items

orgId, appId, appSecret, projectId, userId are stored in the teambition settings namespace and survive restart. baseUrl and curlPath are also configurable (private cloud / non-Windows hosts).

  • projectId is the default project for project-scoped tools (still overridable per call).
  • userId is always sent as X-Operator-Id (visibility filtering and write attribution), and it is now the default executor filter: tb_list_tasks with executorId omitted queries only the configured user's tasks (addressed as "my tasks"). Pass executorId: "all" (or "*") to query the whole project, or another user id to filter by that member. The implicit filter never applies when raw q is supplied (raw TQL wins), and it is always echoed in warnings + the returned tql, so a narrowed query is never silent.
teambition:
  orgId: ""
  appId: ""
  appSecret: ""
  projectId: ""
  userId: ""

Verified against the live API

npm run probe runs a read-only check (tools/probe.mjs, never calls any write endpoint) and confirmed:

  • TQL filter field for task type is scenarioId (not sfcId) — filtered query returned 50 tasks, all matching the requested sfcId.
  • TQL filter field for parent task is parentId (not parentTaskId) — filtered query returned 3 direct children, all matching, including a known child.
  • GET /v3/task/{taskId}/tfs returns the workflow statuses.

Two environment facts worth knowing:

  • GET /v3/project/{id}/scenariofieldconfig/search can return HTTP 200 with code: 403 when the app lacks tb-core:project.sfc:list. Success is therefore decided on the business envelope, not the HTTP status. Without that permission, tb_list_task_types and the taskType name-resolution path in tb_list_tasks will fail; grant the permission and republish the app.
  • A workflow may have more than one kind: "end" status (e.g. 已完成 and 已取消), so "the status with kind=end" is not a safe way to pick a target. Pass an explicit statusName or taskflowstatusId.

Permissions required

Permission Used by
tb-core:project:get tb_list_projects
tb-core:project.sfc:list tb_list_task_types, tb_list_tasks (taskType resolution)
tb-core:task:list tb_list_tasks
tb-core:task:get tb_get_task_detail, tb_list_task_statuses
tb-core:task:update tb_update_task_status (write)

Permission changes require republishing the app and re-approval by the enterprise admin.

Development

npm test         # 51 unit tests (TQL builder, HTTP encoding, envelope, status resolution, JWT)
npm run smoke    # 25 offline integration checks (tool registration, generated curl argv, write safety, output rendering)
npm run probe    # read-only verification against the live API (needs configured credentials)

Tool output rendering (learned the hard way)

defineTool requires output: { schema, render }, and DSH calls render(args, result) — the second parameter is what execute returned. A render that returns an empty array (or only reads the first parameter) silently discards every result: the tool runs, but the model sees nothing. This plugin therefore renders the result explicitly:

const renderJson = (args, result) => [{
  type: 'text',
  text: typeof result === 'string' ? result : JSON.stringify(result ?? null, null, 2),
}]

Related requirement: a tool's return value must be lossless JSON. An undefined property is rejected at runtime ("must be lossless JSON data"), so nullable fields use || null and optional keys are omitted rather than set to undefined. Both rules are covered by regression checks in npm run smoke.

Layout:

lib/constants.js   field-name traps (scenarioId/sfcId, parentId/parentTaskId) + endpoint table
lib/tql.js         TQL builder with quoting and an ORDER BY allow-list
lib/http.js        URL building (encoding) + response envelope unwrapping
lib/task.js        task field projection + status-name resolution (refuses ambiguity)
lib/jwt.js         pure-JS HS256 appAccessToken signing (no node:crypto dependency)
lib/index.js       Cordis plugin: settings, transport, tools
tools/probe.mjs    dev-only read-only probe (not shipped)

Install

dsh plugin add dsh-plugin-teambition

Or reference the .tgz from your profile package.json as a file: dependency.

Notes

  • Transport uses the harness subprocess service to run curl (the dynamic Host sandbox has no fetch). Default C:\Windows\System32\curl.exe, overridable via curlPath.
  • Success is decided on the business envelope { code, errorMessage, result }, not the HTTP status alone.
  • Task results are projected to a default field set to control context cost; use fields for a few whitelisted extras.

License

MIT