@yijiancaotang/cli 0.0.0-stage → 1.1.0
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 +311 -2
- package/bin/caotang.cjs +15 -0
- package/dist/agent-identity.js +14 -0
- package/dist/auth.js +340 -0
- package/dist/calendar-contract.js +358 -0
- package/dist/calendar-help.js +12 -0
- package/dist/calendar.js +64 -0
- package/dist/capability-result.js +85 -0
- package/dist/cli.js +257 -0
- package/dist/command-help.js +105 -0
- package/dist/commands.js +356 -0
- package/dist/config.js +14 -0
- package/dist/connection-copy.js +40 -0
- package/dist/context.js +30 -0
- package/dist/contracts.js +1 -0
- package/dist/create-bundle.js +103 -0
- package/dist/create-file-failure.js +18 -0
- package/dist/credential-store.js +594 -0
- package/dist/doctor.js +221 -0
- package/dist/errors.js +214 -0
- package/dist/generated/knowledge-growth.js +37 -0
- package/dist/habit-day.js +58 -0
- package/dist/habit-read.js +32 -0
- package/dist/habit-wire.js +87 -0
- package/dist/http.js +211 -0
- package/dist/input-validation.js +76 -0
- package/dist/installation.js +761 -0
- package/dist/knowledge-growth.js +85 -0
- package/dist/local-commands.js +229 -0
- package/dist/local-lock.js +245 -0
- package/dist/local-records.js +149 -0
- package/dist/local-types.js +3 -0
- package/dist/managed-skill-baseline.js +35 -0
- package/dist/missing-sections.js +72 -0
- package/dist/onboarding.js +48 -0
- package/dist/output.js +54 -0
- package/dist/pending-requests.js +45 -0
- package/dist/platforms.js +239 -0
- package/dist/query-daily.js +70 -0
- package/dist/query-search.js +43 -0
- package/dist/query-validation.js +45 -0
- package/dist/reviewed-managed-bundle-baseline.js +356 -0
- package/dist/schedule-contract.js +289 -0
- package/dist/schedule-help.js +35 -0
- package/dist/schedule-start-wire.js +80 -0
- package/dist/schedule.js +70 -0
- package/dist/skill-assets.js +136 -0
- package/dist/upload.js +130 -0
- package/dist/web-citations.js +68 -0
- package/dist/write-permissions.js +41 -0
- package/dist/write-routing.js +26 -0
- package/package.json +27 -4
- package/skills/caotang-context/SKILL.md +52 -0
- package/skills/caotang-first-connect/SKILL.md +74 -0
- package/skills/caotang-project-progress/SKILL.md +91 -0
- package/skills/caotang-save-knowledge/SKILL.md +42 -0
- package/skills/caotang-schedule/SKILL.md +54 -0
- package/skills/caotang-search/SKILL.md +79 -0
- package/skills/caotang-workday/SKILL.md +81 -0
- package/skills/manifest.json +33 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
export const scheduleRoot = "/api/v1/agent";
|
|
2
|
+
export const scheduleKeyPattern = /^[A-Za-z0-9._:-]{16,128}(?![\s\S])/;
|
|
3
|
+
export const scheduleUUID = (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?![\s\S])/i.test(value);
|
|
4
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
const scalarText = (value) => ![...value].some(char => { const n = char.codePointAt(0); return n >= 0xd800 && n <= 0xdfff; });
|
|
6
|
+
const trim = (value) => value.replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "");
|
|
7
|
+
export const validScheduleRevision = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
8
|
+
export function validScheduleDate(value) {
|
|
9
|
+
if (typeof value !== "string" || !/^(?!0000)\d{4}-\d{2}-\d{2}(?![\s\S])/.test(value))
|
|
10
|
+
return false;
|
|
11
|
+
const date = new Date(value + "T00:00:00Z");
|
|
12
|
+
return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
|
|
13
|
+
}
|
|
14
|
+
export function validScheduleTime(value) {
|
|
15
|
+
if (typeof value !== "string" || !/^(?!0000)\d{4}-\d{2}-\d{2}T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d{1,9})?(Z|[+-]([01]\d|2[0-3]):[0-5]\d)(?![\s\S])/.test(value))
|
|
16
|
+
return false;
|
|
17
|
+
return validScheduleDate(value.slice(0, 10)) && Number.isFinite(Date.parse(value));
|
|
18
|
+
}
|
|
19
|
+
export class ScheduleProblem extends Error {
|
|
20
|
+
code;
|
|
21
|
+
humanMessage;
|
|
22
|
+
nextAction;
|
|
23
|
+
exitCode;
|
|
24
|
+
details;
|
|
25
|
+
constructor(code, humanMessage, nextAction, exitCode = 2, details = {}) {
|
|
26
|
+
super(JSON.stringify({ code, message: humanMessage, next_action: nextAction, ...details }));
|
|
27
|
+
this.code = code;
|
|
28
|
+
this.humanMessage = humanMessage;
|
|
29
|
+
this.nextAction = nextAction;
|
|
30
|
+
this.exitCode = exitCode;
|
|
31
|
+
this.details = details;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function scheduleInvalid() {
|
|
35
|
+
throw new ScheduleProblem("invalid_arguments", "日程命令或参数无效。", "请查看本命令帮助,只提交本人确认的字段;日期用 YYYY-MM-DD,精确时间用带时区偏移的 RFC3339。");
|
|
36
|
+
}
|
|
37
|
+
export function scheduleKey(value) {
|
|
38
|
+
if (typeof value !== "string" || !scheduleKeyPattern.test(value))
|
|
39
|
+
return scheduleInvalid();
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export function scheduleID(value) {
|
|
43
|
+
return scheduleUUID(value) ? value : scheduleInvalid();
|
|
44
|
+
}
|
|
45
|
+
export function scheduleQuery(input) {
|
|
46
|
+
const allowed = ["view", "status", "group_by", "project_id", "q", "start_date", "end_date", "limit", "offset"];
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [name, value] of Object.entries(input)) {
|
|
49
|
+
if (!allowed.includes(name))
|
|
50
|
+
scheduleInvalid();
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
if (name === "limit" || name === "offset") {
|
|
54
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < (name === "limit" ? 1 : 0) || (name === "limit" && value > 100))
|
|
55
|
+
scheduleInvalid();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
if (typeof value !== "string" || value === "" || !scalarText(value) || value.includes("\0"))
|
|
59
|
+
scheduleInvalid();
|
|
60
|
+
if (name === "view" && !["pending", "workbench", "recent_completed", "history"].includes(value))
|
|
61
|
+
scheduleInvalid();
|
|
62
|
+
if (name === "status" && (!["all", "completed", "cancelled"].includes(value) || input.view !== "history"))
|
|
63
|
+
scheduleInvalid();
|
|
64
|
+
if (name === "group_by" && !["date", "project"].includes(value))
|
|
65
|
+
scheduleInvalid();
|
|
66
|
+
if (name === "project_id" && value !== "none" && !scheduleUUID(value))
|
|
67
|
+
scheduleInvalid();
|
|
68
|
+
if ((name === "start_date" || name === "end_date") && !validScheduleDate(value))
|
|
69
|
+
scheduleInvalid();
|
|
70
|
+
if (name === "q" && [...trim(value)].length > 200)
|
|
71
|
+
scheduleInvalid();
|
|
72
|
+
}
|
|
73
|
+
out[name] = value;
|
|
74
|
+
}
|
|
75
|
+
if (typeof input.start_date === "string" && typeof input.end_date === "string" && input.start_date > input.end_date)
|
|
76
|
+
scheduleInvalid();
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
export function scheduleBody(operation, value) {
|
|
80
|
+
if (!record(value))
|
|
81
|
+
return scheduleInvalid();
|
|
82
|
+
const fields = operation === "create" || operation === "update" ? ["title", "due_date", "due_at", "project_id", "confirmed"] : ["confirmed"];
|
|
83
|
+
if (Object.keys(value).some(name => !fields.includes(name)) || value.confirmed !== true)
|
|
84
|
+
return scheduleInvalid();
|
|
85
|
+
if (operation === "create" && !Object.hasOwn(value, "title"))
|
|
86
|
+
scheduleInvalid();
|
|
87
|
+
if (operation === "update" && !["title", "due_date", "due_at", "project_id"].some(name => Object.hasOwn(value, name)))
|
|
88
|
+
scheduleInvalid();
|
|
89
|
+
const out = { ...value };
|
|
90
|
+
if (Object.hasOwn(value, "title")) {
|
|
91
|
+
if (typeof value.title !== "string" || !scalarText(value.title))
|
|
92
|
+
scheduleInvalid();
|
|
93
|
+
const title = trim(value.title);
|
|
94
|
+
if (!title || [...title].length > 200 || /[\p{Cc}\u2028\u2029]/u.test(title))
|
|
95
|
+
scheduleInvalid();
|
|
96
|
+
out.title = title;
|
|
97
|
+
}
|
|
98
|
+
for (const name of ["due_date", "due_at", "project_id"]) {
|
|
99
|
+
if (!Object.hasOwn(value, name))
|
|
100
|
+
continue;
|
|
101
|
+
const item = value[name];
|
|
102
|
+
if (item === null || item === "")
|
|
103
|
+
continue;
|
|
104
|
+
if (name === "due_date" ? !validScheduleDate(item) : name === "due_at" ? !validScheduleTime(item) : !scheduleUUID(item))
|
|
105
|
+
scheduleInvalid();
|
|
106
|
+
}
|
|
107
|
+
if (value.due_date && value.due_at)
|
|
108
|
+
scheduleInvalid();
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
export function scheduleUnknown(key, status) {
|
|
112
|
+
return new ScheduleProblem("write_outcome_unknown", "日程写入结果未确认,不能视为成功。", "保留原幂等键,用 schedule status 或 get_schedule_write_status 核对同一连接的回执;不要换键、自动重试或执行依赖写入。", 6, { write_status: "unknown", idempotency_key: key, ...(status === undefined ? {} : { http_status: status }) });
|
|
113
|
+
}
|
|
114
|
+
export function scheduleFailure(status, body, capabilities, key) {
|
|
115
|
+
if (key && (status === 202 || status >= 500))
|
|
116
|
+
return scheduleUnknown(key, status);
|
|
117
|
+
const raw = record(body) ? body.code ?? body.error : undefined;
|
|
118
|
+
const consistent = record(body) && (body.code === undefined || body.code === raw) && (body.error === undefined || body.error === raw);
|
|
119
|
+
const codes = {
|
|
120
|
+
400: ["invalid_schedule_query", "invalid_schedule_id", "invalid_idempotency_key", "invalid_schedule_revision", "invalid_schedule_input", "schedule_confirmation_required"],
|
|
121
|
+
401: ["not_authenticated"], 403: ["token_scope_insufficient", "capability_disabled", "second_confirmation_required", "legal_consent_required", "account_unavailable"],
|
|
122
|
+
404: ["schedule_not_found"], 409: ["schedule_revision_conflict", "idempotency_conflict", "schedule_closed", "schedule_undo_expired"],
|
|
123
|
+
415: ["invalid_content_type"], 429: ["rate_limited"], 503: ["schedule_unavailable"]
|
|
124
|
+
};
|
|
125
|
+
const code = consistent && typeof raw === "string" && codes[status]?.includes(raw) ? raw : "request_rejected";
|
|
126
|
+
const details = { http_status: status, ...(key ? { idempotency_key: key } : {}) };
|
|
127
|
+
if (code === "capability_disabled" && record(body) && typeof body.required_capability === "string" && capabilities.includes(body.required_capability)
|
|
128
|
+
&& (!Object.hasOwn(body, "missing_sections") || Array.isArray(body.missing_sections) && body.missing_sections.length === 0))
|
|
129
|
+
details.required_capability = body.required_capability;
|
|
130
|
+
const next = status === 403 ? "请本人在网页核对账号能力、设备只读/读写范围或协议确认;不要重新授权或换入口绕过限制。"
|
|
131
|
+
: status === 409 ? "请先读取当前版本或到网页核对;撤销只在完成后的约 5 秒内有效,不自动重试或换键。"
|
|
132
|
+
: "请核对命令输入、服务版本和实际状态,不把失败或权限缺口解释为没有日程,不自动重试写入。";
|
|
133
|
+
return new ScheduleProblem(code, status === 403 ? "日程操作权限不足。" : status === 409 ? "日程状态或版本冲突,本次未确认写入。" : "日程请求未完成。", next, status === 401 ? 3 : status === 403 ? 4 : status >= 500 ? 7 : 2, details);
|
|
134
|
+
}
|
|
135
|
+
const buckets = ["overdue", "today", "tomorrow", "this_week", "later", "undated"];
|
|
136
|
+
const countNames = [...buckets, "completed", "cancelled"];
|
|
137
|
+
const natural = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
138
|
+
const safeText = (value) => typeof value === "string" && scalarText(value) && !/[\p{Cc}\u2028\u2029]/u.test(value);
|
|
139
|
+
const projectText = (value) => typeof value === "string" && scalarText(value) && [...value].length >= 1 && [...value].length <= 80;
|
|
140
|
+
function readInvalid() {
|
|
141
|
+
throw new ScheduleProblem("invalid_response", "日程读取回执无法核实。", "请核对服务版本和访问范围,不把异常响应解释为没有日程。", 7);
|
|
142
|
+
}
|
|
143
|
+
function projectItem(value) {
|
|
144
|
+
if (!record(value) || !scheduleUUID(value.id) || !validScheduleRevision(value.revision)
|
|
145
|
+
|| !safeText(value.title) || !trim(value.title) || [...value.title].length > 200
|
|
146
|
+
|| typeof value.status !== "string" || !["pending", "completed", "cancelled"].includes(value.status)
|
|
147
|
+
|| !validScheduleTime(value.created_at) || !validScheduleTime(value.updated_at))
|
|
148
|
+
return readInvalid();
|
|
149
|
+
const out = { id: value.id, title: value.title, status: value.status, revision: value.revision, created_at: value.created_at, updated_at: value.updated_at };
|
|
150
|
+
for (const name of ["due_date", "due_at", "project_id", "project_title", "project_unavailable_reason", "completed_at", "cancelled_at", "undo_until", "bucket", "closed_actor_kind", "closed_actor_name"]) {
|
|
151
|
+
if (!Object.hasOwn(value, name))
|
|
152
|
+
continue;
|
|
153
|
+
const field = value[name];
|
|
154
|
+
if (name === "project_title" ? !projectText(field) : !safeText(field) || !field)
|
|
155
|
+
readInvalid();
|
|
156
|
+
if (name === "due_date" && !validScheduleDate(field) || name === "project_id" && !scheduleUUID(field)
|
|
157
|
+
|| ["due_at", "completed_at", "cancelled_at", "undo_until"].includes(name) && !validScheduleTime(field)
|
|
158
|
+
|| name === "bucket" && !buckets.includes(field)
|
|
159
|
+
|| name === "project_unavailable_reason" && field !== "project_unavailable")
|
|
160
|
+
readInvalid();
|
|
161
|
+
out[name] = field;
|
|
162
|
+
}
|
|
163
|
+
if (out.due_date && out.due_at)
|
|
164
|
+
readInvalid();
|
|
165
|
+
if (out.project_unavailable_reason) {
|
|
166
|
+
delete out.project_id;
|
|
167
|
+
delete out.project_title;
|
|
168
|
+
}
|
|
169
|
+
else if (out.project_title && !out.project_id)
|
|
170
|
+
readInvalid();
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
export function scheduleReadResult(status, value, detail) {
|
|
174
|
+
if (status !== 200 || !record(value) || value.timezone !== "Asia/Shanghai")
|
|
175
|
+
return readInvalid();
|
|
176
|
+
if (detail)
|
|
177
|
+
return { schedule_item: projectItem(value.schedule_item), timezone: "Asia/Shanghai" };
|
|
178
|
+
if (!Array.isArray(value.items) || !Array.isArray(value.groups) || !record(value.counts)
|
|
179
|
+
|| !natural(value.total) || !natural(value.offset) || !natural(value.limit) || value.limit < 1 || value.limit > 100
|
|
180
|
+
|| typeof value.has_more !== "boolean")
|
|
181
|
+
return readInvalid();
|
|
182
|
+
const total = value.total, offset = value.offset, limit = value.limit;
|
|
183
|
+
const items = value.items.map(projectItem);
|
|
184
|
+
if (items.length !== Math.min(limit, Math.max(0, total - offset)) || value.has_more !== (offset < total && items.length < total - offset))
|
|
185
|
+
readInvalid();
|
|
186
|
+
const counts = {};
|
|
187
|
+
for (const name of countNames) {
|
|
188
|
+
if (!natural(value.counts[name]))
|
|
189
|
+
readInvalid();
|
|
190
|
+
counts[name] = value.counts[name];
|
|
191
|
+
}
|
|
192
|
+
if (Object.values(counts).reduce((sum, count) => sum + count, 0) !== total)
|
|
193
|
+
readInvalid();
|
|
194
|
+
const itemMap = new Map(), shownCounts = {};
|
|
195
|
+
for (const item of items) {
|
|
196
|
+
if (itemMap.has(item.id) || typeof item.bucket !== "string")
|
|
197
|
+
readInvalid();
|
|
198
|
+
itemMap.set(item.id, item);
|
|
199
|
+
const name = item.status === "pending" ? item.bucket : item.status;
|
|
200
|
+
shownCounts[name] = (shownCounts[name] ?? 0) + 1;
|
|
201
|
+
if (shownCounts[name] > counts[name])
|
|
202
|
+
readInvalid();
|
|
203
|
+
}
|
|
204
|
+
const groupKeys = new Set(), groupedIDs = new Set();
|
|
205
|
+
let groupTotal = 0;
|
|
206
|
+
const groups = value.groups.map(raw => {
|
|
207
|
+
if (!record(raw) || !safeText(raw.key) || !raw.key || groupKeys.has(raw.key)
|
|
208
|
+
|| !natural(raw.total) || !natural(raw.shown) || raw.shown < 1 || raw.total < raw.shown || raw.total > total
|
|
209
|
+
|| typeof raw.has_more !== "boolean" || raw.has_more !== (raw.shown < raw.total)
|
|
210
|
+
|| !Array.isArray(raw.items) || raw.items.length !== raw.shown)
|
|
211
|
+
return readInvalid();
|
|
212
|
+
groupKeys.add(raw.key);
|
|
213
|
+
groupTotal += raw.total;
|
|
214
|
+
const groupItems = raw.items.map(projectItem);
|
|
215
|
+
for (const item of groupItems) {
|
|
216
|
+
const id = item.id;
|
|
217
|
+
if (groupedIDs.has(id) || !itemMap.has(id) || JSON.stringify(itemMap.get(id)) !== JSON.stringify(item))
|
|
218
|
+
readInvalid();
|
|
219
|
+
groupedIDs.add(id);
|
|
220
|
+
}
|
|
221
|
+
const group = { key: raw.key, total: raw.total, shown: raw.shown, has_more: raw.has_more, items: groupItems };
|
|
222
|
+
if (Object.hasOwn(raw, "title")) {
|
|
223
|
+
if (!projectText(raw.title))
|
|
224
|
+
readInvalid();
|
|
225
|
+
group.title = raw.title;
|
|
226
|
+
}
|
|
227
|
+
if (Object.hasOwn(raw, "project_id")) {
|
|
228
|
+
if (!scheduleUUID(raw.project_id) || raw.key !== raw.project_id)
|
|
229
|
+
readInvalid();
|
|
230
|
+
group.project_id = raw.project_id;
|
|
231
|
+
}
|
|
232
|
+
return group;
|
|
233
|
+
});
|
|
234
|
+
if (groupedIDs.size !== items.length || groupTotal > total)
|
|
235
|
+
readInvalid();
|
|
236
|
+
return { items, groups, counts, total, limit, offset, has_more: value.has_more, timezone: "Asia/Shanghai" };
|
|
237
|
+
}
|
|
238
|
+
export function scheduleReceipt(status, value, key) {
|
|
239
|
+
if (![200, 201].includes(status) || !record(value) || !scheduleUUID(value.id) || value.write_status !== "confirmed" || typeof value.replayed !== "boolean"
|
|
240
|
+
|| value.code !== undefined || value.error !== undefined
|
|
241
|
+
|| value.read_permission_missing !== undefined && typeof value.read_permission_missing !== "boolean"
|
|
242
|
+
|| value.read_unavailable !== undefined && typeof value.read_unavailable !== "boolean")
|
|
243
|
+
throw scheduleUnknown(key, status);
|
|
244
|
+
const out = { id: value.id, write_status: "confirmed", replayed: value.replayed };
|
|
245
|
+
const missing = value.missing_sections;
|
|
246
|
+
if (value.read_permission_missing === true || value.read_unavailable === true || Array.isArray(missing) && missing.length > 0) {
|
|
247
|
+
const unavailable = value.read_unavailable === true;
|
|
248
|
+
const reason = unavailable ? "read_failed" : "capability_disabled";
|
|
249
|
+
if (!Array.isArray(missing) || missing.length !== 1 || !record(missing[0]) || missing[0].section !== "schedule_items"
|
|
250
|
+
|| missing[0].required_capability !== "read_workbench" || missing[0].reason !== reason
|
|
251
|
+
|| unavailable && value.read_permission_missing === true)
|
|
252
|
+
throw scheduleUnknown(key, status);
|
|
253
|
+
out[unavailable ? "read_unavailable" : "read_permission_missing"] = true;
|
|
254
|
+
out.missing_sections = [{ section: "schedule_items", required_capability: "read_workbench", reason }];
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
if (missing !== undefined && (!Array.isArray(missing) || missing.length !== 0))
|
|
258
|
+
throw scheduleUnknown(key, status);
|
|
259
|
+
try {
|
|
260
|
+
out.schedule_item = projectItem(value.schedule_item);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
throw scheduleUnknown(key, status);
|
|
264
|
+
}
|
|
265
|
+
if (out.schedule_item.id !== value.id)
|
|
266
|
+
throw scheduleUnknown(key, status);
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
export function isScheduleHeaderPath(path, method) {
|
|
271
|
+
return method === "GET" && path === scheduleRoot + "/m1/schedule-write-status"
|
|
272
|
+
|| method === "POST" && (path === scheduleRoot + "/m2/schedule-items" || /^\/api\/v1\/agent\/m2\/schedule-items\/[^/]+\/(complete|cancel|undo)$/.test(path))
|
|
273
|
+
|| method === "PATCH" && /^\/api\/v1\/agent\/m2\/schedule-items\/[^/]+$/.test(path);
|
|
274
|
+
}
|
|
275
|
+
export function scheduleHeaders(path, method, value) {
|
|
276
|
+
if (!record(value) || Object.keys(value).some(name => !["Idempotency-Key", "If-Match"].includes(name)))
|
|
277
|
+
return scheduleInvalid();
|
|
278
|
+
const headers = { "Idempotency-Key": scheduleKey(value["Idempotency-Key"]) };
|
|
279
|
+
const revisionRequired = method !== "GET" && path !== scheduleRoot + "/m2/schedule-items";
|
|
280
|
+
if (revisionRequired) {
|
|
281
|
+
const revision = value["If-Match"];
|
|
282
|
+
if (typeof revision !== "string" || !/^"[1-9]\d*"(?![\s\S])/.test(revision) || !validScheduleRevision(Number(revision.slice(1, -1))))
|
|
283
|
+
return scheduleInvalid();
|
|
284
|
+
headers["If-Match"] = revision;
|
|
285
|
+
}
|
|
286
|
+
else if (value["If-Match"] !== undefined)
|
|
287
|
+
scheduleInvalid();
|
|
288
|
+
return headers;
|
|
289
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const id = "11111111-1111-4111-8111-111111111111";
|
|
2
|
+
const key = "schedule-example-key-0001";
|
|
3
|
+
const notes = "先复述标题、完整日期和时间(含时区);没日期明确说“先不定日期”,本人确认后才传 confirmed:true。服务端不能验证口头确认。所有写必须用16-128位安全ASCII幂等键;未知202不是成功,保留原键和连接,用 schedule status 核对,不自动重试、换键或执行依赖写入。读取归 read_workbench,写入归 write_schedule,只读设备不能写;项目标签受 read_projects/read_drafts 限制。没有删除接口。";
|
|
4
|
+
const fields = {
|
|
5
|
+
title: "create必填,update选填;按Go规则去首尾空白后1-200个Unicode码点,不含控制符。",
|
|
6
|
+
due_date: "选填,真实YYYY-MM-DD;null或空字符串清除时间,省略保持(create默认不定日期)。",
|
|
7
|
+
due_at: "选填,RFC3339含Z或明确时区偏移,例如2026-11-09T12:00:00+08:00;可null清除。due_date与due_at不能同时非空。",
|
|
8
|
+
project_id: "选填,本人项目UUID;null或空字符串解除关联,省略保持。",
|
|
9
|
+
confirmed: "必填且只能为true;表示调用方已取得本人确认,不代表服务器验证了口头确认。",
|
|
10
|
+
};
|
|
11
|
+
export const scheduleCommandHelp = {
|
|
12
|
+
"schedule list": {
|
|
13
|
+
command: "schedule list", usage: "caotang schedule list", description: "读取本人日程/待办,保留服务端排序、分组及分页;取消和完成记录永久保留。",
|
|
14
|
+
options: ["--view", "--status", "--group-by", "--project", "--q", "--start-date", "--end-date", "--limit", "--offset"],
|
|
15
|
+
fields: { view: "pending(默认)、workbench、recent_completed或history。", status: "仅view=history可传:all、completed、cancelled。", "group-by": "date(默认)或project。", project: "none表示不属于项目,否则传项目UUID;省略为全部。", "start-date": "北京时间含首尾日期YYYY-MM-DD;只到日期或精确时间,未定日期不进入日期范围。", "end-date": "YYYY-MM-DD,不早于start-date。", limit: "1-100;默认20,workbench默认5。", offset: "0至9007199254740991。", q: "标题关键字,去首尾空白后最多200个Unicode码点。" },
|
|
16
|
+
example: { command: "caotang schedule list --view history --status cancelled --limit 100" }, notes,
|
|
17
|
+
},
|
|
18
|
+
"schedule show": { command: "schedule show", usage: "caotang schedule show <id>", description: "读取一条日程及schedule_item.revision;写入时使用这一版本,不猜版本号。", options: [], example: { command: `caotang schedule show ${id}` }, notes },
|
|
19
|
+
"schedule status": { command: "schedule status", usage: "caotang schedule status --idempotency-key KEY", description: "只读核对同一连接的原写入回执;原键仅发送为Idempotency-Key header,不在query/body。未知结果退出非0。", options: ["--idempotency-key"], example: { command: `caotang schedule status --idempotency-key ${key}` }, notes },
|
|
20
|
+
};
|
|
21
|
+
for (const operation of ["create", "update", "complete", "cancel", "undo", "add", "done"]) {
|
|
22
|
+
const create = operation === "create" || operation === "add";
|
|
23
|
+
const edit = create || operation === "update";
|
|
24
|
+
const command = `schedule ${operation}`;
|
|
25
|
+
const suffix = `${create ? "" : " <id>"} --input file|- --idempotency-key KEY${create ? "" : " --revision N"}`;
|
|
26
|
+
scheduleCommandHelp[command] = {
|
|
27
|
+
command, usage: `caotang ${command}${suffix}`,
|
|
28
|
+
description: operation === "add" ? "create兼容别名,确认后新增待办。" : operation === "done" ? "complete兼容别名,确认后标完成。" : operation === "undo" ? "仅撤销刚完成的记录,约5秒窗口;不恢复取消、不删除记录。" : operation === "update" ? "按presence修改待办,只提交确认过的字段,至少一个业务字段。" : `确认后${{ create: "新增待办", complete: "标完成", cancel: "取消待办,保留记录" }[operation]}。`,
|
|
29
|
+
options: ["--input", "--idempotency-key", ...(create ? [] : ["--revision"])],
|
|
30
|
+
fields: { ...(edit ? fields : { confirmed: fields.confirmed }), ...(!create ? { revision: "必填--revision N,来自最近读回的正安全整数revision;发送strong If-Match:双引号包围N。冲突后核对,不自动重发。" } : {}) },
|
|
31
|
+
required_fields: [...(create ? ["title"] : []), "confirmed"],
|
|
32
|
+
example: { command: `caotang ${command}${create ? "" : ` ${id}`} --input - --idempotency-key ${key}${create ? "" : " --revision 3"}`, input: create ? { title: "本人确认的待办", confirmed: true } : operation === "update" ? { due_date: "2026-11-09", confirmed: true } : { confirmed: true } }, notes,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export const scheduleCommandUsages = Object.values(scheduleCommandHelp).map(item => item.usage.replace(/^caotang /, ""));
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { validDate as validDay } from "./query-validation.js";
|
|
2
|
+
const row = (value) => {
|
|
3
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4
|
+
throw Error();
|
|
5
|
+
return value;
|
|
6
|
+
};
|
|
7
|
+
const count = (value) => {
|
|
8
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
9
|
+
throw Error();
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
const text = (value, max) => {
|
|
13
|
+
if (typeof value !== "string" || !value || [...value].length > max || /[\p{Cc}\u2028\u2029\uD800-\uDFFF]/u.test(value))
|
|
14
|
+
throw Error();
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
const identity = (value) => {
|
|
18
|
+
const result = text(value, 36);
|
|
19
|
+
if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(result))
|
|
20
|
+
throw Error();
|
|
21
|
+
return result;
|
|
22
|
+
};
|
|
23
|
+
const projectTitle = (value) => {
|
|
24
|
+
if (typeof value !== "string" || !value || [...value].length > 80 || /[\u0000\uD800-\uDFFF]/u.test(value))
|
|
25
|
+
throw Error();
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
const timestamp = (value) => {
|
|
29
|
+
const result = text(value, 40);
|
|
30
|
+
if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\.[0-9]{1,9})?(?:Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9])$/.test(result) || !validDay(result.slice(0, 10)) || !Number.isFinite(Date.parse(result)))
|
|
31
|
+
throw Error();
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
function values(value, cap) {
|
|
35
|
+
if (!Array.isArray(value) || value.length > cap)
|
|
36
|
+
throw Error();
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function item(value, bucket) {
|
|
40
|
+
const r = row(value);
|
|
41
|
+
if (r.status !== "pending" || r.bucket !== bucket || (r.due_date === undefined) === (r.due_at === undefined))
|
|
42
|
+
throw Error();
|
|
43
|
+
let when;
|
|
44
|
+
if (r.due_date !== undefined) {
|
|
45
|
+
if (!validDay(r.due_date))
|
|
46
|
+
throw Error();
|
|
47
|
+
when = { due_date: r.due_date };
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
when = { due_at: timestamp(r.due_at) };
|
|
51
|
+
const project = r.project_id === undefined ? {} : { project_id: identity(r.project_id), project_title: projectTitle(r.project_title) };
|
|
52
|
+
if (r.project_id === undefined && r.project_title !== undefined)
|
|
53
|
+
throw Error();
|
|
54
|
+
if (r.project_unavailable_reason !== undefined && r.project_unavailable_reason !== "project_unavailable")
|
|
55
|
+
throw Error();
|
|
56
|
+
return { id: identity(r.id), title: text(r.title, 80), status: "pending", bucket, ...when, ...project,
|
|
57
|
+
...(r.project_unavailable_reason === undefined ? {} : { project_unavailable_reason: r.project_unavailable_reason }) };
|
|
58
|
+
}
|
|
59
|
+
function segment(value, bucket) {
|
|
60
|
+
const r = row(value), total = count(r.total), items = values(r.items, 8).map(v => item(v, bucket));
|
|
61
|
+
if (items.length > total || r.has_more !== (items.length < total))
|
|
62
|
+
throw Error();
|
|
63
|
+
return { total, items, has_more: r.has_more };
|
|
64
|
+
}
|
|
65
|
+
function titles(value, cap) {
|
|
66
|
+
const r = row(value), total = count(r.total), titles = values(r.titles, cap).map(v => text(v, 80));
|
|
67
|
+
if (titles.length > total || r.has_more !== (titles.length < total))
|
|
68
|
+
throw Error();
|
|
69
|
+
return { total, titles, has_more: r.has_more };
|
|
70
|
+
}
|
|
71
|
+
export function verifiedScheduleStart(value) {
|
|
72
|
+
const r = row(value);
|
|
73
|
+
if (r.timezone !== "Asia/Shanghai" || r.item_limit !== 20)
|
|
74
|
+
throw Error();
|
|
75
|
+
const overdue = segment(r.overdue, "overdue"), today = segment(r.today, "today"), undated = titles(r.undated, 3), next = titles(r.next_seven_days, 1), total = count(r.total_stored), later = count(r.later_total);
|
|
76
|
+
if (overdue.total + today.total + undated.total + next.total + later > total)
|
|
77
|
+
throw Error();
|
|
78
|
+
return { timezone: "Asia/Shanghai", as_of: timestamp(r.as_of), item_limit: 20, total_stored: total,
|
|
79
|
+
overdue, today, undated, next_seven_days: next, later_total: later };
|
|
80
|
+
}
|
package/dist/schedule.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CliError } from "./errors.js";
|
|
2
|
+
import { validateCommandArguments } from "./input-validation.js";
|
|
3
|
+
import { ScheduleProblem, scheduleBody, scheduleFailure, scheduleID, scheduleInvalid, scheduleKey, scheduleQuery, scheduleReadResult, scheduleReceipt, scheduleRoot, scheduleUnknown, validScheduleRevision } from "./schedule-contract.js";
|
|
4
|
+
export async function executeSchedule(args, flags, ctx) {
|
|
5
|
+
try {
|
|
6
|
+
validateCommandArguments(args, flags);
|
|
7
|
+
const operation = args[1] === "add" ? "create" : args[1] === "done" ? "complete" : args[1];
|
|
8
|
+
let path, options = { method: "GET", token: ctx.token }, key;
|
|
9
|
+
const detail = operation === "show";
|
|
10
|
+
if (operation === "list") {
|
|
11
|
+
const names = { view: "view", status: "status", "group-by": "group_by", project: "project_id", q: "q", "start-date": "start_date", "end-date": "end_date", limit: "limit", offset: "offset" };
|
|
12
|
+
const input = {};
|
|
13
|
+
for (const [name, value] of Object.entries(flags)) {
|
|
14
|
+
if (!Object.hasOwn(names, name))
|
|
15
|
+
scheduleInvalid();
|
|
16
|
+
if (name === "limit" || name === "offset") {
|
|
17
|
+
if (typeof value !== "string" || !/^\d+$/.test(value))
|
|
18
|
+
scheduleInvalid();
|
|
19
|
+
input[names[name]] = Number(value);
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
input[names[name]] = value;
|
|
23
|
+
}
|
|
24
|
+
const query = new URLSearchParams(Object.entries(scheduleQuery(input)).map(([name, value]) => [name, String(value)])).toString();
|
|
25
|
+
path = `${scheduleRoot}/m1/schedule-items${query ? `?${query}` : ""}`;
|
|
26
|
+
}
|
|
27
|
+
else if (detail)
|
|
28
|
+
path = `${scheduleRoot}/m1/schedule-items/${scheduleID(args[2])}`;
|
|
29
|
+
else if (operation === "status") {
|
|
30
|
+
key = scheduleKey(flags["idempotency-key"]);
|
|
31
|
+
path = `${scheduleRoot}/m1/schedule-write-status`;
|
|
32
|
+
options.headers = { "Idempotency-Key": key };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (!["create", "update", "complete", "cancel", "undo"].includes(operation))
|
|
36
|
+
return scheduleInvalid();
|
|
37
|
+
key = scheduleKey(flags["idempotency-key"]);
|
|
38
|
+
const create = operation === "create";
|
|
39
|
+
const headers = { "Idempotency-Key": key };
|
|
40
|
+
if (!create) {
|
|
41
|
+
const revision = flags.revision;
|
|
42
|
+
if (typeof revision !== "string" || !/^[1-9]\d*$/.test(revision) || !validScheduleRevision(Number(revision)))
|
|
43
|
+
scheduleInvalid();
|
|
44
|
+
headers["If-Match"] = `"${revision}"`;
|
|
45
|
+
}
|
|
46
|
+
if (typeof flags.input !== "string" || !flags.input.trim())
|
|
47
|
+
scheduleInvalid();
|
|
48
|
+
const body = scheduleBody(operation, await ctx.readInput(flags.input));
|
|
49
|
+
path = `${scheduleRoot}/m2/schedule-items${create ? "" : `/${scheduleID(args[2])}${operation === "update" ? "" : `/${operation}`}`}`;
|
|
50
|
+
options = { ...options, method: operation === "update" ? "PATCH" : "POST", body, headers };
|
|
51
|
+
}
|
|
52
|
+
let result;
|
|
53
|
+
try {
|
|
54
|
+
result = await ctx.api.request(path, options);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
if (key)
|
|
58
|
+
throw scheduleUnknown(key);
|
|
59
|
+
throw new ScheduleProblem("network_error", "日程读取未完成。", "检查当前执行环境的网络;不要反复重试或重新授权。", 6);
|
|
60
|
+
}
|
|
61
|
+
if (![200, 201].includes(result.status))
|
|
62
|
+
throw scheduleFailure(result.status, result.body, operation === "list" || detail ? ["read_workbench", "read_projects"] : operation === "status" ? [] : ["write_schedule"], key);
|
|
63
|
+
return { ...result, body: key ? scheduleReceipt(result.status, result.body, key) : scheduleReadResult(result.status, result.body, detail) };
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof ScheduleProblem)
|
|
67
|
+
throw new CliError(error.code, error.humanMessage, error.nextAction, error.exitCode, error.details);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { agentIdentity, shellWord } from "./agent-identity.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { lstat, open } from "node:fs/promises";
|
|
5
|
+
import { isAbsolute, join, parse, resolve, sep } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { asRecord, CliError } from "./errors.js";
|
|
8
|
+
// One release path allowlist, checked against the real package manifest.
|
|
9
|
+
// Never use an arbitrary manifest entry as a filesystem installation target.
|
|
10
|
+
export const skillNames = Object.freeze(["caotang-context", "caotang-save-knowledge", "caotang-project-progress", "caotang-search", "caotang-workday", "caotang-first-connect", "caotang-schedule"]);
|
|
11
|
+
export function completeSkillInventory(names) {
|
|
12
|
+
return names.length === skillNames.length && new Set(names).size === skillNames.length && names.every(name => skillNames.includes(name));
|
|
13
|
+
}
|
|
14
|
+
const manifestLimit = 16 * 1024;
|
|
15
|
+
const contentLimit = 64 * 1024;
|
|
16
|
+
const invalid = () => new CliError("skill_assets_invalid", "本包技能资源未通过安全或格式检查。", "请使用完整、未修改的一间草堂命令行安装包,不要复制不明技能文件。", 7);
|
|
17
|
+
const sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino;
|
|
18
|
+
const unchanged = (a, b) => sameFile(a, b) && a.size === b.size && a.mtimeMs === b.mtimeMs && a.ctimeMs === b.ctimeMs;
|
|
19
|
+
async function addDirectory(directories, path) {
|
|
20
|
+
const stat = await lstat(path);
|
|
21
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
22
|
+
throw invalid();
|
|
23
|
+
directories.push({ path, stat });
|
|
24
|
+
}
|
|
25
|
+
async function checkDirectories(directories) {
|
|
26
|
+
for (const directory of directories) {
|
|
27
|
+
const current = await lstat(directory.path);
|
|
28
|
+
if (!current.isDirectory() || current.isSymbolicLink() || !sameFile(directory.stat, current))
|
|
29
|
+
throw invalid();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function checkRegular(stat, limit) {
|
|
33
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size < 1 || stat.size > limit)
|
|
34
|
+
throw invalid();
|
|
35
|
+
}
|
|
36
|
+
async function readAsset(path, limit, directories) {
|
|
37
|
+
await checkDirectories(directories);
|
|
38
|
+
const before = await lstat(path);
|
|
39
|
+
checkRegular(before, limit);
|
|
40
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
41
|
+
try {
|
|
42
|
+
const opened = await handle.stat();
|
|
43
|
+
checkRegular(opened, limit);
|
|
44
|
+
if (!unchanged(before, opened))
|
|
45
|
+
throw invalid();
|
|
46
|
+
// Bound the read even if a regular file grows after fstat; never wait on a FIFO.
|
|
47
|
+
const buffer = Buffer.alloc(limit + 1);
|
|
48
|
+
let length = 0;
|
|
49
|
+
while (length < buffer.length) {
|
|
50
|
+
const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length);
|
|
51
|
+
if (bytesRead === 0)
|
|
52
|
+
break;
|
|
53
|
+
length += bytesRead;
|
|
54
|
+
}
|
|
55
|
+
const after = await handle.stat();
|
|
56
|
+
const current = await lstat(path);
|
|
57
|
+
checkRegular(after, limit);
|
|
58
|
+
checkRegular(current, limit);
|
|
59
|
+
if (length > limit || length !== opened.size || !unchanged(opened, after) || !unchanged(after, current))
|
|
60
|
+
throw invalid();
|
|
61
|
+
await checkDirectories(directories);
|
|
62
|
+
const bytes = buffer.subarray(0, length);
|
|
63
|
+
const content = bytes.toString("utf8");
|
|
64
|
+
if (!Buffer.from(content, "utf8").equals(bytes))
|
|
65
|
+
throw invalid();
|
|
66
|
+
return content;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
await handle.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function metadata(content) {
|
|
73
|
+
const manifest = asRecord(JSON.parse(content));
|
|
74
|
+
if (!manifest || Object.keys(manifest).sort().join(",") !== "formatVersion,skills" || manifest.formatVersion !== 1
|
|
75
|
+
|| !Array.isArray(manifest.skills) || manifest.skills.length !== skillNames.length)
|
|
76
|
+
throw invalid();
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
return manifest.skills.map((value) => {
|
|
79
|
+
const item = asRecord(value);
|
|
80
|
+
if (!item || Object.keys(item).sort().join(",") !== "description,name" || typeof item.name !== "string"
|
|
81
|
+
|| !skillNames.some(name => name === item.name) || seen.has(item.name)
|
|
82
|
+
|| typeof item.description !== "string" || !item.description.trim() || item.description.trim() !== item.description
|
|
83
|
+
|| item.description.length > 1000 || /[\x00-\x1f\x7f]/.test(item.description))
|
|
84
|
+
throw invalid();
|
|
85
|
+
seen.add(item.name);
|
|
86
|
+
return { name: item.name, description: item.description };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export async function loadSkillAssets(packageRoot) {
|
|
90
|
+
try {
|
|
91
|
+
const requested = packageRoot ?? fileURLToPath(new URL("../", import.meta.url));
|
|
92
|
+
if (typeof requested !== "string" || !requested || !isAbsolute(requested) || requested.includes("\0"))
|
|
93
|
+
throw invalid();
|
|
94
|
+
const root = resolve(requested);
|
|
95
|
+
const directories = [];
|
|
96
|
+
let path = parse(root).root;
|
|
97
|
+
await addDirectory(directories, path);
|
|
98
|
+
for (const part of root.slice(path.length).split(sep).filter(Boolean)) {
|
|
99
|
+
path = join(path, part);
|
|
100
|
+
await addDirectory(directories, path);
|
|
101
|
+
}
|
|
102
|
+
const skills = join(root, "skills");
|
|
103
|
+
await addDirectory(directories, skills);
|
|
104
|
+
const entries = metadata(await readAsset(join(skills, "manifest.json"), manifestLimit, directories));
|
|
105
|
+
for (const name of skillNames)
|
|
106
|
+
await addDirectory(directories, join(skills, name));
|
|
107
|
+
const assets = [];
|
|
108
|
+
for (const item of entries) {
|
|
109
|
+
const content = await readAsset(join(skills, item.name, "SKILL.md"), contentLimit, directories);
|
|
110
|
+
// JSON strings are valid YAML double-quoted scalars; no general YAML parser is needed.
|
|
111
|
+
const prefix = `---\nname: ${item.name}\ndescription: ${JSON.stringify(item.description)}\n---\n`;
|
|
112
|
+
if (!content.startsWith(prefix) || !content.slice(prefix.length).trim())
|
|
113
|
+
throw invalid();
|
|
114
|
+
assets.push({ ...item, content, sha256: createHash("sha256").update(content, "utf8").digest("hex") });
|
|
115
|
+
}
|
|
116
|
+
await checkDirectories(directories);
|
|
117
|
+
return assets;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw invalid();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Bind only the files installed into one AI platform; the shared runtime bundle
|
|
124
|
+
// remains byte-for-byte independent of the selected identity.
|
|
125
|
+
export function bindSkillAgent(asset, name) {
|
|
126
|
+
if (name === undefined)
|
|
127
|
+
return asset;
|
|
128
|
+
const agent = agentIdentity(name), word = shellWord(agent.key);
|
|
129
|
+
const marker = asset.content.indexOf("\n---\n");
|
|
130
|
+
if (marker < 0)
|
|
131
|
+
throw invalid();
|
|
132
|
+
const prefix = asset.content.slice(0, marker + 5);
|
|
133
|
+
const body = asset.content.slice(marker + 5).replace(/\bcaotang(?=\s+(?:--|[a-z]))/g, `caotang --agent ${word}`);
|
|
134
|
+
const content = `${prefix}\n本技能使用的 AI 名字是 ${JSON.stringify(agent.key)}。每条草堂命令都必须保留 --agent,不能借用其它 AI 的连接。\n${body}`;
|
|
135
|
+
return { ...asset, content, sha256: createHash("sha256").update(content, "utf8").digest("hex") };
|
|
136
|
+
}
|