Chuyển đến nội dung chính

dsh-plugin-teambition

Đã xác minh

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.

Cài đặt

dsh plugin add dsh-plugin-teambition

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.

Thẻ

Tác giả

Readme

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