@clowder-ai/enterprise-workflow 0.0.0-stage → 0.1.0-alpha.4
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/README.en.md +38 -0
- package/README.md +19 -2
- package/assets/icon.svg +5 -0
- package/dist/LarkActionService.d.ts +117 -0
- package/dist/LarkActionService.d.ts.map +1 -0
- package/dist/LarkActionService.js +189 -0
- package/dist/LarkActionService.js.map +1 -0
- package/dist/LarkCliExecutor.d.ts +83 -0
- package/dist/LarkCliExecutor.d.ts.map +1 -0
- package/dist/LarkCliExecutor.js +180 -0
- package/dist/LarkCliExecutor.js.map +1 -0
- package/dist/WeComActionService.d.ts +75 -0
- package/dist/WeComActionService.d.ts.map +1 -0
- package/dist/WeComActionService.js +183 -0
- package/dist/WeComActionService.js.map +1 -0
- package/dist/WeComCliExecutor.d.ts +61 -0
- package/dist/WeComCliExecutor.d.ts.map +1 -0
- package/dist/WeComCliExecutor.js +136 -0
- package/dist/WeComCliExecutor.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/lark-types.d.ts +120 -0
- package/dist/lark-types.d.ts.map +1 -0
- package/dist/lark-types.js +18 -0
- package/dist/lark-types.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +22 -0
- package/dist/logger.js.map +1 -0
- package/dist/plugin-entrypoint.d.ts +38 -0
- package/dist/plugin-entrypoint.d.ts.map +1 -0
- package/dist/plugin-entrypoint.js +180 -0
- package/dist/plugin-entrypoint.js.map +1 -0
- package/dist/wecom-types.d.ts +85 -0
- package/dist/wecom-types.d.ts.map +1 -0
- package/dist/wecom-types.js +8 -0
- package/dist/wecom-types.js.map +1 -0
- package/npm-shrinkwrap.json +687 -0
- package/package.json +59 -4
- package/plugin.yaml +220 -0
- package/skills/enterprise-workflow/SKILL.md +209 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Enterprise Workflow
|
|
2
|
+
|
|
3
|
+
[Chinese](README.md) · English
|
|
4
|
+
|
|
5
|
+
`@clowder-ai/enterprise-workflow` exposes governed WeCom and Lark actions as two
|
|
6
|
+
Host-managed direct tools. It does not open an HTTP callback endpoint and it never
|
|
7
|
+
receives or stores a callback token. The Host authenticates the calling cat and
|
|
8
|
+
rechecks the live plugin instance before `plugin_call` reaches the package action.
|
|
9
|
+
|
|
10
|
+
## Operator setup
|
|
11
|
+
|
|
12
|
+
Install and authenticate `wecom-cli` and/or `lark-cli` on the Host machine, then set:
|
|
13
|
+
|
|
14
|
+
- `wecomCliPath`: executable name or absolute path for `wecom-cli`.
|
|
15
|
+
- `larkCliPath`: executable name or absolute path for `lark-cli`.
|
|
16
|
+
|
|
17
|
+
The paths are ordinary configuration, not secrets. Vendor credentials remain owned
|
|
18
|
+
by each local CLI.
|
|
19
|
+
|
|
20
|
+
## Agent journey
|
|
21
|
+
|
|
22
|
+
1. Confirm `official.enterprise-workflow` is installed, configured, enabled, and running.
|
|
23
|
+
2. Call `plugin_list_tools` for that plugin.
|
|
24
|
+
3. Select exactly `wecom-actions/wecom_action` or `lark-actions/lark_action` and construct
|
|
25
|
+
arguments from the returned schema.
|
|
26
|
+
4. Call `plugin_call`; the returned value is the resource handle for the selected action.
|
|
27
|
+
|
|
28
|
+
The packaged `skills/enterprise-workflow/SKILL.md` contains branch examples. Do not invoke
|
|
29
|
+
the CLI directly: doing so bypasses Host invocation identity, liveness, grants, and audit.
|
|
30
|
+
|
|
31
|
+
## Package ownership and recovery
|
|
32
|
+
|
|
33
|
+
The package owns provider argument adaptation, CLI process execution, output parsing, and
|
|
34
|
+
resource handles. The Host owns installation, configuration, grants, invocation identity,
|
|
35
|
+
audit, enable/disable, and uninstall. Disable or uninstall stops new tool calls; created
|
|
36
|
+
vendor resources remain in the vendor tenant and are returned as explicit handles.
|
|
37
|
+
|
|
38
|
+
The Host owns installation, grants, credentials and lifecycle; saving configuration preserves enable intent. The language link opens a separate document and does not imply automatic language selection.
|
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 企业工作流
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
简体中文 · [English](README.en.md)
|
|
4
|
+
|
|
5
|
+
通过两个受宿主管理的工具调用企业微信和飞书工作流,执行供应商 CLI 支持的操作并返回明确的资源标识。
|
|
6
|
+
|
|
7
|
+
## 开始使用
|
|
8
|
+
|
|
9
|
+
1. 在运行 Clowder AI 的机器上安装并登录 `wecom-cli` 或 `lark-cli`。
|
|
10
|
+
2. 在插件设置填写需要使用的 `wecomCliPath`、`larkCliPath`。可以使用可执行文件名或绝对路径;这些路径不是密钥,平台凭据仍由各 CLI 管理。
|
|
11
|
+
3. 启用插件,让猫通过 `plugin_list_tools` 查看本插件的实际参数说明。
|
|
12
|
+
4. 按说明调用 `wecom-actions/wecom_action` 或 `lark-actions/lark_action`,经 `plugin_call` 执行。
|
|
13
|
+
|
|
14
|
+
随包的企业工作流 skill 提供分支示例。直接绕过工具运行 CLI 不会经过宿主的调用身份、授权和审计。
|
|
15
|
+
|
|
16
|
+
## 数据与停用
|
|
17
|
+
|
|
18
|
+
插件不开放 HTTP 回调,也不接收或保存回调令牌。宿主认证调用者并检查当前实例;插件负责参数转换、子进程和结果解析。停用或卸载阻止新调用;已创建的平台资源仍保留在企业租户,使用返回的资源标识管理。
|
|
19
|
+
|
|
20
|
+
宿主管理安装、授权、凭据和启停;配置保存不自动改变启用意图。英文文档是独立文件,互链不代表页面已支持自动语言切换。
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Enterprise workflow">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#3157d5"/>
|
|
3
|
+
<path d="M18 18h28v10H18zm0 18h18v10H18z" fill="#fff"/>
|
|
4
|
+
<path d="m40 36 8 5-8 5z" fill="#7ee2c2"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F162 Phase B: Lark/Feishu Action Service.
|
|
3
|
+
*
|
|
4
|
+
* Typed methods for enterprise operations via lark-cli (larksuite/cli, Go binary).
|
|
5
|
+
* This is the governance boundary (ADR-029): audit log, error normalization.
|
|
6
|
+
* All cat-facing Lark enterprise actions go through here — never raw CLI calls.
|
|
7
|
+
*
|
|
8
|
+
* Feishu capabilities covered (Phase B):
|
|
9
|
+
* - docs (Markdown-backed Lark Docs)
|
|
10
|
+
* - base (Bitable, multi-dimensional tables) + fields/records scaffolding
|
|
11
|
+
* - task (task v2)
|
|
12
|
+
* - calendar (event v4; lark-cli v1.x does not expose VC/meeting URLs)
|
|
13
|
+
* - slides (presentation; Feishu-exclusive vs WeCom)
|
|
14
|
+
*
|
|
15
|
+
* ADR-029 Core Principle: ActionService is required for all external tool actions.
|
|
16
|
+
*/
|
|
17
|
+
import { type LarkCliExecutor } from './LarkCliExecutor.js';
|
|
18
|
+
import type { EnterpriseLogger } from './logger.js';
|
|
19
|
+
import type { LarkBaseHandle, LarkCalendarEventHandle, LarkDocHandle, LarkGoldenChainResult, LarkSlideHandle, LarkTaskHandle } from './lark-types.js';
|
|
20
|
+
export interface CreateDocOpts {
|
|
21
|
+
/** Document title */
|
|
22
|
+
title: string;
|
|
23
|
+
/** Markdown content (Lark-flavored). Pass plain markdown; lark-cli handles conversion. */
|
|
24
|
+
markdown?: string;
|
|
25
|
+
/** Optional parent folder token */
|
|
26
|
+
folderToken?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface CreateBaseOpts {
|
|
29
|
+
/** Base (Bitable) app name */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Optional parent folder token */
|
|
32
|
+
folderToken?: string;
|
|
33
|
+
/** e.g. "Asia/Shanghai" */
|
|
34
|
+
timeZone?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CreateTaskOpts {
|
|
37
|
+
/** Task title */
|
|
38
|
+
summary: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
/** Assignee open_id (single assignee for Phase B; use assignMany for multi) */
|
|
41
|
+
assigneeOpenId?: string;
|
|
42
|
+
/** Due date: ISO 8601, YYYY-MM-DD, +2d relative, or ms timestamp */
|
|
43
|
+
due?: string;
|
|
44
|
+
/** Client token for idempotency */
|
|
45
|
+
idempotencyKey?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface CreateCalendarEventOpts {
|
|
48
|
+
/** Event title */
|
|
49
|
+
summary: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
/** ISO 8601 start time */
|
|
52
|
+
start: string;
|
|
53
|
+
/** ISO 8601 end time */
|
|
54
|
+
end: string;
|
|
55
|
+
/** Attendee IDs (ou_xxx user, oc_xxx chat, omm_xxx room), comma-joined input allowed */
|
|
56
|
+
attendeeOpenIds?: string[];
|
|
57
|
+
/** Calendar ID (default: primary) */
|
|
58
|
+
calendarId?: string;
|
|
59
|
+
/** RFC5545 recurrence rule */
|
|
60
|
+
rrule?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface CreateSlidesOpts {
|
|
63
|
+
title: string;
|
|
64
|
+
folderToken?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface GoldenChainOpts {
|
|
67
|
+
docTitle: string;
|
|
68
|
+
docMarkdown: string;
|
|
69
|
+
baseName: string;
|
|
70
|
+
tasks: Array<{
|
|
71
|
+
summary: string;
|
|
72
|
+
assigneeOpenId: string;
|
|
73
|
+
due?: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
}>;
|
|
76
|
+
calendarSummary: string;
|
|
77
|
+
calendarStart: string;
|
|
78
|
+
calendarEnd: string;
|
|
79
|
+
calendarAttendeeOpenIds: string[];
|
|
80
|
+
/** If true, also create a Slides deck and include in summary */
|
|
81
|
+
includeSlides?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export declare class LarkActionService {
|
|
84
|
+
private readonly executor;
|
|
85
|
+
private readonly log;
|
|
86
|
+
constructor(executor: LarkCliExecutor, log: EnterpriseLogger);
|
|
87
|
+
isAvailable(): Promise<boolean>;
|
|
88
|
+
createDoc(opts: CreateDocOpts): Promise<LarkDocHandle>;
|
|
89
|
+
createBase(opts: CreateBaseOpts): Promise<LarkBaseHandle>;
|
|
90
|
+
createTask(opts: CreateTaskOpts): Promise<LarkTaskHandle>;
|
|
91
|
+
createCalendarEvent(opts: CreateCalendarEventOpts): Promise<LarkCalendarEventHandle>;
|
|
92
|
+
createSlides(opts: CreateSlidesOpts): Promise<LarkSlideHandle>;
|
|
93
|
+
/**
|
|
94
|
+
* Best-effort user lookup by name/query. Degrades to empty array ONLY when the failure
|
|
95
|
+
* is a scope/permission error (not every tenant grants contact:contact.search). Other
|
|
96
|
+
* failures (protocol error, CLI unavailable, vendor outage, unexpected API codes) bubble
|
|
97
|
+
* up to the caller so real problems aren't masked as "no matches".
|
|
98
|
+
*/
|
|
99
|
+
searchUsers(query: string): Promise<Array<{
|
|
100
|
+
openId: string;
|
|
101
|
+
name: string;
|
|
102
|
+
}>>;
|
|
103
|
+
/**
|
|
104
|
+
* Golden Chain: One sentence → Doc + Base + Tasks + Calendar Event (+ optional Slides).
|
|
105
|
+
* Feishu showcase, parity with WeCom F162 Phase A goldenChain but adapted to Lark primitives:
|
|
106
|
+
* - Doc (Markdown) replaces WeCom Doc
|
|
107
|
+
* - Base (Bitable) replaces WeCom smart table
|
|
108
|
+
* - Task v2 replaces WeCom Todo
|
|
109
|
+
* - Calendar Event (with VC link) replaces WeCom Meeting
|
|
110
|
+
* - Slides is Feishu-exclusive bonus
|
|
111
|
+
*/
|
|
112
|
+
goldenChain(opts: GoldenChainOpts): Promise<LarkGoldenChainResult & {
|
|
113
|
+
slides?: LarkSlideHandle;
|
|
114
|
+
}>;
|
|
115
|
+
private audit;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=LarkActionService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LarkActionService.d.ts","sourceRoot":"","sources":["../src/LarkActionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F162 Phase B: Lark/Feishu Action Service.
|
|
3
|
+
*
|
|
4
|
+
* Typed methods for enterprise operations via lark-cli (larksuite/cli, Go binary).
|
|
5
|
+
* This is the governance boundary (ADR-029): audit log, error normalization.
|
|
6
|
+
* All cat-facing Lark enterprise actions go through here — never raw CLI calls.
|
|
7
|
+
*
|
|
8
|
+
* Feishu capabilities covered (Phase B):
|
|
9
|
+
* - docs (Markdown-backed Lark Docs)
|
|
10
|
+
* - base (Bitable, multi-dimensional tables) + fields/records scaffolding
|
|
11
|
+
* - task (task v2)
|
|
12
|
+
* - calendar (event v4; lark-cli v1.x does not expose VC/meeting URLs)
|
|
13
|
+
* - slides (presentation; Feishu-exclusive vs WeCom)
|
|
14
|
+
*
|
|
15
|
+
* ADR-029 Core Principle: ActionService is required for all external tool actions.
|
|
16
|
+
*/
|
|
17
|
+
import { LarkApiError } from './LarkCliExecutor.js';
|
|
18
|
+
export class LarkActionService {
|
|
19
|
+
executor;
|
|
20
|
+
log;
|
|
21
|
+
constructor(executor, log) {
|
|
22
|
+
this.executor = executor;
|
|
23
|
+
this.log = log;
|
|
24
|
+
}
|
|
25
|
+
async isAvailable() {
|
|
26
|
+
return this.executor.isAvailable();
|
|
27
|
+
}
|
|
28
|
+
async createDoc(opts) {
|
|
29
|
+
this.audit('createDoc', { title: opts.title, hasMarkdown: Boolean(opts.markdown) });
|
|
30
|
+
const res = await this.executor.exec('docs', '+create', {
|
|
31
|
+
title: opts.title,
|
|
32
|
+
...(opts.markdown ? { markdown: opts.markdown } : {}),
|
|
33
|
+
...(opts.folderToken ? { 'folder-token': opts.folderToken } : {}),
|
|
34
|
+
});
|
|
35
|
+
const data = res.data;
|
|
36
|
+
if (!data?.doc_id) {
|
|
37
|
+
throw new Error(`Lark docs +create returned no doc_id: ${JSON.stringify(res)}`);
|
|
38
|
+
}
|
|
39
|
+
const url = data.doc_url ?? `https://feishu.cn/docx/${data.doc_id}`;
|
|
40
|
+
return { documentId: data.doc_id, url, title: opts.title };
|
|
41
|
+
}
|
|
42
|
+
async createBase(opts) {
|
|
43
|
+
this.audit('createBase', { name: opts.name });
|
|
44
|
+
const res = await this.executor.exec('base', '+base-create', {
|
|
45
|
+
name: opts.name,
|
|
46
|
+
...(opts.folderToken ? { 'folder-token': opts.folderToken } : {}),
|
|
47
|
+
...(opts.timeZone ? { 'time-zone': opts.timeZone } : {}),
|
|
48
|
+
});
|
|
49
|
+
const base = res.data?.base;
|
|
50
|
+
if (!base?.base_token) {
|
|
51
|
+
throw new Error(`Lark base +base-create returned no base_token: ${JSON.stringify(res)}`);
|
|
52
|
+
}
|
|
53
|
+
const url = base.url ?? `https://feishu.cn/base/${base.base_token}`;
|
|
54
|
+
return { appToken: base.base_token, url, name: base.name ?? opts.name };
|
|
55
|
+
}
|
|
56
|
+
async createTask(opts) {
|
|
57
|
+
this.audit('createTask', { summary: opts.summary });
|
|
58
|
+
const res = await this.executor.exec('task', '+create', {
|
|
59
|
+
summary: opts.summary,
|
|
60
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
61
|
+
...(opts.assigneeOpenId ? { assignee: opts.assigneeOpenId } : {}),
|
|
62
|
+
...(opts.due ? { due: opts.due } : {}),
|
|
63
|
+
...(opts.idempotencyKey ? { 'idempotency-key': opts.idempotencyKey } : {}),
|
|
64
|
+
});
|
|
65
|
+
const data = res.data;
|
|
66
|
+
if (!data?.guid) {
|
|
67
|
+
throw new Error(`Lark task +create returned no guid: ${JSON.stringify(res)}`);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
guid: data.guid,
|
|
71
|
+
summary: opts.summary,
|
|
72
|
+
...(data.url ? { url: data.url } : {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async createCalendarEvent(opts) {
|
|
76
|
+
this.audit('createCalendarEvent', { summary: opts.summary });
|
|
77
|
+
const attendeeIds = opts.attendeeOpenIds?.length ? opts.attendeeOpenIds.join(',') : undefined;
|
|
78
|
+
const res = await this.executor.exec('calendar', '+create', {
|
|
79
|
+
summary: opts.summary,
|
|
80
|
+
start: opts.start,
|
|
81
|
+
end: opts.end,
|
|
82
|
+
...(opts.description ? { description: opts.description } : {}),
|
|
83
|
+
...(attendeeIds ? { 'attendee-ids': attendeeIds } : {}),
|
|
84
|
+
...(opts.calendarId ? { 'calendar-id': opts.calendarId } : {}),
|
|
85
|
+
...(opts.rrule ? { rrule: opts.rrule } : {}),
|
|
86
|
+
});
|
|
87
|
+
const data = res.data;
|
|
88
|
+
if (!data?.event_id) {
|
|
89
|
+
throw new Error(`Lark calendar +create returned no event_id: ${JSON.stringify(res)}`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
eventId: data.event_id,
|
|
93
|
+
calendarId: opts.calendarId ?? 'primary',
|
|
94
|
+
summary: data.summary ?? opts.summary,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async createSlides(opts) {
|
|
98
|
+
this.audit('createSlides', { title: opts.title });
|
|
99
|
+
const res = await this.executor.exec('slides', '+create', {
|
|
100
|
+
title: opts.title,
|
|
101
|
+
...(opts.folderToken ? { 'folder-token': opts.folderToken } : {}),
|
|
102
|
+
});
|
|
103
|
+
const data = res.data;
|
|
104
|
+
if (!data?.xml_presentation_id) {
|
|
105
|
+
throw new Error(`Lark slides +create returned no xml_presentation_id: ${JSON.stringify(res)}`);
|
|
106
|
+
}
|
|
107
|
+
const url = data.url ?? `https://feishu.cn/slides/${data.xml_presentation_id}`;
|
|
108
|
+
return { presentationId: data.xml_presentation_id, url, title: data.title ?? opts.title };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Best-effort user lookup by name/query. Degrades to empty array ONLY when the failure
|
|
112
|
+
* is a scope/permission error (not every tenant grants contact:contact.search). Other
|
|
113
|
+
* failures (protocol error, CLI unavailable, vendor outage, unexpected API codes) bubble
|
|
114
|
+
* up to the caller so real problems aren't masked as "no matches".
|
|
115
|
+
*/
|
|
116
|
+
async searchUsers(query) {
|
|
117
|
+
this.audit('searchUsers', { query });
|
|
118
|
+
try {
|
|
119
|
+
const res = await this.executor.exec('contact', '+search-user', { query });
|
|
120
|
+
return (res.data?.users ?? []).map((u) => ({ openId: u.open_id, name: u.name }));
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err instanceof LarkApiError && isScopeOrPermissionError(err)) {
|
|
124
|
+
this.log.warn({ err, query }, '[LarkAction] searchUsers degraded — contact scope not granted');
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Golden Chain: One sentence → Doc + Base + Tasks + Calendar Event (+ optional Slides).
|
|
132
|
+
* Feishu showcase, parity with WeCom F162 Phase A goldenChain but adapted to Lark primitives:
|
|
133
|
+
* - Doc (Markdown) replaces WeCom Doc
|
|
134
|
+
* - Base (Bitable) replaces WeCom smart table
|
|
135
|
+
* - Task v2 replaces WeCom Todo
|
|
136
|
+
* - Calendar Event (with VC link) replaces WeCom Meeting
|
|
137
|
+
* - Slides is Feishu-exclusive bonus
|
|
138
|
+
*/
|
|
139
|
+
async goldenChain(opts) {
|
|
140
|
+
this.audit('goldenChain', { docTitle: opts.docTitle, taskCount: opts.tasks.length });
|
|
141
|
+
const doc = await this.createDoc({ title: opts.docTitle, markdown: opts.docMarkdown });
|
|
142
|
+
const base = await this.createBase({ name: opts.baseName });
|
|
143
|
+
const tasks = [];
|
|
144
|
+
for (const t of opts.tasks) {
|
|
145
|
+
tasks.push(await this.createTask({
|
|
146
|
+
summary: t.summary,
|
|
147
|
+
assigneeOpenId: t.assigneeOpenId,
|
|
148
|
+
...(t.due ? { due: t.due } : {}),
|
|
149
|
+
...(t.description ? { description: t.description } : {}),
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
const calendarEvent = await this.createCalendarEvent({
|
|
153
|
+
summary: opts.calendarSummary,
|
|
154
|
+
start: opts.calendarStart,
|
|
155
|
+
end: opts.calendarEnd,
|
|
156
|
+
attendeeOpenIds: opts.calendarAttendeeOpenIds,
|
|
157
|
+
});
|
|
158
|
+
let slides;
|
|
159
|
+
if (opts.includeSlides) {
|
|
160
|
+
try {
|
|
161
|
+
slides = await this.createSlides({ title: `${opts.docTitle} — Slides` });
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
this.log.warn({ err }, '[LarkAction] Slides creation failed — continuing without');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const lines = [
|
|
168
|
+
`📄 文档: ${doc.title} — ${doc.url}`,
|
|
169
|
+
`📊 多维表: ${base.name} — ${base.url}`,
|
|
170
|
+
`✅ 任务: ${tasks.length} 条已分发`,
|
|
171
|
+
`🗓 日程: ${calendarEvent.summary}`,
|
|
172
|
+
];
|
|
173
|
+
if (slides)
|
|
174
|
+
lines.push(`🎞 幻灯片: ${slides.title} — ${slides.url}`);
|
|
175
|
+
const summary = lines.join('\n');
|
|
176
|
+
return { doc, base, tasks, calendarEvent, summary, ...(slides ? { slides } : {}) };
|
|
177
|
+
}
|
|
178
|
+
audit(method, params) {
|
|
179
|
+
this.log.info({ service: 'LarkAction', method, params }, '[LarkAction] audit');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function isScopeOrPermissionError(err) {
|
|
183
|
+
const type = err.type.toLowerCase();
|
|
184
|
+
if (type.includes('permission') || type.includes('scope') || type.includes('forbidden'))
|
|
185
|
+
return true;
|
|
186
|
+
// Common Lark permission/scope codes (99991664 scope_denied, 99991668 forbidden, 1254xxx contact-scope)
|
|
187
|
+
return err.code === 99991664 || err.code === 99991668 || (err.code >= 1254000 && err.code <= 1254999);
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=LarkActionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LarkActionService.js","sourceRoot":"","sources":["../src/LarkActionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F162 Phase B: Lark/Feishu CLI Executor.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around `lark-cli` (Go binary distributed via @larksuite/cli npm).
|
|
5
|
+
* Unlike wecom-cli (single-JSON-blob args), lark-cli uses cobra-style flags:
|
|
6
|
+
* lark-cli <domain> <+command> [--flag value ...]
|
|
7
|
+
*
|
|
8
|
+
* Responses come back as raw JSON with the shape:
|
|
9
|
+
* success: { ok: true, identity, data: {...} }
|
|
10
|
+
* failure: { ok: false, identity, error: { type, code, message, hint } }
|
|
11
|
+
*
|
|
12
|
+
* The CLI exits 0 in both cases — success is determined by the `ok` field.
|
|
13
|
+
*
|
|
14
|
+
* ADR-029 Decision 1: CliExecutor is the execution backend for LarkActionService.
|
|
15
|
+
*/
|
|
16
|
+
import type { LarkBaseResponse, LarkCliErrorDetail } from './lark-types.js';
|
|
17
|
+
import type { EnterpriseLogger } from './logger.js';
|
|
18
|
+
interface ExecFileResult {
|
|
19
|
+
readonly stdout: string;
|
|
20
|
+
readonly stderr: string;
|
|
21
|
+
}
|
|
22
|
+
type ExecFileRunner = (executable: string, args: readonly string[], options: Readonly<Record<string, unknown>>) => Promise<ExecFileResult>;
|
|
23
|
+
/** Flag value type: strings, numbers, booleans accepted */
|
|
24
|
+
export type LarkFlagValue = string | number | boolean;
|
|
25
|
+
/** Thrown when lark-cli returns ok: false */
|
|
26
|
+
export declare class LarkApiError extends Error {
|
|
27
|
+
readonly domain: string;
|
|
28
|
+
readonly command: string;
|
|
29
|
+
readonly code: number;
|
|
30
|
+
readonly type: string;
|
|
31
|
+
readonly hint?: string;
|
|
32
|
+
constructor(error: LarkCliErrorDetail, domain: string, command: string);
|
|
33
|
+
}
|
|
34
|
+
/** Thrown when lark-cli itself fails (not installed, timeout, crash, auth missing) */
|
|
35
|
+
export declare class LarkCliUnavailableError extends Error {
|
|
36
|
+
readonly reason?: unknown;
|
|
37
|
+
constructor(message: string, reason?: unknown);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Thrown when lark-cli responds but the stdout does not conform to the expected
|
|
41
|
+
* `{ok, identity, data|error}` envelope (non-JSON, truncated, protocol drift).
|
|
42
|
+
*
|
|
43
|
+
* Distinct from `LarkCliUnavailableError`: the CLI was reachable and returned output,
|
|
44
|
+
* but the output cannot be interpreted. Surfacing this as a protocol error (→ 500)
|
|
45
|
+
* prevents misdiagnosis as "CLI not installed" (→ 503) and preserves the raw payload
|
|
46
|
+
* for debugging vendor shape changes.
|
|
47
|
+
*/
|
|
48
|
+
export declare class LarkCliProtocolError extends Error {
|
|
49
|
+
readonly reason?: unknown;
|
|
50
|
+
readonly rawOutput?: string;
|
|
51
|
+
constructor(message: string, reason?: unknown, rawOutput?: string);
|
|
52
|
+
}
|
|
53
|
+
export declare class LarkCliExecutor {
|
|
54
|
+
private available;
|
|
55
|
+
private readonly log;
|
|
56
|
+
private readonly executable;
|
|
57
|
+
private readonly timeoutMs;
|
|
58
|
+
private readonly run;
|
|
59
|
+
constructor(log: EnterpriseLogger, executable: string, timeoutMs?: number, run?: ExecFileRunner);
|
|
60
|
+
/** Check if lark-cli is available. Result is cached after first check. */
|
|
61
|
+
isAvailable(): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Execute a lark-cli command and parse JSON response.
|
|
64
|
+
*
|
|
65
|
+
* @param domain - Top-level command (docs, task, calendar, base, im, ...)
|
|
66
|
+
* @param command - Subcommand (often prefixed with +, e.g. +create)
|
|
67
|
+
* @param flags - Flag map; values are stringified. `--flag value` for each entry.
|
|
68
|
+
* @returns Parsed response with ok: true guaranteed
|
|
69
|
+
* @throws LarkApiError if ok: false
|
|
70
|
+
* @throws LarkCliUnavailableError if CLI is missing or crashes
|
|
71
|
+
*/
|
|
72
|
+
exec<T extends LarkBaseResponse>(domain: string, command: string, flags?: Record<string, LarkFlagValue | undefined>): Promise<T>;
|
|
73
|
+
/**
|
|
74
|
+
* Parse lark-cli stdout. Accepts the `{ok, identity, data|error}` envelope.
|
|
75
|
+
* Some commands may emit pure data (no envelope); treat absence of `ok` as
|
|
76
|
+
* success and synthesize the envelope.
|
|
77
|
+
*/
|
|
78
|
+
private parseOutput;
|
|
79
|
+
/** Reset cached availability (for testing) */
|
|
80
|
+
_resetCache(): void;
|
|
81
|
+
}
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=LarkCliExecutor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LarkCliExecutor.d.ts","sourceRoot":"","sources":["../src/LarkCliExecutor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,UAAU,cAAc;IAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC7E,KAAK,cAAc,GAAG,CACpB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KACvC,OAAO,CAAC,cAAc,CAAC,CAAC;AAM7B,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtD,6CAA6C;AAC7C,qBAAa,YAAa,SAAQ,KAAK;aAOnB,MAAM,EAAE,MAAM;aACd,OAAO,EAAE,MAAM;IAPjC,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,IAAI,EAAE,MAAM,CAAC;IAC7B,SAAgB,IAAI,CAAC,EAAE,MAAM,CAAC;gBAG5B,KAAK,EAAE,kBAAkB,EACT,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;CAQlC;AAED,sFAAsF;AACtF,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,SAAgB,MAAM,CAAC,EAAE,OAAO,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO;CAK9C;AAED;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAgB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjC,SAAgB,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEvB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM;CAMlE;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAmB;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAiB;gBAGnC,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAClB,SAAS,SAAqB,EAC9B,GAAG,GAAE,cAA8B;IAQrC,0EAA0E;IACpE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAarC;;;;;;;;;OASG;IACG,IAAI,CAAC,CAAC,SAAS,gBAAgB,EACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,CAAM,GACpD,OAAO,CAAC,CAAC,CAAC;IA2Db;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAanB,8CAA8C;IAC9C,WAAW,IAAI,IAAI;CAGpB"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F162 Phase B: Lark/Feishu CLI Executor.
|
|
3
|
+
*
|
|
4
|
+
* Thin wrapper around `lark-cli` (Go binary distributed via @larksuite/cli npm).
|
|
5
|
+
* Unlike wecom-cli (single-JSON-blob args), lark-cli uses cobra-style flags:
|
|
6
|
+
* lark-cli <domain> <+command> [--flag value ...]
|
|
7
|
+
*
|
|
8
|
+
* Responses come back as raw JSON with the shape:
|
|
9
|
+
* success: { ok: true, identity, data: {...} }
|
|
10
|
+
* failure: { ok: false, identity, error: { type, code, message, hint } }
|
|
11
|
+
*
|
|
12
|
+
* The CLI exits 0 in both cases — success is determined by the `ok` field.
|
|
13
|
+
*
|
|
14
|
+
* ADR-029 Decision 1: CliExecutor is the execution backend for LarkActionService.
|
|
15
|
+
*/
|
|
16
|
+
import { execFile } from 'node:child_process';
|
|
17
|
+
import { promisify } from 'node:util';
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
/** Default timeout for lark-cli commands (30 seconds) */
|
|
20
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
21
|
+
/** Thrown when lark-cli returns ok: false */
|
|
22
|
+
export class LarkApiError extends Error {
|
|
23
|
+
domain;
|
|
24
|
+
command;
|
|
25
|
+
code;
|
|
26
|
+
type;
|
|
27
|
+
hint;
|
|
28
|
+
constructor(error, domain, command) {
|
|
29
|
+
super(`Lark API error [${error.code} ${error.type}]: ${error.message} (${domain} ${command})`);
|
|
30
|
+
this.domain = domain;
|
|
31
|
+
this.command = command;
|
|
32
|
+
this.name = 'LarkApiError';
|
|
33
|
+
this.code = error.code;
|
|
34
|
+
this.type = error.type;
|
|
35
|
+
this.hint = error.hint;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Thrown when lark-cli itself fails (not installed, timeout, crash, auth missing) */
|
|
39
|
+
export class LarkCliUnavailableError extends Error {
|
|
40
|
+
reason;
|
|
41
|
+
constructor(message, reason) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'LarkCliUnavailableError';
|
|
44
|
+
this.reason = reason;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Thrown when lark-cli responds but the stdout does not conform to the expected
|
|
49
|
+
* `{ok, identity, data|error}` envelope (non-JSON, truncated, protocol drift).
|
|
50
|
+
*
|
|
51
|
+
* Distinct from `LarkCliUnavailableError`: the CLI was reachable and returned output,
|
|
52
|
+
* but the output cannot be interpreted. Surfacing this as a protocol error (→ 500)
|
|
53
|
+
* prevents misdiagnosis as "CLI not installed" (→ 503) and preserves the raw payload
|
|
54
|
+
* for debugging vendor shape changes.
|
|
55
|
+
*/
|
|
56
|
+
export class LarkCliProtocolError extends Error {
|
|
57
|
+
reason;
|
|
58
|
+
rawOutput;
|
|
59
|
+
constructor(message, reason, rawOutput) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.name = 'LarkCliProtocolError';
|
|
62
|
+
this.reason = reason;
|
|
63
|
+
this.rawOutput = rawOutput;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export class LarkCliExecutor {
|
|
67
|
+
available = null;
|
|
68
|
+
log;
|
|
69
|
+
executable;
|
|
70
|
+
timeoutMs;
|
|
71
|
+
run;
|
|
72
|
+
constructor(log, executable, timeoutMs = DEFAULT_TIMEOUT_MS, run = execFileAsync) {
|
|
73
|
+
this.log = log;
|
|
74
|
+
this.executable = executable;
|
|
75
|
+
this.timeoutMs = timeoutMs;
|
|
76
|
+
this.run = run;
|
|
77
|
+
}
|
|
78
|
+
/** Check if lark-cli is available. Result is cached after first check. */
|
|
79
|
+
async isAvailable() {
|
|
80
|
+
if (this.available !== null)
|
|
81
|
+
return this.available;
|
|
82
|
+
try {
|
|
83
|
+
const { stdout } = await this.run(this.executable, ['--version'], { timeout: 5_000 });
|
|
84
|
+
this.log.info({ version: stdout.trim() }, '[LarkCli] lark-cli detected');
|
|
85
|
+
this.available = true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
this.log.warn('[LarkCli] lark-cli not found — enterprise actions will be unavailable');
|
|
89
|
+
this.available = false;
|
|
90
|
+
}
|
|
91
|
+
return this.available;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Execute a lark-cli command and parse JSON response.
|
|
95
|
+
*
|
|
96
|
+
* @param domain - Top-level command (docs, task, calendar, base, im, ...)
|
|
97
|
+
* @param command - Subcommand (often prefixed with +, e.g. +create)
|
|
98
|
+
* @param flags - Flag map; values are stringified. `--flag value` for each entry.
|
|
99
|
+
* @returns Parsed response with ok: true guaranteed
|
|
100
|
+
* @throws LarkApiError if ok: false
|
|
101
|
+
* @throws LarkCliUnavailableError if CLI is missing or crashes
|
|
102
|
+
*/
|
|
103
|
+
async exec(domain, command, flags = {}) {
|
|
104
|
+
const available = await this.isAvailable();
|
|
105
|
+
if (!available) {
|
|
106
|
+
throw new LarkCliUnavailableError('lark-cli is not installed or not configured');
|
|
107
|
+
}
|
|
108
|
+
const args = [domain, command];
|
|
109
|
+
for (const [key, value] of Object.entries(flags)) {
|
|
110
|
+
if (value === undefined || value === null)
|
|
111
|
+
continue;
|
|
112
|
+
if (typeof value === 'boolean') {
|
|
113
|
+
if (value)
|
|
114
|
+
args.push(`--${key}`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
args.push(`--${key}`, String(value));
|
|
118
|
+
}
|
|
119
|
+
this.log.info({ domain, command, flags: Object.keys(flags) }, '[LarkCli] exec');
|
|
120
|
+
try {
|
|
121
|
+
const { stdout, stderr } = await this.run(this.executable, args, {
|
|
122
|
+
timeout: this.timeoutMs,
|
|
123
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
124
|
+
});
|
|
125
|
+
if (stderr.trim()) {
|
|
126
|
+
this.log.debug({ domain, command, stderr: stderr.trim() }, '[LarkCli] stderr');
|
|
127
|
+
}
|
|
128
|
+
const parsed = this.parseOutput(stdout.trim());
|
|
129
|
+
if (parsed.ok === false) {
|
|
130
|
+
const errorDetail = parsed.error ?? {
|
|
131
|
+
type: 'unknown',
|
|
132
|
+
code: -1,
|
|
133
|
+
message: 'lark-cli reported ok:false without error detail',
|
|
134
|
+
};
|
|
135
|
+
throw new LarkApiError(errorDetail, domain, command);
|
|
136
|
+
}
|
|
137
|
+
this.log.info({ domain, command }, '[LarkCli] success');
|
|
138
|
+
return parsed;
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
if (err instanceof LarkApiError)
|
|
142
|
+
throw err;
|
|
143
|
+
if (err instanceof LarkCliProtocolError)
|
|
144
|
+
throw err;
|
|
145
|
+
const error = err;
|
|
146
|
+
if (error.killed) {
|
|
147
|
+
throw new LarkCliUnavailableError(`lark-cli timed out after ${this.timeoutMs}ms`, err);
|
|
148
|
+
}
|
|
149
|
+
if (error.code === 'ENOENT') {
|
|
150
|
+
this.available = false;
|
|
151
|
+
throw new LarkCliUnavailableError('lark-cli binary not found', err);
|
|
152
|
+
}
|
|
153
|
+
const detail = error.stderr ? `: ${error.stderr.trim().slice(0, 500)}` : '';
|
|
154
|
+
throw new LarkCliUnavailableError(`lark-cli execution failed: ${error.message}${detail}`, err);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Parse lark-cli stdout. Accepts the `{ok, identity, data|error}` envelope.
|
|
159
|
+
* Some commands may emit pure data (no envelope); treat absence of `ok` as
|
|
160
|
+
* success and synthesize the envelope.
|
|
161
|
+
*/
|
|
162
|
+
parseOutput(raw) {
|
|
163
|
+
let parsed;
|
|
164
|
+
try {
|
|
165
|
+
parsed = JSON.parse(raw);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
throw new LarkCliProtocolError(`lark-cli returned non-JSON stdout (${raw.length} chars)`, err, raw.slice(0, 500));
|
|
169
|
+
}
|
|
170
|
+
if (typeof parsed.ok !== 'boolean') {
|
|
171
|
+
return { ok: true, data: parsed };
|
|
172
|
+
}
|
|
173
|
+
return parsed;
|
|
174
|
+
}
|
|
175
|
+
/** Reset cached availability (for testing) */
|
|
176
|
+
_resetCache() {
|
|
177
|
+
this.available = null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=LarkCliExecutor.js.map
|