@zhin.js/gateway 0.0.1-bootstrap.0 → 1.1.1
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/LICENSE +21 -0
- package/README.md +61 -0
- package/lib/config/ai-config-loader.d.ts +22 -0
- package/lib/config/ai-config-loader.js +96 -0
- package/lib/config/configuration-chat-policy.d.ts +57 -0
- package/lib/config/configuration-chat-policy.js +423 -0
- package/lib/config/configuration-patch.d.ts +16 -0
- package/lib/config/configuration-patch.js +105 -0
- package/lib/config/configuration-path.d.ts +6 -0
- package/lib/config/configuration-path.js +40 -0
- package/lib/config/configuration-projection.d.ts +6 -0
- package/lib/config/configuration-projection.js +28 -0
- package/lib/config/configuration-redaction.d.ts +24 -0
- package/lib/config/configuration-redaction.js +128 -0
- package/lib/config/configuration.d.ts +81 -0
- package/lib/config/configuration.js +686 -0
- package/lib/config/console-schema.d.ts +11 -0
- package/lib/config/console-schema.js +222 -0
- package/lib/config/environment-files.d.ts +2 -0
- package/lib/config/environment-files.js +21 -0
- package/lib/config/plugin-package-map.d.ts +2 -0
- package/lib/config/plugin-package-map.js +14 -0
- package/lib/config/plugin-schema-catalog.d.ts +22 -0
- package/lib/config/plugin-schema-catalog.js +262 -0
- package/lib/config.d.ts +12 -0
- package/lib/config.js +12 -0
- package/lib/database/config.d.ts +22 -0
- package/lib/database/config.js +159 -0
- package/lib/database/index.d.ts +2 -0
- package/lib/database/index.js +2 -0
- package/lib/database/project-database.d.ts +31 -0
- package/lib/database/project-database.js +130 -0
- package/lib/database.d.ts +2 -0
- package/lib/database.js +2 -0
- package/lib/http/console-rpc.d.ts +19 -0
- package/lib/http/console-rpc.js +51 -0
- package/lib/http/gateway-console.d.ts +63 -0
- package/lib/http/gateway-console.js +309 -0
- package/lib/http/gateway-events.d.ts +35 -0
- package/lib/http/gateway-events.js +172 -0
- package/lib/http/gateway-http-server.d.ts +95 -0
- package/lib/http/gateway-http-server.js +518 -0
- package/lib/http/local-principal.d.ts +2 -0
- package/lib/http/local-principal.js +16 -0
- package/lib/http/run-gateway.d.ts +43 -0
- package/lib/http/run-gateway.js +268 -0
- package/lib/http/start-gateway.d.ts +114 -0
- package/lib/http/start-gateway.js +220 -0
- package/lib/http-response.d.ts +6 -0
- package/lib/http-response.js +25 -0
- package/lib/http.d.ts +6 -0
- package/lib/http.js +6 -0
- package/lib/iam/crypto.d.ts +12 -0
- package/lib/iam/crypto.js +58 -0
- package/lib/iam/index.d.ts +8 -0
- package/lib/iam/index.js +8 -0
- package/lib/iam/install.d.ts +36 -0
- package/lib/iam/install.js +74 -0
- package/lib/iam/local-admin.d.ts +27 -0
- package/lib/iam/local-admin.js +102 -0
- package/lib/iam/rate-limit.d.ts +15 -0
- package/lib/iam/rate-limit.js +52 -0
- package/lib/iam/routes.d.ts +23 -0
- package/lib/iam/routes.js +157 -0
- package/lib/iam/rpc.d.ts +14 -0
- package/lib/iam/rpc.js +100 -0
- package/lib/iam/service.d.ts +203 -0
- package/lib/iam/service.js +784 -0
- package/lib/iam/setup-service.d.ts +83 -0
- package/lib/iam/setup-service.js +206 -0
- package/lib/iam/tables.d.ts +20 -0
- package/lib/iam/tables.js +82 -0
- package/lib/iam.d.ts +2 -0
- package/lib/iam.js +2 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +22 -0
- package/lib/local/client.d.ts +71 -0
- package/lib/local/client.js +296 -0
- package/lib/local/handler.d.ts +30 -0
- package/lib/local/handler.js +86 -0
- package/lib/local/local-socket.d.ts +55 -0
- package/lib/local/local-socket.js +232 -0
- package/lib/local/paths.d.ts +34 -0
- package/lib/local/paths.js +58 -0
- package/lib/local/protocol.d.ts +140 -0
- package/lib/local/protocol.js +90 -0
- package/lib/local/state-file.d.ts +8 -0
- package/lib/local/state-file.js +45 -0
- package/lib/local.d.ts +10 -0
- package/lib/local.js +10 -0
- package/lib/onboarding/allowlist.d.ts +54 -0
- package/lib/onboarding/allowlist.js +106 -0
- package/lib/onboarding/errors.d.ts +7 -0
- package/lib/onboarding/errors.js +17 -0
- package/lib/onboarding/index.d.ts +6 -0
- package/lib/onboarding/index.js +6 -0
- package/lib/onboarding/install.d.ts +99 -0
- package/lib/onboarding/install.js +132 -0
- package/lib/onboarding/package-manager.d.ts +47 -0
- package/lib/onboarding/package-manager.js +179 -0
- package/lib/onboarding/service.d.ts +53 -0
- package/lib/onboarding/service.js +144 -0
- package/lib/onboarding/state-store.d.ts +26 -0
- package/lib/onboarding/state-store.js +97 -0
- package/lib/onboarding.d.ts +2 -0
- package/lib/onboarding.js +2 -0
- package/lib/packages/authorization.d.ts +23 -0
- package/lib/packages/authorization.js +109 -0
- package/lib/packages/errors.d.ts +11 -0
- package/lib/packages/errors.js +26 -0
- package/lib/packages/index.d.ts +5 -0
- package/lib/packages/index.js +5 -0
- package/lib/packages/rpc.d.ts +30 -0
- package/lib/packages/rpc.js +155 -0
- package/lib/packages/service.d.ts +121 -0
- package/lib/packages/service.js +441 -0
- package/lib/packages/snapshot.d.ts +14 -0
- package/lib/packages/snapshot.js +73 -0
- package/lib/packages.d.ts +2 -0
- package/lib/packages.js +2 -0
- package/lib/plugin-management/activity.d.ts +6 -0
- package/lib/plugin-management/activity.js +14 -0
- package/lib/plugin-management/config-explanation.d.ts +6 -0
- package/lib/plugin-management/config-explanation.js +113 -0
- package/lib/plugin-management/use-cases.d.ts +15 -0
- package/lib/plugin-management/use-cases.js +120 -0
- package/lib/plugin-management/workflow.d.ts +92 -0
- package/lib/plugin-management/workflow.js +772 -0
- package/lib/plugin-management.d.ts +5 -0
- package/lib/plugin-management.js +5 -0
- package/lib/project/config-file.d.ts +6 -0
- package/lib/project/config-file.js +58 -0
- package/lib/project/environment.d.ts +13 -0
- package/lib/project/environment.js +52 -0
- package/lib/project/instance-declarations.d.ts +55 -0
- package/lib/project/instance-declarations.js +542 -0
- package/lib/project/legacy-config.d.ts +25 -0
- package/lib/project/legacy-config.js +70 -0
- package/lib/project/node-requirements.d.ts +7 -0
- package/lib/project/node-requirements.js +20 -0
- package/lib/project/tsx-module-loader.d.ts +7 -0
- package/lib/project/tsx-module-loader.js +29 -0
- package/lib/project.d.ts +7 -0
- package/lib/project.js +7 -0
- package/lib/server.d.ts +11 -0
- package/lib/server.js +11 -0
- package/lib/supervisor/instance-logs.d.ts +39 -0
- package/lib/supervisor/instance-logs.js +152 -0
- package/lib/supervisor/instance-supervisor.d.ts +140 -0
- package/lib/supervisor/instance-supervisor.js +408 -0
- package/lib/supervisor/run-instance.d.ts +35 -0
- package/lib/supervisor/run-instance.js +161 -0
- package/lib/supervisor/terminal-state.d.ts +30 -0
- package/lib/supervisor/terminal-state.js +57 -0
- package/lib/supervisor.d.ts +4 -0
- package/lib/supervisor.js +4 -0
- package/lib/terminal/backends.d.ts +38 -0
- package/lib/terminal/backends.js +193 -0
- package/lib/terminal/config.d.ts +63 -0
- package/lib/terminal/config.js +237 -0
- package/lib/terminal/host.d.ts +26 -0
- package/lib/terminal/host.js +76 -0
- package/lib/terminal/index.d.ts +60 -0
- package/lib/terminal/index.js +135 -0
- package/lib/terminal/modules.d.ts +87 -0
- package/lib/terminal/modules.js +44 -0
- package/lib/terminal/service.d.ts +101 -0
- package/lib/terminal/service.js +455 -0
- package/lib/terminal/tickets.d.ts +46 -0
- package/lib/terminal/tickets.js +76 -0
- package/lib/terminal.d.ts +3 -0
- package/lib/terminal.js +3 -0
- package/lib/usage/aggregate.d.ts +87 -0
- package/lib/usage/aggregate.js +212 -0
- package/lib/usage.d.ts +2 -0
- package/lib/usage.js +2 -0
- package/package.json +117 -4
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { parsePluginConfigSchema } from '@zhin.js/runtime';
|
|
3
|
+
import { readStringLiteralOptions } from '../config/console-schema.js';
|
|
4
|
+
const complexKeywords = ['$ref', '$dynamicRef', 'anyOf', 'oneOf', 'allOf', 'not', 'if', 'then', 'else', 'dependentSchemas', 'dependentRequired', 'patternProperties', 'prefixItems', 'unevaluatedProperties'];
|
|
5
|
+
const forbiddenKeys = new Set(['__proto__', 'prototype', 'constructor']);
|
|
6
|
+
const record = (value) => Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
7
|
+
/** Read-only metadata projection. Actual values never leave this function. */
|
|
8
|
+
export function explainConfigFields(schema, input, current, committedInput) {
|
|
9
|
+
const unsupported = (reason) => ({ projection: 'unsupported', reason, fields: [] });
|
|
10
|
+
const fields = [];
|
|
11
|
+
let issue;
|
|
12
|
+
const visit = (node, path, inheritedSecret) => {
|
|
13
|
+
if (issue)
|
|
14
|
+
return;
|
|
15
|
+
if (!record(node)) {
|
|
16
|
+
issue = 'complex-schema';
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// A fixed string-literal union is the one `anyOf` the Console form edits as a plain choice; its value is a string scalar.
|
|
20
|
+
const literalChoice = readStringLiteralOptions(node) !== null;
|
|
21
|
+
if (!literalChoice && (complexKeywords.some(key => Object.hasOwn(node, key))
|
|
22
|
+
|| (node.additionalProperties !== undefined && node.additionalProperties !== false))) {
|
|
23
|
+
issue = 'complex-schema';
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (path.length > 8 || path.some(key => !key || key.length > 128 || forbiddenKeys.has(key))) {
|
|
27
|
+
issue = 'bound-exceeded';
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const metadata = record(node['x-zhin-config']) ? node['x-zhin-config'] : {};
|
|
31
|
+
const secret = inheritedSecret || node.writeOnly === true || metadata.secret === true || metadata.hidden === true || metadata.component === 'password';
|
|
32
|
+
if (node.type === 'object') {
|
|
33
|
+
if (node.properties !== undefined && !record(node.properties)) {
|
|
34
|
+
issue = 'complex-schema';
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const [key, child] of Object.entries(node.properties ?? {}))
|
|
38
|
+
visit(child, [...path, key], secret);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// A homogeneous list of scalars (e.g. the framework's `disabledCommands`) is one leaf value, compared as a whole.
|
|
42
|
+
const list = !literalChoice && node.type === 'array';
|
|
43
|
+
if (list && !scalarList(node)) {
|
|
44
|
+
issue = 'complex-schema';
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!literalChoice && !list && !scalarSchema(node)) {
|
|
48
|
+
issue = 'complex-schema';
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (path.length === 0 || fields.length >= 128) {
|
|
52
|
+
issue = 'bound-exceeded';
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const items = list && record(node.items) ? node.items : {};
|
|
56
|
+
const itemMetadata = record(items['x-zhin-config']) ? items['x-zhin-config'] : {};
|
|
57
|
+
const itemSecret = items.writeOnly === true || itemMetadata.secret === true || itemMetadata.hidden === true || itemMetadata.component === 'password';
|
|
58
|
+
fields.push({ path: Object.freeze([...path]), secret: secret || itemSecret, list });
|
|
59
|
+
};
|
|
60
|
+
visit(schema, [], false);
|
|
61
|
+
if (issue)
|
|
62
|
+
return unsupported(issue);
|
|
63
|
+
let candidate;
|
|
64
|
+
try {
|
|
65
|
+
candidate = parsePluginConfigSchema(schema, input);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return unsupported('candidate-invalid');
|
|
69
|
+
}
|
|
70
|
+
if (candidate.errors.length)
|
|
71
|
+
return unsupported('candidate-invalid');
|
|
72
|
+
const metadata = fields.map(field => {
|
|
73
|
+
const raw = at(input, field.path), next = at(candidate.value, field.path), applied = at(current, field.path), beforeExpansion = at(committedInput, field.path);
|
|
74
|
+
const candidateOrigin = raw.present ? 'file' : next.present ? 'schema-default' : 'unset';
|
|
75
|
+
const redacted = field.secret || [raw, next, beforeExpansion].some(item => (field.list && Array.isArray(item.value) ? item.value : [item.value])
|
|
76
|
+
.some(value => typeof value === 'string' && value.includes('${')));
|
|
77
|
+
// A schema scalar (or scalar list) unexpectedly represented by an object or missing pre-expansion
|
|
78
|
+
// evidence has no proven standard comparison. Never serialize that value to explain why.
|
|
79
|
+
const unknown = [next, applied].some(item => !scalarValue(item.value, field.list))
|
|
80
|
+
|| (applied.present && !beforeExpansion.present);
|
|
81
|
+
const generationMatch = redacted ? 'redacted' : unknown ? 'unknown'
|
|
82
|
+
: next.present === applied.present && isDeepStrictEqual(next.value, applied.value) ? 'equal' : 'different';
|
|
83
|
+
return Object.freeze({ path: field.path, candidateOrigin, generationMatch, generationOrigin: 'unknown' });
|
|
84
|
+
});
|
|
85
|
+
if (Buffer.byteLength(JSON.stringify(metadata), 'utf8') > 16 * 1024)
|
|
86
|
+
return unsupported('bound-exceeded');
|
|
87
|
+
return Object.freeze({ projection: 'supported', fields: Object.freeze(metadata) });
|
|
88
|
+
}
|
|
89
|
+
function scalarSchema(node) {
|
|
90
|
+
return readStringLiteralOptions(node) !== null || (['string', 'number', 'integer', 'boolean', 'null'].includes(String(node.type)) && !Array.isArray(node.type));
|
|
91
|
+
}
|
|
92
|
+
/** `{ type: 'array', items: <scalar or literal choice> }` with no tuple, `contains` or nested structure. */
|
|
93
|
+
function scalarList(node) {
|
|
94
|
+
const items = node.items;
|
|
95
|
+
if (!record(items) || Object.hasOwn(node, 'contains') || Object.hasOwn(node, 'additionalItems'))
|
|
96
|
+
return false;
|
|
97
|
+
if (readStringLiteralOptions(items) === null && (complexKeywords.some(key => Object.hasOwn(items, key)) || items.additionalProperties !== undefined))
|
|
98
|
+
return false;
|
|
99
|
+
return scalarSchema(items);
|
|
100
|
+
}
|
|
101
|
+
function scalarValue(value, list) {
|
|
102
|
+
if (value === null || typeof value !== 'object')
|
|
103
|
+
return true;
|
|
104
|
+
return list && Array.isArray(value) && value.every(item => item === null || typeof item !== 'object');
|
|
105
|
+
}
|
|
106
|
+
function at(value, path) {
|
|
107
|
+
for (const key of path) {
|
|
108
|
+
if (!record(value) || !Object.hasOwn(value, key))
|
|
109
|
+
return { present: false };
|
|
110
|
+
value = value[key];
|
|
111
|
+
}
|
|
112
|
+
return { present: true, value };
|
|
113
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Locale } from '@zhin.js/plugin-runtime';
|
|
2
|
+
/** Task-oriented entry points. Installation/configuration remain owned by their existing services. */
|
|
3
|
+
export interface PluginUseCase {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly title: string;
|
|
6
|
+
readonly summary: string;
|
|
7
|
+
readonly packageName: string;
|
|
8
|
+
readonly mode: 'administrator-task' | 'plugin-configuration' | 'conversation-command';
|
|
9
|
+
readonly steps: readonly string[];
|
|
10
|
+
readonly limitations: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const pluginUseCases: readonly PluginUseCase[];
|
|
13
|
+
/** Locale is display-only; package IDs and workflow effects are unchanged. */
|
|
14
|
+
export declare function listPluginUseCases(locale?: Locale): readonly PluginUseCase[];
|
|
15
|
+
export declare function formatPluginUseCases(id?: string): string;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export const pluginUseCases = Object.freeze([
|
|
2
|
+
{
|
|
3
|
+
id: 'rss-subscription', title: '订阅 RSS 新文章', summary: '在任务中心选择精确目标,创建独立管理员 RSS 订阅。',
|
|
4
|
+
packageName: '@zhin.js/plugin-rss', mode: 'administrator-task',
|
|
5
|
+
steps: ['通过现有安装计划安装 RSS 插件,配置持久 Database Host、Schedule Host 与支持精确目标的 Outbound Host,保存并应用。',
|
|
6
|
+
'在 Console 任务中心选择当前已加载的 RSS 实例,核对账号、会话、父容器和线程。',
|
|
7
|
+
'填写 HTTP(S) RSS / Atom 地址,阅读抓取和发送影响后创建;保存不会抓取源站或发送测试消息。',
|
|
8
|
+
'等待实例轮询首次成功抓取建立基线;后续新文章才推送,可在任务中心暂停、编辑、恢复或取消。'],
|
|
9
|
+
limitations: ['首次和暂停恢复后的首次成功抓取只建基线,不补发历史;每轮只检查源前 20 条,最多展示 maxItems 条,其余提示查看源站,不续轮补发。',
|
|
10
|
+
'管理员订阅与聊天订阅独立;聊天 rss-list、rss-remove 和 rss-check 不管理或触发管理员任务。',
|
|
11
|
+
'投递未确认时不自动重试或恢复,只能取消;先核实目标会话,确需继续时再新建。'],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'reminder', title: '创建定时提醒', summary: '指定账号、会话与时间,创建可查看、暂停和取消的管理员提醒。',
|
|
15
|
+
packageName: '@zhin.js/plugin-reminder', mode: 'administrator-task',
|
|
16
|
+
steps: ['通过现有安装计划安装持久提醒插件,配置 Database Host 并应用。',
|
|
17
|
+
'在 Console 任务中心选择当前已加载的提醒实例,检查当前版本与可用表单。',
|
|
18
|
+
'选择精确投递账号及会话,填写含时区的时间和内容,确认未来发送的效果后创建。',
|
|
19
|
+
'查看下次执行时间与实际结果;投递不确定时先核对目标会话,不盲目重建。'],
|
|
20
|
+
limitations: ['Console 只管理管理员任务;聊天创建的提醒仍归原发送者。',
|
|
21
|
+
'调度状态不证明平台在线或消息已展示;周期表示固定间隔,不能当作跨夏令时的本地日历计划。'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'personal-reminder', title: '在聊天里设置个人提醒', summary: '不启用 AI,补全时间和正文,确认后保存到原会话。',
|
|
25
|
+
packageName: '@zhin.js/plugin-reminder', mode: 'conversation-command',
|
|
26
|
+
steps: ['安装持久提醒插件,启用 Database Host 与 Schedule Feature,明确实例的 IANA timezone;默认 UTC。',
|
|
27
|
+
'由真实发送者在原会话执行 remind create 十分钟后 喝水;只输入 remind create 时会逐项补问。',
|
|
28
|
+
'使用实际前缀与插件命名空间;后续回答不加前缀,取消后再开始新流程。',
|
|
29
|
+
'核对绝对时间、时区、正文与完整原会话,明确回复“确认”才保存;用 remind list 查看个人任务。'],
|
|
30
|
+
limitations: ['仅支持有限中英文自然时间与含 offset 的 ISO 时间,不猜发送者时区、裸时钟或夏令时重复时刻。',
|
|
31
|
+
'聊天任务归真实发送者;Console 不代为执行,管理员任务仍使用单独入口。',
|
|
32
|
+
'超时或取消不创建;投递或创建结果不确定时先核对,不自动重试。'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'group-welcome', title: '配置新成员欢迎', summary: '为上报群成员增加事件的适配器配置欢迎文案。',
|
|
36
|
+
packageName: '@zhin.js/plugin-group-suite', mode: 'plugin-configuration',
|
|
37
|
+
steps: ['安装群运营套件并选择明确的插件实例;多实例时不能只凭包名修改。',
|
|
38
|
+
'在该实例的配置表单填写 welcomeMessage,使用 {name} 和 {id} 表示新成员。',
|
|
39
|
+
'在 welcomeConversations 选择精确账号与群;父容器和话题如有也须填写。核对范围后保存并应用。',
|
|
40
|
+
'等待真实且已授权的成员加入事件验证,查看投递结果,不能用启动成功代替。'],
|
|
41
|
+
limitations: ['适配器须提供标准群成员增加通知,缺少该事件时不会发送。',
|
|
42
|
+
'省略 welcomeConversations 覆盖全部符合条件的群;空列表不欢迎任何会话。未填父容器或话题只匹配同样缺省的事件,不是通配。',
|
|
43
|
+
'此范围只约束欢迎,不改变签到、词库、统计或命令 permit;空 welcomeMessage 仍关闭欢迎。'],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'keyword-reply', title: '配置群关键词回复', summary: '在指定群维护持久词库,并检查修改权限与匹配效果。',
|
|
47
|
+
packageName: '@zhin.js/plugin-group-suite', mode: 'conversation-command',
|
|
48
|
+
steps: ['安装群运营套件,配置持久 Database Host,在目标实例启用 keywordReply 并应用。',
|
|
49
|
+
'由有权限的真实群成员在目标群使用 keyword-add、keyword-list 和 keyword-remove。',
|
|
50
|
+
'使用该账号实际配置的前缀与命名空间,添加词条后在同一精确会话验证。'],
|
|
51
|
+
limitations: ['Console 不伪造平台用户执行聊天命令;词库仍由插件按完整会话持久化。',
|
|
52
|
+
'两个 Bot、父频道或话题不同的同名会话互不共享词库;临时词库不是持久保存。'],
|
|
53
|
+
},
|
|
54
|
+
].map(item => Object.freeze({ ...item, steps: Object.freeze(item.steps), limitations: Object.freeze(item.limitations) })));
|
|
55
|
+
const englishUseCases = {
|
|
56
|
+
'rss-subscription': {
|
|
57
|
+
title: 'Subscribe to RSS updates', summary: 'Choose an exact delivery target and create a separate administrator RSS subscription.',
|
|
58
|
+
steps: ['Install the RSS plugin through the existing installation plan. Configure persistent Database, Schedule and Outbound Hosts with exact-target support, then save and apply.',
|
|
59
|
+
'Select a loaded RSS instance in Console Tasks. Check its account, conversation, parent container and thread.',
|
|
60
|
+
'Enter an HTTP(S) RSS or Atom URL, review fetching and delivery effects, then create. Saving does not fetch the feed or send a test message.',
|
|
61
|
+
'Wait for the first successful poll to establish a baseline. Only later articles are delivered. Use Tasks to pause, edit, resume or cancel.'],
|
|
62
|
+
limitations: ['The first successful fetch after creation or resuming only establishes a baseline; it does not replay history. Each poll examines the first 20 feed entries and displays at most maxItems, with a link to the feed for the remainder; there is no later catch-up.',
|
|
63
|
+
'Administrator subscriptions and chat subscriptions are separate. Chat rss-list, rss-remove and rss-check do not manage or trigger administrator tasks.',
|
|
64
|
+
'An unconfirmed delivery is not automatically retried or resumed. Cancel it, check the destination, and create a new task only if needed.'],
|
|
65
|
+
},
|
|
66
|
+
reminder: {
|
|
67
|
+
title: 'Create a scheduled reminder', summary: 'Choose an account, conversation and time, then manage an administrator reminder.',
|
|
68
|
+
steps: ['Install the persistent reminder plugin, configure a Database Host and apply.',
|
|
69
|
+
'Select a loaded reminder instance in Console Tasks and check its current version and form.',
|
|
70
|
+
'Choose the exact account and conversation, enter a time with an offset and the content, and confirm the future delivery effect.',
|
|
71
|
+
'Check the next execution time and actual result. If delivery is uncertain, inspect the destination before creating another task.'],
|
|
72
|
+
limitations: ['Console manages administrator tasks only. Chat reminders still belong to the actual sender.',
|
|
73
|
+
'A scheduled state does not prove the platform is online or the message was displayed. Recurrence is a fixed interval, not a local calendar schedule spanning daylight-saving changes.'],
|
|
74
|
+
},
|
|
75
|
+
'personal-reminder': {
|
|
76
|
+
title: 'Set a personal reminder in chat', summary: 'Provide a time and message, then confirm saving to the original conversation without enabling AI.',
|
|
77
|
+
steps: ['Install the persistent reminder plugin, enable the Database Host and Schedule Feature, and configure an explicit IANA timezone; the default is UTC.',
|
|
78
|
+
'Use the remind create command in the original conversation with a real sender identity, for example with arguments in 10 minutes drink water. With no arguments, the command asks for the missing information.',
|
|
79
|
+
'Use the actual account prefix and plugin namespace. Follow-up answers do not need a prefix; cancel before starting a new flow.',
|
|
80
|
+
'Check the absolute time, timezone, content and complete original conversation. Reply with the current language’s confirmation input to save; use remind list to view personal tasks.'],
|
|
81
|
+
limitations: ['Only bounded Chinese and English natural-time syntax and offset-bearing ISO times are supported. Sender timezone, bare clock times and ambiguous daylight-saving times are not guessed.',
|
|
82
|
+
'Chat tasks belong to the actual sender. Console does not impersonate that sender; administrator tasks have a separate entry.',
|
|
83
|
+
'Timeout or cancellation creates no task. If creation or delivery is uncertain, check first; there is no automatic retry.'],
|
|
84
|
+
},
|
|
85
|
+
'group-welcome': {
|
|
86
|
+
title: 'Welcome new group members', summary: 'Configure welcome content for adapters that report group member additions.',
|
|
87
|
+
steps: ['Install the group suite and select an exact plugin instance. A package name is insufficient when multiple instances exist.',
|
|
88
|
+
'Set welcomeMessage in the instance form. Use {name} and {id} for the new member.',
|
|
89
|
+
'Select exact accounts and groups in welcomeConversations, including parent containers and threads when present. Check the scope, save and apply.',
|
|
90
|
+
'Verify an authorized real member-addition event and its delivery result. Successful startup is not business verification.'],
|
|
91
|
+
limitations: ['The adapter must report a standard group-member-addition notice. Without it, no welcome is sent.',
|
|
92
|
+
'Omitting welcomeConversations covers all eligible groups; an empty list covers none. Missing parent or thread only matches events that also lack it, not a wildcard.',
|
|
93
|
+
'This scope only controls welcome messages, not check-ins, keywords, statistics or command permit. An empty welcomeMessage still disables welcoming.'],
|
|
94
|
+
},
|
|
95
|
+
'keyword-reply': {
|
|
96
|
+
title: 'Configure group keyword replies', summary: 'Maintain a persistent keyword library in a specific group and verify permissions and matching.',
|
|
97
|
+
steps: ['Install the group suite, configure a persistent Database Host, enable keywordReply on the target instance, and apply.',
|
|
98
|
+
'An authorized real group member uses keyword-add, keyword-list and keyword-remove in the target group.',
|
|
99
|
+
'Use the account’s actual prefix and namespace, then verify the new entry in the same exact conversation.'],
|
|
100
|
+
limitations: ['Console does not impersonate platform users. The plugin persists entries against the full conversation identity.',
|
|
101
|
+
'Different Bot accounts, parent channels or threads do not share a keyword library just because conversation names match. Temporary entries are not persistent.'],
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
/** Locale is display-only; package IDs and workflow effects are unchanged. */
|
|
105
|
+
export function listPluginUseCases(locale = 'zh-CN') {
|
|
106
|
+
return locale === 'en' ? pluginUseCases.map(item => ({ ...item, ...englishUseCases[item.id] })) : pluginUseCases;
|
|
107
|
+
}
|
|
108
|
+
export function formatPluginUseCases(id) {
|
|
109
|
+
const selected = id === undefined ? pluginUseCases : pluginUseCases.filter(item => item.id === id);
|
|
110
|
+
if (!selected.length)
|
|
111
|
+
throw new Error(`未知用途:${id}。运行 zhin use-cases 查看可用入口。`);
|
|
112
|
+
if (id === undefined)
|
|
113
|
+
return ['我想做什么?', ...selected.map(item => ` ${item.id} ${item.title}\n ${item.summary}`),
|
|
114
|
+
'', '运行 zhin use-cases <用途> 查看步骤;本命令不安装、不改配置、不启动 Runtime,也不发送消息。'].join('\n');
|
|
115
|
+
const item = selected[0];
|
|
116
|
+
return [`${item.title} (${item.id})`, item.summary, '', `插件:${item.packageName}`,
|
|
117
|
+
`安装预览:zhin install ${item.packageName} --dry-run`, '',
|
|
118
|
+
...item.steps.map((step, index) => `${index + 1}. ${step}`), '', '适用边界:',
|
|
119
|
+
...item.limitations.map(value => `- ${value}`)].join('\n');
|
|
120
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type SnapshotReader } from '@zhin.js/plugin-runtime';
|
|
2
|
+
import type { PluginInstallPlan, PluginManagementPort, PluginManagementState, PluginUninstallPlan, PluginUpdatePlan, PluginVerificationResult, PluginVerificationPlan } from '@zhin.js/host-http';
|
|
3
|
+
import type { ConsoleConfigExplanation, GatewayPluginConfigurationNeed } from '@zhin.js/console-protocol';
|
|
4
|
+
import type { PluginManagementActivity } from './activity.js';
|
|
5
|
+
export interface PluginManagementOptions {
|
|
6
|
+
readonly activity?: PluginManagementActivity;
|
|
7
|
+
readonly snapshots?: SnapshotReader;
|
|
8
|
+
readonly requestRestart?: () => void | Promise<void>;
|
|
9
|
+
/** Real package-manager boundary; tests use independent on-disk packages. */
|
|
10
|
+
readonly installDependencies?: (args: readonly string[], projectRoot: string) => Promise<void>;
|
|
11
|
+
readonly lifecycleFile?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PluginInstallOptions {
|
|
14
|
+
readonly enable?: boolean;
|
|
15
|
+
readonly saveDev?: boolean;
|
|
16
|
+
readonly source?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface PluginDeclareOptions {
|
|
19
|
+
/** Default: derived from the package name. */
|
|
20
|
+
readonly instanceKey?: string;
|
|
21
|
+
/** Root config revision the caller read (CAS). */
|
|
22
|
+
readonly expectedRevision?: string;
|
|
23
|
+
}
|
|
24
|
+
/** A declaration refused before anything was written (invalid request or configuration). */
|
|
25
|
+
export declare class PluginDeclarationError extends Error {
|
|
26
|
+
readonly errors: readonly {
|
|
27
|
+
readonly path: string;
|
|
28
|
+
readonly message: string;
|
|
29
|
+
}[];
|
|
30
|
+
readonly missingEnv: readonly string[];
|
|
31
|
+
constructor(message: string, errors?: readonly {
|
|
32
|
+
path: string;
|
|
33
|
+
message: string;
|
|
34
|
+
}[], missingEnv?: readonly string[]);
|
|
35
|
+
}
|
|
36
|
+
/** One installation authority for CLI and Console. No generation or package parser of its own. */
|
|
37
|
+
export declare class PluginManagementWorkflow implements PluginManagementPort {
|
|
38
|
+
#private;
|
|
39
|
+
constructor(projectRoot: string, options?: PluginManagementOptions);
|
|
40
|
+
inspect(request: string): Promise<PluginManagementState>;
|
|
41
|
+
/** Full-scope, metadata-only projection over the same file and generation observation as inspect. */
|
|
42
|
+
explainConfiguration(instanceKey: string, expectedRevision: string): Promise<ConsoleConfigExplanation>;
|
|
43
|
+
planInstall(request: string, options?: Pick<PluginInstallOptions, 'enable'>): Promise<PluginInstallPlan>;
|
|
44
|
+
install(request: string, expectedRevision?: string, expectedProjectRevision?: string, options?: PluginInstallOptions): Promise<Readonly<{
|
|
45
|
+
readonly plan: PluginInstallPlan;
|
|
46
|
+
readonly restartRequired: boolean;
|
|
47
|
+
readonly state: PluginManagementState;
|
|
48
|
+
readonly needsConfiguration?: GatewayPluginConfigurationNeed;
|
|
49
|
+
}>>;
|
|
50
|
+
/**
|
|
51
|
+
* Validates a declaration without writing: the package must be an installed production plugin,
|
|
52
|
+
* the instance key free, and `config` must satisfy the plugin schema (defaults applied).
|
|
53
|
+
*/
|
|
54
|
+
planDeclaration(request: string, config?: unknown, options?: Pick<PluginDeclareOptions, 'instanceKey'>): Promise<Readonly<{
|
|
55
|
+
revision: string;
|
|
56
|
+
schemaAvailable: boolean | undefined;
|
|
57
|
+
packageName: string;
|
|
58
|
+
instanceKey: string;
|
|
59
|
+
}>>;
|
|
60
|
+
/** Declares `plugins.<instanceKey>` and writes its section in one atomic config write. */
|
|
61
|
+
declare(request: string, config?: unknown, options?: PluginDeclareOptions): Promise<Readonly<{
|
|
62
|
+
packageName: string;
|
|
63
|
+
instanceKey: string;
|
|
64
|
+
restartRequired: boolean;
|
|
65
|
+
state: PluginManagementState;
|
|
66
|
+
}>>;
|
|
67
|
+
planUninstall(request: string): Promise<PluginUninstallPlan>;
|
|
68
|
+
uninstall(request: string, expectedRevision?: string, expectedProjectRevision?: string, options?: {
|
|
69
|
+
removeDependency?: boolean;
|
|
70
|
+
}): Promise<{
|
|
71
|
+
plan: PluginUninstallPlan;
|
|
72
|
+
restartRequired: boolean;
|
|
73
|
+
}>;
|
|
74
|
+
planUpdate(request: string, targetVersion: string): Promise<PluginUpdatePlan>;
|
|
75
|
+
update(request: string, targetVersion: string, expectedRevision?: string, expectedProjectRevision?: string): Promise<{
|
|
76
|
+
plan: PluginUpdatePlan;
|
|
77
|
+
installedVersion: string;
|
|
78
|
+
restartRequired: boolean;
|
|
79
|
+
}>;
|
|
80
|
+
apply(request: string, expectedProjectRevision?: string): Promise<{
|
|
81
|
+
requested: boolean;
|
|
82
|
+
state: PluginManagementState;
|
|
83
|
+
}>;
|
|
84
|
+
verificationPlan(request: string): Promise<PluginVerificationPlan>;
|
|
85
|
+
verify(request: string, checkId: string, expectedGeneration: number | null, expectedProjectRevision: string, expectedCheckRevision: string): Promise<PluginVerificationResult>;
|
|
86
|
+
}
|
|
87
|
+
/** Errors retain completed-stage evidence instead of pretending an unsafe rollback succeeded. */
|
|
88
|
+
export declare class PluginManagementOperationError extends Error {
|
|
89
|
+
readonly completedStages: readonly string[];
|
|
90
|
+
readonly state?: PluginManagementState;
|
|
91
|
+
constructor(cause: unknown, completed: readonly string[], state?: PluginManagementState);
|
|
92
|
+
}
|