@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
package/dist/commands.js
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { verifiedFileFailure } from "./create-file-failure.js";
|
|
2
|
+
import { CliError, expectOK, expectProjectTextOK, expectKnowledgeChangeNotBlocked, checkApplicationReceipt, asRecord, unverifiedSwitchError, directPublicWriteError } from "./errors.js";
|
|
3
|
+
import { executeUpload } from "./upload.js";
|
|
4
|
+
import { readContext } from "./context.js";
|
|
5
|
+
import { requestPendingAction } from "./pending-requests.js";
|
|
6
|
+
import { commandAcceptsSwitch, expectCapabilityResult } from "./capability-result.js";
|
|
7
|
+
import { dateFlags, validateDateFlags, expectQueryOK, isPagedQueryPath, isProjectIDParameterError } from "./query-validation.js";
|
|
8
|
+
import { expectSearchOK, validateSearch } from "./query-search.js";
|
|
9
|
+
import { expectDailyOK, validateDailyDates } from "./query-daily.js";
|
|
10
|
+
import { createText, createHTTPURL, validateCreateSources, isCreatePath, expectCreateOK, expectKnowledgeTextOK } from "./create-bundle.js";
|
|
11
|
+
import { withKnowledgeGrowth } from "./knowledge-growth.js";
|
|
12
|
+
import { readHabit } from "./habit-read.js";
|
|
13
|
+
import { citationOrigin, webCitations } from "./web-citations.js";
|
|
14
|
+
import { KNOWLEDGE_GROWTH_STEPS, knowledgeGrowthMetadata } from "./generated/knowledge-growth.js";
|
|
15
|
+
import { inputDetails, invalidInput, validateInput, validateCommandArguments } from "./input-validation.js";
|
|
16
|
+
import { executeSchedule } from "./schedule.js";
|
|
17
|
+
import { executeCalendar } from "./calendar.js";
|
|
18
|
+
const root = "/api/v1/agent";
|
|
19
|
+
// Go limits display text by Unicode code points, not UTF-16 code units.
|
|
20
|
+
const runeLength = (value) => [...value].length;
|
|
21
|
+
const text = (min = 0, max = Infinity) => value => typeof value === "string" && runeLength(value) >= min && runeLength(value) <= max;
|
|
22
|
+
const oneOf = (...values) => value => typeof value === "string" && values.includes(value);
|
|
23
|
+
const stageText = value => typeof value === "string" && value.trim().length > 0 && runeLength(value) <= 30;
|
|
24
|
+
const stages = value => Array.isArray(value) && value.length >= 1 && value.length <= 8 && value.every(stageText);
|
|
25
|
+
const url = value => {
|
|
26
|
+
if (typeof value !== "string")
|
|
27
|
+
return false;
|
|
28
|
+
try {
|
|
29
|
+
new URL(value);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const visibility = oneOf("private");
|
|
37
|
+
const projectStatus = oneOf("draft", "active", "paused", "archived", "hidden");
|
|
38
|
+
const sourceType = oneOf("knowledge_card", "project", "project_update", "checkin", "discussion_post", "discussion_reply", "collaboration_application", "external_url", "file");
|
|
39
|
+
const knowledgeSourceTypes = ["manual", "douyin", "xiaohongshu", "forum", "book", "publisher_preview", "expert", "agent", "web"];
|
|
40
|
+
const knowledgeSourceHelp = "source_type 可选:manual、douyin、xiaohongshu、forum、book、publisher_preview、expert、agent、web;请按实际来源选择,不补默认值。";
|
|
41
|
+
const knowledgeRequiredFields = ["title", "intent", "summary", "source_type"];
|
|
42
|
+
const schemas = {
|
|
43
|
+
project: {
|
|
44
|
+
fields: { title: createText(2, 80), summary: createText(0, 280), stage: createText(0, 30), stages: value => Array.isArray(value) && value.length <= 8 && value.every(createText(0, 30)), cognition_summary: createText(0, 1000), visibility, status: projectStatus },
|
|
45
|
+
required: ["title", "summary"]
|
|
46
|
+
},
|
|
47
|
+
projectUpdate: { fields: { title: text(2, 80), summary: text(0, 280) }, trimText: true },
|
|
48
|
+
progress: { fields: { body: text(2, 360), update_type: oneOf("progress", "knowledge_applied", "cognition", "stage"), visibility }, required: ["body"] },
|
|
49
|
+
cognition: { fields: { cognition_summary: text(0, 1000) }, required: ["cognition_summary"] },
|
|
50
|
+
stages: { fields: { stage: stageText, stages, status: oneOf("draft", "active", "paused") } },
|
|
51
|
+
knowledge: {
|
|
52
|
+
fields: { title: createText(2, 160), intent: createText(2, 500), summary: createText(1, 4000), source_type: oneOf(...knowledgeSourceTypes), source_label: createText(0, 300), topic: createText(0, 120), linked_project_id: text(), origin_url: createHTTPURL, visibility, publication_status: oneOf("draft", "published"), sources: value => validateCreateSources(value, schemas.source.fields) },
|
|
53
|
+
required: knowledgeRequiredFields
|
|
54
|
+
},
|
|
55
|
+
knowledgeUpdate: { fields: { title: text(2, 160), intent: text(2, 500), summary: text(1, 4000), topic: text(0, 120) } },
|
|
56
|
+
source: {
|
|
57
|
+
fields: { evidence_type: oneOf("source", "summary", "project_link", "application", "review", "template"), source_type: sourceType, source_id: text(), source_url: url, source_title: text(0, 300), excerpt: text(0, 2000), file_key: text(0, 500), summary: text(0, 1200) },
|
|
58
|
+
required: ["source_type"]
|
|
59
|
+
},
|
|
60
|
+
advance: { fields: { internalization_status: value => knowledgeGrowthMetadata(value) !== undefined, evidence_summary: text(0, 1200), source_type: sourceType, source_id: text() }, required: ["internalization_status"] }
|
|
61
|
+
};
|
|
62
|
+
function invalid() {
|
|
63
|
+
throw new CliError("invalid_arguments", "命令或参数无效。", "请检查命令帮助;写入JSON请使用 --input 文件路径 或 --input -。");
|
|
64
|
+
}
|
|
65
|
+
function allowedFlags(flags, allowed) {
|
|
66
|
+
if (Object.entries(flags).some(([name, value]) => value !== undefined && !allowed.includes(name)))
|
|
67
|
+
invalid();
|
|
68
|
+
}
|
|
69
|
+
function id(value) {
|
|
70
|
+
if (typeof value !== "string" || !value.trim() || value === "." || value === ".." || /[\x00-\x1f\x7f]/.test(value))
|
|
71
|
+
invalid();
|
|
72
|
+
try {
|
|
73
|
+
return encodeURIComponent(value);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return invalid();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const queryNames = { limit: "limit", offset: "offset", "core-only": "core_only", q: "q", project: "project_id", topic: "topic", status: "status", "publication-status": "publication_status", since: "since", filter: "filter", "start-date": "start_date", "end-date": "end_date", "date-field": "date_field" };
|
|
80
|
+
const knowledgeFilters = ["limit", "offset", "core-only", "q", "project", "topic", "status", "publication-status", ...dateFlags];
|
|
81
|
+
function validateKnowledgeStatus(flags) {
|
|
82
|
+
if (flags.status === undefined)
|
|
83
|
+
return;
|
|
84
|
+
if (!knowledgeGrowthMetadata(flags.status)) {
|
|
85
|
+
throw new CliError("invalid_arguments", "知识长势筛选值无效。", "--status 请使用 allowed_values 中的机器枚举,中文名称、说明和级数一并列出;不筛选时省略 --status,草稿筛选请用 --publication-status draft。", 2, { allowed_values: KNOWLEDGE_GROWTH_STEPS.map(step => ({ value: step.value, ...knowledgeGrowthMetadata(step.value) })) });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function query(path, flags, maxLimit) {
|
|
89
|
+
validateDateFlags(flags, path.endsWith("/updates") || path.endsWith("/checkins"));
|
|
90
|
+
const params = new URLSearchParams();
|
|
91
|
+
for (const [flag, name] of Object.entries(queryNames)) {
|
|
92
|
+
const value = flags[flag];
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
continue;
|
|
95
|
+
if (flag === "core-only") {
|
|
96
|
+
if (typeof value !== "boolean")
|
|
97
|
+
invalid();
|
|
98
|
+
params.set(name, String(value));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (flag === "limit" || flag === "offset") {
|
|
102
|
+
const minimum = flag === "limit" ? 1 : 0;
|
|
103
|
+
const maximum = flag === "limit" ? maxLimit : Number.MAX_SAFE_INTEGER;
|
|
104
|
+
if (typeof value !== "string" || !/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < minimum || Number(value) > maximum) {
|
|
105
|
+
const message = flag === "limit" ? "--limit 参数无效。" : "--offset 参数无效。";
|
|
106
|
+
const nextAction = flag === "offset" ? "--offset 必须是 0 至 9007199254740991 的整数。"
|
|
107
|
+
: maxLimit === 50 ? "--limit 必须是 1 至 50 的整数。" : "--limit 必须是 1 至 100 的整数。";
|
|
108
|
+
throw new CliError("invalid_arguments", message, nextAction);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (typeof value !== "string")
|
|
112
|
+
invalid();
|
|
113
|
+
if ((flag === "q" && !path.endsWith("/m1/search") && runeLength(value) > 200) || (flag === "since" && (!value || value.length > 4096)))
|
|
114
|
+
invalid();
|
|
115
|
+
if (flag === "publication-status" && !["published", "draft", "all"].includes(value))
|
|
116
|
+
invalid();
|
|
117
|
+
params.set(name, value);
|
|
118
|
+
}
|
|
119
|
+
const suffix = params.toString();
|
|
120
|
+
return suffix ? `${path}?${suffix}` : path;
|
|
121
|
+
}
|
|
122
|
+
async function readCommandInput(flags, ctx, command) {
|
|
123
|
+
if (flags.input === undefined)
|
|
124
|
+
throw invalidInput(command, { missing: ["--input"] });
|
|
125
|
+
if (typeof flags.input !== "string" || !flags.input.trim())
|
|
126
|
+
throw invalidInput(command, { invalid: ["--input"] });
|
|
127
|
+
try {
|
|
128
|
+
return await ctx.readInput(flags.input);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof CliError && error.code === "invalid_input") {
|
|
132
|
+
Object.assign(error.details, inputDetails(command, { invalid: ["$input"] }));
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function inputBody(flags, ctx, schema, command) {
|
|
139
|
+
const body = asRecord(await readCommandInput(flags, ctx, command));
|
|
140
|
+
if (body?.visibility === "public" && [schemas.knowledge, schemas.project, schemas.progress].includes(schema)) {
|
|
141
|
+
const error = directPublicWriteError(true);
|
|
142
|
+
Object.assign(error.details, inputDetails(command, { invalid: ["visibility"] }));
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
let normalized;
|
|
146
|
+
const sourceField = schema === schemas.source && body && schemas.source.fields.source_type(body.source_type)
|
|
147
|
+
? body.source_type === "external_url" ? "source_url" : body.source_type === "file" ? "file_key" : "source_id" : undefined;
|
|
148
|
+
try {
|
|
149
|
+
normalized = validateInput(body, sourceField ? { ...schema, required: [...schema.required ?? [], sourceField] } : schema, command);
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof CliError && schema === schemas.knowledge && Array.isArray(body?.sources)
|
|
153
|
+
&& body.sources.some(entry => { const source = asRecord(entry); return source && Object.keys(source).some(key => !Object.hasOwn(schemas.source.fields, key)); })) {
|
|
154
|
+
error.details.unknown_fields = ["<unknown_field>"];
|
|
155
|
+
}
|
|
156
|
+
if (error instanceof CliError && body && schema === schemas.knowledge) {
|
|
157
|
+
const missing = knowledgeRequiredFields.filter(field => !Object.hasOwn(body, field));
|
|
158
|
+
if (missing.length)
|
|
159
|
+
throw new CliError("invalid_arguments", `缺少必填字段:${missing.join("、")}。`, missing.includes("source_type") ? knowledgeSourceHelp : "请在 --input 的 JSON 中补齐 missing_fields 列出的所有字段;只提交本人确认的内容。", 2, error.details);
|
|
160
|
+
if (!schema.fields.source_type(body.source_type))
|
|
161
|
+
throw new CliError("invalid_arguments", "source_type 参数无效。", knowledgeSourceHelp, 2, error.details);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
// Creation knows the intended status; PATCH deliberately leaves the current
|
|
166
|
+
// stored status check on the server without adding a read-permission dependency.
|
|
167
|
+
if (schema === schemas.project && body?.status !== "draft" && !createText(1, 280)(body?.summary)) {
|
|
168
|
+
throw invalidInput(command, { invalid: ["summary"] });
|
|
169
|
+
}
|
|
170
|
+
if (sourceField && !(sourceField === "source_url" ? createHTTPURL(normalized[sourceField]) : createText(1, Infinity)(normalized[sourceField]))) {
|
|
171
|
+
throw invalidInput(command, { invalid: [sourceField] });
|
|
172
|
+
}
|
|
173
|
+
return normalized;
|
|
174
|
+
}
|
|
175
|
+
async function dispatch(p, flags, ctx) {
|
|
176
|
+
const request = async (path, options = {}) => {
|
|
177
|
+
const raw = await ctx.api.request(path, { ...options, token: ctx.token });
|
|
178
|
+
const growth = withKnowledgeGrowth(path, raw.body, raw.status);
|
|
179
|
+
const mapped = options.method === "GET" && raw.status === 200 ? webCitations(path, growth, citationOrigin(ctx.api.baseUrl)) : growth;
|
|
180
|
+
const result = mapped === raw.body ? raw : { ...raw, body: mapped };
|
|
181
|
+
if (options.method === "GET" && result.status === 400
|
|
182
|
+
&& isProjectIDParameterError(path.split("?")[0], result.body))
|
|
183
|
+
throw new CliError("invalid_arguments", "项目 ID 格式无效。", "请使用项目列表或详情返回的项目 UUID;progress list 的项目参数、knowledge --project 均需有效项目 ID。", 2, { http_status: 400 });
|
|
184
|
+
if (options.method === "POST" && path === `${root}/m2/knowledge-cards`) {
|
|
185
|
+
const failure = verifiedFileFailure(result.status, result.body, options.body, options.headers?.["Idempotency-Key"] !== undefined);
|
|
186
|
+
if (failure) {
|
|
187
|
+
const { code, message, ...details } = failure;
|
|
188
|
+
throw new CliError(code, message, "请先核对这次文件确认回执,不自动重试。", result.status === 503 ? 7 : 2, details);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (options.method === "POST" && path === `${root}/m2/projects` && result.status === 400
|
|
192
|
+
&& (result.body === "project payload is invalid" || result.body === "project payload is invalid\n")) {
|
|
193
|
+
throw new CliError("invalid_arguments", "服务端拒绝了项目创建参数。", "请按 requirements 核对项目字段;非草稿简介必填,但服务端未提供具体字段,不能断言只是哪一项错误。只提交本人确认的内容,不自动补简介、改状态或重试。", 2, { http_status: 400, ...inputDetails(p, { invalid: ["$input"] }) });
|
|
194
|
+
}
|
|
195
|
+
if (options.method === "PATCH" && /^\/api\/v1\/agent\/m2\/knowledge-cards\/[^/]+\/stage$/.test(path))
|
|
196
|
+
checkApplicationReceipt(result, asRecord(options.body)?.internalization_status);
|
|
197
|
+
if (options.method === "GET" && [`${root}/m1/projects`, `${root}/m1/knowledge`].includes(path.split("?")[0]) && result.status === 400 && (result.body === "invalid core_only" || result.body === "invalid core_only\n"))
|
|
198
|
+
throw new CliError("invalid_arguments", "核心筛选参数无效。", "只看核心请使用 --core-only,不筛选时省略。", 2, { http_status: 400 });
|
|
199
|
+
if (options.method === "PATCH" && /^\/api\/v1\/agent\/m2\/knowledge-cards\/[^/]+(?:\/stage)?$/.test(path))
|
|
200
|
+
expectKnowledgeChangeNotBlocked(result);
|
|
201
|
+
if (options.method === "POST" && isCreatePath(path))
|
|
202
|
+
return expectCreateOK(result, path.endsWith("/knowledge-cards") ? "knowledge" : "project", options.headers?.["Idempotency-Key"] !== undefined, Array.isArray(asRecord(options.body)?.sources) && asRecord(options.body).sources.length > 0);
|
|
203
|
+
if (options.method === "PATCH" && /^\/api\/v1\/agent\/m2\/knowledge-cards\/[^/]+$/.test(path))
|
|
204
|
+
return expectKnowledgeTextOK(result);
|
|
205
|
+
if (options.method === "GET" && path.split("?")[0] === `${root}/m1/activity/daily`)
|
|
206
|
+
return expectDailyOK(result);
|
|
207
|
+
if (options.method === "GET" && path.split("?")[0] === `${root}/m1/search`)
|
|
208
|
+
return expectSearchOK(result);
|
|
209
|
+
return options.method === "GET" && isPagedQueryPath(path.split("?")[0])
|
|
210
|
+
? expectQueryOK(result, path.split("?")[0].endsWith("/pending-actions") ? 100 : 50)
|
|
211
|
+
: expectOK(result, options.method === "GET" ? "read" : "write");
|
|
212
|
+
};
|
|
213
|
+
const read = async (path, allowed, maxLimit = 50, capability) => {
|
|
214
|
+
allowedFlags(flags, allowed);
|
|
215
|
+
const target = query(path, flags, maxLimit);
|
|
216
|
+
return capability ? expectCapabilityResult(await ctx.api.request(target, { method: "GET", token: ctx.token }), capability, "read") : request(target, { method: "GET" });
|
|
217
|
+
};
|
|
218
|
+
const write = async (path, method, schema, progress = false) => {
|
|
219
|
+
allowedFlags(flags, progress ? ["input", "idempotency-key"] : ["input"]);
|
|
220
|
+
const key = flags["idempotency-key"];
|
|
221
|
+
if (key !== undefined && (typeof key !== "string" || !/^[A-Za-z0-9._:-]{16,128}(?![\s\S])/.test(key)))
|
|
222
|
+
throw invalidInput(p, { invalid: ["--idempotency-key"] });
|
|
223
|
+
const body = await inputBody(flags, ctx, schema, p);
|
|
224
|
+
return request(path, { method, body, ...(key === undefined ? {} : { headers: { "Idempotency-Key": key } }) });
|
|
225
|
+
};
|
|
226
|
+
if (p[0] === "rules" && p.length === 1) {
|
|
227
|
+
allowedFlags(flags, []);
|
|
228
|
+
return readHabit(ctx, false);
|
|
229
|
+
}
|
|
230
|
+
if (p[0] === "context" && p.length === 1 && flags.start === true) {
|
|
231
|
+
allowedFlags(flags, ["start"]);
|
|
232
|
+
return readHabit(ctx, true);
|
|
233
|
+
}
|
|
234
|
+
if (p[0] === "context" && p.length === 1) {
|
|
235
|
+
allowedFlags(flags, ["limit", "since"]);
|
|
236
|
+
const briefingPath = query(`${root}/m1/briefing`, flags, 100);
|
|
237
|
+
return readContext(ctx, briefingPath);
|
|
238
|
+
}
|
|
239
|
+
if (p[0] === "notifications" && p.length === 1) {
|
|
240
|
+
if (flags.filter !== undefined && (typeof flags.filter !== "string" || !["all", "unread", "read", "agent", "official"].includes(flags.filter)))
|
|
241
|
+
invalid();
|
|
242
|
+
return read(`${root}/m3/notifications`, ["limit", "filter", "offset"], 50, "read_notifications");
|
|
243
|
+
}
|
|
244
|
+
if (p[0] === "drafts" && p.length === 1)
|
|
245
|
+
return read(`${root}/m3/drafts`, ["limit"], 50, "read_drafts");
|
|
246
|
+
if (p[0] === "request" && p.length === 2 && ["delete", "archive", "visibility"].includes(p[1])) {
|
|
247
|
+
allowedFlags(flags, ["input"]);
|
|
248
|
+
return requestPendingAction(p[1], await readCommandInput(flags, ctx, p), ctx);
|
|
249
|
+
}
|
|
250
|
+
if (p[0] === "projects" && p.length === 1)
|
|
251
|
+
return read(`${root}/m1/projects`, ["limit", "offset", "status", "core-only", ...dateFlags]);
|
|
252
|
+
if (p[0] === "activity" && p.length === 1) {
|
|
253
|
+
allowedFlags(flags, ["start-date", "end-date", "limit", "offset"]);
|
|
254
|
+
validateDailyDates(flags);
|
|
255
|
+
return read(`${root}/m1/activity/daily`, ["start-date", "end-date", "limit", "offset"]);
|
|
256
|
+
}
|
|
257
|
+
if (p[0] === "checkins" && p.length === 1)
|
|
258
|
+
return read(`${root}/m1/checkins`, ["limit", "offset", ...dateFlags]);
|
|
259
|
+
if (p[0] === "progress" && p[1] === "list" && p.length === 3)
|
|
260
|
+
return read(`${root}/m1/projects/${id(p[2])}/updates`, ["limit", "offset", ...dateFlags]);
|
|
261
|
+
if (p[0] === "project" && p[1] === "update") {
|
|
262
|
+
if (p.length !== 3)
|
|
263
|
+
invalid();
|
|
264
|
+
allowedFlags(flags, ["input"]);
|
|
265
|
+
const path = `${root}/m2/projects/${id(p[2])}`;
|
|
266
|
+
const body = await inputBody(flags, ctx, schemas.projectUpdate, p);
|
|
267
|
+
return expectProjectTextOK(await ctx.api.request(path, { method: "PATCH", body, token: ctx.token }));
|
|
268
|
+
}
|
|
269
|
+
if (p[0] === "project" && p.length === 2) {
|
|
270
|
+
if (p[1] === "create")
|
|
271
|
+
return write(`${root}/m2/projects`, "POST", schemas.project, true);
|
|
272
|
+
return read(`${root}/m1/projects/${id(p[1])}`, []);
|
|
273
|
+
}
|
|
274
|
+
if (p[0] === "knowledge") {
|
|
275
|
+
if (p.length === 1) {
|
|
276
|
+
validateKnowledgeStatus(flags);
|
|
277
|
+
return read(`${root}/m1/knowledge`, knowledgeFilters);
|
|
278
|
+
}
|
|
279
|
+
if (p[1] === "show" && p.length === 3)
|
|
280
|
+
return read(`${root}/m1/knowledge/${id(p[2])}`, []);
|
|
281
|
+
if (p[1] === "add" && p.length === 2)
|
|
282
|
+
return write(`${root}/m2/knowledge-cards`, "POST", schemas.knowledge, true);
|
|
283
|
+
if (p[1] === "update" && p.length === 3)
|
|
284
|
+
return write(`${root}/m2/knowledge-cards/${id(p[2])}`, "PATCH", schemas.knowledgeUpdate);
|
|
285
|
+
}
|
|
286
|
+
if (p[0] === "search" && (p.length === 1 || p.length === 2)) {
|
|
287
|
+
allowedFlags(flags, knowledgeFilters.filter(flag => flag !== "q"));
|
|
288
|
+
validateKnowledgeStatus(flags);
|
|
289
|
+
validateSearch(p[1], flags);
|
|
290
|
+
return request(query(`${root}/m1/search`, { ...flags, q: p[1] }, 50), { method: "GET" });
|
|
291
|
+
}
|
|
292
|
+
if (p[0] === "pending" && p.length === 1) {
|
|
293
|
+
if (flags.status !== undefined && !["", "all", "pending", "confirmed", "rejected", "expired"].includes(String(flags.status)))
|
|
294
|
+
invalid();
|
|
295
|
+
return read(`${root}/pending-actions`, ["limit", "offset", "status"], 100);
|
|
296
|
+
}
|
|
297
|
+
if (p[0] === "progress" && p[1] === "add" && p.length === 3)
|
|
298
|
+
return write(`${root}/m2/projects/${id(p[2])}/updates`, "POST", schemas.progress, true);
|
|
299
|
+
if (p[0] === "cognition" && p[1] === "set" && p.length === 3)
|
|
300
|
+
return write(`${root}/m2/projects/${id(p[2])}/cognition`, "PATCH", schemas.cognition);
|
|
301
|
+
if (p[0] === "stage" && p[1] === "set" && p.length === 3)
|
|
302
|
+
return write(`${root}/m2/projects/${id(p[2])}/stages`, "PATCH", schemas.stages);
|
|
303
|
+
if (p[0] === "source" && p[1] === "add" && p.length === 3)
|
|
304
|
+
return write(`${root}/m2/knowledge-cards/${id(p[2])}/sources`, "POST", schemas.source);
|
|
305
|
+
if (p[0] === "advance" && p.length === 2)
|
|
306
|
+
return write(`${root}/m2/knowledge-cards/${id(p[1])}/stage`, "PATCH", schemas.advance);
|
|
307
|
+
if (p[0] === "link" && p.length === 3) {
|
|
308
|
+
allowedFlags(flags, ["core"]);
|
|
309
|
+
if (flags.core !== undefined && typeof flags.core !== "boolean")
|
|
310
|
+
invalid();
|
|
311
|
+
const path = `${root}/m2/knowledge-cards/${id(p[1])}/project-link`;
|
|
312
|
+
id(p[2]);
|
|
313
|
+
const result = await request(path, { method: "POST", body: { project_id: p[2], ...(flags.core === undefined ? {} : { is_core: flags.core }) } });
|
|
314
|
+
const body = asRecord(result.body);
|
|
315
|
+
return flags.core === true && body ? { ...result, body: { ...body, core_note: "此参数仅用于关联,不会设置项目或知识的核心标记。" } } : result;
|
|
316
|
+
}
|
|
317
|
+
if (p[0] === "unlink" && p.length === 3) {
|
|
318
|
+
allowedFlags(flags, []);
|
|
319
|
+
return request(`${root}/m2/knowledge-cards/${id(p[1])}/project-link/${id(p[2])}`, { method: "DELETE" });
|
|
320
|
+
}
|
|
321
|
+
if (p[0] === "upload" && p.length === 2) {
|
|
322
|
+
allowedFlags(flags, ["card", "source-title", "summary"]);
|
|
323
|
+
return executeUpload(p[1], flags, ctx);
|
|
324
|
+
}
|
|
325
|
+
return invalid();
|
|
326
|
+
}
|
|
327
|
+
export async function executeCommand(positionals, flags, ctx) {
|
|
328
|
+
if (positionals[0] === "schedule")
|
|
329
|
+
return executeSchedule(positionals, flags, ctx);
|
|
330
|
+
if (["calendar", "timeline", "daily-summary"].includes(positionals[0]))
|
|
331
|
+
return executeCalendar(positionals, flags, ctx);
|
|
332
|
+
try {
|
|
333
|
+
validateCommandArguments(positionals, flags);
|
|
334
|
+
const result = await dispatch(positionals, flags, ctx);
|
|
335
|
+
if (positionals[0] !== "context")
|
|
336
|
+
return result;
|
|
337
|
+
const body = asRecord(result.body);
|
|
338
|
+
if (!body)
|
|
339
|
+
return result;
|
|
340
|
+
const path = "/api/v1/agent/m1/workbench";
|
|
341
|
+
const workbench = webCitations(path, withKnowledgeGrowth(path, body.workbench, result.status), citationOrigin(ctx.api.baseUrl));
|
|
342
|
+
return workbench === body.workbench ? result : { ...result, body: { ...body, workbench } };
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
if (error instanceof CliError && error.code === "invalid_arguments" && error.details.missing_fields === undefined
|
|
346
|
+
&& ["project", "progress", "cognition", "stage", "knowledge", "source", "advance", "link", "unlink", "upload", "request"].includes(positionals[0])
|
|
347
|
+
&& (flags.input !== undefined || ["link", "unlink", "upload"].includes(positionals[0]))) {
|
|
348
|
+
Object.assign(error.details, inputDetails(positionals, { invalid: ["$arguments"] }));
|
|
349
|
+
}
|
|
350
|
+
if (error instanceof CliError && error.code === "capability_disabled" && error.details.required_capability !== undefined && !commandAcceptsSwitch(positionals, error.details.required_capability))
|
|
351
|
+
throw unverifiedSwitchError();
|
|
352
|
+
if (error instanceof CliError)
|
|
353
|
+
throw error;
|
|
354
|
+
throw new CliError("command_failed", "未能完成命令。", "请先在网页核对实际结果,不要重复结果尚未确认的写入。", 7);
|
|
355
|
+
}
|
|
356
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CliError, validToken } from "./errors.js";
|
|
2
|
+
export const productionOrigin = "https://caotang.gtcai.cn";
|
|
3
|
+
export const legacyProductionOrigin = "https://opc.gtcai.cn";
|
|
4
|
+
export function resolveConfig(environment, env, homeDir) {
|
|
5
|
+
if (environment !== undefined && environment !== "production" && environment !== "test")
|
|
6
|
+
throw new CliError("invalid_environment", "环境只能是 production 或 test。", "默认使用生产环境;测试时指定 --env test。");
|
|
7
|
+
const selected = environment === "test" ? "test" : "production";
|
|
8
|
+
if (env.CAOTANG_CLI_DEV_TOKEN !== undefined)
|
|
9
|
+
throw new CliError("invalid_token_environment", "通用令牌变量没有绑定目标环境。", "使用按环境区分的开发变量;不要把测试令牌传给生产环境。", 3);
|
|
10
|
+
const tokenOverride = selected === "test" ? env.CAOTANG_CLI_DEV_TEST_TOKEN : env.CAOTANG_CLI_DEV_PRODUCTION_TOKEN;
|
|
11
|
+
if (tokenOverride !== undefined && !validToken(tokenOverride))
|
|
12
|
+
throw new CliError("invalid_token", "环境中的授权令牌格式无效。", "检查环境变量,不要将令牌作为命令行参数或发到聊天中。", 3);
|
|
13
|
+
return { environment: selected, baseUrl: selected === "test" ? "https://test.gtcai.cn" : productionOrigin, homeDir, tokenOverride };
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Shared connection copy: skills and the later connection page use this table.
|
|
2
|
+
export const connectionCopy = Object.freeze({
|
|
3
|
+
connected: "接好了。",
|
|
4
|
+
first_empty: "接好了。刚才聊的,要存几条吗?",
|
|
5
|
+
first_existing: "接好了。要我看看你最近在忙什么吗?内容会发给当前这个 AI。",
|
|
6
|
+
repeated: "已经接好了,不用批准。",
|
|
7
|
+
read_only: "接好了,目前只能读取。改权限要在网页重新批准一条连接。",
|
|
8
|
+
read_only_existing: "接好了,目前只能读取。改权限要在网页重新批准一条连接。要我看看你最近在忙什么吗?内容会发给当前这个 AI。",
|
|
9
|
+
daily_next_step: "上次说下一步是 XX,接着来?",
|
|
10
|
+
expired: "确认码过期了,再运行一次拿新的。",
|
|
11
|
+
denied: "已取消,没有接入。",
|
|
12
|
+
uncertain: "没确认接上。去“Agent 管理”看有没有这条连接,有就告诉我。",
|
|
13
|
+
disconnected: "连接已断开,要用就重新接入。",
|
|
14
|
+
not_ready: "还没接好,请先按提示处理。",
|
|
15
|
+
});
|
|
16
|
+
export function approvalMessage(verificationUrl, userCode) {
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(verificationUrl);
|
|
19
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/app/device-auth"
|
|
20
|
+
|| verificationUrl !== `${url.origin}/app/device-auth`
|
|
21
|
+
|| !(url.protocol === "https:" || url.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname))
|
|
22
|
+
|| !/^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{4}-[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]{4}$/.test(userCode))
|
|
23
|
+
return connectionCopy.not_ready;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return connectionCopy.not_ready;
|
|
27
|
+
}
|
|
28
|
+
return `打开这个链接(${verificationUrl}),输入 ${userCode},点批准。`;
|
|
29
|
+
}
|
|
30
|
+
export function setupFailureMessage(code) {
|
|
31
|
+
if (code === "access_denied")
|
|
32
|
+
return connectionCopy.denied;
|
|
33
|
+
if (code === "expired_token")
|
|
34
|
+
return connectionCopy.expired;
|
|
35
|
+
if (code === "authorization_delivery_unknown" || code === "authorization_identity_changed" || code === "authorization_consumed")
|
|
36
|
+
return connectionCopy.uncertain;
|
|
37
|
+
if (code === "unauthorized")
|
|
38
|
+
return connectionCopy.disconnected;
|
|
39
|
+
return connectionCopy.not_ready;
|
|
40
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { apiError, asRecord, CliError, expectOK } from "./errors.js";
|
|
2
|
+
/** Only a permission denial may make a context section unavailable. */
|
|
3
|
+
export async function readContext(ctx, briefingPath) {
|
|
4
|
+
const parts = [
|
|
5
|
+
{ section: "workbench", capability: "read_workbench", path: "/api/v1/agent/m1/workbench" },
|
|
6
|
+
{ section: "briefing", capability: "read_notifications", path: briefingPath },
|
|
7
|
+
];
|
|
8
|
+
const available = { workbench: null, briefing: null };
|
|
9
|
+
const missing = [];
|
|
10
|
+
for (const part of parts) {
|
|
11
|
+
const result = await ctx.api.request(part.path, { method: "GET", token: ctx.token });
|
|
12
|
+
if (result.status === 403) {
|
|
13
|
+
const denial = apiError(result, "read");
|
|
14
|
+
if (denial.code === "legal_consent_required")
|
|
15
|
+
throw denial;
|
|
16
|
+
const mismatch = denial.details.required_capability !== undefined && denial.details.required_capability !== part.capability;
|
|
17
|
+
missing.push({ section: part.section, required_capability: part.capability, reason: mismatch ? "permission_denied" : denial.code });
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
expectOK(result, "read");
|
|
21
|
+
if (result.status !== 200 || !asRecord(result.body)) {
|
|
22
|
+
throw new CliError("invalid_response", "无法核对工作现状的返回内容。", "请稍后重新读取;不要把本次读取失败理解为没有资料。", 7);
|
|
23
|
+
}
|
|
24
|
+
available[part.section] = result.body;
|
|
25
|
+
}
|
|
26
|
+
if (missing.length === parts.length) {
|
|
27
|
+
throw new CliError("context_unavailable", "当前没有可读取的工作现状。", "请在网页检查“读取工作台”和“读取通知”开关及这台设备的权限。", 4, { http_status: 403, partial: false, missing_sections: missing });
|
|
28
|
+
}
|
|
29
|
+
return { status: 200, body: missing.length ? { ...available, partial: true, missing_sections: missing } : available };
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { asRecord, CliError, expectOK, singleSwitchMetadata, unverifiedSwitchError } from "./errors.js";
|
|
2
|
+
import { writeMissingSections } from "./write-permissions.js";
|
|
3
|
+
export const trimCreateText = (value) => value.replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "");
|
|
4
|
+
export const createText = (min, max) => (value) => typeof value === "string" && [...trimCreateText(value)].length >= min && [...trimCreateText(value)].length <= max;
|
|
5
|
+
export function createHTTPURL(value) {
|
|
6
|
+
if (typeof value !== "string" || [...trimCreateText(value)].length > 1000)
|
|
7
|
+
return false;
|
|
8
|
+
try {
|
|
9
|
+
const parsed = new URL(trimCreateText(value));
|
|
10
|
+
return ["http:", "https:"].includes(parsed.protocol) && parsed.hostname !== "";
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function validateCreateSources(value, fields) {
|
|
17
|
+
if (!Array.isArray(value))
|
|
18
|
+
return false;
|
|
19
|
+
if (value.length > 20)
|
|
20
|
+
throw new CliError("invalid_arguments", "sources 最多20项,本次未创建卡片。", "请明确选择本次要保存的来源;不会自动截断、拆批或去重。");
|
|
21
|
+
return value.every(entry => {
|
|
22
|
+
const source = asRecord(entry);
|
|
23
|
+
if (!source || typeof source.source_type !== "string")
|
|
24
|
+
return false;
|
|
25
|
+
const normalized = {};
|
|
26
|
+
for (const [key, raw] of Object.entries(source)) {
|
|
27
|
+
const item = typeof raw === "string" ? trimCreateText(raw) : raw;
|
|
28
|
+
if (!Object.hasOwn(fields, key) || !fields[key](item) || key === "source_url" && !createHTTPURL(item))
|
|
29
|
+
return false;
|
|
30
|
+
normalized[key] = item;
|
|
31
|
+
}
|
|
32
|
+
if (normalized.source_type === "external_url")
|
|
33
|
+
return typeof normalized.source_url === "string" && normalized.source_url !== "";
|
|
34
|
+
if (normalized.source_type === "file")
|
|
35
|
+
return typeof normalized.file_key === "string" && normalized.file_key !== "";
|
|
36
|
+
return typeof normalized.source_id === "string" && normalized.source_id !== "";
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function isCreatePath(path) { return path === "/api/v1/agent/m2/projects" || path === "/api/v1/agent/m2/knowledge-cards"; }
|
|
40
|
+
const receiptID = (value) => typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
41
|
+
const fixedReceiptMessage = "这是原回执,当前审核请到网页核对;本次未重新执行,不要自动重试。";
|
|
42
|
+
export function expectKnowledgeTextOK(result) {
|
|
43
|
+
const body = asRecord(result.body), code = body?.code ?? body?.error;
|
|
44
|
+
if (result.status === 400 && code === "knowledge_text_contains_url" && (body?.code === undefined || body.code === code) && (body?.error === undefined || body.error === code)) {
|
|
45
|
+
const fields = Array.isArray(body?.fields) ? [...new Set(body.fields.filter(value => value === "summary" || value === "intent"))] : [];
|
|
46
|
+
throw new CliError(code, "摘要和想解决的问题不能包含网址,本次未写入。", "请把链接放进sources或通过source add保存来源;不要把来源塞回正文,不会自动改写内容。", 2, { http_status: 400, ...(fields.length ? { fields } : {}) });
|
|
47
|
+
}
|
|
48
|
+
return expectOK(result);
|
|
49
|
+
}
|
|
50
|
+
export function expectCreateOK(result, kind, keyed, hasSources) {
|
|
51
|
+
const body = asRecord(result.body), code = body?.code ?? body?.error;
|
|
52
|
+
const aliasesAgree = (body?.code === undefined || body.code === code) && (body?.error === undefined || body.error === code);
|
|
53
|
+
// New create failures cannot activate a legacy error branch through one
|
|
54
|
+
// conflicting alias. Keep transport facts, not disputed business metadata.
|
|
55
|
+
if (!aliasesAgree && (result.status < 200 || result.status >= 300))
|
|
56
|
+
return expectOK({ ...result, body: undefined });
|
|
57
|
+
if (aliasesAgree) {
|
|
58
|
+
if (result.status === 403 && code === "capability_disabled" && body?.required_capability === "write_knowledge_sources"
|
|
59
|
+
&& (kind !== "knowledge" || !hasSources || !singleSwitchMetadata(body) || body.phase === "publication" || body.private_candidate_may_exist === true))
|
|
60
|
+
throw unverifiedSwitchError();
|
|
61
|
+
if (((result.status === 500 && code === "write_publication_failed") || (result.status === 404 && code === "write_target_not_found")) && body?.write_status === "failed" && body.phase === "publication" && body.private_candidate_may_exist === true && body.idempotency_replayed === false)
|
|
62
|
+
throw new CliError(code, "公开流程未完成;先前暂存的完整私有候选可能保留。", "请到网页核对,不要自动重试、换键或去掉幂等键重发。", result.status === 404 ? 2 : 7, { http_status: result.status, write_status: "failed", phase: "publication", private_candidate_may_exist: true, idempotency_replayed: false, ...(receiptID(body.request_id) ? { request_id: body.request_id } : {}) });
|
|
63
|
+
if (result.status === 403 && code === "capability_disabled" && kind === "knowledge" && hasSources && body?.required_capability === "write_knowledge_sources" && body.phase !== "publication" && body.private_candidate_may_exist !== true)
|
|
64
|
+
throw new CliError(code, "本次调用未新建卡片;写入知识来源所需的“管理知识来源”开关未开启。", "请本人到网页的 Agent 管理核对开关;不要重新授权或重试,不要把链接写回正文;如为幂等重放,请核对原请求结果。", 4, { http_status: 403, required_capability: "write_knowledge_sources" });
|
|
65
|
+
if (result.status === 400 && code === "sources_limit_exceeded")
|
|
66
|
+
throw new CliError(code, "sources最多20项,本次未创建卡片。", "不会截断来源;请明确选择本次要保存的来源后再提交。", 2, { http_status: 400 });
|
|
67
|
+
if (result.status === 400 && code === "invalid_sources")
|
|
68
|
+
throw new CliError(code, "sources必须是合法来源数组,本次未创建卡片。", "请检查来源字段与类型;只保存元数据,不会抓取URL或自动补协议。", 2, { http_status: 400 });
|
|
69
|
+
if (result.status === 409 && code === "duplicate_title") {
|
|
70
|
+
const missing = writeMissingSections(body?.missing_sections);
|
|
71
|
+
throw new CliError(code, body?.idempotency_replayed === true ? "原请求因同名冲突未创建;这是原回执,当前详情请网页核对。" : "本次请求因同名冲突未创建,当前详情请网页核对。", "不要自动换键重试;当前目标可能已经改名或删除,不能从原回执推断当前仍同名。", 2, { http_status: 409, created: false, ...(typeof body?.idempotency_replayed === "boolean" ? { idempotency_replayed: body.idempotency_replayed } : {}), ...(receiptID(body?.request_id) ? { request_id: body.request_id } : {}), ...(missing.length ? { missing_sections: missing } : {}) });
|
|
72
|
+
}
|
|
73
|
+
if (result.status === 422 && code === "content_blocked") {
|
|
74
|
+
const original = body?.idempotency_replayed === true && body.original_receipt === true && body.receipt_status === "failed";
|
|
75
|
+
throw new CliError(code, original ? "原请求发布被拦;这是原回执,不是当前审核结论。" : "本次发布被拦,不代表用户输入格式错误。", "完整私有候选可能已保留,请到网页核对;不要自动重试、换键或改成公开操作。", 2, { http_status: 422, ...(original ? { idempotency_replayed: true, original_receipt: true, receipt_status: "failed" } : {}), ...(receiptID(body?.request_id) ? { request_id: body.request_id } : {}), ...(receiptID(body?.moderation_case_id) ? { moderation_case_id: body.moderation_case_id } : {}) });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
expectKnowledgeTextOK(result);
|
|
79
|
+
if (keyed && result.status >= 200 && result.status < 300 && result.status !== 202 && (![200, 201].includes(result.status) || !body || !receiptID(body.request_id) || typeof body.idempotency_replayed !== "boolean" || body.write_status !== "succeeded"))
|
|
80
|
+
throw new CliError("idempotency_unverified", "响应可能已经写入,但未核到有效幂等收据,不能确认防重。", "请先到网页核对;不要自动重试、换键或去掉幂等键降级重发。", 7, { http_status: result.status });
|
|
81
|
+
if (!body)
|
|
82
|
+
return result;
|
|
83
|
+
if (!Object.hasOwn(body, "draft_notice") && !(body.original_receipt === true && body.idempotency_replayed === true && body.receipt_status === "moderation_pending" && result.status === 202))
|
|
84
|
+
return result;
|
|
85
|
+
const { draft_notice: notice, ...rest } = body;
|
|
86
|
+
const validNotice = (result.status === 200 || result.status === 201) && body.write_status === "succeeded" && body.idempotency_replayed === false && notice === `${kind}_draft`;
|
|
87
|
+
const normalized = validNotice ? { ...rest, draft_notice: notice } : rest;
|
|
88
|
+
if (body.original_receipt === true && body.idempotency_replayed === true && body.receipt_status === "moderation_pending" && result.status === 202) {
|
|
89
|
+
delete normalized.moderation_status;
|
|
90
|
+
normalized.message = fixedReceiptMessage;
|
|
91
|
+
}
|
|
92
|
+
return { ...result, body: normalized };
|
|
93
|
+
}
|
|
94
|
+
export function createDraftNotice(value) {
|
|
95
|
+
const body = asRecord(value);
|
|
96
|
+
if (body?.write_status !== "succeeded" || body.idempotency_replayed !== false)
|
|
97
|
+
return "";
|
|
98
|
+
if (body.draft_notice === "knowledge_draft")
|
|
99
|
+
return "已创建为知识草稿,不在默认published列表;可用knowledge --publication-status draft查询,需要读取知识和读取草稿权限。\n";
|
|
100
|
+
if (body.draft_notice === "project_draft")
|
|
101
|
+
return "已创建为项目草稿;可用projects --status draft查询,需要读取项目和读取草稿权限。项目默认列表在权限允许时可以包含草稿。\n";
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Interpreted only for an explicit keyed creation with an actual file source.
|
|
2
|
+
// A generic 503, a publication failure, or an uncertain commit cannot authorize a new key.
|
|
3
|
+
export function verifiedFileFailure(status, value, input, keyed) {
|
|
4
|
+
if (!keyed || !value || typeof value !== "object" || !input || typeof input !== "object")
|
|
5
|
+
return;
|
|
6
|
+
const b = value, sources = input.sources;
|
|
7
|
+
if (!Array.isArray(sources) || !sources.some(s => typeof s?.file_key === "string" && s.file_key.trim()))
|
|
8
|
+
return;
|
|
9
|
+
const codes = { 400: ["invalid_upload"], 401: ["upload_unauthorized"], 404: ["upload_not_found"],
|
|
10
|
+
409: ["storage_quota_exceeded", "upload_unverified", "upload_in_progress", "upload_conflict"], 503: ["storage_unavailable"] };
|
|
11
|
+
if (typeof b.code !== "string" || b.error !== b.code || !codes[status]?.includes(b.code) || b.write_status !== "failed"
|
|
12
|
+
|| b.retry_with_new_key_after_verification !== true || typeof b.idempotency_replayed !== "boolean"
|
|
13
|
+
|| typeof b.request_id !== "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(b.request_id)
|
|
14
|
+
|| b.private_candidate_may_exist === true || b.phase !== undefined)
|
|
15
|
+
return;
|
|
16
|
+
return { code: b.code, http_status: status, request_id: b.request_id, write_status: "failed", idempotency_replayed: b.idempotency_replayed,
|
|
17
|
+
retry_with_new_key_after_verification: true, message: "本次未创建卡片;请先核实或修复文件,确认后可使用新幂等键重新提交。不要自动重试。" };
|
|
18
|
+
}
|