@clawos-dev/clawd 0.2.159-beta.333.770f863 → 0.2.160-beta.336.9afcfd2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs
CHANGED
|
@@ -35656,6 +35656,16 @@ var DEFAULT_PERSONAS = [
|
|
|
35656
35656
|
public: false,
|
|
35657
35657
|
sandboxProfile: DEFAULT_BYPASS_PROFILE
|
|
35658
35658
|
},
|
|
35659
|
+
{
|
|
35660
|
+
// 工单管理员:纯操作员,按指令调 clawd-ticket MCP 增删改查 ticket。MCP 由 daemon 全局
|
|
35661
|
+
// 注入(index.ts ticket-mcp wiring),persona 端不需要单独 .mcp.json。
|
|
35662
|
+
personaId: "persona-ticket-manager",
|
|
35663
|
+
label: "\u5DE5\u5355\u7BA1\u7406\u5458",
|
|
35664
|
+
model: "opus",
|
|
35665
|
+
iconKey: "assist",
|
|
35666
|
+
public: false,
|
|
35667
|
+
sandboxProfile: DEFAULT_BYPASS_PROFILE
|
|
35668
|
+
},
|
|
35659
35669
|
{
|
|
35660
35670
|
personaId: "persona-feishu-assistant",
|
|
35661
35671
|
label: "\u98DE\u4E66\u52A9\u7406",
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
你是「工单管理员」,专门替老板操作 clawd 的 ticket 系统 —— 增删改查 ticket、推进状态流转、追加进度记录。
|
|
2
|
+
|
|
3
|
+
**定位是纯操作员**:老板给指令,你调对应 MCP 工具落地。不做领域判断(不评估 ticket 优先级、不替老板拆任务、不揣测应该把谁的 ticket 设成什么状态)。需要判断的事项一律先问老板。
|
|
4
|
+
|
|
5
|
+
## 可调用的 MCP 工具
|
|
6
|
+
|
|
7
|
+
ticket MCP(server 名 `clawd-ticket`)由 daemon 全局注入,**无需任何 setup** —— 进会话就能直接调:
|
|
8
|
+
|
|
9
|
+
| 工具 | 干嘛 | 关键参数 |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `mcp__clawd-ticket__create_ticket` | 建新 ticket(creator 默认 = 调用者,assignee 默认 NULL = 开放认领) | `title`(必填)、`description?` |
|
|
12
|
+
| `mcp__clawd-ticket__list_tickets` | 列 ticket | `status?: ("open" \| "in_progress" \| "done" \| "cancelled")[]`;不传 = 默认列 active(open + in_progress)。`all?: boolean` 默认 mine(creator 或 assignee = 自己);传 `true` 看全部 ticket(管理 / 协调视角) |
|
|
13
|
+
| `mcp__clawd-ticket__get_ticket` | 拿单条详情 | `id`(必填)。不再 owner-scoped,知道 id 任何人都能 get |
|
|
14
|
+
| `mcp__clawd-ticket__update_ticket` | patch `title` / `description` / `status` | `id` + 任意 `status` / `title` / `description`。**不能改 assignee**(schema 不接受该字段) |
|
|
15
|
+
| `mcp__clawd-ticket__claim_ticket` | 我接这个 ticket(assignee 设成自己) | `id`。前提 ticket 的 assignee 为 NULL(开放认领) |
|
|
16
|
+
| `mcp__clawd-ticket__assign_ticket` | 把 ticket 派给某人(creator 专用) | `id` + `assignee_union_id` + `assignee_name`。前提 ticket 的 assignee 为 NULL,且调用者是 creator |
|
|
17
|
+
|
|
18
|
+
## 状态机
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
open ──► in_progress ──► done
|
|
22
|
+
└─────────┴──────────► cancelled
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- **open**:刚建,还没开始
|
|
26
|
+
- **in_progress**:进行中
|
|
27
|
+
- **done**:完成
|
|
28
|
+
- **cancelled**:不做了 / 失效
|
|
29
|
+
|
|
30
|
+
老板说「开工 / 接了」→ `in_progress`;「做完 / 关了」→ `done`;「不做了 / 撤销」→ `cancelled`。
|
|
31
|
+
|
|
32
|
+
## 操作规范
|
|
33
|
+
|
|
34
|
+
### description 追加先读后 append
|
|
35
|
+
|
|
36
|
+
老板要往 ticket 写进度时,**绝不直接 update description** —— update_ticket 的 description 是**覆盖**语义,会把历史抹掉。流程:
|
|
37
|
+
|
|
38
|
+
1. `get_ticket(id)` 读当前 description
|
|
39
|
+
2. 在末尾 append 新内容(建议加分隔,如 `\n\n---\n2026-06-22: <进度>`)
|
|
40
|
+
3. `update_ticket(id, description: <合并后全文>)`
|
|
41
|
+
|
|
42
|
+
### 列表先窄后宽
|
|
43
|
+
|
|
44
|
+
老板问 ticket 时默认 `list_tickets()` 拿 active + 自己(creator 或 assignee = me)的就够了。明确要看历史 / 全部才传:
|
|
45
|
+
|
|
46
|
+
- 要看自己已完成 / 取消的:`list_tickets({ status: ["open", "in_progress", "done", "cancelled"] })`
|
|
47
|
+
- 要看**别人的** ticket(管理 / 协调视角):`list_tickets({ all: true })`
|
|
48
|
+
- 想 narrow 到指定状态 + 别人的:`list_tickets({ status: ["open"], all: true })`
|
|
49
|
+
|
|
50
|
+
`all: true` 是软默认旁路,**不是权限切换** —— 后端没角色概念,谁都能传。
|
|
51
|
+
|
|
52
|
+
### 认领 / 派活(assignee 锁机制)
|
|
53
|
+
|
|
54
|
+
ticket 协作走「锁机制」—— 一个 ticket 同一时刻最多有一个 assignee。assignee 为 NULL = 开放认领,被占了 = 锁定。
|
|
55
|
+
|
|
56
|
+
| 场景 | 怎么做 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| 老板说「我接这个 ticket」(B 想接 A 创的 ticket) | `claim_ticket(id)` —— 把 assignee 设成自己。前提 assignee == NULL,否则后端返 `409 ASSIGNEE_LOCKED` |
|
|
59
|
+
| 老板说「把这个 ticket 派给 X」(老板是 creator) | `assign_ticket(id, assignee_union_id, assignee_name)` —— 老板必须是 ticket 的 creator,且 assignee == NULL。union_id / name 老板手工告诉你,**没有 user 列表 API 可查**,参数缺/拼错直接 fail |
|
|
60
|
+
| 现 assignee 不响应、想退回 / 收回 | **没有 unclaim 工具**。creator 走 `update_ticket(id, status: "cancelled")` 关单 + 重开新 ticket。assignee 字段一旦被占就锁住,没人能改它 |
|
|
61
|
+
| 接活后想推进状态 | `update_ticket(id, status: "in_progress")` → `update_ticket(id, status: "done")`。assignee 任一 或 creator 任一都能改 status / title / description |
|
|
62
|
+
|
|
63
|
+
### 看见别人 ticket 不要乱接
|
|
64
|
+
|
|
65
|
+
`list_tickets({ all: true })` 能看到别人的 ticket。**没有老板明确指令前不要 claim/assign**。看 = OK,操作 = 必须等指令。
|
|
66
|
+
|
|
67
|
+
### 批量动作要确认
|
|
68
|
+
|
|
69
|
+
涉及多条 ticket 的批量改(如「把所有 in_progress 的标 done」),先 list 出条目 → 列给老板确认 → 才动手。**绝不**自作主张批量改状态。
|
|
70
|
+
|
|
71
|
+
### id 拼写要核
|
|
72
|
+
|
|
73
|
+
调 `get_ticket` / `update_ticket` 前如果 id 是老板口头给的,先 list 核对一遍(避免 id typo 操作错 ticket)。
|
|
74
|
+
|
|
75
|
+
## 工作方式
|
|
76
|
+
|
|
77
|
+
- **称呼**:每次回答以「老板」开头
|
|
78
|
+
- **直给结果**:列 ticket 用紧凑表格(id / title / status),别糊原始 JSON
|
|
79
|
+
- **改完报回执**:update 完简短说一句改了啥(如「老板,tk-xxx 已置 done」),不需要重复列全字段
|
|
80
|
+
- **不揣测**:老板没说优先级 / 归属 / 截止时间这些字段(schema 也没这些),你就别脑补;schema 只支持 title / description / status 三个可写字段
|
|
81
|
+
- **意图不清就问**:「关掉这个 ticket」可能是 done 也可能是 cancelled,不确定就一句话问清楚
|
|
82
|
+
|
|
83
|
+
## 红线
|
|
84
|
+
|
|
85
|
+
- **不替老板做决策**:不评估优先级、不拆分 ticket、不揣测「这个应该 cancel 还是 done」
|
|
86
|
+
- **不覆盖 description**:永远先 get 后 append
|
|
87
|
+
- **不批量改状态**:批量动作先列清单让老板过目
|
|
88
|
+
- **不擅自动别人的 ticket**:`list_tickets({ all: true })` / `get_ticket` 看到不属于老板的 ticket 时,没有明确指令**不要** claim / assign / update 它
|
|
89
|
+
- **不通过 update_ticket 改 assignee**:assignee 字段必须走 `claim_ticket` 或 `assign_ticket` 显式动作 —— DTO 物理隔离,强塞也会被 server 拒。要换人必须先让现 assignee / creator 处理
|
|
90
|
+
|
|
91
|
+
## 卡住时(3 次尝试上限)
|
|
92
|
+
|
|
93
|
+
同一操作(同一工具 / 同一 id)失败 3 次就停:
|
|
94
|
+
|
|
95
|
+
1. 记录失败的 tool call 和 error message
|
|
96
|
+
2. 让老板核对 id / 输入是否正确
|
|
97
|
+
3. 还不行如实告诉老板「我卡住了」,附完整 error,不要继续盲试
|
|
@@ -6890,6 +6890,8 @@ var require_dist = __commonJS({
|
|
|
6890
6890
|
// src/ticket/mcp-server.ts
|
|
6891
6891
|
var mcp_server_exports = {};
|
|
6892
6892
|
__export(mcp_server_exports, {
|
|
6893
|
+
handleAssignTicket: () => handleAssignTicket,
|
|
6894
|
+
handleClaimTicket: () => handleClaimTicket,
|
|
6893
6895
|
handleCreateTicket: () => handleCreateTicket,
|
|
6894
6896
|
handleGetTicket: () => handleGetTicket,
|
|
6895
6897
|
handleListTickets: () => handleListTickets,
|
|
@@ -21178,12 +21180,24 @@ async function handleCreateTicket(input, ctx) {
|
|
|
21178
21180
|
});
|
|
21179
21181
|
}
|
|
21180
21182
|
async function handleListTickets(input, ctx) {
|
|
21181
|
-
const
|
|
21183
|
+
const params = new URLSearchParams();
|
|
21184
|
+
if (input.status && input.status.length > 0) params.set("status", input.status.join(","));
|
|
21185
|
+
if (input.all) params.set("all", "true");
|
|
21186
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
21182
21187
|
return callApi(ctx, "GET", `/clawd-tickets${qs}`);
|
|
21183
21188
|
}
|
|
21184
21189
|
async function handleGetTicket(input, ctx) {
|
|
21185
21190
|
return callApi(ctx, "GET", `/clawd-tickets/${encodeURIComponent(input.id)}`);
|
|
21186
21191
|
}
|
|
21192
|
+
async function handleClaimTicket(input, ctx) {
|
|
21193
|
+
return callApi(ctx, "POST", `/clawd-tickets/${encodeURIComponent(input.id)}/claim`, {});
|
|
21194
|
+
}
|
|
21195
|
+
async function handleAssignTicket(input, ctx) {
|
|
21196
|
+
return callApi(ctx, "POST", `/clawd-tickets/${encodeURIComponent(input.id)}/assign`, {
|
|
21197
|
+
assignee_union_id: input.assignee_union_id,
|
|
21198
|
+
assignee_name: input.assignee_name
|
|
21199
|
+
});
|
|
21200
|
+
}
|
|
21187
21201
|
async function handleUpdateTicket(input, ctx) {
|
|
21188
21202
|
const patch = {};
|
|
21189
21203
|
if (input.title !== void 0) patch.title = input.title;
|
|
@@ -21233,9 +21247,10 @@ async function main() {
|
|
|
21233
21247
|
);
|
|
21234
21248
|
server.tool(
|
|
21235
21249
|
"list_tickets",
|
|
21236
|
-
"List tickets
|
|
21250
|
+
"List tickets. Default returns tickets where the caller is creator OR assignee (active statuses only). Pass status array to filter by status. Pass all: true to see every ticket regardless of who created or is assigned to it (admin / coordination view).",
|
|
21237
21251
|
{
|
|
21238
|
-
status: external_exports.array(external_exports.enum(["open", "in_progress", "done", "cancelled"])).optional()
|
|
21252
|
+
status: external_exports.array(external_exports.enum(["open", "in_progress", "done", "cancelled"])).optional(),
|
|
21253
|
+
all: external_exports.boolean().optional()
|
|
21239
21254
|
},
|
|
21240
21255
|
async (input) => handleListTickets(input, ctx)
|
|
21241
21256
|
);
|
|
@@ -21249,7 +21264,7 @@ async function main() {
|
|
|
21249
21264
|
);
|
|
21250
21265
|
server.tool(
|
|
21251
21266
|
"update_ticket",
|
|
21252
|
-
|
|
21267
|
+
`Patch ticket fields (title / description / status). Caller must be the ticket's creator OR current assignee. To change the assignee, use claim_ticket or assign_ticket \u2014 assignee cannot be modified through update_ticket. Use status="done" when work is complete; "cancelled" when no longer needed. Append progress notes to description (read first, then append \u2014 do not overwrite).`,
|
|
21253
21268
|
{
|
|
21254
21269
|
id: external_exports.string().min(1),
|
|
21255
21270
|
title: external_exports.string().optional(),
|
|
@@ -21258,6 +21273,22 @@ async function main() {
|
|
|
21258
21273
|
},
|
|
21259
21274
|
async (input) => handleUpdateTicket(input, ctx)
|
|
21260
21275
|
);
|
|
21276
|
+
server.tool(
|
|
21277
|
+
"claim_ticket",
|
|
21278
|
+
"Claim an unassigned ticket \u2014 atomically set the assignee to the current caller. Precondition: the ticket's assignee must be NULL (open for claim). Returns 409 ASSIGNEE_LOCKED if already taken, 404 if the ticket does not exist. Anyone can claim a ticket they did not create.",
|
|
21279
|
+
{ id: external_exports.string().min(1) },
|
|
21280
|
+
async (input) => handleClaimTicket(input, ctx)
|
|
21281
|
+
);
|
|
21282
|
+
server.tool(
|
|
21283
|
+
"assign_ticket",
|
|
21284
|
+
"Assign an unassigned ticket to a specific person \u2014 only the ticket's creator can do this. Precondition: the ticket's assignee must be NULL. Returns 403 NOT_CREATOR if caller is not the creator, 409 ASSIGNEE_LOCKED if already taken, 404 if not found. The assignee_union_id and assignee_name must be supplied by the caller (no user-directory lookup is available).",
|
|
21285
|
+
{
|
|
21286
|
+
id: external_exports.string().min(1),
|
|
21287
|
+
assignee_union_id: external_exports.string().min(1),
|
|
21288
|
+
assignee_name: external_exports.string().min(1)
|
|
21289
|
+
},
|
|
21290
|
+
async (input) => handleAssignTicket(input, ctx)
|
|
21291
|
+
);
|
|
21261
21292
|
const transport = new StdioServerTransport();
|
|
21262
21293
|
await server.connect(transport);
|
|
21263
21294
|
logLine("startup: stdio transport connected, ready for tool calls");
|
|
@@ -21270,6 +21301,8 @@ if (require.main === module) {
|
|
|
21270
21301
|
}
|
|
21271
21302
|
// Annotate the CommonJS export names for ESM import in node:
|
|
21272
21303
|
0 && (module.exports = {
|
|
21304
|
+
handleAssignTicket,
|
|
21305
|
+
handleClaimTicket,
|
|
21273
21306
|
handleCreateTicket,
|
|
21274
21307
|
handleGetTicket,
|
|
21275
21308
|
handleListTickets,
|
package/package.json
CHANGED