@nextclaw/channel-plugin-feishu 0.2.16 → 0.2.17

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.
@@ -0,0 +1,95 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
3
+ import { assertLarkOk, createToolContext, handleInvokeError, json, registerTool, StringEnum } from "./user-tool-helpers.js";
4
+
5
+ const CommentSchema = Type.Union([
6
+ Type.Object({ action: Type.Literal("create"), task_guid: Type.String(), content: Type.String(), reply_to_comment_id: Type.Optional(Type.String()) }),
7
+ Type.Object({ action: Type.Literal("list"), resource_id: Type.String(), direction: Type.Optional(StringEnum(["asc", "desc"])), page_size: Type.Optional(Type.Number()), page_token: Type.Optional(Type.String()) }),
8
+ Type.Object({ action: Type.Literal("get"), comment_id: Type.String() }),
9
+ ]);
10
+
11
+ export function registerFeishuTaskCommentTool(api: OpenClawPluginApi) {
12
+ const { toolClient } = createToolContext(api, "feishu_task_comment");
13
+ registerTool(api, {
14
+ name: "feishu_task_comment",
15
+ label: "Feishu Task Comment",
16
+ description: "按本人身份创建、获取、列出任务评论。",
17
+ parameters: CommentSchema,
18
+ async execute(_toolCallId, params) {
19
+ const payload = params as {
20
+ action: "create" | "get" | "list";
21
+ task_guid?: string;
22
+ content?: string;
23
+ reply_to_comment_id?: string;
24
+ resource_id?: string;
25
+ direction?: "asc" | "desc";
26
+ page_size?: number;
27
+ page_token?: string;
28
+ comment_id?: string;
29
+ };
30
+ try {
31
+ const client = toolClient();
32
+ if (payload.action === "create") {
33
+ const response = await client.invoke(
34
+ "feishu_task_comment.create",
35
+ (sdk, opts) =>
36
+ sdk.task.v2.comment.create(
37
+ {
38
+ params: { user_id_type: "open_id" as never },
39
+ data: {
40
+ content: payload.content,
41
+ resource_type: "task",
42
+ resource_id: payload.task_guid,
43
+ reply_to_comment_id: payload.reply_to_comment_id,
44
+ },
45
+ },
46
+ opts,
47
+ ),
48
+ { as: "user" },
49
+ );
50
+ assertLarkOk(response);
51
+ return json({ comment: (response.data as { comment?: unknown } | undefined)?.comment });
52
+ }
53
+ if (payload.action === "get") {
54
+ const response = await client.invoke(
55
+ "feishu_task_comment.get",
56
+ (sdk, opts) =>
57
+ sdk.task.v2.comment.get(
58
+ { path: { comment_id: payload.comment_id! }, params: { user_id_type: "open_id" as never } },
59
+ opts,
60
+ ),
61
+ { as: "user" },
62
+ );
63
+ assertLarkOk(response);
64
+ return json({ comment: (response.data as { comment?: unknown } | undefined)?.comment });
65
+ }
66
+ const response = await client.invoke(
67
+ "feishu_task_comment.list",
68
+ (sdk, opts) =>
69
+ sdk.task.v2.comment.list(
70
+ {
71
+ params: {
72
+ resource_type: "task",
73
+ resource_id: payload.resource_id,
74
+ direction: payload.direction,
75
+ page_size: payload.page_size,
76
+ page_token: payload.page_token,
77
+ user_id_type: "open_id" as never,
78
+ },
79
+ },
80
+ opts,
81
+ ),
82
+ { as: "user" },
83
+ );
84
+ assertLarkOk(response);
85
+ return json({
86
+ comments: (response.data as { items?: unknown[] } | undefined)?.items ?? [],
87
+ has_more: (response.data as { has_more?: boolean } | undefined)?.has_more ?? false,
88
+ page_token: (response.data as { page_token?: string } | undefined)?.page_token,
89
+ });
90
+ } catch (error) {
91
+ return handleInvokeError(error, api);
92
+ }
93
+ },
94
+ }, { name: "feishu_task_comment" });
95
+ }
@@ -0,0 +1,10 @@
1
+ import { parseTimeToTimestampMs } from "./user-tool-helpers.js";
2
+
3
+ export function normalizeTaskTime(input?: { timestamp?: string; is_all_day?: boolean }) {
4
+ if (!input) return undefined;
5
+ const ts = parseTimeToTimestampMs(input.timestamp);
6
+ if (!ts) {
7
+ throw new Error("任务时间字段必须是带时区的 ISO 8601 / RFC 3339 时间。");
8
+ }
9
+ return { timestamp: ts, is_all_day: input.is_all_day ?? false };
10
+ }
@@ -0,0 +1,94 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
3
+ import { normalizeTaskTime } from "./task-shared.js";
4
+ import { assertLarkOk, createToolContext, handleInvokeError, json, registerTool, StringEnum } from "./user-tool-helpers.js";
5
+
6
+ const SubtaskSchema = Type.Union([
7
+ Type.Object({
8
+ action: Type.Literal("create"),
9
+ task_guid: Type.String(),
10
+ summary: Type.String(),
11
+ description: Type.Optional(Type.String()),
12
+ due: Type.Optional(Type.Object({ timestamp: Type.String(), is_all_day: Type.Optional(Type.Boolean()) })),
13
+ start: Type.Optional(Type.Object({ timestamp: Type.String(), is_all_day: Type.Optional(Type.Boolean()) })),
14
+ members: Type.Optional(Type.Array(Type.Object({ id: Type.String(), role: Type.Optional(StringEnum(["assignee", "follower"])) }))),
15
+ }),
16
+ Type.Object({ action: Type.Literal("list"), task_guid: Type.String(), page_size: Type.Optional(Type.Number()), page_token: Type.Optional(Type.String()) }),
17
+ ]);
18
+
19
+ export function registerFeishuTaskSubtaskTool(api: OpenClawPluginApi) {
20
+ const { toolClient } = createToolContext(api, "feishu_task_subtask");
21
+ registerTool(api, {
22
+ name: "feishu_task_subtask",
23
+ label: "Feishu Task Subtask",
24
+ description: "按本人身份创建、列出任务的子任务。",
25
+ parameters: SubtaskSchema,
26
+ async execute(_toolCallId, params) {
27
+ const payload = params as {
28
+ action: "create" | "list";
29
+ task_guid: string;
30
+ summary?: string;
31
+ description?: string;
32
+ due?: { timestamp?: string; is_all_day?: boolean };
33
+ start?: { timestamp?: string; is_all_day?: boolean };
34
+ members?: Array<{ id: string; role?: string }>;
35
+ page_size?: number;
36
+ page_token?: string;
37
+ };
38
+ try {
39
+ const client = toolClient();
40
+ if (payload.action === "create") {
41
+ const response = await client.invoke(
42
+ "feishu_task_subtask.create",
43
+ (sdk, opts) =>
44
+ sdk.task.v2.taskSubtask.create(
45
+ {
46
+ path: { task_guid: payload.task_guid },
47
+ params: { user_id_type: "open_id" as never },
48
+ data: {
49
+ summary: payload.summary,
50
+ description: payload.description,
51
+ due: normalizeTaskTime(payload.due),
52
+ start: normalizeTaskTime(payload.start),
53
+ members: payload.members?.map((member) => ({
54
+ id: member.id,
55
+ type: "user",
56
+ role: member.role ?? "assignee",
57
+ })),
58
+ },
59
+ },
60
+ opts,
61
+ ),
62
+ { as: "user" },
63
+ );
64
+ assertLarkOk(response);
65
+ return json({ subtask: (response.data as { subtask?: unknown } | undefined)?.subtask });
66
+ }
67
+ const response = await client.invoke(
68
+ "feishu_task_subtask.list",
69
+ (sdk, opts) =>
70
+ sdk.task.v2.taskSubtask.list(
71
+ {
72
+ path: { task_guid: payload.task_guid },
73
+ params: {
74
+ page_size: payload.page_size,
75
+ page_token: payload.page_token,
76
+ user_id_type: "open_id" as never,
77
+ },
78
+ },
79
+ opts,
80
+ ),
81
+ { as: "user" },
82
+ );
83
+ assertLarkOk(response);
84
+ return json({
85
+ subtasks: (response.data as { items?: unknown[] } | undefined)?.items ?? [],
86
+ has_more: (response.data as { has_more?: boolean } | undefined)?.has_more ?? false,
87
+ page_token: (response.data as { page_token?: string } | undefined)?.page_token,
88
+ });
89
+ } catch (error) {
90
+ return handleInvokeError(error, api);
91
+ }
92
+ },
93
+ }, { name: "feishu_task_subtask" });
94
+ }
@@ -0,0 +1,172 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
3
+ import { normalizeTaskTime } from "./task-shared.js";
4
+ import { assertLarkOk, createToolContext, handleInvokeError, json, parseTimeToTimestampMs, registerTool, StringEnum } from "./user-tool-helpers.js";
5
+
6
+ const TaskSchema = Type.Union([
7
+ Type.Object({
8
+ action: Type.Literal("create"),
9
+ summary: Type.String(),
10
+ current_user_id: Type.Optional(Type.String()),
11
+ description: Type.Optional(Type.String()),
12
+ due: Type.Optional(Type.Object({ timestamp: Type.String(), is_all_day: Type.Optional(Type.Boolean()) })),
13
+ start: Type.Optional(Type.Object({ timestamp: Type.String(), is_all_day: Type.Optional(Type.Boolean()) })),
14
+ members: Type.Optional(Type.Array(Type.Object({ id: Type.String(), role: Type.Optional(StringEnum(["assignee", "follower"])) }))),
15
+ tasklists: Type.Optional(Type.Array(Type.Object({ tasklist_guid: Type.String(), section_guid: Type.Optional(Type.String()) }))),
16
+ repeat_rule: Type.Optional(Type.String()),
17
+ }),
18
+ Type.Object({ action: Type.Literal("get"), task_guid: Type.String() }),
19
+ Type.Object({ action: Type.Literal("list"), page_size: Type.Optional(Type.Number()), page_token: Type.Optional(Type.String()), completed: Type.Optional(Type.Boolean()) }),
20
+ Type.Object({
21
+ action: Type.Literal("patch"),
22
+ task_guid: Type.String(),
23
+ summary: Type.Optional(Type.String()),
24
+ description: Type.Optional(Type.String()),
25
+ due: Type.Optional(Type.Object({ timestamp: Type.String(), is_all_day: Type.Optional(Type.Boolean()) })),
26
+ start: Type.Optional(Type.Object({ timestamp: Type.String(), is_all_day: Type.Optional(Type.Boolean()) })),
27
+ completed_at: Type.Optional(Type.String()),
28
+ members: Type.Optional(Type.Array(Type.Object({ id: Type.String(), role: Type.Optional(StringEnum(["assignee", "follower"])) }))),
29
+ repeat_rule: Type.Optional(Type.String()),
30
+ }),
31
+ ]);
32
+
33
+ export function registerFeishuTaskTaskTool(api: OpenClawPluginApi) {
34
+ const { toolClient } = createToolContext(api, "feishu_task_task");
35
+ registerTool(api, {
36
+ name: "feishu_task_task",
37
+ label: "Feishu Task",
38
+ description: "按本人身份创建、查询、列出、更新飞书任务。",
39
+ parameters: TaskSchema,
40
+ async execute(_toolCallId, params) {
41
+ const payload = params as {
42
+ action: "create" | "get" | "list" | "patch";
43
+ task_guid?: string;
44
+ summary?: string;
45
+ current_user_id?: string;
46
+ description?: string;
47
+ due?: { timestamp?: string; is_all_day?: boolean };
48
+ start?: { timestamp?: string; is_all_day?: boolean };
49
+ members?: Array<{ id: string; role?: string }>;
50
+ tasklists?: Array<{ tasklist_guid: string; section_guid?: string }>;
51
+ repeat_rule?: string;
52
+ page_size?: number;
53
+ page_token?: string;
54
+ completed?: boolean;
55
+ completed_at?: string;
56
+ };
57
+ try {
58
+ const client = toolClient();
59
+ if (payload.action === "create") {
60
+ const members = [...(payload.members ?? [])];
61
+ if (payload.current_user_id && !members.some((member) => member.id === payload.current_user_id)) {
62
+ members.push({ id: payload.current_user_id, role: "follower" });
63
+ }
64
+ const response = await client.invoke(
65
+ "feishu_task_task.create",
66
+ (sdk, opts) =>
67
+ sdk.task.v2.task.create(
68
+ {
69
+ params: { user_id_type: "open_id" as never },
70
+ data: {
71
+ summary: payload.summary,
72
+ description: payload.description,
73
+ due: normalizeTaskTime(payload.due),
74
+ start: normalizeTaskTime(payload.start),
75
+ repeat_rule: payload.repeat_rule,
76
+ members: members.length
77
+ ? members.map((member) => ({
78
+ id: member.id,
79
+ type: "user",
80
+ role: member.role ?? "assignee",
81
+ }))
82
+ : undefined,
83
+ tasklists: payload.tasklists,
84
+ },
85
+ },
86
+ opts,
87
+ ),
88
+ { as: "user" },
89
+ );
90
+ assertLarkOk(response);
91
+ return json({ task: (response.data as { task?: unknown } | undefined)?.task });
92
+ }
93
+ if (payload.action === "get") {
94
+ const response = await client.invoke(
95
+ "feishu_task_task.get",
96
+ (sdk, opts) =>
97
+ sdk.task.v2.task.get(
98
+ { path: { task_guid: payload.task_guid! }, params: { user_id_type: "open_id" as never } },
99
+ opts,
100
+ ),
101
+ { as: "user" },
102
+ );
103
+ assertLarkOk(response);
104
+ return json({ task: (response.data as { task?: unknown } | undefined)?.task });
105
+ }
106
+ if (payload.action === "list") {
107
+ const response = await client.invoke(
108
+ "feishu_task_task.list",
109
+ (sdk, opts) =>
110
+ sdk.task.v2.task.list(
111
+ {
112
+ params: {
113
+ page_size: payload.page_size,
114
+ page_token: payload.page_token,
115
+ completed: payload.completed,
116
+ user_id_type: "open_id" as never,
117
+ },
118
+ },
119
+ opts,
120
+ ),
121
+ { as: "user" },
122
+ );
123
+ assertLarkOk(response);
124
+ return json({
125
+ tasks: (response.data as { items?: unknown[] } | undefined)?.items ?? [],
126
+ has_more: (response.data as { has_more?: boolean } | undefined)?.has_more ?? false,
127
+ page_token: (response.data as { page_token?: string } | undefined)?.page_token,
128
+ });
129
+ }
130
+
131
+ const patchData: Record<string, unknown> = {};
132
+ if (payload.summary) patchData.summary = payload.summary;
133
+ if (payload.description) patchData.description = payload.description;
134
+ if (payload.due) patchData.due = normalizeTaskTime(payload.due);
135
+ if (payload.start) patchData.start = normalizeTaskTime(payload.start);
136
+ if (payload.repeat_rule) patchData.repeat_rule = payload.repeat_rule;
137
+ if (payload.completed_at !== undefined) {
138
+ patchData.completed_at =
139
+ payload.completed_at === "0"
140
+ ? "0"
141
+ : /^\d+$/.test(payload.completed_at)
142
+ ? payload.completed_at
143
+ : parseTimeToTimestampMs(payload.completed_at);
144
+ }
145
+ if (payload.members) {
146
+ patchData.members = payload.members.map((member) => ({
147
+ id: member.id,
148
+ type: "user",
149
+ role: member.role ?? "assignee",
150
+ }));
151
+ }
152
+ const response = await client.invoke(
153
+ "feishu_task_task.patch",
154
+ (sdk, opts) =>
155
+ sdk.task.v2.task.patch(
156
+ {
157
+ path: { task_guid: payload.task_guid! },
158
+ params: { user_id_type: "open_id" as never },
159
+ data: patchData,
160
+ },
161
+ opts,
162
+ ),
163
+ { as: "user" },
164
+ );
165
+ assertLarkOk(response);
166
+ return json({ task: (response.data as { task?: unknown } | undefined)?.task });
167
+ } catch (error) {
168
+ return handleInvokeError(error, api);
169
+ }
170
+ },
171
+ }, { name: "feishu_task_task" });
172
+ }
@@ -0,0 +1,154 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
3
+ import { assertLarkOk, createToolContext, handleInvokeError, json, registerTool, StringEnum } from "./user-tool-helpers.js";
4
+
5
+ const TasklistSchema = Type.Union([
6
+ Type.Object({ action: Type.Literal("create"), name: Type.String(), members: Type.Optional(Type.Array(Type.Object({ id: Type.String(), role: Type.Optional(StringEnum(["editor", "viewer"])) }))) }),
7
+ Type.Object({ action: Type.Literal("get"), tasklist_guid: Type.String() }),
8
+ Type.Object({ action: Type.Literal("list"), page_size: Type.Optional(Type.Number()), page_token: Type.Optional(Type.String()) }),
9
+ Type.Object({ action: Type.Literal("tasks"), tasklist_guid: Type.String(), page_size: Type.Optional(Type.Number()), page_token: Type.Optional(Type.String()), completed: Type.Optional(Type.Boolean()) }),
10
+ Type.Object({ action: Type.Literal("patch"), tasklist_guid: Type.String(), name: Type.Optional(Type.String()) }),
11
+ Type.Object({ action: Type.Literal("add_members"), tasklist_guid: Type.String(), members: Type.Array(Type.Object({ id: Type.String(), role: Type.Optional(StringEnum(["editor", "viewer"])) }))) }),
12
+ ]);
13
+
14
+ export function registerFeishuTaskTasklistTool(api: OpenClawPluginApi) {
15
+ const { toolClient } = createToolContext(api, "feishu_task_tasklist");
16
+ registerTool(api, {
17
+ name: "feishu_task_tasklist",
18
+ label: "Feishu Tasklist",
19
+ description: "按本人身份创建、查询和管理飞书任务清单。",
20
+ parameters: TasklistSchema,
21
+ async execute(_toolCallId, params) {
22
+ const payload = params as {
23
+ action: "create" | "get" | "list" | "tasks" | "patch" | "add_members";
24
+ tasklist_guid?: string;
25
+ name?: string;
26
+ members?: Array<{ id: string; role?: string }>;
27
+ page_size?: number;
28
+ page_token?: string;
29
+ completed?: boolean;
30
+ };
31
+ try {
32
+ const client = toolClient();
33
+ if (payload.action === "create") {
34
+ const response = await client.invoke(
35
+ "feishu_task_tasklist.create",
36
+ (sdk, opts) =>
37
+ sdk.task.v2.tasklist.create(
38
+ {
39
+ params: { user_id_type: "open_id" as never },
40
+ data: {
41
+ name: payload.name,
42
+ members: payload.members?.map((member) => ({
43
+ id: member.id,
44
+ type: "user",
45
+ role: member.role ?? "editor",
46
+ })),
47
+ },
48
+ },
49
+ opts,
50
+ ),
51
+ { as: "user" },
52
+ );
53
+ assertLarkOk(response);
54
+ return json({ tasklist: (response.data as { tasklist?: unknown } | undefined)?.tasklist });
55
+ }
56
+ if (payload.action === "get") {
57
+ const response = await client.invoke(
58
+ "feishu_task_tasklist.get",
59
+ (sdk, opts) =>
60
+ sdk.task.v2.tasklist.get(
61
+ { path: { tasklist_guid: payload.tasklist_guid! }, params: { user_id_type: "open_id" as never } },
62
+ opts,
63
+ ),
64
+ { as: "user" },
65
+ );
66
+ assertLarkOk(response);
67
+ return json({ tasklist: (response.data as { tasklist?: unknown } | undefined)?.tasklist });
68
+ }
69
+ if (payload.action === "list") {
70
+ const response = await client.invoke(
71
+ "feishu_task_tasklist.list",
72
+ (sdk, opts) =>
73
+ sdk.task.v2.tasklist.list(
74
+ { params: { page_size: payload.page_size, page_token: payload.page_token, user_id_type: "open_id" as never } },
75
+ opts,
76
+ ),
77
+ { as: "user" },
78
+ );
79
+ assertLarkOk(response);
80
+ return json({
81
+ tasklists: (response.data as { items?: unknown[] } | undefined)?.items ?? [],
82
+ has_more: (response.data as { has_more?: boolean } | undefined)?.has_more ?? false,
83
+ page_token: (response.data as { page_token?: string } | undefined)?.page_token,
84
+ });
85
+ }
86
+ if (payload.action === "tasks") {
87
+ const response = await client.invoke(
88
+ "feishu_task_tasklist.tasks",
89
+ (sdk, opts) =>
90
+ sdk.task.v2.tasklist.tasks(
91
+ {
92
+ path: { tasklist_guid: payload.tasklist_guid! },
93
+ params: {
94
+ page_size: payload.page_size,
95
+ page_token: payload.page_token,
96
+ completed: payload.completed,
97
+ user_id_type: "open_id" as never,
98
+ },
99
+ },
100
+ opts,
101
+ ),
102
+ { as: "user" },
103
+ );
104
+ assertLarkOk(response);
105
+ return json({
106
+ tasks: (response.data as { items?: unknown[] } | undefined)?.items ?? [],
107
+ has_more: (response.data as { has_more?: boolean } | undefined)?.has_more ?? false,
108
+ page_token: (response.data as { page_token?: string } | undefined)?.page_token,
109
+ });
110
+ }
111
+ if (payload.action === "patch") {
112
+ const response = await client.invoke(
113
+ "feishu_task_tasklist.patch",
114
+ (sdk, opts) =>
115
+ sdk.task.v2.tasklist.patch(
116
+ {
117
+ path: { tasklist_guid: payload.tasklist_guid! },
118
+ params: { user_id_type: "open_id" as never },
119
+ data: { name: payload.name },
120
+ },
121
+ opts,
122
+ ),
123
+ { as: "user" },
124
+ );
125
+ assertLarkOk(response);
126
+ return json({ tasklist: (response.data as { tasklist?: unknown } | undefined)?.tasklist });
127
+ }
128
+ const response = await client.invoke(
129
+ "feishu_task_tasklist.add_members",
130
+ (sdk, opts) =>
131
+ sdk.task.v2.tasklist.addMembers(
132
+ {
133
+ path: { tasklist_guid: payload.tasklist_guid! },
134
+ params: { user_id_type: "open_id" as never },
135
+ data: {
136
+ members: (payload.members ?? []).map((member) => ({
137
+ id: member.id,
138
+ type: "user",
139
+ role: member.role ?? "editor",
140
+ })),
141
+ },
142
+ },
143
+ opts,
144
+ ),
145
+ { as: "user" },
146
+ );
147
+ assertLarkOk(response);
148
+ return json({ success: true, tasklist_guid: payload.tasklist_guid });
149
+ } catch (error) {
150
+ return handleInvokeError(error, api);
151
+ }
152
+ },
153
+ }, { name: "feishu_task_tasklist" });
154
+ }
package/src/task.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
2
+ import { listEnabledFeishuAccounts } from "./accounts.js";
3
+ import { resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
4
+ import { registerFeishuTaskCommentTool } from "./task-comment.js";
5
+ import { registerFeishuTaskSubtaskTool } from "./task-subtask.js";
6
+ import { registerFeishuTaskTaskTool } from "./task-task.js";
7
+ import { registerFeishuTaskTasklistTool } from "./task-tasklist.js";
8
+
9
+ export function registerFeishuTaskTools(api: OpenClawPluginApi) {
10
+ if (!api.config) return;
11
+ const accounts = listEnabledFeishuAccounts(api.config);
12
+ if (accounts.length === 0) return;
13
+ if (!resolveAnyEnabledFeishuToolsConfig(accounts).task) return;
14
+ registerFeishuTaskTaskTool(api);
15
+ registerFeishuTaskTasklistTool(api);
16
+ registerFeishuTaskCommentTool(api);
17
+ registerFeishuTaskSubtaskTool(api);
18
+ }