@yijiancaotang/cli 1.1.0 → 1.2.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/README.md +35 -23
- package/dist/cli.js +47 -8
- package/dist/command-help.js +9 -4
- package/dist/commands.js +5 -0
- package/dist/doctor.js +2 -1
- package/dist/generated/agent-operations.js +12 -0
- package/dist/handoff-read.js +29 -0
- package/dist/handoff-wire.js +42 -0
- package/dist/input-validation.js +14 -0
- package/dist/installation.js +39 -16
- package/dist/local-commands.js +38 -14
- package/dist/local-records.js +5 -4
- package/dist/local-types.js +1 -0
- package/dist/onboarding.js +4 -20
- package/dist/output.js +4 -0
- package/dist/platforms.js +18 -0
- package/dist/project-binding-cli.js +110 -0
- package/dist/project-binding.js +370 -0
- package/dist/reviewed-managed-bundle-baseline.js +780 -0
- package/dist/setup-guidance.js +40 -0
- package/dist/skills-export.js +206 -0
- package/dist/work-context-human.js +67 -0
- package/dist/work-context-wire.js +391 -0
- package/package.json +2 -2
- package/skills/caotang-context/SKILL.md +16 -8
- package/skills/caotang-first-connect/SKILL.md +41 -39
- package/skills/manifest.json +2 -2
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { productionOrigin } from "./config.js";
|
|
2
|
+
import { getAgentOperations } from "./generated/agent-operations.js";
|
|
3
|
+
// Match the existing closed config origins, not an arbitrary apiFactory URL.
|
|
4
|
+
// This reads public metadata only; it never loads a credential or probes an API.
|
|
5
|
+
function currentOrigin(input) {
|
|
6
|
+
const expected = input.environment === "production" ? productionOrigin : input.environment === "test" ? "https://test.gtcai.cn" : undefined;
|
|
7
|
+
if (!expected || typeof input.baseUrl !== "string" || input.credentialOrigin !== expected || input.baseUrl.trim() !== input.baseUrl)
|
|
8
|
+
return undefined;
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(input.baseUrl);
|
|
11
|
+
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.pathname !== "/" || url.origin !== expected)
|
|
12
|
+
return undefined;
|
|
13
|
+
return url.origin;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function setupGuidance(input) {
|
|
20
|
+
const operations = getAgentOperations(), origin = currentOrigin(input);
|
|
21
|
+
const verified = input.connectionVerified === true && origin !== undefined;
|
|
22
|
+
const name = typeof input.agentName === "string" && input.agentName.trim() === input.agentName && [...input.agentName].length >= 1 && [...input.agentName].length <= 80
|
|
23
|
+
&& !/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(input.agentName) ? input.agentName : undefined;
|
|
24
|
+
const scope = verified && (input.scope === "read_only" || input.scope === "read_write") ? input.scope : "unknown";
|
|
25
|
+
return {
|
|
26
|
+
schema_version: 1,
|
|
27
|
+
user_experience: { state: "unknown", basis: "not_recorded" },
|
|
28
|
+
agent_preparation: {
|
|
29
|
+
status: "required",
|
|
30
|
+
body_obtained: "unknown",
|
|
31
|
+
evidence: "not_observable_by_cli",
|
|
32
|
+
operations: { version: operations.version, sha256: operations.sha256, source: "package_generated", command: "caotang guide", manual_path: operations.manual_path },
|
|
33
|
+
},
|
|
34
|
+
presentation_reason: input.ready === true && verified ? "requested" : "not_ready",
|
|
35
|
+
identity: { verified, ...(verified && name !== undefined ? { agent_name: name } : {}), ...(origin ? { environment: input.environment, origin } : {}) },
|
|
36
|
+
permissions: { scope, account_capabilities: "unknown", effective_capabilities: "unknown" },
|
|
37
|
+
entry: { transport: "cli", skill_support: "unknown" },
|
|
38
|
+
...(origin ? { manual_url: new URL(operations.manual_path, origin).href } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { constants, closeSync, fstatSync, fsyncSync, linkSync, lstatSync, openSync, readSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { asRecord, CliError } from "./errors.js";
|
|
5
|
+
import { getAgentOperations } from "./generated/agent-operations.js";
|
|
6
|
+
import { loadSkillAssets } from "./skill-assets.js";
|
|
7
|
+
const unsafe = () => new CliError("skills_export_path_unsafe", "导出位置未通过安全检查。", "请明确指定一个新ZIP文件,父目录须已存在、属于本人且不是符号链接;不会自动创建目录、改权限或覆盖文件。", 2);
|
|
8
|
+
const exists = () => new CliError("skills_export_exists", "导出目标已存在,未覆盖。", "保留原文件;请本人明确选择一个新的ZIP文件路径。", 2);
|
|
9
|
+
const same = (a, b) => a.dev === b.dev && a.ino === b.ino;
|
|
10
|
+
const missing = (error) => asRecord(error)?.code === "ENOENT";
|
|
11
|
+
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
12
|
+
const crcTable = Uint32Array.from({ length: 256 }, (_, index) => {
|
|
13
|
+
let value = index;
|
|
14
|
+
for (let bit = 0; bit < 8; bit++)
|
|
15
|
+
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
16
|
+
return value >>> 0;
|
|
17
|
+
});
|
|
18
|
+
function crc32(bytes) {
|
|
19
|
+
let crc = 0xffffffff;
|
|
20
|
+
for (const byte of bytes)
|
|
21
|
+
crc = crcTable[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
22
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
function zip(entries) {
|
|
25
|
+
if (entries.length > 32 || entries.reduce((total, entry) => total + entry.content.length, 0) > 1048576)
|
|
26
|
+
throw unsafe();
|
|
27
|
+
const local = [], central = [];
|
|
28
|
+
let offset = 0;
|
|
29
|
+
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name, "en"))) {
|
|
30
|
+
if (!/^(?:[a-zA-Z0-9_-]+\/)*[a-zA-Z0-9_.-]+$/.test(entry.name) || entry.name.split("/").some(part => part === "." || part === ".."))
|
|
31
|
+
throw unsafe();
|
|
32
|
+
const name = Buffer.from(entry.name, "utf8"), crc = crc32(entry.content);
|
|
33
|
+
const header = Buffer.alloc(30);
|
|
34
|
+
header.writeUInt32LE(0x04034b50);
|
|
35
|
+
header.writeUInt16LE(20, 4);
|
|
36
|
+
header.writeUInt16LE(0x0800, 6);
|
|
37
|
+
header.writeUInt16LE(0x0021, 12); // Fixed DOS 1980-01-01; no local identity/time metadata.
|
|
38
|
+
header.writeUInt32LE(crc, 14);
|
|
39
|
+
header.writeUInt32LE(entry.content.length, 18);
|
|
40
|
+
header.writeUInt32LE(entry.content.length, 22);
|
|
41
|
+
header.writeUInt16LE(name.length, 26);
|
|
42
|
+
const directory = Buffer.alloc(46);
|
|
43
|
+
directory.writeUInt32LE(0x02014b50);
|
|
44
|
+
directory.writeUInt16LE(0x0314, 4);
|
|
45
|
+
directory.writeUInt16LE(20, 6);
|
|
46
|
+
directory.writeUInt16LE(0x0800, 8);
|
|
47
|
+
directory.writeUInt16LE(0x0021, 14);
|
|
48
|
+
directory.writeUInt32LE(crc, 16);
|
|
49
|
+
directory.writeUInt32LE(entry.content.length, 20);
|
|
50
|
+
directory.writeUInt32LE(entry.content.length, 24);
|
|
51
|
+
directory.writeUInt16LE(name.length, 28);
|
|
52
|
+
directory.writeUInt32LE((0o100600 << 16) >>> 0, 38);
|
|
53
|
+
directory.writeUInt32LE(offset, 42);
|
|
54
|
+
local.push(header, name, entry.content);
|
|
55
|
+
central.push(directory, name);
|
|
56
|
+
offset += header.length + name.length + entry.content.length;
|
|
57
|
+
}
|
|
58
|
+
const directory = Buffer.concat(central), end = Buffer.alloc(22);
|
|
59
|
+
end.writeUInt32LE(0x06054b50);
|
|
60
|
+
end.writeUInt16LE(entries.length, 8);
|
|
61
|
+
end.writeUInt16LE(entries.length, 10);
|
|
62
|
+
end.writeUInt32LE(directory.length, 12);
|
|
63
|
+
end.writeUInt32LE(offset, 16);
|
|
64
|
+
return Buffer.concat([...local, directory, end]);
|
|
65
|
+
}
|
|
66
|
+
function outputPath(value) {
|
|
67
|
+
if (typeof value !== "string" || !value.trim() || value.length > 4096 || /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(value))
|
|
68
|
+
throw unsafe();
|
|
69
|
+
const target = resolve(value);
|
|
70
|
+
if (!isAbsolute(target) || target === parse(target).root || !basename(target).toLowerCase().endsWith(".zip"))
|
|
71
|
+
throw unsafe();
|
|
72
|
+
return target;
|
|
73
|
+
}
|
|
74
|
+
// Public ZIP output is not restricted to HOME. All existing ancestors must be
|
|
75
|
+
// real directories; only trusted root/sticky system ancestors may precede the
|
|
76
|
+
// user's private final parent. No directory contents are scanned or modified.
|
|
77
|
+
function checkedParent(path) {
|
|
78
|
+
if (typeof process.getuid !== "function")
|
|
79
|
+
throw unsafe();
|
|
80
|
+
const uid = process.getuid(), parent = dirname(path), root = parse(parent).root;
|
|
81
|
+
let current = root, result;
|
|
82
|
+
for (const part of [null, ...relative(root, parent).split(sep).filter(Boolean)]) {
|
|
83
|
+
if (part !== null)
|
|
84
|
+
current = join(current, part);
|
|
85
|
+
const info = lstatSync(current), final = current === parent;
|
|
86
|
+
const stickyRootAncestor = !final && info.uid === 0 && (info.mode & 0o1000) !== 0;
|
|
87
|
+
if (!info.isDirectory() || (final ? info.uid !== uid : info.uid !== uid && info.uid !== 0)
|
|
88
|
+
|| (info.mode & 0o022) !== 0 && !stickyRootAncestor)
|
|
89
|
+
throw unsafe();
|
|
90
|
+
result = info;
|
|
91
|
+
}
|
|
92
|
+
if (!result || realpathSync(parent) !== parent)
|
|
93
|
+
throw unsafe();
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function readDescriptor(fd, length) {
|
|
97
|
+
const bytes = Buffer.alloc(length);
|
|
98
|
+
let offset = 0;
|
|
99
|
+
while (offset < length) {
|
|
100
|
+
const count = readSync(fd, bytes, offset, length - offset, offset);
|
|
101
|
+
if (!count)
|
|
102
|
+
break;
|
|
103
|
+
offset += count;
|
|
104
|
+
}
|
|
105
|
+
return bytes.subarray(0, offset);
|
|
106
|
+
}
|
|
107
|
+
function publish(path, bytes) {
|
|
108
|
+
const parent = dirname(path), identity = checkedParent(path), original = process.cwd();
|
|
109
|
+
let fd, stagedIdentity, temporary, published = false;
|
|
110
|
+
try {
|
|
111
|
+
// Keep all operations synchronous while pinned to the verified parent inode.
|
|
112
|
+
process.chdir(parent);
|
|
113
|
+
const current = lstatSync(".");
|
|
114
|
+
if (!same(current, identity) || current.uid !== identity.uid || current.mode !== identity.mode)
|
|
115
|
+
throw unsafe();
|
|
116
|
+
const name = basename(path);
|
|
117
|
+
try {
|
|
118
|
+
lstatSync(name);
|
|
119
|
+
throw exists();
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (!missing(error))
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
temporary = `.caotang-skills-export-${randomUUID()}.tmp`;
|
|
126
|
+
fd = openSync(temporary, constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
127
|
+
stagedIdentity = fstatSync(fd);
|
|
128
|
+
const valid = (info, links) => info.isFile() && info.uid === identity.uid && info.nlink === links && (info.mode & 0o7777) === 0o600 && same(info, stagedIdentity);
|
|
129
|
+
if (!valid(stagedIdentity, 1) || stagedIdentity.size !== 0)
|
|
130
|
+
throw unsafe();
|
|
131
|
+
writeFileSync(fd, bytes);
|
|
132
|
+
fsyncSync(fd);
|
|
133
|
+
const written = fstatSync(fd);
|
|
134
|
+
if (!valid(written, 1) || written.size !== bytes.length || !same(written, lstatSync(temporary)) || !readDescriptor(fd, bytes.length).equals(bytes))
|
|
135
|
+
throw unsafe();
|
|
136
|
+
try {
|
|
137
|
+
linkSync(temporary, name);
|
|
138
|
+
published = true;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (asRecord(error)?.code === "EEXIST")
|
|
142
|
+
throw exists();
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
if (!valid(lstatSync(name), 2) || !same(lstatSync(temporary), stagedIdentity))
|
|
146
|
+
throw unsafe();
|
|
147
|
+
unlinkSync(temporary);
|
|
148
|
+
temporary = undefined;
|
|
149
|
+
const final = lstatSync(name);
|
|
150
|
+
if (!valid(final, 1) || final.size !== bytes.length || !readDescriptor(fd, bytes.length).equals(bytes))
|
|
151
|
+
throw unsafe();
|
|
152
|
+
const after = lstatSync(".");
|
|
153
|
+
if (!same(after, identity) || after.uid !== identity.uid || after.mode !== identity.mode)
|
|
154
|
+
throw unsafe();
|
|
155
|
+
// cwd remains the old inode after a concurrent rename. Also verify the
|
|
156
|
+
// absolute location promised to the caller, including its safe ancestry.
|
|
157
|
+
const promisedParent = checkedParent(path), promisedTarget = lstatSync(path);
|
|
158
|
+
if (!same(promisedParent, identity) || promisedParent.uid !== identity.uid || promisedParent.mode !== identity.mode
|
|
159
|
+
|| !valid(promisedTarget, 1) || promisedTarget.size !== bytes.length || !same(promisedTarget, final)
|
|
160
|
+
|| !readDescriptor(fd, bytes.length).equals(bytes))
|
|
161
|
+
throw unsafe();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (published)
|
|
165
|
+
throw new CliError("skills_export_unconfirmed", "已创建导出目标,但最终核对未完成。", "保留现有ZIP并由本人核对文件;不要自动删除、覆盖或重新导出到同一路径。", 6);
|
|
166
|
+
if (error instanceof CliError)
|
|
167
|
+
throw error;
|
|
168
|
+
throw unsafe();
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
if (fd !== undefined)
|
|
172
|
+
closeSync(fd);
|
|
173
|
+
if (temporary && stagedIdentity) {
|
|
174
|
+
try {
|
|
175
|
+
if (same(lstatSync(temporary), stagedIdentity))
|
|
176
|
+
unlinkSync(temporary);
|
|
177
|
+
}
|
|
178
|
+
catch { /* Preserve an unverified pathname; never remove another inode. */ }
|
|
179
|
+
}
|
|
180
|
+
process.chdir(original);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export async function exportSkillsBundle(options) {
|
|
184
|
+
const path = outputPath(options.outputPath);
|
|
185
|
+
const assets = await loadSkillAssets(options.packageRoot);
|
|
186
|
+
if (assets.some(asset => asset.content.includes("本技能使用的 AI 名字是")))
|
|
187
|
+
throw new CliError("skills_export_source_invalid", "本包技能含绑定身份,未导出。", "只使用官方未绑定个人身份的技能包,不从已安装个人目录导出。", 7);
|
|
188
|
+
const operations = getAgentOperations();
|
|
189
|
+
const manifest = { formatVersion: 1, skills: assets.map(({ name, description }) => ({ name, description })) };
|
|
190
|
+
const entries = [
|
|
191
|
+
{ name: "skills/manifest.json", content: Buffer.from(JSON.stringify(manifest, null, 2) + "\n") },
|
|
192
|
+
...assets.map(asset => ({ name: `skills/${asset.name}/SKILL.md`, content: Buffer.from(asset.content) })),
|
|
193
|
+
{ name: "operations.md", content: Buffer.from(operations.content) },
|
|
194
|
+
{ name: "README.md", content: Buffer.from(`# 一间草堂官方公共技能包\n\n七项技能来自本CLI包内官方资源,未绑定个人身份;不含账号、令牌、同意或个人配置。\n公共短合同版本:${operations.version};正文SHA256:${operations.sha256}。\n人用手册:当前服务的 ${operations.manual_path}。\n\n导出不等于安装、授权、当前会话加载或行为通过。请按目标宿主实际支持的导入方式使用,核对共享范围;不会自动上传共享库、启动Agent或改个人配置。\n能执行CLI但无Skill的入口仍可按公共说明使用完整业务能力;纯网页无外部工具的AI尚未接通。\n`) },
|
|
195
|
+
];
|
|
196
|
+
const bytes = zip(entries);
|
|
197
|
+
try {
|
|
198
|
+
publish(path, bytes);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
if (error instanceof CliError)
|
|
202
|
+
throw error;
|
|
203
|
+
throw unsafe();
|
|
204
|
+
}
|
|
205
|
+
return { status: 200, body: { operation: "skills_export", output_path: path, format: "zip", compression: "store", size_bytes: bytes.length, sha256: digest(bytes), entry_count: entries.length, skill_count: assets.length, uploaded: false } };
|
|
206
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { verifiedWorkContext } from "./work-context-wire.js";
|
|
2
|
+
const row = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : undefined;
|
|
3
|
+
const clean = (v, limit = 60) => {
|
|
4
|
+
if (typeof v !== "string")
|
|
5
|
+
return "";
|
|
6
|
+
const chars = [...v.replace(/[\p{Cc}\u2028\u2029\u202a-\u202e\u2066-\u2069]/gu, " ").replace(/\s+/gu, " ").trim()];
|
|
7
|
+
return chars.length > limit ? chars.slice(0, limit).join("") + "…" : chars.join("");
|
|
8
|
+
};
|
|
9
|
+
function unavailable(section) {
|
|
10
|
+
if (section?.status === "disabled")
|
|
11
|
+
return "未读取(没有权限)";
|
|
12
|
+
if (section?.status === "read_failed")
|
|
13
|
+
return "本次读取未完成";
|
|
14
|
+
if (section?.status === "deferred")
|
|
15
|
+
return section.reason === "budget_limited" ? "部分内容延后展示" : "本次尚未提供";
|
|
16
|
+
if (section?.status === "not_recorded")
|
|
17
|
+
return "尚未记录";
|
|
18
|
+
if (section?.status === "not_applicable")
|
|
19
|
+
return "本次不适用";
|
|
20
|
+
return "未核实";
|
|
21
|
+
}
|
|
22
|
+
function total(section) {
|
|
23
|
+
return (section?.status === "available" || section?.status === "empty") && typeof section.total === "number" ? String(section.total) : unavailable(section);
|
|
24
|
+
}
|
|
25
|
+
// Human display only. Agents retain the default complete JSON for rules and scope.
|
|
26
|
+
export function workContextHuman(value) {
|
|
27
|
+
try {
|
|
28
|
+
const source = row(value), sourceContext = row(source?.context), selection = row(row(sourceContext?.current_project)?.data);
|
|
29
|
+
const selected = typeof selection?.project_id === "string" ? selection.project_id : undefined;
|
|
30
|
+
const verified = verifiedWorkContext(value, selected);
|
|
31
|
+
if (!verified)
|
|
32
|
+
return undefined;
|
|
33
|
+
const context = verified.context, overview = row(context.project_overview), projects = row(context.projects), current = row(context.current_project), counts = row(overview?.data);
|
|
34
|
+
const at = new Intl.DateTimeFormat("zh-CN", { timeZone: "Asia/Shanghai", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(new Date(verified.as_of));
|
|
35
|
+
const header = counts ? `可读项目 ${counts.total},进行中 ${counts.active},核心 ${counts.core}${counts.draft === null ? "(草稿未计入)" : ""}` : `项目:${unavailable(overview)}`;
|
|
36
|
+
const lines = [`草堂摘要 · ${at} · ${header}`];
|
|
37
|
+
const list = Array.isArray(projects?.data) ? projects.data : [];
|
|
38
|
+
if (current?.status === "available" && list.length) {
|
|
39
|
+
const project = list[0], next = row(project.next_step), step = row(next?.data);
|
|
40
|
+
lines.push(`本次:${clean(project.title)};上次留下的下一步:${step ? clean(step.summary, 100) : "未留下明确记录"}`);
|
|
41
|
+
}
|
|
42
|
+
else if (list.length) {
|
|
43
|
+
lines.push(`核心/近期:${list.slice(0, 2).map(p => clean(p.title)).join("、")}${list.length > 2 ? `;另有 ${list.length - 2} 个候选` : ""}。`);
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
lines.push(`项目详情:${projects?.status === "empty" ? "当前可读范围内暂无项目" : unavailable(projects)}`);
|
|
47
|
+
const schedule = row(context.schedule_items), data = row(schedule?.data);
|
|
48
|
+
if (schedule?.status === "available" && data) {
|
|
49
|
+
const overdue = row(data.overdue), today = row(data.today), undated = row(data.undated);
|
|
50
|
+
const urgent = [...overdue.items, ...today.items].slice(0, 3).map(p => clean(p.title, 40));
|
|
51
|
+
lines.push(`待办:今日 ${today.total},逾期 ${overdue.total},未定日期 ${undated.total}${urgent.length ? `;优先:${urgent.join("、")}` : ""}`);
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
lines.push(`待办:${unavailable(schedule)}`);
|
|
55
|
+
const rules = row(context.rules), pending = row(context.pending), notices = row(context.official_notices), noticeData = row(notices?.data);
|
|
56
|
+
lines.push(`待确认:${total(pending)}${typeof pending?.total === "number" ? " 项(网页核对)" : ""};规矩:${total(rules)}${typeof rules?.total === "number" ? " 条(原文见 JSON)" : ""};通知:${noticeData ? noticeData.unread_count : unavailable(notices)}`);
|
|
57
|
+
const connections = row(context.recent_connections), reminders = row(context.conditional_reminders);
|
|
58
|
+
const connectionText = connections?.status === "available" || connections?.status === "empty" ? `${connections.total} 条近期记录` : unavailable(connections);
|
|
59
|
+
const reminderData = row(reminders?.data);
|
|
60
|
+
const reminderText = reminderData ? `项目 ${total(row(reminderData.projects))},知识 ${total(row(reminderData.knowledge))}` : unavailable(reminders);
|
|
61
|
+
lines.push(`协作:${connectionText};整理提示:${reminderText}。完整内容可去掉 --human 查看。`);
|
|
62
|
+
return lines.join("\n");
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|