@capybara-hq/kernel 0.1.0-rc.19 → 0.1.0-rc.20
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/bootstrap.json +25 -25
- package/dist/admin/admin-agents.controller.d.ts +28 -0
- package/dist/admin/admin-agents.controller.js +79 -0
- package/dist/admin/admin-connectors.controller.d.ts +29 -0
- package/dist/admin/admin-connectors.controller.js +79 -0
- package/dist/admin/admin-items.schemas.d.ts +42 -0
- package/dist/admin/admin-items.schemas.js +48 -0
- package/dist/admin/admin.controller.d.ts +11 -20
- package/dist/admin/admin.controller.js +28 -34
- package/dist/admin/admin.module.d.ts +4 -1
- package/dist/admin/admin.module.js +50 -4
- package/dist/admin/admin.schemas.d.ts +4 -0
- package/dist/admin/admin.schemas.js +4 -1
- package/dist/admin/audit.controller.d.ts +14 -0
- package/dist/admin/audit.controller.js +42 -0
- package/dist/admin/audit.schemas.d.ts +8 -0
- package/dist/admin/audit.schemas.js +17 -0
- package/dist/admin/health.service.d.ts +104 -0
- package/dist/admin/health.service.js +246 -0
- package/dist/admin/overview.controller.d.ts +31 -0
- package/dist/admin/overview.controller.js +78 -0
- package/dist/admin/overview.schemas.d.ts +8 -0
- package/dist/admin/overview.schemas.js +13 -0
- package/dist/admin/overview.service.d.ts +112 -0
- package/dist/admin/overview.service.js +223 -0
- package/dist/admin/platform-audit.service.d.ts +41 -0
- package/dist/admin/platform-audit.service.js +70 -0
- package/dist/admin/usage.service.d.ts +149 -0
- package/dist/admin/usage.service.js +321 -0
- package/dist/agents/admin-agents.d.ts +92 -0
- package/dist/agents/admin-agents.js +170 -0
- package/dist/agents/agents.module.d.ts +4 -3
- package/dist/agents/agents.module.js +7 -3
- package/dist/agents/agents.service.d.ts +41 -3
- package/dist/agents/agents.service.js +113 -27
- package/dist/agents/definition.d.ts +16 -0
- package/dist/agents/definition.js +32 -2
- package/dist/agents/org-templates.js +4 -0
- package/dist/background/launch.d.ts +1 -1
- package/dist/background/launch.js +3 -3
- package/dist/background/tasks.service.d.ts +35 -3
- package/dist/background/tasks.service.js +91 -36
- package/dist/catalog/catalog.module.js +8 -2
- package/dist/channels/channels.module.js +11 -2
- package/dist/channels/supervisor.d.ts +2 -0
- package/dist/channels/supervisor.js +4 -0
- package/dist/conversations/events.js +1 -1
- package/dist/conversations/titles.js +4 -8
- package/dist/db/kernel-schema.d.ts +8 -1
- package/dist/devices/devices.module.js +5 -2
- package/dist/errors.d.ts +22 -2
- package/dist/errors.js +22 -2
- package/dist/evals/evals.module.js +11 -2
- package/dist/extensions/permission-view.js +3 -3
- package/dist/http/api.module.js +10 -1
- package/dist/identity/settings.js +3 -2
- package/dist/identity/write-actor.d.ts +11 -0
- package/dist/identity/write-actor.js +7 -0
- package/dist/inbox/inbox.service.d.ts +2 -1
- package/dist/inbox/platform-inbox.service.d.ts +7 -1
- package/dist/inbox/platform-inbox.service.js +20 -2
- package/dist/infra/presence/presence.module.d.ts +6 -0
- package/dist/infra/presence/presence.module.js +22 -0
- package/dist/infra/presence/replica-presence.d.ts +87 -0
- package/dist/infra/presence/replica-presence.js +189 -0
- package/dist/kernel.module.js +3 -0
- package/dist/lifecycle/kernel-lifecycle.d.ts +3 -1
- package/dist/lifecycle/kernel-lifecycle.js +17 -3
- package/dist/local-devices/local-device-gateway.d.ts +6 -0
- package/dist/local-devices/local-device-gateway.js +12 -0
- package/dist/overrides.d.ts +7 -1
- package/dist/overrides.js +4 -0
- package/dist/platform/exclusions.js +15 -0
- package/dist/platform/operation.d.ts +1 -1
- package/dist/platform/operation.js +1 -1
- package/dist/platform/ops/admin-items.ops.d.ts +37 -0
- package/dist/platform/ops/admin-items.ops.js +278 -0
- package/dist/platform/ops/admin.ops.d.ts +10 -10
- package/dist/platform/ops/admin.ops.js +111 -42
- package/dist/platform/ops/agent-skills.ops.d.ts +29 -3
- package/dist/platform/ops/agent-skills.ops.js +290 -7
- package/dist/platform/ops/inbox.ops.js +7 -4
- package/dist/platform/ops/platform.ops.js +1 -1
- package/dist/platform/ops/skills.ops.js +1 -57
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +8 -0
- package/dist/roles/gateway-role.d.ts +4 -0
- package/dist/roles/gateway-role.js +8 -0
- package/dist/runtime/run-jobs.js +4 -2
- package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
- package/dist/sandbox/sandbox-file-reader.js +98 -0
- package/dist/skills/agent-skills.controller.d.ts +48 -0
- package/dist/skills/agent-skills.controller.js +131 -0
- package/dist/skills/agent-skills.service.d.ts +262 -0
- package/dist/skills/agent-skills.service.js +996 -0
- package/dist/skills/catalog.d.ts +8 -6
- package/dist/skills/catalog.js +8 -4
- package/dist/skills/changes.controller.d.ts +5 -22
- package/dist/skills/changes.controller.js +7 -55
- package/dist/skills/changes.service.d.ts +69 -115
- package/dist/skills/changes.service.js +249 -599
- package/dist/skills/conversation-catalog.d.ts +53 -0
- package/dist/skills/conversation-catalog.js +149 -0
- package/dist/skills/invocation-keys.d.ts +1 -1
- package/dist/skills/invocation-keys.js +1 -1
- package/dist/skills/package.d.ts +0 -5
- package/dist/skills/package.js +0 -14
- package/dist/skills/skills.controller.js +1 -10
- package/dist/skills/skills.module.js +15 -2
- package/dist/skills/skills.schemas.d.ts +11 -15
- package/dist/skills/skills.schemas.js +27 -20
- package/dist/skills/skills.service.d.ts +37 -30
- package/dist/skills/skills.service.js +107 -59
- package/dist/skills/usable.d.ts +4 -3
- package/dist/skills/usable.js +13 -4
- package/dist/tools/admin-connectors.d.ts +69 -0
- package/dist/tools/admin-connectors.js +135 -0
- package/dist/tools/builtin-names.d.ts +9 -14
- package/dist/tools/builtin-names.js +13 -22
- package/dist/tools/builtins/builtin-tools.d.ts +2 -3
- package/dist/tools/builtins/builtin-tools.js +21 -26
- package/dist/tools/builtins/sandbox/bash.js +2 -2
- package/dist/tools/builtins/skill-load.js +1 -0
- package/dist/tools/connectors.controller.d.ts +5 -1
- package/dist/tools/connectors.controller.js +17 -3
- package/dist/tools/connectors.service.d.ts +20 -1
- package/dist/tools/connectors.service.js +61 -0
- package/dist/tools/legacy-keys.js +0 -1
- package/dist/tools/org-tool-switches.d.ts +3 -2
- package/dist/tools/org-tool-switches.js +4 -4
- package/dist/tools/policy.d.ts +7 -9
- package/dist/tools/policy.js +11 -22
- package/dist/tools/tools.module.js +3 -0
- package/package.json +1 -1
- package/src/migrations/0109_audit_platform_idx.sql +5 -0
- package/src/migrations/0110_usage_time_idx.sql +8 -0
- package/src/migrations/0111_agent_skill_sharing.sql +22 -0
- package/src/migrations/0112_skill_share_requests.sql +4 -0
- package/dist/tools/builtins/skill-write.d.ts +0 -59
- package/dist/tools/builtins/skill-write.js +0 -244
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { isSkillName, parseSkillMd, SKILL_ENTRY_FILE, SKILL_LIMITS, skillInvocation, ToolError, validateSkillPackage, } from "@capybara-hq/plugin-sdk";
|
|
3
|
-
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4
|
-
import { storedInvocation } from "../../skills/package.js";
|
|
5
|
-
import { display } from "./sandbox/common.js";
|
|
6
|
-
/**
|
|
7
|
-
* 内置工具 `SkillWrite`(M5 T15,R4):agent 新建或更新它自己的技能(`scope = agent`)。
|
|
8
|
-
* - 输入 `{ name, description, files: [{ path, content }] }`,只收文本文件,整个技能 ≤ {@link SKILL_WRITE_MAX_BYTES};
|
|
9
|
-
* - the kernel generates SKILL.md ({@link buildSkillWrite}): its frontmatter is `name` / `description`
|
|
10
|
-
* plus the invocation keys (`disable-model-invocation`, `context`, ... phase D) of the frontmatter
|
|
11
|
-
* of the `SKILL.md` entry in `files`, whose other keys are dropped; its body is that entry's body
|
|
12
|
-
* (empty without the entry);
|
|
13
|
-
* - 每次调用写一个完整的新版本(上一版有、这次没给的文件不再属于新版本);
|
|
14
|
-
* - 私有 agent 直接生成新版本并立即生效;共享 agent 由触发对话的人先确认这次调用(R3 固定 ask,见 tools/policy.ts),
|
|
15
|
-
* 再生成变更申请等负责人批准,期间只有本对话试用新版本(见 skills/changes.service.ts);
|
|
16
|
-
* - 成功后立即在日志追加技能目录快照(`skills_catalog`,Ruling M17),同一运行的下一步就能 `Skill`。
|
|
17
|
-
*/
|
|
18
|
-
/** 一个技能(含合成的 SKILL.md)的总大小上限(UTF-8 字节)。 */
|
|
19
|
-
export const SKILL_WRITE_MAX_BYTES = 1024 * 1024;
|
|
20
|
-
/** 一次最多的文件数(含 SKILL.md)。 */
|
|
21
|
-
export const SKILL_WRITE_MAX_FILES = 200;
|
|
22
|
-
const SPEC = {
|
|
23
|
-
name: "SkillWrite",
|
|
24
|
-
description: "Create or update one of this agent's own skills: reusable instructions plus optional text files " +
|
|
25
|
-
"(scripts, templates, reference notes) that you or later conversations can load with Skill. " +
|
|
26
|
-
"Each call writes a complete new version. SKILL.md is generated for you: its frontmatter comes from " +
|
|
27
|
-
"`name` and `description`, and its body is the content of the `SKILL.md` entry in `files` if you " +
|
|
28
|
-
"include one (its frontmatter is dropped, except the invocation keys disable-model-invocation, " +
|
|
29
|
-
"user-invocable, context, agent, argument-hint and arguments, which are kept; with " +
|
|
30
|
-
"disable-model-invocation: true only a person can invoke the skill and you cannot load it). " +
|
|
31
|
-
"Every other entry in `files` is stored at " +
|
|
32
|
-
"<name>@<version-id>/<path> in the skills directory once loaded. Files of the previous version that you leave out are not part of the new " +
|
|
33
|
-
"version. Text files only, at most 1 MB per skill in total. Use an existing skill's name to update " +
|
|
34
|
-
"it; an organization skill with the same name is shadowed by this agent's own skill. On a private " +
|
|
35
|
-
"agent the new version takes effect immediately. On a shared agent the change is sent to the " +
|
|
36
|
-
"agent's owner for approval and is usable only in this conversation until approved.",
|
|
37
|
-
inputSchema: {
|
|
38
|
-
type: "object",
|
|
39
|
-
properties: {
|
|
40
|
-
name: {
|
|
41
|
-
type: "string",
|
|
42
|
-
minLength: 1,
|
|
43
|
-
maxLength: SKILL_LIMITS.nameMaxChars,
|
|
44
|
-
description: 'Skill name: lowercase letters, digits and single hyphens, e.g. "weekly-report". Also the folder name in the skills directory.',
|
|
45
|
-
},
|
|
46
|
-
description: {
|
|
47
|
-
type: "string",
|
|
48
|
-
minLength: 1,
|
|
49
|
-
maxLength: SKILL_LIMITS.descriptionMaxChars,
|
|
50
|
-
description: "What the skill does and when to use it, in one or two sentences (shown in the skills list). No XML tags.",
|
|
51
|
-
},
|
|
52
|
-
files: {
|
|
53
|
-
type: "array",
|
|
54
|
-
maxItems: SKILL_WRITE_MAX_FILES,
|
|
55
|
-
description: 'The skill\'s files. Include {"path": "SKILL.md", "content": "<instructions in Markdown>"} for the body; other paths are relative, e.g. "scripts/build.sh".',
|
|
56
|
-
items: {
|
|
57
|
-
type: "object",
|
|
58
|
-
properties: {
|
|
59
|
-
path: { type: "string", minLength: 1, maxLength: SKILL_LIMITS.pathMaxChars },
|
|
60
|
-
content: { type: "string", description: "The full text content of the file." },
|
|
61
|
-
},
|
|
62
|
-
required: ["path", "content"],
|
|
63
|
-
additionalProperties: false,
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
required: ["name", "description", "files"],
|
|
68
|
-
additionalProperties: false,
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
/**
|
|
72
|
-
* Splits off a leading YAML frontmatter (the same rule as `parseSkillMd`: a first line `---`, then a
|
|
73
|
-
* later `---` line): the body after it and the frontmatter text (null when there is none).
|
|
74
|
-
*/
|
|
75
|
-
function splitFrontmatter(text) {
|
|
76
|
-
const src = (text.startsWith("\uFEFF") ? text.slice(1) : text).replace(/\r\n?/g, "\n");
|
|
77
|
-
const lines = src.split("\n");
|
|
78
|
-
if (lines[0]?.trimEnd() !== "---")
|
|
79
|
-
return { body: src, front: null };
|
|
80
|
-
const end = lines.findIndex((l, i) => i > 0 && l.trimEnd() === "---");
|
|
81
|
-
if (end < 0)
|
|
82
|
-
return { body: src, front: null };
|
|
83
|
-
return { body: lines.slice(end + 1).join("\n"), front: lines.slice(1, end).join("\n") };
|
|
84
|
-
}
|
|
85
|
-
/** The invocation keys (phase D) kept from the frontmatter of the `SKILL.md` entry. */
|
|
86
|
-
const INVOCATION_KEYS = [
|
|
87
|
-
"disable-model-invocation",
|
|
88
|
-
"user-invocable",
|
|
89
|
-
"context",
|
|
90
|
-
"agent",
|
|
91
|
-
"argument-hint",
|
|
92
|
-
"arguments",
|
|
93
|
-
];
|
|
94
|
-
/** The invocation keys of a frontmatter text; unparsable YAML or a non-mapping keeps none. */
|
|
95
|
-
function invocationKeysOf(front) {
|
|
96
|
-
if (front === null)
|
|
97
|
-
return {};
|
|
98
|
-
let data;
|
|
99
|
-
try {
|
|
100
|
-
data = parseYaml(front, { maxAliasCount: 50 });
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return {};
|
|
104
|
-
}
|
|
105
|
-
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
106
|
-
return {};
|
|
107
|
-
const out = {};
|
|
108
|
-
for (const key of INVOCATION_KEYS) {
|
|
109
|
-
const v = data[key];
|
|
110
|
-
if (v !== undefined)
|
|
111
|
-
out[key] = v;
|
|
112
|
-
}
|
|
113
|
-
return out;
|
|
114
|
-
}
|
|
115
|
-
const enc = new TextEncoder();
|
|
116
|
-
const sha256 = (b) => createHash("sha256").update(b).digest("hex");
|
|
117
|
-
/**
|
|
118
|
-
* 由工具输入合成技能文件(纯函数):
|
|
119
|
-
* - SKILL.md = `---\n<yaml name/description>---\n\n<正文>\n`,正文为 `files` 里 `SKILL.md` 的内容去掉 frontmatter、
|
|
120
|
-
* 去掉首尾空行;合成结果再用 `parseSkillMd` 校验(名字规则、描述长度、不含 XML 标签);
|
|
121
|
-
* - 其余文件按 `validateSkillPackage` 校验路径(不能越出 `/skills/<name>/`、不能重复、大小写冲突等);
|
|
122
|
-
* - 只收文本:内容含 NUL 视为二进制,拒绝;全部文件(含 SKILL.md)的 UTF-8 字节数之和 ≤ {@link SKILL_WRITE_MAX_BYTES}。
|
|
123
|
-
* 返回合成结果或全部问题(英文,直接给模型)。
|
|
124
|
-
*/
|
|
125
|
-
export function buildSkillWrite(input) {
|
|
126
|
-
const errors = [];
|
|
127
|
-
if (!isSkillName(input.name)) {
|
|
128
|
-
errors.push(`name "${String(input.name).slice(0, 80)}" is invalid: use 1-64 lowercase letters, digits and single hyphens (no leading or trailing hyphen)`);
|
|
129
|
-
}
|
|
130
|
-
if (input.description.includes("\u0000")) {
|
|
131
|
-
errors.push("description: only text is supported (it contains NUL bytes)");
|
|
132
|
-
}
|
|
133
|
-
const entries = input.files.filter((f) => f.path === SKILL_ENTRY_FILE);
|
|
134
|
-
if (entries.length > 1)
|
|
135
|
-
errors.push(`${SKILL_ENTRY_FILE}: duplicate path`);
|
|
136
|
-
const split = splitFrontmatter(entries[0]?.content ?? "");
|
|
137
|
-
const body = split.body.replace(/^\n+|\s+$/g, "");
|
|
138
|
-
const keys = invocationKeysOf(split.front);
|
|
139
|
-
const front = stringifyYaml({ name: input.name, description: input.description.trim(), ...keys }, { lineWidth: 0 });
|
|
140
|
-
const md = `---\n${front}---\n${body === "" ? "" : `\n${body}\n`}`;
|
|
141
|
-
let invocation;
|
|
142
|
-
if (errors.length === 0) {
|
|
143
|
-
const parsed = parseSkillMd(md);
|
|
144
|
-
if ("errors" in parsed)
|
|
145
|
-
errors.push(...parsed.errors);
|
|
146
|
-
else
|
|
147
|
-
invocation = storedInvocation(skillInvocation(parsed.frontmatter));
|
|
148
|
-
}
|
|
149
|
-
const others = input.files.filter((f) => f.path !== SKILL_ENTRY_FILE);
|
|
150
|
-
const files = [];
|
|
151
|
-
const mdData = enc.encode(md);
|
|
152
|
-
files.push({
|
|
153
|
-
path: SKILL_ENTRY_FILE,
|
|
154
|
-
data: mdData,
|
|
155
|
-
size: mdData.byteLength,
|
|
156
|
-
sha256: sha256(mdData),
|
|
157
|
-
});
|
|
158
|
-
for (const f of input.files) {
|
|
159
|
-
if (!f.content.includes("\u0000"))
|
|
160
|
-
continue;
|
|
161
|
-
errors.push(`${f.path.slice(0, 200)}: only text files are supported (the content contains NUL bytes)`);
|
|
162
|
-
}
|
|
163
|
-
for (const f of others) {
|
|
164
|
-
if (f.content.includes("\u0000"))
|
|
165
|
-
continue;
|
|
166
|
-
const data = enc.encode(f.content);
|
|
167
|
-
files.push({ path: f.path, data, size: data.byteLength, sha256: sha256(data) });
|
|
168
|
-
}
|
|
169
|
-
errors.push(...validateSkillPackage(files.map((f) => ({ path: f.path, size: f.size }))));
|
|
170
|
-
const total = files.reduce((n, f) => n + f.size, 0);
|
|
171
|
-
if (total > SKILL_WRITE_MAX_BYTES) {
|
|
172
|
-
errors.push(`the skill is ${total} bytes in total, over the 1 MB (${SKILL_WRITE_MAX_BYTES} bytes) limit; shorten the files or move large data out of the skill`);
|
|
173
|
-
}
|
|
174
|
-
if (errors.length > 0)
|
|
175
|
-
return { errors };
|
|
176
|
-
const description = input.description.trim();
|
|
177
|
-
return {
|
|
178
|
-
name: input.name,
|
|
179
|
-
description,
|
|
180
|
-
files,
|
|
181
|
-
manifest: {
|
|
182
|
-
name: input.name,
|
|
183
|
-
description,
|
|
184
|
-
files: files.map(({ path, size, sha256 }) => ({ path, size, sha256 })),
|
|
185
|
-
...(invocation !== undefined && { invocation }),
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
/** 内置工具 `SkillWrite`(见文件头)。 */
|
|
190
|
-
export function skillWriteTool(deps) {
|
|
191
|
-
return {
|
|
192
|
-
spec: SPEC,
|
|
193
|
-
// 私有 agent 不询问;共享 agent 由 R3 固定 ask 把关(tools/policy.ts 的 R3_SHARED_AGENT_ONLY_TOOL_KEYS)
|
|
194
|
-
defaultPolicy: "allow",
|
|
195
|
-
async run(input, ctx) {
|
|
196
|
-
const built = buildSkillWrite(input);
|
|
197
|
-
if ("errors" in built) {
|
|
198
|
-
throw new ToolError(`The skill was not written. Fix these problems and call SkillWrite again:\n- ${built.errors.slice(0, 20).join("\n- ")}`);
|
|
199
|
-
}
|
|
200
|
-
const r = await deps.changes.write({
|
|
201
|
-
orgId: ctx.orgId,
|
|
202
|
-
agentId: ctx.agentId,
|
|
203
|
-
accountId: ctx.accountId,
|
|
204
|
-
conversationId: ctx.conversationId,
|
|
205
|
-
...(ctx.runId !== undefined && { runId: ctx.runId }),
|
|
206
|
-
}, built);
|
|
207
|
-
const name = built.name;
|
|
208
|
-
// A manual-only version (phase D) is not in the model's catalog, so Skill cannot load it
|
|
209
|
-
const manualOnly = built.manifest.invocation?.model === false;
|
|
210
|
-
const manualText = `It is manual-only (disable-model-invocation: true): it is not in your skills catalog and ` +
|
|
211
|
-
`Skill cannot load it; a person invokes it with /${name}.`;
|
|
212
|
-
if (r.kind === "version") {
|
|
213
|
-
const what = r.created ? "Created" : "Updated";
|
|
214
|
-
return {
|
|
215
|
-
content: [
|
|
216
|
-
{
|
|
217
|
-
type: "text",
|
|
218
|
-
text: `${what} skill "${name}" (version ${r.version}, ${built.files.length} files). ` +
|
|
219
|
-
(manualOnly
|
|
220
|
-
? `It is in effect now for every conversation with this agent. ${manualText}`
|
|
221
|
-
: "It is in effect now for every conversation with this agent; call Skill to use it."),
|
|
222
|
-
},
|
|
223
|
-
],
|
|
224
|
-
display: display(`${r.created ? "新建" : "更新"}技能 ${name}(版本 ${r.version})`, `${what} skill ${name} (version ${r.version})`),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
content: [
|
|
229
|
-
{
|
|
230
|
-
type: "text",
|
|
231
|
-
text: `Saved skill "${name}" (version ${r.version}, ${built.files.length} files) as a change ` +
|
|
232
|
-
"awaiting the owner's approval, because this agent is shared. Until the owner approves it, " +
|
|
233
|
-
(manualOnly
|
|
234
|
-
? `the new version applies only in this conversation; other conversations keep the current version. ${manualText} `
|
|
235
|
-
: "the new version is usable only in this conversation (call Skill to use it); other " +
|
|
236
|
-
"conversations keep the current version. ") +
|
|
237
|
-
"Tell the user the change needs the owner's approval.",
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
display: display(`技能 ${name} 的改动(版本 ${r.version})已提交,等待负责人批准`, `Change to skill ${name} (version ${r.version}) awaits the owner's approval`),
|
|
241
|
-
};
|
|
242
|
-
},
|
|
243
|
-
};
|
|
244
|
-
}
|