@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,358 @@
|
|
|
1
|
+
import { scheduleUUID, scheduleKey, validScheduleDate, validScheduleTime, scheduleReadResult } from "./schedule-contract.js";
|
|
2
|
+
export const calendarRoot = "/api/v1/agent";
|
|
3
|
+
export const calendarKinds = ["project_created", "project_stage", "project_update", "checkin", "knowledge_created", "knowledge_growth", "schedule_completed", "schedule_cancelled", "daily_summary"];
|
|
4
|
+
const record = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
5
|
+
const scalar = (v, max = 5000) => typeof v === "string" && [...v].length <= max && !v.includes("\0") && ![...v].some(c => { const n = c.codePointAt(0); return n >= 0xd800 && n <= 0xdfff; });
|
|
6
|
+
const natural = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
7
|
+
const revision = (v) => natural(v) && v > 0;
|
|
8
|
+
const actors = ["user", "agent", "unknown"];
|
|
9
|
+
const trim = (v) => v.replace(/^\p{White_Space}+|\p{White_Space}+$/gu, "");
|
|
10
|
+
export class CalendarProblem extends Error {
|
|
11
|
+
code;
|
|
12
|
+
humanMessage;
|
|
13
|
+
nextAction;
|
|
14
|
+
exitCode;
|
|
15
|
+
details;
|
|
16
|
+
constructor(code, humanMessage, nextAction, exitCode = 2, details = {}) {
|
|
17
|
+
super(JSON.stringify({ code, message: humanMessage, next_action: nextAction, ...details }));
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.humanMessage = humanMessage;
|
|
20
|
+
this.nextAction = nextAction;
|
|
21
|
+
this.exitCode = exitCode;
|
|
22
|
+
this.details = details;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function calendarInvalid() {
|
|
26
|
+
throw new CalendarProblem("invalid_arguments", "日历、时间线或当天总结参数无效。", "请查看本命令帮助;日期使用真实 YYYY-MM-DD,含首尾最多一个日历年;只提交本人确认的一句话,保持原幂等键和当前版本。");
|
|
27
|
+
}
|
|
28
|
+
function invalidResponse() {
|
|
29
|
+
throw new CalendarProblem("invalid_response", "日历或时间线返回了无法核实的内容。", "请核对服务版本和实际读取范围,不把异常响应、权限缺口或未记录的历史解释为没有活动。", 7);
|
|
30
|
+
}
|
|
31
|
+
const leap = (y) => y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0);
|
|
32
|
+
const ordinal = (y, m, d) => { const p = y - 1; return 365 * p + Math.floor(p / 4) - Math.floor(p / 100) + Math.floor(p / 400) + [31, leap(y) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31].slice(0, m - 1).reduce((s, n) => s + n, 0) + d - 1; };
|
|
33
|
+
function validRange(start, end) {
|
|
34
|
+
const [sy, sm, sd] = start.split("-").map(Number), [ey, em, ed] = end.split("-").map(Number);
|
|
35
|
+
const nm = sm === 2 && sd === 29 && !leap(sy + 1) ? 3 : sm, nd = nm !== sm ? 1 : sd;
|
|
36
|
+
return start <= end && ordinal(ey, em, ed) + 1 <= ordinal(sy + 1, nm, nd);
|
|
37
|
+
}
|
|
38
|
+
export function calendarQuery(input) {
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [name, value] of Object.entries(input)) {
|
|
41
|
+
if (!["start_date", "end_date", "project_id", "actor_kind", "actor_name", "actor_source_id", "kind", "only_core", "limit", "cursor"].includes(name))
|
|
42
|
+
calendarInvalid();
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (name === "only_core") {
|
|
46
|
+
if (typeof value !== "boolean")
|
|
47
|
+
calendarInvalid();
|
|
48
|
+
}
|
|
49
|
+
else if (name === "limit") {
|
|
50
|
+
if (!natural(value) || value < 1 || value > 100)
|
|
51
|
+
calendarInvalid();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (!scalar(value, name === "cursor" ? 4096 : name === "actor_name" ? 80 : 100) || !value)
|
|
55
|
+
calendarInvalid();
|
|
56
|
+
if ((name === "start_date" || name === "end_date") && !validScheduleDate(value))
|
|
57
|
+
calendarInvalid();
|
|
58
|
+
if ((name === "project_id" || name === "actor_source_id") && !scheduleUUID(value))
|
|
59
|
+
calendarInvalid();
|
|
60
|
+
if (name === "actor_kind" && !actors.includes(value))
|
|
61
|
+
calendarInvalid();
|
|
62
|
+
if (name === "actor_name" && (trim(value) !== value || /[\p{Cc}\u2028\u2029]/u.test(value)))
|
|
63
|
+
calendarInvalid();
|
|
64
|
+
if (name === "kind" && !calendarKinds.includes(value))
|
|
65
|
+
calendarInvalid();
|
|
66
|
+
if (name === "cursor" && /\s/u.test(value))
|
|
67
|
+
calendarInvalid();
|
|
68
|
+
}
|
|
69
|
+
out[name] = value;
|
|
70
|
+
}
|
|
71
|
+
if ((input.start_date === undefined) !== (input.end_date === undefined))
|
|
72
|
+
calendarInvalid();
|
|
73
|
+
if (typeof input.start_date === "string" && typeof input.end_date === "string" && !validRange(input.start_date, input.end_date))
|
|
74
|
+
calendarInvalid();
|
|
75
|
+
if (input.actor_source_id !== undefined && input.actor_kind !== undefined && input.actor_kind !== "agent")
|
|
76
|
+
calendarInvalid();
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
export function dailySummaryText(value) {
|
|
80
|
+
if (!scalar(value, 1000) || /[\p{Cc}\u2028\u2029]/u.test(value))
|
|
81
|
+
return calendarInvalid();
|
|
82
|
+
const text = trim(value);
|
|
83
|
+
if (!text || [...text].length > 200)
|
|
84
|
+
return calendarInvalid();
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
87
|
+
// Format-only validation is also used for immutable historical read receipts.
|
|
88
|
+
export function dailySummaryBody(value) {
|
|
89
|
+
if (!record(value) || Object.keys(value).some(k => !["date", "summary", "confirmed"].includes(k)) || value.confirmed !== true || !validScheduleDate(value.date))
|
|
90
|
+
return calendarInvalid();
|
|
91
|
+
return { date: value.date, summary: dailySummaryText(value.summary), confirmed: true };
|
|
92
|
+
}
|
|
93
|
+
export function dailySummaryDateBounds(now = Date.now()) {
|
|
94
|
+
if (!Number.isFinite(now) || Number.isNaN(new Date(now).getTime()))
|
|
95
|
+
return calendarInvalid();
|
|
96
|
+
const parts = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(new Date(now));
|
|
97
|
+
const part = (type) => parts.find(value => value.type === type).value;
|
|
98
|
+
const max_date = part("year").padStart(4, "0") + "-" + part("month") + "-" + part("day");
|
|
99
|
+
const min_date = new Date(Date.parse(max_date + "T00:00:00Z") - 30 * 86400000).toISOString().slice(0, 10);
|
|
100
|
+
return { min_date, max_date, timezone: "Asia/Shanghai" };
|
|
101
|
+
}
|
|
102
|
+
export function dailySummaryWriteBody(value, now = Date.now()) {
|
|
103
|
+
const body = dailySummaryBody(value), bounds = dailySummaryDateBounds(now);
|
|
104
|
+
if (body.date < bounds.min_date || body.date > bounds.max_date)
|
|
105
|
+
throw new CalendarProblem("summary_date_out_of_range", "当天总结只接受北京时间今天及此前30天(含)。", "请在返回的日期范围内重新确认;不接受未来日期,保留原幂等键,不自动重试。", 2, bounds);
|
|
106
|
+
return body;
|
|
107
|
+
}
|
|
108
|
+
export function calendarKey(value) { try {
|
|
109
|
+
return scheduleKey(value);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return calendarInvalid();
|
|
113
|
+
} }
|
|
114
|
+
export function validSummaryRevision(value) { return natural(value); }
|
|
115
|
+
export function calendarUnknown(key, status) {
|
|
116
|
+
return new CalendarProblem("write_outcome_unknown", "当天总结写入结果未确认,不能视为已收工。", "保留原幂等键和连接,用 daily-summary status 或 get_daily_summary_write_status 核对;不要换键、自动重试或执行依赖写入。", 6, { write_status: "unknown", idempotency_key: key, ...(status === undefined ? {} : { http_status: status }) });
|
|
117
|
+
}
|
|
118
|
+
export function calendarFailure(status, body, key, operation = "read") {
|
|
119
|
+
if (key && (status === 202 || status >= 500))
|
|
120
|
+
return calendarUnknown(key, status);
|
|
121
|
+
const raw = record(body) ? body.code ?? body.error : undefined;
|
|
122
|
+
const known = { 400: ["summary_date_out_of_range", "invalid_calendar_query", "invalid_calendar_input", "invalid_daily_summary", "invalid_summary_input", "summary_confirmation_required", "invalid_idempotency_key", "invalid_summary_revision"], 401: ["not_authenticated"], 403: ["token_scope_insufficient", "capability_disabled", "second_confirmation_required", "legal_consent_required", "account_unavailable"], 404: ["summary_not_found", "daily_summary_not_found"], 409: ["calendar_cursor_stale", "summary_revision_conflict", "idempotency_conflict"], 415: ["invalid_content_type"], 429: ["rate_limited"], 503: ["calendar_unavailable", "summary_unavailable"] };
|
|
123
|
+
const code = record(body) && (body.code === undefined || body.code === raw) && (body.error === undefined || body.error === raw) && typeof raw === "string" && known[status]?.includes(raw) ? raw : "request_rejected";
|
|
124
|
+
const details = { http_status: status, ...(key ? { idempotency_key: key } : {}) };
|
|
125
|
+
if (code === "summary_date_out_of_range" && operation === "write") {
|
|
126
|
+
if (record(body) && body.timezone === "Asia/Shanghai" && validScheduleDate(body.min_date) && validScheduleDate(body.max_date)) {
|
|
127
|
+
const [ay, am, ad] = body.min_date.split("-").map(Number), [by, bm, bd] = body.max_date.split("-").map(Number);
|
|
128
|
+
if (ordinal(by, bm, bd) - ordinal(ay, am, ad) === 30)
|
|
129
|
+
Object.assign(details, { min_date: body.min_date, max_date: body.max_date, timezone: "Asia/Shanghai" });
|
|
130
|
+
}
|
|
131
|
+
return new CalendarProblem("summary_date_out_of_range", "当天总结只接受北京时间今天及此前30天(含)。", "请按可接受的日期范围重新确认;不接受未来日期,保留原幂等键,不自动重试。", 2, details);
|
|
132
|
+
}
|
|
133
|
+
const allowedCapabilities = operation === "write" ? ["write_project_update"] : operation === "status" ? [] : ["read_projects", "read_knowledge", "read_workbench", "read_drafts"];
|
|
134
|
+
if (code === "capability_disabled" && record(body) && allowedCapabilities.includes(body.required_capability) && (body.missing_sections === undefined || Array.isArray(body.missing_sections) && body.missing_sections.length === 0))
|
|
135
|
+
details.required_capability = body.required_capability;
|
|
136
|
+
const next = code === "calendar_cursor_stale" ? "数据或权限已变化;从第一页重新读取,不自动轮询或拼接新旧页面。" : status === 403 ? "请本人在网页核对账号开关、连接只读/读写范围或协议确认;不要重新授权或换入口绕过限制。" : status === 409 ? "先核对当前总结和版本;保留原幂等键,不自动重试或覆盖。" : "请核对命令输入、服务版本和实际状态,不把失败解释为没有活动,也不自动重试写入。";
|
|
137
|
+
return new CalendarProblem(code, code === "calendar_cursor_stale" ? "时间线游标已失效。" : status === 403 ? "当前操作权限不足。" : "日历或当天总结请求未完成。", next, status === 401 ? 3 : status === 403 ? 4 : status >= 500 ? 7 : 2, details);
|
|
138
|
+
}
|
|
139
|
+
function actor(value) {
|
|
140
|
+
if (typeof value.actor_kind !== "string" || !actors.includes(value.actor_kind) || !scalar(value.actor_name, 80) || !value.actor_name || /[\p{Cc}\u2028\u2029]/u.test(value.actor_name))
|
|
141
|
+
return invalidResponse();
|
|
142
|
+
const out = { actor_kind: value.actor_kind, actor_name: value.actor_name };
|
|
143
|
+
if (value.actor_source_id !== undefined) {
|
|
144
|
+
if (value.actor_kind !== "agent" || !scheduleUUID(value.actor_source_id))
|
|
145
|
+
invalidResponse();
|
|
146
|
+
out.actor_source_id = value.actor_source_id;
|
|
147
|
+
}
|
|
148
|
+
if (value.actor_kind === "unknown" && value.actor_name !== "未记录")
|
|
149
|
+
invalidResponse();
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
export function dailySummaryResult(value) {
|
|
153
|
+
if (!record(value) || !scheduleUUID(value.id) || !validScheduleDate(value.date) || !validScheduleTime(value.confirmed_at) || !revision(value.revision) || typeof value.modified !== "boolean")
|
|
154
|
+
return invalidResponse();
|
|
155
|
+
let body;
|
|
156
|
+
try {
|
|
157
|
+
body = dailySummaryBody({ date: value.date, summary: value.summary, confirmed: true });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return invalidResponse();
|
|
161
|
+
}
|
|
162
|
+
if (body.summary !== value.summary)
|
|
163
|
+
return invalidResponse();
|
|
164
|
+
return { id: value.id, date: body.date, summary: body.summary, confirmed_at: value.confirmed_at, revision: value.revision, ...actor(value), modified: value.modified };
|
|
165
|
+
}
|
|
166
|
+
const sections = { calendar_projects: "read_projects", calendar_knowledge: "read_knowledge", calendar_schedule: "read_workbench", daily_progress: "read_projects", daily_knowledge: "read_knowledge", project_drafts: "read_drafts", project_draft_checkins: "read_drafts", draft_knowledge: "read_drafts", knowledge_applied_updates: "read_knowledge", draft_knowledge_applied_updates: "read_drafts", daily_summary: "read_projects", daily_activity: "read_workbench", schedule_items: "read_workbench" };
|
|
167
|
+
function missingSections(value, receipt = false) {
|
|
168
|
+
if (!Array.isArray(value))
|
|
169
|
+
return invalidResponse();
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
return value.map(raw => { if (!record(raw) || typeof raw.section !== "string" || sections[raw.section] !== raw.required_capability || !["capability_disabled", ...(receipt ? ["read_failed"] : [])].includes(raw.reason) || seen.has(raw.section))
|
|
172
|
+
return invalidResponse(); seen.add(raw.section); return { section: raw.section, required_capability: raw.required_capability, reason: raw.reason }; });
|
|
173
|
+
}
|
|
174
|
+
function eventResult(value) {
|
|
175
|
+
if (!record(value) || typeof value.kind !== "string" || !calendarKinds.includes(value.kind) || !scalar(value.id, 200) || !/^[A-Za-z0-9:._-]+$/.test(value.id) || !validScheduleDate(value.date) || !validScheduleTime(value.occurred_at) || !scalar(value.snippet) || !scalar(value.title, 300) || typeof value.modified !== "boolean")
|
|
176
|
+
return invalidResponse();
|
|
177
|
+
const out = { kind: value.kind, id: value.id, date: value.date, occurred_at: value.occurred_at, ...actor(value), snippet: value.snippet, title: value.title, modified: value.modified };
|
|
178
|
+
for (const name of ["project_id", "knowledge_card_id"]) {
|
|
179
|
+
if (value[name] !== undefined) {
|
|
180
|
+
if (!scheduleUUID(value[name]))
|
|
181
|
+
invalidResponse();
|
|
182
|
+
out[name] = value[name];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (value.project_title !== undefined) {
|
|
186
|
+
if (!scalar(value.project_title, 300) || !out.project_id)
|
|
187
|
+
invalidResponse();
|
|
188
|
+
out.project_title = value.project_title;
|
|
189
|
+
}
|
|
190
|
+
for (const name of ["from_status", "to_status"]) {
|
|
191
|
+
const status = value[name];
|
|
192
|
+
if (status === undefined)
|
|
193
|
+
continue;
|
|
194
|
+
if (typeof status !== "string")
|
|
195
|
+
return invalidResponse();
|
|
196
|
+
if (value.kind === "knowledge_growth") {
|
|
197
|
+
if (!["captured", "summarized", "applied", "reviewed", "templated"].includes(status))
|
|
198
|
+
return invalidResponse();
|
|
199
|
+
}
|
|
200
|
+
else if (value.kind === "project_stage") {
|
|
201
|
+
// Project stages are user text, bounded by the existing 30-code-point contract.
|
|
202
|
+
if (!scalar(status, 30) || !status || trim(status) !== status || /[\p{Cc}\u2028\u2029]/u.test(status))
|
|
203
|
+
return invalidResponse();
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
if (status !== "")
|
|
207
|
+
return invalidResponse();
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
out[name] = status;
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
export function calendarReadResult(status, value, calendar) {
|
|
215
|
+
if (status !== 200 || !record(value) || value.timezone !== "Asia/Shanghai" || !validScheduleDate(value.start_date) || !validScheduleDate(value.end_date) || !validRange(value.start_date, value.end_date) || !Array.isArray(value.items) || !natural(value.total) || !natural(value.limit) || value.limit < 1 || value.limit > 100 || typeof value.has_more !== "boolean" || value.next_cursor !== undefined && !scalar(value.next_cursor, 4096) || typeof value.partial !== "boolean")
|
|
216
|
+
return invalidResponse();
|
|
217
|
+
const cursor = value.next_cursor ?? "";
|
|
218
|
+
if (value.has_more ? (!cursor || value.items.length !== value.limit) : cursor !== "")
|
|
219
|
+
invalidResponse();
|
|
220
|
+
const items = value.items.map(eventResult), ids = new Set();
|
|
221
|
+
if (items.length > value.limit || items.length > value.total)
|
|
222
|
+
invalidResponse();
|
|
223
|
+
let prior = Infinity;
|
|
224
|
+
for (const item of items) {
|
|
225
|
+
const k = `${item.kind}:${item.id}`;
|
|
226
|
+
if (ids.has(k) || String(item.date) < value.start_date || String(item.date) > value.end_date)
|
|
227
|
+
invalidResponse();
|
|
228
|
+
ids.add(k);
|
|
229
|
+
const time = Date.parse(item.occurred_at);
|
|
230
|
+
if (time > prior)
|
|
231
|
+
invalidResponse();
|
|
232
|
+
prior = time;
|
|
233
|
+
}
|
|
234
|
+
const missing = missingSections(value.missing_sections === undefined ? [] : value.missing_sections);
|
|
235
|
+
if (!value.partial && missing.length)
|
|
236
|
+
invalidResponse();
|
|
237
|
+
const out = { timezone: "Asia/Shanghai", start_date: value.start_date, end_date: value.end_date, items, total: value.total, limit: value.limit, has_more: value.has_more, next_cursor: cursor, partial: value.partial, missing_sections: missing };
|
|
238
|
+
if (Array.isArray(value.warnings)) {
|
|
239
|
+
const warnings = [...new Set(value.warnings.filter((v) => typeof v === "string" && ["historical_mirror_ambiguous", "historical_project_stage_not_recorded", "historical_actor_not_recorded", "historical_knowledge_growth_not_recorded", "actor_attribution_incomplete"].includes(v)))];
|
|
240
|
+
if (warnings.length)
|
|
241
|
+
out.warnings = warnings;
|
|
242
|
+
}
|
|
243
|
+
if (value.event_coverage !== undefined) {
|
|
244
|
+
if (!Array.isArray(value.event_coverage))
|
|
245
|
+
return invalidResponse();
|
|
246
|
+
const seen = new Set();
|
|
247
|
+
out.event_coverage = value.event_coverage.map(raw => { if (!record(raw) || typeof raw.stream !== "string" || !["project_stage", "actor", "knowledge_growth"].includes(raw.stream) || !validScheduleTime(raw.started_at) || seen.has(raw.stream))
|
|
248
|
+
return invalidResponse(); seen.add(raw.stream); return { stream: raw.stream, started_at: raw.started_at }; });
|
|
249
|
+
}
|
|
250
|
+
if (value.accumulation !== undefined || value.forgotten !== undefined) {
|
|
251
|
+
const a = value.accumulation, f = value.forgotten;
|
|
252
|
+
if (!record(a) || a.scope !== "filtered_period_events" || !validScheduleTime(a.as_of) || !natural(a.event_total) || a.event_total !== value.total || !natural(a.growth_total) || a.growth_total > a.event_total || !record(f) || f.scope !== "current_readable_assets" || !validScheduleTime(f.as_of))
|
|
253
|
+
return invalidResponse();
|
|
254
|
+
out.accumulation = { scope: a.scope, as_of: a.as_of, event_total: a.event_total, growth_total: a.growth_total, applied_cards: assetGroup(a.applied_cards, ["applied"]), reviewed_cards: assetGroup(a.reviewed_cards, ["reviewed"]), templated_cards: assetGroup(a.templated_cards, ["templated"]) };
|
|
255
|
+
out.forgotten = { scope: f.scope, as_of: f.as_of, projects: assetGroup(f.projects, ["draft", "active", "paused"]), knowledge: assetGroup(f.knowledge, ["captured"]) };
|
|
256
|
+
}
|
|
257
|
+
if (calendar || value.days !== undefined) {
|
|
258
|
+
if (!Array.isArray(value.days))
|
|
259
|
+
return invalidResponse();
|
|
260
|
+
const dates = new Set();
|
|
261
|
+
let dayTotal = 0;
|
|
262
|
+
out.days = value.days.map(raw => {
|
|
263
|
+
if (!record(raw) || !validScheduleDate(raw.date) || raw.date < value.start_date || raw.date > value.end_date || dates.has(raw.date) || !natural(raw.total) || !Array.isArray(raw.schedule_items) || !natural(raw.schedule_total) || typeof raw.schedule_has_more !== "boolean")
|
|
264
|
+
return invalidResponse();
|
|
265
|
+
dates.add(raw.date);
|
|
266
|
+
dayTotal += raw.total;
|
|
267
|
+
const day = { date: raw.date, total: raw.total };
|
|
268
|
+
for (const field of ["started_at", "finished_at"]) {
|
|
269
|
+
if (raw[field] !== null && !validScheduleTime(raw[field]))
|
|
270
|
+
invalidResponse();
|
|
271
|
+
day[field] = raw[field];
|
|
272
|
+
}
|
|
273
|
+
if (raw.started_actor_name !== null && (!scalar(raw.started_actor_name, 80) || !raw.started_actor_name))
|
|
274
|
+
invalidResponse();
|
|
275
|
+
if (raw.started_token_id !== null && !scheduleUUID(raw.started_token_id))
|
|
276
|
+
invalidResponse();
|
|
277
|
+
if (raw.started_at === null && (raw.started_actor_name !== null || raw.started_token_id !== null))
|
|
278
|
+
invalidResponse();
|
|
279
|
+
day.started_actor_name = raw.started_actor_name;
|
|
280
|
+
day.started_token_id = raw.started_token_id;
|
|
281
|
+
day.summary = raw.summary === null ? null : dailySummaryResult(raw.summary);
|
|
282
|
+
if (day.summary && (day.summary.date !== raw.date || raw.finished_at !== day.summary.confirmed_at))
|
|
283
|
+
invalidResponse();
|
|
284
|
+
// The same schedule projector used by existing schedule reads validates each entry.
|
|
285
|
+
let projected;
|
|
286
|
+
try {
|
|
287
|
+
projected = raw.schedule_items.map(item => scheduleReadResult(200, { timezone: "Asia/Shanghai", schedule_item: item }, true).schedule_item);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return invalidResponse();
|
|
291
|
+
}
|
|
292
|
+
if (projected.length > raw.schedule_total || raw.schedule_has_more !== (projected.length < raw.schedule_total))
|
|
293
|
+
invalidResponse();
|
|
294
|
+
day.schedule_items = projected;
|
|
295
|
+
day.schedule_total = raw.schedule_total;
|
|
296
|
+
day.schedule_has_more = raw.schedule_has_more;
|
|
297
|
+
return day;
|
|
298
|
+
});
|
|
299
|
+
if (dayTotal !== value.total)
|
|
300
|
+
invalidResponse();
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
}
|
|
304
|
+
function assetGroup(value, statuses) {
|
|
305
|
+
if (!record(value) || !Array.isArray(value.items) || value.items.length > 20 || !natural(value.total) || value.items.length > value.total || typeof value.has_more !== "boolean" || value.has_more !== (value.items.length < value.total))
|
|
306
|
+
return invalidResponse();
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
const items = value.items.map(raw => { if (!record(raw) || !scheduleUUID(raw.id) || seen.has(raw.id) || !scalar(raw.title, 300) || !raw.title || typeof raw.status !== "string" || !statuses.includes(raw.status))
|
|
309
|
+
return invalidResponse(); seen.add(raw.id); const out = { id: raw.id, title: raw.title, status: raw.status }; if (raw.project_id !== undefined) {
|
|
310
|
+
if (!scheduleUUID(raw.project_id))
|
|
311
|
+
return invalidResponse();
|
|
312
|
+
out.project_id = raw.project_id;
|
|
313
|
+
} if (raw.project_title !== undefined) {
|
|
314
|
+
if (!out.project_id || !scalar(raw.project_title, 300) || !raw.project_title)
|
|
315
|
+
return invalidResponse();
|
|
316
|
+
out.project_title = raw.project_title;
|
|
317
|
+
} return out; });
|
|
318
|
+
return { items, total: value.total, has_more: value.has_more };
|
|
319
|
+
}
|
|
320
|
+
export function dailySummaryReceipt(status, value, key) {
|
|
321
|
+
if (![200, 201].includes(status) || !record(value) || !scheduleUUID(value.id) || value.write_status !== "confirmed" || typeof value.replayed !== "boolean" || value.code !== undefined || value.error !== undefined || value.read_permission_missing !== undefined && typeof value.read_permission_missing !== "boolean" || value.read_unavailable !== undefined && typeof value.read_unavailable !== "boolean")
|
|
322
|
+
throw calendarUnknown(key, status);
|
|
323
|
+
const out = { id: value.id, write_status: "confirmed", replayed: value.replayed };
|
|
324
|
+
try {
|
|
325
|
+
const missing = missingSections(value.missing_sections === undefined ? [] : value.missing_sections, true);
|
|
326
|
+
if (value.read_permission_missing === true || value.read_unavailable === true || missing.length) {
|
|
327
|
+
const unavailable = value.read_unavailable === true;
|
|
328
|
+
if (value.read_permission_missing === true && unavailable || missing.length !== 1 || missing[0].section !== "daily_summary" || missing[0].reason !== (unavailable ? "read_failed" : "capability_disabled"))
|
|
329
|
+
throw calendarUnknown(key, status);
|
|
330
|
+
out[unavailable ? "read_unavailable" : "read_permission_missing"] = true;
|
|
331
|
+
out.missing_sections = missing;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
out.daily_summary = dailySummaryResult(value.daily_summary);
|
|
335
|
+
if (out.daily_summary.id !== value.id)
|
|
336
|
+
throw calendarUnknown(key, status);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
throw calendarUnknown(key, status);
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
export function isCalendarHeaderPath(path, method) { return method === "POST" && path === `${calendarRoot}/m2/daily-summary` || method === "GET" && path === `${calendarRoot}/m1/daily-summary-write-status`; }
|
|
345
|
+
export function calendarHeaders(path, method, value) {
|
|
346
|
+
if (!isCalendarHeaderPath(path, method) || !record(value) || Object.keys(value).some(k => !["Idempotency-Key", "If-Match"].includes(k)))
|
|
347
|
+
return calendarInvalid();
|
|
348
|
+
const out = { "Idempotency-Key": calendarKey(value["Idempotency-Key"]) };
|
|
349
|
+
if (method === "POST") {
|
|
350
|
+
const rev = value["If-Match"];
|
|
351
|
+
if (typeof rev !== "string" || !/^"(0|[1-9]\d*)"(?![\s\S])/.test(rev) || !validSummaryRevision(Number(rev.slice(1, -1))))
|
|
352
|
+
return calendarInvalid();
|
|
353
|
+
out["If-Match"] = rev;
|
|
354
|
+
}
|
|
355
|
+
else if (value["If-Match"] !== undefined)
|
|
356
|
+
return calendarInvalid();
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { dailySummaryDateBounds } from "./calendar-contract.js";
|
|
2
|
+
const key = "calendar-example-key-0001";
|
|
3
|
+
const readOptions = ["--start-date", "--end-date", "--project", "--actor-kind", "--actor-name", "--actor-source-id", "--kind", "--only-core", "--limit", "--cursor"];
|
|
4
|
+
const notes = "只读本人可见事件;默认最近7个北京时间自然日,含首尾最多一个日历年。按项目、本人/AI名称或来源ID、类型筛选;旧来源显示未记录,断开连接保留历史名称。同名AI可用来源ID区分。日历当天明细与时间线同源;总结只取每天最新一句,撤销的待办完成不显示。total是筛选后全部可读事件数,items只是当前页;partial/missing_sections不是没有记录。next_cursor不为空才翻下一页;数据或权限变化返回calendar_cursor_stale时从第一页重新读,不自动轮询或拼接新旧页面。";
|
|
5
|
+
export const calendarCommandHelp = {};
|
|
6
|
+
for (const command of ["calendar", "timeline"]) {
|
|
7
|
+
calendarCommandHelp[command] = { command, usage: `caotang ${command}`, description: command === "calendar" ? "按日读取开工、当天总结、事件及日程;未记录开工/未收工如实留空。" : "按时间倒序读取本人和各AI做过的事;接手另一个AI的活之前,先看它最近做了什么。", options: readOptions, fields: { "start-date": "选填,真实YYYY-MM-DD;与end-date同时给或同时省;默认最近7天。", "end-date": "选填,真实YYYY-MM-DD;与start-date同时给或同时省;默认今天。", project: "本人项目UUID,不接受none。", "actor-kind": "user、agent或unknown。", "actor-name": "确切来源名称,最多80个Unicode码点。", "actor-source-id": "可信来源连接UUID,配合agent筛选,不猜ID。", kind: "project_created、project_stage、project_update、checkin、knowledge_created、knowledge_growth、schedule_completed、schedule_cancelled或daily_summary。", "only-core": "true或false,省略为false。", limit: "1-100,默认50。", cursor: "上一页返回的不透明next_cursor,最多4096字符;与同一筛选和权限一起使用。" }, example: { command: `caotang ${command} --start-date 2026-11-09 --end-date 2026-11-09 --actor-name Codex` }, notes };
|
|
8
|
+
}
|
|
9
|
+
const writeNotes = "总结日期只接受北京时间今天及此前30个自然日(含),不接受未来;超范围返回summary_date_out_of_range和min_date/max_date。先向本人展示完整日期(北京时间)和当天核心一句,得到确认后才写confirmed:true;平台不能核实口头确认,也不生成或改写总结。summary去首尾空白后1-200个Unicode码点,不含换行/控制符。写归write_project_update,读归read_projects;只读连接不能写。一次保存成功记录收工时刻,同日修改只展示最新一句并标已修改,原键回放不推进收工。幂等键16-128位安全ASCII;revision=0仅首次创建,修改用读回revision。202、网络失败或畸形回执不是成功,保留原键和连接,用daily-summary status核对,不自动换键重试。N22精简回执只确认ID与成败,不代表读到了正文。";
|
|
10
|
+
calendarCommandHelp["daily-summary save"] = { command: "daily-summary save", usage: "caotang daily-summary save --input file|- --idempotency-key KEY --revision N", description: "保存本人已确认的当天核心一句,同时记录收工;同日修改使用当前版本。", options: ["--input", "--idempotency-key", "--revision"], fields: { date: "必填,真实YYYY-MM-DD;仅北京时间今天及此前30天(含),不接受未来。", summary: "必填,已确认的1-200码点单行原文。", confirmed: "必填且只能为true。", revision: "--revision N;首次0,修改为最近读回的正安全整数。" }, required_fields: ["date", "summary", "confirmed"], example: { command: `caotang daily-summary save --input - --idempotency-key ${key} --revision 0`, input: { date: dailySummaryDateBounds().max_date, summary: "本人已确认的当天核心一句", confirmed: true } }, notes: writeNotes };
|
|
11
|
+
calendarCommandHelp["daily-summary status"] = { command: "daily-summary status", usage: "caotang daily-summary status --idempotency-key KEY", description: "只读核对同一连接的原总结回执;原键只放header,不放query/body。", options: ["--idempotency-key"], example: { command: `caotang daily-summary status --idempotency-key ${key}` }, notes: writeNotes };
|
|
12
|
+
export const calendarCommandUsages = Object.values(calendarCommandHelp).map(v => v.usage.replace(/^caotang /, ""));
|
package/dist/calendar.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CliError } from "./errors.js";
|
|
2
|
+
import { validateCommandArguments } from "./input-validation.js";
|
|
3
|
+
import { CalendarProblem, calendarFailure, calendarInvalid, calendarKey, calendarQuery, calendarReadResult, calendarRoot, calendarUnknown, dailySummaryWriteBody, dailySummaryReceipt, validSummaryRevision } from "./calendar-contract.js";
|
|
4
|
+
export async function executeCalendar(args, flags, ctx) {
|
|
5
|
+
try {
|
|
6
|
+
validateCommandArguments(args, flags);
|
|
7
|
+
let path, key, options = { method: "GET", token: ctx.token };
|
|
8
|
+
if (args[0] === "calendar" || args[0] === "timeline") {
|
|
9
|
+
const names = { "start-date": "start_date", "end-date": "end_date", project: "project_id", "actor-kind": "actor_kind", "actor-name": "actor_name", "actor-source-id": "actor_source_id", kind: "kind", "only-core": "only_core", limit: "limit", cursor: "cursor" };
|
|
10
|
+
const input = {};
|
|
11
|
+
for (const [name, value] of Object.entries(flags)) {
|
|
12
|
+
if (!Object.hasOwn(names, name))
|
|
13
|
+
calendarInvalid();
|
|
14
|
+
if (name === "limit") {
|
|
15
|
+
if (typeof value !== "string" || !/^\d+$/.test(value))
|
|
16
|
+
calendarInvalid();
|
|
17
|
+
input[names[name]] = Number(value);
|
|
18
|
+
}
|
|
19
|
+
else if (name === "only-core") {
|
|
20
|
+
if (value !== true && value !== false && value !== "true" && value !== "false")
|
|
21
|
+
calendarInvalid();
|
|
22
|
+
input[names[name]] = value === true || value === "true";
|
|
23
|
+
}
|
|
24
|
+
else
|
|
25
|
+
input[names[name]] = value;
|
|
26
|
+
}
|
|
27
|
+
const query = new URLSearchParams(Object.entries(calendarQuery(input)).map(([k, v]) => [k, String(v)])).toString();
|
|
28
|
+
path = `${calendarRoot}/m1/${args[0]}${query ? `?${query}` : ""}`;
|
|
29
|
+
}
|
|
30
|
+
else if (args[0] === "daily-summary" && args[1] === "status") {
|
|
31
|
+
key = calendarKey(flags["idempotency-key"]);
|
|
32
|
+
path = `${calendarRoot}/m1/daily-summary-write-status`;
|
|
33
|
+
options.headers = { "Idempotency-Key": key };
|
|
34
|
+
}
|
|
35
|
+
else if (args[0] === "daily-summary" && args[1] === "save") {
|
|
36
|
+
key = calendarKey(flags["idempotency-key"]);
|
|
37
|
+
const rev = flags.revision;
|
|
38
|
+
if (typeof rev !== "string" || !/^(0|[1-9]\d*)$/.test(rev) || !validSummaryRevision(Number(rev)) || typeof flags.input !== "string" || !flags.input.trim())
|
|
39
|
+
calendarInvalid();
|
|
40
|
+
const body = dailySummaryWriteBody(await ctx.readInput(flags.input));
|
|
41
|
+
path = `${calendarRoot}/m2/daily-summary`;
|
|
42
|
+
options = { ...options, method: "POST", body, headers: { "Idempotency-Key": key, "If-Match": `"${rev}"` } };
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
return calendarInvalid();
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = await ctx.api.request(path, options);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
if (key)
|
|
52
|
+
throw calendarUnknown(key);
|
|
53
|
+
throw new CalendarProblem("network_error", "日历或时间线读取未完成。", "检查当前执行环境的网络;不要反复重试或重新授权。", 6);
|
|
54
|
+
}
|
|
55
|
+
if (![200, 201].includes(result.status))
|
|
56
|
+
throw calendarFailure(result.status, result.body, key, key ? (args[1] === "save" ? "write" : "status") : "read");
|
|
57
|
+
return { ...result, body: key ? dailySummaryReceipt(result.status, result.body, key) : calendarReadResult(result.status, result.body, args[0] === "calendar") };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof CalendarProblem)
|
|
61
|
+
throw new CliError(error.code, error.humanMessage, error.nextAction, error.exitCode, error.details);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { apiError, asRecord, CliError, expectOK, singleSwitchMetadata, unverifiedSwitchError } from "./errors.js";
|
|
2
|
+
const capabilityActions = {
|
|
3
|
+
read_notifications: "请在网页检查“读取通知”开关及这台设备的权限。",
|
|
4
|
+
read_drafts: "请在网页检查“读取草稿”开关及这台设备的权限。",
|
|
5
|
+
delete_or_archive: "请在网页检查“删除或归档申请”开关及这台设备的读写权限;申请仍需本人在网页确认。",
|
|
6
|
+
};
|
|
7
|
+
export function expectCapabilityResult(result, capability, requestKind) {
|
|
8
|
+
if (result.status === 403) {
|
|
9
|
+
const error = apiError(result, requestKind), body = asRecord(result.body);
|
|
10
|
+
if (error.code === "legal_consent_required" || error.code === "token_scope_insufficient" || error.code === "second_confirmation_required")
|
|
11
|
+
throw error;
|
|
12
|
+
if (body && !singleSwitchMetadata(body))
|
|
13
|
+
throw unverifiedSwitchError();
|
|
14
|
+
if (body && (Object.hasOwn(body, "required_capability") || (body.code !== undefined && body.error !== undefined && body.code !== body.error))) {
|
|
15
|
+
if (error.details.required_capability !== undefined && error.details.required_capability !== capability)
|
|
16
|
+
throw unverifiedSwitchError();
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
// Older servers returned bare 403. Offer the known operation's check as
|
|
20
|
+
// guidance, not a claim that this particular switch was observed closed.
|
|
21
|
+
const nextAction = capabilityActions[capability];
|
|
22
|
+
throw new CliError(error.code, "当前操作权限不足。", nextAction, error.exitCode, { ...error.details, required_capability: capability });
|
|
23
|
+
}
|
|
24
|
+
return expectOK(result, requestKind);
|
|
25
|
+
}
|
|
26
|
+
// A known switch name is still not evidence if unrelated to this operation.
|
|
27
|
+
export function commandAcceptsSwitch(args, capability) {
|
|
28
|
+
const [name, op] = args;
|
|
29
|
+
let eligible;
|
|
30
|
+
switch (name) {
|
|
31
|
+
case "knowledge":
|
|
32
|
+
eligible = op === "add" ? ["write_knowledge", "write_knowledge_sources"] : op === "update" ? ["write_knowledge"] : ["read_knowledge", "read_drafts"];
|
|
33
|
+
break;
|
|
34
|
+
case "projects":
|
|
35
|
+
eligible = ["read_projects", "read_drafts"];
|
|
36
|
+
break;
|
|
37
|
+
case "project":
|
|
38
|
+
eligible = op === "create" || op === "update" ? ["write_project"] : ["read_projects", "read_drafts"];
|
|
39
|
+
break;
|
|
40
|
+
case "search":
|
|
41
|
+
eligible = ["read_projects", "read_knowledge", "read_workbench"];
|
|
42
|
+
break;
|
|
43
|
+
case "activity":
|
|
44
|
+
eligible = ["read_projects", "read_knowledge"];
|
|
45
|
+
break;
|
|
46
|
+
case "progress":
|
|
47
|
+
eligible = op === "add" ? ["write_project_update"] : ["read_projects"];
|
|
48
|
+
break;
|
|
49
|
+
case "checkins":
|
|
50
|
+
eligible = ["read_projects"];
|
|
51
|
+
break;
|
|
52
|
+
case "context":
|
|
53
|
+
eligible = ["read_workbench"];
|
|
54
|
+
break;
|
|
55
|
+
case "source":
|
|
56
|
+
case "upload":
|
|
57
|
+
eligible = ["write_knowledge_sources"];
|
|
58
|
+
break;
|
|
59
|
+
case "link":
|
|
60
|
+
case "unlink":
|
|
61
|
+
eligible = ["write_knowledge"];
|
|
62
|
+
break;
|
|
63
|
+
case "advance":
|
|
64
|
+
eligible = ["write_knowledge_stage", "write_project_update"];
|
|
65
|
+
break;
|
|
66
|
+
case "cognition":
|
|
67
|
+
eligible = ["write_project_cognition"];
|
|
68
|
+
break;
|
|
69
|
+
case "stage":
|
|
70
|
+
eligible = ["write_project"];
|
|
71
|
+
break;
|
|
72
|
+
case "notifications":
|
|
73
|
+
case "pending":
|
|
74
|
+
eligible = ["read_notifications"];
|
|
75
|
+
break;
|
|
76
|
+
case "drafts":
|
|
77
|
+
eligible = ["read_drafts"];
|
|
78
|
+
break;
|
|
79
|
+
case "request":
|
|
80
|
+
eligible = ["delete_or_archive"];
|
|
81
|
+
break;
|
|
82
|
+
default: eligible = [];
|
|
83
|
+
}
|
|
84
|
+
return typeof capability === "string" && eligible.includes(capability);
|
|
85
|
+
}
|