@kobinflow/canvas-agent 0.6.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/LICENSE +22 -0
- package/README.md +159 -0
- package/agent-instructions.md +13 -0
- package/dist/agent/claude.d.ts +3 -0
- package/dist/agent/claude.js +54 -0
- package/dist/agent/codex-client.d.ts +139 -0
- package/dist/agent/codex-client.js +937 -0
- package/dist/agent/codex-event-history.d.ts +21 -0
- package/dist/agent/codex-event-history.js +163 -0
- package/dist/agent/codex-history.d.ts +46 -0
- package/dist/agent/codex-history.js +822 -0
- package/dist/agent/codex-protocol.d.ts +335 -0
- package/dist/agent/codex-protocol.js +1 -0
- package/dist/agent/codex.d.ts +178 -0
- package/dist/agent/codex.js +509 -0
- package/dist/agent/message-metadata.d.ts +51 -0
- package/dist/agent/message-metadata.js +378 -0
- package/dist/agent/types.d.ts +45 -0
- package/dist/agent/types.js +1 -0
- package/dist/canvas/operations.d.ts +13 -0
- package/dist/canvas/operations.js +167 -0
- package/dist/canvas/schemas.d.ts +989 -0
- package/dist/canvas/schemas.js +155 -0
- package/dist/canvas/session.d.ts +163 -0
- package/dist/canvas/session.js +523 -0
- package/dist/canvas/tools.d.ts +214 -0
- package/dist/canvas/tools.js +27 -0
- package/dist/canvas/types.d.ts +34 -0
- package/dist/canvas/types.js +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/server/http.d.ts +2 -0
- package/dist/server/http.js +576 -0
- package/dist/server/mcp.d.ts +2 -0
- package/dist/server/mcp.js +27 -0
- package/dist/skills/store.d.ts +54 -0
- package/dist/skills/store.js +426 -0
- package/dist/utils/agent-runtime.d.ts +8 -0
- package/dist/utils/agent-runtime.js +33 -0
- package/dist/utils/date.d.ts +2 -0
- package/dist/utils/date.js +7 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.js +81 -0
- package/dist/utils/value.d.ts +5 -0
- package/dist/utils/value.js +8 -0
- package/dist/version-check.d.ts +2 -0
- package/dist/version-check.js +65 -0
- package/package.json +55 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CodexSupplementalHistory, CodexSupplementalHistoryItem, CodexSupplementalHistoryTurn } from "./codex-history.js";
|
|
2
|
+
export declare const CODEX_EVENT_HISTORY_FILE: string;
|
|
3
|
+
/** 保存 Codex 持久线程投影可能省略的实时完成事件。 */
|
|
4
|
+
export declare class CodexEventHistory {
|
|
5
|
+
private file;
|
|
6
|
+
private data?;
|
|
7
|
+
private queue;
|
|
8
|
+
constructor(file?: string);
|
|
9
|
+
/** 按 threadId、turnId 和 itemId 新增或更新一条补充事件。 */
|
|
10
|
+
record(entry: CodexSupplementalHistoryItem): Promise<void>;
|
|
11
|
+
/** 保存 turn 终态,使标准线程历史尚未物化时仍可恢复完整轮次。 */
|
|
12
|
+
recordTurn(entry: CodexSupplementalHistoryTurn): Promise<void>;
|
|
13
|
+
/** 按 item 开始顺序返回指定线程的补充事件。 */
|
|
14
|
+
readThread(threadId: string): Promise<CodexSupplementalHistory>;
|
|
15
|
+
/** 归档线程后删除其补充事件。 */
|
|
16
|
+
removeThread(threadId: string): Promise<void>;
|
|
17
|
+
private run;
|
|
18
|
+
private load;
|
|
19
|
+
private save;
|
|
20
|
+
}
|
|
21
|
+
export declare const codexEventHistory: CodexEventHistory;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CONFIG_DIR } from "../config.js";
|
|
4
|
+
const MAX_ITEMS = 5000;
|
|
5
|
+
const MAX_STRING_LENGTH = 100_000;
|
|
6
|
+
export const CODEX_EVENT_HISTORY_FILE = path.join(CONFIG_DIR, "codex-event-history.json");
|
|
7
|
+
/** 保存 Codex 持久线程投影可能省略的实时完成事件。 */
|
|
8
|
+
export class CodexEventHistory {
|
|
9
|
+
file;
|
|
10
|
+
data;
|
|
11
|
+
queue = Promise.resolve();
|
|
12
|
+
constructor(file = CODEX_EVENT_HISTORY_FILE) {
|
|
13
|
+
this.file = file;
|
|
14
|
+
}
|
|
15
|
+
/** 按 threadId、turnId 和 itemId 新增或更新一条补充事件。 */
|
|
16
|
+
record(entry) {
|
|
17
|
+
return this.run(async () => {
|
|
18
|
+
const data = await this.load();
|
|
19
|
+
const index = data.items.findIndex((item) => sameItem(item, entry));
|
|
20
|
+
const previous = index >= 0 ? data.items[index] : undefined;
|
|
21
|
+
const nextEntry = normalizeEntry({
|
|
22
|
+
...entry,
|
|
23
|
+
...(entry.sequence === undefined && previous?.sequence !== undefined ? { sequence: previous.sequence } : {}),
|
|
24
|
+
item: mergeRecord(previous?.item, entry.item),
|
|
25
|
+
});
|
|
26
|
+
const items = [...data.items];
|
|
27
|
+
if (index >= 0)
|
|
28
|
+
items[index] = nextEntry;
|
|
29
|
+
else
|
|
30
|
+
items.push(nextEntry);
|
|
31
|
+
const nextData = { ...data, items: items.slice(-MAX_ITEMS) };
|
|
32
|
+
await this.save(nextData);
|
|
33
|
+
this.data = nextData;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** 保存 turn 终态,使标准线程历史尚未物化时仍可恢复完整轮次。 */
|
|
37
|
+
recordTurn(entry) {
|
|
38
|
+
return this.run(async () => {
|
|
39
|
+
const data = await this.load();
|
|
40
|
+
const index = data.turns.findIndex((turn) => sameTurn(turn, entry));
|
|
41
|
+
const previous = index >= 0 ? data.turns[index] : undefined;
|
|
42
|
+
const nextEntry = normalizeTurn({ ...entry, turn: mergeRecord(previous?.turn, entry.turn) });
|
|
43
|
+
const turns = [...data.turns];
|
|
44
|
+
if (index >= 0)
|
|
45
|
+
turns[index] = nextEntry;
|
|
46
|
+
else
|
|
47
|
+
turns.push(nextEntry);
|
|
48
|
+
const nextData = { ...data, turns: turns.slice(-MAX_ITEMS) };
|
|
49
|
+
await this.save(nextData);
|
|
50
|
+
this.data = nextData;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** 按 item 开始顺序返回指定线程的补充事件。 */
|
|
54
|
+
readThread(threadId) {
|
|
55
|
+
return this.run(async () => {
|
|
56
|
+
const data = await this.load();
|
|
57
|
+
return {
|
|
58
|
+
items: data.items.filter((item) => item.threadId === threadId).sort(compareEntries).map(cloneEntry),
|
|
59
|
+
turns: data.turns.filter((turn) => turn.threadId === threadId).map(cloneTurn),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/** 归档线程后删除其补充事件。 */
|
|
64
|
+
removeThread(threadId) {
|
|
65
|
+
return this.run(async () => {
|
|
66
|
+
const data = await this.load();
|
|
67
|
+
const items = data.items.filter((item) => item.threadId !== threadId);
|
|
68
|
+
const turns = data.turns.filter((turn) => turn.threadId !== threadId);
|
|
69
|
+
if (items.length === data.items.length && turns.length === data.turns.length)
|
|
70
|
+
return;
|
|
71
|
+
const nextData = { version: 1, items, turns };
|
|
72
|
+
await this.save(nextData);
|
|
73
|
+
this.data = nextData;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
run(task) {
|
|
77
|
+
const result = this.queue.then(task, task);
|
|
78
|
+
this.queue = result.then(() => undefined, () => undefined);
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
async load() {
|
|
82
|
+
if (this.data)
|
|
83
|
+
return this.data;
|
|
84
|
+
try {
|
|
85
|
+
const value = JSON.parse(await fs.readFile(this.file, "utf8"));
|
|
86
|
+
this.data = value.version === 1 && Array.isArray(value.items) && Array.isArray(value.turns) ? { version: 1, items: value.items.map(normalizeEntry), turns: value.turns.map(normalizeTurn) } : emptyHistory();
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code !== "ENOENT" && !(error instanceof SyntaxError))
|
|
90
|
+
throw error;
|
|
91
|
+
this.data = emptyHistory();
|
|
92
|
+
}
|
|
93
|
+
return this.data;
|
|
94
|
+
}
|
|
95
|
+
async save(data) {
|
|
96
|
+
await fs.mkdir(path.dirname(this.file), { recursive: true });
|
|
97
|
+
const temporaryFile = `${this.file}.${process.pid}.${Date.now()}.tmp`;
|
|
98
|
+
try {
|
|
99
|
+
await fs.writeFile(temporaryFile, JSON.stringify(data, null, 2));
|
|
100
|
+
await fs.rename(temporaryFile, this.file);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await fs.unlink(temporaryFile).catch(() => undefined);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export const codexEventHistory = new CodexEventHistory();
|
|
108
|
+
function emptyHistory() {
|
|
109
|
+
return { version: 1, items: [], turns: [] };
|
|
110
|
+
}
|
|
111
|
+
function sameItem(left, right) {
|
|
112
|
+
return left.threadId === right.threadId && left.turnId === right.turnId && left.itemId === right.itemId;
|
|
113
|
+
}
|
|
114
|
+
function sameTurn(left, right) {
|
|
115
|
+
return left.threadId === right.threadId && left.turnId === right.turnId;
|
|
116
|
+
}
|
|
117
|
+
function cloneEntry(entry) {
|
|
118
|
+
return structuredClone(entry);
|
|
119
|
+
}
|
|
120
|
+
function cloneTurn(entry) {
|
|
121
|
+
return structuredClone(entry);
|
|
122
|
+
}
|
|
123
|
+
function compareEntries(left, right) {
|
|
124
|
+
if (left.sequence !== undefined && right.sequence !== undefined && left.sequence !== right.sequence)
|
|
125
|
+
return left.sequence - right.sequence;
|
|
126
|
+
if (left.sequence !== undefined)
|
|
127
|
+
return -1;
|
|
128
|
+
if (right.sequence !== undefined)
|
|
129
|
+
return 1;
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
function normalizeEntry(entry) {
|
|
133
|
+
return {
|
|
134
|
+
...entry,
|
|
135
|
+
...(entry.sequence === undefined ? {} : { sequence: entry.sequence }),
|
|
136
|
+
item: truncateRecord(entry.item),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function normalizeTurn(entry) {
|
|
140
|
+
return { ...entry, turn: truncateRecord({ ...entry.turn, id: entry.turnId }) };
|
|
141
|
+
}
|
|
142
|
+
function mergeRecord(previous, next) {
|
|
143
|
+
if (!previous)
|
|
144
|
+
return next;
|
|
145
|
+
const merged = { ...previous };
|
|
146
|
+
Object.entries(next).forEach(([key, value]) => {
|
|
147
|
+
if (value !== undefined)
|
|
148
|
+
merged[key] = value;
|
|
149
|
+
});
|
|
150
|
+
return merged;
|
|
151
|
+
}
|
|
152
|
+
function truncateRecord(value) {
|
|
153
|
+
return truncateValue(value);
|
|
154
|
+
}
|
|
155
|
+
function truncateValue(value) {
|
|
156
|
+
if (typeof value === "string")
|
|
157
|
+
return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH)}\n[输出已截断]` : value;
|
|
158
|
+
if (Array.isArray(value))
|
|
159
|
+
return value.map(truncateValue);
|
|
160
|
+
if (value && typeof value === "object")
|
|
161
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, truncateValue(item)]));
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { CodexPlanUpdate } from "./codex-protocol.js";
|
|
2
|
+
type AgentHistoryMessage = {
|
|
3
|
+
id: string;
|
|
4
|
+
itemId: string;
|
|
5
|
+
threadId: string;
|
|
6
|
+
turnId: string;
|
|
7
|
+
role: "user" | "assistant" | "tool" | "error";
|
|
8
|
+
title?: string;
|
|
9
|
+
text: string;
|
|
10
|
+
detail?: unknown;
|
|
11
|
+
activityItems?: Record<string, string>;
|
|
12
|
+
};
|
|
13
|
+
export type CodexSupplementalHistoryItem = {
|
|
14
|
+
threadId: string;
|
|
15
|
+
turnId: string;
|
|
16
|
+
itemId: string;
|
|
17
|
+
sequence?: number;
|
|
18
|
+
item: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
export type CodexSupplementalHistoryTurn = {
|
|
21
|
+
threadId: string;
|
|
22
|
+
turnId: string;
|
|
23
|
+
turn: Record<string, unknown>;
|
|
24
|
+
};
|
|
25
|
+
export type CodexSupplementalHistory = {
|
|
26
|
+
items: CodexSupplementalHistoryItem[];
|
|
27
|
+
turns: CodexSupplementalHistoryTurn[];
|
|
28
|
+
};
|
|
29
|
+
/** 将 Codex 线程转换为列表展示所需的摘要。 */
|
|
30
|
+
export declare function summarizeCodexThread(thread: unknown): {
|
|
31
|
+
id: string;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
preview: string;
|
|
34
|
+
name: string | null;
|
|
35
|
+
cwd: string;
|
|
36
|
+
status: string;
|
|
37
|
+
source: unknown;
|
|
38
|
+
threadSource: unknown;
|
|
39
|
+
createdAt: number;
|
|
40
|
+
updatedAt: number;
|
|
41
|
+
};
|
|
42
|
+
/** 返回已经终结、可由持久化历史作为权威内容的 turn。 */
|
|
43
|
+
export declare function settledTurnIds(thread: unknown, supplementalHistory?: CodexSupplementalHistory): string[];
|
|
44
|
+
/** 将 Codex turn items 转换为网页聊天历史。 */
|
|
45
|
+
export declare function threadMessages(thread: unknown, planUpdates?: CodexPlanUpdate[], supplementalHistory?: CodexSupplementalHistory): AgentHistoryMessage[];
|
|
46
|
+
export {};
|